Add SMS OTP, harden auth, and fix order/checkout server bugs.
Ship 4-digit OTP with rate-limit attempts, Tehran-safe expiry/rass math, larger JSON body limit for check images, and safer order pricing/status guards. Include rebuilt dist for prebuilt deploy. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Vendored
+62
-17
@@ -46,24 +46,31 @@ exports.AuthService = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const config_1 = require("@nestjs/config");
|
||||
const jwt_1 = require("@nestjs/jwt");
|
||||
const node_crypto_1 = require("node:crypto");
|
||||
const bcrypt = __importStar(require("bcryptjs"));
|
||||
const prisma_service_1 = require("../prisma/prisma.service");
|
||||
const sms_service_1 = require("../sms/sms.service");
|
||||
const phone_1 = require("../common/utils/phone");
|
||||
const MAX_OTP_ATTEMPTS = 5;
|
||||
let AuthService = class AuthService {
|
||||
prisma;
|
||||
jwt;
|
||||
config;
|
||||
constructor(prisma, jwt, config) {
|
||||
sms;
|
||||
constructor(prisma, jwt, config, sms) {
|
||||
this.prisma = prisma;
|
||||
this.jwt = jwt;
|
||||
this.config = config;
|
||||
this.sms = sms;
|
||||
}
|
||||
async requestOtp(dto) {
|
||||
const phone = (0, phone_1.normalizePhone)((0, phone_1.toEnglishDigits)(dto.phone));
|
||||
if (!(0, phone_1.isValidPhone)(phone)) {
|
||||
throw new common_1.BadRequestException('شماره موبایل نامعتبر است');
|
||||
}
|
||||
const existing = await this.prisma.customer.findUnique({ where: { phone } });
|
||||
const existing = await this.prisma.customer.findUnique({
|
||||
where: { phone },
|
||||
});
|
||||
const isRegister = Boolean(dto.isRegister);
|
||||
if (isRegister) {
|
||||
if (!dto.firstName?.trim() || !dto.lastName?.trim()) {
|
||||
@@ -76,27 +83,49 @@ let AuthService = class AuthService {
|
||||
else if (!existing) {
|
||||
throw new common_1.BadRequestException('کاربری با این شماره یافت نشد');
|
||||
}
|
||||
const code = this.config.get('MOCK_OTP_CODE', '123456');
|
||||
else if (!existing.isActive) {
|
||||
throw new common_1.UnauthorizedException('حساب کاربری شما غیرفعال است');
|
||||
}
|
||||
const smsEnabled = this.sms.isEnabled();
|
||||
const code = smsEnabled
|
||||
? String((0, node_crypto_1.randomInt)(1000, 10000))
|
||||
: this.config.get('MOCK_OTP_CODE', '1234');
|
||||
const ttl = Number(this.config.get('OTP_TTL_MINUTES', 5));
|
||||
const expiresAt = new Date(Date.now() + ttl * 60_000);
|
||||
await this.prisma.otpChallenge.deleteMany({ where: { phone } });
|
||||
await this.prisma.otpChallenge.create({
|
||||
data: {
|
||||
phone,
|
||||
code,
|
||||
firstName: dto.firstName?.trim(),
|
||||
lastName: dto.lastName?.trim(),
|
||||
isRegister,
|
||||
expiresAt,
|
||||
},
|
||||
const challenge = await this.prisma.$transaction(async (tx) => {
|
||||
await tx.otpChallenge.deleteMany({ where: { phone } });
|
||||
return tx.otpChallenge.create({
|
||||
data: {
|
||||
phone,
|
||||
code,
|
||||
firstName: dto.firstName?.trim(),
|
||||
lastName: dto.lastName?.trim(),
|
||||
isRegister,
|
||||
expiresAt,
|
||||
},
|
||||
});
|
||||
});
|
||||
if (smsEnabled) {
|
||||
const sent = await this.sms.send(phone, this.sms.buildOtpMessage(code));
|
||||
if (!sent.success) {
|
||||
await this.prisma.otpChallenge.deleteMany({
|
||||
where: { id: challenge.id },
|
||||
});
|
||||
throw new common_1.ServiceUnavailableException('ارسال پیامک با خطا مواجه شد؛ دوباره تلاش کنید');
|
||||
}
|
||||
}
|
||||
return {
|
||||
success: true,
|
||||
message: 'کد تأیید ارسال شد',
|
||||
expiresAt,
|
||||
debugCode: code,
|
||||
...(this.exposesDebugCode(smsEnabled) ? { debugCode: code } : {}),
|
||||
};
|
||||
}
|
||||
exposesDebugCode(smsEnabled) {
|
||||
if (smsEnabled)
|
||||
return false;
|
||||
return this.config.get('NODE_ENV') !== 'production';
|
||||
}
|
||||
async verifyOtp(dto) {
|
||||
const phone = (0, phone_1.normalizePhone)((0, phone_1.toEnglishDigits)(dto.phone));
|
||||
const code = (0, phone_1.toEnglishDigits)(dto.code).trim();
|
||||
@@ -107,7 +136,15 @@ let AuthService = class AuthService {
|
||||
if (!challenge || challenge.expiresAt < new Date()) {
|
||||
throw new common_1.UnauthorizedException('کد منقضی شده است');
|
||||
}
|
||||
if (challenge.attempts >= MAX_OTP_ATTEMPTS) {
|
||||
await this.prisma.otpChallenge.deleteMany({ where: { phone } });
|
||||
throw new common_1.UnauthorizedException('تعداد تلاشهای نادرست زیاد است؛ کد جدید بگیرید');
|
||||
}
|
||||
if (challenge.code !== code) {
|
||||
await this.prisma.otpChallenge.update({
|
||||
where: { id: challenge.id },
|
||||
data: { attempts: { increment: 1 } },
|
||||
});
|
||||
throw new common_1.UnauthorizedException('کد تأیید نادرست است');
|
||||
}
|
||||
let user = await this.prisma.customer.findUnique({ where: { phone } });
|
||||
@@ -126,6 +163,9 @@ let AuthService = class AuthService {
|
||||
else if (!user) {
|
||||
throw new common_1.UnauthorizedException('کاربر یافت نشد');
|
||||
}
|
||||
else if (!user.isActive) {
|
||||
throw new common_1.UnauthorizedException('حساب کاربری شما غیرفعال است');
|
||||
}
|
||||
await this.prisma.otpChallenge.deleteMany({ where: { phone } });
|
||||
const accessToken = await this.signCustomerToken(user.id, user.phone);
|
||||
return {
|
||||
@@ -166,7 +206,9 @@ let AuthService = class AuthService {
|
||||
}
|
||||
async me(userId, type) {
|
||||
if (type === 'admin') {
|
||||
const admin = await this.prisma.adminUser.findUnique({ where: { id: userId } });
|
||||
const admin = await this.prisma.adminUser.findUnique({
|
||||
where: { id: userId },
|
||||
});
|
||||
if (!admin)
|
||||
throw new common_1.UnauthorizedException();
|
||||
return {
|
||||
@@ -177,7 +219,9 @@ let AuthService = class AuthService {
|
||||
role: admin.role,
|
||||
};
|
||||
}
|
||||
const user = await this.prisma.customer.findUnique({ where: { id: userId } });
|
||||
const user = await this.prisma.customer.findUnique({
|
||||
where: { id: userId },
|
||||
});
|
||||
if (!user)
|
||||
throw new common_1.UnauthorizedException();
|
||||
return this.mapCustomer(user);
|
||||
@@ -216,6 +260,7 @@ exports.AuthService = AuthService = __decorate([
|
||||
(0, common_1.Injectable)(),
|
||||
__metadata("design:paramtypes", [prisma_service_1.PrismaService,
|
||||
jwt_1.JwtService,
|
||||
config_1.ConfigService])
|
||||
config_1.ConfigService,
|
||||
sms_service_1.SmsService])
|
||||
], AuthService);
|
||||
//# sourceMappingURL=auth.service.js.map
|
||||
Reference in New Issue
Block a user