forked from Yara724/api
Fixed resendCarParts label_fa's + OTP
This commit is contained in:
@@ -5,7 +5,9 @@ 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";
|
||||
@@ -20,6 +22,8 @@ export class UserLinkAccessService {
|
||||
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,
|
||||
) {}
|
||||
|
||||
@@ -39,10 +43,12 @@ export class UserLinkAccessService {
|
||||
throwUserAuthError(UserAuthErrorCode.LINK_NOT_FOUND);
|
||||
}
|
||||
|
||||
const normalizedMobile = this.normalizePhone(params.mobile);
|
||||
const normalizedMobile = normalizeIranMobile(params.mobile);
|
||||
if (
|
||||
!normalizedMobile ||
|
||||
!allowedMobiles.some((mobile) => this.normalizePhone(mobile) === normalizedMobile)
|
||||
!allowedMobiles.some(
|
||||
(mobile) => normalizeIranMobile(mobile) === normalizedMobile,
|
||||
)
|
||||
) {
|
||||
throwUserAuthError(UserAuthErrorCode.LINK_MOBILE_MISMATCH);
|
||||
}
|
||||
@@ -58,15 +64,18 @@ export class UserLinkAccessService {
|
||||
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(),
|
||||
]);
|
||||
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.addClaimOwnerPhone(allowedMobiles, claimRequest);
|
||||
await this.addLegacyClaimOwnerPhone(allowedMobiles, legacyClaim);
|
||||
await this.addClaimCaseOwnerPhone(allowedMobiles, claimCase);
|
||||
|
||||
return Array.from(allowedMobiles);
|
||||
}
|
||||
@@ -119,7 +128,7 @@ export class UserLinkAccessService {
|
||||
}
|
||||
}
|
||||
|
||||
private async addClaimOwnerPhone(
|
||||
private async addLegacyClaimOwnerPhone(
|
||||
allowedMobiles: Set<string>,
|
||||
claimRequest: any,
|
||||
) {
|
||||
@@ -146,29 +155,51 @@ export class UserLinkAccessService {
|
||||
_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 = this.normalizePhone(phone);
|
||||
const normalized = normalizeIranMobile(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();
|
||||
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 {
|
||||
|
||||
@@ -7,6 +7,15 @@ import {
|
||||
} from "src/auth/auth-services/user-auth-error";
|
||||
import { UserLinkAccessService } from "src/auth/auth-services/user-link-access.service";
|
||||
import { LoginDtoRs } from "src/auth/dto/user/login.dto";
|
||||
import {
|
||||
buildUserLookupByPhone,
|
||||
normalizeIranMobile,
|
||||
} from "src/helpers/iran-mobile";
|
||||
import {
|
||||
computeOtpExpireMs,
|
||||
isOtpExpiryActive,
|
||||
readOtpExpireMinutesFromEnv,
|
||||
} from "src/helpers/user-otp-expiry";
|
||||
import { OtpGeneratorService } from "src/sms-orchestration/otp-generator.service";
|
||||
import { UserDbService } from "src/users/entities/db-service/user.db.service";
|
||||
import { SmsOrchestrationService } from "src/sms-orchestration/sms-orchestration.service";
|
||||
@@ -17,7 +26,6 @@ export interface LinkBinding {
|
||||
linkContext?: string;
|
||||
}
|
||||
|
||||
// TODO FIX REGISTER TO USER.SERVICE AND AUTH IN THIS MODULE
|
||||
@Injectable()
|
||||
export class UserAuthService {
|
||||
private readonly logger = new Logger(UserAuthService.name);
|
||||
@@ -36,18 +44,21 @@ export class UserAuthService {
|
||||
pass: string,
|
||||
binding: LinkBinding = {},
|
||||
): Promise<any> {
|
||||
const canonicalMobile = normalizeIranMobile(username) ?? username.trim();
|
||||
|
||||
await this.userLinkAccessService.assertMobileAllowed({
|
||||
mobile: username,
|
||||
mobile: canonicalMobile,
|
||||
linkToken: binding.linkToken,
|
||||
linkContext: binding.linkContext,
|
||||
});
|
||||
|
||||
const user = await this.userDbService.findOne({ username });
|
||||
const user = await this.userDbService.findOne(
|
||||
buildUserLookupByPhone(canonicalMobile),
|
||||
);
|
||||
if (!user) throwUserAuthError(UserAuthErrorCode.USER_NOT_FOUND);
|
||||
|
||||
const now = new Date().getTime();
|
||||
if (user.otp == null) throwUserAuthError(UserAuthErrorCode.OTP_REQUIRED);
|
||||
if (user.otpExpire < now) {
|
||||
if (!isOtpExpiryActive(user.otpExpire)) {
|
||||
throwUserAuthError(UserAuthErrorCode.OTP_EXPIRED);
|
||||
}
|
||||
if (await this.hashService.compare(pass, user.otp)) {
|
||||
@@ -57,9 +68,10 @@ export class UserAuthService {
|
||||
}
|
||||
|
||||
async login(user: any) {
|
||||
const userId = String(user._id ?? user.id ?? "");
|
||||
const payload = {
|
||||
username: user.username,
|
||||
sub: user.id,
|
||||
sub: userId,
|
||||
role: "user",
|
||||
};
|
||||
const accToken = this.jwtService.sign(payload, {
|
||||
@@ -70,10 +82,11 @@ export class UserAuthService {
|
||||
{
|
||||
tokens: { token: accToken },
|
||||
otp: null,
|
||||
otpExpire: 0,
|
||||
},
|
||||
);
|
||||
return {
|
||||
userId: user._id,
|
||||
userId,
|
||||
access_token: accToken,
|
||||
};
|
||||
}
|
||||
@@ -82,29 +95,31 @@ export class UserAuthService {
|
||||
mobile: string,
|
||||
binding: LinkBinding = {},
|
||||
): Promise<LoginDtoRs> {
|
||||
const canonicalMobile = normalizeIranMobile(mobile) ?? mobile.trim();
|
||||
if (!canonicalMobile) {
|
||||
throwUserAuthError(UserAuthErrorCode.USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
await this.userLinkAccessService.assertMobileAllowed({
|
||||
mobile,
|
||||
mobile: canonicalMobile,
|
||||
linkToken: binding.linkToken,
|
||||
linkContext: binding.linkContext,
|
||||
});
|
||||
|
||||
const userExist = await this.userDbService.findOne({
|
||||
mobile,
|
||||
});
|
||||
const userExist = await this.userDbService.findOne(
|
||||
buildUserLookupByPhone(canonicalMobile),
|
||||
);
|
||||
const otp = this.otpCreator.create();
|
||||
const hashOtp = await this.hashService.hash(otp);
|
||||
const rawExpireMinutes = Number(process.env.EXP_OTP_TIME ?? "2");
|
||||
const expireMinutes =
|
||||
Number.isFinite(rawExpireMinutes) && rawExpireMinutes > 0
|
||||
? rawExpireMinutes
|
||||
: 2;
|
||||
const otpExpire = Date.now() + expireMinutes * 60 * 1000;
|
||||
const expireMinutes = readOtpExpireMinutesFromEnv();
|
||||
const nowMs = Date.now();
|
||||
const otpExpire = computeOtpExpireMs(expireMinutes, nowMs);
|
||||
|
||||
if (!userExist) {
|
||||
await this.smsSender(otp, mobile);
|
||||
/// create otp request
|
||||
await this.smsSender(otp, canonicalMobile);
|
||||
const newUser = await this.userDbService.createUser({
|
||||
mobile,
|
||||
username: mobile,
|
||||
mobile: canonicalMobile,
|
||||
username: canonicalMobile,
|
||||
otp: hashOtp,
|
||||
tokens: {
|
||||
token: "",
|
||||
@@ -122,22 +137,22 @@ export class UserAuthService {
|
||||
});
|
||||
return new LoginDtoRs(newUser);
|
||||
}
|
||||
if (userExist) {
|
||||
if (userExist.otpExpire > new Date(new Date().getTime()).getTime()) {
|
||||
throwUserAuthError(UserAuthErrorCode.OTP_REQUEST_TOO_SOON);
|
||||
}
|
||||
await this.smsSender(otp, mobile);
|
||||
const updateTokens = await this.userDbService.findOneAndUpdate(
|
||||
{
|
||||
username: userExist.username,
|
||||
},
|
||||
{
|
||||
otp: hashOtp,
|
||||
otpExpire,
|
||||
},
|
||||
);
|
||||
if (updateTokens) return new LoginDtoRs(userExist);
|
||||
|
||||
if (isOtpExpiryActive(userExist.otpExpire, nowMs)) {
|
||||
throwUserAuthError(UserAuthErrorCode.OTP_REQUEST_TOO_SOON);
|
||||
}
|
||||
|
||||
await this.smsSender(otp, canonicalMobile);
|
||||
await this.userDbService.findOneAndUpdate(
|
||||
buildUserLookupByPhone(canonicalMobile),
|
||||
{
|
||||
otp: hashOtp,
|
||||
otpExpire,
|
||||
mobile: canonicalMobile,
|
||||
username: userExist.username || canonicalMobile,
|
||||
},
|
||||
);
|
||||
return new LoginDtoRs(userExist);
|
||||
}
|
||||
|
||||
private async smsSender(otp: string, mobile: string) {
|
||||
|
||||
@@ -9,6 +9,10 @@ import { UserAuthController } from "src/auth/auth-controllers/user/user.auth.con
|
||||
import { ActorAuthService } from "src/auth/auth-services/actor.auth.service";
|
||||
import { UserAuthService } from "src/auth/auth-services/user.auth.service";
|
||||
import { UserLinkAccessService } from "src/auth/auth-services/user-link-access.service";
|
||||
import {
|
||||
ClaimCase,
|
||||
ClaimCaseSchema,
|
||||
} from "src/claim-request-management/entites/schema/claim-cases.schema";
|
||||
import {
|
||||
ClaimRequestManagementModel,
|
||||
ClaimRequestManagementSchema,
|
||||
@@ -44,6 +48,7 @@ import { CaptchaModule } from "src/captcha/captcha.module";
|
||||
name: ClaimRequestManagementModel.name,
|
||||
schema: ClaimRequestManagementSchema,
|
||||
},
|
||||
{ name: ClaimCase.name, schema: ClaimCaseSchema },
|
||||
]),
|
||||
JwtModule.register({
|
||||
signOptions: { expiresIn: "1h" },
|
||||
|
||||
Reference in New Issue
Block a user