mirror of
https://github.com/screentinker/screentinker.git
synced 2026-06-15 02:33:15 -06:00
fix(auth/me): broaden non-admin accessible_workspaces to include org_owner/org_admin paths
The non-admin branch of /me's accessible_workspaces query drove from workspace_members, so users with org_owner or org_admin on an organization but no direct workspace_members row were missing those workspaces from their /me response - and therefore from the switcher dropdown. Mirrors the access logic in accessibleWorkspaceIds() (lib/tenancy.js) while keeping the full-row SELECT shape /me needs. Verified end-to-end with switcher-test@local.test acting as org_owner of Acme Studios with no workspace_members row on Studio B - Studio B now appears in /me's accessible_workspaces with workspace_role: null, can_admin: true. Also updates the stale TODO comment in tenancy.js that flagged this exact gap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3294525f4c
commit
d2a3bdfd15
|
|
@ -142,10 +142,9 @@ function resolveTenancy(req, res, next) {
|
|||
// - direct workspace_members rows
|
||||
// - any workspace in an org where they are org_owner / org_admin
|
||||
// - platform_admin / superadmin: every workspace in the system
|
||||
// Used by socket.io rooms (Phase 2.3) to scope outbound broadcasts. Also a
|
||||
// candidate to broaden /me's accessible_workspaces query - currently /me only
|
||||
// returns direct workspace_members for non-admins, missing the org-admin
|
||||
// path. Future cleanup tracked in the handoff doc.
|
||||
// Used by socket.io rooms (Phase 2.3) to scope outbound broadcasts. /me's
|
||||
// accessible_workspaces query mirrors this access logic but selects full rows
|
||||
// rather than reusing this helper (different shape needs).
|
||||
function accessibleWorkspaceIds(userId, role) {
|
||||
if (!userId) return [];
|
||||
if (role === 'platform_admin' || role === 'superadmin') {
|
||||
|
|
|
|||
|
|
@ -295,11 +295,14 @@ function getMicrosoftProfile(accessToken) {
|
|||
router.get('/me', requireAuth, resolveTenancy, (req, res) => {
|
||||
// Platform admins see every workspace in the system (via the LEFT JOIN they
|
||||
// still get their own workspace_role for direct memberships; NULL elsewhere,
|
||||
// matching accessContext's actingAs semantics). Regular users see only
|
||||
// workspaces they have a direct workspace_members row in. Role is read from
|
||||
// the signed JWT (not user-supplied), so non-admins cannot reach the admin
|
||||
// branch. No cap on the admin list yet - revisit at 50+ workspaces when
|
||||
// dropdown UX without search starts to degrade.
|
||||
// matching accessContext's actingAs semantics). Regular users see every
|
||||
// workspace they can reach via either path: direct workspace_members row, OR
|
||||
// org_owner / org_admin on the parent organization. Mirrors the access
|
||||
// logic in accessibleWorkspaceIds() (lib/tenancy.js); kept as a separate
|
||||
// query rather than reusing it because /me needs full row shape, not just
|
||||
// IDs. Role is read from the signed JWT (not user-supplied), so non-admins
|
||||
// cannot reach the admin branch. No cap on the admin list yet - revisit at
|
||||
// 50+ workspaces when dropdown UX without search starts to degrade.
|
||||
//
|
||||
// Each accessible_workspaces entry also carries `can_admin: bool` so the
|
||||
// UI can render admin affordances (rename pencil etc.) only where the
|
||||
|
|
@ -325,11 +328,12 @@ router.get('/me', requireAuth, resolveTenancy, (req, res) => {
|
|||
SELECT w.id, w.name, w.organization_id, o.name AS organization_name,
|
||||
wm.role AS workspace_role, om.role AS org_role,
|
||||
(SELECT COUNT(*) FROM devices WHERE workspace_id = w.id) AS device_count
|
||||
FROM workspace_members wm
|
||||
JOIN workspaces w ON w.id = wm.workspace_id
|
||||
FROM workspaces w
|
||||
JOIN organizations o ON o.id = w.organization_id
|
||||
LEFT JOIN workspace_members wm ON wm.workspace_id = w.id AND wm.user_id = ?
|
||||
LEFT JOIN organization_members om ON om.organization_id = w.organization_id AND om.user_id = ?
|
||||
WHERE wm.user_id = ?
|
||||
WHERE wm.user_id IS NOT NULL
|
||||
OR (om.user_id IS NOT NULL AND om.role IN ('org_owner', 'org_admin'))
|
||||
ORDER BY o.name, w.name
|
||||
`).all(req.user.id, req.user.id);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue