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 assertCanReadPublicDoc when the access-control check ac.user(userId).doc(workspaceId, docId).can('Doc.Read') returns false. It guards public/shared document reads (published docs, shared links) so a user without read permission cannot fetch the doc binary or metadata.

Source

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

  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 26c515e050)

Solutions

  1. Verify the doc is published/shared and that the requesting user is the intended recipient.
  2. Re-share the doc with the user or re-publish it.
  3. Confirm the correct user identity/session is being sent (CurrentUser).
  4. Check the access-control rules and role assignments for the workspace/doc.

Example fix

// before
const can = await ac.user(userId).doc(ws, docId).can('Doc.Read')
if (!can) proceed() // ignores denial
// after
const can = await ac.user(userId).doc(ws, docId).can('Doc.Read')
if (!can) throw new Forbidden('ask the owner to share this doc')
Defensive patterns

Strategy: validation

Validate before calling

const canRead = await ac.user(userId).doc(workspaceId, docId).can('Doc.Read')
if (!canRead) return showShareRequestUI(workspaceId, docId)

Try / catch

try { await fetchPublicDoc(ws, docId) } catch (e) {
  if (e.code === 'doc_action_denied') requestAccess(ws, docId)
  else throw e
}

Prevention

When it happens

Trigger: A request to read a doc (public doc endpoint, shared link) by a user who lacks Doc.Read on that doc: the doc is not actually published/shared, the share was revoked, or the wrong user identity is being passed.

Common situations: A shared link was expired/revoked; a public doc switched back to private; the wrong userId is passed (anonymous vs authenticated); permission propagation lag after a role change.

Related errors


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