Fixed mismatched userId on field expert for claim

This commit is contained in:
SepehrYahyaee
2026-06-20 11:53:18 +03:30
parent d355771518
commit 5a89a0ff16
26 changed files with 144 additions and 414 deletions

View File

@@ -1,21 +0,0 @@
import { Body, Controller, Post } from "@nestjs/common";
import { Public } from "./decorators";
import { UserLoginDto, UserRegisterDto } from "./dtos";
import { AuthService } from "./auth.service";
@Controller("auth")
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Public()
@Post("user/register")
async register(@Body() userRegisterDto: UserRegisterDto) {
return await this.authService.registerUser(userRegisterDto);
}
@Public()
@Post("user/login")
async login(@Body() userLoginDto: UserLoginDto) {
return await this.authService.loginUser(userLoginDto);
}
}

View File

@@ -1,32 +0,0 @@
import { Module } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { APP_GUARD } from "@nestjs/core";
import { JwtModule } from "@nestjs/jwt";
import { StringValue } from "ms";
import { AuthGuard, RolesGuard } from "./guards";
import { AuthController } from "./auth.controller";
import { AuthService } from "./auth.service";
import { HashService } from "./providers";
@Module({
imports: [
JwtModule.registerAsync({
global: true,
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
secret: configService.get<string>("JWT_SECRET"),
signOptions: {
expiresIn: configService.get<StringValue>("JWT_EXPIRY"),
},
}),
}),
],
controllers: [AuthController],
providers: [
{ provide: APP_GUARD, useClass: AuthGuard },
{ provide: APP_GUARD, useClass: RolesGuard },
AuthService,
HashService,
],
})
export class AuthModule {}

View File

@@ -1,31 +0,0 @@
import { BadRequestException, Injectable } from "@nestjs/common";
import { UserRepository } from "src/features/user/repositories";
import { UserLoginDto, UserRegisterDto } from "./dtos";
@Injectable()
export class AuthService {
constructor(private readonly userRepo: UserRepository) {}
async registerUser(userRegisterDto: UserRegisterDto) {
const user = await this.userRepo.retrieveByCellPhoneNumber(
userRegisterDto.cellphoneNumber,
);
if (!user) {
// Create user + Send OTP and update database
}
// Send OTP to the cellphone number of user and update database
// TODO: Create a mock otp for once we got no otp senders or development phase
}
async loginUser(userLoginDto: UserLoginDto) {
const user = await this.userRepo.retrieveByCellPhoneNumber(
userLoginDto.cellphoneNumber,
);
if (!user) throw new BadRequestException("User does not exist");
// Generate token (access + refresh) and save inside the database, return them to the user
}
}

View File

@@ -1,8 +1,18 @@
import { ScryptOptions } from "node:crypto";
export const CURRENT_ALGORITHM = "scrypt";
export const CURRENT_VERSION = 1; // 0 = legacy salt:hash ; 1 = scrypt with different salt and hash and differnet format of salt:hash!
export const KEY_LENGTH = 64;
export const SCRYPT_PARAMS: ScryptOptions = {
N: 19456, // CPU/memory cost
r: 8, // block size
p: 1, // parallelization
};
export const KEY_LENGTH_FOR_OTP = 32;
export const SCRYPT_PARAMS_FOR_OTP: ScryptOptions = {
N: 1024,
r: 8,
p: 1,
};

View File

@@ -1,2 +0,0 @@
export { UserRegisterDto } from "./user-register.dto";
export { UserLoginDto } from "./user-login.dto";

View File

@@ -1,10 +0,0 @@
import { IsMobilePhone, IsNumberString, IsString } from "class-validator";
export class UserLoginDto {
@IsString({ message: "Cellphone number must be string" })
@IsMobilePhone("fa-IR")
cellphoneNumber: string;
@IsNumberString()
otp: string;
}

View File

@@ -1,7 +0,0 @@
import { IsMobilePhone, IsNumberString, IsString } from "class-validator";
export class UserRegisterDto {
@IsString({ message: "Cellphone number must be string" })
@IsMobilePhone("fa-IR")
cellphoneNumber: string;
}

View File

@@ -1,68 +0,0 @@
import {
BinaryLike,
randomBytes,
scrypt,
ScryptOptions,
timingSafeEqual,
} from "node:crypto";
import { promisify } from "node:util";
import { Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { KEY_LENGTH, SCRYPT_PARAMS } from "../constants/hash.constants";
const scryptAsync = promisify<
BinaryLike,
BinaryLike,
number,
ScryptOptions,
Buffer
>(scrypt);
@Injectable()
export class HashService {
private readonly pepper: string;
constructor(private readonly configService: ConfigService) {
this.pepper = this.configService.get<string>("HASH_PEPPER");
}
private applyPepper(input: string): string {
return this.pepper ? `${input}${this.pepper}` : input;
}
private generateSalt(bytes: number = 16): string {
return randomBytes(bytes).toString("hex");
}
private async scrypt(password: string): Promise<[string, string]> {
const salt = this.generateSalt();
const pepperedInput = this.applyPepper(password);
const hashedPassword = await scryptAsync(
pepperedInput,
salt,
KEY_LENGTH,
SCRYPT_PARAMS,
);
return [salt, hashedPassword.toString("hex")];
}
private async verifyScrypt(
password: string,
salt: string,
hashedPassword: string,
): Promise<boolean> {
const pepperedInput = this.applyPepper(password);
const derived = await scryptAsync(
pepperedInput,
salt,
KEY_LENGTH,
SCRYPT_PARAMS,
);
const storedBuffer = Buffer.from(hashedPassword, "hex");
if (derived.length !== storedBuffer.length) return false;
return timingSafeEqual(derived, storedBuffer);
}
}

View File

@@ -1 +0,0 @@
export { HashService } from "./hash.provider";

View File

@@ -1 +1,2 @@
export { JwtPayload } from "./payload.types";
export { PasswordHash } from "./password.types";

View File

@@ -1,7 +1,6 @@
export interface JwtPayload {
sub: string;
role: string;
clientId?: string;
iat?: number;
exp?: number;
}