import crypto from 'node:crypto'; import type { Request, Response, NextFunction } from 'express'; import { env } from '../config/env.js'; import { createLogger } from './logger.js'; const log = createLogger('owner-auth'); const COOKIE_NAME = 'owner_session'; const COOKIE_MAX_AGE_MS = 30 * 24 * 3600 * 1000; const OWNER_ID = 'owner'; function expectedCookieValue(): string { if (!env.ownerSecret) return ''; return crypto.createHmac('sha256', env.ownerSecret).update('owner').digest('hex'); } export function setOwnerCookie(res: Response): void { res.cookie(COOKIE_NAME, expectedCookieValue(), { httpOnly: true, secure: env.nodeEnv === 'production', sameSite: 'lax', maxAge: COOKIE_MAX_AGE_MS, path: '/', }); } export function clearOwnerCookie(res: Response): void { res.clearCookie(COOKIE_NAME, { path: '/' }); } /** True when the request carries a valid owner session cookie. */ export function isOwnerAuthenticated(req: Request): boolean { if (!env.ownerSecret) return false; const got = (req as any).cookies?.[COOKIE_NAME]; if (typeof got !== 'string' || got.length === 0) return false; const expected = expectedCookieValue(); if (got.length !== expected.length) return false; try { return crypto.timingSafeEqual(Buffer.from(got, 'hex'), Buffer.from(expected, 'hex')); } catch { return false; } } /** Returns the authenticated owner id or null. Always a single 'owner' id today. */ export function ownerIdFromRequest(req: Request): string | null { return isOwnerAuthenticated(req) ? OWNER_ID : null; } /** Express middleware: 401 if the request is not owner-authenticated. */ export function requireOwner(req: Request, res: Response, next: NextFunction): void { if (isOwnerAuthenticated(req)) return next(); res.status(401).json({ error: 'auth_required' }); } /** Validates an owner secret against env.ownerSecret in constant time. */ export function verifyOwnerSecret(input: unknown): boolean { if (!env.ownerSecret || typeof input !== 'string') return false; const a = Buffer.from(input); const b = Buffer.from(env.ownerSecret); if (a.length !== b.length) return false; return crypto.timingSafeEqual(a, b); } /** Logs a warning if owner auth isn't configured — Tesla routes will return 401. */ export function warnIfMisconfigured(): void { if (!env.ownerSecret) { log.warn('OWNER_SECRET is not set — Tesla routes will refuse all requests with 401. Set it to enable owner login.'); } }