{"record":{"id":"9e63b5f9bfeb0584","repo":"toeverything/AFFiNE","slug":"space-access-denied","errorCode":"space_access_denied","errorMessage":"You do not have permission to access Space ${spaceId}.","messagePattern":"You do not have permission to access Space (.+?)\\.","errorType":"exception","errorClass":"SpaceAccessDenied","httpStatus":403,"severity":"error","filePath":"packages/backend/server/src/core/permission/service.ts","lineNumber":125,"sourceCode":"    workspaceId: string;\n    action: PermissionWorkspaceAction;\n    allowLocal?: boolean;\n  }) {\n    const output = await this.workspacePermissions({\n      ...input,\n      actions: [input.action],\n    });\n    return output.decisions[0]?.allowed ?? false;\n  }\n\n  async assertWorkspace(input: {\n    userId?: string;\n    workspaceId: string;\n    action: PermissionWorkspaceAction;\n    allowLocal?: boolean;\n  }) {\n    if (!(await this.canWorkspace(input))) {\n      throw new SpaceAccessDenied({ spaceId: input.workspaceId });\n    }\n  }\n\n  async docPermissions(input: {\n    userId?: string;\n    workspaceId: string;\n    docId: string;\n    actions: PermissionDocAction[];\n    allowLocal?: boolean;\n  }) {\n    const output = await this.evaluateLoaded({\n      userId: input.userId,\n      workspaceId: input.workspaceId,\n      docs: [{ docId: input.docId, actions: input.actions }],\n      allowLocal: input.allowLocal,\n    });\n    const doc = output.docs[0];\n    return {","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/core/permission/service.ts#L107-L143","documentation":"Thrown by PermissionService.assertWorkspace when canWorkspace() returns false for the requested workspace action. It indicates the user (or anonymous caller) failed the permission rule evaluation for the given workspace-level action (e.g. Workspace.Read, Workspace.Blobs.Write). The error mirrors the user-friendly 'space_access_denied' code and carries the offending workspaceId as spaceId.","triggerScenarios":"Calling assertWorkspace({ userId, workspaceId, action }) where the user is not a member of the workspace, has been removed, has a role that does not grant the requested action, or where allowLocal rules reject access; also raised for anonymous users hitting a non-public workspace route.","commonSituations":"User was removed from a workspace but the client still holds stale tokens; cross-workspace doc/blob fetches with a wrong workspaceId; permission cache desync after role downgrade; integration tests that forget to seed workspaceUser records.","solutions":["Verify the user still has an active role on workspaceId via models.workspaceUser.getActive(workspaceId, userId).","Confirm the action string is one of the supported PermissionWorkspaceAction values and matches the granted role.","Check that the authenticated session/current user is correctly propagated to the service call (missing userId defaults to anonymous).","If access was recently revoked, force-refresh the permission context / clear the loader cache."],"exampleFix":"// before\nawait perms.assertWorkspace({ workspaceId, action: 'Workspace.Read' });\n\n// after\nconst allowed = await perms.canWorkspace({ userId, workspaceId, action: 'Workspace.Read' });\nif (!allowed) throw new SpaceAccessDenied({ spaceId: workspaceId });","handlingStrategy":"validation","validationCode":"const allowed = await perms.canWorkspace({ userId, workspaceId, action });\nif (!allowed) {\n  // surface a friendly 'request access' UI instead of throwing\n  return { denied: true, workspaceId, action };\n}","typeGuard":"function isWorkspaceAction(a: string): a is PermissionWorkspaceAction {\n  return [\n    'Workspace.Read','Workspace.Sync','Workspace.CreateDoc','Workspace.Delete',\n    'Workspace.TransferOwner','Workspace.Users.Manage','Workspace.Administrators.Manage',\n    'Workspace.Settings.Update','Workspace.Properties.Create','Workspace.Properties.Update',\n    'Workspace.Properties.Delete','Workspace.Blobs.Write','Workspace.Payment.Manage'\n  ].includes(a);\n}","tryCatchPattern":"try {\n  await perms.assertWorkspace({ userId, workspaceId, action });\n} catch (e) {\n  if (e instanceof SpaceAccessDenied) {\n    // redirect to 'no access' screen, do not crash the request handler\n    return res.status(403).render('no-access', { workspaceId });\n  }\n  throw e;\n}","preventionTips":["Always pass userId explicitly rather than relying on the anonymous default.","Cache canWorkspace results only for the lifetime of the request; clear on role-change events.","Audit new routes for missing assertWorkspace guards."],"tags":["permissions","authorization","workspace","nestjs"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}