jefflix-website/lib/token.ts

59 lines
1.5 KiB
TypeScript

import { createHmac } from 'crypto'
interface ApprovePayload {
channels: { id: string; name: string }[]
email: string
exp: number
}
const SEVEN_DAYS_MS = 7 * 24 * 60 * 60 * 1000
function getSecret(): string {
const secret = process.env.TOKEN_SECRET
if (!secret) throw new Error('TOKEN_SECRET env var is not set')
return secret
}
export function createApproveToken(
channels: { id: string; name: string }[],
email: string,
): string {
const payload: ApprovePayload = {
channels,
email,
exp: Date.now() + SEVEN_DAYS_MS,
}
const data = Buffer.from(JSON.stringify(payload)).toString('base64url')
const sig = createHmac('sha256', getSecret()).update(data).digest('hex')
return `${data}.${sig}`
}
export function verifyApproveToken(token: string): ApprovePayload | null {
const parts = token.split('.')
if (parts.length !== 2) return null
const [data, sig] = parts
const expected = createHmac('sha256', getSecret()).update(data).digest('hex')
// Constant-time comparison
if (sig.length !== expected.length) return null
let mismatch = 0
for (let i = 0; i < sig.length; i++) {
mismatch |= sig.charCodeAt(i) ^ expected.charCodeAt(i)
}
if (mismatch !== 0) return null
try {
const payload: ApprovePayload = JSON.parse(
Buffer.from(data, 'base64url').toString('utf-8'),
)
if (!payload.exp || !payload.channels || !payload.email) return null
if (Date.now() > payload.exp) return null
return payload
} catch {
return null
}
}