{"record":{"id":"5d7703bdea6808dd","repo":"toeverything/AFFiNE","slug":"action-forbidden-5d7703","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/plugins/payment/service.ts","lineNumber":137,"sourceCode":"    return [\n      ...(await this.userManager.filterPrices(prices, customer)),\n      ...this.workspaceManager.filterPrices(prices, customer),\n    ];\n  }\n\n  async checkout(\n    params: z.infer<typeof CheckoutParams>,\n    args: z.infer<typeof CheckoutExtraArgs>\n  ) {\n    const { plan, recurring, variant } = params;\n\n    if (\n      env.namespaces.canary &&\n      env.prod &&\n      args.user &&\n      !this.feature.isStaff(args.user.email)\n    ) {\n      throw new ActionForbidden();\n    }\n\n    const manager = this.select(plan);\n    const result = CheckoutExtraArgs.safeParse(args);\n\n    if (!result.success) {\n      throw new InvalidCheckoutParameters();\n    }\n\n    return manager.checkout(\n      {\n        plan,\n        recurring,\n        variant: variant ?? null,\n      },\n      params,\n      args\n    );","sourceCodeStart":119,"sourceCodeEnd":155,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b4c8548c09da21b2898443559a5b846f0ccf5dd8/packages/backend/server/src/plugins/payment/service.ts#L119-L155","documentation":"Thrown by SubscriptionService.checkout when the server runs with the canary namespace flag in production (env.namespaces.canary && env.prod) and the requesting user's email is not in the staff allowlist (feature.isStaff). During a canary rollout of the payments stack, checkout is restricted to staff.","triggerScenarios":"Any checkout call by a non-staff user while the deployment is canary-enabled production; canary flag left on after testing; staging-like canary environment shared with external beta users.","commonSituations":"Canary namespace not disabled after a payment migration; real users routed to the canary instance by a load balancer/feature flag; developer testing with a personal (non-staff) account on a canary build.","solutions":["If you operate the server: turn off the canary namespace (env.namespaces.canary) on production once checkout is stable.","If you must test on canary-prod: use a staff email allowlisted in FeatureService.isStaff.","If you are an end-user/client: wait for full rollout or use the non-canary endpoint; no client change can bypass the gate."],"exampleFix":"# before (server env)\nNAMESPACES_CANARY=true\n# non-staff user checkout -> ActionForbidden\n\n# after\nNAMESPACES_CANARY=false\n# checkout open to all users","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  const url = await svc.checkout(params, args);\n} catch (e) {\n  if (gqlErrorCode(e) === 'action_forbidden') {\n    notify('Checkout is temporarily limited to staff. Try again after rollout.');\n    return;\n  }\n  throw e;\n}","preventionTips":["Operators: disable the canary namespace on production once checkout is validated.","Test on canary with a staff-allowlisted account only.","Monitor for action_forbidden rate in prod as a signal the canary gate is still on."],"tags":["payment","feature-flag","canary","authorization","checkout"],"backgroundTag":"feature-not-enabled","analyzedSha":"b4c8548c09da21b2898443559a5b846f0ccf5dd8","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}