100 lines
4.0 KiB
TypeScript
100 lines
4.0 KiB
TypeScript
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
const bcrypt = require("bcryptjs");
|
|
import { createUser, getUserByEmail, getUserById, updateUser, updateUserPassword } from "./db";
|
|
|
|
export function requireAuth(req: any, res: any, next: any) {
|
|
if (!(req.session as any)?.userId) {
|
|
return res.status(401).json({ error: "Not authenticated" });
|
|
}
|
|
next();
|
|
}
|
|
|
|
export function registerAuthRoutes(app: any) {
|
|
// POST /api/auth/register
|
|
app.post("/api/auth/register", async (req: any, res: any) => {
|
|
const { name, email, password } = req.body;
|
|
if (!name || !email || !password || password.length < 6) {
|
|
return res.status(400).json({ error: "Name, email, and password (min 6 chars) are required" });
|
|
}
|
|
const existing = await getUserByEmail(email.toLowerCase());
|
|
if (existing) {
|
|
return res.status(409).json({ error: "Email already in use" });
|
|
}
|
|
const hash = await bcrypt.hash(password, 10);
|
|
const user = await createUser({ name, email: email.toLowerCase(), passwordHash: hash });
|
|
(req.session as any).userId = user.id;
|
|
res.status(201).json({ id: user.id, name: user.name, email: user.email });
|
|
});
|
|
|
|
// POST /api/auth/login
|
|
app.post("/api/auth/login", async (req: any, res: any) => {
|
|
const { email, password } = req.body;
|
|
if (!email || !password) {
|
|
return res.status(400).json({ error: "Email and password are required" });
|
|
}
|
|
const user = await getUserByEmail(email.toLowerCase());
|
|
if (!user) {
|
|
return res.status(401).json({ error: "Invalid email or password" });
|
|
}
|
|
const valid = await bcrypt.compare(password, user.passwordHash);
|
|
if (!valid) {
|
|
return res.status(401).json({ error: "Invalid email or password" });
|
|
}
|
|
(req.session as any).userId = user.id;
|
|
res.json({ id: user.id, name: user.name, email: user.email });
|
|
});
|
|
|
|
// POST /api/auth/logout
|
|
app.post("/api/auth/logout", (req: any, res: any) => {
|
|
req.session.destroy(() => { res.json({ ok: true }); });
|
|
});
|
|
|
|
// GET /api/auth/me
|
|
app.get("/api/auth/me", requireAuth, async (req: any, res: any) => {
|
|
const user = await getUserById((req.session as any).userId);
|
|
if (!user) {
|
|
req.session.destroy();
|
|
return res.status(401).json({ error: "Not authenticated" });
|
|
}
|
|
res.json({ id: user.id, name: user.name, email: user.email });
|
|
});
|
|
|
|
// PUT /api/auth/profile
|
|
app.put("/api/auth/profile", requireAuth, async (req: any, res: any) => {
|
|
const { name, email } = req.body;
|
|
const updates: any = {};
|
|
if (name) updates.name = name;
|
|
if (email) {
|
|
const existing = await getUserByEmail(email.toLowerCase());
|
|
if (existing && existing.id !== (req.session as any).userId) {
|
|
return res.status(409).json({ error: "Email already in use" });
|
|
}
|
|
updates.email = email.toLowerCase();
|
|
}
|
|
const user = await updateUser((req.session as any).userId, updates);
|
|
res.json({ id: user.id, name: user.name, email: user.email });
|
|
});
|
|
|
|
// PUT /api/auth/password
|
|
app.put("/api/auth/password", requireAuth, async (req: any, res: any) => {
|
|
const { currentPassword, newPassword } = req.body;
|
|
if (!currentPassword || !newPassword || newPassword.length < 6) {
|
|
return res.status(400).json({ error: "Current password and new password (min 6 chars) required" });
|
|
}
|
|
const user = await getUserById((req.session as any).userId);
|
|
if (!user) return res.status(401).json({ error: "Not authenticated" });
|
|
const valid = await bcrypt.compare(currentPassword, user.passwordHash);
|
|
if (!valid) return res.status(401).json({ error: "Current password is incorrect" });
|
|
const hash = await bcrypt.hash(newPassword, 10);
|
|
await updateUserPassword(user.id, hash);
|
|
res.json({ ok: true });
|
|
});
|
|
|
|
// POST /api/auth/request-reset (stub)
|
|
app.post("/api/auth/request-reset", async (req: any, res: any) => {
|
|
const { email } = req.body;
|
|
if (!email) return res.status(400).json({ error: "Email is required" });
|
|
res.json({ ok: true, message: "If an account exists, a reset link would be sent" });
|
|
});
|
|
}
|