{"record":{"id":"c5cb90bbf036b211","repo":"hcengineering/platform","slug":"platform-status-badrequest-c5cb90","errorCode":"platform.status.BadRequest","errorMessage":"BadRequest","messagePattern":"BadRequest","errorType":"exception","errorClass":"PlatformError","httpStatus":null,"severity":"error","filePath":"server/account/src/operations.ts","lineNumber":770,"sourceCode":"    extra?: string\n    navigateUrl?: string\n    spaces?: string[]\n\n    notBefore?: number\n    expiration?: number\n    personalized?: boolean\n  }\n): Promise<string> {\n  const { role, firstName, lastName, navigateUrl, spaces, notBefore, expiration, personalized = true } = params\n  const { account, workspace: workspaceUuid, extra } = decodeTokenVerbose(ctx, token)\n\n  const currentAccount = await db.account.findOne({ uuid: account })\n  if (currentAccount == null) {\n    throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountNotFound, { account }))\n  }\n\n  if (workspaceUuid == null) {\n    throw new PlatformError(new Status(Severity.ERROR, platform.status.BadRequest, {}))\n  }\n\n  const workspace = await db.workspace.findOne({ uuid: workspaceUuid })\n  if (workspace == null) {\n    throw new PlatformError(new Status(Severity.ERROR, platform.status.WorkspaceNotFound, { workspaceUuid }))\n  }\n\n  let extraObj: Record<string, string> | undefined\n\n  if (params.extra != null) {\n    try {\n      extraObj = JSON.parse(params.extra)\n    } catch (e) {\n      ctx.error(\"Invalid extra parameter, couldn't parse JSON\", { extra: params.extra })\n      throw new PlatformError(new Status(Severity.ERROR, platform.status.BadRequest, {}))\n    }\n  }\n","sourceCodeStart":752,"sourceCodeEnd":788,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/server/account/src/operations.ts#L752-L788","documentation":"After resolving the account, createAccessLink requires the decoded token to carry a workspace UUID. When decodeTokenVerbose returns workspace == null (token has no workspace claim), the platform throws BadRequest. It signals a malformed token rather than missing data.","triggerScenarios":"Calling createAccessLink with a token that lacks a workspace claim — e.g. a token minted without a selected workspace, a system/anonymous token, or a truncated token payload.","commonSituations":"Using pre-workspace-selection tokens (right after signup before joining a workspace); tokens issued by older service versions without the workspace field; hand-built test tokens omitting claims.","solutions":["Mint the token in a workspace context (after the user selects/joins a workspace) before calling createAccessLink.","Decode the token locally and check the workspace claim is present before the call.","Upgrade/align client and server versions so token minting includes the workspace claim."],"exampleFix":"// before: token without workspace claim\nconst link = await createAccessLink(noWorkspaceToken, params)\n// after: ensure token carries workspace\nconst payload = decodeJwt(noWorkspaceToken)\nif (payload.workspace == null) throw new Error('token has no workspace; re-authenticate')\nconst link = await createAccessLink(noWorkspaceToken, params)","handlingStrategy":"validation","validationCode":"const payload = decodeJwt(token)\nif (payload?.workspace == null) {\n  throw new Error('Token has no workspace claim; authenticate within a workspace context first')\n}","typeGuard":"function hasWorkspaceClaim(t: unknown): t is { account: string; workspace: string } {\n  return typeof t === 'object' && t !== null &&\n    typeof (t as any).workspace === 'string' && (t as any).workspace.length > 0\n}","tryCatchPattern":null,"preventionTips":["Only mint tokens after the user has selected/joined a workspace.","Decode and sanity-check token claims before each API call.","Keep token-issuing and token-consuming service versions aligned."],"tags":["auth","token","bad-request","workspace"],"backgroundTag":"token-missing-workspace-claim","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}