Added Joi for schema validation, tidied up envs

This commit is contained in:
SepehrYahyaee
2026-05-30 10:37:23 +03:30
parent 10df869efb
commit 6ac0bf060e
12 changed files with 176 additions and 59 deletions

View File

@@ -1,7 +1,7 @@
import { join } from "node:path";
import { APP_INTERCEPTOR } from "@nestjs/core";
import { Module } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";
import { APP_INTERCEPTOR, APP_PIPE } from "@nestjs/core";
import { Module, ValidationPipe } from "@nestjs/common";
import { ConfigModule, ConfigService } from "@nestjs/config";
import { UnicodeDigitsNormalizeInterceptor } from "./common/interceptors/unicode-digits-normalize.interceptor";
import { MongooseModule } from "@nestjs/mongoose";
import { ServeStaticModule } from "@nestjs/serve-static";
@@ -23,31 +23,77 @@ import { UsersModule } from "./users/users.module";
import { applyIranFaTimestampPlugin } from "./helpers/mongoose-fa-timestamps.plugin";
import { CronModule } from "./utils/cron/cron.module";
import { WorkflowStepManagementModule } from "./workflow-step-management/workflow-step-management.module";
import * as Joi from "joi";
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
ConfigModule.forRoot({
isGlobal: true,
validationSchema: Joi.object({
NODE_ENV: Joi.string()
.valid("development", "production")
.default("development"),
PORT: Joi.number().port().default(9001),
URL: Joi.string().uri().required(),
BASE_URL_DEV: Joi.string()
.uri()
.when("NODE_ENV", {
is: Joi.string().valid("development"),
then: Joi.required(),
otherwise: Joi.optional(),
}),
SWAGGER_USER_DEV: Joi.string().when("NODE_ENV", {
is: Joi.string().valid("development"),
then: Joi.required(),
otherwise: Joi.optional(),
}),
SWAGGER_PASSWORD_DEV: Joi.string().when("NODE_ENV", {
is: Joi.string().valid("development"),
then: Joi.required(),
otherwise: Joi.optional(),
}),
MONGO_HOST: Joi.string().required(),
MONGO_PORT: Joi.number().port().required(),
MONGO_USER: Joi.string().required(),
MONGO_PASS: Joi.string().required(),
MONGO_DB_NAME: Joi.string().required(),
JWT_SECRET: Joi.string().required(),
SANHUB_BASE_URL: Joi.string().uri(),
SANHUB_URL_LOGIN: Joi.string().uri(),
SANHUB_USERNAME: Joi.string(),
SANHUB_PASSWORD: Joi.string(),
AI_URL: Joi.string().uri(),
AI_URL_V2: Joi.string().uri(),
AI_USERNAME: Joi.string(),
AI_PASSWORD: Joi.string(),
SMS_PROVIDER: Joi.string()
.valid("kavenegar", "parsian")
.default("kavenegar"),
SMS_API_KEY: Joi.string(),
AUTH_SMS_TEMPLATE: Joi.string(),
EXP_OTP_TIME: Joi.number(),
}),
}),
CronModule,
ServeStaticModule.forRoot({
rootPath: join(__dirname, "..", "files"),
serveRoot: "/files",
}),
MongooseModule.forRoot(
`mongodb://${process.env.MONGO_URL}:${process.env.MONGO_PORT}/`,
{
dbName: "yara724",
autoIndex: true,
user: process.env.MONGO_USER,
pass: process.env.MONGO_PASS,
authMechanism: "SCRAM-SHA-256",
tls: true,
tlsAllowInvalidCertificates: true,
connectionFactory: (connection) => {
applyIranFaTimestampPlugin(connection);
return connection;
},
MongooseModule.forRootAsync({
inject: [ConfigService],
useFactory: (configService: ConfigService) => {
return {
uri: `mongodb://${configService.get<string>("MONGO_USER")}:${configService.get<string>("MONGO_PASS")}@${configService.get<string>("MONGO_HOST")}:${configService.get<string>("MONGO_PORT")}/${configService.get<string>("MONGO_DB_NAME")}?authSource=admin&${configService.get<string>("MONGO_OPTIONS")}`,
tls: true,
tlsAllowInvalidCertificates: true,
autoIndex: configService.get<string>("NODE_ENV") !== "production",
connectionFactory: (connection) => {
applyIranFaTimestampPlugin(connection);
return connection;
},
};
},
),
}),
UsersModule,
AuthModule,
ClientModule,

View File

@@ -78,8 +78,7 @@ export class ActorAuthService {
res = await this.fieldExpertDbService.findOne({
_id: new Types.ObjectId(userId),
});
else
res = await this.fieldExpertDbService.findOne({ email: username });
else res = await this.fieldExpertDbService.findOne({ email: username });
break;
case RoleEnum.REGISTRAR:
if (username == null && userId)
@@ -139,7 +138,7 @@ export class ActorAuthService {
};
const access_token = this.jwtService.sign(payload, {
secret: `${process.env.SECRET}`,
secret: `${process.env.JWT_SECRET}`,
expiresIn: "1h",
});
@@ -159,9 +158,7 @@ export class ActorAuthService {
throw new UnauthorizedException("user not assigned to this role");
}
if (!(await this.hashService.compare(pass, user.password))) {
throw new UnauthorizedException(
"password is incorrect or access Denied",
);
throw new UnauthorizedException("password is incorrect or access Denied");
}
return user;
}
@@ -225,7 +222,7 @@ export class ActorAuthService {
firstName: body.firstName,
lastName: body.lastName,
phone: body.phone,
mobile:body.mobile,
mobile: body.mobile,
city: body.city,
state: body.state,
address: body.address,
@@ -445,13 +442,7 @@ export class ActorAuthService {
"state",
"address",
],
field_expert: [
"firstName",
"lastName",
"email",
"phone",
"mobile",
],
field_expert: ["firstName", "lastName", "email", "phone", "mobile"],
registrar: ["email"],
};
@@ -477,7 +468,11 @@ export class ActorAuthService {
}
// fetch user detail (document or plain object)
const document = await this.dynamicDbController(role, currentUser.role === "company" ? currentUser.username : null, userId);
const document = await this.dynamicDbController(
role,
currentUser.role === "company" ? currentUser.username : null,
userId,
);
if (!document) throw new NotFoundException("Profile not found");

View File

@@ -75,7 +75,7 @@ export class UserAuthService {
role: "user",
};
const accToken = this.jwtService.sign(payload, {
secret: `${process.env.SECRET}`,
secret: `${process.env.JWT_SECRET}`,
});
await this.userDbService.findOneAndUpdate(
{ username: user.username },
@@ -162,10 +162,7 @@ export class UserAuthService {
process.env.AUTH_SMS_TEMPLATE,
);
if (!ok) {
throw new HttpException(
"auth sms send failed",
HttpStatus.BAD_GATEWAY,
);
throw new HttpException("auth sms send failed", HttpStatus.BAD_GATEWAY);
}
this.logger.log(
`Auth OTP SMS accepted by provider phone=${mobile} otp=${otp}`,

View File

@@ -49,9 +49,9 @@ import { CaptchaModule } from "src/captcha/captcha.module";
{ name: ClaimCase.name, schema: ClaimCaseSchema },
]),
JwtModule.register({
signOptions: { expiresIn: "1h" },
signOptions: { expiresIn: "1h" }, // TODO: MAKE IT ENV
global: true,
secret: `${process.env.SECRET}`,
secret: `${process.env.JWT_SECRET}`,
}),
],
providers: [

View File

@@ -33,7 +33,7 @@ export class LocalActorAuthGuard implements CanActivate {
try {
const payload = await this.jwtService.verifyAsync(token, {
secret: `${process.env.SECRET}`,
secret: `${process.env.JWT_SECRET}`,
});
if (

View File

@@ -31,7 +31,7 @@ export class ClaimAccessGuard implements CanActivate {
try {
const payload = await this.jwtService.verifyAsync(token, {
secret: `${process.env.SECRET}`,
secret: `${process.env.JWT_SECRET}`,
});
// Allow users to pass through (they will be checked by service methods)
@@ -81,7 +81,10 @@ export class ClaimAccessGuard implements CanActivate {
throw new UnauthorizedException("Invalid role");
} catch (error) {
if (error instanceof ForbiddenException || error instanceof UnauthorizedException) {
if (
error instanceof ForbiddenException ||
error instanceof UnauthorizedException
) {
throw error;
}
throw new UnauthorizedException();
@@ -124,4 +127,3 @@ export class ClaimAccessGuard implements CanActivate {
return type === "Bearer" ? token : undefined;
}
}

View File

@@ -22,10 +22,13 @@ export class GlobalGuard implements CanActivate {
try {
const payload = await this.jwtService.verifyAsync(token, {
secret: `${process.env.SECRET}`,
secret: `${process.env.JWT_SECRET}`,
});
if (payload.role !== RoleEnum.USER && payload.role !== RoleEnum.FIELD_EXPERT) {
if (
payload.role !== RoleEnum.USER &&
payload.role !== RoleEnum.FIELD_EXPERT
) {
throw new UnauthorizedException();
}

View File

@@ -24,7 +24,7 @@ export class SettingsJwtGuard implements CanActivate {
try {
const payload = await this.jwtService.verifyAsync(token, {
secret: `${process.env.SECRET}`,
secret: `${process.env.JWT_SECRET}`,
});
(request as any).user = payload;
(request as any).identity = payload;

View File

@@ -39,9 +39,9 @@ async function bootstrap() {
.split(":");
const password = rest.join(":");
const expectedUser = configService.get<string>("SWAGGER_USER", "");
const expectedUser = configService.get<string>("SWAGGER_USER_DEV", "");
const expectedPassword = configService.get<string>(
"SWAGGER_PASSWORD",
"SWAGGER_PASSWORD_DEV",
"",
);
@@ -59,7 +59,7 @@ async function bootstrap() {
const config = new DocumentBuilder()
.setTitle("yara724-backend")
.setVersion("1.0.0")
.addServer(process.env.BASE_URL + "/api")
.addServer(process.env.BASE_URL_DEV + "/api")
.addServer("http://192.168.20.170:9001")
.addServer("http://localhost:9001")
.addBearerAuth()

View File

@@ -182,9 +182,9 @@ export class SandHubService {
try {
const response = await firstValueFrom(
this.httpService.post(process.env.SANDHUB_URL_LOGIN, {
email: process.env.SANDHUB_USERNAME,
password: process.env.SANDHUB_PASSWORD,
this.httpService.post(process.env.SANHUB_URL_LOGIN, {
email: process.env.SANHUB_USERNAME,
password: process.env.SANHUB_PASSWORD,
}),
);
@@ -476,7 +476,7 @@ export class SandHubService {
rightTwoDigits: String(userDetail.plate.ir),
nationalCode: userDetail.nationalCodeOfInsurer,
};
const base = process.env.SANDHUB_BASE_URL ?? "";
const base = process.env.SANHUB_BASE_URL ?? "";
const requestUrl = `${base}/block-inquiry-tejarat`;
let response: any;
@@ -509,7 +509,7 @@ export class SandHubService {
*/
async getPersonalInquiry(nationalCode: string, birthDate: number | string) {
try {
const requestUrl = `${process.env.SANDHUB_BASE_URL}/personal-inquiry/tejarat-no`;
const requestUrl = `${process.env.SANHUB_BASE_URL}/personal-inquiry/tejarat-no`;
const gregorianBirthdate = jalaliToGregorianDate(birthDate);
if (!gregorianBirthdate) {
@@ -549,7 +549,7 @@ export class SandHubService {
nationalCode: string,
driverLicenseNumber: string,
) {
const requestUrl = `${process.env.SANDHUB_BASE_URL}/driver-license-check`;
const requestUrl = `${process.env.SANHUB_BASE_URL}/driver-license-check`;
const requestPayload = {
driverLicenseNumber,
nationalCode,
@@ -588,7 +588,7 @@ export class SandHubService {
async getCarOwnershipInfo(plate: any, nationalCode: string) {
try {
const requestUrl = `${process.env.SANDHUB_BASE_URL}/ownership`;
const requestUrl = `${process.env.SANHUB_BASE_URL}/ownership`;
const requestPayload = {
Plk1: String(plate.leftDigits),
Plk2: String(plate.centerAlphabet),
@@ -624,7 +624,7 @@ export class SandHubService {
async getShebaValidation(nationalId: string, shebaId: string) {
try {
const requestUrl = `${process.env.SANDHUB_BASE_URL}/sheba/sheba-tejaratno`;
const requestUrl = `${process.env.SANHUB_BASE_URL}/sheba/sheba-tejaratno`;
const requestPayload = {
AccountOwnerType: "1",
NationalId: nationalId,