diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts deleted file mode 100644 index 354551a..0000000 --- a/src/auth/auth.module.ts +++ /dev/null @@ -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 {} diff --git a/src/auth/controllers/auth.controller.ts b/src/auth/controllers/auth.controller.ts deleted file mode 100644 index 6bf7ba8..0000000 --- a/src/auth/controllers/auth.controller.ts +++ /dev/null @@ -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> { - return { - data: await this.service.getUsers(), - total: 101, - }; - } -} diff --git a/src/auth/data/user.dataservice.ts b/src/auth/data/user.dataservice.ts deleted file mode 100644 index 8af7586..0000000 --- a/src/auth/data/user.dataservice.ts +++ /dev/null @@ -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 { - const user = mockUsers.find((user) => { - return user.username == username && user.password == password; - }); - - return user; - } - - async users(): Promise { - return mockUsers; - } -} diff --git a/src/auth/domain/entities/user.interface.ts b/src/auth/domain/entities/user.interface.ts deleted file mode 100644 index 8c9d3bc..0000000 --- a/src/auth/domain/entities/user.interface.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface User { - id: number; - name: string; - username: string; - password: string; - roles: string[]; -} diff --git a/src/auth/domain/services/auth.service.spec.ts b/src/auth/domain/services/auth.service.spec.ts deleted file mode 100644 index 800ab66..0000000 --- a/src/auth/domain/services/auth.service.spec.ts +++ /dev/null @@ -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); - }); - - it('should be defined', () => { - expect(service).toBeDefined(); - }); -}); diff --git a/src/auth/domain/services/auth.service.ts b/src/auth/domain/services/auth.service.ts deleted file mode 100644 index 4c43368..0000000 --- a/src/auth/domain/services/auth.service.ts +++ /dev/null @@ -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 { - 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 { - return this.userDataService.users(); - } -} diff --git a/src/core/helpers/password/bcrypt.helpers.ts b/src/core/helpers/password/bcrypt.helpers.ts new file mode 100644 index 0000000..7dc493b --- /dev/null +++ b/src/core/helpers/password/bcrypt.helpers.ts @@ -0,0 +1,17 @@ +import { compare, hash } from 'bcrypt'; + +export async function hashPassword( + password: string, + saltRounds: number, +): Promise { + const hashedPassword = await hash(password, 10); + return hashedPassword; +} + +export async function validatePassword( + password: string, + hashedPassword: string, +): Promise { + const isPasswordValid = await compare(password, hashedPassword); + return isPasswordValid; +} diff --git a/src/modules/configuration/auth/auth.module.ts b/src/modules/configuration/auth/auth.module.ts new file mode 100644 index 0000000..29a6f8a --- /dev/null +++ b/src/modules/configuration/auth/auth.module.ts @@ -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 {} diff --git a/src/auth/constants.ts b/src/modules/configuration/auth/constants.ts similarity index 100% rename from src/auth/constants.ts rename to src/modules/configuration/auth/constants.ts diff --git a/src/modules/configuration/auth/domain/auth.orchestrator.ts b/src/modules/configuration/auth/domain/auth.orchestrator.ts new file mode 100644 index 0000000..e46c00d --- /dev/null +++ b/src/modules/configuration/auth/domain/auth.orchestrator.ts @@ -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 { + this.loginManager.setData(data); + this.loginManager.setService(this.serviceData); + await this.loginManager.execute(); + return this.loginManager.getResult(); + } + + async logout(): Promise { + this.logoutManager.setService(this.serviceData); + await this.logoutManager.execute(); + return this.logoutManager.getResult(); + } +} diff --git a/src/auth/domain/entities/request.interface.ts b/src/modules/configuration/auth/domain/entities/request.interface.ts similarity index 100% rename from src/auth/domain/entities/request.interface.ts rename to src/modules/configuration/auth/domain/entities/request.interface.ts diff --git a/src/modules/configuration/auth/domain/managers/login.manager.ts b/src/modules/configuration/auth/domain/managers/login.manager.ts new file mode 100644 index 0000000..0f64799 --- /dev/null +++ b/src/modules/configuration/auth/domain/managers/login.manager.ts @@ -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 { + @Inject() + protected session: SessionService; + protected token; + protected userLogin; + + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async process(): Promise { + 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 { + 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', + }); + } +} diff --git a/src/modules/configuration/auth/domain/managers/logout.manager.ts b/src/modules/configuration/auth/domain/managers/logout.manager.ts new file mode 100644 index 0000000..c6e47ac --- /dev/null +++ b/src/modules/configuration/auth/domain/managers/logout.manager.ts @@ -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 { + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async process(): Promise { + await this.dataService.update( + this.queryRunner, + this.entityTarget, + { id: this.user.id }, + { + refresh_token: null, + }, + ); + + return; + } + + async afterProcess(): Promise { + return; + } + + getResult() { + return `Success Logout user`; + } + + get entityTarget(): any { + return UserModel; + } + + get eventTopics(): EventTopics[] { + return []; + } +} diff --git a/src/auth/index.ts b/src/modules/configuration/auth/index.ts similarity index 100% rename from src/auth/index.ts rename to src/modules/configuration/auth/index.ts diff --git a/src/modules/configuration/auth/infrastructure/auth.controller.ts b/src/modules/configuration/auth/infrastructure/auth.controller.ts new file mode 100644 index 0000000..559ba75 --- /dev/null +++ b/src/modules/configuration/auth/infrastructure/auth.controller.ts @@ -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(); + } +} diff --git a/src/modules/configuration/auth/infrastructure/dto/login.dto.ts b/src/modules/configuration/auth/infrastructure/dto/login.dto.ts new file mode 100644 index 0000000..c4afbca --- /dev/null +++ b/src/modules/configuration/auth/infrastructure/dto/login.dto.ts @@ -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; +}