toeverything/AFFiNE · error · SpaceAccessDenied

space_access_denied

space_access_denied

Error message

You do not have permission to access Space ${spaceId}.

What it means

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.

Source

Thrown at packages/backend/server/src/core/permission/service.ts:125

    workspaceId: string;
    action: PermissionWorkspaceAction;
    allowLocal?: boolean;
  }) {
    const output = await this.workspacePermissions({
      ...input,
      actions: [input.action],
    });
    return output.decisions[0]?.allowed ?? false;
  }

  async assertWorkspace(input: {
    userId?: string;
    workspaceId: string;
    action: PermissionWorkspaceAction;
    allowLocal?: boolean;
  }) {
    if (!(await this.canWorkspace(input))) {
      throw new SpaceAccessDenied({ spaceId: input.workspaceId });
    }
  }

  async docPermissions(input: {
    userId?: string;
    workspaceId: string;
    docId: string;
    actions: PermissionDocAction[];
    allowLocal?: boolean;
  }) {
    const output = await this.evaluateLoaded({
      userId: input.userId,
      workspaceId: input.workspaceId,
      docs: [{ docId: input.docId, actions: input.actions }],
      allowLocal: input.allowLocal,
    });
    const doc = output.docs[0];
    return {

View on GitHub (pinned to 26c515e050)

Solutions

  1. Verify the user still has an active role on workspaceId via models.workspaceUser.getActive(workspaceId, userId).
  2. Confirm the action string is one of the supported PermissionWorkspaceAction values and matches the granted role.
  3. Check that the authenticated session/current user is correctly propagated to the service call (missing userId defaults to anonymous).
  4. If access was recently revoked, force-refresh the permission context / clear the loader cache.

Example fix

// before
await perms.assertWorkspace({ workspaceId, action: 'Workspace.Read' });

// after
const allowed = await perms.canWorkspace({ userId, workspaceId, action: 'Workspace.Read' });
if (!allowed) throw new SpaceAccessDenied({ spaceId: workspaceId });
Defensive patterns

Strategy: validation

Validate before calling

const allowed = await perms.canWorkspace({ userId, workspaceId, action });
if (!allowed) {
  // surface a friendly 'request access' UI instead of throwing
  return { denied: true, workspaceId, action };
}

Type guard

function isWorkspaceAction(a: string): a is PermissionWorkspaceAction {
  return [
    'Workspace.Read','Workspace.Sync','Workspace.CreateDoc','Workspace.Delete',
    'Workspace.TransferOwner','Workspace.Users.Manage','Workspace.Administrators.Manage',
    'Workspace.Settings.Update','Workspace.Properties.Create','Workspace.Properties.Update',
    'Workspace.Properties.Delete','Workspace.Blobs.Write','Workspace.Payment.Manage'
  ].includes(a);
}

Try / catch

try {
  await perms.assertWorkspace({ userId, workspaceId, action });
} catch (e) {
  if (e instanceof SpaceAccessDenied) {
    // redirect to 'no access' screen, do not crash the request handler
    return res.status(403).render('no-access', { workspaceId });
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12). Data as JSON: /api/errors/9e63b5f9bfeb0584. Report an issue: GitHub.