import { RequestHandler } from 'express'; export type BillingType = 'stripe' | 'purchase_order'; export interface Customer { id: string; name: string; tierId: string; apiKey: string; stripeCustomerId?: string; multiplierOverride?: number; /** How this customer is billed. Absent means 'stripe' (the default). */ billingType?: BillingType; email?: string; /** scrypt hash for portal login. Absent = no portal password set yet. */ passwordHash?: string; /** Base32 TOTP secret. Present once 2FA setup begins. */ totpSecret?: string; /** True once the customer has confirmed a TOTP code. */ totpEnabled?: boolean; /** Prepaid balance in cents, drawn down when invoices are issued. */ balanceCents?: number; /** Email a copy of each issued invoice to the customer's address. */ emailInvoicing?: boolean; } export interface CustomerRepo { findByApiKey(apiKey: string): Customer | undefined; findByEmail(email: string): Customer | undefined; list(): Customer[]; save(customer: Customer): void; } export class InMemoryCustomerRepo implements CustomerRepo { private customers: Customer[]; constructor(customers: Customer[] = []) { // Defensive copy: save() replaces elements, and callers often pass shared // seed arrays (DEFAULT_CUSTOMERS) that must not be mutated across tests. this.customers = customers.map((c) => ({ ...c })); } findByApiKey(apiKey: string): Customer | undefined { return this.customers.find((c) => c.apiKey === apiKey); } findByEmail(email: string): Customer | undefined { const needle = email.toLowerCase(); return this.customers.find((c) => c.email?.toLowerCase() === needle); } list(): Customer[] { return [...this.customers]; } save(customer: Customer): void { const i = this.customers.findIndex((c) => c.id === customer.id); if (i >= 0) this.customers[i] = customer; else this.customers.push(customer); } } declare global { // eslint-disable-next-line @typescript-eslint/no-namespace namespace Express { interface Request { customer?: Customer; } } } export function apiKeyAuth(repo: CustomerRepo): RequestHandler { return (req, res, next) => { const key = req.header('x-api-key'); const customer = key ? repo.findByApiKey(key) : undefined; if (!customer) { res.status(401).json({ error: 'invalid or missing API key' }); return; } req.customer = customer; next(); }; }