From f4cf5178b8dc984839a6106aaaa20d72724772d8 Mon Sep 17 00:00:00 2001 From: Firman Ramdhani <33869609+firmanramdhani@users.noreply.github.com> Date: Wed, 18 Sep 2024 16:25:29 +0700 Subject: [PATCH 1/2] feat: change auth concept --- src/app.module.ts | 2 + src/core/guards/domain/jwt.guard.ts | 27 ++++++- src/core/guards/domain/roles.guard.ts | 2 +- .../guards/domain/services/auth.service.ts | 78 +++++++++++++++++++ src/core/helpers/constant/index.ts | 7 ++ .../entities/user-sessions.interface.ts | 2 + src/core/sessions/domain/providers/user.ts | 5 ++ ...726642119207-change-user-login-relation.ts | 35 +++++++++ ...2499135-add-column-source-at-user-login.ts | 29 +++++++ .../1726647442006-add-source-on-log-login.ts | 21 +++++ .../domain/auth-admin-queue.orchestrator.ts | 8 +- .../auth/domain/auth.orchestrator.ts | 4 + .../admin-queue/login-admin-queue.manager.ts | 52 +++++++------ .../admin-queue/logout-admin-queue.manager.ts | 19 ++--- .../auth/domain/managers/login.manager.ts | 27 ++++--- .../auth/domain/managers/logout.manager.ts | 17 ++-- .../auth-admin-queue.controller.ts | 16 ++-- .../auth/infrastructure/auth.controller.ts | 10 ++- .../auth/infrastructure/dto/login.dto.ts | 6 ++ .../log/data/models/log-user-login.model.ts | 5 +- .../tenant/infrastructure/dto/tenant.dto.ts | 3 - .../dto/update-password-tenant.dto.ts | 3 - .../infrastructure/dto/update-tenant.dto.ts | 3 - .../user/data/models/user-login.model.ts | 12 ++- .../user/data/models/user.model.ts | 5 +- .../user/data/services/user-data.service.ts | 62 ++++++++++++++- .../user/domain/entities/user-login.entity.ts | 5 ++ .../user/domain/entities/user.entity.ts | 1 - .../usecases/managers/index-user.manager.ts | 1 + .../dto/update-password-user.dto.ts | 3 - .../infrastructure/dto/update-user.dto.ts | 3 - .../user/infrastructure/dto/user.dto.ts | 3 - 32 files changed, 378 insertions(+), 98 deletions(-) create mode 100644 src/core/guards/domain/services/auth.service.ts create mode 100644 src/database/migrations/1726642119207-change-user-login-relation.ts create mode 100644 src/database/migrations/1726642499135-add-column-source-at-user-login.ts create mode 100644 src/database/migrations/1726647442006-add-source-on-log-login.ts diff --git a/src/app.module.ts b/src/app.module.ts index fabac75..c346f5a 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -79,6 +79,7 @@ import { SupersetModule } from './modules/configuration/superset/superset.module import { GateScanModule } from './modules/gates/gate.module'; import { UserLoginModel } from './modules/user-related/user/data/models/user-login.model'; import { LogUserLoginModel } from './modules/configuration/log/data/models/log-user-login.model'; +import { AuthService } from './core/guards/domain/services/auth.service'; @Module({ imports: [ @@ -189,6 +190,7 @@ import { LogUserLoginModel } from './modules/configuration/log/data/models/log-u ], controllers: [], providers: [ + AuthService, PrivilegeService, /** * By default all request from client will protect by JWT diff --git a/src/core/guards/domain/jwt.guard.ts b/src/core/guards/domain/jwt.guard.ts index d02fba3..a0d750f 100644 --- a/src/core/guards/domain/jwt.guard.ts +++ b/src/core/guards/domain/jwt.guard.ts @@ -7,10 +7,10 @@ import { UnauthorizedException, } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; -import { Observable } from 'rxjs'; import { SessionService, UsersSession } from 'src/core/sessions'; import { UNPROTECTED_URL } from '../constants'; import { PrivilegeService } from './services/privilege.service'; +import { AuthService } from './services/auth.service'; @Injectable({ scope: Scope.REQUEST }) export class JWTGuard implements CanActivate { @@ -18,14 +18,13 @@ export class JWTGuard implements CanActivate { protected readonly session: SessionService, protected readonly reflector: Reflector, protected readonly privilege: PrivilegeService, + protected readonly authService: AuthService, ) {} protected isPublic = false; protected userSession: UsersSession; - canActivate( - context: ExecutionContext, - ): boolean | Promise | Observable { + async canActivate(context: ExecutionContext) { /** * Check if access url is protected or not * By default `isUnprotected` equals `false` @@ -61,9 +60,29 @@ export class JWTGuard implements CanActivate { */ try { this.userSession = this.session.verifyToken(token); + await this.authService.verifyRegisteredLoginToken(token); + Logger.log(`Access from ${this.userSession.name}`, 'AuthGuard'); return true; } catch (error) { + const expiredError = error.message; + if (expiredError === 'jwt expired') { + const [, body] = token.split('.'); + const bodyToken = JSON.parse(atob(body)); + + const user = { + role: bodyToken.role, + user_id: bodyToken.id, + username: bodyToken.username, + user_privilege_id: bodyToken.user_privilege_id, + item_id: bodyToken.item_id, + item_name: bodyToken.item_name, + source: bodyToken.source, + }; + + this.authService.logoutUser(user, token); + } + throw new UnauthorizedException({ code: 10001, message: diff --git a/src/core/guards/domain/roles.guard.ts b/src/core/guards/domain/roles.guard.ts index f52fa79..595eb41 100644 --- a/src/core/guards/domain/roles.guard.ts +++ b/src/core/guards/domain/roles.guard.ts @@ -9,7 +9,7 @@ import { MAIN_MENU } from '../constants'; @Injectable() export class RolesGuard extends JWTGuard { async canActivate(context: ExecutionContext): Promise { - super.canActivate(context); + await super.canActivate(context); // jika endpoint tersebut bukan public, maka lakukan check lanjutan if (!this.isPublic) { diff --git a/src/core/guards/domain/services/auth.service.ts b/src/core/guards/domain/services/auth.service.ts new file mode 100644 index 0000000..e5b76d6 --- /dev/null +++ b/src/core/guards/domain/services/auth.service.ts @@ -0,0 +1,78 @@ +import { + HttpStatus, + Injectable, + Scope, + UnauthorizedException, +} from '@nestjs/common'; +import { InjectDataSource } from '@nestjs/typeorm'; +import { + CONNECTION_NAME, + OPERATION, +} from 'src/core/strings/constants/base.constants'; +import { DataSource } from 'typeorm'; +import { UserRole } from 'src/modules/user-related/user/constants'; +import { UserModel } from 'src/modules/user-related/user/data/models/user.model'; +import { AppSource, LogUserType } from 'src/core/helpers/constant'; +import { EventBus } from '@nestjs/cqrs'; +import { LogUserLoginEvent } from 'src/modules/configuration/log/domain/entities/log-user-login.event'; +import { UserLoginModel } from 'src/modules/user-related/user/data/models/user-login.model'; + +interface UserEntity { + user_id: string; + username: string; + role: UserRole; + user_privilege_id: string; + item_id: string; + item_name: string; + source: AppSource; +} + +@Injectable({ scope: Scope.REQUEST }) +export class AuthService { + constructor( + @InjectDataSource(CONNECTION_NAME.DEFAULT) + protected readonly dataSource: DataSource, + + private eventBus: EventBus, + ) {} + + get repository() { + return this.dataSource.getRepository(UserLoginModel); + } + + async logoutUser(user: UserEntity, token: string) { + await this.repository.delete({ login_token: token }); + + const userLogout = { + type: LogUserType.logout, + created_at: new Date().getTime(), + name: user.username, + user_privilege_id: user.user_privilege_id, + ...user, + }; + + this.eventBus.publish( + new LogUserLoginEvent({ + id: user.user_id, + old: null, + data: userLogout, + user: userLogout as any, + description: 'Logout', + module: UserModel.name, + op: OPERATION.UPDATE, + }), + ); + } + + async verifyRegisteredLoginToken(token: string) { + const data = await this.repository.findOneBy({ login_token: token }); + + if (!data) { + throw new UnauthorizedException({ + statusCode: HttpStatus.UNAUTHORIZED, + message: `Invalid token`, + error: 'Unauthorized', + }); + } + } +} diff --git a/src/core/helpers/constant/index.ts b/src/core/helpers/constant/index.ts index fdb844c..4da6b95 100644 --- a/src/core/helpers/constant/index.ts +++ b/src/core/helpers/constant/index.ts @@ -2,3 +2,10 @@ export enum LogUserType { login = 'login', logout = 'logout', } + +export enum AppSource { + POS_ADMIN = 'POS_ADMIN', + POS_COUNTER = 'POS_COUNTER', + QUEUE_ADMIN = 'QUEUE_ADMIN', + QUEUE_CUSTOMER = 'QUEUE_CUSTOMER', +} diff --git a/src/core/sessions/domain/entities/user-sessions.interface.ts b/src/core/sessions/domain/entities/user-sessions.interface.ts index 58004d5..848d44e 100644 --- a/src/core/sessions/domain/entities/user-sessions.interface.ts +++ b/src/core/sessions/domain/entities/user-sessions.interface.ts @@ -1,8 +1,10 @@ +import { AppSource } from 'src/core/helpers/constant'; import { UserRole } from 'src/modules/user-related/user/constants'; export interface UsersSession { id: number; name: string; role: UserRole; + source?: AppSource; user_privilege_id: string; } diff --git a/src/core/sessions/domain/providers/user.ts b/src/core/sessions/domain/providers/user.ts index 0c1b980..2787470 100644 --- a/src/core/sessions/domain/providers/user.ts +++ b/src/core/sessions/domain/providers/user.ts @@ -23,4 +23,9 @@ export class UserProvider { const [, token] = this.request.headers['authorization'].split(' '); return this.session.verifyToken(token); } + + get token(): string { + const [, token] = this.request.headers['authorization'].split(' '); + return token; + } } diff --git a/src/database/migrations/1726642119207-change-user-login-relation.ts b/src/database/migrations/1726642119207-change-user-login-relation.ts new file mode 100644 index 0000000..fe55b5a --- /dev/null +++ b/src/database/migrations/1726642119207-change-user-login-relation.ts @@ -0,0 +1,35 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class ChangeUserLoginRelation1726642119207 + implements MigrationInterface +{ + name = 'ChangeUserLoginRelation1726642119207'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "refresh_token"`); + await queryRunner.query( + `ALTER TABLE "users_login" DROP CONSTRAINT "FK_2a80a213b51423ce5b8211f0584"`, + ); + await queryRunner.query( + `ALTER TABLE "users_login" DROP CONSTRAINT "REL_2a80a213b51423ce5b8211f058"`, + ); + await queryRunner.query( + `ALTER TABLE "users_login" ADD CONSTRAINT "FK_2a80a213b51423ce5b8211f0584" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "users_login" DROP CONSTRAINT "FK_2a80a213b51423ce5b8211f0584"`, + ); + await queryRunner.query( + `ALTER TABLE "users_login" ADD CONSTRAINT "REL_2a80a213b51423ce5b8211f058" UNIQUE ("user_id")`, + ); + await queryRunner.query( + `ALTER TABLE "users_login" ADD CONSTRAINT "FK_2a80a213b51423ce5b8211f0584" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "users" ADD "refresh_token" character varying`, + ); + } +} diff --git a/src/database/migrations/1726642499135-add-column-source-at-user-login.ts b/src/database/migrations/1726642499135-add-column-source-at-user-login.ts new file mode 100644 index 0000000..7d48211 --- /dev/null +++ b/src/database/migrations/1726642499135-add-column-source-at-user-login.ts @@ -0,0 +1,29 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddColumnSourceAtUserLogin1726642499135 + implements MigrationInterface +{ + name = 'AddColumnSourceAtUserLogin1726642499135'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TYPE "public"."users_login_role_enum" AS ENUM('superadmin', 'staff', 'tenant', 'queue_admin')`, + ); + await queryRunner.query( + `ALTER TABLE "users_login" ADD "role" "public"."users_login_role_enum"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."users_login_source_enum" AS ENUM('POS_ADMIN', 'POS_COUNTER', 'QUEUE_ADMIN', 'QUEUE_CUSTOMER')`, + ); + await queryRunner.query( + `ALTER TABLE "users_login" ADD "source" "public"."users_login_source_enum"`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "users_login" DROP COLUMN "source"`); + await queryRunner.query(`DROP TYPE "public"."users_login_source_enum"`); + await queryRunner.query(`ALTER TABLE "users_login" DROP COLUMN "role"`); + await queryRunner.query(`DROP TYPE "public"."users_login_role_enum"`); + } +} diff --git a/src/database/migrations/1726647442006-add-source-on-log-login.ts b/src/database/migrations/1726647442006-add-source-on-log-login.ts new file mode 100644 index 0000000..2f5cf26 --- /dev/null +++ b/src/database/migrations/1726647442006-add-source-on-log-login.ts @@ -0,0 +1,21 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddSourceOnLogLogin1726647442006 implements MigrationInterface { + name = 'AddSourceOnLogLogin1726647442006'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TYPE "public"."log_users_login_source_enum" AS ENUM('POS_ADMIN', 'POS_COUNTER', 'QUEUE_ADMIN', 'QUEUE_CUSTOMER')`, + ); + await queryRunner.query( + `ALTER TABLE "log_users_login" ADD "source" "public"."log_users_login_source_enum"`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "log_users_login" DROP COLUMN "source"`, + ); + await queryRunner.query(`DROP TYPE "public"."log_users_login_source_enum"`); + } +} diff --git a/src/modules/configuration/auth/domain/auth-admin-queue.orchestrator.ts b/src/modules/configuration/auth/domain/auth-admin-queue.orchestrator.ts index 0a755bf..dc17f9a 100644 --- a/src/modules/configuration/auth/domain/auth-admin-queue.orchestrator.ts +++ b/src/modules/configuration/auth/domain/auth-admin-queue.orchestrator.ts @@ -19,10 +19,14 @@ export class AuthAdminQueueOrchestrator { return this.loginManager.getResult(); } - async logout(id?: string): Promise { - if (id) this.logoutManager.setData({ id }); + async logout(userId?: string): Promise { + if (userId) this.logoutManager.setData({ user_id: userId }); this.logoutManager.setService(this.serviceData, TABLE_NAME.USER); await this.logoutManager.execute(); return this.logoutManager.getResult(); } + + async forceLogout(token): Promise { + return this.serviceData.forceLogout(token); + } } diff --git a/src/modules/configuration/auth/domain/auth.orchestrator.ts b/src/modules/configuration/auth/domain/auth.orchestrator.ts index 1be43e4..f1d3efd 100644 --- a/src/modules/configuration/auth/domain/auth.orchestrator.ts +++ b/src/modules/configuration/auth/domain/auth.orchestrator.ts @@ -24,4 +24,8 @@ export class AuthOrchestrator { await this.logoutManager.execute(); return this.logoutManager.getResult(); } + + async forceLogout(token): Promise { + return this.serviceData.forceLogout(token); + } } diff --git a/src/modules/configuration/auth/domain/managers/admin-queue/login-admin-queue.manager.ts b/src/modules/configuration/auth/domain/managers/admin-queue/login-admin-queue.manager.ts index 51ac335..a9ea8ff 100644 --- a/src/modules/configuration/auth/domain/managers/admin-queue/login-admin-queue.manager.ts +++ b/src/modules/configuration/auth/domain/managers/admin-queue/login-admin-queue.manager.ts @@ -14,8 +14,9 @@ import { UserModel } from 'src/modules/user-related/user/data/models/user.model' import { UserEntity } from 'src/modules/user-related/user/domain/entities/user.entity'; import { In } from 'typeorm'; import { UserRole } from 'src/modules/user-related/user/constants'; -import { LogUserType } from 'src/core/helpers/constant'; +import { AppSource, LogUserType } from 'src/core/helpers/constant'; import { LogUserLoginEvent } from 'src/modules/configuration/log/domain/entities/log-user-login.event'; +import { UserLoginEntity } from 'src/modules/user-related/user/domain/entities/user-login.entity'; @Injectable() export class LoginAdminQueueManager extends BaseCustomManager { @@ -62,13 +63,19 @@ export class LoginAdminQueueManager extends BaseCustomManager { }, }); - if (this.userLogin.user_login) { + const hasLoginAsQueue = this.userLogin?.user_login?.find( + (item) => item.source === AppSource.QUEUE_ADMIN, + ); + + console.log(hasLoginAsQueue, userLoginItem); + + if (hasLoginAsQueue && hasLoginAsQueue?.item_id !== this.data.item_id) { throw new UnauthorizedException({ statusCode: HttpStatus.UNAUTHORIZED, - message: `Akun anda sudah login di perangkat lain.`, + message: `Akun anda sudah login di item "${hasLoginAsQueue?.item_name}"`, error: 'Unauthorized', }); - } else if (itemLogin) { + } else if (itemLogin && itemLogin.user_id !== this.userLogin.id) { throw new UnauthorizedException({ statusCode: HttpStatus.UNAUTHORIZED, message: `"${userLoginItem.name}" masih login sebagai admin antrian `, @@ -85,32 +92,28 @@ export class LoginAdminQueueManager extends BaseCustomManager { user_privilege_id: this.userLogin.user_privilege_id, item_id: this.data.item_id, item_name: this.data.item_name, + source: AppSource.QUEUE_ADMIN, }; Logger.debug('Sign Token Admin Queue', 'LoginAdminQueueManager'); this.token = this.session.createAccessToken(tokenData); - Logger.debug('refreshToken Admin Queue', 'LoginAdminQueueManager'); - const refreshToken = this.session.createAccessToken(tokenData); - - Logger.debug('Update Refresh Token Admin Queue', 'LoginAdminQueueManager'); - + Logger.debug('Save Login Token', 'LoginManager'); + const userLoginData: UserLoginEntity = { + user_id: this.userLogin.id, + login_token: this.token, + login_date: new Date().getTime(), + source: AppSource.QUEUE_ADMIN, + role: this.userLogin.role, + item_id: this.data.item_id, + item_name: this.data.item_name, + }; + if (hasLoginAsQueue?.item_id === this.data.item_id) { + Object.assign(userLoginData, { id: hasLoginAsQueue.id }); + } // Update refresh token - await this.dataService.update( - this.queryRunner, - this.entityTarget, - { id: this.userLogin.id }, - { - refresh_token: refreshToken, - user_login: { - user_id: this.userLogin.id, - login_token: this.token, - login_date: new Date().getTime(), - item_id: this.data.item_id, - item_name: this.data.item_name, - }, - }, - ); + await this.dataService.saveUserLogin(userLoginData); + await this.publishEvents(); Logger.debug('Process Login Admin Queue Done', 'LoginAdminQueueManager'); @@ -149,6 +152,7 @@ export class LoginAdminQueueManager extends BaseCustomManager { created_at: new Date().getTime(), item_id: this.data.item_id, item_name: this.data.item_name, + source: AppSource.QUEUE_ADMIN, }, }, ]; diff --git a/src/modules/configuration/auth/domain/managers/admin-queue/logout-admin-queue.manager.ts b/src/modules/configuration/auth/domain/managers/admin-queue/logout-admin-queue.manager.ts index b40f7ad..4475ad0 100644 --- a/src/modules/configuration/auth/domain/managers/admin-queue/logout-admin-queue.manager.ts +++ b/src/modules/configuration/auth/domain/managers/admin-queue/logout-admin-queue.manager.ts @@ -1,4 +1,4 @@ -import { LogUserType } from 'src/core/helpers/constant'; +import { AppSource, LogUserType } from 'src/core/helpers/constant'; import { BaseCustomManager } from 'src/core/modules/domain/usecase/managers/base-custom.manager'; import { EventTopics } from 'src/core/strings/constants/interface.constants'; import { LogUserLoginEvent } from 'src/modules/configuration/log/domain/entities/log-user-login.event'; @@ -17,21 +17,17 @@ export class LogoutAdminQueueManager extends BaseCustomManager { } async process(): Promise { - const id = this.data?.id ?? this.user.id; + const id = this.data?.user_id ?? this.user.id; this.userLogin = await this.dataService.getOneByOptions({ where: { id }, }); - await this.dataService.update( - this.queryRunner, - this.entityTarget, - { id: this.userLogin.id }, - { - refresh_token: null, - user_login: null, - }, - ); + await this.dataService.removeUserLogin({ + user_id: id, + source: AppSource.QUEUE_ADMIN, + }); + await this.publishEvents(); return; } @@ -58,6 +54,7 @@ export class LogoutAdminQueueManager extends BaseCustomManager { user_id: this.userLogin.id, username: this.userLogin.name, created_at: new Date().getTime(), + source: AppSource.QUEUE_ADMIN, }, }, ]; diff --git a/src/modules/configuration/auth/domain/managers/login.manager.ts b/src/modules/configuration/auth/domain/managers/login.manager.ts index 0ef6596..a773b9e 100644 --- a/src/modules/configuration/auth/domain/managers/login.manager.ts +++ b/src/modules/configuration/auth/domain/managers/login.manager.ts @@ -14,8 +14,9 @@ import { UserModel } from 'src/modules/user-related/user/data/models/user.model' import { UserEntity } from 'src/modules/user-related/user/domain/entities/user.entity'; import { Not } from 'typeorm'; import { UserRole } from 'src/modules/user-related/user/constants'; -import { LogUserType } from 'src/core/helpers/constant'; +import { AppSource, LogUserType } from 'src/core/helpers/constant'; import { LogUserLoginEvent } from 'src/modules/configuration/log/domain/entities/log-user-login.event'; +import { UserLoginEntity } from 'src/modules/user-related/user/domain/entities/user-login.entity'; @Injectable() export class LoginManager extends BaseCustomManager { @@ -62,24 +63,25 @@ export class LoginManager extends BaseCustomManager { username: this.userLogin.username, role: this.userLogin.role, user_privilege_id: this.userLogin.user_privilege_id, + source: AppSource.POS_ADMIN, }; Logger.debug('Sign Token', 'LoginManager'); this.token = this.session.createAccessToken(tokenData); - Logger.debug('refreshToken', 'LoginManager'); - const refreshToken = this.session.createAccessToken(tokenData); + Logger.debug('Save Login Token', 'LoginManager'); + const userLoginData: UserLoginEntity = { + user_id: this.userLogin.id, + login_token: this.token, + login_date: new Date().getTime(), + source: AppSource.POS_ADMIN, + role: this.userLogin.role, + item_id: null, + item_name: null, + }; - Logger.debug('Update Refresh Token', 'LoginManager'); // Update refresh token - await this.dataService.update( - this.queryRunner, - this.entityTarget, - { id: this.userLogin.id }, - { - refresh_token: refreshToken, - }, - ); + await this.dataService.saveUserLogin(userLoginData); await this.publishEvents(); Logger.debug('Process Login Done', 'LoginManager'); @@ -131,6 +133,7 @@ export class LoginManager extends BaseCustomManager { user_id: this.userLogin.id, username: this.userLogin.username, created_at: new Date().getTime(), + source: AppSource.POS_ADMIN, }, }, ]; diff --git a/src/modules/configuration/auth/domain/managers/logout.manager.ts b/src/modules/configuration/auth/domain/managers/logout.manager.ts index cb1b18b..9586750 100644 --- a/src/modules/configuration/auth/domain/managers/logout.manager.ts +++ b/src/modules/configuration/auth/domain/managers/logout.manager.ts @@ -2,7 +2,7 @@ import { BaseCustomManager } from 'src/core/modules/domain/usecase/managers/base 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'; -import { LogUserType } from 'src/core/helpers/constant'; +import { AppSource, LogUserType } from 'src/core/helpers/constant'; import { LogUserLoginEvent } from 'src/modules/configuration/log/domain/entities/log-user-login.event'; export class LogoutManager extends BaseCustomManager { @@ -15,15 +15,11 @@ export class LogoutManager extends BaseCustomManager { } async process(): Promise { - await this.dataService.update( - this.queryRunner, - this.entityTarget, - { id: this.user.id }, - { - refresh_token: null, - }, - ); - + await this.dataService.removeUserLogin({ + user_id: this.user.id, + login_token: this.userProvider.token, + source: AppSource.POS_ADMIN, + }); await this.publishEvents(); return; } @@ -50,6 +46,7 @@ export class LogoutManager extends BaseCustomManager { user_id: this.user.id, username: this.user.name, created_at: new Date().getTime(), + source: AppSource.POS_ADMIN, }, }, ]; diff --git a/src/modules/configuration/auth/infrastructure/auth-admin-queue.controller.ts b/src/modules/configuration/auth/infrastructure/auth-admin-queue.controller.ts index 41da84e..9da9a14 100644 --- a/src/modules/configuration/auth/infrastructure/auth-admin-queue.controller.ts +++ b/src/modules/configuration/auth/infrastructure/auth-admin-queue.controller.ts @@ -1,7 +1,7 @@ import { Body, Controller, Delete, Param, Post, Put } from '@nestjs/common'; import { ExcludePrivilege, Public } from 'src/core/guards'; import { ApiBearerAuth } from '@nestjs/swagger'; -import { LoginQueueDto } from './dto/login.dto'; +import { ForceLogoutDto, LoginQueueDto } from './dto/login.dto'; import { AuthAdminQueueOrchestrator } from '../domain/auth-admin-queue.orchestrator'; @Controller('v1/auth/queue') @@ -18,12 +18,18 @@ export class AuthAdminQueueController { @Public(false) @ExcludePrivilege() @Delete('logout') - async logoout() { + async logout() { return await this.orchestrator.logout(); } - @Put(':id/logout') - async logoutQueueAdmin(@Param('id') dataId: string) { - return await this.orchestrator.logout(dataId); + @Put(':user_id/logout') + async logoutQueueAdmin(@Param('user_id') userId: string) { + return await this.orchestrator.logout(userId); + } + + @Post('force-logout') + @Public(true) + async forceLogout(@Body() body: ForceLogoutDto) { + return await this.orchestrator.forceLogout(body.token); } } diff --git a/src/modules/configuration/auth/infrastructure/auth.controller.ts b/src/modules/configuration/auth/infrastructure/auth.controller.ts index 9c0bf10..0c0f0ae 100644 --- a/src/modules/configuration/auth/infrastructure/auth.controller.ts +++ b/src/modules/configuration/auth/infrastructure/auth.controller.ts @@ -2,7 +2,7 @@ import { Body, Controller, Delete, Post } from '@nestjs/common'; import { ExcludePrivilege, Public } from 'src/core/guards'; import { AuthOrchestrator } from '../domain/auth.orchestrator'; import { ApiBearerAuth } from '@nestjs/swagger'; -import { LoginDto } from './dto/login.dto'; +import { ForceLogoutDto, LoginDto } from './dto/login.dto'; @Controller('v1/auth') export class AuthController { @@ -18,7 +18,13 @@ export class AuthController { @Public(false) @ExcludePrivilege() @Delete('logout') - async logoout() { + async logout() { return await this.orchestrator.logout(); } + + @Post('force-logout') + @Public(true) + async forceLogout(@Body() body: ForceLogoutDto) { + return await this.orchestrator.forceLogout(body.token); + } } diff --git a/src/modules/configuration/auth/infrastructure/dto/login.dto.ts b/src/modules/configuration/auth/infrastructure/dto/login.dto.ts index 7036ea8..145437c 100644 --- a/src/modules/configuration/auth/infrastructure/dto/login.dto.ts +++ b/src/modules/configuration/auth/infrastructure/dto/login.dto.ts @@ -29,3 +29,9 @@ export class LoginQueueDto implements LoginRequest { @IsString() item_name: string; } + +export class ForceLogoutDto { + @ApiProperty({ required: true }) + @IsString() + token: string; +} diff --git a/src/modules/configuration/log/data/models/log-user-login.model.ts b/src/modules/configuration/log/data/models/log-user-login.model.ts index f831f78..e99a3b1 100644 --- a/src/modules/configuration/log/data/models/log-user-login.model.ts +++ b/src/modules/configuration/log/data/models/log-user-login.model.ts @@ -4,7 +4,7 @@ import { Column, Entity } from 'typeorm'; import { BaseCoreModel } from 'src/core/modules/data/model/base-core.model'; import { LogUserLoginEntity } from '../../domain/entities/log-user-login.entity'; import { UserRole } from '../../../../user-related/user/constants'; -import { LogUserType } from 'src/core/helpers/constant'; +import { AppSource, LogUserType } from 'src/core/helpers/constant'; @Entity(TABLE_NAME.LOG_USER_LOGIN) export class LogUserLoginModel @@ -31,4 +31,7 @@ export class LogUserLoginModel @Column({ type: 'bigint', nullable: true }) created_at: number; + + @Column({ type: 'enum', enum: AppSource, nullable: true }) + source: AppSource; } diff --git a/src/modules/user-related/tenant/infrastructure/dto/tenant.dto.ts b/src/modules/user-related/tenant/infrastructure/dto/tenant.dto.ts index 48076e4..9d2328b 100644 --- a/src/modules/user-related/tenant/infrastructure/dto/tenant.dto.ts +++ b/src/modules/user-related/tenant/infrastructure/dto/tenant.dto.ts @@ -35,7 +35,4 @@ export class TenantDto extends BaseStatusDto implements UserEntity { @Exclude() role: UserRole; - - @Exclude() - refresh_token: string; } diff --git a/src/modules/user-related/tenant/infrastructure/dto/update-password-tenant.dto.ts b/src/modules/user-related/tenant/infrastructure/dto/update-password-tenant.dto.ts index a4fee8b..ed85930 100644 --- a/src/modules/user-related/tenant/infrastructure/dto/update-password-tenant.dto.ts +++ b/src/modules/user-related/tenant/infrastructure/dto/update-password-tenant.dto.ts @@ -27,7 +27,4 @@ export class UpdatePasswordTenantDto @Exclude() role: UserRole; - - @Exclude() - refresh_token: string; } diff --git a/src/modules/user-related/tenant/infrastructure/dto/update-tenant.dto.ts b/src/modules/user-related/tenant/infrastructure/dto/update-tenant.dto.ts index 14487dc..666aaff 100644 --- a/src/modules/user-related/tenant/infrastructure/dto/update-tenant.dto.ts +++ b/src/modules/user-related/tenant/infrastructure/dto/update-tenant.dto.ts @@ -34,7 +34,4 @@ export class UpdateTenantDto extends BaseStatusDto implements UserEntity { @Exclude() role: UserRole; - - @Exclude() - refresh_token: string; } diff --git a/src/modules/user-related/user/data/models/user-login.model.ts b/src/modules/user-related/user/data/models/user-login.model.ts index 31b47e5..baf4442 100644 --- a/src/modules/user-related/user/data/models/user-login.model.ts +++ b/src/modules/user-related/user/data/models/user-login.model.ts @@ -1,9 +1,11 @@ import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; import { UserEntity } from '../../domain/entities/user.entity'; -import { Column, Entity, JoinColumn, OneToOne } from 'typeorm'; +import { Column, Entity, JoinColumn, ManyToOne, OneToOne } from 'typeorm'; import { UserLoginEntity } from '../../domain/entities/user-login.entity'; import { UserModel } from './user.model'; import { BaseCoreModel } from 'src/core/modules/data/model/base-core.model'; +import { UserRole } from '../../constants'; +import { AppSource } from 'src/core/helpers/constant'; @Entity(TABLE_NAME.USER_LOGIN) export class UserLoginModel @@ -25,7 +27,13 @@ export class UserLoginModel @Column({ type: 'varchar', nullable: true }) item_name: string; - @OneToOne(() => UserModel, (model) => model.user_login, { + @Column({ type: 'enum', enum: UserRole, nullable: true }) + role: UserRole; + + @Column({ type: 'enum', enum: AppSource, nullable: true }) + source: AppSource; + + @ManyToOne(() => UserModel, (model) => model.user_login, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false, diff --git a/src/modules/user-related/user/data/models/user.model.ts b/src/modules/user-related/user/data/models/user.model.ts index 1d8c33c..ff0a6f8 100644 --- a/src/modules/user-related/user/data/models/user.model.ts +++ b/src/modules/user-related/user/data/models/user.model.ts @@ -19,9 +19,6 @@ export class UserModel extends BaseStatusModel implements UserEntity { - @Column('varchar', { name: 'refresh_token', nullable: true }) - refresh_token: string; - @Column('varchar', { name: 'name', nullable: true }) name: string; @@ -58,7 +55,7 @@ export class UserModel items: ItemModel[]; // relasi ke user login for admin queue - @OneToOne(() => UserLoginModel, (model) => model.user, { + @OneToMany(() => UserLoginModel, (model) => model.user, { cascade: true, }) user_login: UserLoginModel; diff --git a/src/modules/user-related/user/data/services/user-data.service.ts b/src/modules/user-related/user/data/services/user-data.service.ts index bac12c5..9a5a781 100644 --- a/src/modules/user-related/user/data/services/user-data.service.ts +++ b/src/modules/user-related/user/data/services/user-data.service.ts @@ -3,9 +3,16 @@ import { BaseDataService } from 'src/core/modules/data/service/base-data.service import { UserEntity } from '../../domain/entities/user.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { UserModel } from '../models/user.model'; -import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; +import { + CONNECTION_NAME, + OPERATION, +} from 'src/core/strings/constants/base.constants'; import { IsNull, Not, Repository } from 'typeorm'; import { UserLoginModel } from '../models/user-login.model'; +import { UserLoginEntity } from '../../domain/entities/user-login.entity'; +import { LogUserType } from 'src/core/helpers/constant'; +import { EventBus } from '@nestjs/cqrs'; +import { LogUserLoginEvent } from 'src/modules/configuration/log/domain/entities/log-user-login.event'; @Injectable() export class UserDataService extends BaseDataService { @@ -15,6 +22,8 @@ export class UserDataService extends BaseDataService { @InjectRepository(UserLoginModel, CONNECTION_NAME.DEFAULT) private repoLoginUser: Repository, + + private eventBus: EventBus, ) { super(repo); } @@ -24,4 +33,55 @@ export class UserDataService extends BaseDataService { where: { item_id: itemId, user_id: Not(IsNull()) }, }); } + + async saveUserLogin(userLogin: UserLoginEntity) { + return this.repoLoginUser.save(userLogin); + } + + async removeUserLogin(userLogin: Partial) { + return this.repoLoginUser.delete(userLogin); + } + + async forceLogout(token: string) { + const data = await this.repoLoginUser.findOneBy({ login_token: token }); + + if (data) return; + else { + await this.repoLoginUser.delete({ login_token: token }); + + const [, body] = token.split('.'); + const bodyToken = JSON.parse(atob(body)); + const user = { + role: bodyToken.role, + user_id: bodyToken.id, + username: bodyToken.username, + user_privilege_id: bodyToken.user_privilege_id, + item_id: bodyToken.item_id, + item_name: bodyToken.item_name, + source: bodyToken.source, + }; + + const userLogout = { + type: LogUserType.logout, + created_at: new Date().getTime(), + name: user.username, + user_privilege_id: user.user_privilege_id, + ...user, + }; + + this.eventBus.publish( + new LogUserLoginEvent({ + id: user.user_id, + old: null, + data: userLogout, + user: userLogout as any, + description: 'Logout', + module: UserModel.name, + op: OPERATION.UPDATE, + }), + ); + + return; + } + } } diff --git a/src/modules/user-related/user/domain/entities/user-login.entity.ts b/src/modules/user-related/user/domain/entities/user-login.entity.ts index 1c5dd0c..cc940e2 100644 --- a/src/modules/user-related/user/domain/entities/user-login.entity.ts +++ b/src/modules/user-related/user/domain/entities/user-login.entity.ts @@ -1,8 +1,13 @@ import { BaseCoreEntity } from 'src/core/modules/domain/entities/base-core.entity'; +import { UserRole } from '../../constants'; +import { AppSource } from 'src/core/helpers/constant'; export interface UserLoginEntity extends BaseCoreEntity { login_date: number; login_token: string; + user_id: string; item_id: string; item_name: string; + role: UserRole; + source: AppSource; } diff --git a/src/modules/user-related/user/domain/entities/user.entity.ts b/src/modules/user-related/user/domain/entities/user.entity.ts index 11f3363..fcaaae6 100644 --- a/src/modules/user-related/user/domain/entities/user.entity.ts +++ b/src/modules/user-related/user/domain/entities/user.entity.ts @@ -6,7 +6,6 @@ export interface UserEntity extends BaseStatusEntity { username: string; password: string; role: UserRole; - refresh_token: string; // tenant data share_margin: number; diff --git a/src/modules/user-related/user/domain/usecases/managers/index-user.manager.ts b/src/modules/user-related/user/domain/usecases/managers/index-user.manager.ts index 120e459..c437dc7 100644 --- a/src/modules/user-related/user/domain/usecases/managers/index-user.manager.ts +++ b/src/modules/user-related/user/domain/usecases/managers/index-user.manager.ts @@ -54,6 +54,7 @@ export class IndexUserManager extends BaseIndexManager { 'user_login.login_date', 'user_login.item_id', 'user_login.item_name', + 'user_login.source', ]; } diff --git a/src/modules/user-related/user/infrastructure/dto/update-password-user.dto.ts b/src/modules/user-related/user/infrastructure/dto/update-password-user.dto.ts index 310e70b..771fbc3 100644 --- a/src/modules/user-related/user/infrastructure/dto/update-password-user.dto.ts +++ b/src/modules/user-related/user/infrastructure/dto/update-password-user.dto.ts @@ -24,7 +24,4 @@ export class UpdatePasswordUserDto extends BaseStatusDto implements UserEntity { @Exclude() role: UserRole; - - @Exclude() - refresh_token: string; } diff --git a/src/modules/user-related/user/infrastructure/dto/update-user.dto.ts b/src/modules/user-related/user/infrastructure/dto/update-user.dto.ts index d2daca3..738a7d8 100644 --- a/src/modules/user-related/user/infrastructure/dto/update-user.dto.ts +++ b/src/modules/user-related/user/infrastructure/dto/update-user.dto.ts @@ -39,7 +39,4 @@ export class UpdateUserDto extends BaseStatusDto implements UserEntity { @Exclude() password: string; - - @Exclude() - refresh_token: string; } diff --git a/src/modules/user-related/user/infrastructure/dto/user.dto.ts b/src/modules/user-related/user/infrastructure/dto/user.dto.ts index 97156f4..239e146 100644 --- a/src/modules/user-related/user/infrastructure/dto/user.dto.ts +++ b/src/modules/user-related/user/infrastructure/dto/user.dto.ts @@ -40,7 +40,4 @@ export class UserDto extends BaseStatusDto implements UserEntity { @Exclude() email: string; - - @Exclude() - refresh_token: string; } From 09d6dbaab2686415701a97329fd291a4a96e5abf Mon Sep 17 00:00:00 2001 From: Firman Ramdhani <33869609+firmanramdhani@users.noreply.github.com> Date: Wed, 18 Sep 2024 16:30:23 +0700 Subject: [PATCH 2/2] feat: remove console on auth --- .../domain/managers/admin-queue/login-admin-queue.manager.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/modules/configuration/auth/domain/managers/admin-queue/login-admin-queue.manager.ts b/src/modules/configuration/auth/domain/managers/admin-queue/login-admin-queue.manager.ts index a9ea8ff..cc6ecab 100644 --- a/src/modules/configuration/auth/domain/managers/admin-queue/login-admin-queue.manager.ts +++ b/src/modules/configuration/auth/domain/managers/admin-queue/login-admin-queue.manager.ts @@ -67,8 +67,6 @@ export class LoginAdminQueueManager extends BaseCustomManager { (item) => item.source === AppSource.QUEUE_ADMIN, ); - console.log(hasLoginAsQueue, userLoginItem); - if (hasLoginAsQueue && hasLoginAsQueue?.item_id !== this.data.item_id) { throw new UnauthorizedException({ statusCode: HttpStatus.UNAUTHORIZED,