Files
auth-service/src/users/users.service.ts

263 lines
8.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<User> {
return this.dbConnection.model<User>(USERS_CONTROLLER, UserSchema);
}
async findUser(login: string): Promise<UserModel> {
const user = await this.userModel().findOne({login});
return user ? prepareUserToUserModel(user) : user;
}
async findOne(login: string): Promise<UserResponse> {
const user = await this.userModel().findOne({login});
if (!user) {
throw new NotFoundException(`Not found user "${login}"`);
}
return prepareUserToUserResponse(user);
}
async findAll(): Promise<UserResponse[]> {
const users = await this.userModel().find().exec();
return users.map(prepareUserToUserResponse);
}
async create(user: CreateUserRequest, requesterLogin: string): Promise<UserResponse> {
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<UserResponse> {
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<UserResponse> {
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<boolean> {
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<TokenResponse> {
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<TokenResponse> {
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<boolean> {
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<UserResponse> {
const token = jwt.decode(access_token) as Token;
return await this.findOne(token.login);
}
async updateUser(user: UpdateUserRequest, requesterLogin: string): Promise<UserResponse> {
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<UserResponse> {
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<string> {
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');
}
}