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

  1. For userspace operations, always set `spaceId` to the current user's id.
  2. If cross-user collaboration is needed, use a workspace, not a userspace.
  3. 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

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.