import { randomBytes, scrypt } from "node:crypto"; import { jwtVerify, SignJWT } from "@panva/jose"; import { type AuthPayload, InvitePayload, isAuthPayload, isInvitePayload, } from "../model/interfaces.ts"; const jwtSecret = Deno.env.get("JWT_SECRET"); if (!jwtSecret) { throw new Error( "JWT_SECRET environment variable is required. Generate one with: openssl rand -hex 32", ); } const JWT_KEY = new TextEncoder().encode(jwtSecret); // ── Invite tokens ───────────────────────────────────────────────────────────── export async function createInviteToken(inviterId: string): Promise { return await new SignJWT({ purpose: "invite", inviterId }) .setProtectedHeader({ alg: "HS256" }) .setJti(crypto.randomUUID()) .setExpirationTime("7d") .sign(JWT_KEY); } export async function verifyInviteToken( token: string, ): Promise { try { const { payload } = await jwtVerify(token, JWT_KEY); if (!isInvitePayload(payload)) return null; return payload as InvitePayload; } catch { return null; } } export async function createJWT( payload: Omit, ): Promise { return await new SignJWT(payload) .setProtectedHeader({ alg: "HS256" }) .setExpirationTime("24h") .sign(JWT_KEY); } export async function verifyJWT(token: string): Promise { try { const { payload } = await jwtVerify(token, JWT_KEY); if (!isAuthPayload(payload)) { return null; } return payload; } catch (err) { console.error("JWT verification failed:", err); return null; } } export function hashPassword(password: string): Promise { const salt = randomBytes(16).toString("hex"); return new Promise((resolve, reject) => { scrypt(password, salt, 64, (err, derivedKey) => { if (err) reject(err); else resolve(`${derivedKey.toString("hex")}.${salt}`); }); }); } export function verifyPassword( password: string, storedHash: string, ): Promise { const [hash, salt] = storedHash.split("."); return new Promise((resolve, reject) => { scrypt(password, salt, 64, (err, derivedKey) => { if (err) reject(err); else resolve(hash === derivedKey.toString("hex")); }); }); }