{"record":{"id":"5bcfdf435f493b56","repo":"toeverything/AFFiNE","slug":"can-not-revoke-yourself","errorCode":"can_not_revoke_yourself","errorMessage":"You can not revoke your own permission.","messagePattern":"You can not revoke your own permission\\.","errorType":"exception","errorClass":"CanNotRevokeYourself","httpStatus":403,"severity":"warning","filePath":"packages/backend/server/src/core/workspaces/resolvers/member.ts","lineNumber":602,"sourceCode":"        inviteeId\n      );\n      status = invitation?.status;\n    } else {\n      const invitation = await this.models.workspaceUser.getById(inviteId);\n      status = invitation?.status;\n    }\n\n    return { workspace, user: owner, invitee, status };\n  }\n\n  @Mutation(() => Boolean)\n  async revokeMember(\n    @CurrentUser() me: CurrentUser,\n    @Args('workspaceId') workspaceId: string,\n    @Args('userId') userId: string\n  ) {\n    if (userId === me.id) {\n      throw new CanNotRevokeYourself();\n    }\n\n    const role = await this.models.workspaceUser.get(workspaceId, userId);\n\n    if (!role) {\n      throw new MemberNotFoundInSpace({ spaceId: workspaceId });\n    }\n\n    await this.ac\n      .user(me.id)\n      .workspace(workspaceId)\n      .assert(\n        role.type === WorkspaceRole.Admin\n          ? 'Workspace.Administrators.Manage'\n          : 'Workspace.Users.Manage'\n      );\n\n    await this.models.workspaceUser.delete(workspaceId, userId);","sourceCodeStart":584,"sourceCodeEnd":620,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/49536827790702ad0ab6d3be3405d891a6b19f83/packages/backend/server/src/core/workspaces/resolvers/member.ts#L584-L620","documentation":"Thrown by the revokeMember mutation when the caller passes their own user id — you cannot revoke your own membership with this API. The self-check runs first, before the member lookup and permission assertions, so it fires even if you are not otherwise authorized. Code can_not_revoke_yourself, type action_forbidden, HTTP 403.","triggerScenarios":"Calling revokeMember(workspaceId, me.id) — typically a member-management UI whose row action is accidentally bound to the current user's row, or an automated cleanup script iterating all member ids including the operator's.","commonSituations":"Remove buttons not disabled on the 'you' row in the members table; batch scripts that revoke everyone then re-add, forgetting to skip the caller; owners trying to leave via revoke instead of a leave-workspace flow.","solutions":["Skip or disable the revoke action on the current user's row in the UI (show 'Leave workspace' instead if that is the intent).","In scripts, filter out the acting user's id before calling revokeMember.","If the goal is to leave the workspace, use the leave/transfer flow — owners must transfer ownership first (owner cannot leave)."],"exampleFix":"// before\nfor (const m of members) {\n  await revokeMember(ws.id, m.id); // includes me -> can_not_revoke_yourself\n}\n\n// after\nfor (const m of members.filter(m => m.id !== me.id)) {\n  await revokeMember(ws.id, m.id);\n}","handlingStrategy":"validation","validationCode":"// never point revoke at yourself\nif (targetUserId === me.id) {\n  disableRevokeButton(); // or route to a Leave-workspace flow instead\n}","typeGuard":"function isCanNotRevokeYourself(e: unknown): boolean {\n  const ext = (e as { extensions?: Record<string, unknown> })?.extensions;\n  return String(ext?.name ?? '').toLowerCase() === 'can_not_revoke_yourself';\n}","tryCatchPattern":"try {\n  await revokeMember(workspaceId, targetUserId);\n} catch (e) {\n  if (isCanNotRevokeYourself(e)) {\n    // row action bound to the wrong id — hide remove on the 'you' row\n  } else throw e;\n}","preventionTips":["Disable the remove action on the current user's row in the members table.","Bulk-revoke scripts must skip the operator's own id."],"tags":["affine","graphql","member-management","self-operation","forbidden"],"backgroundTag":"cannot-modify-own-permissions","analyzedSha":"49536827790702ad0ab6d3be3405d891a6b19f83","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}