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

266 lines
8.7 KiB
TypeScript

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 (!await this.checkPassword(old_password, user.password)) {
throw new BadRequestException('Неверный старый пароль');
}
if (user) {
const salt = user.salt;
const password = await bcrypt.hash(new_password, salt);
await user.updateOne({
password,
});
return 'ok';
}
throw new BadRequestException('Unauthorized request');
}
}