Files
yara724api/src/auth/auth-services/actor.auth.service.ts

550 lines
17 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 { FieldExpertDbService } from "src/users/entities/db-service/field-expert.db.service";
import { RegistrarDbService } from "src/users/entities/db-service/registrar.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 fieldExpertDbService: FieldExpertDbService,
private readonly registrarDbService: RegistrarDbService,
private readonly insurerExpertDbService: InsurerExpertDbService,
// private readonly mailService: MailService, // Mailer disabled not used
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.FIELD_EXPERT:
if (username == null && userId)
res = await this.fieldExpertDbService.findOne({
_id: new Types.ObjectId(userId),
});
else
res = await this.fieldExpertDbService.findOne({ email: username });
break;
case RoleEnum.REGISTRAR:
if (username == null && userId)
res = await this.registrarDbService.findOne({
_id: new Types.ObjectId(userId),
});
else res = await this.registrarDbService.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,
phone: body.phone,
mobile:body.mobile,
city: body.city,
state: body.state,
address: body.address,
};
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) {
actor = await this.fieldExpertDbService.findOne(filter);
dbServiceToUpdate = this.fieldExpertDbService as any;
}
if (!actor) {
actor = await this.registrarDbService.findOne(filter);
dbServiceToUpdate = this.registrarDbService as any;
}
if (!actor) {
throw new NotFoundException("actor not found");
}
const otp = this.otpService.create();
// Mailer disabled not used
// const sendEmail = await this.mailService.sendMail(normalizedEmail, otp);
// if (sendEmail) {
const hashOtp = await this.hashService.hash(otp);
await dbServiceToUpdate.findOneAndUpdate(filter, { otp: hashOtp });
return {
message: "OTP generated (mail sending disabled)",
code: 200,
emailSent: false,
};
// } else {
// throw new BadGatewayException("mailServer in unavailable");
// }
}
async forgetPasswordVerify(email, otp, newPassword) {
let userExist: any = await this.expertDbService.findOne({ email });
let dbServiceToUpdate: any = this.expertDbService;
if (!userExist) {
userExist = await this.damageExpertDbService.findOne({ email });
dbServiceToUpdate = this.damageExpertDbService;
}
if (!userExist) {
userExist = await this.insurerExpertDbService.findOne({ email });
dbServiceToUpdate = this.insurerExpertDbService;
}
if (!userExist) {
userExist = await this.fieldExpertDbService.findOne({ email });
dbServiceToUpdate = this.fieldExpertDbService;
}
if (!userExist) {
userExist = await this.registrarDbService.findOne({ email });
dbServiceToUpdate = this.registrarDbService as any;
}
if (!userExist) throw new NotFoundException("user not found");
const decodeOtp = await this.hashService.compare(otp, userExist.otp);
if (!decodeOtp) throw new UnauthorizedException("otp invalid");
const hashNewPassword = await this.hashService.hash(newPassword);
await dbServiceToUpdate.findOneAndUpdate(
{ email },
{ password: hashNewPassword },
);
return new ForgetPasswordVerifyCodeDtoRs(userExist, "update password");
}
async getProfiles(currentUser) {
const userDetail = await this.dynamicDbController(
currentUser.role,
currentUser.role === "company" ? currentUser.username : 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",
"firstName",
"lastName",
"email",
"phone",
"companyName",
"city",
"state",
"bio",
"nationalCode",
"birthDate",
"avatarUrl",
"address",
],
company: [
"firstName",
"lastName",
"phone",
"mobile",
"city",
"state",
"address",
],
field_expert: [
"firstName",
"lastName",
"email",
"phone",
"mobile",
],
registrar: ["email"],
};
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, currentUser.role === "company" ? currentUser.username : null, userId);
if (!document) throw new NotFoundException("Profile not found");
try {
// if its 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;
case "company":
await this.insurerExpertDbService.updateOne(
{ _id: new Types.ObjectId(userId) },
{ $set: sanitized },
);
break;
case "field_expert":
await this.fieldExpertDbService.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");
}
}
/** Mock endpoint: create a field expert for testing. Make private later. */
async createFieldExpertMock(body: {
email: string;
password: string;
firstName: string;
lastName: string;
mobile: string;
phone?: string;
}) {
const hashPassword = await this.hashService.hash(body.password);
const payload = {
email: body.email.toLowerCase().trim(),
password: hashPassword,
firstName: body.firstName,
lastName: body.lastName,
mobile: body.mobile,
phone: body.phone,
role: RoleEnum.FIELD_EXPERT,
};
try {
const created = await this.fieldExpertDbService.create(payload);
return {
id: created._id,
email: created.email,
firstName: created.firstName,
lastName: created.lastName,
mobile: created.mobile,
role: created.role,
};
} catch (er) {
if (er.code === 11000) {
throw new BadRequestException(
"A field expert with this email already exists.",
);
}
throw er;
}
}
async createRegistrarMock(body: {
email: string;
password: string;
clientId: string;
}) {
const hashPassword = await this.hashService.hash(body.password);
const payload = {
email: body.email.toLowerCase().trim(),
password: hashPassword,
clientKey: new Types.ObjectId(body.clientId),
role: RoleEnum.REGISTRAR,
};
try {
const created = await this.registrarDbService.create(payload as any);
return {
id: (created as any)._id,
email: (created as any).email,
clientKey: (created as any).clientKey,
role: (created as any).role,
};
} catch (er) {
if (er.code === 11000) {
throw new BadRequestException(
"A registrar with this email already exists.",
);
}
throw er;
}
}
}