{"record":{"id":"ff7b5fb64ec7ac98","repo":"hcengineering/platform","slug":"missing-workspace-role-ff7b5f","errorCode":null,"errorMessage":"Missing workspace role","messagePattern":"Missing workspace role","errorType":"http","errorClass":null,"httpStatus":401,"severity":"error","filePath":"services/payment/pod-payment/src/middleware.ts","lineNumber":91,"sourceCode":"\nconst withOwnerAsync = 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  if (req.params.workspace != null && req.token.workspace !== req.params.workspace) {\n    res.status(401).json({ message: 'Workspace mismatch' }).end()\n    return\n  }\n  if (req.token.account !== systemAccountUuid && req.token.extra?.admin !== 'true') {\n    const accountClient = getAccountClient(req.headers.authorization?.split(' ')[1])\n    const loginInfo = req.loginInfo ?? (await accountClient.getLoginInfoByToken())\n    if (loginInfo == null) {\n      res.status(403).json({ message: 'Missing auth info' }).end()\n      return\n    }\n    if (!('role' in loginInfo)) {\n      res.status(401).json({ message: 'Missing workspace role' }).end()\n      return\n    }\n    if (loginInfo.role !== AccountRole.Owner) {\n      res.status(401).json({ message: 'Workspace owners only' }).end()\n      return\n    }\n  }\n\n  next()\n}\n","sourceCodeStart":73,"sourceCodeEnd":102,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/services/payment/pod-payment/src/middleware.ts#L73-L102","documentation":"withOwnerAsync rejects with 401 'Missing workspace role' when the resolved loginInfo lacks a 'role' property — the account service returned login info that is not workspace-scoped (e.g. global/system login info instead of a workspace membership record).","triggerScenarios":"A non-admin caller whose getLoginInfoByToken() result has no 'role' field, meaning they have no workspace-scoped membership record for the token's workspace, or the account client fetched the wrong kind of login info.","commonSituations":"User authenticated globally but never added to the workspace; account service version returning a shape without role (API change); requesting login info for the wrong workspace context; data migration leaving workspace memberships incomplete.","solutions":["Add the user to the target workspace with an assigned role in the account service","Verify the account service version returns workspace-scoped login info including 'role' (check for API drift)","Ensure the token/workspace used to fetch login info is the same workspace the route targets","Check that account service data migrations completed for this workspace"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// verify workspace membership/role before calling owner-guarded routes\nconst loginInfo = await accountClient.getLoginInfoByToken()\nif (loginInfo == null || !('role' in loginInfo)) {\n  throw new Error('No workspace role: join the workspace before calling this API')\n}","typeGuard":"function hasWorkspaceRole(info: unknown): info is { role: AccountRole } {\n  return typeof info === 'object' && info != null && 'role' in info\n}","tryCatchPattern":"try {\n  const res = await callWorkspaceApi()\n} catch (err) {\n  if (err.response?.status === 401 && err.response.data?.message === 'Missing workspace role') {\n    // direct user to join the workspace / check account service membership data\n  }\n}","preventionTips":["Ensure users are added to the workspace with a role before granting UI access to workspace routes","Keep account service client and server versions aligned (role field in login info)","Validate workspace membership on app login and route accordingly","After data migrations, verify memberships include role fields"],"tags":["auth","http-401","workspace-scoping","missing-role","account-service"],"backgroundTag":"missing-workspace-role","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}