forked from Yara724/api
Initial commit after migration to gitea
This commit is contained in:
123
src/auth/auth-controllers/actor/actor.auth.controller.ts
Normal file
123
src/auth/auth-controllers/actor/actor.auth.controller.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
Param,
|
||||
Patch,
|
||||
Post,
|
||||
Req,
|
||||
UseGuards,
|
||||
} from "@nestjs/common";
|
||||
import {
|
||||
ApiBody,
|
||||
ApiAcceptedResponse,
|
||||
ApiResponse,
|
||||
ApiTags,
|
||||
ApiBearerAuth,
|
||||
} from "@nestjs/swagger";
|
||||
import { ActorAuthService } from "src/auth/auth-services/actor.auth.service";
|
||||
import {
|
||||
ForgetPasswordSendCodeDto,
|
||||
ForgetPasswordVerifyCodeDto,
|
||||
} from "src/auth/dto/actor/forget-password.actor.dto";
|
||||
import { LoginActorDto } from "src/auth/dto/actor/login.actor.dto";
|
||||
import { ActorEditUserProfileDto } from "src/auth/dto/actor/profile.actor.dto";
|
||||
import {
|
||||
GenuineRegisterDto,
|
||||
InsurerRegisterDto,
|
||||
LegalRegisterDto,
|
||||
} from "src/auth/dto/actor/register.actor.dto";
|
||||
import { LocalActorAuthGuard } from "src/auth/guards/actor-local.guard";
|
||||
import { ClientKey } from "src/decorators/clientKey.decorator";
|
||||
import { Roles } from "src/decorators/roles.decorator";
|
||||
import { CurrentUser } from "src/decorators/user.decorator";
|
||||
|
||||
@Controller("actor")
|
||||
@ApiTags("actor")
|
||||
export class ActorAuthController {
|
||||
constructor(private readonly actorAuthService: ActorAuthService) {}
|
||||
|
||||
@Post("register/genuine")
|
||||
@ApiBody({ type: GenuineRegisterDto })
|
||||
async registerGenuine(@Body() body: GenuineRegisterDto) {
|
||||
return await this.actorAuthService.genuineRegister(body);
|
||||
}
|
||||
|
||||
@Post("register/legal")
|
||||
@ApiBody({ type: LegalRegisterDto })
|
||||
async registerLegal(@Body() body: LegalRegisterDto) {
|
||||
return await this.actorAuthService.legalRegister(body);
|
||||
}
|
||||
|
||||
@Post("register/insurer")
|
||||
@ApiBody({ type: InsurerRegisterDto })
|
||||
async registerInsurer(@Body() body: InsurerRegisterDto) {
|
||||
return await this.actorAuthService.insurerRegister(body);
|
||||
}
|
||||
|
||||
@UseGuards(LocalActorAuthGuard)
|
||||
@Post("login")
|
||||
@Roles()
|
||||
@ApiBody({
|
||||
type: LoginActorDto,
|
||||
description: "user verify otp -- call this api and get a tokens",
|
||||
})
|
||||
@ApiAcceptedResponse()
|
||||
async login(@Body() body, @Req() req, @ClientKey() client) {
|
||||
return await this.actorAuthService.loginActors(req.user);
|
||||
}
|
||||
|
||||
@Post("forget-password")
|
||||
@ApiBody({
|
||||
type: ForgetPasswordSendCodeDto,
|
||||
description: "send otp when call this api",
|
||||
})
|
||||
@ApiAcceptedResponse()
|
||||
@ApiResponse({ type: ForgetPasswordSendCodeDto })
|
||||
async forgetPassword(@Body() body: ForgetPasswordSendCodeDto) {
|
||||
return await this.actorAuthService.forgetPasswordSendMail(body.email);
|
||||
}
|
||||
|
||||
@Post("forget-password-verify")
|
||||
@ApiBody({
|
||||
type: ForgetPasswordVerifyCodeDto,
|
||||
description: "send otp when call this api",
|
||||
})
|
||||
@ApiAcceptedResponse()
|
||||
@ApiResponse({ type: ForgetPasswordVerifyCodeDto })
|
||||
async forgetPasswordVerify(@Body() body: ForgetPasswordVerifyCodeDto) {
|
||||
const { email, otp, newPassword } = body;
|
||||
return await this.actorAuthService.forgetPasswordVerify(
|
||||
email,
|
||||
otp,
|
||||
newPassword,
|
||||
);
|
||||
}
|
||||
|
||||
@Get("register/form/states/list")
|
||||
async getStates() {
|
||||
return await this.actorAuthService.getStates();
|
||||
}
|
||||
|
||||
@Get("register/form/cities/list/:stateId")
|
||||
async getCities(@Param("stateId") stateId: number) {
|
||||
return await this.actorAuthService.getCities(stateId);
|
||||
}
|
||||
|
||||
@Get("/profile")
|
||||
@UseGuards(LocalActorAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
async getProfile(@CurrentUser() user) {
|
||||
return await this.actorAuthService.getProfiles(user);
|
||||
}
|
||||
|
||||
@Patch("/profile")
|
||||
@UseGuards(LocalActorAuthGuard)
|
||||
@ApiBearerAuth()
|
||||
async editProfile(
|
||||
@CurrentUser() user,
|
||||
@Body() update: ActorEditUserProfileDto,
|
||||
) {
|
||||
return await this.actorAuthService.modifyProfile(user, update);
|
||||
}
|
||||
}
|
||||
45
src/auth/auth-controllers/user/user.auth.controller.ts
Normal file
45
src/auth/auth-controllers/user/user.auth.controller.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Post,
|
||||
Req,
|
||||
UseGuards,
|
||||
} from "@nestjs/common";
|
||||
import { ApiAcceptedResponse, ApiBody, ApiTags } from "@nestjs/swagger";
|
||||
import { UserAuthService } from "src/auth/auth-services/user.auth.service";
|
||||
import { UserLoginDto } from "src/auth/dto/user/login.dto";
|
||||
import { UserVerifyOtp } from "src/auth/dto/user/verify.dto";
|
||||
import { LocalUserAuthGuard } from "src/auth/guards/user-local.guard";
|
||||
import { CurrentUser } from "src/decorators/user.decorator";
|
||||
|
||||
@Controller("user")
|
||||
@ApiTags("user")
|
||||
export class UserAuthController {
|
||||
constructor(private readonly userAuthService: UserAuthService) {}
|
||||
|
||||
@Post("/send-otp")
|
||||
@ApiBody({
|
||||
type: UserLoginDto,
|
||||
description: "user login api -- call this api and send otp",
|
||||
})
|
||||
@ApiAcceptedResponse()
|
||||
async sendOtpRq(@Body() body: UserLoginDto) {
|
||||
const res = await this.userAuthService.sendOtpRequest(body.mobile);
|
||||
if (res) {
|
||||
throw new HttpException(res, HttpStatus.ACCEPTED);
|
||||
}
|
||||
}
|
||||
|
||||
@Post("/login")
|
||||
@UseGuards(LocalUserAuthGuard)
|
||||
@ApiBody({
|
||||
type: UserVerifyOtp,
|
||||
description: "user verify otp -- call this api and get a tokens",
|
||||
})
|
||||
@ApiAcceptedResponse()
|
||||
async login(@Body() body, @Req() req, @CurrentUser() user) {
|
||||
return await this.userAuthService.login(req.user);
|
||||
}
|
||||
}
|
||||
397
src/auth/auth-services/actor.auth.service.ts
Normal file
397
src/auth/auth-services/actor.auth.service.ts
Normal file
@@ -0,0 +1,397 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import {
|
||||
BadGatewayException,
|
||||
BadRequestException,
|
||||
ForbiddenException,
|
||||
Injectable,
|
||||
InternalServerErrorException,
|
||||
NotFoundException,
|
||||
UnauthorizedException,
|
||||
} from "@nestjs/common";
|
||||
import { JwtService } from "@nestjs/jwt";
|
||||
import { Types } from "mongoose";
|
||||
import { ForgetPasswordVerifyCodeDtoRs } from "src/auth/dto/actor/forget-password.actor.dto";
|
||||
import { ProfileActor } from "src/auth/dto/actor/profile.actor.dto";
|
||||
import {
|
||||
InsurerRegisterDto,
|
||||
RegisterDto,
|
||||
RegisterDtoRs,
|
||||
} from "src/auth/dto/actor/register.actor.dto";
|
||||
import { StateListDtoRs } from "src/auth/dto/actor/states.dto";
|
||||
import { ClientDbService } from "src/client/entities/db-service/client.db.service";
|
||||
import { RoleEnum } from "src/Types&Enums/role.enum";
|
||||
import { UserType } from "src/Types&Enums/userType.enum";
|
||||
import { InsurerExpertDbService } from "src/users/entities/db-service/insurer-expert.db.service";
|
||||
import { DamageExpertDbService } from "src/users/entities/db-service/damage-expert.db.service";
|
||||
import { ExpertDbService } from "src/users/entities/db-service/expert.db.service";
|
||||
import { HashService } from "src/utils/hash/hash.service";
|
||||
import { MailService } from "src/utils/mail/mail.service";
|
||||
import { OtpService } from "src/utils/otp/otp.service";
|
||||
|
||||
function pick(obj: Record<string, any>, keys: string[]) {
|
||||
const out: Record<string, any> = {};
|
||||
for (const key of keys) {
|
||||
if (obj[key] !== undefined) out[key] = obj[key];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// TODO FIX REGISTER TO ACTOR.SERVICE AND AUTH IN THIS MODULE
|
||||
@Injectable()
|
||||
export class ActorAuthService {
|
||||
constructor(
|
||||
private readonly jwtService: JwtService,
|
||||
private readonly hashService: HashService,
|
||||
private readonly expertDbService: ExpertDbService,
|
||||
private readonly damageExpertDbService: DamageExpertDbService,
|
||||
private readonly insurerExpertDbService: InsurerExpertDbService,
|
||||
private readonly mailService: MailService,
|
||||
private readonly clientDbService: ClientDbService,
|
||||
private readonly otpService: OtpService,
|
||||
) {}
|
||||
|
||||
// TODO convrt to class for dynamic controller
|
||||
public async dynamicDbController(role, username, userId?: string) {
|
||||
let res;
|
||||
switch (role) {
|
||||
case RoleEnum.EXPERT:
|
||||
if (username == null && userId)
|
||||
res = await this.expertDbService.findOne({
|
||||
_id: new Types.ObjectId(userId),
|
||||
});
|
||||
else res = await this.expertDbService.findOne({ email: username });
|
||||
break;
|
||||
case RoleEnum.DAMAGE_EXPERT:
|
||||
if (username == null && userId)
|
||||
res = await this.damageExpertDbService.findOne({
|
||||
_id: new Types.ObjectId(userId),
|
||||
});
|
||||
else
|
||||
res = await this.damageExpertDbService.findOne({ email: username });
|
||||
break;
|
||||
case RoleEnum.COMPANY:
|
||||
res = await this.insurerExpertDbService.findOne({ email: username });
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
async validateActor(username: string, pass: string, role): Promise<any> {
|
||||
const user = await this.dynamicDbController(role, username);
|
||||
if (user) {
|
||||
if (user.role !== role) {
|
||||
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",
|
||||
);
|
||||
} else {
|
||||
return user;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async loginActors(user: any) {
|
||||
let foundedUser = await this.dynamicDbController(user.role, user.username);
|
||||
if (foundedUser) {
|
||||
const payload = {
|
||||
username: foundedUser.username || foundedUser.email,
|
||||
sub: foundedUser._id,
|
||||
fullName:
|
||||
`${foundedUser.firstName || ""} ${foundedUser.lastName || ""}`.trim(),
|
||||
role: foundedUser.role || "User",
|
||||
userType: foundedUser.userType || "UserType",
|
||||
clientKey: foundedUser.clientKey || null,
|
||||
};
|
||||
|
||||
const accToken = this.jwtService.sign(payload, {
|
||||
secret: `${process.env.SECRET}`,
|
||||
expiresIn: "1h",
|
||||
});
|
||||
|
||||
return {
|
||||
...payload,
|
||||
access_token: accToken,
|
||||
};
|
||||
} else {
|
||||
throw new UnauthorizedException("expert or damage_expert not found");
|
||||
}
|
||||
}
|
||||
|
||||
async registerActors(
|
||||
body: RegisterDto | InsurerRegisterDto,
|
||||
userType: UserType,
|
||||
) {
|
||||
const hashPassword = await this.hashService.hash(body.password);
|
||||
body.password = hashPassword;
|
||||
|
||||
(body as any).userType = userType;
|
||||
|
||||
try {
|
||||
if ("username" in body) {
|
||||
if (userType === UserType.INSURER) {
|
||||
if (!body.clientKey) {
|
||||
throw new BadRequestException(
|
||||
"clientKey is required for this user type.",
|
||||
);
|
||||
}
|
||||
const clientName = await this.clientDbService.find({
|
||||
_id: new Types.ObjectId(body.clientKey),
|
||||
});
|
||||
if (!clientName) throw new NotFoundException("Client Not Found");
|
||||
}
|
||||
|
||||
const newCompanyPayload = {
|
||||
email: body.username,
|
||||
password: body.password,
|
||||
role: RoleEnum.COMPANY,
|
||||
userType: (body as any).userType,
|
||||
clientKey: body.clientKey,
|
||||
firstName: body.firstName,
|
||||
lastName: body.lastName,
|
||||
};
|
||||
|
||||
return new RegisterDtoRs(
|
||||
await this.insurerExpertDbService.create(newCompanyPayload),
|
||||
);
|
||||
} else {
|
||||
if (userType === UserType.LEGAL || userType === UserType.GENUINE) {
|
||||
if (!body.insuActivityCo) {
|
||||
throw new BadRequestException(
|
||||
"insuActivityCo is required for this user type.",
|
||||
);
|
||||
}
|
||||
const clientName = await this.clientDbService.find({
|
||||
_id: body.insuActivityCo,
|
||||
});
|
||||
if (!clientName) throw new NotFoundException("Client Not Found");
|
||||
|
||||
body.clientKey = body.insuActivityCo;
|
||||
body.insuActivityCo = clientName.clientName.english;
|
||||
}
|
||||
|
||||
switch (body.role) {
|
||||
case RoleEnum.EXPERT:
|
||||
return new RegisterDtoRs(await this.expertDbService.create(body));
|
||||
case RoleEnum.DAMAGE_EXPERT:
|
||||
return new RegisterDtoRs(
|
||||
await this.damageExpertDbService.create(body),
|
||||
);
|
||||
default:
|
||||
throw new BadRequestException(
|
||||
`Invalid role for this registration type: ${body.role}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (er) {
|
||||
if (er.code === 11000) {
|
||||
throw new BadRequestException(
|
||||
"A user with these details already exists.",
|
||||
);
|
||||
} else if (er.errors) {
|
||||
const errObjKey = Object.keys(er.errors)[0];
|
||||
const { path, kind } = er.errors[errObjKey];
|
||||
throw new BadRequestException(
|
||||
`Validation failed for field '${path}'. Expected a valid ${kind}.`,
|
||||
);
|
||||
} else {
|
||||
throw er;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async legalRegister(body: RegisterDto) {
|
||||
return await this.registerActors(body, UserType.LEGAL);
|
||||
}
|
||||
|
||||
async genuineRegister(body: RegisterDto) {
|
||||
return await this.registerActors(body, UserType.GENUINE);
|
||||
}
|
||||
|
||||
async insurerRegister(body: InsurerRegisterDto) {
|
||||
Object.assign(body, { role: RoleEnum.COMPANY });
|
||||
return await this.registerActors(body, UserType.INSURER);
|
||||
}
|
||||
|
||||
/// TODO need to seed
|
||||
async getStates() {
|
||||
const states = JSON.parse(
|
||||
readFileSync(`${process.cwd()}/src/static/states.json`, "utf8"),
|
||||
);
|
||||
return new StateListDtoRs(states);
|
||||
}
|
||||
|
||||
/// TODO need to seeds
|
||||
async getCities(id: number) {
|
||||
const cities = JSON.parse(
|
||||
readFileSync(`${process.cwd()}/src/static/cities.json`, "utf8"),
|
||||
);
|
||||
const citiesList = cities.filter((c) => c.province_id == id);
|
||||
return new StateListDtoRs(citiesList);
|
||||
}
|
||||
|
||||
async forgetPasswordSendMail(email: string) {
|
||||
const normalizedEmail = email.toLowerCase().trim();
|
||||
const filter = { email: normalizedEmail };
|
||||
|
||||
let actor;
|
||||
|
||||
// expert
|
||||
actor = await this.expertDbService.findOne(filter);
|
||||
let dbServiceToUpdate = this.expertDbService;
|
||||
|
||||
if (!actor) {
|
||||
actor = (await this.damageExpertDbService.findOne(filter)) as any;
|
||||
dbServiceToUpdate = this.damageExpertDbService as any;
|
||||
}
|
||||
|
||||
if (!actor) {
|
||||
actor = await this.insurerExpertDbService.findOne(filter);
|
||||
dbServiceToUpdate = this.insurerExpertDbService as any;
|
||||
}
|
||||
|
||||
if (!actor) {
|
||||
throw new NotFoundException("actor not found");
|
||||
}
|
||||
|
||||
const otp = this.otpService.create();
|
||||
const sendEmail = await this.mailService.sendMail(normalizedEmail, otp);
|
||||
|
||||
if (sendEmail) {
|
||||
const hashOtp = await this.hashService.hash(otp);
|
||||
|
||||
await dbServiceToUpdate.findOneAndUpdate(filter, { otp: hashOtp });
|
||||
|
||||
return sendEmail;
|
||||
} else {
|
||||
throw new BadGatewayException("mailServer in unavailable");
|
||||
}
|
||||
}
|
||||
|
||||
async forgetPasswordVerify(email, otp, newPassword) {
|
||||
const userExist = await this.expertDbService.findOne({ email });
|
||||
if (!userExist) throw new NotFoundException("user not found");
|
||||
const decodeOtp = await this.hashService.compare(otp, userExist.otp);
|
||||
if (!decodeOtp) throw new UnauthorizedException("otp invalid");
|
||||
if (decodeOtp) {
|
||||
const hashNewPassword = await this.hashService.hash(newPassword);
|
||||
await this.expertDbService.findOneAndUpdate(
|
||||
{ email },
|
||||
{ password: hashNewPassword },
|
||||
);
|
||||
return new ForgetPasswordVerifyCodeDtoRs(userExist, "update password");
|
||||
}
|
||||
}
|
||||
|
||||
async getProfiles(currentUser) {
|
||||
const userDetail = await this.dynamicDbController(
|
||||
currentUser.role,
|
||||
null,
|
||||
currentUser.sub,
|
||||
);
|
||||
return new ProfileActor(userDetail);
|
||||
}
|
||||
|
||||
async modifyProfile(currentUser: any, update: Record<string, any>) {
|
||||
const role = currentUser?.role;
|
||||
const userId = currentUser?.sub;
|
||||
|
||||
if (!role || !userId) {
|
||||
throw new BadRequestException("Invalid actor payload");
|
||||
}
|
||||
|
||||
const allowedFieldsByRole: Record<string, string[]> = {
|
||||
expert: [
|
||||
"fullName",
|
||||
"firstName",
|
||||
"lastName",
|
||||
"email",
|
||||
"phone",
|
||||
"city",
|
||||
"state",
|
||||
"bio",
|
||||
"nationalCode",
|
||||
"birthDate",
|
||||
"avatarUrl",
|
||||
"address",
|
||||
],
|
||||
damage_expert: [
|
||||
"fullName",
|
||||
"email",
|
||||
"phone",
|
||||
"companyName",
|
||||
"city",
|
||||
"state",
|
||||
"bio",
|
||||
"nationalCode",
|
||||
"birthDate",
|
||||
"avatarUrl",
|
||||
"address",
|
||||
],
|
||||
};
|
||||
|
||||
const allowedFields = allowedFieldsByRole[role];
|
||||
if (!allowedFields) {
|
||||
throw new ForbiddenException("Profile update not allowed for this role");
|
||||
}
|
||||
|
||||
const sanitized = Object.fromEntries(
|
||||
Object.entries(update).filter(([key]) => allowedFields.includes(key)),
|
||||
);
|
||||
|
||||
if (Object.keys(sanitized).length === 0) {
|
||||
throw new BadRequestException("No valid fields to update");
|
||||
}
|
||||
|
||||
if (sanitized.birthDate && typeof sanitized.birthDate === "string") {
|
||||
const parsed = new Date(sanitized.birthDate);
|
||||
if (isNaN(parsed.getTime())) {
|
||||
throw new BadRequestException("birthDate must be a valid date");
|
||||
}
|
||||
sanitized.birthDate = parsed;
|
||||
}
|
||||
|
||||
// fetch user detail (document or plain object)
|
||||
const document = await this.dynamicDbController(role, null, userId);
|
||||
|
||||
if (!document) throw new NotFoundException("Profile not found");
|
||||
|
||||
try {
|
||||
// if it’s a mongoose document (has .save)
|
||||
if (typeof document.save === "function") {
|
||||
Object.assign(document, sanitized);
|
||||
await document.save();
|
||||
return document;
|
||||
}
|
||||
|
||||
// else, update directly via corresponding DB service
|
||||
switch (role) {
|
||||
case "expert":
|
||||
await this.expertDbService.updateOne(
|
||||
{ _id: new Types.ObjectId(userId) },
|
||||
{ $set: sanitized },
|
||||
);
|
||||
break;
|
||||
|
||||
case "damage_expert":
|
||||
await this.damageExpertDbService.updateOne(
|
||||
{ _id: new Types.ObjectId(userId) },
|
||||
{ $set: sanitized },
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ForbiddenException("Unsupported role for update");
|
||||
}
|
||||
|
||||
// return the fresh document
|
||||
return await this.dynamicDbController(role, null, userId);
|
||||
} catch (err) {
|
||||
throw new InternalServerErrorException("Failed to update profile");
|
||||
}
|
||||
}
|
||||
}
|
||||
137
src/auth/auth-services/user.auth.service.ts
Normal file
137
src/auth/auth-services/user.auth.service.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import {
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
Injectable,
|
||||
Logger,
|
||||
NotAcceptableException,
|
||||
NotFoundException,
|
||||
} from "@nestjs/common";
|
||||
import { JwtService } from "@nestjs/jwt";
|
||||
import { Types } from "mongoose";
|
||||
import { LoginDtoRs } from "src/auth/dto/user/login.dto";
|
||||
import { UserDbService } from "src/users/entities/db-service/user.db.service";
|
||||
import { HashService } from "src/utils/hash/hash.service";
|
||||
import { OtpService } from "src/utils/otp/otp.service";
|
||||
import { SmsManagerService } from "src/utils/sms-manager/sms-manager.service";
|
||||
|
||||
// TODO FIX REGISTER TO USER.SERVICE AND AUTH IN THIS MODULE
|
||||
@Injectable()
|
||||
export class UserAuthService {
|
||||
private readonly logger = new Logger(UserAuthService.name);
|
||||
|
||||
constructor(
|
||||
private readonly jwtService: JwtService,
|
||||
private readonly userDbService: UserDbService,
|
||||
private readonly hashService: HashService,
|
||||
private readonly otpCreator: OtpService,
|
||||
private readonly smsManagerService: SmsManagerService,
|
||||
) {}
|
||||
|
||||
async validateUser(username: string, pass: string): Promise<any> {
|
||||
const user = await this.userDbService.findOne({ username });
|
||||
if (!user) throw new NotFoundException("user not found");
|
||||
|
||||
const now = new Date().getTime();
|
||||
if (user.otp == null) throw new NotAcceptableException("please get otp");
|
||||
if (user.otpExpire < now) {
|
||||
throw new NotAcceptableException("expire otp");
|
||||
}
|
||||
if (await this.hashService.compare(pass, user.otp)) {
|
||||
return user;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async login(user: any) {
|
||||
const payload = {
|
||||
username: user.username,
|
||||
sub: user.id,
|
||||
role: "user",
|
||||
};
|
||||
const accToken = this.jwtService.sign(payload, {
|
||||
secret: `${process.env.SECRET}`,
|
||||
});
|
||||
await this.userDbService.findOneAndUpdate(
|
||||
{ username: user.username },
|
||||
{
|
||||
tokens: { token: accToken },
|
||||
otp: null,
|
||||
},
|
||||
);
|
||||
return {
|
||||
userId: user._id,
|
||||
access_token: accToken,
|
||||
};
|
||||
}
|
||||
|
||||
async sendOtpRequest(mobile: string): Promise<LoginDtoRs> {
|
||||
const userExist = await this.userDbService.findOne({
|
||||
mobile,
|
||||
});
|
||||
const otp = this.otpCreator.create();
|
||||
const hashOtp = await this.hashService.hash(otp);
|
||||
if (!userExist) {
|
||||
await this.smsSender(otp, mobile);
|
||||
/// create otp request
|
||||
const newUser = await this.userDbService.createUser({
|
||||
mobile,
|
||||
username: mobile,
|
||||
otp: hashOtp,
|
||||
tokens: {
|
||||
token: "",
|
||||
rfToken: "",
|
||||
},
|
||||
fullName: "",
|
||||
nationalCode: "",
|
||||
lastLogin: new Date(),
|
||||
clientKey: new Types.ObjectId(),
|
||||
birthDay: "",
|
||||
city: "",
|
||||
address: "",
|
||||
state: "",
|
||||
otpExpire: new Date(
|
||||
new Date().getTime() + +process.env.EXP_OTP_TIME * 60 * 1000,
|
||||
).getTime(),
|
||||
});
|
||||
return new LoginDtoRs(newUser);
|
||||
}
|
||||
if (userExist) {
|
||||
await this.smsSender(otp, mobile);
|
||||
const updateTokens = await this.userDbService.findOneAndUpdate(
|
||||
{
|
||||
username: userExist.username,
|
||||
},
|
||||
{
|
||||
otp: hashOtp,
|
||||
otpExpire: new Date(
|
||||
new Date().getTime() + +process.env.EXP_OTP_TIME * 60 * 1000,
|
||||
).getTime(),
|
||||
},
|
||||
);
|
||||
if (updateTokens) return new LoginDtoRs(userExist);
|
||||
}
|
||||
}
|
||||
|
||||
private async smsSender(otp: string, mobile: string) {
|
||||
return this.smsManagerService
|
||||
.verifyLookUp({
|
||||
token: otp,
|
||||
template: process.env.AUTH_SMS_TEMPLATE,
|
||||
receptor: mobile,
|
||||
})
|
||||
.then((smsRes) => {
|
||||
this.logger.log(
|
||||
`${"phone : " + mobile + " " + ", status : " + smsRes["return"].status + ", otp : " + otp} `,
|
||||
);
|
||||
})
|
||||
.catch((er) => {
|
||||
this.logger.error(
|
||||
`${"phone : " + mobile + " " + ", status : " + er["return"].status + ", otp : " + otp} `,
|
||||
);
|
||||
throw new HttpException(
|
||||
" auth sms send failed",
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
42
src/auth/auth.module.ts
Normal file
42
src/auth/auth.module.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { JwtModule, JwtService } from "@nestjs/jwt";
|
||||
import { PassportModule } from "@nestjs/passport";
|
||||
import { LocalStrategy } from "src/auth/stratregys/local.strategy";
|
||||
import { LocalActorStrategy } from "src/auth/stratregys/local-actor.strategy";
|
||||
import { ActorAuthController } from "src/auth/auth-controllers/actor/actor.auth.controller";
|
||||
import { UserAuthController } from "src/auth/auth-controllers/user/user.auth.controller";
|
||||
import { ActorAuthService } from "src/auth/auth-services/actor.auth.service";
|
||||
import { UserAuthService } from "src/auth/auth-services/user.auth.service";
|
||||
import { ClientModule } from "src/client/client.module";
|
||||
import { UsersModule } from "src/users/users.module";
|
||||
import { HashModule } from "src/utils/hash/hash.module";
|
||||
import { MailModule } from "src/utils/mail/mail.module";
|
||||
import { OtpModule } from "src/utils/otp/otp.module";
|
||||
import { SmsManagerModule } from "src/utils/sms-manager/sms-manager.module";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
MailModule,
|
||||
UsersModule,
|
||||
ClientModule,
|
||||
HashModule,
|
||||
OtpModule,
|
||||
PassportModule,
|
||||
SmsManagerModule,
|
||||
JwtModule.register({
|
||||
signOptions: { expiresIn: "1h" },
|
||||
global: true,
|
||||
secret: `${process.env.SECRET}`,
|
||||
}),
|
||||
],
|
||||
providers: [
|
||||
UserAuthService,
|
||||
ActorAuthService,
|
||||
LocalStrategy,
|
||||
LocalActorStrategy,
|
||||
JwtService,
|
||||
],
|
||||
exports: [LocalStrategy, UserAuthService, ActorAuthService, JwtService],
|
||||
controllers: [UserAuthController, ActorAuthController],
|
||||
})
|
||||
export class AuthModule {}
|
||||
30
src/auth/dto/actor/forget-password.actor.dto.ts
Normal file
30
src/auth/dto/actor/forget-password.actor.dto.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
|
||||
export class ForgetPasswordSendCodeDto {
|
||||
@ApiProperty({ example: "balali.arash@gmail.com", type: String })
|
||||
email: string;
|
||||
}
|
||||
|
||||
export class ForgetPasswordVerifyCodeDto {
|
||||
@ApiProperty({ example: "09331009989", type: String })
|
||||
email: string;
|
||||
|
||||
@ApiProperty({ type: String, examples: { true: "22222" } })
|
||||
otp: string;
|
||||
|
||||
@ApiProperty({ type: String })
|
||||
newPassword: string;
|
||||
}
|
||||
|
||||
export class ForgetPasswordVerifyCodeDtoRs {
|
||||
@ApiProperty({ example: "balali.arash@gmail.com", type: String })
|
||||
email: string;
|
||||
|
||||
@ApiProperty({ type: String, examples: { true: "22222" } })
|
||||
message: string;
|
||||
|
||||
constructor(user, message) {
|
||||
this.email = user.email;
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
41
src/auth/dto/actor/login.actor.dto.ts
Normal file
41
src/auth/dto/actor/login.actor.dto.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { RoleEnum } from "src/Types&Enums/role.enum";
|
||||
|
||||
export class LoginActorDto {
|
||||
@ApiProperty({ example: RoleEnum, type: "array", description: "LOGIN_DTO" })
|
||||
role: RoleEnum[];
|
||||
|
||||
@ApiProperty({})
|
||||
username: string;
|
||||
|
||||
@ApiProperty({})
|
||||
password: string;
|
||||
}
|
||||
|
||||
export class LoginActorDtoRs extends LoginActorDto {
|
||||
private readonly userId;
|
||||
constructor(userData) {
|
||||
super();
|
||||
this.userId = userData._id;
|
||||
this.role = userData.role;
|
||||
this.username = userData.email;
|
||||
this.clientKey = userData.clientKey;
|
||||
this.token = userData.token;
|
||||
this.refreshToken = userData.refreshToken;
|
||||
}
|
||||
|
||||
@ApiProperty({ type: "string", description: "LOGIN_DTO_RS" })
|
||||
fullName: string;
|
||||
|
||||
@ApiProperty({ type: "string", description: "LOGIN_DTO_RS" })
|
||||
role: RoleEnum[];
|
||||
|
||||
@ApiProperty({ type: "string", description: "LOGIN_DTO_RS" })
|
||||
token: string;
|
||||
|
||||
@ApiProperty({ type: "string", description: "LOGIN_DTO_RS" })
|
||||
refreshToken: string;
|
||||
|
||||
@ApiProperty({ type: "string", description: "LOGIN_DTO_RS" })
|
||||
clientKey: string;
|
||||
}
|
||||
98
src/auth/dto/actor/profile.actor.dto.ts
Normal file
98
src/auth/dto/actor/profile.actor.dto.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { ApiProperty, ApiSchema } from "@nestjs/swagger";
|
||||
import { IsMobilePhone, IsString, Length } from "class-validator";
|
||||
import { DamageExpertModel } from "src/users/entities/schema/damage-expert.schema";
|
||||
|
||||
export class ProfileActor {
|
||||
public email: string;
|
||||
public fullName: string;
|
||||
public nationalCode: string;
|
||||
public insuActivityCo: string;
|
||||
public userType: string;
|
||||
public mobile: string;
|
||||
public address: string;
|
||||
public state: string;
|
||||
public city: string;
|
||||
|
||||
constructor(Profile: DamageExpertModel) {
|
||||
this.email = Profile?.email;
|
||||
this.fullName = Profile?.firstName + " " + Profile?.lastName;
|
||||
this.nationalCode = Profile?.nationalCode;
|
||||
this.insuActivityCo = Profile?.insuActivityCo;
|
||||
this.userType = Profile?.userType;
|
||||
this.mobile = Profile?.mobile;
|
||||
this.address = Profile?.address;
|
||||
this.state = Profile?.state;
|
||||
this.city = Profile?.city;
|
||||
}
|
||||
}
|
||||
|
||||
@ApiSchema({
|
||||
name: "Edit Actor Profile",
|
||||
description: "Body of the request to edit the actor's profile",
|
||||
})
|
||||
export class ActorEditUserProfileDto {
|
||||
@ApiProperty({
|
||||
type: "string",
|
||||
description: "First name of the actor",
|
||||
example: "heshmat",
|
||||
nullable: true,
|
||||
})
|
||||
@IsString()
|
||||
@Length(2, 40)
|
||||
firstName?: string;
|
||||
|
||||
@ApiProperty({
|
||||
type: "string",
|
||||
description: "Last name of the actor",
|
||||
example: "Heshmati",
|
||||
nullable: true,
|
||||
})
|
||||
@IsString()
|
||||
@Length(1, 10)
|
||||
lastName?: string;
|
||||
|
||||
@ApiProperty({
|
||||
type: "string",
|
||||
description: "Mobile phone of the actor",
|
||||
example: "09123456789",
|
||||
nullable: true,
|
||||
})
|
||||
@IsMobilePhone("fa-IR")
|
||||
mobile?: string;
|
||||
|
||||
@ApiProperty({
|
||||
type: "string",
|
||||
description: "تلفن خط ثابت",
|
||||
example: "02122222222",
|
||||
nullable: true,
|
||||
})
|
||||
@IsString()
|
||||
phone?: string;
|
||||
|
||||
@ApiProperty({
|
||||
type: "string",
|
||||
description: "City of the user",
|
||||
example: "استان",
|
||||
nullable: true,
|
||||
})
|
||||
@IsString()
|
||||
city?: string;
|
||||
|
||||
@ApiProperty({
|
||||
type: "string",
|
||||
description: "State of the user",
|
||||
example: "شهر",
|
||||
nullable: true,
|
||||
})
|
||||
@IsString()
|
||||
state?: string;
|
||||
|
||||
@ApiProperty({
|
||||
type: "string",
|
||||
description: "Address of the user",
|
||||
example: "آدرس",
|
||||
nullable: true,
|
||||
})
|
||||
@IsString()
|
||||
address?: string;
|
||||
}
|
||||
341
src/auth/dto/actor/register.actor.dto.ts
Normal file
341
src/auth/dto/actor/register.actor.dto.ts
Normal file
@@ -0,0 +1,341 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Exclude } from "class-transformer";
|
||||
import { IsEmail } from "class-validator";
|
||||
import { Types } from "mongoose";
|
||||
import { Degrees } from "src/Types&Enums/degrees.enum";
|
||||
import {
|
||||
ExpertizedAtEnum,
|
||||
PreviousWorkEnum,
|
||||
SkillEnum,
|
||||
} from "src/Types&Enums/damage-expert.enum";
|
||||
import { RoleEnum } from "src/Types&Enums/role.enum";
|
||||
import { UserType } from "src/Types&Enums/userType.enum";
|
||||
|
||||
@Exclude()
|
||||
export class RegisterDto {
|
||||
clientKey?: string;
|
||||
@ApiProperty({
|
||||
example: "Soheil",
|
||||
type: "string",
|
||||
description: "firstname of actor",
|
||||
})
|
||||
firstName: string;
|
||||
|
||||
@ApiProperty({
|
||||
enum: RoleEnum,
|
||||
examples: RoleEnum,
|
||||
type: "string",
|
||||
description: "firstname of actor",
|
||||
})
|
||||
role: RoleEnum;
|
||||
|
||||
userType: UserType;
|
||||
|
||||
@ApiProperty({
|
||||
example: "Hajizadeh",
|
||||
type: "string",
|
||||
description: "lastname of actor",
|
||||
})
|
||||
lastName: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: "4311402422",
|
||||
type: "string",
|
||||
description: "nationalCode of actor",
|
||||
})
|
||||
nationalCode: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: "dev.callmeskylark@gmail.com",
|
||||
type: "string",
|
||||
description: "email of actor",
|
||||
})
|
||||
email: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: "123321",
|
||||
type: "string",
|
||||
description: "password of actor",
|
||||
})
|
||||
password: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: "7522312365495123",
|
||||
type: "string",
|
||||
description: "sheba of actor",
|
||||
})
|
||||
sheba?: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: "02133564521",
|
||||
type: "string",
|
||||
description: "phone of actor",
|
||||
})
|
||||
phone?: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: "09226187419",
|
||||
type: "string",
|
||||
description: "mobile of actor",
|
||||
})
|
||||
mobile: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: "Tehran",
|
||||
type: "string",
|
||||
description: "province of actor",
|
||||
})
|
||||
state?: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: "Tehran",
|
||||
type: "string",
|
||||
description: "city of actor",
|
||||
})
|
||||
city?: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: "Gisha , No 7",
|
||||
type: "string",
|
||||
description: "address of actor",
|
||||
})
|
||||
address?: string;
|
||||
|
||||
@ApiProperty({
|
||||
enum: Degrees,
|
||||
examples: Degrees,
|
||||
type: String,
|
||||
description: "expDegree of actor",
|
||||
})
|
||||
expDegree?: Degrees;
|
||||
|
||||
@ApiProperty({
|
||||
example: "5",
|
||||
type: "number",
|
||||
description: "insuActivityTime of actor",
|
||||
})
|
||||
insuActivityTime?: number;
|
||||
|
||||
@ApiProperty({
|
||||
example: "64fc4978b74d670939b08920",
|
||||
type: String,
|
||||
description: "insuActivityCo of actor",
|
||||
})
|
||||
insuActivityCo: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: "5",
|
||||
type: "number",
|
||||
description: "policeActivityTime of actor",
|
||||
})
|
||||
policeActivityTime?: number;
|
||||
|
||||
@ApiProperty({
|
||||
examples: ["headquarters", "queue"],
|
||||
type: "string",
|
||||
description: "policeActivityKind of actor",
|
||||
})
|
||||
policeActivityKind?: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: "Tehran",
|
||||
type: String,
|
||||
description: "policeServicePlace of actor",
|
||||
})
|
||||
policeServicePlace?: string;
|
||||
}
|
||||
|
||||
export class GenuineRegisterDto extends RegisterDto {
|
||||
@ApiProperty({
|
||||
type: () => ({
|
||||
fullName: { type: "string", example: "John Doe" },
|
||||
nationalCode: { type: "string", example: "4311402422" },
|
||||
dob: {
|
||||
type: "string",
|
||||
example: "1990-01-01",
|
||||
description: "date of birth in ISO string or yyyy-mm-dd",
|
||||
},
|
||||
mobile: { type: "string", example: "09226187419" },
|
||||
}),
|
||||
description: "personal information of damage expert (duplicated with some flat fields)",
|
||||
required: false,
|
||||
})
|
||||
personalInfo?: {
|
||||
fullName: string;
|
||||
nationalCode: string;
|
||||
dob: string;
|
||||
mobile: string;
|
||||
};
|
||||
|
||||
@ApiProperty({
|
||||
type: () => ({
|
||||
nationalCard: {
|
||||
type: "string",
|
||||
example: "665f0e5ab74d670939b08920",
|
||||
description: "fileId of uploaded national card",
|
||||
},
|
||||
selfie: {
|
||||
type: "string",
|
||||
example: "665f0e5ab74d670939b08921",
|
||||
description: "fileId of uploaded selfie",
|
||||
},
|
||||
activityCert: {
|
||||
type: "string",
|
||||
example: "665f0e5ab74d670939b08922",
|
||||
description: "fileId of uploaded activity certificate",
|
||||
},
|
||||
}),
|
||||
description:
|
||||
"IDs of already uploaded documents in upload module (no files uploaded directly here)",
|
||||
required: false,
|
||||
})
|
||||
personalDocs?: {
|
||||
nationalCard?: string;
|
||||
selfie?: string;
|
||||
activityCert?: string;
|
||||
};
|
||||
|
||||
@ApiProperty({
|
||||
type: () => ({
|
||||
state: {
|
||||
type: "number",
|
||||
example: 8,
|
||||
description: "state id from /actor/register/form/states/list",
|
||||
},
|
||||
city: {
|
||||
type: "number",
|
||||
example: 101,
|
||||
description: "city id from /actor/register/form/cities/list/:stateId",
|
||||
},
|
||||
expertizedAt: {
|
||||
type: "array",
|
||||
items: { enum: Object.values(ExpertizedAtEnum) },
|
||||
example: [ExpertizedAtEnum.BADANE, ExpertizedAtEnum.FANI],
|
||||
description: "one or more expertized fields",
|
||||
},
|
||||
}),
|
||||
required: false,
|
||||
})
|
||||
workExperience?: {
|
||||
state: number;
|
||||
city: number;
|
||||
expertizedAt: ExpertizedAtEnum[];
|
||||
};
|
||||
|
||||
@ApiProperty({
|
||||
type: () => ({
|
||||
workingYears: {
|
||||
type: "number",
|
||||
example: 5,
|
||||
},
|
||||
casesCount: {
|
||||
type: "number",
|
||||
example: 120,
|
||||
},
|
||||
previousWork: {
|
||||
enum: PreviousWorkEnum,
|
||||
example: PreviousWorkEnum.INSURANCE_COMPANY,
|
||||
},
|
||||
}),
|
||||
required: false,
|
||||
})
|
||||
workRecords?: {
|
||||
workingYears: number;
|
||||
casesCount: number;
|
||||
previousWork: PreviousWorkEnum;
|
||||
};
|
||||
|
||||
@ApiProperty({
|
||||
type: "array",
|
||||
items: {
|
||||
type: "string",
|
||||
enum: Object.values(SkillEnum),
|
||||
},
|
||||
required: false,
|
||||
description: "one or more skills of the damage expert",
|
||||
example: [SkillEnum.SMOOTHING, SkillEnum.SCENE_EXPERT],
|
||||
})
|
||||
skills?: SkillEnum[];
|
||||
|
||||
@ApiProperty({
|
||||
type: () => ({
|
||||
IBAN: {
|
||||
type: "string",
|
||||
example: "IR820540102680020817909002",
|
||||
},
|
||||
cardNum: {
|
||||
type: "string",
|
||||
example: "6037991234567890",
|
||||
},
|
||||
accountNum: {
|
||||
type: "string",
|
||||
example: "0101234567000",
|
||||
},
|
||||
}),
|
||||
required: false,
|
||||
})
|
||||
financialInfo?: {
|
||||
IBAN: string;
|
||||
cardNum: string;
|
||||
accountNum: string;
|
||||
};
|
||||
}
|
||||
|
||||
export class LegalRegisterDto implements RegisterDto {
|
||||
userType: UserType;
|
||||
@ApiProperty()
|
||||
firstName: string;
|
||||
|
||||
@ApiProperty()
|
||||
role: RoleEnum;
|
||||
|
||||
@ApiProperty()
|
||||
lastName: string;
|
||||
|
||||
@ApiProperty()
|
||||
nationalCode: string;
|
||||
|
||||
@ApiProperty()
|
||||
email: string;
|
||||
|
||||
@ApiProperty()
|
||||
password: string;
|
||||
|
||||
@ApiProperty()
|
||||
mobile: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: "64fc4978b74d670939b08920",
|
||||
type: String,
|
||||
description: "insuActivityCo of actor",
|
||||
})
|
||||
insuActivityCo: string;
|
||||
}
|
||||
|
||||
export class InsurerRegisterDto {
|
||||
@ApiProperty()
|
||||
firstName: string;
|
||||
|
||||
@ApiProperty()
|
||||
lastName: string;
|
||||
|
||||
@ApiProperty({ description: "just submit by Email" })
|
||||
@IsEmail()
|
||||
username: string;
|
||||
|
||||
@ApiProperty()
|
||||
password: string;
|
||||
|
||||
@ApiProperty({ example: "{saman : 651abe5814ed4bb6ee20cd81}" })
|
||||
clientKey: Types.ObjectId;
|
||||
}
|
||||
|
||||
export class RegisterDtoRs extends RegisterDto {
|
||||
@ApiProperty({ type: "string", description: "REGISTER_DTO_RS" })
|
||||
message: string;
|
||||
|
||||
constructor(registerData) {
|
||||
super();
|
||||
this.message = registerData.message || "ثبت نام با موفقیت انجام شد.";
|
||||
}
|
||||
}
|
||||
15
src/auth/dto/actor/states.dto.ts
Normal file
15
src/auth/dto/actor/states.dto.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export class StatesDtoRs {
|
||||
name: string;
|
||||
id: string;
|
||||
constructor(states) {
|
||||
this.name = states.name;
|
||||
this.id = states.id;
|
||||
}
|
||||
}
|
||||
|
||||
export class StateListDtoRs {
|
||||
list: StatesDtoRs[];
|
||||
constructor(statesLis: []) {
|
||||
this.list = statesLis.map((s) => new StatesDtoRs(s));
|
||||
}
|
||||
}
|
||||
0
src/auth/dto/user/forget-password.dto.ts
Normal file
0
src/auth/dto/user/forget-password.dto.ts
Normal file
44
src/auth/dto/user/login.dto.ts
Normal file
44
src/auth/dto/user/login.dto.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { UserModel } from "src/users/entities/schema/user.schema";
|
||||
|
||||
export class UserLoginDto {
|
||||
@ApiProperty({
|
||||
example: "09226187419",
|
||||
type: "string",
|
||||
description: "User login dto",
|
||||
})
|
||||
mobile: string;
|
||||
}
|
||||
|
||||
export class LoginDtoRs extends UserModel {
|
||||
message: string;
|
||||
@ApiProperty({
|
||||
example: "09226187419",
|
||||
type: "string",
|
||||
description: "User login dto",
|
||||
})
|
||||
tokens: { token: string; rfToken: string };
|
||||
|
||||
@ApiProperty({
|
||||
example: "09226187419",
|
||||
type: "string",
|
||||
description: "User login dto",
|
||||
})
|
||||
userId: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: "09226187419",
|
||||
type: "string",
|
||||
description: "User login dto",
|
||||
})
|
||||
firstName: string;
|
||||
|
||||
lastName: string;
|
||||
username: string;
|
||||
mobile: string;
|
||||
nationalCode: string;
|
||||
constructor(loginData) {
|
||||
super();
|
||||
this.mobile = loginData.mobile;
|
||||
}
|
||||
}
|
||||
17
src/auth/dto/user/verify.dto.ts
Normal file
17
src/auth/dto/user/verify.dto.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
|
||||
export class UserVerifyOtp {
|
||||
@ApiProperty({
|
||||
example: "09226187419",
|
||||
type: "string",
|
||||
description: "User login dto",
|
||||
})
|
||||
username: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: "258567",
|
||||
type: "string",
|
||||
description: "User login verify dto",
|
||||
})
|
||||
password: string;
|
||||
}
|
||||
63
src/auth/guards/actor-local.guard.ts
Normal file
63
src/auth/guards/actor-local.guard.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import {
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
UnauthorizedException,
|
||||
} from "@nestjs/common";
|
||||
import { JwtService } from "@nestjs/jwt";
|
||||
import { AuthGuard } from "@nestjs/passport";
|
||||
import { ActorAuthService } from "src/auth/auth-services/actor.auth.service";
|
||||
import { RoleEnum } from "src/Types&Enums/role.enum";
|
||||
|
||||
@Injectable()
|
||||
export class LocalActorAuthGuard extends AuthGuard("actor") {
|
||||
constructor(
|
||||
private readonly actorAuthService: ActorAuthService,
|
||||
private readonly jwtService: JwtService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const token = this.extractTokenFromHeader(request);
|
||||
const path = request.url;
|
||||
|
||||
if (!token) {
|
||||
if (path === "/actor/login") {
|
||||
const loginData = await this.actorAuthService.loginActors(request.body);
|
||||
request.user = loginData;
|
||||
request.identity = request;
|
||||
return true;
|
||||
} else {
|
||||
throw new UnauthorizedException("Token not found");
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const payload = await this.jwtService.verifyAsync(token, {
|
||||
secret: `${process.env.SECRET}`,
|
||||
});
|
||||
|
||||
if (
|
||||
![RoleEnum.EXPERT, RoleEnum.DAMAGE_EXPERT, RoleEnum.COMPANY].includes(
|
||||
payload.role,
|
||||
)
|
||||
) {
|
||||
throw new UnauthorizedException("User role is not authorized");
|
||||
}
|
||||
|
||||
request.user = payload;
|
||||
request.identity = request.user;
|
||||
} catch {
|
||||
throw new UnauthorizedException("Invalid token");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private extractTokenFromHeader(request: Request): string | undefined {
|
||||
//@ts-ignore
|
||||
const [type, token] = request.headers.authorization?.split(" ") ?? [];
|
||||
return type === "Bearer" ? token : undefined;
|
||||
}
|
||||
}
|
||||
127
src/auth/guards/claim-access.guard.ts
Normal file
127
src/auth/guards/claim-access.guard.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
UnauthorizedException,
|
||||
ForbiddenException,
|
||||
} from "@nestjs/common";
|
||||
import { JwtService } from "@nestjs/jwt";
|
||||
import { RoleEnum } from "src/Types&Enums/role.enum";
|
||||
import { ClaimRequestManagementDbService } from "src/claim-request-management/entites/db-service/claim-request-management.db.service";
|
||||
import { Types } from "mongoose";
|
||||
|
||||
/**
|
||||
* Guard that allows:
|
||||
* - Users to access their own claim files
|
||||
* - Experts to access IN_PERSON claim files they initiated
|
||||
*/
|
||||
@Injectable()
|
||||
export class ClaimAccessGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly jwtService: JwtService,
|
||||
private readonly claimDbService: ClaimRequestManagementDbService,
|
||||
) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const token = this.extractTokenFromHeader(request);
|
||||
if (!token) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
|
||||
try {
|
||||
const payload = await this.jwtService.verifyAsync(token, {
|
||||
secret: `${process.env.SECRET}`,
|
||||
});
|
||||
|
||||
// Allow users to pass through (they will be checked by service methods)
|
||||
if (payload.role === RoleEnum.USER) {
|
||||
request.user = payload;
|
||||
request.identity = request.user;
|
||||
return true;
|
||||
}
|
||||
|
||||
// For experts, check if they're accessing an IN_PERSON claim file they initiated
|
||||
if (
|
||||
payload.role === RoleEnum.EXPERT ||
|
||||
payload.role === RoleEnum.DAMAGE_EXPERT
|
||||
) {
|
||||
// Extract claimRequestId from route params
|
||||
const claimRequestId =
|
||||
request.params?.claimRequestId ||
|
||||
request.params?.claimRequestID ||
|
||||
request.params?.id;
|
||||
|
||||
if (!claimRequestId) {
|
||||
// If no claim ID in params, allow access (e.g., for listing endpoints)
|
||||
// The service will filter appropriately
|
||||
request.user = payload;
|
||||
request.actor = payload;
|
||||
request.identity = payload;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Verify expert has access to this specific claim file
|
||||
const hasAccess = await this.verifyExpertClaimAccess(
|
||||
claimRequestId,
|
||||
payload.sub,
|
||||
);
|
||||
|
||||
if (!hasAccess) {
|
||||
throw new ForbiddenException(
|
||||
"You can only access IN_PERSON claim files that you initiated",
|
||||
);
|
||||
}
|
||||
|
||||
request.user = payload;
|
||||
request.actor = payload;
|
||||
request.identity = payload;
|
||||
return true;
|
||||
}
|
||||
|
||||
throw new UnauthorizedException("Invalid role");
|
||||
} catch (error) {
|
||||
if (error instanceof ForbiddenException || error instanceof UnauthorizedException) {
|
||||
throw error;
|
||||
}
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
}
|
||||
|
||||
private async verifyExpertClaimAccess(
|
||||
claimRequestId: string,
|
||||
expertId: string,
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const claim = await this.claimDbService.findOne(claimRequestId);
|
||||
if (!claim) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const blameFile = claim.blameFile;
|
||||
if (!blameFile) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if it's an expert-initiated IN_PERSON file
|
||||
if (
|
||||
blameFile.expertInitiated &&
|
||||
blameFile.creationMethod === "IN_PERSON" &&
|
||||
blameFile.initiatedBy
|
||||
) {
|
||||
// Verify the expert is the one who initiated it
|
||||
return String(blameFile.initiatedBy) === expertId;
|
||||
}
|
||||
|
||||
return false;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private extractTokenFromHeader(request: any): string | undefined {
|
||||
const [type, token] = request.headers.authorization?.split(" ") ?? [];
|
||||
return type === "Bearer" ? token : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
44
src/auth/guards/global.guard.ts
Normal file
44
src/auth/guards/global.guard.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
UnauthorizedException,
|
||||
} from "@nestjs/common";
|
||||
import { JwtService } from "@nestjs/jwt";
|
||||
import { Request } from "express";
|
||||
import { RoleEnum } from "src/Types&Enums/role.enum";
|
||||
|
||||
@Injectable()
|
||||
export class GlobalGuard implements CanActivate {
|
||||
constructor(private readonly jwtService: JwtService) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const token = this.extractTokenFromHeader(request);
|
||||
if (!token) {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
try {
|
||||
const payload = await this.jwtService.verifyAsync(token, {
|
||||
secret: `${process.env.SECRET}`,
|
||||
});
|
||||
if (payload.role !== RoleEnum.USER) {
|
||||
console.log(
|
||||
"🚀 ~ GlobalGuard ~ canActivate ~ request.user.role:",
|
||||
request.user.role,
|
||||
);
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
request.user = payload;
|
||||
request.identity = request.user;
|
||||
} catch {
|
||||
throw new UnauthorizedException();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private extractTokenFromHeader(request: Request): string | undefined {
|
||||
const [type, token] = request.headers.authorization?.split(" ") ?? [];
|
||||
return type === "Bearer" ? token : undefined;
|
||||
}
|
||||
}
|
||||
24
src/auth/guards/role.guard.ts
Normal file
24
src/auth/guards/role.guard.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Injectable, CanActivate, ExecutionContext } from "@nestjs/common";
|
||||
import { Reflector } from "@nestjs/core";
|
||||
|
||||
@Injectable()
|
||||
export class RolesGuard implements CanActivate {
|
||||
constructor(private readonly reflector: Reflector) {}
|
||||
canActivate(context: ExecutionContext): boolean {
|
||||
// get the roles required
|
||||
const roles = this.reflector.getAllAndOverride<string[]>("role", [
|
||||
context.getHandler(),
|
||||
context.getClass(),
|
||||
]);
|
||||
if (!roles) {
|
||||
return false;
|
||||
}
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const userRoles = request.user?.role?.split(",");
|
||||
return this.validateRoles(roles, userRoles);
|
||||
}
|
||||
|
||||
validateRoles(roles: string[], userRoles: string[]) {
|
||||
return roles.some((role) => userRoles.includes(role));
|
||||
}
|
||||
}
|
||||
27
src/auth/guards/user-local.guard.ts
Normal file
27
src/auth/guards/user-local.guard.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import {
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
NotAcceptableException,
|
||||
} from "@nestjs/common";
|
||||
import { AuthGuard } from "@nestjs/passport";
|
||||
import { UserAuthService } from "src/auth/auth-services/user.auth.service";
|
||||
|
||||
@Injectable()
|
||||
export class LocalUserAuthGuard extends AuthGuard("local") {
|
||||
constructor(private readonly userAuthService: UserAuthService) {
|
||||
super();
|
||||
}
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const { username, password } = request.body;
|
||||
let isValidUser = await this.userAuthService.validateUser(
|
||||
username,
|
||||
password,
|
||||
);
|
||||
if (!isValidUser) {
|
||||
throw new NotAcceptableException("otp is wrong");
|
||||
}
|
||||
request["user"] = isValidUser;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
22
src/auth/stratregys/local-actor.strategy.ts
Normal file
22
src/auth/stratregys/local-actor.strategy.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Injectable } from "@nestjs/common";
|
||||
import { PassportStrategy } from "@nestjs/passport";
|
||||
import { Strategy } from "passport-local";
|
||||
import { ActorAuthService } from "src/auth/auth-services/actor.auth.service";
|
||||
|
||||
@Injectable()
|
||||
export class LocalActorStrategy extends PassportStrategy(Strategy, "actor") {
|
||||
constructor(private readonly actorAuthService: ActorAuthService) {
|
||||
super();
|
||||
}
|
||||
|
||||
// async validate(username, password): Promise<any> {
|
||||
// const user = await this.actorAuthService.validateActor(
|
||||
// username,
|
||||
// password,
|
||||
// );
|
||||
// if (!user) {
|
||||
// throw new UnauthorizedException("user not found");
|
||||
// }
|
||||
// return user;
|
||||
// }
|
||||
}
|
||||
19
src/auth/stratregys/local.strategy.ts
Normal file
19
src/auth/stratregys/local.strategy.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Injectable, UnauthorizedException } from "@nestjs/common";
|
||||
import { PassportStrategy } from "@nestjs/passport";
|
||||
import { Strategy } from "passport-local";
|
||||
import { UserAuthService } from "src/auth/auth-services/user.auth.service";
|
||||
|
||||
@Injectable()
|
||||
export class LocalStrategy extends PassportStrategy(Strategy) {
|
||||
constructor(private readonly userAuthService: UserAuthService) {
|
||||
super();
|
||||
}
|
||||
|
||||
async validate(username: string, password: string): Promise<any> {
|
||||
const user = await this.userAuthService.validateUser(username, password);
|
||||
if (!user) {
|
||||
throw new UnauthorizedException("user not found please register");
|
||||
}
|
||||
return user;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user