Добавлена работа с пользователями по всем методам, добавлена авторизация пользователей
This commit is contained in:
@ -4,6 +4,7 @@ import {MongooseModule} from '@nestjs/mongoose';
|
||||
import {MONGO_URL, DB_NAME} from './consts';
|
||||
import {User, UserSchema} from './users/users.schema';
|
||||
import {UserService} from './users/users.service';
|
||||
import {AuthController} from './auth/auth.controller';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@ -16,6 +17,7 @@ import {UserService} from './users/users.service';
|
||||
],
|
||||
controllers: [
|
||||
UsersController,
|
||||
AuthController,
|
||||
],
|
||||
providers: [
|
||||
UserService,
|
||||
|
||||
80
src/auth/auth.controller.ts
Normal file
80
src/auth/auth.controller.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import {Controller, Req, Post, Options, Header, HttpCode} from '@nestjs/common';
|
||||
|
||||
import {ApiResponse, ApiTags, ApiBody} from '@nestjs/swagger';
|
||||
import {ALLOW_ORIGIN_ALL, ALLOW_METHOD, ALLOW_CREDENTIALS, CONTENT_LENGTH, ALLOW_HEADERS, AUTH_CONTROLLER} from '../consts';
|
||||
import {UserService} from '../users/users.service';
|
||||
import {AuthRequest, TokenResponse, RefreshAuthRequest, CheckAuthTokenRequest} from '../users/users.schema';
|
||||
import {Request} from 'express';
|
||||
|
||||
|
||||
@Controller(AUTH_CONTROLLER)
|
||||
@ApiTags(AUTH_CONTROLLER)
|
||||
export class AuthController {
|
||||
constructor(
|
||||
private readonly userService: UserService
|
||||
) {}
|
||||
|
||||
@Post()
|
||||
@Header(...ALLOW_ORIGIN_ALL)
|
||||
@ApiResponse({
|
||||
status: 201,
|
||||
description: 'Метод авторизации пользователя',
|
||||
type: TokenResponse,
|
||||
})
|
||||
@ApiBody({
|
||||
type: AuthRequest,
|
||||
description: 'Ожидает логин и пароль пользователя для авторизации'
|
||||
})
|
||||
async authUser(@Req() request: Request<null, AuthRequest>): Promise<TokenResponse> {
|
||||
const host = request.headers.host;
|
||||
const agent = request.headers['user-agent'];
|
||||
return await this.userService.authUser(request.body.login, request.body.password, host, agent);
|
||||
}
|
||||
|
||||
@Post('refresh')
|
||||
@Header(...ALLOW_ORIGIN_ALL)
|
||||
@ApiResponse({
|
||||
status: 201,
|
||||
description: 'Метод обновления токенов',
|
||||
type: TokenResponse,
|
||||
})
|
||||
@ApiBody({
|
||||
type: RefreshAuthRequest,
|
||||
description: 'Токен для сброса токенов'
|
||||
})
|
||||
async refreshAuth(@Req() request: Request<null, RefreshAuthRequest>): Promise<TokenResponse> {
|
||||
const host = request.headers.host;
|
||||
const agent = request.headers['user-agent'];
|
||||
const refresh_token = request.body.refresh_token;
|
||||
return await this.userService.refreshAuth(refresh_token, host, agent);
|
||||
}
|
||||
|
||||
@Post('check')
|
||||
@ApiResponse({
|
||||
status: 200,
|
||||
description: 'Проверяет токен авторизации',
|
||||
type: Boolean,
|
||||
})
|
||||
@ApiBody({
|
||||
type: CheckAuthTokenRequest,
|
||||
description: 'Токен для проверки',
|
||||
})
|
||||
async checkAccessToken(@Req() request: Request<null, CheckAuthTokenRequest>): Promise<boolean> {
|
||||
const {access_token, host, agent} = request.body;
|
||||
console.log(request.headers)
|
||||
return this.userService.checkAccessToken(access_token, host, agent);
|
||||
}
|
||||
|
||||
@Options([
|
||||
'', 'refresh', 'check'
|
||||
])
|
||||
@Header(...ALLOW_ORIGIN_ALL)
|
||||
@Header(...ALLOW_METHOD)
|
||||
@Header(...ALLOW_CREDENTIALS)
|
||||
@Header(...CONTENT_LENGTH)
|
||||
@Header(...ALLOW_HEADERS)
|
||||
@HttpCode(204)
|
||||
async optionsAuth(): Promise<string> {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
@ -8,4 +8,7 @@ export const ALLOW_ORIGIN_ALL: [string, string] = ['Access-Control-Allow-Origin'
|
||||
export const ALLOW_CREDENTIALS: [string, string] = ['Access-Control-Allow-Credentials', 'true'];
|
||||
export const CONTENT_LENGTH: [string, string] = ['Content-Length', '0'];
|
||||
export const ALLOW_METHOD: [string, string] = ['Access-Control-Allow-Methods', 'GET,HEAD,PUT,PATCH,POST,DELETE'];
|
||||
export const ALLOW_HEADERS: [string, string] = ['Access-Control-Allow-Headers', 'Version, Authorization, Content-Type'];
|
||||
export const ALLOW_HEADERS: [string, string] = ['Access-Control-Allow-Headers', 'Version, Authorization, Content-Type, Api-Name, x-turbo-id, x-turbo-compression, chrome-proxy, chrome-proxy-ect'];
|
||||
|
||||
export const SECRET_JWT_ACCESS_KEY = 'secret_hui_key';
|
||||
export const SECRET_JWT_REFRESH_KEY = 'secret_pizda_key';
|
||||
@ -1,7 +1,7 @@
|
||||
import {NestFactory} from '@nestjs/core';
|
||||
import {AppModule} from './app.module';
|
||||
import {SwaggerModule, DocumentBuilder} from '@nestjs/swagger';
|
||||
import {USERS_CONTROLLER} from './consts';
|
||||
import {USERS_CONTROLLER, AUTH_CONTROLLER} from './consts';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
@ -11,6 +11,7 @@ async function bootstrap() {
|
||||
.setDescription('API для авторизации приложений и работы с пользователями')
|
||||
.setVersion('1.0.0')
|
||||
.addTag(USERS_CONTROLLER)
|
||||
.addTag(AUTH_CONTROLLER)
|
||||
.build();
|
||||
|
||||
const document = SwaggerModule.createDocument(app, options);
|
||||
|
||||
@ -83,7 +83,9 @@ export class UsersController {
|
||||
return await this.userService.removeOne(request.params.login);
|
||||
}
|
||||
|
||||
@Options()
|
||||
@Options([
|
||||
'', ':login'
|
||||
])
|
||||
@Header(...ALLOW_ORIGIN_ALL)
|
||||
@Header(...ALLOW_METHOD)
|
||||
@Header(...ALLOW_CREDENTIALS)
|
||||
|
||||
@ -2,11 +2,6 @@ import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
|
||||
import {ApiProperty} from '@nestjs/swagger';
|
||||
import { Document } from 'mongoose';
|
||||
|
||||
interface Token {
|
||||
token: string;
|
||||
expired_at: string;
|
||||
}
|
||||
|
||||
export class CreateUserRequest {
|
||||
@ApiProperty()
|
||||
login: string;
|
||||
@ -49,12 +44,38 @@ export class UserModel {
|
||||
|
||||
@ApiProperty()
|
||||
is_admin: boolean;
|
||||
}
|
||||
|
||||
export class AuthRequest {
|
||||
@ApiProperty()
|
||||
login: string;
|
||||
|
||||
@ApiProperty()
|
||||
access_token: Token[];
|
||||
password: string;
|
||||
}
|
||||
|
||||
export class CheckAuthTokenRequest {
|
||||
@ApiProperty()
|
||||
access_token: string;
|
||||
|
||||
@ApiProperty()
|
||||
refresh_token: Token[];
|
||||
host: string;
|
||||
|
||||
@ApiProperty()
|
||||
agent: string;
|
||||
}
|
||||
|
||||
export class RefreshAuthRequest {
|
||||
@ApiProperty()
|
||||
refresh_token: string;
|
||||
}
|
||||
|
||||
export class TokenResponse {
|
||||
@ApiProperty()
|
||||
access_token: string;
|
||||
|
||||
@ApiProperty()
|
||||
refresh_token: string;
|
||||
}
|
||||
|
||||
@Schema()
|
||||
@ -81,22 +102,6 @@ export class User extends Document {
|
||||
type: Boolean,
|
||||
})
|
||||
is_admin: boolean;
|
||||
|
||||
@Prop({
|
||||
type: [{
|
||||
token: String,
|
||||
expired_at: String,
|
||||
}]
|
||||
})
|
||||
access_token: Token[];
|
||||
|
||||
@Prop({
|
||||
type: [{
|
||||
token: String,
|
||||
expired_at: String,
|
||||
}]
|
||||
})
|
||||
refresh_token: Token[];
|
||||
}
|
||||
|
||||
@Schema()
|
||||
|
||||
@ -1,8 +1,18 @@
|
||||
import {Model, Connection, Document} from 'mongoose';
|
||||
import {Injectable, NotFoundException, BadGatewayException} from '@nestjs/common';
|
||||
import {Injectable, NotFoundException, BadGatewayException, UnauthorizedException} from '@nestjs/common';
|
||||
import {InjectConnection} from '@nestjs/mongoose';
|
||||
import {DB_NAME, USERS_CONTROLLER} from 'src/consts';
|
||||
import {User, UserSchema, CreateUserRequest, UserResponse, UserModel, UpdateUserRequest} from './users.schema';
|
||||
import {DB_NAME, USERS_CONTROLLER, SECRET_JWT_ACCESS_KEY, SECRET_JWT_REFRESH_KEY} from 'src/consts';
|
||||
import {User, UserSchema, CreateUserRequest, UserResponse, UserModel, UpdateUserRequest, TokenResponse} from './users.schema';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
import * as jwt from 'jsonwebtoken';
|
||||
|
||||
interface Token {
|
||||
login: string;
|
||||
host: string;
|
||||
agent: string;
|
||||
iat: number;
|
||||
exp: number;
|
||||
}
|
||||
|
||||
const validateModel = async (user: Document) => {
|
||||
try {
|
||||
@ -17,8 +27,6 @@ const prepareUserToUserModel = (user: User): UserModel => ({
|
||||
avatar: user.avatar,
|
||||
password: user.password,
|
||||
is_admin: user.is_admin,
|
||||
access_token: user.access_token,
|
||||
refresh_token: user.refresh_token,
|
||||
});
|
||||
|
||||
const prepareUserToUserResponse = (user: User): UserResponse => ({
|
||||
@ -62,8 +70,15 @@ export class UserService {
|
||||
throw new NotFoundException(`Пользователь с логином ${user.login} уже существует`);
|
||||
}
|
||||
|
||||
const salt = await bcrypt.genSalt(10);
|
||||
const password = await bcrypt.hash(user.password, salt);
|
||||
|
||||
const Model = await this.userModel();
|
||||
const createUser = new Model(user);
|
||||
const createUser = new Model({
|
||||
...user,
|
||||
salt,
|
||||
password,
|
||||
});
|
||||
await validateModel(createUser);
|
||||
|
||||
const savedUser = await createUser.save();
|
||||
@ -97,4 +112,67 @@ export class UserService {
|
||||
|
||||
return prepareUserToUserResponse(searchUser);
|
||||
}
|
||||
|
||||
checkPassword(password: string, hash: string): Promise<boolean> {
|
||||
return bcrypt.compare(password, hash);
|
||||
}
|
||||
|
||||
generateTokens(login: string, host: string, agent: string): TokenResponse {
|
||||
const access_token = jwt.sign({
|
||||
login,
|
||||
host,
|
||||
agent,
|
||||
iat: Math.floor(Date.now() / 1000),
|
||||
exp: Math.floor(Date.now() / 1000) + (60 * 2), // секунды * минуты
|
||||
}, SECRET_JWT_ACCESS_KEY);
|
||||
const refresh_token = jwt.sign({
|
||||
login,
|
||||
host,
|
||||
agent,
|
||||
iat: Math.floor(Date.now() / 1000),
|
||||
exp: Math.floor(Date.now() / 1000) + (60 * 60 * 24 * 1), // секунды * минуты * часы * дни
|
||||
}, SECRET_JWT_REFRESH_KEY);
|
||||
return {
|
||||
access_token,
|
||||
refresh_token,
|
||||
};
|
||||
}
|
||||
|
||||
async authUser(login: string, password: string, host: string, agent: string): Promise<TokenResponse> {
|
||||
const searchUser = await this.findUser(login);
|
||||
if (searchUser && await this.checkPassword(password, searchUser.password)) {
|
||||
return this.generateTokens(login, host, agent);
|
||||
}
|
||||
|
||||
throw new UnauthorizedException('Не верный пользователь или пароль');
|
||||
}
|
||||
|
||||
verifyToken(token: string, secret: string): void {
|
||||
try {
|
||||
jwt.verify(token, secret);
|
||||
} catch (e) {
|
||||
throw new UnauthorizedException('Авторизация устарела');
|
||||
}
|
||||
}
|
||||
|
||||
checkToken(token: Token, host: string, agent: string): boolean {
|
||||
return token.host === host && token.agent === agent;
|
||||
}
|
||||
|
||||
async refreshAuth(refresh_token: string, host: 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, host, agent)) {
|
||||
return this.generateTokens(token.login, host, agent);
|
||||
}
|
||||
throw new UnauthorizedException('Не санкционированный запрос');
|
||||
}
|
||||
|
||||
async checkAccessToken(access_token: string, host: string, agent: string): Promise<boolean> {
|
||||
this.verifyToken(access_token, SECRET_JWT_ACCESS_KEY);
|
||||
const token = jwt.decode(access_token) as Token;
|
||||
const searchUser = await this.findUser(token.login);
|
||||
return searchUser && this.checkToken(token, host, agent);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user