toeverything/AFFiNE · error · DocActionDenied

doc_action_denied

doc_action_denied

Error message

You do not have permission to perform ${action} action on doc ${docId}.

What it means

Thrown by WorkspacesController.assertCanReadPublicDoc when the acting user fails the Doc.Read capability check on (workspaceId, docId) — this.ac.user(userId).doc(...).can('Doc.Read') returned false. It guards shared/public-doc endpoints that still require at least read access, and carries docId, spaceId, and the failed action ('Doc.Read') in the error payload.

Solutions

  1. Share the doc with the user or publish it publicly (workspace/doc sharing settings) so Doc.Read evaluates true
  2. Verify the URL pairs the correct workspaceId and docId — a mismatched pair fails the check
  3. Authenticate as a workspace member (owner/admin/member) before requesting the doc
  4. Server-side, confirm the docAccessPolicy/sharing rows exist for that doc — they are what let outsiders pass Doc.Read
Defensive patterns

Strategy: try-catch

Validate before calling

// before requesting a shared doc, confirm read access if your client has a capability probe
const canRead = await graphql(`
  query CanRead($wsId: String!, $docId: String!) {
    workspace(id: $wsId) { doc(id: $docId) { canRead } }
  }
`, { wsId, docId });
if (!canRead) {
  showAccessRequest(wsId, docId);
  return;
}

Type guard

function isDocActionDenied(e: unknown): e is { code: 'doc_action_denied'; docId: string; spaceId: string; action: string } {
  return typeof e === 'object' && e !== null && (e as any).code === 'doc_action_denied';
}

Try / catch

try {
  return await fetchPublicDoc(wsId, docId);
} catch (e) {
  if (isDocActionDenied(e)) {
    return renderNoAccessPage(e.docId); // 403: do not retry with same identity
  }
  throw e;
}

Prevention

When it happens

Trigger: Hitting endpoints that funnel through assertCanReadPublicDoc (e.g. public doc reads and the comment-attachment route) for a doc that is not shared with the caller and where the caller has no workspace member role granting Doc.Read.

Common situations: Opening a link to a doc whose public sharing was revoked or never enabled; logged-out visitors hitting member-only docs; wrong workspaceId/docId pair (doc exists in another space); membership or doc-access-policy rows not yet visible after a role change.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of toeverything/AFFiNE@2af30773ae (2026-08-18). Data as JSON: /api/errors/907b1016104fa276. Report an issue: GitHub.

Appendix: source

Thrown at packages/backend/server/src/core/workspaces/controller.ts:67

  private buildVisitorId(req: Request, workspaceId: string, docId: string) {
    const tracker = getRequestTrackerId(req);
    return createHash('sha256')
      .update(`${workspaceId}:${docId}:${tracker}`)
      .digest('hex');
  }

  private async assertCanReadPublicDoc(
    userId: string,
    workspaceId: string,
    docId: string
  ) {
    const canReadSharedDoc = await this.ac
      .user(userId)
      .doc(workspaceId, docId)
      .can('Doc.Read');
    if (!canReadSharedDoc) {
      throw new DocActionDenied({
        docId,
        spaceId: workspaceId,
        action: 'Doc.Read',
      });
    }
  }

  private async getPublishModeHeader(workspaceId: string, docId: string) {
    const docMeta = await this.models.doc.getMeta(workspaceId, docId, {
      select: {
        mode: true,
      },
    });
    return docMeta?.mode === PublicDocMode.Edgeless
      ? DocMode.edgeless
      : DocMode.page;
  }

View on GitHub (pinned to 2af30773ae)