Some checks failed
Build & Deploy to Staging / Build & Deploy to Staging (push) Has been cancelled
- Add reusable header components (X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After) - Reference headers in 200 responses on all conversion and demo endpoints - Add Retry-After header to 429 responses - Update Rate Limits section in API description to mention response headers - Add comprehensive tests for header documentation (21 new tests) - All 809 tests passing
41 lines
1.9 KiB
JavaScript
41 lines
1.9 KiB
JavaScript
import { randomInt, timingSafeEqual } from "crypto";
|
|
import { queryWithRetry } from "./db.js";
|
|
const CODE_EXPIRY_MS = 15 * 60 * 1000;
|
|
const MAX_ATTEMPTS = 3;
|
|
export async function createPendingVerification(email) {
|
|
await queryWithRetry("DELETE FROM pending_verifications WHERE email = $1", [email]);
|
|
const now = new Date();
|
|
const pending = {
|
|
email,
|
|
code: String(randomInt(100000, 999999)),
|
|
createdAt: now.toISOString(),
|
|
expiresAt: new Date(now.getTime() + CODE_EXPIRY_MS).toISOString(),
|
|
attempts: 0,
|
|
};
|
|
await queryWithRetry("INSERT INTO pending_verifications (email, code, created_at, expires_at, attempts) VALUES ($1, $2, $3, $4, $5)", [pending.email, pending.code, pending.createdAt, pending.expiresAt, pending.attempts]);
|
|
return pending;
|
|
}
|
|
export async function verifyCode(email, code) {
|
|
const cleanEmail = email.trim().toLowerCase();
|
|
const result = await queryWithRetry("SELECT * FROM pending_verifications WHERE email = $1", [cleanEmail]);
|
|
const pending = result.rows[0];
|
|
if (!pending)
|
|
return { status: "invalid" };
|
|
if (new Date() > new Date(pending.expires_at)) {
|
|
await queryWithRetry("DELETE FROM pending_verifications WHERE email = $1", [cleanEmail]);
|
|
return { status: "expired" };
|
|
}
|
|
if (pending.attempts >= MAX_ATTEMPTS) {
|
|
await queryWithRetry("DELETE FROM pending_verifications WHERE email = $1", [cleanEmail]);
|
|
return { status: "max_attempts" };
|
|
}
|
|
await queryWithRetry("UPDATE pending_verifications SET attempts = attempts + 1 WHERE email = $1", [cleanEmail]);
|
|
const a = Buffer.from(pending.code, "utf8");
|
|
const b = Buffer.from(code, "utf8");
|
|
const codeMatch = a.length === b.length && timingSafeEqual(a, b);
|
|
if (!codeMatch) {
|
|
return { status: "invalid" };
|
|
}
|
|
await queryWithRetry("DELETE FROM pending_verifications WHERE email = $1", [cleanEmail]);
|
|
return { status: "ok" };
|
|
}
|