import { createCipheriv, createDecipheriv, createHash, randomBytes } from 'node:crypto'; const IV_LEN = 12; const TAG_LEN = 16; function derive_key(secret: string): Buffer { return createHash('sha256').update(secret).digest(); } export function encode_token(secret: string, data: unknown): string { const key = derive_key(secret); const iv = randomBytes(IV_LEN); const cipher = createCipheriv('aes-256-gcm', key, iv); const plain = Buffer.from(JSON.stringify(data), 'utf8'); const ciphertext = Buffer.concat([cipher.update(plain), cipher.final()]); const tag = cipher.getAuthTag(); return Buffer.concat([iv, tag, ciphertext]).toString('base64url'); } export function decode_token(secret: string, token: string): T { const key = derive_key(secret); const raw = Buffer.from(token, 'base64url'); if (raw.length < IV_LEN + TAG_LEN) throw new Error('invalid token'); const iv = raw.subarray(0, IV_LEN); const tag = raw.subarray(IV_LEN, IV_LEN + TAG_LEN); const ciphertext = raw.subarray(IV_LEN + TAG_LEN); const decipher = createDecipheriv('aes-256-gcm', key, iv); decipher.setAuthTag(tag); const plain = Buffer.concat([decipher.update(ciphertext), decipher.final()]); return JSON.parse(plain.toString('utf8')) as T; }