initial commit
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
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('<!DOCTYPE html>\n<html lang="ru"><head><meta charset="utf-8"/><title>Telegram Auth</title><style>*{margin:0;padding:0}body{display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;background:#1a1a2e;font-family:sans-serif;color:#fff}h2{font-size:24px;font-weight:700;margin-bottom:6px}p{font-size:13px;color:#aaa;margin-bottom:28px}.wrap{min-height:48px;display:flex;align-items:center}</style></head><body><h2>One Click</h2><p>Войдите через Telegram</p><div class="wrap" id="tg"></div><script>window.onTelegramAuth=function(u){if(window.opener){window.opener.postMessage({type:"telegram_auth",data:u},"*");}window.close();};</script><script async src="https://telegram.org/js/telegram-widget.js?22" data-telegram-login="generatet_bot" data-size="large" data-radius="12" data-onauth="onTelegramAuth(user)" data-request-access="write"></script></body></html>');
|
||||
});
|
||||
export default router;
|
||||
Reference in New Issue
Block a user