import { Router } from "express"; import type { Request, Response } from "express"; import rateLimit from "express-rate-limit"; import { createPendingVerification, verifyCode } from "../services/verification.js"; import { sendVerificationEmail } from "../services/email.js"; import { getAllKeys, updateKeyEmail } from "../services/keys.js"; const router = Router(); const changeLimiter = rateLimit({ windowMs: 60 * 60 * 1000, max: 3, message: { error: "Too many attempts. Please try again in 1 hour." }, standardHeaders: true, legacyHeaders: false, }); // Step 1: Request email change — sends verification code to NEW email router.post("/", changeLimiter, async (req: Request, res: Response) => { const apiKey = req.headers.authorization?.replace(/^Bearer\s+/i, "") || req.body?.apiKey; const newEmail = req.body?.newEmail; if (!apiKey || typeof apiKey !== "string") { res.status(400).json({ error: "API key is required (Authorization header or body)." }); return; } if (!newEmail || typeof newEmail !== "string" || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(newEmail)) { res.status(400).json({ error: "A valid new email address is required." }); return; } const cleanEmail = newEmail.trim().toLowerCase(); const keys = getAllKeys(); const userKey = keys.find((k: any) => k.key === apiKey); if (!userKey) { res.status(401).json({ error: "Invalid API key." }); return; } const existing = keys.find((k: any) => k.email === cleanEmail); if (existing) { res.status(409).json({ error: "This email is already associated with another account." }); return; } const pending = createPendingVerification(cleanEmail); (pending as any)._changeContext = { apiKey, newEmail: cleanEmail, oldEmail: userKey.email }; sendVerificationEmail(cleanEmail, (pending as any).code).catch((err: Error) => { console.error(`Failed to send email change verification to ${cleanEmail}:`, err); }); res.json({ status: "verification_sent", message: "Verification code sent to your new email address." }); }); // Step 2: Verify code — updates email router.post("/verify", changeLimiter, async (req: Request, res: Response) => { const apiKey = req.headers.authorization?.replace(/^Bearer\s+/i, "") || req.body?.apiKey; const { newEmail, code } = req.body || {}; if (!apiKey || !newEmail || !code) { res.status(400).json({ error: "API key, new email, and code are required." }); return; } const cleanEmail = newEmail.trim().toLowerCase(); const cleanCode = String(code).trim(); const keys = getAllKeys(); const userKey = keys.find((k: any) => k.key === apiKey); if (!userKey) { res.status(401).json({ error: "Invalid API key." }); return; } const result = verifyCode(cleanEmail, cleanCode); switch (result.status) { case "ok": { const updated = updateKeyEmail(apiKey, cleanEmail); if (updated) { res.json({ status: "updated", message: "Email address updated successfully.", newEmail: cleanEmail }); } else { res.status(500).json({ error: "Failed to update email." }); } break; } case "expired": res.status(410).json({ error: "Verification code has expired. Please request a new one." }); break; case "max_attempts": res.status(429).json({ error: "Too many failed attempts. Please request a new code." }); break; case "invalid": res.status(400).json({ error: "Invalid verification code." }); break; } }); export { router as emailChangeRouter };