From 60b2bc7369fadd9764329a034f21bae7e75f1351 Mon Sep 17 00:00:00 2001 From: yusufipk Date: Sat, 25 Jul 2026 16:58:17 +0700 Subject: [PATCH] fix(auth): keep workspace admin permissions on public projects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit checkProjectAccess skipped the workspace membership lookup whenever access was already granted another way — a PUBLIC project, or an existing project membership — and only forced it for intents other than 'view'. The workspace role does not just gate entry though; it feeds canEdit/isWorkspaceMember. So a workspace ADMIN who is not the project owner lost canEdit the moment a project was switched to public: the Add Version item on video cards, plus canManageTags/canResolveComments/canRequestApproval/canShareVideo on the video page, all disappeared, and the approvals endpoint returned 403. The underlying POST routes use intent 'manage' and would still have accepted the write, so the permission was there — only the UI was gone. Resolve the workspace role for every signed-in non-owner. Owners already pass every check on their own, so theirs is still only loaded when they mutate. --- lib/auth.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/auth.ts b/lib/auth.ts index 0233990..32915a7 100644 --- a/lib/auth.ts +++ b/lib/auth.ts @@ -281,9 +281,13 @@ export async function checkProjectAccess( const isProjectMember = !!projectMember; const isProjectAdmin = projectMember?.role === ProjectMemberRole.ADMIN; - const needsWorkspaceForAccess = !!userId && !isOwner && !isProjectMember && !isPublic; - const needsWorkspaceForActions = !!userId && !isOwner && intent !== 'view'; - const shouldLoadWorkspaceRole = needsWorkspaceForAccess || needsWorkspaceForActions; + // The workspace role decides `canEdit`/`isWorkspaceMember`, not just whether the viewer + // gets in at all, so it has to be resolved for every signed-in non-owner. Skipping it + // once access was already granted some other way (public project, or an existing project + // membership) silently downgraded workspace admins to read-only on `intent: 'view'` — + // the intent pages and GET routes use to decide which actions to render. + // Owners pass every check on their own; resolve their role only when they mutate. + const shouldLoadWorkspaceRole = !!userId && (!isOwner || intent !== 'view'); // Check workspace membership/role let workspaceRole: WorkspaceMemberRole | 'OWNER' | null = null;