/
viktorphp
/
back-socket
Обзор
Документация
Войти
/
viktorphp
/
back-socket
Код
Запросы
0
Задачи
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/services/auth/auth.service.ts
373 строки
12 KB
Viktorphp84
release-25-01: test
24 июл 2025, 19:11
24 июл 2025, 19:11
95699f1
Код
Авторство
О чём код?
import { Injectable, NotFoundException } from '@nestjs/common'; import { Response, Request } from 'express'; import { UsersService } from '../users/users.service'; import { JwtService } from '@nestjs/jwt'; import bcrypt from 'bcryptjs'; import { User } from '../../entities/users/user.entity'; import { ConfigService } from '@nestjs/config'; import { RegisterDto } from 'src/dto/register.dto'; interface JwtPayload { email: string; sub: string; username: string; } @Injectable() export class AuthService { constructor( private usersService: UsersService, private jwtService: JwtService, private configService: ConfigService, ) {} /** * Проверка пользователя по email * @param email - Email пользователя * @param password - Пароль пользователя * @returns - Объект пользователя или null, если пользователь не найден или пароль неверный */ async validateUserByEmail( email: string, password: string, ): Promise<Partial<User> | null> { const user = await this.usersService.getUserByEmail(email); if (user && (await bcrypt.compare(password, user.password))) { const { password: _, refreshToken: __, sessionId: ___, sessionExpiresAt: ____, ...userData } = user; return userData; } return null; } /** * Проверка пользователя по логину * @param login - Логин пользователя * @param password - Пароль пользователя * @returns - Объект пользователя или null, если пользователь не найден или пароль неверный */ async validateUserByLogin( login: string, password: string, ): Promise<Partial<User> | null> { // Проверяем, является ли login email-ом const isEmail = login.includes('@'); let user: User | null = null; if (isEmail) { user = await this.usersService.getUserByEmail(login); } else { user = await this.usersService.getUserByUsername(login); } if (user && (await bcrypt.compare(password, user.password))) { const { password: _, refreshToken: __, sessionId: ___, sessionExpiresAt: ____, ...userData } = user; return userData; } return null; } /** * Логин пользователя * @param user - Объект пользователя * @param response - Объект ответа * @returns - Объект со статусом 200 сообщением и объектом пользователя */ async login(user: User, response: Response) { const payload: JwtPayload = { email: user.email, sub: user.id, username: user.username, }; // Генерация accessToken const token = this.jwtService.sign(payload); // Генерация refreshToken const refreshToken = this.jwtService.sign(payload, { expiresIn: this.configService.get('jwt.refreshExpiresIn'), }); // Обновление refreshToken пользователя в базе данных await this.usersService.updateRefreshToken(user.id, refreshToken); // Установка куки access_token response.cookie('access_token', token, { httpOnly: true, secure: this.configService.get('jwt.nodeEnv') === 'production', sameSite: 'strict', maxAge: this.configService.get('jwt.expiresInMs'), // время жизни токена 1 минута }); // Установка куки refresh_token response.cookie('refresh_token', refreshToken, { httpOnly: true, secure: this.configService.get('jwt.nodeEnv') === 'production', sameSite: 'strict', maxAge: this.configService.get('jwt.refreshExpiresInMs'), // время жизни токена 1 день }); // Исключаем пароль из объекта пользователя const { password: _, refreshToken: __, sessionId: ___, sessionExpiresAt: ____, ...userData } = user; return { status: 200, message: 'Успешная авторизация', user: userData, }; } /** * Обновление токена из куки * @param request - Объект запроса * @param response - Объект ответа * @returns - Объект со статусом 200, сообщением и объектом пользователя */ async refreshToken(request: Request, response: Response) { const refreshToken = request.cookies['refresh_token']; if (!refreshToken) { throw new Error('Refresh токен не найден'); } let payload: JwtPayload; try { // Проверяем, можно ли валидировать токен this.jwtService.verify(refreshToken); } catch (e) { if (e.name === 'TokenExpiredError') { throw new Error( 'Срок действия refresh token`а истек. Необходима повторная авторизация', ); } else { throw new Error('Невалидный refreshтокен'); } } // Получение пользователя по id const user = await this.usersService.getUserById(payload.sub); // Проверка, что пользователь найден и refreshToken совпадает с тем, который в базе данных if (!user || user.refreshToken !== refreshToken) { throw new Error('Пользователь не найден'); } const newPayload: JwtPayload = { email: user.email, sub: user.id, username: user.username, }; // Генерация нового accessToken const newAccessToken = this.jwtService.sign(newPayload); // Генерация нового refreshToken const newRefreshToken = this.jwtService.sign(newPayload, { expiresIn: this.configService.get('jwt.refreshExpiresIn'), }); // Установка нового accessToken в куки response.cookie('access_token', newAccessToken, { httpOnly: true, secure: this.configService.get('jwt.nodeEnv') === 'production', sameSite: 'strict', maxAge: this.configService.get('jwt.expiresInMs'), }); // Установка нового refreshToken в куки response.cookie('refresh_token', newRefreshToken, { httpOnly: true, secure: this.configService.get('jwt.nodeEnv') === 'production', sameSite: 'strict', maxAge: this.configService.get('jwt.refreshExpiresInMs'), }); const { password: _, refreshToken: __, sessionId: ___, sessionExpiresAt: ____, ...userData } = user; return { status: 200, message: 'Токен успешно обновлен', user: userData, }; } async createSession(user: User, response: Response) { // Проверяем существование пользователя const existingUser = await this.usersService.getUserById(user.id); if (!existingUser) { throw new NotFoundException('Пользователь не найден'); } // Если у пользователя уже есть активная сессия, удаляем её if (existingUser.sessionId) { await this.usersService.updateSessionId(user.id, null); } // Создание новой сессии const sessionId = crypto.randomUUID(); // Обновление идентификатора сессии пользователя и времени её истечения await this.usersService.updateSessionId(user.id, sessionId); // Устанавливаем куки с sessionId response.cookie('sessionId', sessionId, { httpOnly: true, secure: this.configService.get('nodeEnv') === 'production', sameSite: 'strict', maxAge: this.configService.get('session.maxAge'), }); // Получаем обновленные данные пользователя const updatedUser = await this.usersService.getUserById(existingUser.id); // Исключаем конфиденциальные данные const { password: _, refreshToken: __, sessionId: ___, sessionExpiresAt: ____, ...userData } = updatedUser; return { status: 200, message: 'Сессия успешно создана', user: userData, }; } /** * Проверка авторизации по сессии * @param request - Объект запроса * @returns - Объект пользователя без конфиденциальных данных */ async validateSession(request: Request): Promise<Partial<User>> { const sessionId = request.cookies['sessionId'] || request.headers['sessionId']; if (!sessionId) { throw new Error('Сессия не найдена'); } const user = await this.usersService.getUserBySessionId(sessionId); if (!user) { throw new Error('Недействительная сессия'); } // Исключаем конфиденциальные данные const { refreshToken: __, sessionId: ___, sessionExpiresAt: ____, ...userData } = user; return userData; } /** * Логин пользователя * @param user - Объект пользователя * @param response - Объект ответа * @returns - Объект со статусом, сообщением и объектом пользователя */ async loginSession(user: User, response: Response) { // Создание сессии для пользователя await this.createSession(user, response); // Исключаем конфиденциальные данные из объекта пользователя const { password: _, refreshToken: __, sessionId: ___, sessionExpiresAt: ____, ...userData } = user; return { status: 200, message: 'Успешная авторизация', user: userData, }; } async register(user: RegisterDto, response: Response) { const newUser = await this.usersService.createUser(user); if (!newUser) { throw new Error('Не удалось создать пользователя'); } await this.createSession(newUser, response); const { password: _, refreshToken: __, sessionId: ___, sessionExpiresAt: ____, ...userData } = newUser; return { status: 200, message: 'Успешная регистрация', user: userData, }; } /** * Выход из сессии * @param request - Объект запроса * @param response - Объект ответа * @returns - Объект со статусом и сообщением */ async logout(request: Request, response: Response) { const sessionId = request.cookies['sessionId']; if (!sessionId) { throw new Error('Сессия не найдена'); } if (sessionId) { // Получаем пользователя по sessionId const user = await this.usersService.getUserBySessionId(sessionId); if (user) { // Очищаем sessionId в базе данных await this.usersService.updateSessionId(user.id, null); } } // Очищаем все куки response.clearCookie('sessionId', { httpOnly: true, secure: this.configService.get('nodeEnv') === 'production', sameSite: 'strict', }); return { status: 200, message: 'Успешный выход из системы', }; } }