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 PermissionService.assertDoc when canDoc() evaluates the doc-level permission rule and returns allowed=false. Distinct from workspace access: the user may be a workspace member but lacks the specific doc action (Doc.Read, Doc.Update, Doc.Comments.Create, etc.). Carries docId, action, and spaceId in the payload for client display.
Source
Thrown at packages/backend/server/src/core/permission/service.ts:171
action: PermissionDocAction;
allowLocal?: boolean;
}) {
const output = await this.docPermissions({
...input,
actions: [input.action],
});
return output.decisions[0]?.allowed ?? false;
}
async assertDoc(input: {
userId?: string;
workspaceId: string;
docId: string;
action: PermissionDocAction;
allowLocal?: boolean;
}) {
if (!(await this.canDoc(input))) {
throw new DocActionDenied({
action: input.action,
docId: input.docId,
spaceId: input.workspaceId,
});
}
}
async filterReadableDocs<T extends { docId: string }>(input: {
userId?: string;
workspaceId: string;
docs: T[];
allowLocal?: boolean;
}) {
const decisions = await this.batchDocPermissions({
...input,
docs: input.docs.map(doc => ({
docId: doc.docId,
actions: ['Doc.Read'],View on GitHub (pinned to 26c515e050)
Solutions
- Check the doc access matrix with docPermissions() before issuing the mutating call.
- Ensure the user's workspace role grants the underlying action and no doc-level override revokes it.
- If using a custom permission policy, confirm the rule set includes the requested action for the role.
- For anonymous/public flows, verify the doc is actually published before asserting Doc.Read.
Example fix
// before
await perms.assertDoc({ userId, workspaceId, docId, action: 'Doc.Update' });
// after
const decisions = await perms.docPermissions({ userId, workspaceId, docId, actions: ['Doc.Update'] });
if (!decisions.decisions[0]?.allowed) {
throw new DocActionDenied({ action: 'Doc.Update', docId, spaceId: workspaceId });
} Defensive patterns
Strategy: validation
Validate before calling
const { decisions } = await perms.docPermissions({ userId, workspaceId, docId, actions: [action] });
if (!decisions[0]?.allowed) {
return { denied: true, docId, action };
} Type guard
function isDocAction(a: string): a is PermissionDocAction {
return ['Doc.Read','Doc.Duplicate','Doc.Trash','Doc.Restore','Doc.Delete','Doc.Update','Doc.Publish','Doc.TransferOwner','Doc.Properties.Update','Doc.Users.Manage','Doc.Comments.Create','Doc.Comments.Update','Doc.Comments.Delete','Doc.Comments.Resolve'].includes(a);
} Try / catch
try {
await perms.assertDoc({ userId, workspaceId, docId, action });
} catch (e) {
if (e instanceof DocActionDenied) {
// downgrade UI to read-only mode for this doc
return res.status(403).json({ docId, action: e.action });
}
throw e;
} Prevention
- Call docPermissions once per doc batch and gate each subsequent action on its decision.
- Refresh doc permissions when the user accepts a doc invite or a doc is re-shared.
- In the client, disable edit controls when Doc.Update is denied rather than letting the request fail.
When it happens
Trigger: Invoking assertDoc with an action the user's role does not grant (e.g. a 'Collaborator' trying Doc.Delete); doc-level overrides restricting a doc the workspace otherwise exposes; calling on a docId that exists but for which the user has no explicit grant; anonymous access to a non-published doc.
Common situations: Doc shared with reduced permissions then the user attempts to edit; permission rules changed mid-session; tests creating docs without granting Doc.Read to the acting user; client caching a doc after its access was downgraded.
Related errors
- space_access_denied
- action_forbidden
- space_access_denied
- expect_to_grant_doc_user_roles
- can_not_revoke_yourself
AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12).
Data as JSON: /api/errors/9658ca6b08ce0f06.
Report an issue: GitHub.