All files / web/src/lib/auth integrationToken.ts

100% Statements 33/33
100% Branches 18/18
100% Functions 2/2
100% Lines 33/33

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 341x 1x 1x 1x 1x 1x 1x 1x 1x 34x 34x 34x 1x 1x 24x 24x 24x 24x 24x 24x 24x 24x 24x 24x 24x 24x 24x 24x 24x 24x 24x 24x 24x  
import { createHash, timingSafeEqual } from 'node:crypto'
import { isValidId } from '@/lib/kid-songs/eligibility'
 
export interface KidSongsAuth {
  configured: boolean
  tokenAccepted: boolean
  allowedPlayerIds: ReadonlySet<string>
}
 
function digest(value: string): Buffer {
  return createHash('sha256').update(value, 'utf8').digest()
}
 
export function authorizeKidSongsRequest(request: Request): KidSongsAuth {
  const expected = process.env.KID_SONGS_SYNC_TOKEN ?? ''
  const rawAllowlist = process.env.KID_SONGS_SYNC_PLAYER_IDS
  const entries = rawAllowlist?.split(',').map((value) => value.trim()) ?? []
  const allowlistValid =
    entries.length > 0 && entries.every((value) => value.length > 0 && isValidId(value))
  const configured = expected.length >= 16 && allowlistValid
 
  const header = request.headers.get('authorization') ?? ''
  const syntaxValid = header.startsWith('Bearer ') && header.length > 'Bearer '.length
  const presented = syntaxValid ? header.slice('Bearer '.length) : ''
  const tokenAccepted =
    configured && syntaxValid && timingSafeEqual(digest(presented), digest(expected))
 
  return {
    configured,
    tokenAccepted,
    allowedPlayerIds: allowlistValid ? new Set(entries) : new Set(),
  }
}