Some checks failed
Deploy to Production / Deploy to Server (push) Failing after 20s
- Include compiled TypeScript with new /impressum, /privacy, /terms routes - Temporary commit of dist files for Docker deployment
82 lines
3.4 KiB
JavaScript
82 lines
3.4 KiB
JavaScript
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 };
|