1
0
forked from Yara724/api

Fixed Lock for Field expert + user view of files

This commit is contained in:
2026-06-18 13:31:56 +03:30
parent 6df8044c5a
commit d8f7766f10
8 changed files with 328 additions and 234 deletions

View File

@@ -91,6 +91,12 @@ import {
MUTUAL_AGREEMENT_EXPERT_DECISION_FIELDS,
} from "src/helpers/blame-party-agreement-decision";
import { buildFileLink } from "src/helpers/urlCreator";
import {
buildUserLookupByPhone,
iranMobileLookupVariants,
normalizeIranMobile,
partyPersonMatchesUser,
} from "src/helpers/iran-mobile";
@Injectable()
export class RequestManagementService {
@@ -209,14 +215,8 @@ export class RequestManagementService {
private assertPartyOwner(party: any, user: any, errMsg: string, req?: any) {
// The initiating field-expert/registrar fills steps on behalf of parties.
if (req && this.isBlameOnBehalfActor(req, user)) return;
const partyUserId = party?.person?.userId
? String(party.person.userId)
: null;
const ok =
(partyUserId && partyUserId === String(user?.sub)) ||
(party?.person?.phoneNumber &&
party.person.phoneNumber === user?.username);
if (!ok) throw new ForbiddenException(errMsg);
if (partyPersonMatchesUser(party?.person, user)) return;
throw new ForbiddenException(errMsg);
}
/**
@@ -4434,7 +4434,8 @@ export class RequestManagementService {
throw new BadRequestException("First party not found on request");
const userId = await this.getOrCreateUserByPhoneNumber(phone);
if (!req.parties[firstIdx].person) req.parties[firstIdx].person = {} as any;
req.parties[firstIdx].person.phoneNumber = phone;
req.parties[firstIdx].person.phoneNumber =
normalizeIranMobile(phone) ?? phone;
req.parties[firstIdx].person.userId = userId;
const expertName = `${expert?.lastName || ""}`.trim() || "کارشناس";
@@ -4591,7 +4592,9 @@ export class RequestManagementService {
throw new BadRequestException("First party not found on request");
if (!req.parties[firstIdx].person) req.parties[firstIdx].person = {} as any;
req.parties[firstIdx].person.userId = firstUserId;
req.parties[firstIdx].person.phoneNumber = dto.firstPartyPhoneNumber;
req.parties[firstIdx].person.phoneNumber =
normalizeIranMobile(dto.firstPartyPhoneNumber) ??
dto.firstPartyPhoneNumber;
if (
req.type === BlameRequestType.THIRD_PARTY &&
@@ -4617,7 +4620,9 @@ export class RequestManagementService {
if (!req.parties[secondIdx].person)
req.parties[secondIdx].person = {} as any;
req.parties[secondIdx].person.userId = secondUserId;
req.parties[secondIdx].person.phoneNumber = dto.secondPartyPhoneNumber;
req.parties[secondIdx].person.phoneNumber =
normalizeIranMobile(dto.secondPartyPhoneNumber) ??
dto.secondPartyPhoneNumber;
}
} else if (
req.type === BlameRequestType.THIRD_PARTY &&
@@ -4794,6 +4799,7 @@ export class RequestManagementService {
const valid = await this.hashService.compare(otp, u.otp);
if (!valid) throw new BadRequestException(`Invalid OTP for ${phone}`);
const userId = u._id as Types.ObjectId;
const canonicalPhone = normalizeIranMobile(phone) ?? phone;
if (role === PartyRole.FIRST) {
if (firstIdx === -1)
@@ -4801,13 +4807,21 @@ export class RequestManagementService {
if (!req.parties[firstIdx].person)
req.parties[firstIdx].person = {} as any;
req.parties[firstIdx].person.userId = userId;
req.parties[firstIdx].person.phoneNumber = phone;
req.parties[firstIdx].person.phoneNumber = canonicalPhone;
} else {
const firstPhone =
firstIdx !== -1
? (req.parties[firstIdx]?.person as any)?.phoneNumber
: undefined;
if (firstPhone && firstPhone === phone) {
const firstVariants = firstPhone
? iranMobileLookupVariants(firstPhone)
: [];
if (
firstPhone &&
(firstPhone === phone ||
firstVariants.includes(phone) ||
firstVariants.includes(canonicalPhone))
) {
throw new BadRequestException(
"Second party phone number cannot be the same as first party.",
);
@@ -4816,13 +4830,13 @@ export class RequestManagementService {
if (secondIdx === -1) {
req.parties.push({
role: PartyRole.SECOND,
person: { userId, phoneNumber: phone },
person: { userId, phoneNumber: canonicalPhone },
} as any);
} else {
if (!req.parties[secondIdx].person)
req.parties[secondIdx].person = {} as any;
req.parties[secondIdx].person.userId = userId;
req.parties[secondIdx].person.phoneNumber = phone;
req.parties[secondIdx].person.phoneNumber = canonicalPhone;
}
}
@@ -5018,9 +5032,16 @@ export class RequestManagementService {
user?.sub && Types.ObjectId.isValid(user.sub)
? { "parties.person.userId": new Types.ObjectId(user.sub) }
: null;
const phoneFilter = user?.username
? { "parties.person.phoneNumber": user.username }
: null;
const phoneVariants =
user?.role === RoleEnum.USER && user?.username
? iranMobileLookupVariants(user.username)
: user?.username
? [user.username]
: [];
const phoneFilter =
phoneVariants.length > 0
? { "parties.person.phoneNumber": { $in: phoneVariants } }
: null;
const orConditions: Record<string, unknown>[] = [];
if (userIdFilter) orConditions.push(userIdFilter);
@@ -5061,11 +5082,8 @@ export class RequestManagementService {
req.registrarInitiated &&
String(req.initiatedByRegistrarId) === String(user.sub));
const party = req.parties?.find(
(p: any) =>
(p?.person?.userId &&
String(p.person.userId) === String(user.sub)) ||
(p?.person?.phoneNumber && p.person.phoneNumber === user?.username),
const party = req.parties?.find((p: any) =>
partyPersonMatchesUser(p?.person, user),
);
const obj = req.toObject();
@@ -5340,14 +5358,7 @@ export class RequestManagementService {
String(req.initiatedByRegistrarId) === String(user?.sub);
const isParty =
Array.isArray(req.parties) &&
req.parties.some((p: any) => {
const pid = p?.person?.userId ? String(p.person.userId) : null;
const phone = p?.person?.phoneNumber;
return (
(pid && pid === String(user?.sub)) ||
(phone && phone === user?.username)
);
});
req.parties.some((p: any) => partyPersonMatchesUser(p?.person, user));
if (!isFieldExpertOwner && !isRegistrarOwner && !isParty) {
throw new ForbiddenException("You do not have access to this request");
}
@@ -5359,10 +5370,22 @@ export class RequestManagementService {
Types.ObjectId.isValid(user.sub)
) {
let updated = false;
const phoneVariants = iranMobileLookupVariants(user?.username);
for (const p of req.parties || []) {
if (p?.person?.phoneNumber === user?.username && !p.person?.userId) {
const person = p?.person;
if (!person) continue;
const phoneMatch =
person.phoneNumber &&
phoneVariants.some(
(v) => v === person.phoneNumber || v === normalizeIranMobile(person.phoneNumber),
);
if (phoneMatch && !person?.userId) {
p.person = p.person || {};
(p.person as any).userId = new Types.ObjectId(user.sub);
if (user?.username) {
(p.person as any).phoneNumber =
normalizeIranMobile(user.username) ?? user.username;
}
updated = true;
}
}
@@ -5384,14 +5407,9 @@ export class RequestManagementService {
const visibleParties =
isFieldExpertOwner || isRegistrarOwner
? allParties
: allParties.filter((p: any) => {
const pid = p?.person?.userId ? String(p.person.userId) : null;
const phone = p?.person?.phoneNumber;
return (
(pid && pid === String(user?.sub)) ||
(phone && phone === user?.username)
);
});
: allParties.filter((p: any) =>
partyPersonMatchesUser(p?.person, user),
);
const parties = await Promise.all(
visibleParties.map((p: any) => this.sanitizePartyForBlameUserView(p)),
);
@@ -7288,19 +7306,18 @@ export class RequestManagementService {
private async getOrCreateUserByPhoneNumber(
phoneNumber: string,
): Promise<Types.ObjectId> {
// Try to find existing user by phone number (username or mobile)
let user = await this.userDbService.findOne({
$or: [{ username: phoneNumber }, { mobile: phoneNumber }],
});
const canonical = normalizeIranMobile(phoneNumber) ?? phoneNumber.trim();
let user = await this.userDbService.findOne(
buildUserLookupByPhone(canonical),
);
if (user) {
return new Types.ObjectId((user as any)._id);
}
// User doesn't exist, create a new one
const newUser = await this.userDbService.createUser({
mobile: phoneNumber,
username: phoneNumber,
mobile: canonical,
username: canonical,
otp: "",
tokens: {
token: "",