toeverything/AFFiNE · error · SpaceAccessDenied
space_access_denied
space_access_denied
Error message
You do not have permission to access Space ${spaceId}. What it means
SpaceAccessDenied (code `space_access_denied`, category `no_permission`, carries `{ spaceId }`) at gateway.ts:326, in `assertDocActionAllowed`. For `SpaceType.Userspace`, the spaceId must equal the authenticated userId — a userspace is per-user, so no other user may touch it. Any divergence throws before the doc-level permission check.
Source
Thrown at packages/backend/server/src/core/sync/gateway.ts:326
};
}
}
private rejectJoin(client: Socket) {
// Give socket.io a chance to flush the ack packet before disconnecting.
setImmediate(() => client.disconnect());
}
private async assertDocActionAllowed(
spaceType: SpaceType,
userId: string,
spaceId: string,
docId: string,
action: DocAction
) {
if (spaceType === SpaceType.Userspace) {
if (spaceId !== userId) {
throw new SpaceAccessDenied({ spaceId });
}
return;
}
await this.ac.user(userId).doc(spaceId, docId).assert(action);
}
private assertUserdataSubject(
spaceType: SpaceType,
userId: string,
workspaceId: string,
docId: string
) {
if (
spaceType === SpaceType.Workspace &&
!authorizeUserdataDocSubject(userId, workspaceId, docId)
) {
throw new SpaceAccessDenied({ spaceId: workspaceId });View on GitHub (pinned to 26c515e050)
Solutions
- For userspace operations, always set `spaceId` to the current user's id.
- If cross-user collaboration is needed, use a workspace, not a userspace.
- On the client, distinguish userspace from workspace and never substitute another userId.
Example fix
// before
socket.emit('space:load-doc', { spaceType: 'userspace', spaceId: otherUserId, docId }); // -> 278
// after
socket.emit('space:load-doc', { spaceType: 'userspace', spaceId: currentUser.id, docId }); Defensive patterns
Strategy: try-catch
Validate before calling
// Best-effort client guard: only open userspace for the current user.
function ownUserspace(spaceType: string, spaceId: string, userId: string): boolean {
return spaceType !== 'userspace' || spaceId === userId;
}
if (!ownUserspace(msg.spaceType, msg.spaceId, currentUser.id)) {
// do not emit; surface access error to UI
} Try / catch
// Permission is server-authoritative; catch SpaceAccessDenied and surface it.
socket.on('error', (err: { code?: string; message?: string }) => {
if (err?.code === 'space_access_denied') {
ui.showAccessDenied(err.message ?? 'You do not have permission to access this space.');
return;
}
throw err;
}); Prevention
- For userspace, always set spaceId to the authenticated user's id.
- Treat spaceId as the user's identity, not a shareable handle.
- Use workspaces for multi-user collaboration; never share userspaces.
When it happens
Trigger: A sync gateway message (load-doc, delete-doc, push-doc-update, etc.) with `spaceType === 'userspace'` and `spaceId !== user.id`. The check runs after `assertUserdataSubject` but is the userspace-specific gate; the equivalent also exists in `UserspaceSyncAdapter.assertAccessible` (gateway.ts:999).
Common situations: Client sends another user's userId as spaceId; session user changed but the client kept an old spaceId; cross-user sharing attempt on a userspace doc; client bug confusing workspaceId with userspaceId.
Related errors
AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12).
Data as JSON: /api/errors/f13b0bd3146a86d8.
Report an issue: GitHub.