feat: remove OTP gate from transactions, fix categories auth, add implementation plan
- Remove OtpGateGuard from transactions controller (OTP verified at login) - Fix categories controller to use authenticated user instead of TEMP_USER_ID - Add comprehensive implementation plan document - Update .env.example with WEB_APP_URL - Prepare for admin dashboard development
This commit is contained in:
7
apps/api/dist/otp/otp-gate.guard.d.ts
vendored
Normal file
7
apps/api/dist/otp/otp-gate.guard.d.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
import { CanActivate, ExecutionContext } from '@nestjs/common';
|
||||
import { OtpService } from './otp.service';
|
||||
export declare class OtpGateGuard implements CanActivate {
|
||||
private otpService;
|
||||
constructor(otpService: OtpService);
|
||||
canActivate(context: ExecutionContext): Promise<boolean>;
|
||||
}
|
||||
56
apps/api/dist/otp/otp-gate.guard.js
vendored
Normal file
56
apps/api/dist/otp/otp-gate.guard.js
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
"use strict";
|
||||
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 __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.OtpGateGuard = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const otp_service_1 = require("./otp.service");
|
||||
let OtpGateGuard = class OtpGateGuard {
|
||||
otpService;
|
||||
constructor(otpService) {
|
||||
this.otpService = otpService;
|
||||
}
|
||||
async canActivate(context) {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const userId = request.user?.userId;
|
||||
if (!userId) {
|
||||
return true;
|
||||
}
|
||||
const status = await this.otpService.getStatus(userId);
|
||||
if (!status.emailEnabled && !status.totpEnabled) {
|
||||
return true;
|
||||
}
|
||||
const otpCode = request.headers['x-otp-code'] || request.body?.otpCode;
|
||||
const otpMethod = (request.headers['x-otp-method'] ||
|
||||
request.body?.otpMethod ||
|
||||
'totp');
|
||||
if (!otpCode) {
|
||||
throw new common_1.UnauthorizedException({
|
||||
message: 'OTP verification required',
|
||||
requiresOtp: true,
|
||||
availableMethods: {
|
||||
email: status.emailEnabled,
|
||||
totp: status.totpEnabled,
|
||||
},
|
||||
});
|
||||
}
|
||||
const isValid = await this.otpService.verifyOtpGate(userId, otpCode, otpMethod);
|
||||
if (!isValid) {
|
||||
throw new common_1.UnauthorizedException('Invalid OTP code');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
exports.OtpGateGuard = OtpGateGuard;
|
||||
exports.OtpGateGuard = OtpGateGuard = __decorate([
|
||||
(0, common_1.Injectable)(),
|
||||
__metadata("design:paramtypes", [otp_service_1.OtpService])
|
||||
], OtpGateGuard);
|
||||
//# sourceMappingURL=otp-gate.guard.js.map
|
||||
1
apps/api/dist/otp/otp-gate.guard.js.map
vendored
Normal file
1
apps/api/dist/otp/otp-gate.guard.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"otp-gate.guard.js","sourceRoot":"","sources":["../../src/otp/otp-gate.guard.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAKwB;AACxB,+CAA2C;AAcpC,IAAM,YAAY,GAAlB,MAAM,YAAY;IACH;IAApB,YAAoB,UAAsB;QAAtB,eAAU,GAAV,UAAU,CAAY;IAAG,CAAC;IAE9C,KAAK,CAAC,WAAW,CAAC,OAAyB;QACzC,MAAM,OAAO,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAmB,CAAC;QAGrE,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;QACpC,IAAI,CAAC,MAAM,EAAE,CAAC;YAEZ,OAAO,IAAI,CAAC;QACd,CAAC;QAGD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;QAGvD,IAAI,CAAC,MAAM,CAAC,YAAY,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;YAChD,OAAO,IAAI,CAAC;QACd,CAAC;QAGD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;QACvE,MAAM,SAAS,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC;YAChD,OAAO,CAAC,IAAI,EAAE,SAAS;YACvB,MAAM,CAAqB,CAAC;QAE9B,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,8BAAqB,CAAC;gBAC9B,OAAO,EAAE,2BAA2B;gBACpC,WAAW,EAAE,IAAI;gBACjB,gBAAgB,EAAE;oBAChB,KAAK,EAAE,MAAM,CAAC,YAAY;oBAC1B,IAAI,EAAE,MAAM,CAAC,WAAW;iBACzB;aACF,CAAC,CAAC;QACL,CAAC;QAGD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,aAAa,CACjD,MAAM,EACN,OAAO,EACP,SAAS,CACV,CAAC;QAEF,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,8BAAqB,CAAC,kBAAkB,CAAC,CAAC;QACtD,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;CACF,CAAA;AAnDY,oCAAY;uBAAZ,YAAY;IADxB,IAAA,mBAAU,GAAE;qCAEqB,wBAAU;GAD/B,YAAY,CAmDxB"}
|
||||
92
apps/api/dist/otp/otp.controller.d.ts
vendored
Normal file
92
apps/api/dist/otp/otp.controller.d.ts
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { OtpService } from './otp.service';
|
||||
export declare const IS_PUBLIC_KEY = "isPublic";
|
||||
export declare const Public: () => import("@nestjs/common").CustomDecorator<string>;
|
||||
interface RequestWithUser extends Request {
|
||||
user: {
|
||||
userId: string;
|
||||
email: string;
|
||||
};
|
||||
}
|
||||
export declare class OtpController {
|
||||
private readonly otpService;
|
||||
private readonly jwtService;
|
||||
constructor(otpService: OtpService, jwtService: JwtService);
|
||||
getStatus(req: RequestWithUser): Promise<{
|
||||
emailEnabled: boolean;
|
||||
whatsappEnabled: boolean;
|
||||
totpEnabled: boolean;
|
||||
phone?: undefined;
|
||||
totpSecret?: undefined;
|
||||
} | {
|
||||
phone: string | null;
|
||||
emailEnabled: boolean;
|
||||
whatsappEnabled: boolean;
|
||||
totpEnabled: boolean;
|
||||
totpSecret: string | null;
|
||||
}>;
|
||||
sendEmailOtp(req: RequestWithUser): Promise<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
}>;
|
||||
verifyEmailOtp(req: RequestWithUser, body: {
|
||||
code: string;
|
||||
}): Promise<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
}>;
|
||||
disableEmailOtp(req: RequestWithUser): Promise<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
}>;
|
||||
setupTotp(req: RequestWithUser): Promise<{
|
||||
secret: string;
|
||||
qrCode: string;
|
||||
}>;
|
||||
verifyTotp(req: RequestWithUser, body: {
|
||||
code: string;
|
||||
}): Promise<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
}>;
|
||||
disableTotp(req: RequestWithUser): Promise<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
}>;
|
||||
sendWhatsappOtp(req: RequestWithUser, body: {
|
||||
mode?: 'test' | 'live';
|
||||
}): Promise<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
}>;
|
||||
verifyWhatsappOtp(req: RequestWithUser, body: {
|
||||
code: string;
|
||||
}): Promise<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
}>;
|
||||
disableWhatsappOtp(req: RequestWithUser): Promise<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
}>;
|
||||
checkWhatsappNumber(body: {
|
||||
phone: string;
|
||||
}): Promise<{
|
||||
success: boolean;
|
||||
isRegistered: boolean;
|
||||
message: string;
|
||||
}>;
|
||||
resendEmailOtp(body: {
|
||||
tempToken: string;
|
||||
}): Promise<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
}>;
|
||||
resendWhatsappOtp(body: {
|
||||
tempToken: string;
|
||||
}): Promise<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
}>;
|
||||
}
|
||||
export {};
|
||||
200
apps/api/dist/otp/otp.controller.js
vendored
Normal file
200
apps/api/dist/otp/otp.controller.js
vendored
Normal file
@@ -0,0 +1,200 @@
|
||||
"use strict";
|
||||
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 __metadata = (this && this.__metadata) || function (k, v) {
|
||||
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
||||
};
|
||||
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
||||
return function (target, key) { decorator(target, key, paramIndex); }
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.OtpController = exports.Public = exports.IS_PUBLIC_KEY = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const jwt_1 = require("@nestjs/jwt");
|
||||
const auth_guard_1 = require("../auth/auth.guard");
|
||||
const otp_service_1 = require("./otp.service");
|
||||
exports.IS_PUBLIC_KEY = 'isPublic';
|
||||
const Public = () => (0, common_1.SetMetadata)(exports.IS_PUBLIC_KEY, true);
|
||||
exports.Public = Public;
|
||||
let OtpController = class OtpController {
|
||||
otpService;
|
||||
jwtService;
|
||||
constructor(otpService, jwtService) {
|
||||
this.otpService = otpService;
|
||||
this.jwtService = jwtService;
|
||||
}
|
||||
async getStatus(req) {
|
||||
return this.otpService.getStatus(req.user.userId);
|
||||
}
|
||||
async sendEmailOtp(req) {
|
||||
return this.otpService.sendEmailOtp(req.user.userId);
|
||||
}
|
||||
async verifyEmailOtp(req, body) {
|
||||
return this.otpService.verifyEmailOtp(req.user.userId, body.code);
|
||||
}
|
||||
async disableEmailOtp(req) {
|
||||
return this.otpService.disableEmailOtp(req.user.userId);
|
||||
}
|
||||
async setupTotp(req) {
|
||||
return this.otpService.setupTotp(req.user.userId);
|
||||
}
|
||||
async verifyTotp(req, body) {
|
||||
return this.otpService.verifyTotp(req.user.userId, body.code);
|
||||
}
|
||||
async disableTotp(req) {
|
||||
return this.otpService.disableTotp(req.user.userId);
|
||||
}
|
||||
async sendWhatsappOtp(req, body) {
|
||||
return this.otpService.sendWhatsappOtp(req.user.userId, body.mode || 'test');
|
||||
}
|
||||
async verifyWhatsappOtp(req, body) {
|
||||
return this.otpService.verifyWhatsappOtp(req.user.userId, body.code);
|
||||
}
|
||||
async disableWhatsappOtp(req) {
|
||||
return this.otpService.disableWhatsappOtp(req.user.userId);
|
||||
}
|
||||
async checkWhatsappNumber(body) {
|
||||
return this.otpService.checkWhatsappNumber(body.phone);
|
||||
}
|
||||
async resendEmailOtp(body) {
|
||||
try {
|
||||
const payload = this.jwtService.verify(body.tempToken);
|
||||
if (!payload.temp) {
|
||||
throw new common_1.UnauthorizedException('Invalid token type');
|
||||
}
|
||||
const userId = payload.userId || payload.sub;
|
||||
if (!userId) {
|
||||
throw new common_1.UnauthorizedException('Invalid token payload');
|
||||
}
|
||||
return this.otpService.sendEmailOtp(userId);
|
||||
}
|
||||
catch {
|
||||
throw new common_1.UnauthorizedException('Invalid or expired token');
|
||||
}
|
||||
}
|
||||
async resendWhatsappOtp(body) {
|
||||
try {
|
||||
const payload = this.jwtService.verify(body.tempToken);
|
||||
if (!payload.temp) {
|
||||
throw new common_1.UnauthorizedException('Invalid token type');
|
||||
}
|
||||
const userId = payload.userId || payload.sub;
|
||||
if (!userId) {
|
||||
throw new common_1.UnauthorizedException('Invalid token payload');
|
||||
}
|
||||
return this.otpService.sendWhatsappOtp(userId, 'live');
|
||||
}
|
||||
catch {
|
||||
throw new common_1.UnauthorizedException('Invalid or expired token');
|
||||
}
|
||||
}
|
||||
};
|
||||
exports.OtpController = OtpController;
|
||||
__decorate([
|
||||
(0, common_1.Get)('status'),
|
||||
__param(0, (0, common_1.Req)()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], OtpController.prototype, "getStatus", null);
|
||||
__decorate([
|
||||
(0, common_1.Post)('email/send'),
|
||||
__param(0, (0, common_1.Req)()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], OtpController.prototype, "sendEmailOtp", null);
|
||||
__decorate([
|
||||
(0, common_1.Post)('email/verify'),
|
||||
__param(0, (0, common_1.Req)()),
|
||||
__param(1, (0, common_1.Body)()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object, Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], OtpController.prototype, "verifyEmailOtp", null);
|
||||
__decorate([
|
||||
(0, common_1.Post)('email/disable'),
|
||||
__param(0, (0, common_1.Req)()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], OtpController.prototype, "disableEmailOtp", null);
|
||||
__decorate([
|
||||
(0, common_1.Post)('totp/setup'),
|
||||
__param(0, (0, common_1.Req)()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], OtpController.prototype, "setupTotp", null);
|
||||
__decorate([
|
||||
(0, common_1.Post)('totp/verify'),
|
||||
__param(0, (0, common_1.Req)()),
|
||||
__param(1, (0, common_1.Body)()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object, Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], OtpController.prototype, "verifyTotp", null);
|
||||
__decorate([
|
||||
(0, common_1.Post)('totp/disable'),
|
||||
__param(0, (0, common_1.Req)()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], OtpController.prototype, "disableTotp", null);
|
||||
__decorate([
|
||||
(0, common_1.Post)('whatsapp/send'),
|
||||
__param(0, (0, common_1.Req)()),
|
||||
__param(1, (0, common_1.Body)()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object, Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], OtpController.prototype, "sendWhatsappOtp", null);
|
||||
__decorate([
|
||||
(0, common_1.Post)('whatsapp/verify'),
|
||||
__param(0, (0, common_1.Req)()),
|
||||
__param(1, (0, common_1.Body)()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object, Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], OtpController.prototype, "verifyWhatsappOtp", null);
|
||||
__decorate([
|
||||
(0, common_1.Post)('whatsapp/disable'),
|
||||
__param(0, (0, common_1.Req)()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], OtpController.prototype, "disableWhatsappOtp", null);
|
||||
__decorate([
|
||||
(0, common_1.Post)('whatsapp/check'),
|
||||
__param(0, (0, common_1.Body)()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], OtpController.prototype, "checkWhatsappNumber", null);
|
||||
__decorate([
|
||||
(0, exports.Public)(),
|
||||
(0, common_1.Post)('email/resend'),
|
||||
__param(0, (0, common_1.Body)()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], OtpController.prototype, "resendEmailOtp", null);
|
||||
__decorate([
|
||||
(0, exports.Public)(),
|
||||
(0, common_1.Post)('whatsapp/resend'),
|
||||
__param(0, (0, common_1.Body)()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], OtpController.prototype, "resendWhatsappOtp", null);
|
||||
exports.OtpController = OtpController = __decorate([
|
||||
(0, common_1.Controller)('otp'),
|
||||
(0, common_1.UseGuards)(auth_guard_1.AuthGuard),
|
||||
__metadata("design:paramtypes", [otp_service_1.OtpService,
|
||||
jwt_1.JwtService])
|
||||
], OtpController);
|
||||
//# sourceMappingURL=otp.controller.js.map
|
||||
1
apps/api/dist/otp/otp.controller.js.map
vendored
Normal file
1
apps/api/dist/otp/otp.controller.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"otp.controller.js","sourceRoot":"","sources":["../../src/otp/otp.controller.ts"],"names":[],"mappings":"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"}
|
||||
2
apps/api/dist/otp/otp.module.d.ts
vendored
Normal file
2
apps/api/dist/otp/otp.module.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export declare class OtpModule {
|
||||
}
|
||||
35
apps/api/dist/otp/otp.module.js
vendored
Normal file
35
apps/api/dist/otp/otp.module.js
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
"use strict";
|
||||
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;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.OtpModule = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const jwt_1 = require("@nestjs/jwt");
|
||||
const otp_controller_1 = require("./otp.controller");
|
||||
const otp_service_1 = require("./otp.service");
|
||||
const otp_gate_guard_1 = require("./otp-gate.guard");
|
||||
const auth_module_1 = require("../auth/auth.module");
|
||||
const prisma_module_1 = require("../prisma/prisma.module");
|
||||
let OtpModule = class OtpModule {
|
||||
};
|
||||
exports.OtpModule = OtpModule;
|
||||
exports.OtpModule = OtpModule = __decorate([
|
||||
(0, common_1.Module)({
|
||||
imports: [
|
||||
(0, common_1.forwardRef)(() => auth_module_1.AuthModule),
|
||||
prisma_module_1.PrismaModule,
|
||||
jwt_1.JwtModule.register({
|
||||
secret: process.env.JWT_SECRET || 'your-secret-key',
|
||||
signOptions: { expiresIn: '7d' },
|
||||
}),
|
||||
],
|
||||
controllers: [otp_controller_1.OtpController],
|
||||
providers: [otp_service_1.OtpService, otp_gate_guard_1.OtpGateGuard],
|
||||
exports: [otp_service_1.OtpService, otp_gate_guard_1.OtpGateGuard],
|
||||
})
|
||||
], OtpModule);
|
||||
//# sourceMappingURL=otp.module.js.map
|
||||
1
apps/api/dist/otp/otp.module.js.map
vendored
Normal file
1
apps/api/dist/otp/otp.module.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"otp.module.js","sourceRoot":"","sources":["../../src/otp/otp.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAoD;AACpD,qCAAwC;AACxC,qDAAiD;AACjD,+CAA2C;AAC3C,qDAAgD;AAChD,qDAAiD;AACjD,2DAAuD;AAehD,IAAM,SAAS,GAAf,MAAM,SAAS;CAAG,CAAA;AAAZ,8BAAS;oBAAT,SAAS;IAbrB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE;YACP,IAAA,mBAAU,EAAC,GAAG,EAAE,CAAC,wBAAU,CAAC;YAC5B,4BAAY;YACZ,eAAS,CAAC,QAAQ,CAAC;gBACjB,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,iBAAiB;gBACnD,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE;aACjC,CAAC;SACH;QACD,WAAW,EAAE,CAAC,8BAAa,CAAC;QAC5B,SAAS,EAAE,CAAC,wBAAU,EAAE,6BAAY,CAAC;QACrC,OAAO,EAAE,CAAC,wBAAU,EAAE,6BAAY,CAAC;KACpC,CAAC;GACW,SAAS,CAAG"}
|
||||
67
apps/api/dist/otp/otp.service.d.ts
vendored
Normal file
67
apps/api/dist/otp/otp.service.d.ts
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
export declare class OtpService {
|
||||
private prisma;
|
||||
private emailOtpStore;
|
||||
private whatsappOtpStore;
|
||||
constructor(prisma: PrismaService);
|
||||
sendEmailOtp(userId: string): Promise<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
}>;
|
||||
verifyEmailOtpForLogin(userId: string, code: string): boolean;
|
||||
verifyEmailOtp(userId: string, code: string): Promise<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
}>;
|
||||
disableEmailOtp(userId: string): Promise<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
}>;
|
||||
setupTotp(userId: string): Promise<{
|
||||
secret: string;
|
||||
qrCode: string;
|
||||
}>;
|
||||
verifyTotp(userId: string, code: string): Promise<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
}>;
|
||||
disableTotp(userId: string): Promise<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
}>;
|
||||
getStatus(userId: string): Promise<{
|
||||
emailEnabled: boolean;
|
||||
whatsappEnabled: boolean;
|
||||
totpEnabled: boolean;
|
||||
phone?: undefined;
|
||||
totpSecret?: undefined;
|
||||
} | {
|
||||
phone: string | null;
|
||||
emailEnabled: boolean;
|
||||
whatsappEnabled: boolean;
|
||||
totpEnabled: boolean;
|
||||
totpSecret: string | null;
|
||||
}>;
|
||||
verifyOtpGate(userId: string, code: string, method: 'email' | 'totp'): Promise<boolean>;
|
||||
private generateOtpCode;
|
||||
private sendOtpViaWebhook;
|
||||
sendWhatsappOtp(userId: string, mode?: 'test' | 'live'): Promise<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
}>;
|
||||
verifyWhatsappOtp(userId: string, code: string): Promise<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
}>;
|
||||
verifyWhatsappOtpForLogin(userId: string, code: string): boolean;
|
||||
disableWhatsappOtp(userId: string): Promise<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
}>;
|
||||
checkWhatsappNumber(phone: string): Promise<{
|
||||
success: boolean;
|
||||
isRegistered: boolean;
|
||||
message: string;
|
||||
}>;
|
||||
private sendWhatsappOtpViaWebhook;
|
||||
}
|
||||
351
apps/api/dist/otp/otp.service.js
vendored
Normal file
351
apps/api/dist/otp/otp.service.js
vendored
Normal file
@@ -0,0 +1,351 @@
|
||||
"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);
|
||||
};
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.OtpService = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const otplib_1 = require("otplib");
|
||||
const prisma_service_1 = require("../prisma/prisma.service");
|
||||
const axios_1 = __importDefault(require("axios"));
|
||||
const QRCode = __importStar(require("qrcode"));
|
||||
let OtpService = class OtpService {
|
||||
prisma;
|
||||
emailOtpStore = new Map();
|
||||
whatsappOtpStore = new Map();
|
||||
constructor(prisma) {
|
||||
this.prisma = prisma;
|
||||
}
|
||||
async sendEmailOtp(userId) {
|
||||
const user = await this.prisma.user.findUnique({ where: { id: userId } });
|
||||
if (!user) {
|
||||
throw new common_1.BadRequestException('User not found');
|
||||
}
|
||||
const code = this.generateOtpCode();
|
||||
const expiresAt = new Date(Date.now() + 10 * 60 * 1000);
|
||||
this.emailOtpStore.set(userId, { code, expiresAt });
|
||||
try {
|
||||
await this.sendOtpViaWebhook(user.email, code);
|
||||
return { success: true, message: 'OTP sent to your email' };
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to send OTP via webhook:', error);
|
||||
console.log(`📧 OTP Code for ${user.email}: ${code}`);
|
||||
return {
|
||||
success: true,
|
||||
message: 'OTP sent (check console for dev code)',
|
||||
};
|
||||
}
|
||||
}
|
||||
verifyEmailOtpForLogin(userId, code) {
|
||||
const stored = this.emailOtpStore.get(userId);
|
||||
if (!stored) {
|
||||
return false;
|
||||
}
|
||||
if (new Date() > stored.expiresAt) {
|
||||
this.emailOtpStore.delete(userId);
|
||||
return false;
|
||||
}
|
||||
if (stored.code !== code) {
|
||||
return false;
|
||||
}
|
||||
this.emailOtpStore.delete(userId);
|
||||
return true;
|
||||
}
|
||||
async verifyEmailOtp(userId, code) {
|
||||
const stored = this.emailOtpStore.get(userId);
|
||||
if (!stored) {
|
||||
throw new common_1.BadRequestException('No OTP found. Please request a new one.');
|
||||
}
|
||||
if (new Date() > stored.expiresAt) {
|
||||
this.emailOtpStore.delete(userId);
|
||||
throw new common_1.BadRequestException('OTP has expired. Please request a new one.');
|
||||
}
|
||||
if (stored.code !== code) {
|
||||
throw new common_1.BadRequestException('Invalid OTP code.');
|
||||
}
|
||||
await this.prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: { otpEmailEnabled: true },
|
||||
});
|
||||
this.emailOtpStore.delete(userId);
|
||||
return { success: true, message: 'Email OTP enabled successfully' };
|
||||
}
|
||||
async disableEmailOtp(userId) {
|
||||
await this.prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: { otpEmailEnabled: false },
|
||||
});
|
||||
return { success: true, message: 'Email OTP disabled' };
|
||||
}
|
||||
async setupTotp(userId) {
|
||||
const user = await this.prisma.user.findUnique({ where: { id: userId } });
|
||||
if (!user) {
|
||||
throw new common_1.BadRequestException('User not found');
|
||||
}
|
||||
const secret = otplib_1.authenticator.generateSecret();
|
||||
await this.prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: { otpTotpSecret: secret },
|
||||
});
|
||||
const serviceName = 'Tabungin';
|
||||
const accountName = user.email;
|
||||
const otpauthUrl = otplib_1.authenticator.keyuri(accountName, serviceName, secret);
|
||||
const qrCodeDataUrl = await QRCode.toDataURL(otpauthUrl);
|
||||
return {
|
||||
secret,
|
||||
qrCode: qrCodeDataUrl,
|
||||
};
|
||||
}
|
||||
async verifyTotp(userId, code) {
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: { otpTotpSecret: true },
|
||||
});
|
||||
if (!user?.otpTotpSecret) {
|
||||
throw new common_1.BadRequestException('No TOTP setup found. Please setup TOTP first.');
|
||||
}
|
||||
const isValid = otplib_1.authenticator.verify({
|
||||
token: code,
|
||||
secret: user.otpTotpSecret,
|
||||
});
|
||||
if (!isValid) {
|
||||
throw new common_1.BadRequestException('Invalid TOTP code.');
|
||||
}
|
||||
await this.prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: { otpTotpEnabled: true },
|
||||
});
|
||||
return { success: true, message: 'TOTP enabled successfully' };
|
||||
}
|
||||
async disableTotp(userId) {
|
||||
await this.prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: {
|
||||
otpTotpEnabled: false,
|
||||
otpTotpSecret: null,
|
||||
},
|
||||
});
|
||||
return { success: true, message: 'TOTP disabled' };
|
||||
}
|
||||
async getStatus(userId) {
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: {
|
||||
phone: true,
|
||||
otpEmailEnabled: true,
|
||||
otpWhatsappEnabled: true,
|
||||
otpTotpEnabled: true,
|
||||
otpTotpSecret: true,
|
||||
},
|
||||
});
|
||||
if (!user) {
|
||||
return {
|
||||
emailEnabled: false,
|
||||
whatsappEnabled: false,
|
||||
totpEnabled: false,
|
||||
};
|
||||
}
|
||||
return {
|
||||
phone: user.phone,
|
||||
emailEnabled: user.otpEmailEnabled,
|
||||
whatsappEnabled: user.otpWhatsappEnabled,
|
||||
totpEnabled: user.otpTotpEnabled,
|
||||
totpSecret: user.otpTotpSecret,
|
||||
};
|
||||
}
|
||||
async verifyOtpGate(userId, code, method) {
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: {
|
||||
otpEmailEnabled: true,
|
||||
otpTotpEnabled: true,
|
||||
otpTotpSecret: true,
|
||||
},
|
||||
});
|
||||
if (!user) {
|
||||
return false;
|
||||
}
|
||||
if (method === 'email' && user.otpEmailEnabled) {
|
||||
const stored = this.emailOtpStore.get(userId);
|
||||
if (stored && new Date() <= stored.expiresAt && stored.code === code) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (method === 'totp' && user.otpTotpEnabled && user.otpTotpSecret) {
|
||||
return otplib_1.authenticator.verify({ token: code, secret: user.otpTotpSecret });
|
||||
}
|
||||
return false;
|
||||
}
|
||||
generateOtpCode() {
|
||||
return Math.floor(100000 + Math.random() * 900000).toString();
|
||||
}
|
||||
async sendOtpViaWebhook(email, code, mode = 'test') {
|
||||
const webhookUrl = process.env.OTP_SEND_WEBHOOK_URL_TEST || process.env.OTP_SEND_WEBHOOK_URL;
|
||||
if (!webhookUrl) {
|
||||
throw new Error('OTP_SEND_WEBHOOK_URL or OTP_SEND_WEBHOOK_URL_TEST not configured');
|
||||
}
|
||||
await axios_1.default.post(webhookUrl, {
|
||||
method: 'email',
|
||||
mode,
|
||||
to: email,
|
||||
subject: 'Tabungin - Your OTP Code',
|
||||
message: `Your OTP code is: ${code}. This code will expire in 10 minutes.`,
|
||||
code,
|
||||
});
|
||||
}
|
||||
async sendWhatsappOtp(userId, mode = 'test') {
|
||||
const user = await this.prisma.user.findUnique({ where: { id: userId } });
|
||||
if (!user) {
|
||||
throw new common_1.BadRequestException('User not found');
|
||||
}
|
||||
if (!user.phone) {
|
||||
throw new common_1.BadRequestException('Phone number not set');
|
||||
}
|
||||
const code = this.generateOtpCode();
|
||||
const expiresAt = new Date(Date.now() + 10 * 60 * 1000);
|
||||
this.whatsappOtpStore.set(userId, { code, expiresAt });
|
||||
try {
|
||||
await this.sendWhatsappOtpViaWebhook(user.phone, code, mode);
|
||||
return { success: true, message: 'OTP sent to your WhatsApp' };
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to send WhatsApp OTP via webhook:', error);
|
||||
console.log(`📱 WhatsApp OTP Code for ${user.phone}: ${code}`);
|
||||
return {
|
||||
success: true,
|
||||
message: 'OTP sent (check console for dev code)',
|
||||
};
|
||||
}
|
||||
}
|
||||
async verifyWhatsappOtp(userId, code) {
|
||||
const stored = this.whatsappOtpStore.get(userId);
|
||||
if (!stored) {
|
||||
throw new common_1.BadRequestException('No OTP found. Please request a new one.');
|
||||
}
|
||||
if (new Date() > stored.expiresAt) {
|
||||
this.whatsappOtpStore.delete(userId);
|
||||
throw new common_1.BadRequestException('OTP has expired. Please request a new one.');
|
||||
}
|
||||
if (stored.code !== code) {
|
||||
throw new common_1.BadRequestException('Invalid OTP code');
|
||||
}
|
||||
await this.prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: { otpWhatsappEnabled: true },
|
||||
});
|
||||
this.whatsappOtpStore.delete(userId);
|
||||
return { success: true, message: 'WhatsApp OTP enabled successfully' };
|
||||
}
|
||||
verifyWhatsappOtpForLogin(userId, code) {
|
||||
const stored = this.whatsappOtpStore.get(userId);
|
||||
if (!stored) {
|
||||
return false;
|
||||
}
|
||||
if (new Date() > stored.expiresAt) {
|
||||
this.whatsappOtpStore.delete(userId);
|
||||
return false;
|
||||
}
|
||||
if (stored.code !== code) {
|
||||
return false;
|
||||
}
|
||||
this.whatsappOtpStore.delete(userId);
|
||||
return true;
|
||||
}
|
||||
async disableWhatsappOtp(userId) {
|
||||
await this.prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: { otpWhatsappEnabled: false },
|
||||
});
|
||||
return { success: true, message: 'WhatsApp OTP disabled' };
|
||||
}
|
||||
async checkWhatsappNumber(phone) {
|
||||
try {
|
||||
const webhookUrl = process.env.OTP_SEND_WEBHOOK_URL_TEST ||
|
||||
process.env.OTP_SEND_WEBHOOK_URL;
|
||||
if (!webhookUrl) {
|
||||
throw new Error('Webhook URL not configured');
|
||||
}
|
||||
const response = await axios_1.default.post(webhookUrl, {
|
||||
method: 'whatsapp',
|
||||
mode: 'checknumber',
|
||||
phone,
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
isRegistered: response.data?.isRegistered || false,
|
||||
message: response.data?.message || 'Number checked',
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to check WhatsApp number:', error);
|
||||
console.log(`📱 Checking WhatsApp number: ${phone} - Assumed valid`);
|
||||
return {
|
||||
success: true,
|
||||
isRegistered: true,
|
||||
message: 'Number is valid (dev mode)',
|
||||
};
|
||||
}
|
||||
}
|
||||
async sendWhatsappOtpViaWebhook(phone, code, mode = 'test') {
|
||||
const webhookUrl = process.env.OTP_SEND_WEBHOOK_URL_TEST || process.env.OTP_SEND_WEBHOOK_URL;
|
||||
if (!webhookUrl) {
|
||||
throw new Error('Webhook URL not configured');
|
||||
}
|
||||
await axios_1.default.post(webhookUrl, {
|
||||
method: 'whatsapp',
|
||||
mode,
|
||||
phone,
|
||||
message: `Your Tabungin OTP code is: ${code}. This code will expire in 10 minutes.`,
|
||||
code,
|
||||
});
|
||||
}
|
||||
};
|
||||
exports.OtpService = OtpService;
|
||||
exports.OtpService = OtpService = __decorate([
|
||||
(0, common_1.Injectable)(),
|
||||
__metadata("design:paramtypes", [prisma_service_1.PrismaService])
|
||||
], OtpService);
|
||||
//# sourceMappingURL=otp.service.js.map
|
||||
1
apps/api/dist/otp/otp.service.js.map
vendored
Normal file
1
apps/api/dist/otp/otp.service.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user