import { Router } 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"; import logger from "../services/logger.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, }); router.post("/", changeLimiter, async (req, res) => { 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) => k.key === apiKey); if (!userKey) { res.status(401).json({ error: "Invalid API key." }); return; } const existing = keys.find((k) => k.email === cleanEmail); if (existing) { res.status(409).json({ error: "This email is already associated with another account." }); return; } const pending = await createPendingVerification(cleanEmail); sendVerificationEmail(cleanEmail, pending.code).catch((err) => { logger.error({ err, email: cleanEmail }, "Failed to send email change verification"); }); res.json({ status: "verification_sent", message: "Verification code sent to your new email address." }); }); router.post("/verify", changeLimiter, async (req, res) => { 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) => k.key === apiKey); if (!userKey) { res.status(401).json({ error: "Invalid API key." }); return; } const result = await verifyCode(cleanEmail, cleanCode); switch (result.status) { case "ok": { const updated = await 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 };