This repository has been archived on 2026-05-01. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Nest/nest-backend/src/routes/auth.ts
2026-02-28 14:20:09 +01:00

116 lines
3.3 KiB
TypeScript

import { Router, Request, Response } from 'express';
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
import { z } from 'zod';
import prisma from '../lib/prisma.js';
import { authenticate } from '../middleware/auth.js';
const router = Router();
const loginSchema = z.object({
email: z.string().email(),
password: z.string().min(1),
});
const registerSchema = z.object({
username: z.string().min(2).max(32),
email: z.string().email(),
password: z.string().min(6),
});
function signToken(userId: string, role: string, isAdmin: boolean): string {
return jwt.sign({ userId, role, isAdmin }, process.env.JWT_SECRET!, { expiresIn: '7d' });
}
function safeUser(user: { id: string; username: string; email: string; role: string; isAdmin: boolean; isBanned: boolean; avatarUrl: string | null; createdAt: Date }) {
return {
id: user.id,
username: user.username,
email: user.email,
role: user.role,
isAdmin: user.isAdmin,
isBanned: user.isBanned,
avatarUrl: user.avatarUrl,
createdAt: user.createdAt.toISOString(),
};
}
// POST /api/auth/login
router.post('/login', async (req: Request, res: Response): Promise<void> => {
const parsed = loginSchema.safeParse(req.body);
if (!parsed.success) {
res.status(400).json({ error: parsed.error.flatten() });
return;
}
const { email, password } = parsed.data;
const user = await prisma.user.findUnique({ where: { email: email.toLowerCase() } });
if (!user) {
res.status(401).json({ error: 'No account found with that email address.' });
return;
}
const valid = await bcrypt.compare(password, user.password);
if (!valid) {
res.status(401).json({ error: 'Incorrect password.' });
return;
}
if (user.isBanned) {
res.status(403).json({ error: 'This account has been suspended.' });
return;
}
const token = signToken(user.id, user.role, user.isAdmin);
res.json({ token, user: safeUser(user) });
});
// POST /api/auth/register (public site only — creates role=user)
router.post('/register', async (req: Request, res: Response): Promise<void> => {
const parsed = registerSchema.safeParse(req.body);
if (!parsed.success) {
res.status(400).json({ error: parsed.error.flatten() });
return;
}
const { username, email, password } = parsed.data;
const emailTaken = await prisma.user.findUnique({ where: { email: email.toLowerCase() } });
if (emailTaken) {
res.status(409).json({ error: 'An account with this email already exists.' });
return;
}
const usernameTaken = await prisma.user.findUnique({ where: { username } });
if (usernameTaken) {
res.status(409).json({ error: 'This username is already taken.' });
return;
}
const hashed = await bcrypt.hash(password, 10);
const user = await prisma.user.create({
data: {
username,
email: email.toLowerCase(),
password: hashed,
role: 'user',
},
});
const token = signToken(user.id, user.role, user.isAdmin);
res.status(201).json({ token, user: safeUser(user) });
});
// GET /api/auth/me
router.get('/me', authenticate, async (req: Request, res: Response): Promise<void> => {
const user = await prisma.user.findUnique({ where: { id: req.user!.userId } });
if (!user) {
res.status(404).json({ error: 'User not found' });
return;
}
res.json(safeUser(user));
});
export default router;