forked from Yara724/api
188 lines
6.1 KiB
TypeScript
188 lines
6.1 KiB
TypeScript
import { Injectable } from "@nestjs/common";
|
|
import { InjectModel } from "@nestjs/mongoose";
|
|
import { Model, Types } from "mongoose";
|
|
import {
|
|
UserAuthErrorCode,
|
|
throwUserAuthError,
|
|
} from "src/auth/auth-services/user-auth-error";
|
|
import { ClaimRequestManagementModel } from "src/claim-request-management/entites/schema/claim-request-management.schema";
|
|
import { BlameRequest } from "src/request-management/entities/schema/blame-cases.schema";
|
|
import { PartyRole } from "src/request-management/entities/schema/partyRole.enum";
|
|
import { RequestManagementModel } from "src/request-management/entities/schema/request-management.schema";
|
|
import { UserDbService } from "src/users/entities/db-service/user.db.service";
|
|
|
|
@Injectable()
|
|
export class UserLinkAccessService {
|
|
constructor(
|
|
@InjectModel(RequestManagementModel.name)
|
|
private readonly requestManagementModel: Model<RequestManagementModel>,
|
|
@InjectModel(BlameRequest.name)
|
|
private readonly blameRequestModel: Model<BlameRequest>,
|
|
@InjectModel(ClaimRequestManagementModel.name)
|
|
private readonly claimRequestManagementModel: Model<ClaimRequestManagementModel>,
|
|
private readonly userDbService: UserDbService,
|
|
) {}
|
|
|
|
async assertMobileAllowed(params: {
|
|
mobile: string;
|
|
linkToken?: string;
|
|
linkContext?: string;
|
|
}): Promise<void> {
|
|
const linkToken = params.linkToken?.trim();
|
|
if (!linkToken) return;
|
|
|
|
const allowedMobiles = await this.resolveAllowedMobiles(
|
|
linkToken,
|
|
params.linkContext,
|
|
);
|
|
if (allowedMobiles.length === 0) {
|
|
throwUserAuthError(UserAuthErrorCode.LINK_NOT_FOUND);
|
|
}
|
|
|
|
const normalizedMobile = this.normalizePhone(params.mobile);
|
|
if (
|
|
!normalizedMobile ||
|
|
!allowedMobiles.some((mobile) => this.normalizePhone(mobile) === normalizedMobile)
|
|
) {
|
|
throwUserAuthError(UserAuthErrorCode.LINK_MOBILE_MISMATCH);
|
|
}
|
|
}
|
|
|
|
async resolveAllowedMobiles(
|
|
linkToken: string,
|
|
linkContext?: string,
|
|
): Promise<string[]> {
|
|
if (!Types.ObjectId.isValid(linkToken)) return [];
|
|
|
|
const id = new Types.ObjectId(linkToken);
|
|
const context = this.normalizeContext(linkContext);
|
|
const allowedMobiles = new Set<string>();
|
|
|
|
const [legacyRequest, blameRequest, claimRequest] = await Promise.all([
|
|
this.requestManagementModel.findById(id).lean().exec(),
|
|
this.blameRequestModel.findById(id).lean().exec(),
|
|
this.claimRequestManagementModel.findById(id).lean().exec(),
|
|
]);
|
|
|
|
this.addLegacyRequestPhones(allowedMobiles, legacyRequest, context);
|
|
this.addBlameRequestPhones(allowedMobiles, blameRequest, context);
|
|
await this.addClaimOwnerPhone(allowedMobiles, claimRequest);
|
|
|
|
return Array.from(allowedMobiles);
|
|
}
|
|
|
|
private addLegacyRequestPhones(
|
|
allowedMobiles: Set<string>,
|
|
legacyRequest: any,
|
|
context?: string,
|
|
) {
|
|
if (!legacyRequest) return;
|
|
|
|
const shouldAddFirst = !context || this.isFirstContext(context);
|
|
const shouldAddSecond = !context || this.isSecondContext(context);
|
|
|
|
if (shouldAddFirst) {
|
|
this.addPhone(
|
|
allowedMobiles,
|
|
legacyRequest.firstPartyDetails?.firstPartyPhoneNumber,
|
|
);
|
|
}
|
|
|
|
if (shouldAddSecond) {
|
|
this.addPhone(
|
|
allowedMobiles,
|
|
legacyRequest.secondPartyDetails?.secondPartyPhoneNumber,
|
|
);
|
|
}
|
|
|
|
for (const event of legacyRequest.history || []) {
|
|
const metadata = event?.metadata;
|
|
if (shouldAddSecond) this.addPhone(allowedMobiles, metadata?.secondPartyPhone);
|
|
for (const sent of metadata?.sentTo || []) {
|
|
if (!context || this.matchesRoleContext(context, sent?.role)) {
|
|
this.addPhone(allowedMobiles, sent?.phoneNumber);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private addBlameRequestPhones(
|
|
allowedMobiles: Set<string>,
|
|
blameRequest: any,
|
|
context?: string,
|
|
) {
|
|
if (!blameRequest) return;
|
|
|
|
for (const party of blameRequest.parties || []) {
|
|
if (context && !this.matchesRoleContext(context, party?.role)) continue;
|
|
this.addPhone(allowedMobiles, party?.person?.phoneNumber);
|
|
}
|
|
}
|
|
|
|
private async addClaimOwnerPhone(
|
|
allowedMobiles: Set<string>,
|
|
claimRequest: any,
|
|
) {
|
|
if (!claimRequest) return;
|
|
|
|
const ownerUserId = claimRequest.owner?.userId || claimRequest.userId;
|
|
if (!ownerUserId) return;
|
|
|
|
const ownerUserIdText = String(ownerUserId);
|
|
if (claimRequest.blameRequestId) {
|
|
const blameRequest = await this.blameRequestModel
|
|
.findById(claimRequest.blameRequestId)
|
|
.lean()
|
|
.exec();
|
|
const ownerParty = (blameRequest?.parties || []).find(
|
|
(party: any) =>
|
|
party?.person?.userId && String(party.person.userId) === ownerUserIdText,
|
|
);
|
|
this.addPhone(allowedMobiles, ownerParty?.person?.phoneNumber);
|
|
}
|
|
|
|
if (Types.ObjectId.isValid(ownerUserIdText)) {
|
|
const user = await this.userDbService.findOne({
|
|
_id: new Types.ObjectId(ownerUserIdText),
|
|
});
|
|
this.addPhone(allowedMobiles, user?.mobile);
|
|
}
|
|
}
|
|
|
|
private addPhone(allowedMobiles: Set<string>, phone?: string) {
|
|
const normalized = this.normalizePhone(phone);
|
|
if (normalized) allowedMobiles.add(normalized);
|
|
}
|
|
|
|
private normalizePhone(phone?: string): string | undefined {
|
|
if (!phone) return undefined;
|
|
|
|
const digits = String(phone).replace(/\D/g, "");
|
|
if (!digits) return undefined;
|
|
if (digits.startsWith("0098")) return `0${digits.slice(4)}`;
|
|
if (digits.startsWith("98") && digits.length === 12) {
|
|
return `0${digits.slice(2)}`;
|
|
}
|
|
if (digits.length === 10 && digits.startsWith("9")) return `0${digits}`;
|
|
return digits;
|
|
}
|
|
|
|
private normalizeContext(linkContext?: string): string | undefined {
|
|
return linkContext?.trim().toUpperCase();
|
|
}
|
|
|
|
private matchesRoleContext(context: string, role?: string): boolean {
|
|
if (this.isFirstContext(context)) return role === PartyRole.FIRST;
|
|
if (this.isSecondContext(context)) return role === PartyRole.SECOND;
|
|
return true;
|
|
}
|
|
|
|
private isFirstContext(context: string): boolean {
|
|
return ["FIRST", "USER", "USER1", "FIRST_PARTY"].includes(context);
|
|
}
|
|
|
|
private isSecondContext(context: string): boolean {
|
|
return ["SECOND", "USER2", "SECOND_PARTY"].includes(context);
|
|
}
|
|
}
|