import {Model, Connection, Document} from 'mongoose'; import {Injectable, NotFoundException, BadGatewayException, ConflictException, BadRequestException, NotAcceptableException} from '@nestjs/common'; import {InjectConnection} from '@nestjs/mongoose'; import {DB_NAME, USERS_CONTROLLER, SECRET_JWT_ACCESS_KEY, SECRET_JWT_REFRESH_KEY} from 'src/consts'; import {User, UserSchema, CreateUserRequest, UserResponse, UserModel, UpdateUserRequest, TokenResponse, UpdateUserSelf} from './users.schema'; import * as bcrypt from 'bcrypt'; import * as jwt from 'jsonwebtoken'; import {Token} from './types'; const validateModel = async (user: Document) => { try { await user.validate(); } catch (e) { throw new BadGatewayException(e); } }; const prepareUserToUserModel = (user: User): UserModel => ({ login: user.login, avatar: user.avatar, password: user.password, is_admin: user.is_admin, }); const prepareUserToUserResponse = (user: User): UserResponse => ({ login: user.login, avatar: user.avatar, is_admin: user.is_admin, }); @Injectable() export class UserService { constructor( @InjectConnection(DB_NAME) private dbConnection: Connection, ) {} userModel(): Model { return this.dbConnection.model(USERS_CONTROLLER, UserSchema); } async findUser(login: string): Promise { const user = await this.userModel().findOne({login}); return user ? prepareUserToUserModel(user) : user; } async findOne(login: string): Promise { const user = await this.userModel().findOne({login}); if (!user) { throw new NotFoundException(`Not found user "${login}"`); } return prepareUserToUserResponse(user); } async findAll(): Promise { const users = await this.userModel().find().exec(); return users.map(prepareUserToUserResponse); } async create(user: CreateUserRequest, requesterLogin: string): Promise { const requester = await this.findUser(requesterLogin); if (!requester.is_admin) { throw new NotAcceptableException(`Действие запрещено`); } const searchUser = await this.findUser(user.login); if (searchUser) { throw new ConflictException(`User login "${user.login}" is already in use`); } const Model = await this.userModel(); try { const checkUser = new Model(user); await validateModel(checkUser); } catch (e) { if (e?.message?.includes('validation failed')) { throw new BadRequestException(e.message); } throw e; } const salt = await bcrypt.genSalt(10); const password = await bcrypt.hash(user.password, salt); const createUser = new Model({ ...user, salt, password, }); const savedUser = await createUser.save(); return prepareUserToUserResponse(savedUser); } async update(user: UpdateUserRequest): Promise { if (user.login === 'admin') { throw new BadRequestException('Запрещено менять пользователя admin'); } const searchUser = await this.userModel().findOne({login: user.login}); if (!searchUser) { throw new NotFoundException(`Not found user login "${user.login}"`) } const Model = await this.userModel(); const updateUser = new Model({ ...user, password: searchUser.password, }); try { await validateModel(updateUser); } catch (e) { if (e?.message?.includes('validation failed')) { throw new BadRequestException(e.message); } throw e; } await searchUser.updateOne({ is_admin: user.is_admin, avatar: user.avatar, }); return prepareUserToUserResponse(updateUser); } async removeOne(login: string, requesterLogin: string): Promise { const requester = await this.findUser(requesterLogin); if (!requester.is_admin) { throw new NotAcceptableException(`Действие запрещено`); } if (login === 'admin') { throw new BadRequestException('Запрещено удалять пользователя admin'); } const searchUser = await this.userModel().findOne({login}); if (!searchUser) { throw new NotFoundException(`Not found user login "${login}"`); } await this.userModel().deleteOne({login}); return prepareUserToUserResponse(searchUser); } checkPassword(password: string, hash: string): Promise { return bcrypt.compare(password, hash); } generateTokens(login: string, agent: string): TokenResponse { const access_token = jwt.sign({ login, agent, iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + (60 * 5), }, SECRET_JWT_ACCESS_KEY); const refresh_token = jwt.sign({ login, agent, iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + (60 * 60 * 24 * 3), }, SECRET_JWT_REFRESH_KEY); return { access_token, refresh_token, }; } async authUser(login: string, password: string, agent: string): Promise { const searchUser = await this.findUser(login); if (searchUser && await this.checkPassword(password, searchUser.password)) { return this.generateTokens(login, agent); } throw new BadRequestException('Не верный логин или пароль'); } verifyToken(token: string, secret: string): void { try { jwt.verify(token, secret); } catch (e) { throw new BadRequestException('Authorization is outdated'); } } checkToken(token: Token, agent: string): boolean { return token.agent === agent; } async refreshAuth(refresh_token: string, agent: string): Promise { this.verifyToken(refresh_token, SECRET_JWT_REFRESH_KEY); const token = jwt.decode(refresh_token) as Token; const searchUser = await this.findUser(token.login); if (searchUser && this.checkToken(token, agent)) { return this.generateTokens(token.login, agent); } throw new BadRequestException('Unauthorized request'); } async checkAccessToken(access_token: string, agent: string): Promise { try { this.verifyToken(access_token, SECRET_JWT_ACCESS_KEY); } catch (e) { return false; } const token = jwt.decode(access_token) as Token; const searchUser = await this.findUser(token.login); return searchUser && this.checkToken(token, agent); } async findMe(access_token: string): Promise { const token = jwt.decode(access_token) as Token; return await this.findOne(token.login); } async updateUser(user: UpdateUserRequest, requesterLogin: string): Promise { const requester = await this.findUser(requesterLogin); if (!requester.is_admin) { throw new NotAcceptableException(`Действие запрещено`); } return await this.update(user); } async updateSelf(access_token: string, {avatar}: UpdateUserSelf): Promise { const {login} = jwt.decode(access_token) as Token; const requester = await this.findUser(login); return await this.update({ avatar, login, is_admin: requester.is_admin, }); } async changePassword(access_token: string, old_password: string, new_password: string): Promise { const {login} = jwt.decode(access_token) as Token; if (login === 'admin') { throw new BadRequestException('Запрещено менять пароль пользователя admin'); } const user = await this.userModel().findOne({login}); if (user && await this.checkPassword(old_password, user.password)) { const salt = user.salt; const password = await bcrypt.hash(new_password, salt); await user.updateOne({ password, }); return 'ok'; } throw new BadRequestException('Unauthorized request'); } }