Bri-Tunes/src/routes/auth.js

121 lines
4.4 KiB
JavaScript

const express = require('express');
const { z } = require('zod');
const users = require('../services/users');
const mailer = require('../services/mailer');
const { requireUser } = require('../middleware/auth');
const registerSchema = z.object({
email: z.string().email().max(200),
display_name: z.string().trim().min(1).max(80),
password: z.string().min(8).max(200),
});
const loginSchema = z.object({
email: z.string().email().max(200),
password: z.string().min(1).max(200),
});
module.exports = function authRoutes(csrfProtection) {
const router = express.Router();
router.get('/register', (req, res) => {
if (req.session.user) return res.redirect('/');
res.render('auth/register', { title: 'Register', values: {}, errors: {} });
});
router.post('/register', csrfProtection, async (req, res, next) => {
try {
const parsed = registerSchema.safeParse(req.body);
if (!parsed.success) {
const errors = {};
for (const issue of parsed.error.issues) errors[issue.path[0]] = issue.message;
return res.status(400).render('auth/register', { title: 'Register', values: req.body, errors });
}
if (users.findByEmail(parsed.data.email)) {
return res.status(400).render('auth/register', {
title: 'Register', values: req.body, errors: { email: 'Email already registered.' },
});
}
const user = await users.createUser({
email: parsed.data.email,
displayName: parsed.data.display_name,
password: parsed.data.password,
});
req.session.user = user;
const token = users.createVerificationToken(user.id);
const base = process.env.APP_BASE_URL || 'http://localhost:3000';
mailer.sendVerificationEmail(user.email, `${base}/verify-email?token=${token}`)
.catch(err => console.error('[mailer] send failed:', err));
req.flash('success', `Welcome, ${user.displayName}! Check your email to verify your account.`);
res.redirect('/');
} catch (err) {
next(err);
}
});
router.get('/login', (req, res) => {
if (req.session.user) return res.redirect('/');
res.render('auth/login', { title: 'Log in', values: {}, error: null });
});
router.post('/login', csrfProtection, async (req, res, next) => {
try {
const parsed = loginSchema.safeParse(req.body);
if (!parsed.success) {
return res.status(400).render('auth/login', { title: 'Log in', values: req.body, error: 'Invalid input.' });
}
const user = await users.verifyCredentials(parsed.data.email, parsed.data.password);
if (!user) {
return res.status(401).render('auth/login', { title: 'Log in', values: req.body, error: 'Invalid email or password.' });
}
req.session.user = user;
const target = req.session.returnTo || '/';
delete req.session.returnTo;
req.flash('success', `Welcome back, ${user.displayName}.`);
res.redirect(target);
} catch (err) {
next(err);
}
});
router.post('/logout', csrfProtection, (req, res) => {
req.session.destroy(() => res.redirect('/'));
});
router.get('/verify-email', (req, res, next) => {
try {
const { token } = req.query;
if (!token || typeof token !== 'string') {
return res.render('auth/verify-email', { title: 'Verify Email', state: 'invalid' });
}
const result = users.verifyEmailToken(token);
if (!result.ok) {
return res.render('auth/verify-email', { title: 'Verify Email', state: 'invalid' });
}
if (req.session.user && req.session.user.id === result.userId) {
req.session.user = { ...req.session.user, emailVerified: true };
}
res.render('auth/verify-email', { title: 'Verify Email', state: 'success' });
} catch (err) { next(err); }
});
router.post('/resend-verification', requireUser, csrfProtection, (req, res, next) => {
try {
if (req.session.user.emailVerified) {
req.flash('success', 'Your email is already verified.');
return res.redirect('/');
}
const token = users.createVerificationToken(req.session.user.id);
const base = process.env.APP_BASE_URL || 'http://localhost:3000';
mailer.sendVerificationEmail(req.session.user.email, `${base}/verify-email?token=${token}`)
.catch(err => console.error('[mailer] resend failed:', err));
req.flash('success', 'Verification email sent. Check your inbox.');
res.redirect('/');
} catch (err) { next(err); }
});
return router;
};