initial commit
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
import { env } from '../config/env.js';
|
||||
import { verifyAccessToken } from '../services/token.service.js';
|
||||
|
||||
export async function authRequired(req, res, next) {
|
||||
try {
|
||||
let token = req.cookies?.[env.COOKIE_ACCESS_NAME];
|
||||
|
||||
// Если нет cookie — пробуем Authorization: Bearer <token>
|
||||
if (!token) {
|
||||
const auth = req.headers['authorization'];
|
||||
if (auth && auth.startsWith('Bearer ')) {
|
||||
token = auth.slice(7);
|
||||
}
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({ error: 'UNAUTHORIZED', message: 'Access token is missing' });
|
||||
}
|
||||
|
||||
const payload = await verifyAccessToken(token);
|
||||
|
||||
req.user = {
|
||||
id: payload.sub,
|
||||
role: payload.role,
|
||||
email: payload.email,
|
||||
sessionId: payload.sid,
|
||||
};
|
||||
|
||||
next();
|
||||
} catch (err) {
|
||||
return res.status(401).json({ error: 'UNAUTHORIZED', message: 'Invalid access token' });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { env } from '../config/env.js';
|
||||
|
||||
export function csrfRequired(req, res, next) {
|
||||
const cookieToken = req.cookies?.[env.COOKIE_CSRF_NAME];
|
||||
const headerToken = req.get('x-csrf-token');
|
||||
|
||||
// Для SameSite cookie защита уже встроена в браузер
|
||||
// Если cookie есть и SameSite установлен - это уже защита от CSRF
|
||||
// Дополнительная проверка заголовка для обратной совместимости
|
||||
if (cookieToken) {
|
||||
// Cookie с SameSite=Lax/Strict уже защищает от CSRF
|
||||
// Если заголовок есть - проверяем совпадение (double submit pattern)
|
||||
if (headerToken && cookieToken !== headerToken) {
|
||||
return res.status(403).json({
|
||||
error: 'CSRF_INVALID',
|
||||
message: 'CSRF token mismatch',
|
||||
});
|
||||
}
|
||||
// Если заголовка нет - разрешаем (SameSite cookie защищает)
|
||||
return next();
|
||||
}
|
||||
|
||||
// Cookie нет - это ошибка
|
||||
return res.status(403).json({
|
||||
error: 'CSRF_INVALID',
|
||||
message: 'CSRF token is missing',
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
export function errorHandler(err, req, res, next) {
|
||||
const statusCode = err.statusCode || 500;
|
||||
const code = err.code || 'INTERNAL_ERROR';
|
||||
|
||||
console.error('[BFF ERROR]', {
|
||||
code,
|
||||
statusCode,
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
});
|
||||
|
||||
const response = {
|
||||
ok: false,
|
||||
error: code,
|
||||
message: err.message || 'Internal server error',
|
||||
};
|
||||
|
||||
// Добавляем детали валидации если есть
|
||||
if (err.details && Array.isArray(err.details)) {
|
||||
response.details = err.details;
|
||||
}
|
||||
|
||||
res.status(statusCode).json(response);
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
const ALLOWED_MIME_TYPES = new Set([
|
||||
'image/jpeg',
|
||||
'image/jpg',
|
||||
'image/png',
|
||||
'image/gif',
|
||||
'image/webp',
|
||||
]);
|
||||
|
||||
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MB
|
||||
|
||||
export function validateImage(req, res, next) {
|
||||
const file = req.file;
|
||||
|
||||
if (!file) {
|
||||
return res.status(400).json({
|
||||
ok: false,
|
||||
error: 'BAD_REQUEST',
|
||||
message: 'Файл не предоставлен',
|
||||
});
|
||||
}
|
||||
|
||||
if (!ALLOWED_MIME_TYPES.has(file.mimetype)) {
|
||||
return res.status(400).json({
|
||||
ok: false,
|
||||
error: 'INVALID_FILE_TYPE',
|
||||
message: `Недопустимый тип файла. Разрешены: JPEG, PNG, GIF, WebP`,
|
||||
});
|
||||
}
|
||||
|
||||
if (file.size > MAX_FILE_SIZE) {
|
||||
return res.status(400).json({
|
||||
ok: false,
|
||||
error: 'FILE_TOO_LARGE',
|
||||
message: `Файл слишком большой. Максимум: ${MAX_FILE_SIZE / 1024 / 1024}MB`,
|
||||
});
|
||||
}
|
||||
|
||||
const filename = file.originalname || file.filename;
|
||||
const ext = filename.split('.').pop().toLowerCase();
|
||||
const validExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
|
||||
|
||||
if (!validExtensions.includes(ext)) {
|
||||
return res.status(400).json({
|
||||
ok: false,
|
||||
error: 'INVALID_EXTENSION',
|
||||
message: 'Недопустимое расширение файла',
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
||||
Reference in New Issue
Block a user