auth module controllers carried

This commit is contained in:
Berkay 2025-07-26 21:51:27 +03:00
parent 2e10de9758
commit f39dc541e1
58 changed files with 745 additions and 527 deletions

View File

@ -20,7 +20,8 @@
"redis": "^5.6.1",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1",
"uuid": "^11.1.0"
"uuid": "^11.1.0",
"zod": "^4.0.10"
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",
@ -11655,6 +11656,15 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/zod": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.0.10.tgz",
"integrity": "sha512-3vB+UU3/VmLL2lvwcY/4RV2i9z/YU0DTV/tDuYjrwmx5WeJ7hwy+rGEEx8glHp6Yxw7ibRbKSaIFBgReRPe5KA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
}
}
}

View File

@ -31,7 +31,8 @@
"redis": "^5.6.1",
"reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1",
"uuid": "^11.1.0"
"uuid": "^11.1.0",
"zod": "^4.0.10"
},
"devDependencies": {
"@eslint/eslintrc": "^3.2.0",

View File

@ -3080,6 +3080,7 @@ model occupant_types {
occupant_code String @default("") @db.VarChar
occupant_category String @default("") @db.VarChar
occupant_category_type String @default("") @db.VarChar
function_retriever String @default("") @db.VarChar
occupant_is_unique Boolean @default(false)
ref_id String? @db.VarChar(100)
replication_id Int @default(0) @db.SmallInt
@ -3474,6 +3475,7 @@ model staff {
staff_code String @db.VarChar
duties_id Int
duties_uu_id String @db.VarChar
function_retriever String @default("") @db.VarChar
ref_id String? @db.VarChar(100)
replication_id Int @default(0) @db.SmallInt
cryp_uu_id String? @db.VarChar

View File

@ -7,8 +7,10 @@ import {
Param,
Body,
HttpCode,
UseGuards,
} from '@nestjs/common';
import { AccountsService } from './accounts.service';
import { AuthControlGuard, EndpointControlGuard } from '../middleware/access-control.guard';
@Controller('accounts')
export class AccountsController {
@ -16,6 +18,7 @@ export class AccountsController {
@Post('filter')
@HttpCode(200)
@UseGuards(AuthControlGuard, EndpointControlGuard)
async filterAccounts(@Body() query: any) {
const result = await this.accountsService.findWithPagination(query);
const { pagination, data } = result;

View File

@ -4,10 +4,19 @@ import { AccountsController } from './accounts.controller';
import { PrismaModule } from '@/prisma/prisma.module';
import { CacheService } from '../cache.service';
import { UtilsModule } from '../utils/utils.module';
import {
AuthControlGuard,
EndpointControlGuard,
} from '@/src/middleware/access-control.guard';
@Module({
imports: [PrismaModule, UtilsModule],
providers: [AccountsService, CacheService],
providers: [
AccountsService,
CacheService,
AuthControlGuard,
EndpointControlGuard,
],
controllers: [AccountsController],
})
export class AccountsModule {}

View File

@ -13,6 +13,7 @@ import { AuthModule } from './auth/auth.module';
import { RedisModule } from '@liaoliaots/nestjs-redis';
import { CacheService } from './cache.service';
import { LoggerMiddleware } from '@/src/middleware/logger.middleware';
import { DiscoveryModule } from '@nestjs/core';
const redisConfig = {
host: '10.10.2.15',
@ -26,13 +27,14 @@ const serviceModuleList = [
RedisModule.forRoot({
config: redisConfig,
}),
DiscoveryModule,
];
const controllersList = [AppController];
const providersList = [AppService, CacheService];
const exportsList = [CacheService];
@Module({
imports: [...modulesList, ...serviceModuleList],
imports: [...serviceModuleList, ...modulesList],
controllers: controllersList,
providers: providersList,
exports: exportsList,

View File

@ -0,0 +1,74 @@
import {
Controller,
Get,
Post,
Put,
Delete,
Param,
Body,
HttpCode,
UseGuards,
} from '@nestjs/common';
import { AuthService } from './auth.service';
import { userLoginValidator } from './login/dtoValidator';
import { userSelectValidator } from './select/dtoValidator';
import { userLogoutValidator } from './logout/dtoValidator';
import { AuthControlGuard } from '../middleware/access-control.guard';
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Post('login')
@HttpCode(200)
async login(@Body() query: userLoginValidator) {
return await this.authService.login(query);
}
@Post('select')
@HttpCode(200)
@UseGuards(AuthControlGuard)
async select(@Body() query: userSelectValidator) {
return { message: 'Logout successful' };
}
@Post('/password/create')
@HttpCode(200)
async createPassword() {
return { message: 'Password created successfully' };
}
@Post('/password/change')
@HttpCode(200)
@UseGuards(AuthControlGuard)
async changePassword() {
return { message: 'Password changed successfully' };
}
@Post('/password/reset')
@HttpCode(200)
async resetPassword() {
return { message: 'Password reset successfully' };
}
@Post('/password/verify-otp')
@HttpCode(200)
@UseGuards(AuthControlGuard)
async verifyOtp() {
return { message: 'Password verified successfully' };
}
@Post('logout')
@HttpCode(200)
@UseGuards(AuthControlGuard)
async logout(@Body() query: userLogoutValidator) {
return { message: 'Logout successful' };
}
@Post('disconnect')
@HttpCode(200)
@UseGuards(AuthControlGuard)
async disconnect() {
return { message: 'Disconnect successful' };
}
}

View File

@ -1,21 +1,32 @@
import { Module } from '@nestjs/common';
import { LoginModule } from '@/src/auth/login/login.module';
import { SelectModule } from '@/src/auth/select/select.module';
import { PasswordService } from '@/src/auth/password/password.service';
import { PasswordModule } from '@/src/auth/password/password.module';
import { LogoutModule } from '@/src/auth/logout/logout.module';
import { DisconnectModule } from '@/src/auth/disconnect/disconnect.module';
import { TokenModule } from '@/src/auth/token/token.module';
import { AuthController } from '@/src/auth/auth.controller';
import { LoginService } from '@/src/auth/login/login.service';
import { LogoutService } from '@/src/auth/logout/logout.service';
import { AuthService } from '@/src/auth/auth.service';
import { SelectService } from '@/src/auth/select/select.service';
import { UtilsModule } from '@/src/utils/utils.module';
import { PrismaService } from '../prisma.service';
import { CreatePasswordService } from './password/create/create.service';
import { ResetPasswordService } from './password/reset/reset.service';
import { ChangePasswordService } from './password/change/change.service';
import { VerifyOtpService } from './password/verify-otp/verify-otp.service';
import { DisconnectService } from './disconnect/disconnect.service';
@Module({
imports: [
LoginModule,
LogoutModule,
SelectModule,
PasswordModule,
DisconnectModule,
TokenModule,
imports: [UtilsModule],
controllers: [AuthController],
providers: [
AuthService,
LoginService,
LogoutService,
SelectService,
CreatePasswordService,
ResetPasswordService,
ChangePasswordService,
VerifyOtpService,
DisconnectService,
PrismaService,
],
providers: [PasswordService],
exports: [AuthService],
})
export class AuthModule {}

View File

@ -0,0 +1,57 @@
import { Injectable } from '@nestjs/common';
import { LoginService } from './login/login.service';
import { LogoutService } from './logout/logout.service';
import { SelectService } from './select/select.service';
import { userLoginValidator } from '@/src/auth/login/dtoValidator';
import { userSelectValidator } from './select/dtoValidator';
import { userLogoutValidator } from './logout/dtoValidator';
import { CreatePasswordService } from './password/create/create.service';
import { ResetPasswordService } from './password/reset/reset.service';
import { ChangePasswordService } from './password/change/change.service';
import { VerifyOtpService } from './password/verify-otp/verify-otp.service';
import { DisconnectService } from './disconnect/disconnect.service';
@Injectable()
export class AuthService {
constructor(
private loginService: LoginService,
private logoutService: LogoutService,
private selectService: SelectService,
private createPasswordService: CreatePasswordService,
private changePasswordService: ChangePasswordService,
private resetPasswordService: ResetPasswordService,
private verifyOtpService: VerifyOtpService,
private disconnectService: DisconnectService,
) {}
async login(dto: userLoginValidator) {
return await this.loginService.run(dto);
}
async logout(dto: userLogoutValidator) {
return await this.logoutService.run(dto);
}
async select(dto: userSelectValidator) {
return await this.selectService.run(dto);
}
async createPassword(dto: any) {
return await this.createPasswordService.run(dto);
}
async changePassword(dto: any) {
return await this.changePasswordService.run(dto);
}
async resetPassword(dto: any) {
return await this.resetPasswordService.run(dto);
}
async verifyOtp(dto: any) {
return await this.verifyOtpService.run(dto);
}
async disconnect() {
return await this.disconnectService.run();
}
}

View File

@ -1,18 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { DisconnectController } from './disconnect.controller';
describe('DisconnectController', () => {
let controller: DisconnectController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [DisconnectController],
}).compile();
controller = module.get<DisconnectController>(DisconnectController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@ -1,4 +0,0 @@
import { Controller } from '@nestjs/common';
@Controller('disconnect')
export class DisconnectController {}

View File

@ -1,9 +0,0 @@
import { Module } from '@nestjs/common';
import { DisconnectService } from './disconnect.service';
import { DisconnectController } from './disconnect.controller';
@Module({
providers: [DisconnectService],
controllers: [DisconnectController]
})
export class DisconnectModule {}

View File

@ -1,4 +1,8 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class DisconnectService {}
export class DisconnectService {
async run() {
return { message: 'Disconnect successful' };
}
}

View File

@ -0,0 +1,13 @@
import { IsObject, IsOptional, IsString, IsBoolean } from 'class-validator';
export class userLoginValidator {
@IsString()
accessKey: string;
@IsString()
password: string;
@IsBoolean()
@IsOptional()
rememberMe?: boolean;
}

View File

@ -1,18 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { LoginController } from './login.controller';
describe('LoginController', () => {
let controller: LoginController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [LoginController],
}).compile();
controller = module.get<LoginController>(LoginController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@ -1,4 +0,0 @@
import { Controller } from '@nestjs/common';
@Controller('login')
export class LoginController {}

View File

@ -1,9 +0,0 @@
import { Module } from '@nestjs/common';
import { LoginController } from './login.controller';
import { LoginService } from './login.service';
@Module({
controllers: [LoginController],
providers: [LoginService]
})
export class LoginModule {}

View File

@ -1,4 +1,57 @@
import { Injectable } from '@nestjs/common';
import { userLoginValidator } from '@/src/auth/login/dtoValidator';
import { RedisHandlers } from '@/src/utils/auth/redis_handlers';
import { PasswordHandlers } from '@/src/utils/auth/login_handler';
import { PrismaService } from '@/src/prisma.service';
import { AuthTokenSchema } from '@/src/types/auth/token';
@Injectable()
export class LoginService {}
export class LoginService {
constructor(
private readonly redis: RedisHandlers,
private readonly passHandlers: PasswordHandlers,
private readonly prisma: PrismaService,
) {}
async run(dto: userLoginValidator) {
const foundUser = await this.prisma.users.findFirstOrThrow({
where: { email: dto.accessKey },
});
// if (foundUser.password_token) {
// throw new Error('Password need to be set first');
// }
const isPasswordValid = this.passHandlers.check_password(
foundUser.uu_id,
dto.password,
foundUser.hash_password,
);
// if (!isPasswordValid) {
// throw new Error('Invalid password');
// }
const foundPerson = await this.prisma.people.findFirstOrThrow({
where: { id: foundUser.id },
});
const redisData = AuthTokenSchema.parse({
people: foundPerson,
users: foundUser,
credentials: {
person_id: foundPerson.id,
person_name: foundPerson.firstname,
},
});
const accessToken = await this.redis.setLoginToRedis(
redisData,
foundUser.uu_id,
);
return {
accessToken,
message: 'Login successful',
};
}
}

View File

@ -0,0 +1,6 @@
import { IsString } from 'class-validator';
export class userLogoutValidator {
@IsString()
selected_uu_id: string;
}

View File

@ -1,18 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { LogoutController } from './logout.controller';
describe('LogoutController', () => {
let controller: LogoutController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [LogoutController],
}).compile();
controller = module.get<LogoutController>(LogoutController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@ -1,4 +0,0 @@
import { Controller } from '@nestjs/common';
@Controller('logout')
export class LogoutController {}

View File

@ -1,9 +0,0 @@
import { Module } from '@nestjs/common';
import { LogoutService } from './logout.service';
import { LogoutController } from './logout.controller';
@Module({
providers: [LogoutService],
controllers: [LogoutController]
})
export class LogoutModule {}

View File

@ -1,4 +1,9 @@
import { Injectable } from '@nestjs/common';
import { userLogoutValidator } from '@/src/auth/logout/dtoValidator';
@Injectable()
export class LogoutService {}
export class LogoutService {
async run(dto: userLogoutValidator) {
return dto;
}
}

View File

@ -1,18 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ChangeController } from './change.controller';
describe('ChangeController', () => {
let controller: ChangeController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [ChangeController],
}).compile();
controller = module.get<ChangeController>(ChangeController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@ -1,4 +0,0 @@
import { Controller } from '@nestjs/common';
@Controller('change')
export class ChangeController {}

View File

@ -1,9 +0,0 @@
import { Module } from '@nestjs/common';
import { ChangeService } from './change.service';
import { ChangeController } from './change.controller';
@Module({
providers: [ChangeService],
controllers: [ChangeController]
})
export class ChangeModule {}

View File

@ -1,4 +1,8 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class ChangeService {}
export class ChangePasswordService {
async run(dto: any) {
return dto;
}
}

View File

@ -1,18 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { CreateController } from './create.controller';
describe('CreateController', () => {
let controller: CreateController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [CreateController],
}).compile();
controller = module.get<CreateController>(CreateController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@ -1,4 +0,0 @@
import { Controller } from '@nestjs/common';
@Controller('create')
export class CreateController {}

View File

@ -1,9 +0,0 @@
import { Module } from '@nestjs/common';
import { CreateService } from './create.service';
import { CreateController } from './create.controller';
@Module({
providers: [CreateService],
controllers: [CreateController]
})
export class CreateModule {}

View File

@ -1,4 +1,8 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class CreateService {}
export class CreatePasswordService {
async run(dto: any) {
return { message: 'Password created successfully' };
}
}

View File

@ -1,18 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { PasswordController } from './password.controller';
describe('PasswordController', () => {
let controller: PasswordController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [PasswordController],
}).compile();
controller = module.get<PasswordController>(PasswordController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@ -1,4 +0,0 @@
import { Controller } from '@nestjs/common';
@Controller('password')
export class PasswordController {}

View File

@ -1,12 +0,0 @@
import { Module } from '@nestjs/common';
import { PasswordController } from './password.controller';
import { CreateModule } from './create/create.module';
import { ChangeModule } from './change/change.module';
import { ResetModule } from './reset/reset.module';
import { VerifyOtpModule } from './verify-otp/verify-otp.module';
@Module({
controllers: [PasswordController],
imports: [CreateModule, ChangeModule, ResetModule, VerifyOtpModule]
})
export class PasswordModule {}

View File

@ -1,18 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { PasswordService } from './password.service';
describe('PasswordService', () => {
let service: PasswordService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [PasswordService],
}).compile();
service = module.get<PasswordService>(PasswordService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View File

@ -1,4 +0,0 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class PasswordService {}

View File

@ -1,18 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ResetController } from './reset.controller';
describe('ResetController', () => {
let controller: ResetController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [ResetController],
}).compile();
controller = module.get<ResetController>(ResetController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@ -1,4 +0,0 @@
import { Controller } from '@nestjs/common';
@Controller('reset')
export class ResetController {}

View File

@ -1,9 +0,0 @@
import { Module } from '@nestjs/common';
import { ResetService } from './reset.service';
import { ResetController } from './reset.controller';
@Module({
providers: [ResetService],
controllers: [ResetController]
})
export class ResetModule {}

View File

@ -1,4 +1,8 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class ResetService {}
export class ResetPasswordService {
async run(dto: any) {
return dto;
}
}

View File

@ -1,18 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { VerifyOtpController } from './verify-otp.controller';
describe('VerifyOtpController', () => {
let controller: VerifyOtpController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [VerifyOtpController],
}).compile();
controller = module.get<VerifyOtpController>(VerifyOtpController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@ -1,4 +0,0 @@
import { Controller } from '@nestjs/common';
@Controller('verify-otp')
export class VerifyOtpController {}

View File

@ -1,9 +0,0 @@
import { Module } from '@nestjs/common';
import { VerifyOtpService } from './verify-otp.service';
import { VerifyOtpController } from './verify-otp.controller';
@Module({
providers: [VerifyOtpService],
controllers: [VerifyOtpController]
})
export class VerifyOtpModule {}

View File

@ -1,4 +1,8 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class VerifyOtpService {}
export class VerifyOtpService {
async run(dto: any) {
return dto;
}
}

View File

@ -0,0 +1,6 @@
import { IsString } from 'class-validator';
export class userSelectValidator {
@IsString()
selected_uu_id: string;
}

View File

@ -1,18 +0,0 @@
import { Test, TestingModule } from '@nestjs/testing';
import { SelectController } from './select.controller';
describe('SelectController', () => {
let controller: SelectController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [SelectController],
}).compile();
controller = module.get<SelectController>(SelectController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@ -1,4 +0,0 @@
import { Controller } from '@nestjs/common';
@Controller('select')
export class SelectController {}

View File

@ -1,9 +0,0 @@
import { Module } from '@nestjs/common';
import { SelectController } from './select.controller';
import { SelectService } from './select.service';
@Module({
controllers: [SelectController],
providers: [SelectService]
})
export class SelectModule {}

View File

@ -1,4 +1,9 @@
import { Injectable } from '@nestjs/common';
import { userSelectValidator } from '@/src/auth/select/dtoValidator';
@Injectable()
export class SelectService {}
export class SelectService {
async run(dto: userSelectValidator) {
return dto;
}
}

View File

@ -1,8 +1,13 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { extractAndPersistRoutes } from '@/src/utils/extract-routes';
import { PrismaService } from './prisma.service';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(process.env.PORT ?? 3000);
console.log(`🚀 Uygulama çalışıyor: ${await app.getUrl()}`);
extractAndPersistRoutes(app, app.get(PrismaService));
}
bootstrap();

View File

@ -0,0 +1,66 @@
import {
CanActivate,
ExecutionContext,
Injectable,
ForbiddenException,
} from '@nestjs/common';
import { RedisHandlers } from '@/src/utils/auth/redis_handlers';
const getAccessTokenFromHeader = (req: Request): string => {
console.log(req.headers);
const token = req.headers['acs'];
if (!token) {
throw new ForbiddenException('Access token header is missing');
}
return token;
};
const getSelectTokenFromHeader = (req: Request): string => {
const token = req.headers['slc'];
if (!token) {
throw new ForbiddenException('Select token header is missing');
}
return token;
};
@Injectable()
export class AuthControlGuard implements CanActivate {
constructor(private cacheService: RedisHandlers) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const req = context.switchToHttp().getRequest();
const accessToken = getAccessTokenFromHeader(req);
console.log('AuthControlGuard', accessToken);
// const hasAccess = accessObject.permissions?.some(
// (p: any) => p.method === method && p.url === path,
// );
// if (!hasAccess) {
// throw new ForbiddenException('Access denied to this route');
// }
return true;
}
}
@Injectable()
export class EndpointControlGuard implements CanActivate {
constructor(private cacheService: RedisHandlers) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const req = context.switchToHttp().getRequest();
const selectToken = getSelectTokenFromHeader(req);
const method = req.method;
const path = req.route?.path;
console.log('EndpointControlGuard', selectToken, method, path);
// const hasAccess = accessObject.permissions?.some(
// (p: any) => p.method === method && p.url === path,
// );
// if (!hasAccess) {
// throw new ForbiddenException('Access denied to this route');
// }
return true;
}
}

View File

@ -0,0 +1,90 @@
// Token type redis base TOKEN object
enum UserType {
employee = 1,
occupant = 2,
}
interface Credentials {
person_id: number;
person_name: string;
}
interface ApplicationToken {
// Application Token Object -> is the main object for the user
user_type: number;
credential_token: string;
user_uu_id: string;
user_id: number;
person_id: number;
person_uu_id: string;
request?: Record<string, any>; // Request Info of Client
expires_at?: number; // Expiry timestamp
reachable_event_codes?: Record<string, any>; // ID list of reachable event codes as "endpoint_code": ["UUID", "UUID"]
reachable_app_codes?: Record<string, any>; // ID list of reachable applications as "page_url": ["UUID", "UUID"]
}
interface OccupantToken {
// Selection of the occupant type for a build part is made by the user
living_space_id: number; // Internal use
living_space_uu_id: string; // Outer use
occupant_type_id: number;
occupant_type_uu_id: string;
occupant_type: string;
build_id: number;
build_uuid: string;
build_part_id: number;
build_part_uuid: string;
responsible_company_id?: number;
responsible_company_uuid?: string;
responsible_employee_id?: number;
responsible_employee_uuid?: string;
}
interface CompanyToken {
// Selection of the company for an employee is made by the user
company_id: number;
company_uu_id: string;
department_id: number; // ID list of departments
department_uu_id: string; // UUID list of departments
duty_id: number;
duty_uu_id: string;
staff_id: number;
staff_uu_id: string;
employee_id: number;
employee_uu_id: string;
bulk_duties_id: number;
}
interface OccupantTokenObject extends ApplicationToken {
// Occupant Token Object -> Requires selection of the occupant type for a specific build part
available_occupants: Record<string, any> | null;
selected?: Record<string, any>; // Selected Occupant Type
is_employee: boolean; // Always false
is_occupant: boolean; // Always true
}
interface EmployeeTokenObject extends ApplicationToken {
// Full hierarchy Employee[staff_id] -> Staff -> Duty -> Department -> Company
companies_id_list: number[]; // List of company objects
companies_uu_id_list: string[]; // UUID list of company objects
duty_id_list: number[]; // List of duty objects
duty_uu_id_list: string[]; // UUID list of duty objects
selected?: Record<string, any>; // Selected Company Object
is_employee: boolean; // Always true
is_occupant: boolean; // Always false
}
// Union type for token objects
type TokenDictType = EmployeeTokenObject | OccupantTokenObject;
export {
UserType,
Credentials,
ApplicationToken,
OccupantToken,
CompanyToken,
OccupantTokenObject,
EmployeeTokenObject,
TokenDictType,
};

View File

@ -1,90 +1,122 @@
// Token type redis base TOKEN object
import { z } from 'zod';
enum UserType {
employee = 1,
occupant = 2,
}
// ENUM
export const UserType = {
employee: 1,
occupant: 2,
} as const;
export type UserType = (typeof UserType)[keyof typeof UserType];
interface Credentials {
person_id: number;
person_name: string;
}
// Credentials
export const CredentialsSchema = z.object({
person_id: z.number(),
person_name: z.string(),
});
export type Credentials = z.infer<typeof CredentialsSchema>;
interface ApplicationToken {
// Application Token Object -> is the main object for the user
user_type: number;
credential_token: string;
user_uu_id: string;
user_id: number;
person_id: number;
person_uu_id: string;
request?: Record<string, any>; // Request Info of Client
expires_at?: number; // Expiry timestamp
reachable_event_codes?: Record<string, any>; // ID list of reachable event codes as "endpoint_code": ["UUID", "UUID"]
reachable_app_codes?: Record<string, any>; // ID list of reachable applications as "page_url": ["UUID", "UUID"]
}
export const AuthTokenSchema = z.object({
people: z.object({
firstname: z.string(),
surname: z.string(),
middle_name: z.string(),
birthname: z.string().nullable(),
sex_code: z.string(),
person_ref: z.string().nullable(),
person_tag: z.string(),
father_name: z.string(),
mother_name: z.string(),
country_code: z.string(),
national_identity_id: z.string(),
birth_place: z.string(),
birth_date: z.date(),
tax_no: z.string(),
ref_id: z.string().nullable(),
replication_id: z.number().nullable(),
cryp_uu_id: z.string().nullable(),
created_credentials_token: z.string().nullable(),
updated_credentials_token: z.string().nullable(),
confirmed_credentials_token: z.string().nullable(),
ref_int: z.number().nullable(),
is_confirmed: z.boolean(),
deleted: z.boolean(),
active: z.boolean(),
is_notification_send: z.boolean(),
is_email_send: z.boolean(),
id: z.number(),
uu_id: z.string(),
expiry_starts: z.date(),
expiry_ends: z.date(),
created_at: z.date(),
updated_at: z.date(),
}),
users: z.object({
user_tag: z.string(),
email: z.string(),
phone_number: z.string(),
via: z.string(),
avatar: z.string(),
hash_password: z.string(),
password_token: z.string(),
remember_me: z.boolean(),
password_expires_day: z.number(),
password_expiry_begins: z.date(),
related_company: z.string(),
person_id: z.number(),
person_uu_id: z.string(),
local_timezone: z.string(),
ref_id: z.string().nullable(),
ref_int: z.number().nullable(),
replication_id: z.number().nullable(),
cryp_uu_id: z.string().nullable(),
created_credentials_token: z.string().nullable(),
updated_credentials_token: z.string().nullable(),
confirmed_credentials_token: z.string().nullable(),
is_confirmed: z.boolean(),
deleted: z.boolean(),
active: z.boolean(),
is_notification_send: z.boolean(),
is_email_send: z.boolean(),
id: z.number(),
uu_id: z.string(),
expiry_starts: z.date(),
expiry_ends: z.date(),
created_at: z.date(),
updated_at: z.date(),
default_language: z.string(),
}),
credentials: CredentialsSchema,
});
interface OccupantToken {
// Selection of the occupant type for a build part is made by the user
living_space_id: number; // Internal use
living_space_uu_id: string; // Outer use
occupant_type_id: number;
occupant_type_uu_id: string;
occupant_type: string;
build_id: number;
build_uuid: string;
build_part_id: number;
build_part_uuid: string;
responsible_company_id?: number;
responsible_company_uuid?: string;
responsible_employee_id?: number;
responsible_employee_uuid?: string;
}
export type AuthToken = z.infer<typeof AuthTokenSchema>;
interface CompanyToken {
// Selection of the company for an employee is made by the user
company_id: number;
company_uu_id: string;
department_id: number; // ID list of departments
department_uu_id: string; // UUID list of departments
duty_id: number;
duty_uu_id: string;
staff_id: number;
staff_uu_id: string;
employee_id: number;
employee_uu_id: string;
bulk_duties_id: number;
}
export const EmployeeTokenSchema = z.object({
functionsRetriever: z.string(),
companies: z.object({}),
department: z.object({}),
duties: z.object({}),
employee: z.object({}),
staffs: z.object({}),
reachable_event_codes: z.array(z.object({})),
reachable_app_codes: z.array(z.object({})),
kind: z.literal(UserType.employee),
});
interface OccupantTokenObject extends ApplicationToken {
// Occupant Token Object -> Requires selection of the occupant type for a specific build part
available_occupants: Record<string, any> | null;
selected?: Record<string, any>; // Selected Occupant Type
is_employee: boolean; // Always false
is_occupant: boolean; // Always true
}
export const OccupantTokenSchema = z.object({
functionsRetriever: z.string(),
livingSpace: z.object({}),
occupantType: z.object({}),
build: z.object({}),
buildPart: z.object({}),
responsibleCompany: z.object({}).optional(),
responsibleEmployee: z.object({}).optional(),
kind: z.literal(UserType.occupant),
reachable_event_codes: z.array(z.object({})),
reachable_app_codes: z.array(z.object({})),
});
interface EmployeeTokenObject extends ApplicationToken {
// Full hierarchy Employee[staff_id] -> Staff -> Duty -> Department -> Company
companies_id_list: number[]; // List of company objects
companies_uu_id_list: string[]; // UUID list of company objects
duty_id_list: number[]; // List of duty objects
duty_uu_id_list: string[]; // UUID list of duty objects
selected?: Record<string, any>; // Selected Company Object
is_employee: boolean; // Always true
is_occupant: boolean; // Always false
}
export const TokenDictTypes = z.discriminatedUnion('kind', [
EmployeeTokenSchema,
OccupantTokenSchema,
]);
// Union type for token objects
type TokenDictType = EmployeeTokenObject | OccupantTokenObject;
export {
UserType,
Credentials,
ApplicationToken,
OccupantToken,
CompanyToken,
OccupantTokenObject,
EmployeeTokenObject,
TokenDictType,
};
export type TokenDictInterface = z.infer<typeof TokenDictTypes>;

View File

@ -10,6 +10,13 @@ import {
} from '@nestjs/common';
import { UsersService } from './users.service';
/**
* USER TYPE CODE = BM BLD OCC ...
* class Func
* code = "uuid4"
* TYPE = "build_manager"
*/
@Controller('users')
export class UsersController {
constructor(private usersService: UsersService) {}

View File

@ -1,4 +1,4 @@
import crypto from 'crypto';
import * as crypto from 'crypto';
import { v4 as uuidv4 } from 'uuid';
interface TokenConfig {
@ -12,28 +12,29 @@ const tokenConfig: TokenConfig = {
};
class PasswordHandlers {
generate_random_uu_id(is_string: boolean = true): string {
generateRandomUUID(is_string: boolean = true): string {
return is_string ? uuidv4().toString() : uuidv4();
}
create_hashed_password(
domain: string,
uuid: string,
password: string,
): string {
const data = `${domain}:${uuid}:${password}`;
create_hashed_password(uuid: string, password: string): string {
const data = `${uuid}:${password}`;
console.log(crypto.createHash('sha256').update(data).digest('hex'));
return crypto.createHash('sha256').update(data).digest('hex');
}
createSelectToken(accessToken: string, userUUID: string) {
const data = `${accessToken}:${userUUID}`;
return crypto.createHash('sha256').update(data).digest('hex');
}
check_password(
domain: string,
uuid: string,
password: string,
hashed_password: string,
): boolean {
return (
this.create_hashed_password(domain, uuid, password) === hashed_password
);
const created_hashed_password = this.create_hashed_password(uuid, password);
console.log('created_hashed_password', created_hashed_password);
return created_hashed_password === hashed_password;
}
generateAccessToken(): string {

View File

@ -1,7 +1,7 @@
import {
TokenDictType,
OccupantTokenObject,
EmployeeTokenObject,
TokenDictTypes,
TokenDictInterface,
AuthToken,
UserType,
} from '@/src/types/auth/token';
import { CacheService } from '@/src/cache.service';
@ -15,110 +15,50 @@ export class RedisHandlers {
constructor(
private readonly cacheService: CacheService,
private readonly passwordService: PasswordHandlers,
) {
this.cacheService = cacheService;
this.passwordService = passwordService;
) {}
generateSelectToken(accessToken: string, userUUID: string) {
return this.passwordService.createSelectToken(accessToken, userUUID);
}
async process_redis_object(redis_object: any): Promise<TokenDictType> {
if (!redis_object) {
throw new Error('Invalid Redis object: Object is null or undefined');
}
if (redis_object.user_type === UserType.employee) {
const validateEmployeeToken = (obj: any): obj is EmployeeTokenObject => {
return (
typeof obj === 'object' &&
obj !== null &&
typeof obj.user_type === 'number' &&
typeof obj.user_uu_id === 'string' &&
typeof obj.user_id === 'number' &&
typeof obj.person_id === 'number' &&
typeof obj.person_uu_id === 'string' &&
Array.isArray(obj.companies_id_list) &&
Array.isArray(obj.companies_uu_id_list) &&
Array.isArray(obj.duty_id_list) &&
Array.isArray(obj.duty_uu_id_list)
);
};
const empToken: EmployeeTokenObject = {
...redis_object,
is_employee: true,
is_occupant: false,
user_type: UserType.employee,
credential_token: redis_object.credential_token || '',
};
if (!validateEmployeeToken(empToken)) {
throw new Error(
'Invalid Redis object: Does not match EmployeeTokenObject interface',
);
}
return empToken;
}
if (redis_object.user_type === UserType.occupant) {
const validateOccupantToken = (obj: any): obj is OccupantTokenObject => {
return (
typeof obj === 'object' &&
obj !== null &&
typeof obj.user_type === 'number' &&
typeof obj.user_uu_id === 'string' &&
typeof obj.user_id === 'number' &&
typeof obj.person_id === 'number' &&
typeof obj.person_uu_id === 'string'
);
};
const occToken: OccupantTokenObject = {
...redis_object,
is_employee: false,
is_occupant: true,
user_type: UserType.occupant,
credential_token: redis_object.credential_token || '',
available_occupants: redis_object.available_occupants || null,
};
if (!validateOccupantToken(occToken)) {
throw new Error(
'Invalid Redis object: Does not match OccupantTokenObject interface',
);
}
return occToken;
}
throw new Error(`Invalid user_type: ${redis_object.user_type}`);
generateAccessToken() {
return this.passwordService.generateAccessToken();
}
async get_object_from_redis(access_token: string): Promise<TokenDictType> {
const token = await this.cacheService.get(access_token);
return this.process_redis_object(token);
async getLoginFromRedis(redisKey: string): Promise<AuthToken> {
return this.cacheService.get(redisKey);
}
async set_login_to_redis(user: users, token: TokenDictType): Promise<any> {
const generated_token = this.passwordService.generateAccessToken();
const listKeys = [this.AUTH_TOKEN, generated_token, user.uu_id];
await this.cacheService.set_with_ttl(
this.cacheService.createRegexPattern(listKeys),
token,
60 * 60 * 24,
);
return generated_token;
async getSelectFromRedis(redisKey: string): Promise<TokenDictInterface> {
return this.cacheService.get(redisKey);
}
async update_token_via_token(token: string, additional: any): Promise<any> {
const listKeys = [this.AUTH_TOKEN, token, '*'];
const accessObject = await this.cacheService.get_with_keys(listKeys);
if (!accessObject) throw new Error('Token not found');
const processedObject: TokenDictType =
await this.process_redis_object(accessObject);
if (processedObject.is_employee) {
processedObject.selected = additional;
}
if (processedObject.is_occupant) {
processedObject.selected = additional;
}
const listKeysNew = [this.AUTH_TOKEN, token, processedObject.user_uu_id];
await this.cacheService.set_with_ttl(
this.cacheService.createRegexPattern(listKeysNew),
processedObject,
60 * 60 * 24,
);
return token;
async renewTtlLoginFromRedis(redisKey: string): Promise<any> {
const token = await this.getLoginFromRedis(redisKey);
return this.cacheService.set_with_ttl(redisKey, token, 60 * 30);
}
async renewTtlSelectFromRedis(redisKey: string): Promise<any> {
const token = await this.getSelectFromRedis(redisKey);
return this.cacheService.set_with_ttl(redisKey, token, 60 * 30);
}
async setLoginToRedis(token: AuthToken, userUUID: string): Promise<any> {
const accessToken = this.generateAccessToken();
const redisKey = `${this.AUTH_TOKEN}:${accessToken}:${accessToken}:${userUUID}:${userUUID}`;
await this.cacheService.set_with_ttl(redisKey, token, 60 * 30);
return accessToken;
}
async setSelectToRedis(
accessToken: string,
token: TokenDictInterface,
userUUID: string,
livingUUID: string,
): Promise<any> {
const selectToken = this.generateSelectToken(accessToken, userUUID);
const redisKey = `${this.AUTH_TOKEN}:${accessToken}:${selectToken}:${userUUID}:${livingUUID}`;
await this.cacheService.set_with_ttl(redisKey, token, 60 * 30);
return selectToken;
}
}

View File

@ -0,0 +1,95 @@
import { INestApplication, RequestMethod } from '@nestjs/common';
import { ModulesContainer, Reflector } from '@nestjs/core';
import { PATH_METADATA, METHOD_METADATA } from '@nestjs/common/constants';
import { PrismaService } from '@/src/prisma.service';
/**
* Helper: Method string'i döndür
*/
function getMethodString(requestMethod: RequestMethod): string {
return RequestMethod[requestMethod];
}
/**
* Helper: Path'leri normalize et (iki tane slash varsa düzelt)
*/
function normalizePath(...paths: string[]): string {
const normalized =
'/' +
paths
.filter(Boolean)
.map((p) => p.replace(/^\/|\/$/g, ''))
.filter((p) => p.length > 0)
.join('/');
return normalized === '/' ? '' : normalized; // Home route'ı dışla
}
export async function extractAndPersistRoutes(
app: INestApplication,
prisma: PrismaService,
): Promise<{ method: string; url: string }[]> {
const modulesContainer = app.get(ModulesContainer);
const reflector = app.get(Reflector);
const routes: { method: string; url: string }[] = [];
modulesContainer.forEach((moduleRef) => {
const controllers = [...moduleRef.controllers.values()];
controllers.forEach(({ metatype }) => {
if (!metatype || typeof metatype !== 'function') return;
const controllerPath =
reflector.get<string>(PATH_METADATA, metatype) ?? '';
const prototype = metatype.prototype;
const methodNames = Object.getOwnPropertyNames(prototype).filter(
(m) => m !== 'constructor',
);
methodNames.forEach((methodName) => {
const methodRef = prototype[methodName];
const routePath = reflector.get<string>(PATH_METADATA, methodRef);
const requestMethod = reflector.get<RequestMethod>(
METHOD_METADATA,
methodRef,
);
if (routePath !== undefined && requestMethod !== undefined) {
const method = getMethodString(requestMethod);
const fullPath = normalizePath(controllerPath, routePath);
if (fullPath !== '') {
routes.push({ method, url: fullPath });
}
}
});
});
});
const existing = await prisma.endpoint_restriction.findMany({
select: { endpoint_name: true, endpoint_method: true },
});
const existingSet = new Set(
existing.map((r) => `${r.endpoint_method}_${r.endpoint_name}`),
);
const newOnes = routes.filter(
(r) => !existingSet.has(`${r.method}_${r.url}`),
);
// İsteğe bağlı: veritabanına kaydet
// for (const route of newOnes) {
// await prisma.endpoint_restriction.create({
// data: {
// endpoint_method: route.method,
// endpoint_name: route.url,
// is_active: true,
// },
// });
// }
console.log('🧭 Route JSON Listesi:');
console.dir(routes, { depth: null });
return routes;
}

View File

@ -1,9 +1,18 @@
import { Module } from '@nestjs/common';
import { PaginationHelper } from './pagination-helper';
import { PrismaService } from '@/src/prisma.service';
import { RedisHandlers } from './auth/redis_handlers';
import { PasswordHandlers } from './auth/login_handler';
import { CacheService } from '@/src/cache.service';
@Module({
providers: [PaginationHelper, PrismaService],
exports: [PaginationHelper],
providers: [
PaginationHelper,
PrismaService,
RedisHandlers,
PasswordHandlers,
CacheService,
],
exports: [PaginationHelper, RedisHandlers, PasswordHandlers, CacheService],
})
export class UtilsModule {}