Files
2026-08-01 14:01:17 +02:00

49 lines
1.6 KiB
TypeScript

import type { NextApiRequest, NextApiResponse } from "next";
import bcrypt from "bcryptjs";
import { getDb } from "@/lib/db";
import { randomUUID } from "crypto";
import { isRateLimited } from "@/lib/rate-limit";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== "POST") return res.status(405).end();
// Invite code + password are both guessable secrets — throttle attempts per IP.
if (isRateLimited(req, "signup", 10, 15 * 60 * 1000)) {
return res.status(429).json({ error: "Too many attempts. Try again later." });
}
const { email, password, inviteCode } = req.body as {
email?: string;
password?: string;
inviteCode?: string;
};
if (!email || !password || !inviteCode) {
return res.status(400).json({ error: "Email, password, and invite code are required." });
}
const authPassword = process.env.AUTH_PASSWORD;
if (!authPassword) {
return res.status(500).json({ error: "AUTH_PASSWORD is not configured on this server." });
}
if (inviteCode !== authPassword) {
return res.status(403).json({ error: "Invalid invite code." });
}
if (password.length < 8) {
return res.status(400).json({ error: "Password must be at least 8 characters." });
}
const db = getDb();
const existing = db.prepare("SELECT id FROM users WHERE email = ?").get(email);
if (existing) {
return res.status(409).json({ error: "An account with this email already exists." });
}
const hash = await bcrypt.hash(password, 10);
db.prepare("INSERT INTO users (id, email, password_hash) VALUES (?, ?, ?)").run(randomUUID(), email, hash);
return res.status(201).json({ ok: true });
}