/
h0tnanny
/
IotPlatform
Обзор
Документация
Войти
/
h0tnanny
/
IotPlatform
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
develop
src/api/controllers/UserController.ts
293 строки
9 KB
h0tnanny
исправление вдиимость пользователей
07 фев 2026, 00:12
07 фев 2026, 00:12
20981ca
Код
Авторство
О чём код?
import { Response } from 'express'; import bcrypt from 'bcrypt'; import { authConfig } from '../../config/auth'; import { UserRepository } from '../../repositories/UserRepository'; import { AuthRequest } from '../../middleware/auth'; const PASSWORD_REGEX = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+\-=[\]{};':"\\|,.<>/?]).{8,}$/; const USERNAME_REGEX = /^[a-zA-Z0-9_]{3,50}$/; const EMAIL_REGEX = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; export class UserController { private readonly userRepo: UserRepository; constructor() { this.userRepo = new UserRepository(); } /** * POST /users — создать пользователя (admin only) */ createUser = async (req: AuthRequest, res: Response): Promise<void> => { const { username, email, password, role, firstName, lastName, patronymic, position } = req.body; if (!username || !USERNAME_REGEX.test(username)) { res.status(400).json({ error: { code: 'VALIDATION_ERROR', message: 'Логин: 3-50 символов (буквы, цифры, _)' }, }); return; } 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 validRole = role === 'admin' || role === 'user' ? role : 'user'; const hash = await bcrypt.hash(password, authConfig.bcryptSaltRounds); const user = await this.userRepo.create({ username, email, passwordHash: hash, role: validRole, firstName: firstName || undefined, lastName: lastName || undefined, patronymic: patronymic || undefined, position: position || undefined, }); res.status(201).json({ data: { 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, }, }); }; /** * GET /users — список всех пользователей. * Все авторизованные пользователи видят полный список (нужно для групп и шаринга). */ getAll = async (_req: AuthRequest, res: Response): Promise<void> => { const users = await this.userRepo.findAll(); res.json({ data: users.map((u) => ({ id: u.id, username: u.username, email: u.email, role: u.role, firstName: u.first_name, lastName: u.last_name, patronymic: u.patronymic, position: u.position, isActive: u.is_active, createdAt: u.created_at, })), }); }; /** * PUT /users/:id — обновить профиль (ФИО, должность) */ updateProfile = async (req: AuthRequest, res: Response): Promise<void> => { const { id } = req.params; // Пользователь может обновить только свой профиль, admin — любой if (req.user?.role !== 'admin' && req.user?.userId !== id) { res.status(403).json({ error: { code: 'FORBIDDEN', message: 'Нет прав для редактирования этого профиля' }, }); return; } const { username, firstName, lastName, patronymic, position } = req.body; if (username !== undefined) { if (!username || !USERNAME_REGEX.test(String(username).trim())) { res.status(400).json({ error: { code: 'VALIDATION_ERROR', message: 'Логин: 3-50 символов (буквы, цифры, _)' }, }); return; } const existing = await this.userRepo.findByUsername(String(username).trim()); if (existing && existing.id !== id) { res.status(409).json({ error: { code: 'CONFLICT', message: 'Пользователь с таким логином уже существует' }, }); return; } } const user = await this.userRepo.update(id, { username: username !== undefined ? String(username).trim() : undefined, firstName, lastName, patronymic, position, }); if (!user) { res.status(404).json({ error: { code: 'NOT_FOUND', message: 'Пользователь не найден' }, }); return; } res.json({ data: { 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, }, }); }; /** * PUT /users/:id/password — сменить пароль */ changePassword = async (req: AuthRequest, res: Response): Promise<void> => { const { id } = req.params; if (req.user?.role !== 'admin' && req.user?.userId !== id) { res.status(403).json({ error: { code: 'FORBIDDEN', message: 'Нет прав для смены пароля' }, }); return; } const { currentPassword, newPassword } = req.body; // Обычный пользователь должен подтвердить текущий пароль if (req.user?.role !== 'admin') { const user = await this.userRepo.findById(id); if (!user) { res.status(404).json({ error: { code: 'NOT_FOUND', message: 'Пользователь не найден' }, }); return; } const isValid = await bcrypt.compare(currentPassword || '', user.password_hash); if (!isValid) { res.status(400).json({ error: { code: 'VALIDATION_ERROR', message: 'Неверный текущий пароль' }, }); return; } } if (!newPassword || !PASSWORD_REGEX.test(newPassword)) { res.status(400).json({ error: { code: 'VALIDATION_ERROR', message: 'Новый пароль должен содержать минимум 8 символов, включая заглавную букву, строчную букву, цифру и спецсимвол', }, }); return; } const hash = await bcrypt.hash(newPassword, authConfig.bcryptSaltRounds); const success = await this.userRepo.changePassword(id, hash); if (!success) { res.status(404).json({ error: { code: 'NOT_FOUND', message: 'Пользователь не найден' }, }); return; } res.json({ data: { message: 'Пароль успешно изменён' } }); }; /** * PUT /users/:id/role — сменить роль (admin only) */ changeRole = async (req: AuthRequest, res: Response): Promise<void> => { const { id } = req.params; const { role } = req.body; if (!role || !['admin', 'user'].includes(role)) { res.status(400).json({ error: { code: 'VALIDATION_ERROR', message: 'Роль должна быть "admin" или "user"' }, }); return; } // Нельзя изменить свою собственную роль if (req.user?.userId === id) { res.status(400).json({ error: { code: 'VALIDATION_ERROR', message: 'Нельзя изменить свою собственную роль' }, }); return; } const user = await this.userRepo.changeRole(id, role); if (!user) { res.status(404).json({ error: { code: 'NOT_FOUND', message: 'Пользователь не найден' }, }); return; } res.json({ data: { id: user.id, username: user.username, role: user.role, }, }); }; /** * DELETE /users/:id — удалить пользователя (admin only) */ deleteUser = async (req: AuthRequest, res: Response): Promise<void> => { const { id } = req.params; if (req.user?.userId === id) { res.status(400).json({ error: { code: 'VALIDATION_ERROR', message: 'Нельзя удалить свой аккаунт' }, }); return; } const success = await this.userRepo.delete(id); if (!success) { res.status(404).json({ error: { code: 'NOT_FOUND', message: 'Пользователь не найден' }, }); return; } res.json({ data: { message: 'Пользователь удалён' } }); }; }