Files
fabric_trade_server/dist/auth/auth.service.js
T
mohmdsdgandCursor 345a4a4708 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>
2026-08-08 14:55:26 +03:30

266 lines
11 KiB
JavaScript

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
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;
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 isRegister = Boolean(dto.isRegister);
if (isRegister) {
if (!dto.firstName?.trim() || !dto.lastName?.trim()) {
throw new common_1.BadRequestException('نام و نام خانوادگی الزامی است');
}
if (existing) {
throw new common_1.BadRequestException('این شماره قبلاً ثبت شده است');
}
}
else if (!existing) {
throw new common_1.BadRequestException('کاربری با این شماره یافت نشد');
}
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);
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,
...(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();
const challenge = await this.prisma.otpChallenge.findFirst({
where: { phone },
orderBy: { createdAt: 'desc' },
});
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 } });
if (challenge.isRegister) {
if (user) {
throw new common_1.BadRequestException('این شماره قبلاً ثبت شده است');
}
user = await this.prisma.customer.create({
data: {
phone,
firstName: challenge.firstName ?? 'کاربر',
lastName: challenge.lastName ?? 'جدید',
},
});
}
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 {
accessToken,
user: this.mapCustomer(user),
};
}
async adminLogin(dto) {
const phone = (0, phone_1.normalizePhone)((0, phone_1.toEnglishDigits)(dto.phone));
const admin = await this.prisma.adminUser.findUnique({ where: { phone } });
if (!admin || !admin.isActive) {
throw new common_1.UnauthorizedException('نام کاربری یا رمز عبور نادرست است');
}
const ok = await bcrypt.compare(dto.password, admin.passwordHash);
if (!ok) {
throw new common_1.UnauthorizedException('نام کاربری یا رمز عبور نادرست است');
}
await this.prisma.adminUser.update({
where: { id: admin.id },
data: { lastLoginAt: new Date() },
});
const accessToken = await this.jwt.signAsync({
sub: admin.id,
type: 'admin',
role: admin.role,
phone: admin.phone,
});
return {
accessToken,
user: {
id: admin.id,
fullName: admin.fullName,
phone: admin.phone,
email: admin.email,
role: admin.role,
},
};
}
async me(userId, type) {
if (type === 'admin') {
const admin = await this.prisma.adminUser.findUnique({
where: { id: userId },
});
if (!admin)
throw new common_1.UnauthorizedException();
return {
id: admin.id,
fullName: admin.fullName,
phone: admin.phone,
email: admin.email,
role: admin.role,
};
}
const user = await this.prisma.customer.findUnique({
where: { id: userId },
});
if (!user)
throw new common_1.UnauthorizedException();
return this.mapCustomer(user);
}
async updateProfile(userId, dto) {
const user = await this.prisma.customer.update({
where: { id: userId },
data: {
firstName: dto.firstName?.trim(),
lastName: dto.lastName?.trim(),
nationalId: dto.nationalId?.trim() || null,
},
});
return this.mapCustomer(user);
}
async signCustomerToken(id, phone) {
return this.jwt.signAsync({
sub: id,
type: 'customer',
phone,
});
}
mapCustomer(user) {
return {
id: user.id,
firstName: user.firstName,
lastName: user.lastName,
phone: user.phone,
nationalId: user.nationalId ?? undefined,
createdAt: user.createdAt.toISOString(),
};
}
};
exports.AuthService = AuthService;
exports.AuthService = AuthService = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [prisma_service_1.PrismaService,
jwt_1.JwtService,
config_1.ConfigService,
sms_service_1.SmsService])
], AuthService);
//# sourceMappingURL=auth.service.js.map