{"record":{"id":"3a0a8be37de56e3b","repo":"toeverything/AFFiNE","slug":"mention-user-doc-access-denied","errorCode":"mention_user_doc_access_denied","errorMessage":"Mentioned user can not access doc ${docId}.","messagePattern":"Mentioned user can not access doc (.+?)\\.","errorType":"exception","errorClass":"MentionUserDocAccessDenied","httpStatus":403,"severity":"error","filePath":"packages/backend/server/src/core/notification/resolver.ts","lineNumber":130,"sourceCode":"        createdByUserId: me.id,\n      },\n    });\n    if (parsedInput.userId === me.id) {\n      throw new MentionUserOneselfDenied();\n    }\n    // currentUser can update the doc\n    await this.ac\n      .user(me.id)\n      .doc(parsedInput.body.workspaceId, parsedInput.body.doc.id)\n      .assert('Doc.Update');\n    // mention user can read the doc\n    if (\n      !(await this.ac\n        .user(parsedInput.userId)\n        .doc(parsedInput.body.workspaceId, parsedInput.body.doc.id)\n        .can('Doc.Read'))\n    ) {\n      throw new MentionUserDocAccessDenied({\n        docId: parsedInput.body.doc.id,\n      });\n    }\n    const notification = await this.service.createMention(parsedInput);\n    return notification.id;\n  }\n\n  @Mutation(() => Boolean, {\n    description: 'mark notification as read',\n  })\n  async readNotification(\n    @CurrentUser() me: UserType,\n    @Args('id') notificationId: string\n  ) {\n    await this.service.markAsRead(me.id, notificationId);\n    return true;\n  }\n","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/591f874dad30887a80143a061a44bd3ca7ee3299/packages/backend/server/src/core/notification/resolver.ts#L112-L148","documentation":"After asserting the caller has Doc.Update on the doc, createMention checks that the mentioned user has Doc.Read on the same doc; if the access-control evaluation returns false it throws MentionUserDocAccessDenied (no_permission / mention_user_doc_access_denied) with the docId. Mentioning someone who cannot open the doc would create a notification pointing at forbidden content, so it is blocked.","triggerScenarios":"Mentioning a user who is not a member of the workspace; mentioning a member whose doc-level role does not include read on a private/restricted doc; the doc's visibility was changed after the mention picker loaded its user list.","commonSituations":"Mention pickers listing all workspace members regardless of per-doc permissions; mentioning external collaborators on internal docs; stale permission caches client-side after roles were revoked.","solutions":["Before sending, check the mentioned user's access (same ac.user(...).doc(...).can('Doc.Read') evaluation) or filter mention candidates to users with doc read access","On this error, prompt the author to invite the user or grant read access, then retry the mention","Refresh the mention candidate list when doc visibility/roles change","Ensure the mentioned userId is an active member of the workspace at all"],"exampleFix":"// before\nawait createMention({ userId, body: { workspaceId, doc, createdByUserId: me.id } });\n\n// after\nconst canRead = await ac.user(userId).doc(workspaceId, doc.id).can('Doc.Read');\nif (!canRead) {\n  notifyAuthor(`${userName} cannot read this doc; grant access first`);\n} else {\n  await createMention({ userId, body: { workspaceId, doc, createdByUserId: me.id } });\n}","handlingStrategy":"validation","validationCode":"const canRead = await ac\n  .user(targetUserId)\n  .doc(workspaceId, doc.id)\n  .can('Doc.Read');\nif (!canRead) {\n  return promptGrantAccess(targetUserId, doc.id); // invite or pick someone else\n}\nawait createMention(input);","typeGuard":"function isMentionDocAccessDenied(e: unknown): boolean {\n  return (e as { extensions?: { code?: string } }).extensions?.code === 'mention_user_doc_access_denied';\n}","tryCatchPattern":"try {\n  await createMention(input);\n} catch (e) {\n  if (isMentionDocAccessDenied(e)) {\n    return showInvitePrompt(input.userId, e.extensions.docId); // recoverable: grant read, then retry\n  }\n  throw e;\n}","preventionTips":["Filter mention candidates by doc read permission, not just workspace membership","Re-check permissions when doc visibility changes (public → private)","Offer an inline 'invite to doc' action when this error fires"],"tags":["notification","mention","permission","access-control","collaboration"],"backgroundTag":"permission-denied","analyzedSha":"591f874dad30887a80143a061a44bd3ca7ee3299","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}