forked from Yara724/api
Initial commit after migration to gitea
This commit is contained in:
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,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user