/
h0tnanny
/
IotPlatform
Обзор
Документация
Войти
/
h0tnanny
/
IotPlatform
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
develop
src/api/controllers/AuthController.ts
285 строк
9 KB
h0tnanny
Добавление функциональных требований
08 фев 2026, 23:02
08 фев 2026, 23:02
a8a23c5
Код
Авторство
О чём код?
import { Response } from 'express'; import bcrypt from 'bcrypt'; import jwt from 'jsonwebtoken'; import { authConfig } from '../../config/auth'; import { UserRepository } from '../../repositories/UserRepository'; import { RefreshTokenRepository } from '../../repositories/RefreshTokenRepository'; import { AuthRequest, JwtPayload } from '../../middleware/auth'; import { LogService } from '../../services/LogService'; const EMAIL_REGEX = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; const USERNAME_REGEX = /^[a-zA-Z0-9_]{3,50}$/; const PASSWORD_REGEX = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]).{8,}$/; export class AuthController { private readonly userRepo: UserRepository; private readonly refreshTokenRepo: RefreshTokenRepository; constructor() { this.userRepo = new UserRepository(); this.refreshTokenRepo = new RefreshTokenRepository(); } /** * POST /auth/register */ register = async (req: AuthRequest, res: Response): Promise<void> => { const { username, email, password } = req.body; // Валидация username if (!username || !USERNAME_REGEX.test(username)) { res.status(400).json({ error: { code: 'VALIDATION_ERROR', message: 'Логин должен содержать 3-50 символов (буквы, цифры, _)', }, }); return; } // Валидация email if (!email || !EMAIL_REGEX.test(email)) { res.status(400).json({ error: { code: 'VALIDATION_ERROR', message: 'Некорректный формат email', }, }); return; } // Валидация пароля if (!password || !PASSWORD_REGEX.test(password)) { res.status(400).json({ error: { code: 'VALIDATION_ERROR', message: 'Пароль должен содержать минимум 8 символов, включая заглавную букву, строчную букву, цифру и спецсимвол', }, }); return; } // Проверка уникальности const existingUsername = await this.userRepo.findByUsername(username); if (existingUsername) { res.status(409).json({ error: { code: 'CONFLICT', message: 'Пользователь с таким логином уже существует' }, }); return; } const existingEmail = await this.userRepo.findByEmail(email); if (existingEmail) { res.status(409).json({ error: { code: 'CONFLICT', message: 'Пользователь с таким email уже существует' }, }); return; } const passwordHash = await bcrypt.hash(password, authConfig.bcryptSaltRounds); const user = await this.userRepo.create({ username, email, passwordHash, }); const tokens = this.generateTokens(user.id, user.username, user.role); await this.saveRefreshToken(user.id, tokens.refreshToken); LogService.getInstance().info('auth', `Регистрация пользователя: ${username}`, { userId: user.id, }); res.status(201).json({ data: { user: this.sanitizeUser(user), accessToken: tokens.accessToken, refreshToken: tokens.refreshToken, }, }); }; /** * POST /auth/login */ login = async (req: AuthRequest, res: Response): Promise<void> => { const { login, password } = req.body; if (!login || !password) { res.status(400).json({ error: { code: 'VALIDATION_ERROR', message: 'Необходимы логин и пароль' }, }); return; } const user = await this.userRepo.findByUsernameOrEmail(login); if (!user) { LogService.getInstance().warning('auth', `Неудачная попытка входа: пользователь "${login}" не найден`); res.status(401).json({ error: { code: 'UNAUTHORIZED', message: 'Неверный логин или пароль' }, }); return; } if (!user.is_active) { res.status(403).json({ error: { code: 'FORBIDDEN', message: 'Аккаунт деактивирован' }, }); return; } const isPasswordValid = await bcrypt.compare(password, user.password_hash); if (!isPasswordValid) { LogService.getInstance().warning('auth', `Неудачная попытка входа: неверный пароль для "${user.username}"`, { userId: user.id, }); res.status(401).json({ error: { code: 'UNAUTHORIZED', message: 'Неверный логин или пароль' }, }); return; } const tokens = this.generateTokens(user.id, user.username, user.role); await this.saveRefreshToken(user.id, tokens.refreshToken); LogService.getInstance().info('auth', `Вход пользователя: ${user.username}`, { userId: user.id, }); res.json({ data: { user: this.sanitizeUser(user), accessToken: tokens.accessToken, refreshToken: tokens.refreshToken, }, }); }; /** * POST /auth/refresh */ refresh = async (req: AuthRequest, res: Response): Promise<void> => { const { refreshToken } = req.body; if (!refreshToken) { res.status(400).json({ error: { code: 'VALIDATION_ERROR', message: 'Refresh token не предоставлен' }, }); return; } try { const decoded = jwt.verify(refreshToken, authConfig.jwtRefreshSecret) as JwtPayload; const storedToken = await this.refreshTokenRepo.findByToken(refreshToken); if (!storedToken) { res.status(401).json({ error: { code: 'UNAUTHORIZED', message: 'Refresh token недействителен' }, }); return; } const user = await this.userRepo.findById(decoded.userId); if (!user || !user.is_active) { res.status(401).json({ error: { code: 'UNAUTHORIZED', message: 'Пользователь не найден' }, }); return; } // Удаляем старый refresh token await this.refreshTokenRepo.deleteByToken(refreshToken); // Генерируем новые токены const tokens = this.generateTokens(user.id, user.username, user.role); await this.saveRefreshToken(user.id, tokens.refreshToken); res.json({ data: { user: this.sanitizeUser(user), accessToken: tokens.accessToken, refreshToken: tokens.refreshToken, }, }); } catch (_err) { res.status(401).json({ error: { code: 'UNAUTHORIZED', message: 'Refresh token истёк или недействителен' }, }); } }; /** * POST /auth/logout */ logout = async (req: AuthRequest, res: Response): Promise<void> => { const { refreshToken } = req.body; if (refreshToken) { await this.refreshTokenRepo.deleteByToken(refreshToken); } if (req.user) { LogService.getInstance().info('auth', `Выход пользователя: ${req.user.username}`, { userId: req.user.userId, }); } res.json({ data: { message: 'Выход выполнен успешно' } }); }; /** * GET /auth/me */ me = async (req: AuthRequest, res: Response): Promise<void> => { if (!req.user) { res.status(401).json({ error: { code: 'UNAUTHORIZED', message: 'Необходима авторизация' }, }); return; } const user = await this.userRepo.findById(req.user.userId); if (!user) { res.status(404).json({ error: { code: 'NOT_FOUND', message: 'Пользователь не найден' }, }); return; } res.json({ data: this.sanitizeUser(user) }); }; private generateTokens(userId: string, username: string, role: string) { const payload: JwtPayload = { userId, username, role }; const accessToken = jwt.sign(payload, authConfig.jwtSecret, { expiresIn: authConfig.accessTokenExpiry, } as jwt.SignOptions); const refreshToken = jwt.sign(payload, authConfig.jwtRefreshSecret, { expiresIn: authConfig.refreshTokenExpiry, } as jwt.SignOptions); return { accessToken, refreshToken }; } private async saveRefreshToken(userId: string, token: string): Promise<void> { const expiresAt = new Date(Date.now() + authConfig.refreshTokenExpiryMs); await this.refreshTokenRepo.create(userId, token, expiresAt); } private sanitizeUser(user: { id: string; username: string; email: string; role: string; first_name: string | null; last_name: string | null; patronymic: string | null; position: string | null; is_active: boolean; created_at: Date }) { return { id: user.id, username: user.username, email: user.email, role: user.role, firstName: user.first_name, lastName: user.last_name, patronymic: user.patronymic, position: user.position, isActive: user.is_active, createdAt: user.created_at, }; } }