121 lines
4.4 KiB
JavaScript
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;
|
|
};
|