Files
notify/backend/src/routes/auth.ts
Michael Dong a98e12f286 first commit
2026-02-05 11:24:40 +08:00

94 lines
2.6 KiB
TypeScript

import { Router } from "express";
import bcrypt from "bcryptjs";
import jwt from "jsonwebtoken";
import { z } from "zod";
import { prisma } from "../db";
export const authRouter = Router();
const registerSchema = z.object({
username: z.string().min(3),
password: z.string().min(6),
inviteCode: z.string().min(4),
});
authRouter.post("/register", async (req, res) => {
const parsed = registerSchema.safeParse(req.body);
if (!parsed.success) {
return res.status(400).json({ error: "Invalid payload" });
}
const { username, password, inviteCode } = parsed.data;
const now = new Date();
try {
const result = await prisma.$transaction(async (tx) => {
const invite = await tx.invite.findFirst({
where: {
code: inviteCode,
revokedAt: null,
expiresAt: { gt: now },
},
});
if (!invite || invite.usedCount >= invite.maxUses) {
throw new Error("Invalid invite");
}
const existing = await tx.user.findUnique({ where: { username } });
if (existing) {
throw new Error("Username taken");
}
const passwordHash = await bcrypt.hash(password, 10);
const user = await tx.user.create({
data: { username, passwordHash },
});
await tx.invite.update({
where: { id: invite.id },
data: { usedCount: invite.usedCount + 1 },
});
return user;
});
const token = jwt.sign({ userId: result.id }, process.env.JWT_SECRET || "dev-secret", {
expiresIn: "7d",
});
return res.json({ token, user: { id: result.id, username: result.username } });
} catch (err) {
const message = err instanceof Error ? err.message : "Register failed";
const status = message === "Invalid invite" ? 400 : 409;
return res.status(status).json({ error: message });
}
});
const loginSchema = z.object({
username: z.string().min(3),
password: z.string().min(6),
});
authRouter.post("/login", async (req, res) => {
const parsed = loginSchema.safeParse(req.body);
if (!parsed.success) {
return res.status(400).json({ error: "Invalid payload" });
}
const { username, password } = parsed.data;
const user = await prisma.user.findUnique({ where: { username } });
if (!user) {
return res.status(401).json({ error: "Invalid credentials" });
}
const ok = await bcrypt.compare(password, user.passwordHash);
if (!ok) {
return res.status(401).json({ error: "Invalid credentials" });
}
const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET || "dev-secret", {
expiresIn: "7d",
});
return res.json({ token, user: { id: user.id, username: user.username } });
});