forked from Yara724/api
219 lines
7.3 KiB
TypeScript
219 lines
7.3 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 { ClaimCase } from "src/claim-request-management/entites/schema/claim-cases.schema";
|
|
import { ClaimRequestManagementModel } from "src/claim-request-management/entites/schema/claim-request-management.schema";
|
|
import { normalizeIranMobile } from "src/helpers/iran-mobile";
|
|
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>,
|
|
@InjectModel(ClaimCase.name)
|
|
private readonly claimCaseModel: Model<ClaimCase>,
|
|
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 = normalizeIranMobile(params.mobile);
|
|
if (
|
|
!normalizedMobile ||
|
|
!allowedMobiles.some(
|
|
(mobile) => normalizeIranMobile(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, legacyClaim, claimCase] =
|
|
await Promise.all([
|
|
this.requestManagementModel.findById(id).lean().exec(),
|
|
this.blameRequestModel.findById(id).lean().exec(),
|
|
this.claimRequestManagementModel.findById(id).lean().exec(),
|
|
this.claimCaseModel.findById(id).lean().exec(),
|
|
]);
|
|
|
|
this.addLegacyRequestPhones(allowedMobiles, legacyRequest, context);
|
|
this.addBlameRequestPhones(allowedMobiles, blameRequest, context);
|
|
await this.addLegacyClaimOwnerPhone(allowedMobiles, legacyClaim);
|
|
await this.addClaimCaseOwnerPhone(allowedMobiles, claimCase);
|
|
|
|
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 addLegacyClaimOwnerPhone(
|
|
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);
|
|
this.addPhone(allowedMobiles, user?.username);
|
|
}
|
|
}
|
|
|
|
/** V2 `claimCases` — token in `/caseClaim?token=...` SMS links. */
|
|
private async addClaimCaseOwnerPhone(
|
|
allowedMobiles: Set<string>,
|
|
claimCase: any,
|
|
) {
|
|
if (!claimCase?.owner?.userId) return;
|
|
|
|
const ownerUserIdText = String(claimCase.owner.userId);
|
|
if (claimCase.blameRequestId) {
|
|
const blameRequest = await this.blameRequestModel
|
|
.findById(claimCase.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);
|
|
this.addPhone(allowedMobiles, user?.username);
|
|
}
|
|
}
|
|
|
|
private addPhone(allowedMobiles: Set<string>, phone?: string) {
|
|
const normalized = normalizeIranMobile(phone);
|
|
if (normalized) allowedMobiles.add(normalized);
|
|
}
|
|
|
|
private normalizeContext(linkContext?: string): string | undefined {
|
|
const ctx = linkContext?.trim().toUpperCase();
|
|
if (!ctx) return undefined;
|
|
if (ctx === "USER" || ctx === "USER1") return "FIRST";
|
|
if (ctx === "USER2") return "SECOND";
|
|
if (ctx === "CASECLAIM" || ctx === "CLAIM") return undefined;
|
|
return ctx;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|