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
+1
-1
@@ -9,10 +9,10 @@ export declare class AuthController {
|
||||
service: string;
|
||||
};
|
||||
requestOtp(dto: RequestOtpDto): Promise<{
|
||||
debugCode?: string | undefined;
|
||||
success: boolean;
|
||||
message: string;
|
||||
expiresAt: Date;
|
||||
debugCode: string;
|
||||
}>;
|
||||
verifyOtp(dto: VerifyOtpDto): Promise<{
|
||||
accessToken: string;
|
||||
|
||||
Vendored
+4
-1
@@ -12,17 +12,20 @@ const jwt_1 = require("@nestjs/jwt");
|
||||
const config_1 = require("@nestjs/config");
|
||||
const auth_service_1 = require("./auth.service");
|
||||
const auth_controller_1 = require("./auth.controller");
|
||||
const sms_module_1 = require("../sms/sms.module");
|
||||
const jwt_secret_1 = require("../common/utils/jwt-secret");
|
||||
let AuthModule = class AuthModule {
|
||||
};
|
||||
exports.AuthModule = AuthModule;
|
||||
exports.AuthModule = AuthModule = __decorate([
|
||||
(0, common_1.Module)({
|
||||
imports: [
|
||||
sms_module_1.SmsModule,
|
||||
jwt_1.JwtModule.registerAsync({
|
||||
imports: [config_1.ConfigModule],
|
||||
inject: [config_1.ConfigService],
|
||||
useFactory: (config) => ({
|
||||
secret: config.get('JWT_SECRET', 'fabric-trade-dev-secret'),
|
||||
secret: (0, jwt_secret_1.resolveJwtSecret)(config),
|
||||
signOptions: {
|
||||
expiresIn: config.get('JWT_EXPIRES_IN', '7d'),
|
||||
},
|
||||
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
{"version":3,"file":"auth.module.js","sourceRoot":"","sources":["../../src/auth/auth.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,qCAAwC;AACxC,2CAA6D;AAC7D,iDAA6C;AAC7C,uDAAmD;AAmB5C,IAAM,UAAU,GAAhB,MAAM,UAAU;CAAG,CAAA;AAAb,gCAAU;qBAAV,UAAU;IAjBtB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE;YACP,eAAS,CAAC,aAAa,CAAC;gBACtB,OAAO,EAAE,CAAC,qBAAY,CAAC;gBACvB,MAAM,EAAE,CAAC,sBAAa,CAAC;gBACvB,UAAU,EAAE,CAAC,MAAqB,EAAE,EAAE,CAAC,CAAC;oBACtC,MAAM,EAAE,MAAM,CAAC,GAAG,CAAS,YAAY,EAAE,yBAAyB,CAAC;oBACnE,WAAW,EAAE;wBACX,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,gBAAgB,EAAE,IAAI,CAAiB;qBAC9D;iBACF,CAAC;aACH,CAAC;SACH;QACD,WAAW,EAAE,CAAC,gCAAc,CAAC;QAC7B,SAAS,EAAE,CAAC,0BAAW,CAAC;QACxB,OAAO,EAAE,CAAC,0BAAW,EAAE,eAAS,CAAC;KAClC,CAAC;GACW,UAAU,CAAG"}
|
||||
{"version":3,"file":"auth.module.js","sourceRoot":"","sources":["../../src/auth/auth.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,qCAAwC;AACxC,2CAA6D;AAC7D,iDAA6C;AAC7C,uDAAmD;AACnD,kDAA8C;AAC9C,2DAA8D;AAoBvD,IAAM,UAAU,GAAhB,MAAM,UAAU;CAAG,CAAA;AAAb,gCAAU;qBAAV,UAAU;IAlBtB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE;YACP,sBAAS;YACT,eAAS,CAAC,aAAa,CAAC;gBACtB,OAAO,EAAE,CAAC,qBAAY,CAAC;gBACvB,MAAM,EAAE,CAAC,sBAAa,CAAC;gBACvB,UAAU,EAAE,CAAC,MAAqB,EAAE,EAAE,CAAC,CAAC;oBACtC,MAAM,EAAE,IAAA,6BAAgB,EAAC,MAAM,CAAC;oBAChC,WAAW,EAAE;wBACX,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,gBAAgB,EAAE,IAAI,CAAC;qBAC9C;iBACF,CAAC;aACH,CAAC;SACH;QACD,WAAW,EAAE,CAAC,gCAAc,CAAC;QAC7B,SAAS,EAAE,CAAC,0BAAW,CAAC;QACxB,OAAO,EAAE,CAAC,0BAAW,EAAE,eAAS,CAAC;KAClC,CAAC;GACW,UAAU,CAAG"}
|
||||
Vendored
+5
-2
@@ -1,18 +1,21 @@
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { SmsService } from '../sms/sms.service';
|
||||
import { AdminLoginDto, RequestOtpDto, UpdateProfileDto, VerifyOtpDto } from './dto/auth.dto';
|
||||
export declare class AuthService {
|
||||
private readonly prisma;
|
||||
private readonly jwt;
|
||||
private readonly config;
|
||||
constructor(prisma: PrismaService, jwt: JwtService, config: ConfigService);
|
||||
private readonly sms;
|
||||
constructor(prisma: PrismaService, jwt: JwtService, config: ConfigService, sms: SmsService);
|
||||
requestOtp(dto: RequestOtpDto): Promise<{
|
||||
debugCode?: string | undefined;
|
||||
success: boolean;
|
||||
message: string;
|
||||
expiresAt: Date;
|
||||
debugCode: string;
|
||||
}>;
|
||||
private exposesDebugCode;
|
||||
verifyOtp(dto: VerifyOtpDto): Promise<{
|
||||
accessToken: string;
|
||||
user: {
|
||||
|
||||
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
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -48,7 +48,7 @@ __decorate([
|
||||
], VerifyOtpDto.prototype, "phone", void 0);
|
||||
__decorate([
|
||||
(0, class_validator_1.IsString)(),
|
||||
(0, class_validator_1.MinLength)(4),
|
||||
(0, class_validator_1.Length)(4, 4),
|
||||
__metadata("design:type", String)
|
||||
], VerifyOtpDto.prototype, "code", void 0);
|
||||
class AdminLoginDto {
|
||||
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
{"version":3,"file":"auth.dto.js","sourceRoot":"","sources":["../../../src/auth/dto/auth.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAA6E;AAE7E,MAAa,aAAa;IAExB,KAAK,CAAU;IAIf,SAAS,CAAU;IAInB,QAAQ,CAAU;IAIlB,UAAU,CAAW;CACtB;AAfD,sCAeC;AAbC;IADC,IAAA,0BAAQ,GAAE;;4CACI;AAIf;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;gDACQ;AAInB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;+CACO;AAIlB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,2BAAS,GAAE;;iDACS;AAGvB,MAAa,YAAY;IAEvB,KAAK,CAAU;IAIf,IAAI,CAAU;CACf;AAPD,oCAOC;AALC;IADC,IAAA,0BAAQ,GAAE;;2CACI;AAIf;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,2BAAS,EAAC,CAAC,CAAC;;0CACC;AAGhB,MAAa,aAAa;IAExB,KAAK,CAAU;IAIf,QAAQ,CAAU;CACnB;AAPD,sCAOC;AALC;IADC,IAAA,0BAAQ,GAAE;;4CACI;AAIf;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,2BAAS,EAAC,CAAC,CAAC;;+CACK;AAGpB,MAAa,gBAAgB;IAG3B,SAAS,CAAU;IAInB,QAAQ,CAAU;IAIlB,UAAU,CAAU;CACrB;AAZD,4CAYC;AATC;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;mDACQ;AAInB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;kDACO;AAIlB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;oDACS"}
|
||||
{"version":3,"file":"auth.dto.js","sourceRoot":"","sources":["../../../src/auth/dto/auth.dto.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAMyB;AAEzB,MAAa,aAAa;IAExB,KAAK,CAAU;IAIf,SAAS,CAAU;IAInB,QAAQ,CAAU;IAIlB,UAAU,CAAW;CACtB;AAfD,sCAeC;AAbC;IADC,IAAA,0BAAQ,GAAE;;4CACI;AAIf;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;gDACQ;AAInB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;+CACO;AAIlB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,2BAAS,GAAE;;iDACS;AAGvB,MAAa,YAAY;IAEvB,KAAK,CAAU;IAIf,IAAI,CAAU;CACf;AAPD,oCAOC;AALC;IADC,IAAA,0BAAQ,GAAE;;2CACI;AAIf;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,wBAAM,EAAC,CAAC,EAAE,CAAC,CAAC;;0CACC;AAGhB,MAAa,aAAa;IAExB,KAAK,CAAU;IAIf,QAAQ,CAAU;CACnB;AAPD,sCAOC;AALC;IADC,IAAA,0BAAQ,GAAE;;4CACI;AAIf;IAFC,IAAA,0BAAQ,GAAE;IACV,IAAA,2BAAS,EAAC,CAAC,CAAC;;+CACK;AAGpB,MAAa,gBAAgB;IAG3B,SAAS,CAAU;IAInB,QAAQ,CAAU;IAIlB,UAAU,CAAU;CACrB;AAZD,4CAYC;AATC;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;mDACQ;AAInB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;kDACO;AAIlB;IAFC,IAAA,4BAAU,GAAE;IACZ,IAAA,0BAAQ,GAAE;;oDACS"}
|
||||
Reference in New Issue
Block a user