import { Router } from 'express'; import { env } from '../config/env.js'; import { loginUser, logoutUser, refreshUserSession, registerUser, loginWithTelegram } from '../services/auth.service.js'; import { authRequired } from '../middleware/authRequired.js'; import { findUserById, updateUserBalance } from '../repositories/user.repository.js'; const router = Router(); function buildAccessCookieOptions() { return { httpOnly: true, secure: env.COOKIE_SECURE, sameSite: env.COOKIE_SAME_SITE, // Domain не указывается для __Host- префикса (требование RFC 6265) path: '/', maxAge: env.ACCESS_TOKEN_TTL_SEC * 1000, }; } function buildRefreshCookieOptions() { return { httpOnly: true, secure: env.COOKIE_SECURE, sameSite: env.COOKIE_SAME_SITE, // Domain не указывается для __Host- префикса (требование RFC 6265) path: '/', maxAge: env.REFRESH_TOKEN_TTL_SEC * 1000, }; } function buildCsrfCookieOptions() { return { httpOnly: false, // фронту надо прочитать и отправить в header secure: env.COOKIE_SECURE, sameSite: env.COOKIE_SAME_SITE, // Domain не указывается для работы с __Host- префиксом path: '/', maxAge: env.REFRESH_TOKEN_TTL_SEC * 1000, }; } router.post('/login', async (req, res, next) => { try { const { email, password } = req.body; const result = await loginUser({ email, password, userAgent: req.get('user-agent') || null, ipAddress: req.ip || null, }); res.cookie(env.COOKIE_ACCESS_NAME, result.accessToken, buildAccessCookieOptions()); res.cookie(env.COOKIE_REFRESH_NAME, result.refreshToken, buildRefreshCookieOptions()); res.cookie(env.COOKIE_CSRF_NAME, result.csrfToken, buildCsrfCookieOptions()); res.json({ ok: true, accessToken: result.accessToken, user: result.user, }); } catch (err) { next(err); } }); router.post('/logout', authRequired, async (req, res, next) => { try { await logoutUser({ sessionId: req.user.sessionId }); res.clearCookie(env.COOKIE_ACCESS_NAME, { path: '/' }); res.clearCookie(env.COOKIE_REFRESH_NAME, { path: '/' }); res.clearCookie(env.COOKIE_CSRF_NAME, { path: '/' }); res.json({ ok: true }); } catch (err) { next(err); } }); router.post('/refresh', async (req, res, next) => { try { const refreshToken = req.cookies?.[env.COOKIE_REFRESH_NAME]; if (!refreshToken) { return res.status(401).json({ error: 'REFRESH_TOKEN_MISSING', message: 'Refresh token is missing', }); } const result = await refreshUserSession({ refreshToken, userAgent: req.get('user-agent') || null, ipAddress: req.ip || null, }); res.cookie(env.COOKIE_ACCESS_NAME, result.accessToken, buildAccessCookieOptions()); res.cookie(env.COOKIE_REFRESH_NAME, result.refreshToken, buildRefreshCookieOptions()); res.cookie(env.COOKIE_CSRF_NAME, result.csrfToken, buildCsrfCookieOptions()); res.json({ ok: true, user: result.user, }); } catch (err) { next(err); } }); router.get('/me', authRequired, async (req, res) => { try { const dbUser = await findUserById(req.user.id); if (!dbUser) return res.status(401).json({ error: 'USER_NOT_FOUND' }); res.json({ ok: true, user: { id: dbUser.id, email: dbUser.email, displayName: dbUser.display_name, role: dbUser.role, status: dbUser.status, balance: Number(dbUser.balance || 0), createdAt: dbUser.created_at, sessionId: req.user.sessionId, }, }); } catch (err) { res.status(500).json({ error: err.message }); } }); router.post('/balance/update', authRequired, async (req, res) => { const delta = Number(req.body.delta); if (!delta || isNaN(delta)) return res.status(400).json({ error: 'delta required' }); try { const newBalance = await updateUserBalance(req.user.id, delta); if (newBalance === null) return res.status(404).json({ error: 'User not found' }); res.json({ ok: true, balance: Number(newBalance) }); } catch (err) { res.status(500).json({ error: err.message }); } }); router.get('/csrf', authRequired, async (req, res) => { res.json({ ok: true, csrfToken: req.cookies?.[env.COOKIE_CSRF_NAME] || null, }); }); router.post('/register', async (req, res, next) => { try { const { email, password, name } = req.body; const result = await registerUser({ email, password, displayName: name || null, userAgent: req.get('user-agent') || null, ipAddress: req.ip || null, }); res.cookie(env.COOKIE_ACCESS_NAME, result.accessToken, buildAccessCookieOptions()); res.cookie(env.COOKIE_REFRESH_NAME, result.refreshToken, buildRefreshCookieOptions()); res.cookie(env.COOKIE_CSRF_NAME, result.csrfToken, buildCsrfCookieOptions()); res.status(201).json({ ok: true, accessToken: result.accessToken, user: result.user, }); } catch (err) { if (err.statusCode === 409) { return res.status(409).json({ ok: false, error: err.code, message: err.message }); } if (err.statusCode === 400) { return res.status(400).json({ ok: false, error: err.code, message: err.message }); } next(err); } }); router.post('/telegram', async (req, res, next) => { try { const result = await loginWithTelegram({ telegramData: req.body, userAgent: req.get('user-agent') || null, ipAddress: req.ip || null, }); res.cookie(env.COOKIE_ACCESS_NAME, result.accessToken, buildAccessCookieOptions()); res.cookie(env.COOKIE_REFRESH_NAME, result.refreshToken, buildRefreshCookieOptions()); res.cookie(env.COOKIE_CSRF_NAME, result.csrfToken, buildCsrfCookieOptions()); res.status(200).json({ ok: true, accessToken: result.accessToken, user: result.user }); } catch (err) { const status = err.statusCode || 500; if (status < 500) { return res.status(status).json({ ok: false, error: err.code, message: err.message }); } next(err); } }); router.get('/telegram-widget', (req, res) => { res.setHeader('Content-Type', 'text/html; charset=utf-8'); res.send('\n
Войдите через Telegram
'); }); export default router;