Prefer SMS for auth OTP when email mailer is not configured.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -109,8 +109,11 @@ export class AuthService {
|
||||
|
||||
await this.throttleChallenges('register', email, mobile);
|
||||
|
||||
const emailCode = generateOtp();
|
||||
const returnOtp = this.shouldReturnOtp();
|
||||
const emailOtpEnabled = this.isEmailOtpConfigured();
|
||||
const mobileCode = generateOtp();
|
||||
// تا وقتی سرویس ایمیل نداریم، همان کد پیامک برای تأیید ایمیل هم پذیرفته میشود
|
||||
const emailCode = emailOtpEnabled ? generateOtp() : mobileCode;
|
||||
const passwordHash = await bcrypt.hash(dto.password, 10);
|
||||
const payload: RegisterPayload = {
|
||||
organizationName: dto.organizationName.trim(),
|
||||
@@ -134,8 +137,13 @@ export class AuthService {
|
||||
},
|
||||
});
|
||||
|
||||
const returnOtp = this.shouldReturnOtp();
|
||||
await this.dispatchEmailOtp(email, emailCode, returnOtp);
|
||||
if (emailOtpEnabled) {
|
||||
await this.dispatchEmailOtp(email, emailCode, returnOtp);
|
||||
} else {
|
||||
this.logger.warn(
|
||||
`[auth-otp-email-skipped] ${email} — mailer disabled; SMS code covers both`,
|
||||
);
|
||||
}
|
||||
await this.dispatchMobileOtp(mobile, mobileCode, returnOtp);
|
||||
|
||||
return {
|
||||
@@ -143,8 +151,12 @@ export class AuthService {
|
||||
expiresInSeconds: OTP_TTL_SECONDS,
|
||||
maskedEmail: maskEmail(email),
|
||||
maskedMobile: maskMobile(mobile),
|
||||
emailOtpRequired: emailOtpEnabled,
|
||||
...(returnOtp
|
||||
? { devEmailCode: emailCode, devMobileCode: mobileCode }
|
||||
? {
|
||||
devEmailCode: emailCode,
|
||||
devMobileCode: mobileCode,
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
@@ -229,7 +241,7 @@ export class AuthService {
|
||||
return this.buildLoginResponse(organization.users[0]);
|
||||
}
|
||||
|
||||
/** مرحله ۱ ورود: ایمیل/موبایل + رمز → ارسال OTP روی همان کانال */
|
||||
/** مرحله ۱ ورود: ایمیل/موبایل + رمز → OTP ترجیحاً با پیامک */
|
||||
async startLogin(dto: LoginDto) {
|
||||
const identifier = (dto.identifier ?? dto.email ?? '').trim();
|
||||
const isEmail = looksLikeEmail(identifier);
|
||||
@@ -252,13 +264,8 @@ export class AuthService {
|
||||
throw new UnauthorizedException('اطلاعات ورود نادرست است');
|
||||
}
|
||||
|
||||
const channel = email ? 'email' : 'mobile';
|
||||
if (channel === 'mobile' && !user.mobile) {
|
||||
throw new BadRequestException('موبایل برای این حساب ثبت نشده است');
|
||||
}
|
||||
if (channel === 'email' && !user.email) {
|
||||
throw new BadRequestException('ایمیل برای این حساب ثبت نشده است');
|
||||
}
|
||||
const returnOtp = this.shouldReturnOtp();
|
||||
const channel = this.resolveLoginOtpChannel(user, returnOtp);
|
||||
|
||||
await this.throttleChallenges('login', user.email, user.mobile);
|
||||
|
||||
@@ -277,7 +284,6 @@ export class AuthService {
|
||||
},
|
||||
});
|
||||
|
||||
const returnOtp = this.shouldReturnOtp();
|
||||
if (channel === 'email') {
|
||||
await this.dispatchEmailOtp(user.email, code, returnOtp);
|
||||
} else {
|
||||
@@ -286,7 +292,7 @@ export class AuthService {
|
||||
|
||||
return {
|
||||
challengeId: challenge.id,
|
||||
channel: channel as 'email' | 'mobile',
|
||||
channel,
|
||||
expiresInSeconds: OTP_TTL_SECONDS,
|
||||
maskedDestination:
|
||||
channel === 'email' ? maskEmail(user.email) : maskMobile(user.mobile!),
|
||||
@@ -392,15 +398,44 @@ export class AuthService {
|
||||
);
|
||||
}
|
||||
|
||||
/** تا وقتی SMTP/سرویس ایمیل واقعی نداریم خاموش است */
|
||||
private isEmailOtpConfigured(): boolean {
|
||||
return this.config.get<string>('AUTH_EMAIL_OTP_ENABLED') === 'true';
|
||||
}
|
||||
|
||||
private resolveLoginOtpChannel(
|
||||
user: { email: string; mobile: string | null },
|
||||
returnOtp: boolean,
|
||||
): 'email' | 'mobile' {
|
||||
// ترجیح: پیامک (سرویس آماده است)
|
||||
if (user.mobile) {
|
||||
if (this.sms.isConfigured() || returnOtp) {
|
||||
return 'mobile';
|
||||
}
|
||||
}
|
||||
|
||||
if (this.isEmailOtpConfigured() || returnOtp) {
|
||||
return 'email';
|
||||
}
|
||||
|
||||
if (!user.mobile) {
|
||||
throw new BadRequestException(
|
||||
'برای ورود باید موبایل روی حساب ثبت باشد (ارسال ایمیل فعال نیست)',
|
||||
);
|
||||
}
|
||||
|
||||
throw new BadRequestException(
|
||||
'سامانه پیامک پیکربندی نشده است؛ با پشتیبانی تماس بگیرید',
|
||||
);
|
||||
}
|
||||
|
||||
private async dispatchEmailOtp(
|
||||
email: string,
|
||||
code: string,
|
||||
returnOtp: boolean,
|
||||
) {
|
||||
// فعلاً سرویس ایمیل پیکربندی نشده؛ در محیط غیرپروداکشن کد لاگ میشود
|
||||
this.logger.log(`[auth-otp-email] ${email} => ${code}`);
|
||||
if (!returnOtp && this.config.get<string>('NODE_ENV') === 'production') {
|
||||
// بدون ایمیل واقعی در پروداکشن، ورود/ثبتنام با ایمیل ممکن نیست مگر AUTH_RETURN_OTP
|
||||
if (!this.isEmailOtpConfigured() && !returnOtp) {
|
||||
throw new BadRequestException(
|
||||
'ارسال ایمیل پیکربندی نشده است؛ با پشتیبانی تماس بگیرید',
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user