{"record":{"id":"21614f8b1d6b4eef","repo":"hcengineering/platform","slug":"workspace-owners-only-21614f","errorCode":null,"errorMessage":"Workspace owners only","messagePattern":"Workspace owners only","errorType":"http","errorClass":null,"httpStatus":401,"severity":"error","filePath":"services/payment/pod-payment/src/middleware.ts","lineNumber":95,"sourceCode":"    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":77,"sourceCodeEnd":102,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/services/payment/pod-payment/src/middleware.ts#L77-L102","documentation":"withOwnerAsync rejects with 401 'Workspace owners only' when the caller's loginInfo.role is present but is not AccountRole.Owner. Authentication and workspace scoping succeeded, but the endpoint requires workspace Owner privileges.","triggerScenarios":"Any non-owner workspace member (Admin, Member, etc.) calling an owner-protected payment route, e.g. attempting to delete billing settings or manage payment methods for a workspace they don't own.","commonSituations":"Team members trying to change billing/payment settings; a user who believes they are the owner but is actually an admin or member; ownership transferred to someone else; front-end showing owner-only controls without checking the user's actual role.","solutions":["Ask the actual workspace Owner to perform the action, or have the Owner grant ownership (transfer ownership) in the account service","Check your effective role via the account service and confirm which role the endpoint requires","Update the client UI to hide owner-only actions for non-owners","If the requirement changed (admins should also be allowed), revisit the middleware logic and use withAdmin or a role check accordingly"],"exampleFix":"// before\n// member calls owner-only endpoint\nawait api.delete(`/ws/${wsId}/payment-method`)\n// after\nif (myWorkspaceRole === 'Owner') {\n  await api.delete(`/ws/${wsId}/payment-method`)\n}","handlingStrategy":"validation","validationCode":"const loginInfo = await accountClient.getLoginInfoByToken()\nif (loginInfo == null || !('role' in loginInfo) || loginInfo.role !== AccountRole.Owner) {\n  throw new Error('Owner privileges required for this endpoint')\n}","typeGuard":"function isOwner(info: unknown): info is { role: AccountRole.Owner } {\n  return typeof info === 'object' && info != null && 'role' in info && (info as { role: AccountRole }).role === AccountRole.Owner\n}","tryCatchPattern":"try {\n  const res = await callOwnerApi()\n} catch (err) {\n  if (err.response?.status === 401 && err.response.data?.message === 'Workspace owners only') {\n    // show 'owner permission required' — do not retry\n  }\n}","preventionTips":["Gate owner-only UI actions on the user's decoded role","Transfer ownership before offboarding an owner if others need the capability","Document per-endpoint role requirements in API docs","Never retry on 401 authorization (as opposed to authentication) failures — they are deterministic"],"tags":["auth","authorization","http-401","rbac","workspace-owner"],"backgroundTag":"insufficient-permissions","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}