140 lines
5.0 KiB
JavaScript
140 lines
5.0 KiB
JavaScript
const router = require('express').Router();
|
|
const auth = require('../core/auth.core');
|
|
const jwt = require('../tools/jwt');
|
|
|
|
const CONSOLE_URL = process.env.CONSOLE_URL || 'http://localhost:3004';
|
|
const COOKIE_DOMAIN = process.env.COOKIE_DOMAIN || undefined;
|
|
|
|
// Validazione input
|
|
const USERNAME_REGEX = /^[a-zA-Z0-9_.\-]{3,50}$/;
|
|
const PASSWORD_MIN_LENGTH = 8;
|
|
const PASSWORD_MAX_LENGTH = 128;
|
|
|
|
router.post('/register', async (req, res) => {
|
|
const { username, password } = req.body;
|
|
|
|
if (!username || !password) {
|
|
return res.status(400).json({ error: 'Username e password richiesti' });
|
|
}
|
|
|
|
if (typeof username !== 'string' || typeof password !== 'string') {
|
|
return res.status(400).json({ error: 'Formato dati non valido' });
|
|
}
|
|
|
|
if (!USERNAME_REGEX.test(username)) {
|
|
return res.status(400).json({
|
|
error: 'Username non valido. 3-50 caratteri alfanumerici, underscore, punto o trattino.'
|
|
});
|
|
}
|
|
|
|
if (password.length < PASSWORD_MIN_LENGTH || password.length > PASSWORD_MAX_LENGTH) {
|
|
return res.status(400).json({
|
|
error: `Password deve essere tra ${PASSWORD_MIN_LENGTH} e ${PASSWORD_MAX_LENGTH} caratteri`
|
|
});
|
|
}
|
|
|
|
try {
|
|
await auth.register(username, password);
|
|
res.status(201).end();
|
|
} catch (err) {
|
|
console.error('[AUTH] Register failed:', err.message);
|
|
const status = err.message === 'User already exists' ? 409 : 500;
|
|
res.status(status).json({ error: err.message === 'User already exists' ? err.message : 'Errore interno' });
|
|
}
|
|
});
|
|
|
|
router.post('/login', async (req, res) => {
|
|
const { username, password, redirect, _csrf } = req.body;
|
|
|
|
const loginRedirect = (errorKey, safeRedirect) => {
|
|
const params = new URLSearchParams({ error: errorKey });
|
|
if (safeRedirect) params.set('redirect', safeRedirect);
|
|
return res.redirect(`/login?${params.toString()}`);
|
|
};
|
|
|
|
// Validazione CSRF (double-submit cookie)
|
|
const csrfCookie = req.cookies && req.cookies._csrf;
|
|
if (!_csrf || !csrfCookie || _csrf !== csrfCookie) {
|
|
return loginRedirect('csrf', '');
|
|
}
|
|
|
|
// Validazione base
|
|
if (!username || !password || typeof username !== 'string' || typeof password !== 'string') {
|
|
return loginRedirect('invalid_credentials', redirect || '');
|
|
}
|
|
|
|
// Limiti di lunghezza per prevenire abuse
|
|
if (username.length > 50 || password.length > PASSWORD_MAX_LENGTH) {
|
|
return loginRedirect('invalid_credentials', redirect || '');
|
|
}
|
|
|
|
// Validazione redirect URL per prevenire open redirect attacks
|
|
let safeRedirect = '';
|
|
if (redirect && typeof redirect === 'string') {
|
|
try {
|
|
const redirectUrl = new URL(redirect);
|
|
const consoleUrl = new URL(CONSOLE_URL);
|
|
if (redirectUrl.hostname !== consoleUrl.hostname) {
|
|
return loginRedirect('invalid_redirect', '');
|
|
}
|
|
safeRedirect = redirect;
|
|
} catch {
|
|
// URL relativo o non valido — ignora il redirect
|
|
}
|
|
}
|
|
|
|
try {
|
|
console.log('[DEBUG ROUTES] POST /api/auth/login START - username:', username);\n
|
|
const user = await auth.login(username, password);\n console.log('[DEBUG ROUTES] auth.login() success - user:', user);
|
|
|
|
const session = await auth.newSession(user.id, req.headers['user-agent'], req.ip);\n console.log('[DEBUG ROUTES] auth.newSession() success - session:', session);
|
|
|
|
const token = jwt.generateToken(user, session.id);\n console.log('[DEBUG ROUTES] jwt.generateToken() success');
|
|
|
|
const cookieOptions = {
|
|
httpOnly: true,
|
|
secure: process.env.NODE_ENV === 'production',
|
|
sameSite: 'lax',
|
|
maxAge: 7 * 24 * 60 * 60 * 1000
|
|
};
|
|
|
|
if (COOKIE_DOMAIN) {
|
|
cookieOptions.domain = COOKIE_DOMAIN;
|
|
}
|
|
|
|
res.cookie('auth_token', token, cookieOptions);
|
|
res.clearCookie('_csrf');
|
|
console.log('[DEBUG ROUTES] cookies set - redirecting to:', safeRedirect || CONSOLE_URL);
|
|
|
|
const destination = safeRedirect || CONSOLE_URL;
|
|
res.redirect(destination);
|
|
} catch (err) {
|
|
console.error('[DEBUG ROUTES] Login FAILED:', err.message, err.code, err);\n return loginRedirect('invalid_credentials', safeRedirect);
|
|
}
|
|
});
|
|
|
|
router.post('/logout', async (req, res) => {
|
|
const token = req.cookies && req.cookies.auth_token;
|
|
|
|
if (token) {
|
|
try {
|
|
const verified = jwt.verifyToken(token);
|
|
if (verified.valid) {
|
|
await auth.logout(verified.payload.session_id);
|
|
}
|
|
} catch (err) {
|
|
console.error('[AUTH] Logout error:', err.message);
|
|
}
|
|
}
|
|
|
|
const clearOptions = { httpOnly: true, sameSite: 'lax' };
|
|
if (COOKIE_DOMAIN) {
|
|
clearOptions.domain = COOKIE_DOMAIN;
|
|
}
|
|
|
|
res.clearCookie('auth_token', clearOptions);
|
|
res.redirect('/login');
|
|
});
|
|
|
|
module.exports = router;
|