hcengineering/platform · error

Unauthorized

Error message

Unauthorized

What it means

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.

Source

Thrown at services/backup/backup-api-pod/src/server.ts:162

    },
    15 * 60 * 1000
  )

  async function handleBackup (request: Request<any>, res: Response<any>): Promise<void> {
    const headers = request.headers
    const workspace = request.params.workspace ?? ''
    const file: string | undefined = request.params.file
    const authHeader = headers.authorization ?? ''

    let token = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : undefined

    if (token == null) {
      const cookies = (headers.cookie ?? '').split(';').map((it) => it.trim().split('='))
      token = cookies.find((it) => it[0] === 'presentation-metadata-Token')?.[1]
    }

    if (token === undefined || typeof token !== 'string' || token === '') {
      res.status(401).end('Unauthorized')
      return
    }

    let workspaceId: WorkspaceUuid | undefined
    let isAdmin: boolean = false

    try {
      const decoded = decodeTokenVerbose(ctx, token)
      if (decoded === undefined) {
        res.status(401).end('Unauthorized')
        return
      }
      workspaceId = decoded.workspace
      isAdmin = decoded.extra?.admin === 'true'
    } catch (err: any) {
      res.status(401).end('Unauthorized')
      return
    }

View on GitHub (pinned to 63e28dc964)

Solutions

  1. Send the token in the Authorization header (preferred): Authorization: Bearer <token>.
  2. Ensure the 'presentation-metadata-Token' cookie is set and non-empty when relying on browser sessions.
  3. Re-authenticate to obtain a fresh token if the session expired.
  4. Confirm cookie name/domain/sameSite settings allow the cookie to reach the backup API.

Example fix

// before
fetch(BACKUP_URL, { method: 'POST' })
// after
fetch(BACKUP_URL, {
  method: 'POST',
  headers: { Authorization: `Bearer ${token}` }
})
Defensive patterns

Strategy: validation

Validate before calling

function hasBackupCredentials(init: RequestInit & { token?: string }): boolean {
  const h = new Headers(init.headers)
  return h.has('authorization') || typeof init.token === 'string' && init.token.length > 0
}

Type guard

function hasValidToken(token: unknown): token is string {
  return typeof token === 'string' && token.length > 0
}

Try / catch

const res = await fetch(BACKUP_URL, { headers: { Authorization: `Bearer ${token}` } })
if (res.status === 401) {
  token = await reauthenticate() // refresh session/token then retry once
  return fetch(BACKUP_URL, { headers: { Authorization: `Bearer ${token}` } })
}

Prevention

When it happens

Trigger: Request to the backup endpoint with no Authorization header and no 'presentation-metadata-Token' cookie, or with an empty/non-string cookie value.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of hcengineering/platform@63e28dc964 (2026-08-29). Data as JSON: /api/errors/7055287af2702e76. Report an issue: GitHub.