feat(SPG-885): add validation login admin queue
continuous-integration/drone/push Build is passing Details

pull/89/head
Firman Ramdhani 2024-09-12 19:17:46 +07:00
parent 14dd2880bc
commit c897e4fcde
14 changed files with 136 additions and 21 deletions

View File

@ -0,0 +1,17 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddColumnItemId1726139426994 implements MigrationInterface {
name = 'AddColumnItemId1726139426994';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "users_login" ADD "item_id" uuid`);
await queryRunner.query(`ALTER TABLE "log_users_login" ADD "item_id" uuid`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "log_users_login" DROP COLUMN "item_id"`,
);
await queryRunner.query(`ALTER TABLE "users_login" DROP COLUMN "item_id"`);
}
}

View File

@ -0,0 +1,23 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddColumnItemName1726141393404 implements MigrationInterface {
name = 'AddColumnItemName1726141393404';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "users_login" ADD "item_name" character varying`,
);
await queryRunner.query(
`ALTER TABLE "log_users_login" ADD "item_name" character varying`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "log_users_login" DROP COLUMN "item_name"`,
);
await queryRunner.query(
`ALTER TABLE "users_login" DROP COLUMN "item_name"`,
);
}
}

View File

@ -13,11 +13,15 @@ import { AuthAdminQueueController } from './infrastructure/auth-admin-queue.cont
import { AuthAdminQueueOrchestrator } from './domain/auth-admin-queue.orchestrator';
import { LoginAdminQueueManager } from './domain/managers/admin-queue/login-admin-queue.manager';
import { LogoutAdminQueueManager } from './domain/managers/admin-queue/logout-admin-queue.manager';
import { UserLoginModel } from 'src/modules/user-related/user/data/models/user-login.model';
@Module({
imports: [
ConfigModule.forRoot(),
TypeOrmModule.forFeature([UserModel], CONNECTION_NAME.DEFAULT),
TypeOrmModule.forFeature(
[UserModel, UserLoginModel],
CONNECTION_NAME.DEFAULT,
),
CqrsModule,
],
controllers: [AuthController, AuthAdminQueueController],

View File

@ -33,6 +33,10 @@ export class LoginAdminQueueManager extends BaseCustomManager<UserEntity> {
}
async process(): Promise<void> {
const itemLogin = await this.dataService.getLoginUserByItem(
this.data.item_id,
);
// get user active by username
this.userLogin = await this.dataService.getOneByOptions({
where: {
@ -42,6 +46,7 @@ export class LoginAdminQueueManager extends BaseCustomManager<UserEntity> {
},
relations: ['user_login'],
});
if (!this.userLogin) this.throwError();
// validasi password
@ -51,13 +56,22 @@ export class LoginAdminQueueManager extends BaseCustomManager<UserEntity> {
);
if (!valid) this.throwError();
if (
this.userLogin.user_login &&
this.userLogin.role === UserRole.QUEUE_ADMIN
) {
const userLoginItem = await this.dataService.getOneByOptions({
where: {
id: itemLogin?.user_id,
},
});
if (this.userLogin.user_login) {
throw new UnauthorizedException({
statusCode: HttpStatus.UNAUTHORIZED,
message: `Gagal! akun anda sudah login di perangkat lain.`,
message: `Akun anda sudah login di perangkat lain.`,
error: 'Unauthorized',
});
} else if (itemLogin) {
throw new UnauthorizedException({
statusCode: HttpStatus.UNAUTHORIZED,
message: `"${userLoginItem.name}" masih login sebagai admin antrian `,
error: 'Unauthorized',
});
}
@ -69,6 +83,8 @@ export class LoginAdminQueueManager extends BaseCustomManager<UserEntity> {
username: this.userLogin.username,
role: this.userLogin.role,
user_privilege_id: this.userLogin.user_privilege_id,
item_id: this.data.item_id,
item_name: this.data.item_name,
};
Logger.debug('Sign Token Admin Queue', 'LoginAdminQueueManager');
@ -79,22 +95,21 @@ export class LoginAdminQueueManager extends BaseCustomManager<UserEntity> {
Logger.debug('Update Refresh Token Admin Queue', 'LoginAdminQueueManager');
const newDataUser = { refresh_token: refreshToken };
if (this.userLogin.role === UserRole.QUEUE_ADMIN) {
Object.assign(newDataUser, {
user_login: {
user_id: this.userLogin.id,
login_token: this.token,
login_date: new Date().getTime(),
},
});
}
// Update refresh token
await this.dataService.update(
this.queryRunner,
this.entityTarget,
{ id: this.userLogin.id },
newDataUser,
{
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.publishEvents();
@ -113,6 +128,8 @@ export class LoginAdminQueueManager extends BaseCustomManager<UserEntity> {
username: this.userLogin.username,
role: this.userLogin.role,
token: this.token,
item_id: this.data.item_id,
item_name: this.data.item_name,
};
}
@ -130,6 +147,8 @@ export class LoginAdminQueueManager extends BaseCustomManager<UserEntity> {
user_id: this.userLogin.id,
username: this.userLogin.username,
created_at: new Date().getTime(),
item_id: this.data.item_id,
item_name: this.data.item_name,
},
},
];

View File

@ -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 { LoginDto } from './dto/login.dto';
import { LoginQueueDto } from './dto/login.dto';
import { AuthAdminQueueOrchestrator } from '../domain/auth-admin-queue.orchestrator';
@Controller('v1/auth/queue')
@ -10,7 +10,7 @@ export class AuthAdminQueueController {
@Post()
@Public(true)
async login(@Body() body: LoginDto) {
async login(@Body() body: LoginQueueDto) {
return await this.orchestrator.login(body);
}

View File

@ -11,3 +11,21 @@ export class LoginDto implements LoginRequest {
@IsString()
password: string;
}
export class LoginQueueDto implements LoginRequest {
@ApiProperty({ name: 'username', required: true, default: 'superadmin' })
@IsString()
username: string;
@ApiProperty({ name: 'password', required: true, default: 'Eigen123!' })
@IsString()
password: string;
@ApiProperty({ name: 'item_id', required: true, default: 'string' })
@IsString()
item_id: string;
@ApiProperty({ name: 'item_name', required: true, default: 'string' })
@IsString()
item_name: string;
}

View File

@ -51,6 +51,7 @@ import {
import { SeasonPeriodDataService } from 'src/modules/season-related/season-period/data/services/season-period-data.service';
import { SeasonPeriodModel } from 'src/modules/season-related/season-period/data/models/season-period.model';
import { TransactionDemographyModel } from 'src/modules/transaction/transaction/data/models/transaction-demography.model';
import { UserLoginModel } from 'src/modules/user-related/user/data/models/user-login.model';
@Module({
imports: [
@ -61,6 +62,7 @@ import { TransactionDemographyModel } from 'src/modules/transaction/transaction/
ItemRateModel,
SeasonPeriodModel,
UserModel,
UserLoginModel,
TransactionModel,
TransactionTaxModel,
TransactionItemModel,

View File

@ -20,6 +20,12 @@ export class LogUserLoginModel
@Column({ type: 'uuid', nullable: true })
user_id: string;
@Column({ type: 'uuid', nullable: true })
item_id: string;
@Column({ type: 'varchar', nullable: true })
item_name: string;
@Column({ type: 'varchar', nullable: true })
username: string;

View File

@ -6,6 +6,8 @@ export interface LogUserLoginEntity extends BaseCoreEntity {
type: LogUserType;
role: UserRole;
user_id: string;
item_id: string;
item_name: string;
username: string;
created_at: number;
}

View File

@ -25,11 +25,15 @@ import { UserDataService } from '../user/data/services/user-data.service';
import { UserReadService } from '../user/data/services/user-read.service';
import { TenantItemReadController } from './infrastructure/tenant-item-read.controller';
import { TenantItemDataController } from './infrastructure/tenant-item-data.controller';
import { UserLoginModel } from '../user/data/models/user-login.model';
@Module({
imports: [
ConfigModule.forRoot(),
TypeOrmModule.forFeature([UserModel], CONNECTION_NAME.DEFAULT),
TypeOrmModule.forFeature(
[UserModel, UserLoginModel],
CONNECTION_NAME.DEFAULT,
),
CqrsModule,
],
controllers: [

View File

@ -19,6 +19,12 @@ export class UserLoginModel
@Column('varchar', { name: 'user_id', nullable: true })
user_id: string;
@Column({ type: 'uuid', nullable: true })
item_id: string;
@Column({ type: 'varchar', nullable: true })
item_name: string;
@OneToOne(() => UserModel, (model) => model.user_login, {
onDelete: 'CASCADE',
onUpdate: 'CASCADE',

View File

@ -4,14 +4,24 @@ 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 { Repository } from 'typeorm';
import { IsNull, Not, Repository } from 'typeorm';
import { UserLoginModel } from '../models/user-login.model';
@Injectable()
export class UserDataService extends BaseDataService<UserEntity> {
constructor(
@InjectRepository(UserModel, CONNECTION_NAME.DEFAULT)
private repo: Repository<UserModel>,
@InjectRepository(UserLoginModel, CONNECTION_NAME.DEFAULT)
private repoLoginUser: Repository<UserLoginModel>,
) {
super(repo);
}
async getLoginUserByItem(itemId: string) {
return this.repoLoginUser.findOne({
where: { item_id: itemId, user_id: Not(IsNull()) },
});
}
}

View File

@ -3,4 +3,6 @@ import { BaseCoreEntity } from 'src/core/modules/domain/entities/base-core.entit
export interface UserLoginEntity extends BaseCoreEntity {
login_date: number;
login_token: string;
item_id: string;
item_name: string;
}

View File

@ -52,6 +52,8 @@ export class IndexUserManager extends BaseIndexManager<UserEntity> {
'user_login.id',
'user_login.login_date',
'user_login.item_id',
'user_login.item_name',
];
}