feat(SPG-6) Authentication

pull/2/head
ashar 2024-06-03 15:22:42 +07:00
parent 240bbf4c2e
commit af06f2d08e
16 changed files with 239 additions and 158 deletions

View File

@ -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 {}

View File

@ -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,
};
}
}

View File

@ -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;
}
}

View File

@ -1,7 +0,0 @@
export interface User {
id: number;
name: string;
username: string;
password: string;
roles: string[];
}

View File

@ -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();
});
});

View File

@ -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();
}
}

View File

@ -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;
}

View File

@ -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 {}

View File

@ -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();
}
}

View File

@ -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',
});
}
}

View File

@ -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 [];
}
}

View File

@ -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();
}
}

View File

@ -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;
}