updated login page with qwen

This commit is contained in:
2025-07-29 23:06:15 +03:00
parent ccb5c172ae
commit 0ce522d04a
35 changed files with 1708 additions and 80 deletions

View File

@@ -31,8 +31,8 @@ export class AuthController {
@Post('select')
@HttpCode(200)
@UseGuards(AuthControlGuard)
async select(@Body() query: userSelectValidator) {
return { message: 'Logout successful' };
async select(@Body() query: userSelectValidator, @Req() req: Request) {
return await this.authService.select(query, req);
}
@Post('/password/create')

View File

@@ -32,8 +32,8 @@ export class AuthService {
return await this.logoutService.run(dto);
}
async select(dto: userSelectValidator) {
return await this.selectService.run(dto);
async select(dto: userSelectValidator, req: Request) {
return await this.selectService.run(dto, req);
}
async createPassword(dto: any) {

View File

@@ -18,9 +18,9 @@ export class LoginService {
where: { email: dto.accessKey },
});
// if (foundUser.password_token) {
// throw new Error('Password need to be set first');
// }
if (foundUser.password_token) {
throw new Error('Password need to be set first');
}
const isPasswordValid = this.passHandlers.check_password(
foundUser.uu_id,
@@ -28,14 +28,13 @@ export class LoginService {
foundUser.hash_password,
);
// if (!isPasswordValid) {
// throw new Error('Invalid 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,
@@ -43,8 +42,8 @@ export class LoginService {
person_id: foundPerson.id,
person_name: foundPerson.firstname,
},
selectionList: [],
});
const accessToken = await this.redis.setLoginToRedis(
redisData,
foundUser.uu_id,

View File

@@ -29,5 +29,4 @@ export class VerifyOtpService {
// // controller veya resolver içinden
// const { secret, otpauthUrl } = authService.generate2FASecret('mehmet');
// const qrCodeImage = await authService.generateQRCode(otpauthUrl);
// // qrCodeImage → frontende gönder, <img src="data:image/png;base64,..."> diye gösterilebilir

View File

@@ -1,9 +1,124 @@
import { Injectable } from '@nestjs/common';
import {
Injectable,
BadRequestException,
UnauthorizedException,
NotAcceptableException,
} from '@nestjs/common';
import { userSelectValidator } from '@/src/auth/select/dtoValidator';
import { RedisHandlers } from '@/src/utils/auth/redis_handlers';
import {
EmployeeTokenSchema,
OccupantTokenSchema,
TokenDictInterface,
UserType,
} from '@/src/types/auth/token';
import { PrismaService } from '@/src/prisma.service';
@Injectable()
export class SelectService {
async run(dto: userSelectValidator) {
return dto;
constructor(
private readonly redis: RedisHandlers,
private readonly prisma: PrismaService,
) {}
async run(dto: userSelectValidator, req: Request) {
const accessObject = await this.redis.getLoginFromRedis(req);
if (!accessObject) {
throw new UnauthorizedException(
'Authorization failed. Please login to continue',
);
}
const accessToken = accessObject.key.split(':')[1];
console.log('accessToken', accessToken);
const userType = accessObject.value.users.user_type;
if (userType === 'employee') {
const employee = await this.prisma.employees.findFirstOrThrow({
where: { uu_id: dto.selected_uu_id },
});
const staff = await this.prisma.staff.findFirstOrThrow({
where: { id: employee.staff_id },
});
const duties = await this.prisma.duties.findFirstOrThrow({
where: { id: staff.duties_id },
});
const department = await this.prisma.departments.findFirstOrThrow({
where: { id: duties.department_id },
});
const duty = await this.prisma.duty.findFirstOrThrow({
where: { id: duties.duties_id },
});
const company = await this.prisma.companies.findFirstOrThrow({
where: { id: duties.company_id },
});
const employeeToken = EmployeeTokenSchema.parse({
company: company,
department: department,
duty: duty,
employee: employee,
staff: staff,
menu: null,
pages: null,
config: null,
caches: null,
selection: null,
functionsRetriever: staff.function_retriever,
kind: UserType.employee,
});
const tokenSelect = await this.redis.setSelectToRedis(
accessToken,
employeeToken,
accessObject.value.users.uu_id,
dto.selected_uu_id,
);
return {
message: 'Select successful',
token: tokenSelect,
};
} else if (userType === 'occupant') {
const livingSpace = await this.prisma.build_living_space.findFirstOrThrow(
{ where: { uu_id: dto.selected_uu_id } },
);
const occupantType = await this.prisma.occupant_types.findFirstOrThrow({
where: { id: livingSpace.occupant_type_id },
});
const part = await this.prisma.build_parts.findFirstOrThrow({
where: { id: livingSpace.build_parts_id },
});
const build = await this.prisma.build.findFirstOrThrow({
where: { id: part.build_id },
});
const company = await this.prisma.companies.findFirstOrThrow({
where: { uu_id: accessObject.value.users.related_company },
});
const occupantToken = OccupantTokenSchema.parse({
livingSpace: livingSpace,
occupant: occupantType,
build: build,
part: part,
company: company,
menu: null,
pages: null,
config: null,
caches: null,
selection: null,
functionsRetriever: occupantType.function_retriever,
kind: UserType.occupant,
});
const tokenSelect = await this.redis.setSelectToRedis(
accessToken,
occupantToken,
accessObject.value.users.uu_id,
dto.selected_uu_id,
);
return {
message: 'Select successful',
token: tokenSelect,
};
} else {
throw new NotAcceptableException('Invalid user type');
}
}
}

View File

@@ -14,14 +14,6 @@ export class AuthControlGuard implements CanActivate {
const req = context.switchToHttp().getRequest();
const accessToken = this.cacheService.mergeLoginKey(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;
}
}
@@ -32,18 +24,11 @@ export class EndpointControlGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> {
const req = context.switchToHttp().getRequest();
const selectToken = this.cacheService.mergeSelectKey(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');
// }
// const selectToken = this.cacheService.mergeSelectKey(req);
// const method = req.method;
// const path = req.route?.path;
const accessObject = await this.cacheService.getSelectFromRedis(req);
console.log('EndpointControlGuard', accessObject);
return true;
}
}

View File

@@ -26,7 +26,7 @@ export const AuthTokenSchema = z.object({
father_name: z.string(),
mother_name: z.string(),
country_code: z.string(),
national_identity_id: z.string(),
// national_identity_id: z.string(),
birth_place: z.string(),
birth_date: z.date(),
tax_no: z.string(),
@@ -52,10 +52,11 @@ export const AuthTokenSchema = z.object({
users: z.object({
user_tag: z.string(),
email: z.string(),
user_type: z.string(),
phone_number: z.string(),
via: z.string(),
avatar: z.string(),
hash_password: z.string(),
// hash_password: z.string(),
password_token: z.string(),
remember_me: z.boolean(),
password_expires_day: z.number(),
@@ -85,33 +86,173 @@ export const AuthTokenSchema = z.object({
default_language: z.string(),
}),
credentials: CredentialsSchema,
selectionList: z.array(z.any()).optional().default([]),
});
export type AuthToken = z.infer<typeof AuthTokenSchema>;
export const EmployeeTokenSchema = z.object({
company: z.object({
id: z.number(),
uu_id: z.string(),
formal_name: z.string(),
company_type: z.string(),
commercial_type: z.string(),
tax_no: z.string(),
public_name: z.string(),
company_tag: z.string(),
default_lang_type: z.string(),
default_money_type: z.string(),
is_commercial: z.boolean(),
is_blacklist: z.boolean(),
parent_id: z.number().nullable(),
workplace_no: z.string().nullable(),
official_address_id: z.number().nullable(),
official_address_uu_id: z.string().nullable(),
top_responsible_company_id: z.number().nullable(),
top_responsible_company_uu_id: z.string().nullable(),
ref_id: z.string().nullable(),
replication_id: z.number(),
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(),
expiry_starts: z.date(),
expiry_ends: z.date(),
created_at: z.date(),
updated_at: z.date(),
ref_int: z.number().nullable(),
}),
department: z.object({
id: z.number(),
uu_id: z.string(),
parent_department_id: z.number().nullable(),
department_code: z.string(),
department_name: z.string(),
department_description: z.string(),
company_id: z.number(),
company_uu_id: z.string(),
ref_id: z.string().nullable(),
replication_id: z.number(),
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(),
expiry_starts: z.date(),
expiry_ends: z.date(),
created_at: z.date(),
updated_at: z.date(),
ref_int: z.number().nullable(),
}),
duty: z.object({
id: z.number(),
uu_id: z.string(),
duty_name: z.string(),
duty_code: z.string(),
duty_description: z.string(),
ref_id: z.string().nullable(),
replication_id: z.number(),
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(),
expiry_starts: z.date(),
expiry_ends: z.date(),
created_at: z.date(),
updated_at: z.date(),
ref_int: z.number().nullable(),
}),
employee: z.object({
id: z.number(),
uu_id: z.string(),
staff_id: z.number(),
staff_uu_id: z.string(),
people_id: z.number(),
people_uu_id: z.string(),
ref_id: z.string().nullable(),
replication_id: z.number(),
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(),
expiry_starts: z.date(),
expiry_ends: z.date(),
created_at: z.date(),
updated_at: z.date(),
ref_int: z.number().nullable(),
}),
staff: z.object({
id: z.number(),
uu_id: z.string(),
staff_description: z.string(),
staff_name: z.string(),
staff_code: z.string(),
duties_id: z.number(),
duties_uu_id: z.string(),
function_retriever: z.string().nullable(),
ref_id: z.string().nullable(),
replication_id: z.number(),
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(),
expiry_starts: z.date(),
expiry_ends: z.date(),
created_at: z.date(),
updated_at: z.date(),
ref_int: z.number().nullable(),
}),
menu: z.array(z.object({})).nullable(),
pages: z.array(z.string()).nullable(),
// config: z.record(z.string(), z.unknown()).nullable(),
// caches: z.record(z.string(), z.unknown()).nullable(),
selection: z.record(z.string(), z.unknown()).nullable(),
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),
});
export const OccupantTokenSchema = z.object({
functionsRetriever: z.string(),
livingSpace: z.object({}),
occupantType: z.object({}),
occupant: z.object({}),
build: z.object({}),
buildPart: z.object({}),
responsibleCompany: z.object({}).optional(),
responsibleEmployee: z.object({}).optional(),
part: z.object({}),
company: z.object({}).optional(),
menu: z.array(z.object({})).nullable(),
pages: z.array(z.string()).nullable(),
// config: z.record(z.string(), z.unknown()).nullable(),
// caches: z.record(z.string(), z.unknown()).nullable(),
selection: z.record(z.string(), z.unknown()).nullable(),
functionsRetriever: z.string(),
kind: z.literal(UserType.occupant),
reachable_event_codes: z.array(z.object({})),
reachable_app_codes: z.array(z.object({})),
});
export const TokenDictTypes = z.discriminatedUnion('kind', [

View File

@@ -2,10 +2,9 @@ import {
TokenDictTypes,
TokenDictInterface,
AuthToken,
UserType,
AuthTokenSchema,
} from '@/src/types/auth/token';
import { CacheService } from '@/src/cache.service';
import { users } from '@prisma/client';
import { PasswordHandlers } from './login_handler';
import { Injectable, ForbiddenException } from '@nestjs/common';
@@ -22,6 +21,7 @@ interface SelectFromRedis {
@Injectable()
export class RedisHandlers {
AUTH_TOKEN = 'AUTH_TOKEN';
SELECT_TOKEN = 'SELECT_TOKEN';
constructor(
private readonly cacheService: CacheService,
private readonly passwordService: PasswordHandlers,
@@ -32,10 +32,10 @@ export class RedisHandlers {
* Format: AUTH_TOKEN:token:token:UUID or AUTH_TOKEN:token:token:*:*
this.AUTH_TOKEN:token:token:UUID:UUID
*/
private validateRedisKey(redisKey: string): boolean {
if (!redisKey.startsWith(this.AUTH_TOKEN + ':')) {
private validateRedisKey(redisKey: string, type: string): boolean {
if (!redisKey.startsWith(type + ':')) {
throw new ForbiddenException(
`Invalid Redis key format. Must start with ${this.AUTH_TOKEN}:`,
`Invalid Redis key format. Must start with ${type}:`,
);
}
const colonCount = (redisKey.match(/:/g) || []).length;
@@ -53,7 +53,7 @@ export class RedisHandlers {
throw new ForbiddenException('Access token header is missing');
}
const mergedRedisKey = `${this.AUTH_TOKEN}:${acsToken}:${acsToken}:*:*`;
this.validateRedisKey(mergedRedisKey);
this.validateRedisKey(mergedRedisKey, this.AUTH_TOKEN);
return mergedRedisKey;
}
@@ -66,8 +66,8 @@ export class RedisHandlers {
if (!slcToken) {
throw new ForbiddenException('Select token header is missing');
}
const mergedRedisKey = `${this.AUTH_TOKEN}:${acsToken}:${slcToken}:*:*`;
this.validateRedisKey(mergedRedisKey);
const mergedRedisKey = `${this.SELECT_TOKEN}:${acsToken}:${slcToken}:*:*`;
this.validateRedisKey(mergedRedisKey, this.SELECT_TOKEN);
return mergedRedisKey;
}
@@ -79,8 +79,8 @@ export class RedisHandlers {
return this.passwordService.generateAccessToken();
}
private async scanKeys(pattern: string): Promise<string[]> {
this.validateRedisKey(pattern);
private async scanKeys(pattern: string, type: string): Promise<string[]> {
this.validateRedisKey(pattern, type);
const client = (this.cacheService as any).client;
if (!client) throw new Error('Redis client not available');
@@ -103,7 +103,7 @@ export class RedisHandlers {
async getLoginFromRedis(req: Request): Promise<LoginFromRedis | null> {
const mergedKey = this.mergeLoginKey(req);
if (mergedKey.includes('*')) {
const keys = await this.scanKeys(mergedKey);
const keys = await this.scanKeys(mergedKey, this.AUTH_TOKEN);
if (keys.length === 0) {
throw new ForbiddenException('Authorization failed - No matching keys');
}
@@ -122,13 +122,15 @@ export class RedisHandlers {
}
const value = await this.cacheService.get(mergedKey);
return value ? { key: mergedKey, value } : null;
return value
? { key: mergedKey, value: AuthTokenSchema.parse(value) }
: null;
}
async getSelectFromRedis(req: Request): Promise<SelectFromRedis | null> {
const mergedKey = this.mergeSelectKey(req);
if (mergedKey.includes('*')) {
const keys = await this.scanKeys(mergedKey);
const keys = await this.scanKeys(mergedKey, this.SELECT_TOKEN);
if (keys.length === 0) {
throw new ForbiddenException(
@@ -147,7 +149,9 @@ export class RedisHandlers {
}
const value = await this.cacheService.get(mergedKey);
return value ? { key: mergedKey, value } : null;
return value
? { key: mergedKey, value: TokenDictTypes.parse(value) }
: null;
}
async deleteLoginFromRedis(req: Request): Promise<any> {
@@ -186,7 +190,7 @@ export class RedisHandlers {
livingUUID: string,
): Promise<any> {
const selectToken = this.generateSelectToken(accessToken, userUUID);
const redisKey = `${this.AUTH_TOKEN}:${accessToken}:${selectToken}:${userUUID}:${livingUUID}`;
const redisKey = `${this.SELECT_TOKEN}:${accessToken}:${selectToken}:${userUUID}:${livingUUID}`;
await this.cacheService.set_with_ttl(redisKey, token, 60 * 30);
return selectToken;
}