{"record":{"id":"e3c1067412edd8c9","repo":"toeverything/AFFiNE","slug":"action-forbidden-e3c106","errorCode":"action_forbidden","errorMessage":"You are not allowed to perform this action.","messagePattern":"You are not allowed to perform this action\\.","errorType":"exception","errorClass":"ActionForbidden","httpStatus":403,"severity":"error","filePath":"packages/backend/server/src/core/common/admin-guard.ts","lineNumber":30,"sourceCode":"@Injectable()\nexport class AdminGuard implements CanActivate, OnModuleInit {\n  private feature!: FeatureService;\n\n  constructor(private readonly ref: ModuleRef) {}\n\n  onModuleInit() {\n    this.feature = this.ref.get(FeatureService, { strict: false });\n  }\n\n  async canActivate(context: ExecutionContext) {\n    const { req } = getRequestResponseFromContext(context);\n    let allow = false;\n    if (req.session) {\n      allow = await this.feature.isAdmin(req.session.user.id);\n    }\n\n    if (!allow) {\n      throw new ActionForbidden();\n    }\n\n    return true;\n  }\n}\n\n/**\n * This guard is used to protect routes/queries/mutations that require a user to be administrator.\n *\n * @example\n *\n * ```typescript\n * \\@Admin()\n * \\@Mutation(() => UserType)\n * createAccount(userInput: UserInput) {\n *   // ...\n * }\n * ```","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/26c515e050211269e911f7d9cfe162a26c83ed98/packages/backend/server/src/core/common/admin-guard.ts#L12-L48","documentation":"Thrown by the `AdminGuard` canActivate when the request's session user is not a server administrator (`feature.isAdmin(req.session.user.id)` resolves false, or there is no session). The guard protects any resolver/method decorated with `@Admin()` and rejects with `action_forbidden` (category `action_forbidden`).","triggerScenarios":"Calling any `@Admin()`-guarded GraphQL mutation or route as a non-admin user; calling it with no session/an expired session; calling it before the feature service has populated admin flags.","commonSituations":"Front-end admin panels shown to users whose role changed; token expiry mid-session; self-hosted instances where the first user was not seeded as admin; mis-decorated resolvers accidentally requiring admin.","solutions":["Gate admin-only UI behind a client-side `isAdmin` flag fetched from the session/features so non-admins never trigger the call.","On `action_forbidden`, redirect to a 403 page or sign the user out if the session is stale.","For self-hosted setups, ensure the bootstrap/seed flow grants the initial user admin (check `FeatureService.isAdmin` wiring and admin flag config).","Audit that `@Admin()` is only applied to genuinely admin-only operations."],"exampleFix":"// before\nawait client.mutate({ mutation: SOME_ADMIN_MUTATION, variables });\n\n// after\nconst { data } = await client.query({ query: CURRENT_USER_IS_ADMIN });\nif (!data?.me?.isAdmin) {\n  router.replace('/403');\n  return;\n}\nawait client.mutate({ mutation: SOME_ADMIN_MUTATION, variables });","handlingStrategy":"validation","validationCode":"// Hide/disable admin actions unless the current user is an admin\nconst { data } = await client.query({ query: CURRENT_USER_IS_ADMIN_QUERY });\nconst isAdmin = Boolean(data?.me?.isAdmin);\n\nif (!isAdmin) {\n  router.replace('/403');\n  return;\n}","typeGuard":"function isAdminSession(value: unknown): value is { isAdmin: true } {\n  return typeof value === 'object' &&\n    value !== null &&\n    (value as { isAdmin?: unknown }).isAdmin === true;\n}","tryCatchPattern":"try {\n  await mutateAdminAction(variables);\n} catch (e) {\n  const code = e?.graphQLErrors?.[0]?.extensions?.code;\n  if (code === 'action_forbidden') {\n    router.replace('/403'); // or sign out if session is stale\n    return;\n  }\n  throw e;\n}","preventionTips":["Gate all admin UI behind a client-side `isAdmin` flag from the session/features.","On `action_forbidden`, treat the session as no-longer-admin and refresh credentials.","For self-hosted setups, confirm the seed/bootstrap user is granted admin.","Audit `@Admin()` usage so only truly admin-only operations are guarded."],"tags":["graphql","auth","authorization","admin","guard","permissions"],"backgroundTag":null,"analyzedSha":"26c515e050211269e911f7d9cfe162a26c83ed98","analyzedAt":"2026-08-12T13:15:16.447Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}