{"record":{"id":"294991f29ebc9927","repo":"hcengineering/platform","slug":"admins-only-294991","errorCode":null,"errorMessage":"Admins only","messagePattern":"Admins only","errorType":"http","errorClass":null,"httpStatus":401,"severity":"error","filePath":"services/payment/pod-payment/src/middleware.ts","lineNumber":43,"sourceCode":"}\n\nexport const withToken = (req: RequestWithAuth, res: Response, next: NextFunction): void => {\n  const token = extractToken(req.headers)\n  if (token === undefined || token == null) {\n    res.status(401).json({ message: 'Token error' }).end()\n    return\n  }\n  req.token = token\n  next()\n}\n\nexport const withAdmin = (req: RequestWithAuth, res: Response, next: NextFunction): void => {\n  if (req.token === undefined || req.token == null) {\n    res.status(401).json({ message: 'Token error' }).end()\n    return\n  }\n  if (req.token.account !== systemAccountUuid && req.token.extra?.admin !== 'true') {\n    res.status(401).json({ message: 'Admins only' }).end()\n    return\n  }\n  next()\n}\n\nexport const withLoginInfo = (req: RequestWithAuth, res: Response, next: NextFunction): void => {\n  void withLoginInfoAsync(req, res, next)\n}\n\nconst withLoginInfoAsync = async (req: RequestWithAuth, res: Response, next: NextFunction): Promise<void> => {\n  if (req.token === undefined || req.token == null) {\n    res.status(401).json({ message: 'Token error' }).end()\n    return\n  }\n\n  const accountClient = getAccountClient(req.headers.authorization?.split(' ')[1])\n  const loginInfo = await accountClient.getLoginInfoByToken()\n  if (loginInfo == null) {","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/services/payment/pod-payment/src/middleware.ts#L25-L61","documentation":"withAdmin rejects with 401 'Admins only' when a token IS present but the caller is neither the system account (req.token.account !== systemAccountUuid) nor flagged as admin (req.token.extra?.admin !== 'true'). Authentication succeeded; authorization failed. This guard protects admin-only payment endpoints.","triggerScenarios":"Any authenticated non-admin user calling an endpoint protected by withAdmin; or a legit admin whose token was issued without the extra.admin='true' claim.","commonSituations":"A regular workspace user hitting an internal admin API; an admin whose JWT was minted by a login flow that does not set extra.admin; stale tokens issued before the admin claim was added to the account service; environment mismatch where systemAccountUuid differs from the account in the token (config drift across pods).","solutions":["Obtain a token issued to the system account or one carrying extra.admin='true'","Verify the account service issues the admin claim for this user and re-login to refresh the token","Check that systemAccountUuid in this pod's config matches the account UUID used to mint admin tokens (env var/config check)","If the caller should be admin, grant the admin flag on the account and issue a new token"],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":"function isAdminToken(token: { account: string; extra?: { admin?: string } }, systemAccountUuid: string): boolean {\n  return token.account === systemAccountUuid || token.extra?.admin === 'true'\n}\n// decode the JWT client-side before calling an admin endpoint\nconst claims = JSON.parse(atob(token.split('.')[1]))\nif (!isAdminToken(claims, SYSTEM_ACCOUNT_UUID)) skipAdminCall()","typeGuard":"function isAdminClaims(c: { account?: string; extra?: { admin?: string } } | null, systemAccountUuid: string): c is { account: string; extra: { admin: 'true' } } {\n  return c != null && (c.account === systemAccountUuid || c.extra?.admin === 'true')\n}","tryCatchPattern":"try {\n  const res = await callAdminApi()\n} catch (err) {\n  if (err.response?.status === 401 && err.response.data?.message === 'Admins only') {\n    // surface 'requires admin privileges' to the user; do not retry\n  }\n}","preventionTips":["Hide admin-only UI/actions unless the decoded token actually has the admin claim","Re-issue tokens after granting/revoking admin flags — claims are baked into the JWT","Keep systemAccountUuid config in sync across environments","Never call admin endpoints with end-user tokens; use a service token"],"tags":["auth","authorization","http-401","rbac","admin-only"],"backgroundTag":"insufficient-permissions","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}