feat(SPG-6) Authentication
parent
240bbf4c2e
commit
af06f2d08e
|
@ -1,10 +0,0 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { AuthController } from './controllers/auth.controller';
|
||||
import { UserDataService } from './data/user.dataservice';
|
||||
import { AuthService } from './domain/services/auth.service';
|
||||
|
||||
@Module({
|
||||
providers: [AuthService, UserDataService],
|
||||
controllers: [AuthController],
|
||||
})
|
||||
export class AuthModule {}
|
|
@ -1,32 +0,0 @@
|
|||
import { Body, Controller, Get, Post } from '@nestjs/common';
|
||||
import { Unprotected } from 'src/core/guards';
|
||||
import { Pagination } from 'src/core/response';
|
||||
import { PaginationResponse } from 'src/core/response/domain/ok-response.interface';
|
||||
import { LoginRequest } from '../domain/entities/request.interface';
|
||||
import { User } from '../domain/entities/user.interface';
|
||||
import { AuthService } from '../domain/services/auth.service';
|
||||
|
||||
@Controller('auth')
|
||||
export class AuthController {
|
||||
constructor(private readonly service: AuthService) {}
|
||||
|
||||
@Unprotected()
|
||||
@Post()
|
||||
login(@Body() body: LoginRequest) {
|
||||
return this.service.createAccessToken(body);
|
||||
}
|
||||
|
||||
@Get()
|
||||
user() {
|
||||
return this.service.getUser();
|
||||
}
|
||||
|
||||
@Pagination()
|
||||
@Get('/all')
|
||||
async users(): Promise<PaginationResponse<User>> {
|
||||
return {
|
||||
data: await this.service.getUsers(),
|
||||
total: 101,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
import { LoginRequest } from '../domain/entities/request.interface';
|
||||
import { User } from '../domain/entities/user.interface';
|
||||
|
||||
const mockUsers: User[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'John Doe',
|
||||
username: 'johndoe',
|
||||
password: 'password1',
|
||||
roles: ['admin'],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Jane Doe',
|
||||
username: 'janedoe',
|
||||
password: 'password2',
|
||||
roles: ['user'],
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Jim Brown',
|
||||
username: 'jimbrown',
|
||||
password: 'password3',
|
||||
roles: ['user', 'admin'],
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'Jane Smith',
|
||||
username: 'janesmith',
|
||||
password: 'password4',
|
||||
roles: ['user'],
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'John Smith',
|
||||
username: 'johnsmith',
|
||||
password: 'password5',
|
||||
roles: ['admin'],
|
||||
},
|
||||
];
|
||||
|
||||
export class UserDataService {
|
||||
async login({ username, password }: LoginRequest): Promise<User | undefined> {
|
||||
const user = mockUsers.find((user) => {
|
||||
return user.username == username && user.password == password;
|
||||
});
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
async users(): Promise<User[]> {
|
||||
return mockUsers;
|
||||
}
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
export interface User {
|
||||
id: number;
|
||||
name: string;
|
||||
username: string;
|
||||
password: string;
|
||||
roles: string[];
|
||||
}
|
|
@ -1,18 +0,0 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AuthService } from './auth.service';
|
||||
|
||||
describe('AuthService', () => {
|
||||
let service: AuthService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [AuthService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<AuthService>(AuthService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
|
@ -1,37 +0,0 @@
|
|||
import { Injectable, UnprocessableEntityException } from '@nestjs/common';
|
||||
import { SessionService, UserProvider, UsersSession } from 'src/core/sessions';
|
||||
import { UserDataService } from '../../data/user.dataservice';
|
||||
import { LoginRequest } from '../entities/request.interface';
|
||||
import { User } from '../entities/user.interface';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
constructor(
|
||||
private readonly userDataService: UserDataService,
|
||||
private readonly session: SessionService,
|
||||
private readonly user: UserProvider,
|
||||
) {}
|
||||
async createAccessToken(payload: LoginRequest): Promise<string> {
|
||||
const user = await this.userDataService.login(payload);
|
||||
|
||||
if (!user)
|
||||
throw new UnprocessableEntityException(`Username or Password not match`);
|
||||
|
||||
const token = this.session.createAccessToken({
|
||||
id: user.id,
|
||||
// username: user.username,
|
||||
name: user.name,
|
||||
// roles: user.roles,
|
||||
});
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
getUser(): UsersSession {
|
||||
return this.user.user;
|
||||
}
|
||||
|
||||
async getUsers(): Promise<User[]> {
|
||||
return this.userDataService.users();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import { compare, hash } from 'bcrypt';
|
||||
|
||||
export async function hashPassword(
|
||||
password: string,
|
||||
saltRounds: number,
|
||||
): Promise<string> {
|
||||
const hashedPassword = await hash(password, 10);
|
||||
return hashedPassword;
|
||||
}
|
||||
|
||||
export async function validatePassword(
|
||||
password: string,
|
||||
hashedPassword: string,
|
||||
): Promise<boolean> {
|
||||
const isPasswordValid = await compare(password, hashedPassword);
|
||||
return isPasswordValid;
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { AuthController } from './infrastructure/auth.controller';
|
||||
import { LoginManager } from './domain/managers/login.manager';
|
||||
import { LogoutManager } from './domain/managers/logout.manager';
|
||||
import { AuthOrchestrator } from './domain/auth.orchestrator';
|
||||
import { UserDataService } from 'src/modules/user-related/user/data/services.ts/user-data.service';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { CqrsModule } from '@nestjs/cqrs';
|
||||
import { UserModel } from 'src/modules/user-related/user/data/models/user.model';
|
||||
import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants';
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot(),
|
||||
TypeOrmModule.forFeature([UserModel], CONNECTION_NAME.DEFAULT),
|
||||
CqrsModule,
|
||||
],
|
||||
controllers: [AuthController],
|
||||
providers: [LoginManager, LogoutManager, UserDataService, AuthOrchestrator],
|
||||
})
|
||||
export class AuthModule {}
|
|
@ -0,0 +1,26 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { UserDataService } from 'src/modules/user-related/user/data/services.ts/user-data.service';
|
||||
import { LoginManager } from './managers/login.manager';
|
||||
import { LogoutManager } from './managers/logout.manager';
|
||||
|
||||
@Injectable()
|
||||
export class AuthOrchestrator {
|
||||
constructor(
|
||||
private loginManager: LoginManager,
|
||||
private logoutManager: LogoutManager,
|
||||
private serviceData: UserDataService,
|
||||
) {}
|
||||
|
||||
async login(data): Promise<any> {
|
||||
this.loginManager.setData(data);
|
||||
this.loginManager.setService(this.serviceData);
|
||||
await this.loginManager.execute();
|
||||
return this.loginManager.getResult();
|
||||
}
|
||||
|
||||
async logout(): Promise<any> {
|
||||
this.logoutManager.setService(this.serviceData);
|
||||
await this.logoutManager.execute();
|
||||
return this.logoutManager.getResult();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
import {
|
||||
HttpStatus,
|
||||
Inject,
|
||||
Injectable,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { validatePassword } from 'src/core/helpers/password/bcrypt.helpers';
|
||||
import { BaseCustomManager } from 'src/core/modules/domain/usecase/managers/base-custom.manager';
|
||||
import { SessionService } from 'src/core/sessions';
|
||||
import { EventTopics } from 'src/core/strings/constants/interface.constants';
|
||||
import { UserModel } from 'src/modules/user-related/user/data/models/user.model';
|
||||
import { UserEntity } from 'src/modules/user-related/user/domain/entities/user.entity';
|
||||
|
||||
@Injectable()
|
||||
export class LoginManager extends BaseCustomManager<UserEntity> {
|
||||
@Inject()
|
||||
protected session: SessionService;
|
||||
protected token;
|
||||
protected userLogin;
|
||||
|
||||
async validateProcess(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
async beforeProcess(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
async process(): Promise<void> {
|
||||
this.userLogin = await this.dataService.getOneByOptions({
|
||||
where: {
|
||||
username: this.data.username,
|
||||
},
|
||||
});
|
||||
|
||||
const valid = await validatePassword(
|
||||
this.data.password,
|
||||
this.userLogin?.password,
|
||||
);
|
||||
if (!valid) this.throwError();
|
||||
|
||||
const tokenData = {
|
||||
id: this.userLogin.id,
|
||||
name: this.userLogin.name,
|
||||
username: this.userLogin.username,
|
||||
role: this.userLogin.role,
|
||||
user_privilege_id: this.userLogin.user_privilege_id,
|
||||
};
|
||||
|
||||
console.log(this.session, 'das');
|
||||
this.token = this.session.createAccessToken(tokenData);
|
||||
const refreshToken = this.session.createAccessToken(tokenData);
|
||||
|
||||
await this.dataService.update(
|
||||
this.queryRunner,
|
||||
this.entityTarget,
|
||||
{ id: this.userLogin.id },
|
||||
{
|
||||
refresh_token: refreshToken,
|
||||
},
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
async afterProcess(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
getResult() {
|
||||
return {
|
||||
id: this.userLogin.id,
|
||||
name: this.userLogin.name,
|
||||
username: this.userLogin.username,
|
||||
role: this.userLogin.role,
|
||||
user_privilege_id: this.userLogin.user_privilege_id,
|
||||
token: this.token,
|
||||
};
|
||||
}
|
||||
|
||||
get entityTarget(): any {
|
||||
return UserModel;
|
||||
}
|
||||
|
||||
get eventTopics(): EventTopics[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
throwError() {
|
||||
throw new UnauthorizedException({
|
||||
statusCode: HttpStatus.UNAUTHORIZED,
|
||||
message: `Failed! You have entered an invalid username or password`,
|
||||
error: 'Unauthorized',
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
import { BaseCustomManager } from 'src/core/modules/domain/usecase/managers/base-custom.manager';
|
||||
import { EventTopics } from 'src/core/strings/constants/interface.constants';
|
||||
import { UserModel } from 'src/modules/user-related/user/data/models/user.model';
|
||||
import { UserEntity } from 'src/modules/user-related/user/domain/entities/user.entity';
|
||||
|
||||
export class LogoutManager extends BaseCustomManager<UserEntity> {
|
||||
async validateProcess(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
async beforeProcess(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
async process(): Promise<void> {
|
||||
await this.dataService.update(
|
||||
this.queryRunner,
|
||||
this.entityTarget,
|
||||
{ id: this.user.id },
|
||||
{
|
||||
refresh_token: null,
|
||||
},
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
async afterProcess(): Promise<void> {
|
||||
return;
|
||||
}
|
||||
|
||||
getResult() {
|
||||
return `Success Logout user`;
|
||||
}
|
||||
|
||||
get entityTarget(): any {
|
||||
return UserModel;
|
||||
}
|
||||
|
||||
get eventTopics(): EventTopics[] {
|
||||
return [];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import { Body, Controller, Delete, Post } from '@nestjs/common';
|
||||
import { Public } from 'src/core/guards';
|
||||
import { AuthOrchestrator } from '../domain/auth.orchestrator';
|
||||
import { ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { LoginDto } from './dto/login.dto';
|
||||
|
||||
@Controller('auth')
|
||||
export class AuthController {
|
||||
constructor(private orchestrator: AuthOrchestrator) {}
|
||||
|
||||
@Post()
|
||||
@Public(false)
|
||||
async login(@Body() body: LoginDto) {
|
||||
return await this.orchestrator.login(body);
|
||||
}
|
||||
|
||||
@ApiBearerAuth('JWT')
|
||||
@Public(false)
|
||||
@Delete('logout')
|
||||
async logoout() {
|
||||
return await this.orchestrator.logout();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
import { IsString } from 'class-validator';
|
||||
import { LoginRequest } from '../../domain/entities/request.interface';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class LoginDto implements LoginRequest {
|
||||
@ApiProperty({ name: 'username', required: true })
|
||||
@IsString()
|
||||
username: string;
|
||||
|
||||
@ApiProperty({ name: 'password', required: true })
|
||||
@IsString()
|
||||
password: string;
|
||||
}
|
Loading…
Reference in New Issue