{"record":{"id":"7055287af2702e76","repo":"hcengineering/platform","slug":"unauthorized-705528","errorCode":null,"errorMessage":"Unauthorized","messagePattern":"Unauthorized","errorType":"http","errorClass":null,"httpStatus":401,"severity":"error","filePath":"services/backup/backup-api-pod/src/server.ts","lineNumber":162,"sourceCode":"    },\n    15 * 60 * 1000\n  )\n\n  async function handleBackup (request: Request<any>, res: Response<any>): Promise<void> {\n    const headers = request.headers\n    const workspace = request.params.workspace ?? ''\n    const file: string | undefined = request.params.file\n    const authHeader = headers.authorization ?? ''\n\n    let token = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : undefined\n\n    if (token == null) {\n      const cookies = (headers.cookie ?? '').split(';').map((it) => it.trim().split('='))\n      token = cookies.find((it) => it[0] === 'presentation-metadata-Token')?.[1]\n    }\n\n    if (token === undefined || typeof token !== 'string' || token === '') {\n      res.status(401).end('Unauthorized')\n      return\n    }\n\n    let workspaceId: WorkspaceUuid | undefined\n    let isAdmin: boolean = false\n\n    try {\n      const decoded = decodeTokenVerbose(ctx, token)\n      if (decoded === undefined) {\n        res.status(401).end('Unauthorized')\n        return\n      }\n      workspaceId = decoded.workspace\n      isAdmin = decoded.extra?.admin === 'true'\n    } catch (err: any) {\n      res.status(401).end('Unauthorized')\n      return\n    }","sourceCodeStart":144,"sourceCodeEnd":180,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/services/backup/backup-api-pod/src/server.ts#L144-L180","documentation":"The backup API's handleBackup authenticates requests via a token taken from the Authorization header or, failing that, the 'presentation-metadata-Token' cookie. If neither yields a non-empty string token, it responds HTTP 401 with body 'Unauthorized' and aborts the backup operation.","triggerScenarios":"Request to the backup endpoint with no Authorization header and no 'presentation-metadata-Token' cookie, or with an empty/non-string cookie value.","commonSituations":"Calling the backup API from scripts/curl without session cookies; browser sessions where the cookie expired or was cleared; cookie name changed across versions; same-site cookie policies blocking the cookie.","solutions":["Send the token in the Authorization header (preferred): Authorization: Bearer <token>.","Ensure the 'presentation-metadata-Token' cookie is set and non-empty when relying on browser sessions.","Re-authenticate to obtain a fresh token if the session expired.","Confirm cookie name/domain/sameSite settings allow the cookie to reach the backup API."],"exampleFix":"// before\nfetch(BACKUP_URL, { method: 'POST' })\n// after\nfetch(BACKUP_URL, {\n  method: 'POST',\n  headers: { Authorization: `Bearer ${token}` }\n})","handlingStrategy":"validation","validationCode":"function hasBackupCredentials(init: RequestInit & { token?: string }): boolean {\n  const h = new Headers(init.headers)\n  return h.has('authorization') || typeof init.token === 'string' && init.token.length > 0\n}","typeGuard":"function hasValidToken(token: unknown): token is string {\n  return typeof token === 'string' && token.length > 0\n}","tryCatchPattern":"const res = await fetch(BACKUP_URL, { headers: { Authorization: `Bearer ${token}` } })\nif (res.status === 401) {\n  token = await reauthenticate() // refresh session/token then retry once\n  return fetch(BACKUP_URL, { headers: { Authorization: `Bearer ${token}` } })\n}","preventionTips":["Always send the Authorization header explicitly when scripting backup calls.","Check cookie presence ('presentation-metadata-Token') before relying on browser-session auth.","Re-authenticate on session expiry instead of retrying with stale cookies.","Verify cookie sameSite/domain settings when calling the backup API cross-origin."],"tags":["http-401","authentication","backup"],"backgroundTag":"missing-authorization-header","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}