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:
@@ -1,16 +1,22 @@
|
||||
# Database Configuration
|
||||
DATABASE_URL="postgresql://username:password@localhost:5432/tabungin_dev"
|
||||
SHADOW_DATABASE_URL="postgresql://username:password@localhost:5432/tabungin_shadow"
|
||||
DATABASE_URL="postgresql://user:password@localhost:5432/tabungin?schema=public"
|
||||
DATABASE_URL_SHADOW="postgresql://user:password@localhost:5432/tabungin_shadow?schema=public"
|
||||
|
||||
# Firebase Admin SDK Configuration
|
||||
# Get these from Firebase Console > Project Settings > Service Accounts
|
||||
FIREBASE_PROJECT_ID=your_project_id
|
||||
FIREBASE_CLIENT_EMAIL=firebase-adminsdk-xxxxx@your_project_id.iam.gserviceaccount.com
|
||||
FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nYOUR_PRIVATE_KEY_HERE\n-----END PRIVATE KEY-----\n"
|
||||
# JWT Authentication (generate a random 32+ character string for production)
|
||||
JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
|
||||
|
||||
# API Configuration
|
||||
PORT=3000
|
||||
WEB_APP_URL=http://localhost:5173
|
||||
# Exchange Rate API
|
||||
EXCHANGE_RATE_URL=https://api.exchangerate-api.com/v4/latest/IDR
|
||||
|
||||
# Development User ID (run seed script to create this user)
|
||||
TEMP_USER_ID=16b74848-daa3-4dc9-8de2-3cf59e08f8e3
|
||||
# Google OAuth (for "Continue with Google")
|
||||
GOOGLE_CLIENT_ID=your-google-client-id
|
||||
GOOGLE_CLIENT_SECRET=your-google-client-secret
|
||||
GOOGLE_CALLBACK_URL=http://localhost:3001/api/auth/google/callback
|
||||
|
||||
# OTP Webhook URLs (n8n)
|
||||
OTP_SEND_WEBHOOK_URL=https://your-n8n-instance.com/webhook/send-otp
|
||||
OTP_SEND_WEBHOOK_URL_TEST=https://your-n8n-instance.com/webhook-test/send-otp
|
||||
|
||||
# App Configuration
|
||||
PORT=3001
|
||||
WEB_APP_URL=http://localhost:5174
|
||||
|
||||
2
apps/api/dist/app.module.js
vendored
2
apps/api/dist/app.module.js
vendored
@@ -50,6 +50,7 @@ const users_module_1 = require("./users/users.module");
|
||||
const wallets_module_1 = require("./wallets/wallets.module");
|
||||
const transactions_module_1 = require("./transactions/transactions.module");
|
||||
const categories_module_1 = require("./categories/categories.module");
|
||||
const otp_module_1 = require("./otp/otp.module");
|
||||
let AppModule = class AppModule {
|
||||
};
|
||||
exports.AppModule = AppModule;
|
||||
@@ -69,6 +70,7 @@ exports.AppModule = AppModule = __decorate([
|
||||
wallets_module_1.WalletsModule,
|
||||
transactions_module_1.TransactionsModule,
|
||||
categories_module_1.CategoriesModule,
|
||||
otp_module_1.OtpModule,
|
||||
],
|
||||
controllers: [health_controller_1.HealthController],
|
||||
providers: [],
|
||||
|
||||
2
apps/api/dist/app.module.js.map
vendored
2
apps/api/dist/app.module.js.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"app.module.js","sourceRoot":"","sources":["../src/app.module.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,2CAAwC;AACxC,2CAA8C;AAC9C,2CAA6B;AAC7B,0DAAsD;AACtD,oDAAgD;AAChD,kEAA8D;AAC9D,uDAAmD;AACnD,6DAAyD;AACzD,4EAAwE;AACxE,sEAAkE;AAqB3D,IAAM,SAAS,GAAf,MAAM,SAAS;CAAG,CAAA;AAAZ,8BAAS;oBAAT,SAAS;IAnBrB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE;YACP,qBAAY,CAAC,OAAO,CAAC;gBACnB,QAAQ,EAAE,IAAI;gBACd,WAAW,EAAE;oBACX,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC;oBACnC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,YAAY,CAAC;iBAC1C;aACF,CAAC;YACF,4BAAY;YACZ,wBAAU;YACV,0BAAW;YACX,8BAAa;YACb,wCAAkB;YAClB,oCAAgB;SACjB;QACD,WAAW,EAAE,CAAC,oCAAgB,CAAC;QAC/B,SAAS,EAAE,EAAE;KACd,CAAC;GACW,SAAS,CAAG"}
|
||||
{"version":3,"file":"app.module.js","sourceRoot":"","sources":["../src/app.module.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,2CAAwC;AACxC,2CAA8C;AAC9C,2CAA6B;AAC7B,0DAAsD;AACtD,oDAAgD;AAChD,kEAA8D;AAC9D,uDAAmD;AACnD,6DAAyD;AACzD,4EAAwE;AACxE,sEAAkE;AAClE,iDAA6C;AAsBtC,IAAM,SAAS,GAAf,MAAM,SAAS;CAAG,CAAA;AAAZ,8BAAS;oBAAT,SAAS;IApBrB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE;YACP,qBAAY,CAAC,OAAO,CAAC;gBACnB,QAAQ,EAAE,IAAI;gBACd,WAAW,EAAE;oBACX,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC;oBACnC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,YAAY,CAAC;iBAC1C;aACF,CAAC;YACF,4BAAY;YACZ,wBAAU;YACV,0BAAW;YACX,8BAAa;YACb,wCAAkB;YAClB,oCAAgB;YAChB,sBAAS;SACV;QACD,WAAW,EAAE,CAAC,oCAAgB,CAAC;QAC/B,SAAS,EAAE,EAAE;KACd,CAAC;GACW,SAAS,CAAG"}
|
||||
83
apps/api/dist/auth/auth.controller.d.ts
vendored
Normal file
83
apps/api/dist/auth/auth.controller.d.ts
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
import { AuthService } from './auth.service';
|
||||
import type { Response } from 'express';
|
||||
interface RequestWithUser {
|
||||
user: {
|
||||
userId: string;
|
||||
email: string;
|
||||
};
|
||||
}
|
||||
export declare class AuthController {
|
||||
private authService;
|
||||
constructor(authService: AuthService);
|
||||
register(body: {
|
||||
email: string;
|
||||
password: string;
|
||||
name?: string;
|
||||
}): Promise<{
|
||||
user: {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string | null;
|
||||
avatarUrl: string | null;
|
||||
emailVerified: boolean;
|
||||
};
|
||||
token: string;
|
||||
}>;
|
||||
login(body: {
|
||||
email: string;
|
||||
password: string;
|
||||
}): Promise<{
|
||||
requiresOtp: boolean;
|
||||
availableMethods: {
|
||||
email: boolean;
|
||||
whatsapp: boolean;
|
||||
totp: boolean;
|
||||
};
|
||||
tempToken: string;
|
||||
user?: undefined;
|
||||
token?: undefined;
|
||||
} | {
|
||||
user: {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string | null;
|
||||
avatarUrl: string | null;
|
||||
emailVerified: boolean;
|
||||
};
|
||||
token: string;
|
||||
requiresOtp?: undefined;
|
||||
availableMethods?: undefined;
|
||||
tempToken?: undefined;
|
||||
}>;
|
||||
verifyOtp(body: {
|
||||
tempToken: string;
|
||||
otpCode: string;
|
||||
method: 'email' | 'totp';
|
||||
}): Promise<{
|
||||
user: {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string | null;
|
||||
avatarUrl: string | null;
|
||||
emailVerified: boolean;
|
||||
};
|
||||
token: string;
|
||||
}>;
|
||||
googleAuth(): Promise<void>;
|
||||
googleAuthCallback(req: any, res: Response): Promise<void>;
|
||||
getProfile(req: RequestWithUser): Promise<{
|
||||
id: string;
|
||||
email: string;
|
||||
emailVerified: boolean;
|
||||
name: string | null;
|
||||
avatarUrl: string | null;
|
||||
}>;
|
||||
changePassword(req: RequestWithUser, body: {
|
||||
currentPassword: string;
|
||||
newPassword: string;
|
||||
isSettingPassword?: boolean;
|
||||
}): Promise<{
|
||||
message: string;
|
||||
}>;
|
||||
}
|
||||
export {};
|
||||
112
apps/api/dist/auth/auth.controller.js
vendored
Normal file
112
apps/api/dist/auth/auth.controller.js
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
"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.AuthController = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const auth_guard_1 = require("./auth.guard");
|
||||
const passport_1 = require("@nestjs/passport");
|
||||
const auth_service_1 = require("./auth.service");
|
||||
let AuthController = class AuthController {
|
||||
authService;
|
||||
constructor(authService) {
|
||||
this.authService = authService;
|
||||
}
|
||||
async register(body) {
|
||||
return this.authService.register(body.email, body.password, body.name);
|
||||
}
|
||||
async login(body) {
|
||||
return this.authService.login(body.email, body.password);
|
||||
}
|
||||
async verifyOtp(body) {
|
||||
return this.authService.verifyOtpAndLogin(body.tempToken, body.otpCode, body.method);
|
||||
}
|
||||
async googleAuth() {
|
||||
}
|
||||
async googleAuthCallback(req, res) {
|
||||
const result = await this.authService.googleLogin(req.user);
|
||||
const frontendUrl = process.env.WEB_APP_URL || 'http://localhost:5174';
|
||||
if (result.requiresOtp) {
|
||||
res.redirect(`${frontendUrl}/auth/otp?token=${result.tempToken}&methods=${JSON.stringify(result.availableMethods)}`);
|
||||
}
|
||||
else {
|
||||
res.redirect(`${frontendUrl}/auth/callback?token=${result.token}`);
|
||||
}
|
||||
}
|
||||
async getProfile(req) {
|
||||
return this.authService.getUserProfile(req.user.userId);
|
||||
}
|
||||
async changePassword(req, body) {
|
||||
return this.authService.changePassword(req.user.userId, body.currentPassword, body.newPassword, body.isSettingPassword);
|
||||
}
|
||||
};
|
||||
exports.AuthController = AuthController;
|
||||
__decorate([
|
||||
(0, common_1.Post)('register'),
|
||||
__param(0, (0, common_1.Body)()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], AuthController.prototype, "register", null);
|
||||
__decorate([
|
||||
(0, common_1.Post)('login'),
|
||||
__param(0, (0, common_1.Body)()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], AuthController.prototype, "login", null);
|
||||
__decorate([
|
||||
(0, common_1.Post)('verify-otp'),
|
||||
__param(0, (0, common_1.Body)()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], AuthController.prototype, "verifyOtp", null);
|
||||
__decorate([
|
||||
(0, common_1.Get)('google'),
|
||||
(0, common_1.UseGuards)((0, passport_1.AuthGuard)('google')),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", []),
|
||||
__metadata("design:returntype", Promise)
|
||||
], AuthController.prototype, "googleAuth", null);
|
||||
__decorate([
|
||||
(0, common_1.Get)('google/callback'),
|
||||
(0, common_1.UseGuards)((0, passport_1.AuthGuard)('google')),
|
||||
__param(0, (0, common_1.Req)()),
|
||||
__param(1, (0, common_1.Res)()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object, Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], AuthController.prototype, "googleAuthCallback", null);
|
||||
__decorate([
|
||||
(0, common_1.Get)('me'),
|
||||
(0, common_1.UseGuards)(auth_guard_1.AuthGuard),
|
||||
__param(0, (0, common_1.Req)()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], AuthController.prototype, "getProfile", null);
|
||||
__decorate([
|
||||
(0, common_1.Post)('change-password'),
|
||||
(0, common_1.UseGuards)(auth_guard_1.AuthGuard),
|
||||
__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)
|
||||
], AuthController.prototype, "changePassword", null);
|
||||
exports.AuthController = AuthController = __decorate([
|
||||
(0, common_1.Controller)('auth'),
|
||||
__metadata("design:paramtypes", [auth_service_1.AuthService])
|
||||
], AuthController);
|
||||
//# sourceMappingURL=auth.controller.js.map
|
||||
1
apps/api/dist/auth/auth.controller.js.map
vendored
Normal file
1
apps/api/dist/auth/auth.controller.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"auth.controller.js","sourceRoot":"","sources":["../../src/auth/auth.controller.ts"],"names":[],"mappings":"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"}
|
||||
15
apps/api/dist/auth/auth.guard.d.ts
vendored
15
apps/api/dist/auth/auth.guard.d.ts
vendored
@@ -1,8 +1,9 @@
|
||||
import { CanActivate, ExecutionContext } from '@nestjs/common';
|
||||
import { FirebaseService } from './firebase.service';
|
||||
export declare class AuthGuard implements CanActivate {
|
||||
private firebaseService;
|
||||
constructor(firebaseService: FirebaseService);
|
||||
canActivate(context: ExecutionContext): Promise<boolean>;
|
||||
private extractTokenFromHeader;
|
||||
import { ExecutionContext } from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
declare const AuthGuard_base: import("@nestjs/passport").Type<import("@nestjs/passport").IAuthGuard>;
|
||||
export declare class AuthGuard extends AuthGuard_base {
|
||||
private reflector;
|
||||
constructor(reflector: Reflector);
|
||||
canActivate(context: ExecutionContext): boolean | Promise<boolean> | import("rxjs").Observable<boolean>;
|
||||
}
|
||||
export {};
|
||||
|
||||
41
apps/api/dist/auth/auth.guard.js
vendored
41
apps/api/dist/auth/auth.guard.js
vendored
@@ -11,39 +11,28 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.AuthGuard = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const firebase_service_1 = require("./firebase.service");
|
||||
let AuthGuard = class AuthGuard {
|
||||
firebaseService;
|
||||
constructor(firebaseService) {
|
||||
this.firebaseService = firebaseService;
|
||||
const core_1 = require("@nestjs/core");
|
||||
const passport_1 = require("@nestjs/passport");
|
||||
let AuthGuard = class AuthGuard extends (0, passport_1.AuthGuard)('jwt') {
|
||||
reflector;
|
||||
constructor(reflector) {
|
||||
super();
|
||||
this.reflector = reflector;
|
||||
}
|
||||
async canActivate(context) {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
if (!this.firebaseService.isFirebaseConfigured()) {
|
||||
console.warn('⚠️ Firebase not configured - allowing request without auth');
|
||||
canActivate(context) {
|
||||
const isPublic = this.reflector.getAllAndOverride('isPublic', [
|
||||
context.getHandler(),
|
||||
context.getClass(),
|
||||
]);
|
||||
if (isPublic) {
|
||||
return true;
|
||||
}
|
||||
const token = this.extractTokenFromHeader(request);
|
||||
if (!token) {
|
||||
throw new common_1.UnauthorizedException('No token provided');
|
||||
}
|
||||
try {
|
||||
const decodedToken = await this.firebaseService.verifyIdToken(token);
|
||||
request.user = decodedToken;
|
||||
return true;
|
||||
}
|
||||
catch (error) {
|
||||
throw new common_1.UnauthorizedException('Invalid token');
|
||||
}
|
||||
}
|
||||
extractTokenFromHeader(request) {
|
||||
const [type, token] = request.headers.authorization?.split(' ') ?? [];
|
||||
return type === 'Bearer' ? token : undefined;
|
||||
return super.canActivate(context);
|
||||
}
|
||||
};
|
||||
exports.AuthGuard = AuthGuard;
|
||||
exports.AuthGuard = AuthGuard = __decorate([
|
||||
(0, common_1.Injectable)(),
|
||||
__metadata("design:paramtypes", [firebase_service_1.FirebaseService])
|
||||
__metadata("design:paramtypes", [core_1.Reflector])
|
||||
], AuthGuard);
|
||||
//# sourceMappingURL=auth.guard.js.map
|
||||
2
apps/api/dist/auth/auth.guard.js.map
vendored
2
apps/api/dist/auth/auth.guard.js.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"auth.guard.js","sourceRoot":"","sources":["../../src/auth/auth.guard.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAAkG;AAClG,yDAAqD;AAG9C,IAAM,SAAS,GAAf,MAAM,SAAS;IACA;IAApB,YAAoB,eAAgC;QAAhC,oBAAe,GAAf,eAAe,CAAiB;IAAG,CAAC;IAExD,KAAK,CAAC,WAAW,CAAC,OAAyB;QACzC,MAAM,OAAO,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAE,CAAC;QAGpD,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,oBAAoB,EAAE,EAAE,CAAC;YACjD,OAAO,CAAC,IAAI,CAAC,4DAA4D,CAAC,CAAC;YAC3E,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,sBAAsB,CAAC,OAAO,CAAC,CAAC;QAEnD,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,8BAAqB,CAAC,mBAAmB,CAAC,CAAC;QACvD,CAAC;QAED,IAAI,CAAC;YACH,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,eAAe,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;YACrE,OAAO,CAAC,IAAI,GAAG,YAAY,CAAC;YAC5B,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,8BAAqB,CAAC,eAAe,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;IAEO,sBAAsB,CAAC,OAAY;QACzC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,EAAE,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;QACtE,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;IAC/C,CAAC;CACF,CAAA;AA/BY,8BAAS;oBAAT,SAAS;IADrB,IAAA,mBAAU,GAAE;qCAE0B,kCAAe;GADzC,SAAS,CA+BrB"}
|
||||
{"version":3,"file":"auth.guard.js","sourceRoot":"","sources":["../../src/auth/auth.guard.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA8D;AAC9D,uCAAyC;AACzC,+CAAkE;AAG3D,IAAM,SAAS,GAAf,MAAM,SAAU,SAAQ,IAAA,oBAAiB,EAAC,KAAK,CAAC;IACjC;IAApB,YAAoB,SAAoB;QACtC,KAAK,EAAE,CAAC;QADU,cAAS,GAAT,SAAS,CAAW;IAExC,CAAC;IAED,WAAW,CAAC,OAAyB;QAEnC,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAU,UAAU,EAAE;YACrE,OAAO,CAAC,UAAU,EAAE;YACpB,OAAO,CAAC,QAAQ,EAAE;SACnB,CAAC,CAAC;QAEH,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,KAAK,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IACpC,CAAC;CACF,CAAA;AAlBY,8BAAS;oBAAT,SAAS;IADrB,IAAA,mBAAU,GAAE;qCAEoB,gBAAS;GAD7B,SAAS,CAkBrB"}
|
||||
24
apps/api/dist/auth/auth.module.js
vendored
24
apps/api/dist/auth/auth.module.js
vendored
@@ -8,15 +8,31 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.AuthModule = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const firebase_service_1 = require("./firebase.service");
|
||||
const auth_guard_1 = require("./auth.guard");
|
||||
const jwt_1 = require("@nestjs/jwt");
|
||||
const passport_1 = require("@nestjs/passport");
|
||||
const auth_controller_1 = require("./auth.controller");
|
||||
const auth_service_1 = require("./auth.service");
|
||||
const jwt_strategy_1 = require("./jwt.strategy");
|
||||
const google_strategy_1 = require("./google.strategy");
|
||||
const prisma_module_1 = require("../prisma/prisma.module");
|
||||
const otp_module_1 = require("../otp/otp.module");
|
||||
let AuthModule = class AuthModule {
|
||||
};
|
||||
exports.AuthModule = AuthModule;
|
||||
exports.AuthModule = AuthModule = __decorate([
|
||||
(0, common_1.Module)({
|
||||
providers: [firebase_service_1.FirebaseService, auth_guard_1.AuthGuard],
|
||||
exports: [firebase_service_1.FirebaseService, auth_guard_1.AuthGuard],
|
||||
imports: [
|
||||
prisma_module_1.PrismaModule,
|
||||
passport_1.PassportModule,
|
||||
(0, common_1.forwardRef)(() => otp_module_1.OtpModule),
|
||||
jwt_1.JwtModule.register({
|
||||
secret: process.env.JWT_SECRET || 'your-secret-key',
|
||||
signOptions: { expiresIn: '7d' },
|
||||
}),
|
||||
],
|
||||
controllers: [auth_controller_1.AuthController],
|
||||
providers: [auth_service_1.AuthService, jwt_strategy_1.JwtStrategy, google_strategy_1.GoogleStrategy],
|
||||
exports: [auth_service_1.AuthService],
|
||||
})
|
||||
], AuthModule);
|
||||
//# sourceMappingURL=auth.module.js.map
|
||||
2
apps/api/dist/auth/auth.module.js.map
vendored
2
apps/api/dist/auth/auth.module.js.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"auth.module.js","sourceRoot":"","sources":["../../src/auth/auth.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,yDAAqD;AACrD,6CAAyC;AAMlC,IAAM,UAAU,GAAhB,MAAM,UAAU;CAAG,CAAA;AAAb,gCAAU;qBAAV,UAAU;IAJtB,IAAA,eAAM,EAAC;QACN,SAAS,EAAE,CAAC,kCAAe,EAAE,sBAAS,CAAC;QACvC,OAAO,EAAE,CAAC,kCAAe,EAAE,sBAAS,CAAC;KACtC,CAAC;GACW,UAAU,CAAG"}
|
||||
{"version":3,"file":"auth.module.js","sourceRoot":"","sources":["../../src/auth/auth.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAoD;AACpD,qCAAwC;AACxC,+CAAkD;AAClD,uDAAmD;AACnD,iDAA6C;AAC7C,iDAA6C;AAC7C,uDAAmD;AACnD,2DAAuD;AACvD,kDAA8C;AAgBvC,IAAM,UAAU,GAAhB,MAAM,UAAU;CAAG,CAAA;AAAb,gCAAU;qBAAV,UAAU;IAdtB,IAAA,eAAM,EAAC;QACN,OAAO,EAAE;YACP,4BAAY;YACZ,yBAAc;YACd,IAAA,mBAAU,EAAC,GAAG,EAAE,CAAC,sBAAS,CAAC;YAC3B,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,gCAAc,CAAC;QAC7B,SAAS,EAAE,CAAC,0BAAW,EAAE,0BAAW,EAAE,gCAAc,CAAC;QACrD,OAAO,EAAE,CAAC,0BAAW,CAAC;KACvB,CAAC;GACW,UAAU,CAAG"}
|
||||
93
apps/api/dist/auth/auth.service.d.ts
vendored
Normal file
93
apps/api/dist/auth/auth.service.d.ts
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { OtpService } from '../otp/otp.service';
|
||||
export declare class AuthService {
|
||||
private readonly prisma;
|
||||
private readonly jwtService;
|
||||
private readonly otpService;
|
||||
constructor(prisma: PrismaService, jwtService: JwtService, otpService: OtpService);
|
||||
register(email: string, password: string, name?: string): Promise<{
|
||||
user: {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string | null;
|
||||
avatarUrl: string | null;
|
||||
emailVerified: boolean;
|
||||
};
|
||||
token: string;
|
||||
}>;
|
||||
login(email: string, password: string): Promise<{
|
||||
requiresOtp: boolean;
|
||||
availableMethods: {
|
||||
email: boolean;
|
||||
whatsapp: boolean;
|
||||
totp: boolean;
|
||||
};
|
||||
tempToken: string;
|
||||
user?: undefined;
|
||||
token?: undefined;
|
||||
} | {
|
||||
user: {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string | null;
|
||||
avatarUrl: string | null;
|
||||
emailVerified: boolean;
|
||||
};
|
||||
token: string;
|
||||
requiresOtp?: undefined;
|
||||
availableMethods?: undefined;
|
||||
tempToken?: undefined;
|
||||
}>;
|
||||
googleLogin(googleProfile: {
|
||||
googleId: string;
|
||||
email: string;
|
||||
name: string;
|
||||
avatarUrl?: string;
|
||||
}): Promise<{
|
||||
requiresOtp: boolean;
|
||||
availableMethods: {
|
||||
email: boolean;
|
||||
whatsapp: boolean;
|
||||
totp: boolean;
|
||||
};
|
||||
tempToken: string;
|
||||
user?: undefined;
|
||||
token?: undefined;
|
||||
} | {
|
||||
user: {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string | null;
|
||||
avatarUrl: string | null;
|
||||
emailVerified: boolean;
|
||||
};
|
||||
token: string;
|
||||
requiresOtp?: undefined;
|
||||
availableMethods?: undefined;
|
||||
tempToken?: undefined;
|
||||
}>;
|
||||
verifyOtpAndLogin(tempToken: string, otpCode: string, method: 'email' | 'whatsapp' | 'totp'): Promise<{
|
||||
user: {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string | null;
|
||||
avatarUrl: string | null;
|
||||
emailVerified: boolean;
|
||||
};
|
||||
token: string;
|
||||
}>;
|
||||
private generateToken;
|
||||
private generateTempToken;
|
||||
getUserProfile(userId: string): Promise<{
|
||||
id: string;
|
||||
email: string;
|
||||
emailVerified: boolean;
|
||||
name: string | null;
|
||||
avatarUrl: string | null;
|
||||
}>;
|
||||
changePassword(userId: string, currentPassword: string, newPassword: string, isSettingPassword?: boolean): Promise<{
|
||||
message: string;
|
||||
}>;
|
||||
private downloadAndStoreAvatar;
|
||||
}
|
||||
404
apps/api/dist/auth/auth.service.js
vendored
Normal file
404
apps/api/dist/auth/auth.service.js
vendored
Normal file
@@ -0,0 +1,404 @@
|
||||
"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 __param = (this && this.__param) || function (paramIndex, decorator) {
|
||||
return function (target, key) { decorator(target, key, paramIndex); }
|
||||
};
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.AuthService = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const jwt_1 = require("@nestjs/jwt");
|
||||
const prisma_service_1 = require("../prisma/prisma.service");
|
||||
const otp_service_1 = require("../otp/otp.service");
|
||||
const bcrypt = __importStar(require("bcrypt"));
|
||||
const fs = __importStar(require("fs"));
|
||||
const path = __importStar(require("path"));
|
||||
const axios_1 = __importDefault(require("axios"));
|
||||
let AuthService = class AuthService {
|
||||
prisma;
|
||||
jwtService;
|
||||
otpService;
|
||||
constructor(prisma, jwtService, otpService) {
|
||||
this.prisma = prisma;
|
||||
this.jwtService = jwtService;
|
||||
this.otpService = otpService;
|
||||
}
|
||||
async register(email, password, name) {
|
||||
const existing = await this.prisma.user.findUnique({ where: { email } });
|
||||
if (existing) {
|
||||
throw new common_1.ConflictException('Email already registered');
|
||||
}
|
||||
const passwordHash = await bcrypt.hash(password, 10);
|
||||
const user = await this.prisma.user.create({
|
||||
data: {
|
||||
email,
|
||||
passwordHash,
|
||||
name,
|
||||
emailVerified: false,
|
||||
},
|
||||
});
|
||||
const token = this.generateToken(user.id, user.email);
|
||||
return {
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
avatarUrl: user.avatarUrl,
|
||||
emailVerified: user.emailVerified,
|
||||
},
|
||||
token,
|
||||
};
|
||||
}
|
||||
async login(email, password) {
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { email },
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
passwordHash: true,
|
||||
name: true,
|
||||
avatarUrl: true,
|
||||
emailVerified: true,
|
||||
otpEmailEnabled: true,
|
||||
otpWhatsappEnabled: true,
|
||||
otpTotpEnabled: true,
|
||||
},
|
||||
});
|
||||
if (!user || !user.passwordHash) {
|
||||
throw new common_1.UnauthorizedException('Invalid credentials');
|
||||
}
|
||||
const isValid = await bcrypt.compare(password, user.passwordHash);
|
||||
if (!isValid) {
|
||||
throw new common_1.UnauthorizedException('Invalid credentials');
|
||||
}
|
||||
const requiresOtp = user.otpEmailEnabled || user.otpWhatsappEnabled || user.otpTotpEnabled;
|
||||
if (requiresOtp) {
|
||||
if (user.otpEmailEnabled) {
|
||||
try {
|
||||
await this.otpService.sendEmailOtp(user.id);
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to send email OTP during login:', error);
|
||||
}
|
||||
}
|
||||
if (user.otpWhatsappEnabled) {
|
||||
try {
|
||||
await this.otpService.sendWhatsappOtp(user.id, 'live');
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to send WhatsApp OTP during login:', error);
|
||||
}
|
||||
}
|
||||
return {
|
||||
requiresOtp: true,
|
||||
availableMethods: {
|
||||
email: user.otpEmailEnabled,
|
||||
whatsapp: user.otpWhatsappEnabled,
|
||||
totp: user.otpTotpEnabled,
|
||||
},
|
||||
tempToken: this.generateTempToken(user.id, user.email),
|
||||
};
|
||||
}
|
||||
const token = this.generateToken(user.id, user.email);
|
||||
return {
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
avatarUrl: user.avatarUrl,
|
||||
emailVerified: user.emailVerified,
|
||||
},
|
||||
token,
|
||||
};
|
||||
}
|
||||
async googleLogin(googleProfile) {
|
||||
let user = await this.prisma.user.findUnique({
|
||||
where: { email: googleProfile.email },
|
||||
});
|
||||
if (!user) {
|
||||
user = await this.prisma.user.create({
|
||||
data: {
|
||||
email: googleProfile.email,
|
||||
name: googleProfile.name,
|
||||
avatarUrl: googleProfile.avatarUrl,
|
||||
emailVerified: true,
|
||||
authAccounts: {
|
||||
create: {
|
||||
provider: 'google',
|
||||
issuer: 'google.com',
|
||||
subject: googleProfile.googleId,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
else {
|
||||
const existingAuth = await this.prisma.authAccount.findUnique({
|
||||
where: {
|
||||
issuer_subject: {
|
||||
issuer: 'google.com',
|
||||
subject: googleProfile.googleId,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!existingAuth) {
|
||||
await this.prisma.authAccount.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
provider: 'google',
|
||||
issuer: 'google.com',
|
||||
subject: googleProfile.googleId,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log('Updating user with Google profile:', {
|
||||
name: googleProfile.name,
|
||||
avatarUrl: googleProfile.avatarUrl,
|
||||
});
|
||||
let avatarUrl = user.avatarUrl;
|
||||
if (googleProfile.avatarUrl) {
|
||||
try {
|
||||
avatarUrl = await this.downloadAndStoreAvatar(googleProfile.avatarUrl, user.id);
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to download avatar:', error);
|
||||
avatarUrl = googleProfile.avatarUrl;
|
||||
}
|
||||
}
|
||||
user = await this.prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: {
|
||||
name: googleProfile.name || user.name,
|
||||
avatarUrl: avatarUrl || user.avatarUrl,
|
||||
emailVerified: true,
|
||||
},
|
||||
});
|
||||
console.log('User updated, avatar:', user.avatarUrl);
|
||||
}
|
||||
const requiresOtp = user.otpEmailEnabled || user.otpWhatsappEnabled || user.otpTotpEnabled;
|
||||
if (requiresOtp) {
|
||||
if (user.otpEmailEnabled) {
|
||||
try {
|
||||
await this.otpService.sendEmailOtp(user.id);
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to send email OTP during Google login:', error);
|
||||
}
|
||||
}
|
||||
if (user.otpWhatsappEnabled) {
|
||||
try {
|
||||
await this.otpService.sendWhatsappOtp(user.id, 'live');
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to send WhatsApp OTP during Google login:', error);
|
||||
}
|
||||
}
|
||||
return {
|
||||
requiresOtp: true,
|
||||
availableMethods: {
|
||||
email: user.otpEmailEnabled,
|
||||
whatsapp: user.otpWhatsappEnabled,
|
||||
totp: user.otpTotpEnabled,
|
||||
},
|
||||
tempToken: this.generateTempToken(user.id, user.email),
|
||||
};
|
||||
}
|
||||
const token = this.generateToken(user.id, user.email);
|
||||
return {
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
avatarUrl: user.avatarUrl,
|
||||
emailVerified: user.emailVerified,
|
||||
},
|
||||
token,
|
||||
};
|
||||
}
|
||||
async verifyOtpAndLogin(tempToken, otpCode, method) {
|
||||
let payload;
|
||||
try {
|
||||
payload = this.jwtService.verify(tempToken);
|
||||
}
|
||||
catch {
|
||||
throw new common_1.UnauthorizedException('Invalid or expired token');
|
||||
}
|
||||
if (!payload.temp) {
|
||||
throw new common_1.UnauthorizedException('Invalid token type');
|
||||
}
|
||||
const userId = payload.userId || payload.sub;
|
||||
const email = payload.email;
|
||||
if (!userId || !email) {
|
||||
throw new common_1.UnauthorizedException('Invalid token payload');
|
||||
}
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
});
|
||||
if (!user) {
|
||||
throw new common_1.UnauthorizedException('User not found');
|
||||
}
|
||||
if (method === 'email') {
|
||||
const isValid = this.otpService.verifyEmailOtpForLogin(userId, otpCode);
|
||||
if (!isValid) {
|
||||
throw new common_1.UnauthorizedException('Invalid or expired email OTP code');
|
||||
}
|
||||
}
|
||||
else if (method === 'whatsapp') {
|
||||
const isValid = this.otpService.verifyWhatsappOtpForLogin(userId, otpCode);
|
||||
if (!isValid) {
|
||||
throw new common_1.UnauthorizedException('Invalid or expired WhatsApp OTP code');
|
||||
}
|
||||
}
|
||||
else if (method === 'totp') {
|
||||
if (!user.otpTotpSecret) {
|
||||
throw new common_1.UnauthorizedException('TOTP not set up');
|
||||
}
|
||||
const { authenticator } = await import('otplib');
|
||||
const isValid = authenticator.verify({
|
||||
token: otpCode,
|
||||
secret: user.otpTotpSecret,
|
||||
});
|
||||
if (!isValid) {
|
||||
throw new common_1.UnauthorizedException('Invalid TOTP code');
|
||||
}
|
||||
}
|
||||
const token = this.generateToken(userId, email);
|
||||
return {
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
avatarUrl: user.avatarUrl,
|
||||
emailVerified: user.emailVerified,
|
||||
},
|
||||
token,
|
||||
};
|
||||
}
|
||||
generateToken(userId, email) {
|
||||
return this.jwtService.sign({
|
||||
sub: userId,
|
||||
email,
|
||||
});
|
||||
}
|
||||
generateTempToken(userId, email) {
|
||||
return this.jwtService.sign({ userId, email, temp: true }, { expiresIn: '5m' });
|
||||
}
|
||||
async getUserProfile(userId) {
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
avatarUrl: true,
|
||||
emailVerified: true,
|
||||
},
|
||||
});
|
||||
if (!user) {
|
||||
throw new common_1.UnauthorizedException('User not found');
|
||||
}
|
||||
return user;
|
||||
}
|
||||
async changePassword(userId, currentPassword, newPassword, isSettingPassword) {
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: { passwordHash: true },
|
||||
});
|
||||
if (!user) {
|
||||
throw new common_1.BadRequestException('User not found');
|
||||
}
|
||||
if (isSettingPassword && !user.passwordHash) {
|
||||
const newPasswordHash = await bcrypt.hash(newPassword, 10);
|
||||
await this.prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: { passwordHash: newPasswordHash },
|
||||
});
|
||||
return { message: 'Password set successfully' };
|
||||
}
|
||||
if (!user.passwordHash) {
|
||||
throw new common_1.BadRequestException('Cannot change password for this account');
|
||||
}
|
||||
const isValid = await bcrypt.compare(currentPassword, user.passwordHash);
|
||||
if (!isValid) {
|
||||
throw new common_1.UnauthorizedException('Current password is incorrect');
|
||||
}
|
||||
const newPasswordHash = await bcrypt.hash(newPassword, 10);
|
||||
await this.prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: { passwordHash: newPasswordHash },
|
||||
});
|
||||
return { message: 'Password changed successfully' };
|
||||
}
|
||||
async downloadAndStoreAvatar(avatarUrl, userId) {
|
||||
try {
|
||||
const uploadsDir = path.join(process.cwd(), 'public', 'avatars');
|
||||
if (!fs.existsSync(uploadsDir)) {
|
||||
fs.mkdirSync(uploadsDir, { recursive: true });
|
||||
}
|
||||
const response = await axios_1.default.get(avatarUrl, {
|
||||
responseType: 'arraybuffer',
|
||||
});
|
||||
const ext = 'jpg';
|
||||
const filename = `${userId}.${ext}`;
|
||||
const filepath = path.join(uploadsDir, filename);
|
||||
fs.writeFileSync(filepath, response.data);
|
||||
return `/avatars/${filename}`;
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Error downloading avatar:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
exports.AuthService = AuthService;
|
||||
exports.AuthService = AuthService = __decorate([
|
||||
(0, common_1.Injectable)(),
|
||||
__param(2, (0, common_1.Inject)((0, common_1.forwardRef)(() => otp_service_1.OtpService))),
|
||||
__metadata("design:paramtypes", [prisma_service_1.PrismaService,
|
||||
jwt_1.JwtService,
|
||||
otp_service_1.OtpService])
|
||||
], AuthService);
|
||||
//# sourceMappingURL=auth.service.js.map
|
||||
1
apps/api/dist/auth/auth.service.js.map
vendored
Normal file
1
apps/api/dist/auth/auth.service.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
9
apps/api/dist/auth/firebase.service.d.ts
vendored
9
apps/api/dist/auth/firebase.service.d.ts
vendored
@@ -1,9 +0,0 @@
|
||||
import * as admin from 'firebase-admin';
|
||||
export declare class FirebaseService {
|
||||
private app;
|
||||
private isConfigured;
|
||||
constructor();
|
||||
verifyIdToken(idToken: string): Promise<admin.auth.DecodedIdToken>;
|
||||
getUser(uid: string): Promise<admin.auth.UserRecord>;
|
||||
isFirebaseConfigured(): boolean;
|
||||
}
|
||||
113
apps/api/dist/auth/firebase.service.js
vendored
113
apps/api/dist/auth/firebase.service.js
vendored
@@ -1,113 +0,0 @@
|
||||
"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.FirebaseService = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const admin = __importStar(require("firebase-admin"));
|
||||
let FirebaseService = class FirebaseService {
|
||||
app = null;
|
||||
isConfigured = false;
|
||||
constructor() {
|
||||
const projectId = process.env.FIREBASE_PROJECT_ID;
|
||||
const clientEmail = process.env.FIREBASE_CLIENT_EMAIL;
|
||||
const privateKey = process.env.FIREBASE_PRIVATE_KEY;
|
||||
if (projectId && clientEmail && privateKey) {
|
||||
try {
|
||||
if (!admin.apps.length) {
|
||||
this.app = admin.initializeApp({
|
||||
credential: admin.credential.cert({
|
||||
projectId,
|
||||
clientEmail,
|
||||
privateKey: privateKey.replace(/\\n/g, '\n'),
|
||||
}),
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.app = admin.app();
|
||||
}
|
||||
this.isConfigured = true;
|
||||
console.log('✅ Firebase Admin initialized successfully');
|
||||
}
|
||||
catch (error) {
|
||||
console.warn('⚠️ Firebase Admin initialization failed:', error.message);
|
||||
this.isConfigured = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.warn('⚠️ Firebase credentials not found. Auth will use fallback mode.');
|
||||
this.isConfigured = false;
|
||||
}
|
||||
}
|
||||
async verifyIdToken(idToken) {
|
||||
if (!this.isConfigured || !this.app) {
|
||||
throw new Error('Firebase not configured');
|
||||
}
|
||||
try {
|
||||
return await admin.auth().verifyIdToken(idToken);
|
||||
}
|
||||
catch (error) {
|
||||
throw new Error('Invalid token');
|
||||
}
|
||||
}
|
||||
async getUser(uid) {
|
||||
if (!this.isConfigured || !this.app) {
|
||||
throw new Error('Firebase not configured');
|
||||
}
|
||||
try {
|
||||
return await admin.auth().getUser(uid);
|
||||
}
|
||||
catch (error) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
}
|
||||
isFirebaseConfigured() {
|
||||
return this.isConfigured;
|
||||
}
|
||||
};
|
||||
exports.FirebaseService = FirebaseService;
|
||||
exports.FirebaseService = FirebaseService = __decorate([
|
||||
(0, common_1.Injectable)(),
|
||||
__metadata("design:paramtypes", [])
|
||||
], FirebaseService);
|
||||
//# sourceMappingURL=firebase.service.js.map
|
||||
1
apps/api/dist/auth/firebase.service.js.map
vendored
1
apps/api/dist/auth/firebase.service.js.map
vendored
@@ -1 +0,0 @@
|
||||
{"version":3,"file":"firebase.service.js","sourceRoot":"","sources":["../../src/auth/firebase.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,2CAA4C;AAC5C,sDAAwC;AAGjC,IAAM,eAAe,GAArB,MAAM,eAAe;IAClB,GAAG,GAAyB,IAAI,CAAC;IACjC,YAAY,GAAY,KAAK,CAAC;IAEtC;QAEE,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC;QAClD,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC;QACtD,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC;QAEpD,IAAI,SAAS,IAAI,WAAW,IAAI,UAAU,EAAE,CAAC;YAC3C,IAAI,CAAC;gBACH,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC;oBACvB,IAAI,CAAC,GAAG,GAAG,KAAK,CAAC,aAAa,CAAC;wBAC7B,UAAU,EAAE,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC;4BAChC,SAAS;4BACT,WAAW;4BACX,UAAU,EAAE,UAAU,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC;yBAC7C,CAAC;qBACH,CAAC,CAAC;gBACL,CAAC;qBAAM,CAAC;oBACN,IAAI,CAAC,GAAG,GAAG,KAAK,CAAC,GAAG,EAAE,CAAC;gBACzB,CAAC;gBACD,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;gBACzB,OAAO,CAAC,GAAG,CAAC,2CAA2C,CAAC,CAAC;YAC3D,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,IAAI,CAAC,0CAA0C,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;gBACxE,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC;YAC5B,CAAC;QACH,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,IAAI,CAAC,iEAAiE,CAAC,CAAC;YAChF,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC;QAC5B,CAAC;IACH,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,OAAe;QACjC,IAAI,CAAC,IAAI,CAAC,YAAY,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;YACpC,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;QAC7C,CAAC;QACD,IAAI,CAAC;YACH,OAAO,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;QACnD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC;QACnC,CAAC;IACH,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,GAAW;QACvB,IAAI,CAAC,IAAI,CAAC,YAAY,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;YACpC,MAAM,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC;QAC7C,CAAC;QACD,IAAI,CAAC;YACH,OAAO,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACzC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CAAC,gBAAgB,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IAED,oBAAoB;QAClB,OAAO,IAAI,CAAC,YAAY,CAAC;IAC3B,CAAC;CACF,CAAA;AA5DY,0CAAe;0BAAf,eAAe;IAD3B,IAAA,mBAAU,GAAE;;GACA,eAAe,CA4D3B"}
|
||||
9
apps/api/dist/auth/google.strategy.d.ts
vendored
Normal file
9
apps/api/dist/auth/google.strategy.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
import { Strategy, VerifyCallback } from 'passport-google-oauth20';
|
||||
declare const GoogleStrategy_base: new (...args: [options: import("passport-google-oauth20").StrategyOptionsWithRequest] | [options: import("passport-google-oauth20").StrategyOptions] | [options: import("passport-google-oauth20").StrategyOptions] | [options: import("passport-google-oauth20").StrategyOptionsWithRequest]) => Strategy & {
|
||||
validate(...args: any[]): unknown;
|
||||
};
|
||||
export declare class GoogleStrategy extends GoogleStrategy_base {
|
||||
constructor();
|
||||
validate(accessToken: string, refreshToken: string, profile: any, done: VerifyCallback): Promise<any>;
|
||||
}
|
||||
export {};
|
||||
42
apps/api/dist/auth/google.strategy.js
vendored
Normal file
42
apps/api/dist/auth/google.strategy.js
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
"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.GoogleStrategy = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const passport_1 = require("@nestjs/passport");
|
||||
const passport_google_oauth20_1 = require("passport-google-oauth20");
|
||||
let GoogleStrategy = class GoogleStrategy extends (0, passport_1.PassportStrategy)(passport_google_oauth20_1.Strategy, 'google') {
|
||||
constructor() {
|
||||
super({
|
||||
clientID: process.env.GOOGLE_CLIENT_ID || '',
|
||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET || '',
|
||||
callbackURL: process.env.GOOGLE_CALLBACK_URL ||
|
||||
'http://localhost:3001/api/auth/google/callback',
|
||||
scope: ['email', 'profile'],
|
||||
});
|
||||
}
|
||||
async validate(accessToken, refreshToken, profile, done) {
|
||||
const { id, name, emails, photos } = profile;
|
||||
const user = {
|
||||
googleId: id,
|
||||
email: emails[0].value,
|
||||
name: name.givenName + ' ' + name.familyName,
|
||||
avatarUrl: photos[0]?.value,
|
||||
};
|
||||
done(null, user);
|
||||
}
|
||||
};
|
||||
exports.GoogleStrategy = GoogleStrategy;
|
||||
exports.GoogleStrategy = GoogleStrategy = __decorate([
|
||||
(0, common_1.Injectable)(),
|
||||
__metadata("design:paramtypes", [])
|
||||
], GoogleStrategy);
|
||||
//# sourceMappingURL=google.strategy.js.map
|
||||
1
apps/api/dist/auth/google.strategy.js.map
vendored
Normal file
1
apps/api/dist/auth/google.strategy.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"google.strategy.js","sourceRoot":"","sources":["../../src/auth/google.strategy.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA4C;AAC5C,+CAAoD;AACpD,qEAAmE;AAG5D,IAAM,cAAc,GAApB,MAAM,cAAe,SAAQ,IAAA,2BAAgB,EAAC,kCAAQ,EAAE,QAAQ,CAAC;IACtE;QACE,KAAK,CAAC;YACJ,QAAQ,EAAE,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,EAAE;YAC5C,YAAY,EAAE,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,EAAE;YACpD,WAAW,EACT,OAAO,CAAC,GAAG,CAAC,mBAAmB;gBAC/B,gDAAgD;YAClD,KAAK,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC;SAC5B,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,QAAQ,CACZ,WAAmB,EACnB,YAAoB,EACpB,OAAY,EACZ,IAAoB;QAEpB,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;QAE7C,MAAM,IAAI,GAAG;YACX,QAAQ,EAAE,EAAE;YACZ,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK;YACtB,IAAI,EAAE,IAAI,CAAC,SAAS,GAAG,GAAG,GAAG,IAAI,CAAC,UAAU;YAC5C,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK;SAC5B,CAAC;QAEF,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IACnB,CAAC;CACF,CAAA;AA7BY,wCAAc;yBAAd,cAAc;IAD1B,IAAA,mBAAU,GAAE;;GACA,cAAc,CA6B1B"}
|
||||
18
apps/api/dist/auth/jwt.strategy.d.ts
vendored
Normal file
18
apps/api/dist/auth/jwt.strategy.d.ts
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Strategy } from 'passport-jwt';
|
||||
export interface JwtPayload {
|
||||
sub: string;
|
||||
email: string;
|
||||
iat?: number;
|
||||
exp?: number;
|
||||
}
|
||||
declare const JwtStrategy_base: new (...args: [opt: import("passport-jwt").StrategyOptionsWithRequest] | [opt: import("passport-jwt").StrategyOptionsWithoutRequest]) => Strategy & {
|
||||
validate(...args: any[]): unknown;
|
||||
};
|
||||
export declare class JwtStrategy extends JwtStrategy_base {
|
||||
constructor();
|
||||
validate(payload: JwtPayload): Promise<{
|
||||
userId: string;
|
||||
email: string;
|
||||
}>;
|
||||
}
|
||||
export {};
|
||||
33
apps/api/dist/auth/jwt.strategy.js
vendored
Normal file
33
apps/api/dist/auth/jwt.strategy.js
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
"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.JwtStrategy = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const passport_1 = require("@nestjs/passport");
|
||||
const passport_jwt_1 = require("passport-jwt");
|
||||
let JwtStrategy = class JwtStrategy extends (0, passport_1.PassportStrategy)(passport_jwt_1.Strategy) {
|
||||
constructor() {
|
||||
super({
|
||||
jwtFromRequest: passport_jwt_1.ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
ignoreExpiration: false,
|
||||
secretOrKey: process.env.JWT_SECRET || 'your-secret-key-change-this',
|
||||
});
|
||||
}
|
||||
async validate(payload) {
|
||||
return { userId: payload.sub, email: payload.email };
|
||||
}
|
||||
};
|
||||
exports.JwtStrategy = JwtStrategy;
|
||||
exports.JwtStrategy = JwtStrategy = __decorate([
|
||||
(0, common_1.Injectable)(),
|
||||
__metadata("design:paramtypes", [])
|
||||
], JwtStrategy);
|
||||
//# sourceMappingURL=jwt.strategy.js.map
|
||||
1
apps/api/dist/auth/jwt.strategy.js.map
vendored
Normal file
1
apps/api/dist/auth/jwt.strategy.js.map
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"jwt.strategy.js","sourceRoot":"","sources":["../../src/auth/jwt.strategy.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA4C;AAC5C,+CAAoD;AACpD,+CAAoD;AAU7C,IAAM,WAAW,GAAjB,MAAM,WAAY,SAAQ,IAAA,2BAAgB,EAAC,uBAAQ,CAAC;IACzD;QACE,KAAK,CAAC;YACJ,cAAc,EAAE,yBAAU,CAAC,2BAA2B,EAAE;YACxD,gBAAgB,EAAE,KAAK;YACvB,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,6BAA6B;SACrE,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,OAAmB;QAChC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,GAAG,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC;IACvD,CAAC;CACF,CAAA;AAZY,kCAAW;sBAAX,WAAW;IADvB,IAAA,mBAAU,GAAE;;GACA,WAAW,CAYvB"}
|
||||
@@ -1,24 +1,28 @@
|
||||
import { CategoriesService } from '../categories/categories.service';
|
||||
import { CreateCategoryDto } from '../categories/dto/create-category.dto';
|
||||
interface RequestWithUser {
|
||||
user: {
|
||||
userId: string;
|
||||
};
|
||||
}
|
||||
export declare class CategoriesController {
|
||||
private readonly categoriesService;
|
||||
constructor(categoriesService: CategoriesService);
|
||||
private userId;
|
||||
create(createCategoryDto: CreateCategoryDto): Promise<{
|
||||
create(req: RequestWithUser, createCategoryDto: CreateCategoryDto): Promise<{
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
name: string;
|
||||
userId: string;
|
||||
}>;
|
||||
findAll(): Promise<{
|
||||
findAll(req: RequestWithUser): Promise<{
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
name: string;
|
||||
userId: string;
|
||||
}[]>;
|
||||
remove(id: string): Promise<{
|
||||
remove(req: RequestWithUser, id: string): Promise<{
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
@@ -26,3 +30,4 @@ export declare class CategoriesController {
|
||||
userId: string;
|
||||
}>;
|
||||
}
|
||||
export {};
|
||||
|
||||
@@ -16,51 +16,52 @@ exports.CategoriesController = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const categories_service_1 = require("../categories/categories.service");
|
||||
const create_category_dto_1 = require("../categories/dto/create-category.dto");
|
||||
const user_util_1 = require("../common/user.util");
|
||||
const auth_guard_1 = require("../auth/auth.guard");
|
||||
let CategoriesController = class CategoriesController {
|
||||
categoriesService;
|
||||
constructor(categoriesService) {
|
||||
this.categoriesService = categoriesService;
|
||||
}
|
||||
userId() {
|
||||
return (0, user_util_1.getTempUserId)();
|
||||
}
|
||||
create(createCategoryDto) {
|
||||
create(req, createCategoryDto) {
|
||||
return this.categoriesService.create({
|
||||
...createCategoryDto,
|
||||
userId: this.userId(),
|
||||
userId: req.user.userId,
|
||||
});
|
||||
}
|
||||
findAll() {
|
||||
return this.categoriesService.findAll(this.userId());
|
||||
findAll(req) {
|
||||
return this.categoriesService.findAll(req.user.userId);
|
||||
}
|
||||
remove(id) {
|
||||
return this.categoriesService.remove(id, this.userId());
|
||||
remove(req, id) {
|
||||
return this.categoriesService.remove(id, req.user.userId);
|
||||
}
|
||||
};
|
||||
exports.CategoriesController = CategoriesController;
|
||||
__decorate([
|
||||
(0, common_1.Post)(),
|
||||
__param(0, (0, common_1.Body)()),
|
||||
__param(0, (0, common_1.Req)()),
|
||||
__param(1, (0, common_1.Body)()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [create_category_dto_1.CreateCategoryDto]),
|
||||
__metadata("design:paramtypes", [Object, create_category_dto_1.CreateCategoryDto]),
|
||||
__metadata("design:returntype", void 0)
|
||||
], CategoriesController.prototype, "create", null);
|
||||
__decorate([
|
||||
(0, common_1.Get)(),
|
||||
__param(0, (0, common_1.Req)()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", []),
|
||||
__metadata("design:paramtypes", [Object]),
|
||||
__metadata("design:returntype", void 0)
|
||||
], CategoriesController.prototype, "findAll", null);
|
||||
__decorate([
|
||||
(0, common_1.Delete)(':id'),
|
||||
__param(0, (0, common_1.Param)('id')),
|
||||
__param(0, (0, common_1.Req)()),
|
||||
__param(1, (0, common_1.Param)('id')),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [String]),
|
||||
__metadata("design:paramtypes", [Object, String]),
|
||||
__metadata("design:returntype", void 0)
|
||||
], CategoriesController.prototype, "remove", null);
|
||||
exports.CategoriesController = CategoriesController = __decorate([
|
||||
(0, common_1.Controller)('categories'),
|
||||
(0, common_1.UseGuards)(auth_guard_1.AuthGuard),
|
||||
__metadata("design:paramtypes", [categories_service_1.CategoriesService])
|
||||
], CategoriesController);
|
||||
//# sourceMappingURL=categories.controller.js.map
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"categories.controller.js","sourceRoot":"","sources":["../../src/categories/categories.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAOwB;AACxB,yEAAqE;AACrE,+EAA0E;AAC1E,mDAAoD;AAG7C,IAAM,oBAAoB,GAA1B,MAAM,oBAAoB;IACF;IAA7B,YAA6B,iBAAoC;QAApC,sBAAiB,GAAjB,iBAAiB,CAAmB;IAAG,CAAC;IAE7D,MAAM;QACZ,OAAO,IAAA,yBAAa,GAAE,CAAC;IACzB,CAAC;IAGD,MAAM,CAAS,iBAAoC;QACjD,OAAO,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC;YACnC,GAAG,iBAAiB;YACpB,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE;SACtB,CAAC,CAAC;IACL,CAAC;IAGD,OAAO;QACL,OAAO,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;IACvD,CAAC;IAGD,MAAM,CAAc,EAAU;QAC5B,OAAO,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;IAC1D,CAAC;CACF,CAAA;AAxBY,oDAAoB;AAQ/B;IADC,IAAA,aAAI,GAAE;IACC,WAAA,IAAA,aAAI,GAAE,CAAA;;qCAAoB,uCAAiB;;kDAKlD;AAGD;IADC,IAAA,YAAG,GAAE;;;;mDAGL;AAGD;IADC,IAAA,eAAM,EAAC,KAAK,CAAC;IACN,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;;;;kDAElB;+BAvBU,oBAAoB;IADhC,IAAA,mBAAU,EAAC,YAAY,CAAC;qCAEyB,sCAAiB;GADtD,oBAAoB,CAwBhC"}
|
||||
{"version":3,"file":"categories.controller.js","sourceRoot":"","sources":["../../src/categories/categories.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAA4F;AAC5F,yEAAqE;AACrE,+EAA0E;AAC1E,mDAA+C;AAUxC,IAAM,oBAAoB,GAA1B,MAAM,oBAAoB;IACF;IAA7B,YAA6B,iBAAoC;QAApC,sBAAiB,GAAjB,iBAAiB,CAAmB;IAAG,CAAC;IAGrE,MAAM,CAAQ,GAAoB,EAAU,iBAAoC;QAC9E,OAAO,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC;YACnC,GAAG,iBAAiB;YACpB,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM;SACxB,CAAC,CAAC;IACL,CAAC;IAGD,OAAO,CAAQ,GAAoB;QACjC,OAAO,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACzD,CAAC;IAGD,MAAM,CAAQ,GAAoB,EAAe,EAAU;QACzD,OAAO,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,EAAE,EAAE,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC5D,CAAC;CACF,CAAA;AApBY,oDAAoB;AAI/B;IADC,IAAA,aAAI,GAAE;IACC,WAAA,IAAA,YAAG,GAAE,CAAA;IAAwB,WAAA,IAAA,aAAI,GAAE,CAAA;;6CAAoB,uCAAiB;;kDAK/E;AAGD;IADC,IAAA,YAAG,GAAE;IACG,WAAA,IAAA,YAAG,GAAE,CAAA;;;;mDAEb;AAGD;IADC,IAAA,eAAM,EAAC,KAAK,CAAC;IACN,WAAA,IAAA,YAAG,GAAE,CAAA;IAAwB,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;;;;kDAE/C;+BAnBU,oBAAoB;IAFhC,IAAA,mBAAU,EAAC,YAAY,CAAC;IACxB,IAAA,kBAAS,EAAC,sBAAS,CAAC;qCAE6B,sCAAiB;GADtD,oBAAoB,CAoBhC"}
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"categories.service.js","sourceRoot":"","sources":["../../src/categories/categories.service.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAAkF;AAClF,6DAAyD;AAIlD,IAAM,iBAAiB,GAAvB,MAAM,iBAAiB;IACR;IAApB,YAAoB,MAAqB;QAArB,WAAM,GAAN,MAAM,CAAe;IAAG,CAAC;IAE7C,KAAK,CAAC,MAAM,CAAC,IAA4C;QACvD,IAAI,CAAC;YACH,OAAO,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;gBACvC,IAAI,EAAE;oBACJ,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,MAAM,EAAE,IAAI,CAAC,MAAM;iBACpB;aACF,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;gBAC3B,MAAM,IAAI,0BAAiB,CAAC,yBAAyB,CAAC,CAAC;YACzD,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,MAAc;QAC1B,OAAO,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC;YACnC,KAAK,EAAE,EAAE,MAAM,EAAE;YACjB,OAAO,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE;SACzB,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,EAAU,EAAE,MAAc;QACrC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC;YACpD,KAAK,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE;SACtB,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,0BAAiB,CAAC,oBAAoB,CAAC,CAAC;QACpD,CAAC;QAED,OAAO,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;YACjC,KAAK,EAAE,EAAE,EAAE,EAAE;SACd,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,KAAe,EAAE,MAAc;QAChD,MAAM,UAAU,GAAU,EAAE,CAAC;QAE7B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC;gBAClD,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;aACxB,CAAC,CAAC;YAEH,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;oBAC3C,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;iBACvB,CAAC,CAAC;YACL,CAAC;YAED,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC5B,CAAC;QAED,OAAO,UAAU,CAAC;IACpB,CAAC;CACF,CAAA;AA3DY,8CAAiB;4BAAjB,iBAAiB;IAD7B,IAAA,mBAAU,GAAE;qCAEiB,8BAAa;GAD9B,iBAAiB,CA2D7B"}
|
||||
{"version":3,"file":"categories.service.js","sourceRoot":"","sources":["../../src/categories/categories.service.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAIwB;AACxB,6DAAyD;AAIlD,IAAM,iBAAiB,GAAvB,MAAM,iBAAiB;IACR;IAApB,YAAoB,MAAqB;QAArB,WAAM,GAAN,MAAM,CAAe;IAAG,CAAC;IAE7C,KAAK,CAAC,MAAM,CAAC,IAA4C;QACvD,IAAI,CAAC;YACH,OAAO,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;gBACvC,IAAI,EAAE;oBACJ,IAAI,EAAE,IAAI,CAAC,IAAI;oBACf,MAAM,EAAE,IAAI,CAAC,MAAM;iBACpB;aACF,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;gBAC3B,MAAM,IAAI,0BAAiB,CAAC,yBAAyB,CAAC,CAAC;YACzD,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,MAAc;QAC1B,OAAO,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,QAAQ,CAAC;YACnC,KAAK,EAAE,EAAE,MAAM,EAAE;YACjB,OAAO,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE;SACzB,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,EAAU,EAAE,MAAc;QACrC,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC;YACpD,KAAK,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE;SACtB,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,0BAAiB,CAAC,oBAAoB,CAAC,CAAC;QACpD,CAAC;QAED,OAAO,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;YACjC,KAAK,EAAE,EAAE,EAAE,EAAE;SACd,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,KAAe,EAAE,MAAc;QAChD,MAAM,UAAU,GAAU,EAAE,CAAC;QAE7B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC;gBAClD,KAAK,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;aACxB,CAAC,CAAC;YAEH,IAAI,CAAC,QAAQ,EAAE,CAAC;gBACd,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;oBAC3C,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;iBACvB,CAAC,CAAC;YACL,CAAC;YAED,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC5B,CAAC;QAED,OAAO,UAAU,CAAC;IACpB,CAAC;CACF,CAAA;AA3DY,8CAAiB;4BAAjB,iBAAiB;IAD7B,IAAA,mBAAU,GAAE;qCAEiB,8BAAa;GAD9B,iBAAiB,CA2D7B"}
|
||||
2
apps/api/dist/common/user.util.js.map
vendored
2
apps/api/dist/common/user.util.js.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"user.util.js","sourceRoot":"","sources":["../../src/common/user.util.ts"],"names":[],"mappings":";;AAAA,sCAMG;AAEH,oDAQC;AAED,kDAKC;AAvBD,SAAgB,aAAa;IACzB,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,IAAI,EAAE,CAAC;IAC5C,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,MAAM,IAAI,KAAK,CAAC,mEAAmE,CAAC,CAAC;IACvF,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAEH,SAAgB,oBAAoB,CAAC,OAAY;IAE/C,IAAI,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC;QACtB,OAAO,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC;IAC1B,CAAC;IAGD,OAAO,aAAa,EAAE,CAAC;AACzB,CAAC;AAED,SAAgB,mBAAmB;IACjC,OAAO,CAAC,MAAW,EAAE,WAAmB,EAAE,UAA8B,EAAE,EAAE;IAG5E,CAAC,CAAC;AACJ,CAAC"}
|
||||
{"version":3,"file":"user.util.js","sourceRoot":"","sources":["../../src/common/user.util.ts"],"names":[],"mappings":";;AAAA,sCAQC;AAED,oDAQC;AAED,kDAKC;AAzBD,SAAgB,aAAa;IAC3B,MAAM,EAAE,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,IAAI,EAAE,CAAC;IAC5C,IAAI,CAAC,EAAE,EAAE,CAAC;QACR,MAAM,IAAI,KAAK,CACb,mEAAmE,CACpE,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,SAAgB,oBAAoB,CAAC,OAAY;IAE/C,IAAI,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE,CAAC;QACtB,OAAO,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC;IAC1B,CAAC;IAGD,OAAO,aAAa,EAAE,CAAC;AACzB,CAAC;AAED,SAAgB,mBAAmB;IACjC,OAAO,CAAC,MAAW,EAAE,WAAmB,EAAE,UAA8B,EAAE,EAAE;IAG5E,CAAC,CAAC;AACJ,CAAC"}
|
||||
6
apps/api/dist/main.js
vendored
6
apps/api/dist/main.js
vendored
@@ -2,8 +2,10 @@
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const core_1 = require("@nestjs/core");
|
||||
const app_module_1 = require("./app.module");
|
||||
const path_1 = require("path");
|
||||
async function bootstrap() {
|
||||
const app = await core_1.NestFactory.create(app_module_1.AppModule);
|
||||
app.useStaticAssets((0, path_1.join)(__dirname, '..', 'public'));
|
||||
const webOrigin = process.env.WEB_APP_URL ?? 'http://localhost:5173';
|
||||
app.enableCors({
|
||||
origin: webOrigin,
|
||||
@@ -12,7 +14,7 @@ async function bootstrap() {
|
||||
app.setGlobalPrefix('api');
|
||||
const port = process.env.PORT ? Number(process.env.PORT) : 3000;
|
||||
await app.listen(port);
|
||||
console.log(`API listening on http://localhost:${port}`);
|
||||
console.log(`API listening on ${await app.getUrl()}`);
|
||||
}
|
||||
bootstrap();
|
||||
void bootstrap();
|
||||
//# sourceMappingURL=main.js.map
|
||||
2
apps/api/dist/main.js.map
vendored
2
apps/api/dist/main.js.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";;AAAA,uCAA2C;AAC3C,6CAAyC;AAEzC,KAAK,UAAU,SAAS;IACtB,MAAM,GAAG,GAAG,MAAM,kBAAW,CAAC,MAAM,CAAC,sBAAS,CAAC,CAAC;IAGhD,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,uBAAuB,CAAC;IACrE,GAAG,CAAC,UAAU,CAAC;QACb,MAAM,EAAE,SAAS;QACjB,WAAW,EAAE,IAAI;KAClB,CAAC,CAAC;IAGH,GAAG,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;IAE3B,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAChE,MAAM,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAEvB,OAAO,CAAC,GAAG,CAAC,qCAAqC,IAAI,EAAE,CAAC,CAAC;AAC3D,CAAC;AACD,SAAS,EAAE,CAAC"}
|
||||
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";;AAAA,uCAA2C;AAC3C,6CAAyC;AAEzC,+BAA4B;AAE5B,KAAK,UAAU,SAAS;IACtB,MAAM,GAAG,GAAG,MAAM,kBAAW,CAAC,MAAM,CAAyB,sBAAS,CAAC,CAAC;IAGxE,GAAG,CAAC,eAAe,CAAC,IAAA,WAAI,EAAC,SAAS,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;IAGrD,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,uBAAuB,CAAC;IACrE,GAAG,CAAC,UAAU,CAAC;QACb,MAAM,EAAE,SAAS;QACjB,WAAW,EAAE,IAAI;KAClB,CAAC,CAAC;IAGH,GAAG,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;IAE3B,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAChE,MAAM,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAEvB,OAAO,CAAC,GAAG,CAAC,oBAAoB,MAAM,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACxD,CAAC;AAED,KAAK,SAAS,EAAE,CAAC"}
|
||||
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
15
apps/api/dist/seed.js
vendored
15
apps/api/dist/seed.js
vendored
@@ -2,18 +2,21 @@
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const client_1 = require("@prisma/client");
|
||||
const prisma = new client_1.PrismaClient();
|
||||
const adminSeeder = {
|
||||
email: 'dwindi.ramadhana@gmail.com',
|
||||
password: 'tabungin2k25!@#',
|
||||
};
|
||||
const TEMP_USER_ID = process.env.TEMP_USER_ID || '16b74848-daa3-4dc9-8de2-3cf59e08f8e3';
|
||||
async function main() {
|
||||
const userId = '16b74848-daa3-4dc9-8de2-3cf59e08f8e3';
|
||||
const user = await prisma.user.upsert({
|
||||
where: { id: userId },
|
||||
where: { id: TEMP_USER_ID },
|
||||
update: {},
|
||||
create: {
|
||||
id: userId,
|
||||
id: TEMP_USER_ID,
|
||||
email: 'temp@example.com',
|
||||
},
|
||||
});
|
||||
const existing = await prisma.wallet.findFirst({
|
||||
where: { userId: user.id, kind: 'money' },
|
||||
});
|
||||
const existing = await prisma.wallet.findFirst({});
|
||||
if (!existing) {
|
||||
await prisma.wallet.create({
|
||||
data: {
|
||||
|
||||
2
apps/api/dist/seed.js.map
vendored
2
apps/api/dist/seed.js.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"seed.js","sourceRoot":"","sources":["../src/seed.ts"],"names":[],"mappings":";;AAAA,2CAA8C;AAE9C,MAAM,MAAM,GAAG,IAAI,qBAAY,EAAE,CAAC;AAElC,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,sCAAsC,CAAC;IACtD,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;QACpC,KAAK,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE;QACrB,MAAM,EAAE,EAAE;QACV,MAAM,EAAE;YACN,EAAE,EAAE,MAAM;SACX;KACF,CAAC,CAAC;IAGH,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC;QAC7C,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE;KAC1C,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;YACzB,IAAI,EAAE;gBACJ,MAAM,EAAE,IAAI,CAAC,EAAE;gBACf,IAAI,EAAE,OAAO;gBACb,IAAI,EAAE,MAAM;gBACZ,QAAQ,EAAE,KAAK;aAChB;SACF,CAAC,CAAC;IACL,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,8BAA8B,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;AACvD,CAAC;AAED,IAAI,EAAE;KACH,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;IACX,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACjB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC;KACD,OAAO,CAAC,KAAK,IAAI,EAAE;IAClB,MAAM,MAAM,CAAC,WAAW,EAAE,CAAC;AAC7B,CAAC,CAAC,CAAC"}
|
||||
{"version":3,"file":"seed.js","sourceRoot":"","sources":["../src/seed.ts"],"names":[],"mappings":";;AAAA,2CAA8C;AAC9C,MAAM,MAAM,GAAG,IAAI,qBAAY,EAAE,CAAC;AAElC,MAAM,WAAW,GAAG;IAClB,KAAK,EAAE,4BAA4B;IACnC,QAAQ,EAAE,iBAAiB;CAC5B,CAAA;AAED,MAAM,YAAY,GAChB,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,sCAAsC,CAAC;AAErE,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;QACpC,KAAK,EAAE,EAAE,EAAE,EAAE,YAAY,EAAE;QAC3B,MAAM,EAAE,EAAE;QACV,MAAM,EAAE;YACN,EAAE,EAAE,YAAY;YAChB,KAAK,EAAE,kBAAkB;SAC1B;KACF,CAAC,CAAC;IAGH,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IAEnD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;YACzB,IAAI,EAAE;gBACJ,MAAM,EAAE,IAAI,CAAC,EAAE;gBACf,IAAI,EAAE,OAAO;gBACb,IAAI,EAAE,MAAM;gBACZ,QAAQ,EAAE,KAAK;aAChB;SACF,CAAC,CAAC;IACL,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,8BAA8B,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;AACvD,CAAC;AAED,IAAI,EAAE;KACH,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;IACX,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACjB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC;KACD,OAAO,CAAC,KAAK,IAAI,EAAE;IAClB,MAAM,MAAM,CAAC,WAAW,EAAE,CAAC;AAC7B,CAAC,CAAC,CAAC"}
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"transaction.dto.js","sourceRoot":"","sources":["../../src/transactions/transaction.dto.ts"],"names":[],"mappings":";;;AAAA,6BAAwB;AAEX,QAAA,uBAAuB,GAAG,OAAC,CAAC,MAAM,CAAC;IAC9C,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IACxC,SAAS,EAAE,OAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,EAAE;IAC1C,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IACtC,QAAQ,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IACjD,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;CAC9C,CAAC,CAAC"}
|
||||
{"version":3,"file":"transaction.dto.js","sourceRoot":"","sources":["../../src/transactions/transaction.dto.ts"],"names":[],"mappings":";;;AAAA,6BAAwB;AAEX,QAAA,uBAAuB,GAAG,OAAC,CAAC,MAAM,CAAC;IAC9C,MAAM,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IACxC,SAAS,EAAE,OAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,QAAQ,EAAE;IAC3C,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IACtC,QAAQ,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IACjD,IAAI,EAAE,OAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;CAC9C,CAAC,CAAC"}
|
||||
@@ -1,9 +1,14 @@
|
||||
import type { Response } from 'express';
|
||||
import { TransactionsService } from './transactions.service';
|
||||
interface RequestWithUser {
|
||||
user: {
|
||||
userId: string;
|
||||
};
|
||||
}
|
||||
export declare class TransactionsController {
|
||||
private readonly tx;
|
||||
constructor(tx: TransactionsService);
|
||||
list(walletId: string): import("@prisma/client").Prisma.PrismaPromise<{
|
||||
list(req: RequestWithUser, walletId: string): import("@prisma/client").Prisma.PrismaPromise<{
|
||||
category: string | null;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
@@ -15,7 +20,7 @@ export declare class TransactionsController {
|
||||
walletId: string;
|
||||
recurrenceId: string | null;
|
||||
}[]>;
|
||||
create(walletId: string, body: {
|
||||
create(req: RequestWithUser, walletId: string, body: {
|
||||
amount: number | string;
|
||||
direction: 'in' | 'out';
|
||||
date?: string;
|
||||
@@ -33,8 +38,8 @@ export declare class TransactionsController {
|
||||
walletId: string;
|
||||
recurrenceId: string | null;
|
||||
}>;
|
||||
exportCsv(walletId: string, from: string | undefined, to: string | undefined, category: string | undefined, direction: 'in' | 'out' | undefined, res: Response): Promise<void>;
|
||||
update(walletId: string, id: string, body: unknown): Promise<{
|
||||
exportCsv(req: RequestWithUser, walletId: string, from: string | undefined, to: string | undefined, category: string | undefined, direction: 'in' | 'out' | undefined, res: Response): Promise<void>;
|
||||
update(req: RequestWithUser, walletId: string, id: string, body: unknown): Promise<{
|
||||
category: string | null;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
@@ -46,7 +51,7 @@ export declare class TransactionsController {
|
||||
walletId: string;
|
||||
recurrenceId: string | null;
|
||||
}>;
|
||||
delete(walletId: string, id: string): Promise<{
|
||||
delete(req: RequestWithUser, walletId: string, id: string): Promise<{
|
||||
category: string | null;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
@@ -59,3 +64,4 @@ export declare class TransactionsController {
|
||||
recurrenceId: string | null;
|
||||
}>;
|
||||
}
|
||||
export {};
|
||||
|
||||
@@ -14,6 +14,7 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.TransactionsController = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const auth_guard_1 = require("../auth/auth.guard");
|
||||
const transactions_service_1 = require("./transactions.service");
|
||||
const transaction_dto_1 = require("./transaction.dto");
|
||||
let TransactionsController = class TransactionsController {
|
||||
@@ -21,14 +22,19 @@ let TransactionsController = class TransactionsController {
|
||||
constructor(tx) {
|
||||
this.tx = tx;
|
||||
}
|
||||
list(walletId) {
|
||||
return this.tx.list(walletId);
|
||||
list(req, walletId) {
|
||||
return this.tx.list(req.user.userId, walletId);
|
||||
}
|
||||
create(walletId, body) {
|
||||
return this.tx.create(walletId, body);
|
||||
create(req, walletId, body) {
|
||||
return this.tx.create(req.user.userId, walletId, body);
|
||||
}
|
||||
async exportCsv(walletId, from, to, category, direction, res) {
|
||||
const rows = await this.tx.listWithFilters(walletId, { from, to, category, direction });
|
||||
async exportCsv(req, walletId, from, to, category, direction, res) {
|
||||
const rows = await this.tx.listWithFilters(req.user.userId, walletId, {
|
||||
from,
|
||||
to,
|
||||
category,
|
||||
direction,
|
||||
});
|
||||
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
||||
res.setHeader('Content-Disposition', `attachment; filename="transactions_${walletId}.csv"`);
|
||||
res.write(`date,category,memo,direction,amount\n`);
|
||||
@@ -50,66 +56,73 @@ let TransactionsController = class TransactionsController {
|
||||
}
|
||||
res.end();
|
||||
}
|
||||
async update(walletId, id, body) {
|
||||
async update(req, walletId, id, body) {
|
||||
try {
|
||||
const parsed = transaction_dto_1.TransactionUpdateSchema.parse(body);
|
||||
return this.tx.update(walletId, id, parsed);
|
||||
return this.tx.update(req.user.userId, walletId, id, parsed);
|
||||
}
|
||||
catch (e) {
|
||||
throw new common_1.BadRequestException(e?.errors ?? 'Invalid payload');
|
||||
const error = e;
|
||||
throw new common_1.BadRequestException(error?.errors ?? 'Invalid payload');
|
||||
}
|
||||
}
|
||||
delete(walletId, id) {
|
||||
return this.tx.delete(walletId, id);
|
||||
delete(req, walletId, id) {
|
||||
return this.tx.delete(req.user.userId, walletId, id);
|
||||
}
|
||||
};
|
||||
exports.TransactionsController = TransactionsController;
|
||||
__decorate([
|
||||
(0, common_1.Get)(),
|
||||
__param(0, (0, common_1.Param)('walletId')),
|
||||
__param(0, (0, common_1.Req)()),
|
||||
__param(1, (0, common_1.Param)('walletId')),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [String]),
|
||||
__metadata("design:paramtypes", [Object, String]),
|
||||
__metadata("design:returntype", void 0)
|
||||
], TransactionsController.prototype, "list", null);
|
||||
__decorate([
|
||||
(0, common_1.Post)(),
|
||||
__param(0, (0, common_1.Param)('walletId')),
|
||||
__param(1, (0, common_1.Body)()),
|
||||
__param(0, (0, common_1.Req)()),
|
||||
__param(1, (0, common_1.Param)('walletId')),
|
||||
__param(2, (0, common_1.Body)()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [String, Object]),
|
||||
__metadata("design:paramtypes", [Object, String, Object]),
|
||||
__metadata("design:returntype", void 0)
|
||||
], TransactionsController.prototype, "create", null);
|
||||
__decorate([
|
||||
(0, common_1.Get)('export.csv'),
|
||||
__param(0, (0, common_1.Param)('walletId')),
|
||||
__param(1, (0, common_1.Query)('from')),
|
||||
__param(2, (0, common_1.Query)('to')),
|
||||
__param(3, (0, common_1.Query)('category')),
|
||||
__param(4, (0, common_1.Query)('direction')),
|
||||
__param(5, (0, common_1.Res)()),
|
||||
__param(0, (0, common_1.Req)()),
|
||||
__param(1, (0, common_1.Param)('walletId')),
|
||||
__param(2, (0, common_1.Query)('from')),
|
||||
__param(3, (0, common_1.Query)('to')),
|
||||
__param(4, (0, common_1.Query)('category')),
|
||||
__param(5, (0, common_1.Query)('direction')),
|
||||
__param(6, (0, common_1.Res)()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [String, Object, Object, Object, Object, Object]),
|
||||
__metadata("design:paramtypes", [Object, String, Object, Object, Object, Object, Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], TransactionsController.prototype, "exportCsv", null);
|
||||
__decorate([
|
||||
(0, common_1.Put)(':id'),
|
||||
__param(0, (0, common_1.Param)('walletId')),
|
||||
__param(1, (0, common_1.Param)('id')),
|
||||
__param(2, (0, common_1.Body)()),
|
||||
__param(0, (0, common_1.Req)()),
|
||||
__param(1, (0, common_1.Param)('walletId')),
|
||||
__param(2, (0, common_1.Param)('id')),
|
||||
__param(3, (0, common_1.Body)()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [String, String, Object]),
|
||||
__metadata("design:paramtypes", [Object, String, String, Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], TransactionsController.prototype, "update", null);
|
||||
__decorate([
|
||||
(0, common_1.Delete)(':id'),
|
||||
__param(0, (0, common_1.Param)('walletId')),
|
||||
__param(1, (0, common_1.Param)('id')),
|
||||
__param(0, (0, common_1.Req)()),
|
||||
__param(1, (0, common_1.Param)('walletId')),
|
||||
__param(2, (0, common_1.Param)('id')),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [String, String]),
|
||||
__metadata("design:paramtypes", [Object, String, String]),
|
||||
__metadata("design:returntype", void 0)
|
||||
], TransactionsController.prototype, "delete", null);
|
||||
exports.TransactionsController = TransactionsController = __decorate([
|
||||
(0, common_1.Controller)('wallets/:walletId/transactions'),
|
||||
(0, common_1.UseGuards)(auth_guard_1.AuthGuard),
|
||||
__metadata("design:paramtypes", [transactions_service_1.TransactionsService])
|
||||
], TransactionsController);
|
||||
//# sourceMappingURL=transactions.controller.js.map
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"transactions.controller.js","sourceRoot":"","sources":["../../src/transactions/transactions.controller.ts"],"names":[],"mappings":"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"}
|
||||
{"version":3,"file":"transactions.controller.js","sourceRoot":"","sources":["../../src/transactions/transactions.controller.ts"],"names":[],"mappings":"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"}
|
||||
@@ -11,12 +11,13 @@ const common_1 = require("@nestjs/common");
|
||||
const transactions_service_1 = require("./transactions.service");
|
||||
const transactions_controller_1 = require("./transactions.controller");
|
||||
const prisma_module_1 = require("../prisma/prisma.module");
|
||||
const otp_module_1 = require("../otp/otp.module");
|
||||
let TransactionsModule = class TransactionsModule {
|
||||
};
|
||||
exports.TransactionsModule = TransactionsModule;
|
||||
exports.TransactionsModule = TransactionsModule = __decorate([
|
||||
(0, common_1.Module)({
|
||||
imports: [prisma_module_1.PrismaModule],
|
||||
imports: [prisma_module_1.PrismaModule, otp_module_1.OtpModule],
|
||||
providers: [transactions_service_1.TransactionsService],
|
||||
controllers: [transactions_controller_1.TransactionsController],
|
||||
exports: [transactions_service_1.TransactionsService],
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"transactions.module.js","sourceRoot":"","sources":["../../src/transactions/transactions.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,iEAA6D;AAC7D,uEAAmE;AACnE,2DAAuD;AAQhD,IAAM,kBAAkB,GAAxB,MAAM,kBAAkB;CAAG,CAAA;AAArB,gDAAkB;6BAAlB,kBAAkB;IAN9B,IAAA,eAAM,EAAC;QACN,OAAO,EAAE,CAAC,4BAAY,CAAC;QACvB,SAAS,EAAE,CAAC,0CAAmB,CAAC;QAChC,WAAW,EAAE,CAAC,gDAAsB,CAAC;QACrC,OAAO,EAAE,CAAC,0CAAmB,CAAC;KAC/B,CAAC;GACW,kBAAkB,CAAG"}
|
||||
{"version":3,"file":"transactions.module.js","sourceRoot":"","sources":["../../src/transactions/transactions.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,2CAAwC;AACxC,iEAA6D;AAC7D,uEAAmE;AACnE,2DAAuD;AACvD,kDAA8C;AAQvC,IAAM,kBAAkB,GAAxB,MAAM,kBAAkB;CAAG,CAAA;AAArB,gDAAkB;6BAAlB,kBAAkB;IAN9B,IAAA,eAAM,EAAC;QACN,OAAO,EAAE,CAAC,4BAAY,EAAE,sBAAS,CAAC;QAClC,SAAS,EAAE,CAAC,0CAAmB,CAAC;QAChC,WAAW,EAAE,CAAC,gDAAsB,CAAC;QACrC,OAAO,EAAE,CAAC,0CAAmB,CAAC;KAC/B,CAAC;GACW,kBAAkB,CAAG"}
|
||||
@@ -4,8 +4,7 @@ import type { TransactionUpdateDto } from './transaction.dto';
|
||||
export declare class TransactionsService {
|
||||
private prisma;
|
||||
constructor(prisma: PrismaService);
|
||||
private userId;
|
||||
list(walletId: string): Prisma.PrismaPromise<{
|
||||
list(userId: string, walletId: string): Prisma.PrismaPromise<{
|
||||
category: string | null;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
@@ -17,7 +16,7 @@ export declare class TransactionsService {
|
||||
walletId: string;
|
||||
recurrenceId: string | null;
|
||||
}[]>;
|
||||
listAll(): Prisma.PrismaPromise<{
|
||||
listAll(userId: string): Prisma.PrismaPromise<{
|
||||
category: string | null;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
@@ -29,7 +28,7 @@ export declare class TransactionsService {
|
||||
walletId: string;
|
||||
recurrenceId: string | null;
|
||||
}[]>;
|
||||
listWithFilters(walletId: string, filters: {
|
||||
listWithFilters(userId: string, walletId: string, filters: {
|
||||
from?: string;
|
||||
to?: string;
|
||||
category?: string;
|
||||
@@ -46,7 +45,7 @@ export declare class TransactionsService {
|
||||
walletId: string;
|
||||
recurrenceId: string | null;
|
||||
}[]>;
|
||||
create(walletId: string, input: {
|
||||
create(userId: string, walletId: string, input: {
|
||||
amount: string | number;
|
||||
direction: 'in' | 'out';
|
||||
date?: string;
|
||||
@@ -64,7 +63,7 @@ export declare class TransactionsService {
|
||||
walletId: string;
|
||||
recurrenceId: string | null;
|
||||
}>;
|
||||
update(walletId: string, id: string, dto: TransactionUpdateDto): Promise<{
|
||||
update(userId: string, walletId: string, id: string, dto: TransactionUpdateDto): Promise<{
|
||||
category: string | null;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
@@ -76,7 +75,7 @@ export declare class TransactionsService {
|
||||
walletId: string;
|
||||
recurrenceId: string | null;
|
||||
}>;
|
||||
delete(walletId: string, id: string): Promise<{
|
||||
delete(userId: string, walletId: string, id: string): Promise<{
|
||||
category: string | null;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
|
||||
@@ -12,32 +12,28 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.TransactionsService = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const prisma_service_1 = require("../prisma/prisma.service");
|
||||
const user_util_1 = require("../common/user.util");
|
||||
let TransactionsService = class TransactionsService {
|
||||
prisma;
|
||||
constructor(prisma) {
|
||||
this.prisma = prisma;
|
||||
}
|
||||
userId() {
|
||||
return (0, user_util_1.getTempUserId)();
|
||||
}
|
||||
list(walletId) {
|
||||
list(userId, walletId) {
|
||||
return this.prisma.transaction.findMany({
|
||||
where: { userId: this.userId(), walletId },
|
||||
where: { userId, walletId },
|
||||
orderBy: { date: 'desc' },
|
||||
take: 200,
|
||||
});
|
||||
}
|
||||
listAll() {
|
||||
listAll(userId) {
|
||||
return this.prisma.transaction.findMany({
|
||||
where: { userId: this.userId() },
|
||||
where: { userId },
|
||||
orderBy: { date: 'desc' },
|
||||
take: 1000,
|
||||
});
|
||||
}
|
||||
listWithFilters(walletId, filters) {
|
||||
listWithFilters(userId, walletId, filters) {
|
||||
const where = {
|
||||
userId: (0, user_util_1.getTempUserId)(),
|
||||
userId,
|
||||
walletId,
|
||||
};
|
||||
if (filters.direction)
|
||||
@@ -56,20 +52,20 @@ let TransactionsService = class TransactionsService {
|
||||
orderBy: { date: 'desc' },
|
||||
});
|
||||
}
|
||||
async create(walletId, input) {
|
||||
async create(userId, walletId, input) {
|
||||
const amountNum = typeof input.amount === 'string' ? Number(input.amount) : input.amount;
|
||||
if (!Number.isFinite(amountNum))
|
||||
throw new Error('amount must be a number');
|
||||
const date = input.date ? new Date(input.date) : new Date();
|
||||
const wallet = await this.prisma.wallet.findFirst({
|
||||
where: { id: walletId, userId: this.userId(), deletedAt: null },
|
||||
where: { id: walletId, userId, deletedAt: null },
|
||||
select: { id: true },
|
||||
});
|
||||
if (!wallet)
|
||||
throw new Error('wallet not found');
|
||||
return this.prisma.transaction.create({
|
||||
data: {
|
||||
userId: this.userId(),
|
||||
userId,
|
||||
walletId,
|
||||
amount: amountNum,
|
||||
direction: input.direction,
|
||||
@@ -79,9 +75,9 @@ let TransactionsService = class TransactionsService {
|
||||
},
|
||||
});
|
||||
}
|
||||
async update(walletId, id, dto) {
|
||||
async update(userId, walletId, id, dto) {
|
||||
const existing = await this.prisma.transaction.findFirst({
|
||||
where: { id, walletId, userId: this.userId() },
|
||||
where: { id, walletId, userId },
|
||||
});
|
||||
if (!existing)
|
||||
throw new Error('transaction not found');
|
||||
@@ -101,9 +97,9 @@ let TransactionsService = class TransactionsService {
|
||||
data,
|
||||
});
|
||||
}
|
||||
async delete(walletId, id) {
|
||||
async delete(userId, walletId, id) {
|
||||
const existing = await this.prisma.transaction.findFirst({
|
||||
where: { id, walletId, userId: this.userId() },
|
||||
where: { id, walletId, userId },
|
||||
});
|
||||
if (!existing)
|
||||
throw new Error('transaction not found');
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"transactions.service.js","sourceRoot":"","sources":["../../src/transactions/transactions.service.ts"],"names":[],"mappings":"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"}
|
||||
{"version":3,"file":"transactions.service.js","sourceRoot":"","sources":["../../src/transactions/transactions.service.ts"],"names":[],"mappings":"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"}
|
||||
2
apps/api/dist/tsconfig.build.tsbuildinfo
vendored
2
apps/api/dist/tsconfig.build.tsbuildinfo
vendored
File diff suppressed because one or more lines are too long
40
apps/api/dist/users/users.controller.d.ts
vendored
40
apps/api/dist/users/users.controller.d.ts
vendored
@@ -1,16 +1,54 @@
|
||||
import { UsersService } from './users.service';
|
||||
interface RequestWithUser extends Request {
|
||||
user: {
|
||||
userId: string;
|
||||
email: string;
|
||||
};
|
||||
}
|
||||
export declare class UsersController {
|
||||
private readonly users;
|
||||
constructor(users: UsersService);
|
||||
me(): Promise<{
|
||||
id: string;
|
||||
email: string | null;
|
||||
email: string;
|
||||
phone: string | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
status: string;
|
||||
emailVerified: boolean;
|
||||
passwordHash: string | null;
|
||||
name: string | null;
|
||||
avatarUrl: string | null;
|
||||
defaultCurrency: string | null;
|
||||
timeZone: string | null;
|
||||
otpEmailEnabled: boolean;
|
||||
otpWhatsappEnabled: boolean;
|
||||
otpTotpEnabled: boolean;
|
||||
otpTotpSecret: string | null;
|
||||
} | null>;
|
||||
updateProfile(req: RequestWithUser, body: {
|
||||
name?: string;
|
||||
phone?: string;
|
||||
}): Promise<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
user: {
|
||||
id: string;
|
||||
email: string;
|
||||
phone: string | null;
|
||||
name: string | null;
|
||||
avatarUrl: string | null;
|
||||
};
|
||||
}>;
|
||||
getAuthInfo(req: RequestWithUser): Promise<{
|
||||
hasGoogleAuth: boolean;
|
||||
hasPassword: boolean;
|
||||
}>;
|
||||
deleteAccount(req: RequestWithUser, body: {
|
||||
password: string;
|
||||
}): Promise<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
}>;
|
||||
}
|
||||
export {};
|
||||
|
||||
37
apps/api/dist/users/users.controller.js
vendored
37
apps/api/dist/users/users.controller.js
vendored
@@ -8,9 +8,13 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
||||
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.UsersController = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const auth_guard_1 = require("../auth/auth.guard");
|
||||
const users_service_1 = require("./users.service");
|
||||
let UsersController = class UsersController {
|
||||
users;
|
||||
@@ -20,6 +24,15 @@ let UsersController = class UsersController {
|
||||
me() {
|
||||
return this.users.me();
|
||||
}
|
||||
async updateProfile(req, body) {
|
||||
return this.users.updateProfile(req.user.userId, body);
|
||||
}
|
||||
async getAuthInfo(req) {
|
||||
return this.users.getAuthInfo(req.user.userId);
|
||||
}
|
||||
async deleteAccount(req, body) {
|
||||
return this.users.deleteAccount(req.user.userId, body.password);
|
||||
}
|
||||
};
|
||||
exports.UsersController = UsersController;
|
||||
__decorate([
|
||||
@@ -28,8 +41,32 @@ __decorate([
|
||||
__metadata("design:paramtypes", []),
|
||||
__metadata("design:returntype", void 0)
|
||||
], UsersController.prototype, "me", null);
|
||||
__decorate([
|
||||
(0, common_1.Put)('profile'),
|
||||
__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)
|
||||
], UsersController.prototype, "updateProfile", null);
|
||||
__decorate([
|
||||
(0, common_1.Get)('auth-info'),
|
||||
__param(0, (0, common_1.Req)()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], UsersController.prototype, "getAuthInfo", null);
|
||||
__decorate([
|
||||
(0, common_1.Delete)('account'),
|
||||
__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)
|
||||
], UsersController.prototype, "deleteAccount", null);
|
||||
exports.UsersController = UsersController = __decorate([
|
||||
(0, common_1.Controller)('users'),
|
||||
(0, common_1.UseGuards)(auth_guard_1.AuthGuard),
|
||||
__metadata("design:paramtypes", [users_service_1.UsersService])
|
||||
], UsersController);
|
||||
//# sourceMappingURL=users.controller.js.map
|
||||
2
apps/api/dist/users/users.controller.js.map
vendored
2
apps/api/dist/users/users.controller.js.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"users.controller.js","sourceRoot":"","sources":["../../src/users/users.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAAiD;AACjD,mDAA+C;AAGxC,IAAM,eAAe,GAArB,MAAM,eAAe;IACG;IAA7B,YAA6B,KAAmB;QAAnB,UAAK,GAAL,KAAK,CAAc;IAAG,CAAC;IAGpD,EAAE;QACA,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;IACzB,CAAC;CACF,CAAA;AAPY,0CAAe;AAI1B;IADC,IAAA,YAAG,EAAC,IAAI,CAAC;;;;yCAGT;0BANU,eAAe;IAD3B,IAAA,mBAAU,EAAC,OAAO,CAAC;qCAEkB,4BAAY;GADrC,eAAe,CAO3B"}
|
||||
{"version":3,"file":"users.controller.js","sourceRoot":"","sources":["../../src/users/users.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAAoF;AACpF,mDAA+C;AAC/C,mDAA+C;AAWxC,IAAM,eAAe,GAArB,MAAM,eAAe;IACG;IAA7B,YAA6B,KAAmB;QAAnB,UAAK,GAAL,KAAK,CAAc;IAAG,CAAC;IAGpD,EAAE;QACA,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;IACzB,CAAC;IAGK,AAAN,KAAK,CAAC,aAAa,CACV,GAAoB,EACnB,IAAuC;QAE/C,OAAO,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACzD,CAAC;IAGK,AAAN,KAAK,CAAC,WAAW,CAAQ,GAAoB;QAC3C,OAAO,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACjD,CAAC;IAGK,AAAN,KAAK,CAAC,aAAa,CACV,GAAoB,EACnB,IAA0B;QAElC,OAAO,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IAClE,CAAC;CACF,CAAA;AA5BY,0CAAe;AAI1B;IADC,IAAA,YAAG,EAAC,IAAI,CAAC;;;;yCAGT;AAGK;IADL,IAAA,YAAG,EAAC,SAAS,CAAC;IAEZ,WAAA,IAAA,YAAG,GAAE,CAAA;IACL,WAAA,IAAA,aAAI,GAAE,CAAA;;;;oDAGR;AAGK;IADL,IAAA,YAAG,EAAC,WAAW,CAAC;IACE,WAAA,IAAA,YAAG,GAAE,CAAA;;;;kDAEvB;AAGK;IADL,IAAA,eAAM,EAAC,SAAS,CAAC;IAEf,WAAA,IAAA,YAAG,GAAE,CAAA;IACL,WAAA,IAAA,aAAI,GAAE,CAAA;;;;oDAGR;0BA3BU,eAAe;IAF3B,IAAA,mBAAU,EAAC,OAAO,CAAC;IACnB,IAAA,kBAAS,EAAC,sBAAS,CAAC;qCAEiB,4BAAY;GADrC,eAAe,CA4B3B"}
|
||||
31
apps/api/dist/users/users.service.d.ts
vendored
31
apps/api/dist/users/users.service.d.ts
vendored
@@ -4,13 +4,42 @@ export declare class UsersService {
|
||||
constructor(prisma: PrismaService);
|
||||
me(): Promise<{
|
||||
id: string;
|
||||
email: string | null;
|
||||
email: string;
|
||||
phone: string | null;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
status: string;
|
||||
emailVerified: boolean;
|
||||
passwordHash: string | null;
|
||||
name: string | null;
|
||||
avatarUrl: string | null;
|
||||
defaultCurrency: string | null;
|
||||
timeZone: string | null;
|
||||
otpEmailEnabled: boolean;
|
||||
otpWhatsappEnabled: boolean;
|
||||
otpTotpEnabled: boolean;
|
||||
otpTotpSecret: string | null;
|
||||
} | null>;
|
||||
updateProfile(userId: string, data: {
|
||||
name?: string;
|
||||
phone?: string;
|
||||
}): Promise<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
user: {
|
||||
id: string;
|
||||
email: string;
|
||||
phone: string | null;
|
||||
name: string | null;
|
||||
avatarUrl: string | null;
|
||||
};
|
||||
}>;
|
||||
getAuthInfo(userId: string): Promise<{
|
||||
hasGoogleAuth: boolean;
|
||||
hasPassword: boolean;
|
||||
}>;
|
||||
deleteAccount(userId: string, password: string): Promise<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
107
apps/api/dist/users/users.service.js
vendored
107
apps/api/dist/users/users.service.js
vendored
@@ -1,10 +1,43 @@
|
||||
"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);
|
||||
};
|
||||
@@ -13,6 +46,7 @@ exports.UsersService = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const prisma_service_1 = require("../prisma/prisma.service");
|
||||
const user_util_1 = require("../common/user.util");
|
||||
const bcrypt = __importStar(require("bcrypt"));
|
||||
let UsersService = class UsersService {
|
||||
prisma;
|
||||
constructor(prisma) {
|
||||
@@ -22,6 +56,79 @@ let UsersService = class UsersService {
|
||||
const userId = (0, user_util_1.getTempUserId)();
|
||||
return this.prisma.user.findUnique({ where: { id: userId } });
|
||||
}
|
||||
async updateProfile(userId, data) {
|
||||
try {
|
||||
const user = await this.prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: {
|
||||
...(data.name !== undefined && { name: data.name }),
|
||||
...(data.phone !== undefined && { phone: data.phone }),
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
phone: true,
|
||||
avatarUrl: true,
|
||||
},
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
message: 'Profile updated successfully',
|
||||
user,
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
if (error.code === 'P2002') {
|
||||
throw new common_1.BadRequestException('Phone number already in use');
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
async getAuthInfo(userId) {
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: {
|
||||
passwordHash: true,
|
||||
avatarUrl: true,
|
||||
},
|
||||
});
|
||||
const hasGoogleAuth = user?.avatarUrl?.includes('googleusercontent.com') ||
|
||||
user?.avatarUrl?.startsWith('/avatars/') ||
|
||||
false;
|
||||
return {
|
||||
hasGoogleAuth,
|
||||
hasPassword: user?.passwordHash !== null,
|
||||
};
|
||||
}
|
||||
async deleteAccount(userId, password) {
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: {
|
||||
passwordHash: true,
|
||||
},
|
||||
});
|
||||
if (!user) {
|
||||
throw new common_1.BadRequestException('User not found');
|
||||
}
|
||||
if (!user.passwordHash) {
|
||||
throw new common_1.BadRequestException('Cannot delete account without password. Please set a password first.');
|
||||
}
|
||||
const isValid = await bcrypt.compare(password, user.passwordHash);
|
||||
if (!isValid) {
|
||||
throw new common_1.UnauthorizedException('Incorrect password');
|
||||
}
|
||||
await this.prisma.authAccount.deleteMany({
|
||||
where: { userId: userId },
|
||||
});
|
||||
await this.prisma.user.delete({
|
||||
where: { id: userId },
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
message: 'Account deleted successfully',
|
||||
};
|
||||
}
|
||||
};
|
||||
exports.UsersService = UsersService;
|
||||
exports.UsersService = UsersService = __decorate([
|
||||
|
||||
2
apps/api/dist/users/users.service.js.map
vendored
2
apps/api/dist/users/users.service.js.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"users.service.js","sourceRoot":"","sources":["../../src/users/users.service.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA4C;AAC5C,6DAAyD;AACzD,mDAAoD;AAG7C,IAAM,YAAY,GAAlB,MAAM,YAAY;IACH;IAApB,YAAoB,MAAqB;QAArB,WAAM,GAAN,MAAM,CAAe;IAAG,CAAC;IAE7C,KAAK,CAAC,EAAE;QACN,MAAM,MAAM,GAAG,IAAA,yBAAa,GAAE,CAAC;QAC/B,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC,CAAC;IAChE,CAAC;CACF,CAAA;AAPY,oCAAY;uBAAZ,YAAY;IADxB,IAAA,mBAAU,GAAE;qCAEiB,8BAAa;GAD9B,YAAY,CAOxB"}
|
||||
{"version":3,"file":"users.service.js","sourceRoot":"","sources":["../../src/users/users.service.ts"],"names":[],"mappings":"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"}
|
||||
16
apps/api/dist/wallets/wallets.controller.d.ts
vendored
16
apps/api/dist/wallets/wallets.controller.d.ts
vendored
@@ -1,10 +1,15 @@
|
||||
import { WalletsService } from './wallets.service';
|
||||
import { TransactionsService } from '../transactions/transactions.service';
|
||||
interface RequestWithUser {
|
||||
user: {
|
||||
userId: string;
|
||||
};
|
||||
}
|
||||
export declare class WalletsController {
|
||||
private readonly wallets;
|
||||
private readonly transactions;
|
||||
constructor(wallets: WalletsService, transactions: TransactionsService);
|
||||
list(): import("@prisma/client").Prisma.PrismaPromise<{
|
||||
list(req: RequestWithUser): import("@prisma/client").Prisma.PrismaPromise<{
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
@@ -17,7 +22,7 @@ export declare class WalletsController {
|
||||
pricePerUnit: import("@prisma/client/runtime/library").Decimal | null;
|
||||
deletedAt: Date | null;
|
||||
}[]>;
|
||||
getAllTransactions(): Promise<{
|
||||
getAllTransactions(req: RequestWithUser): Promise<{
|
||||
category: string | null;
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
@@ -29,7 +34,7 @@ export declare class WalletsController {
|
||||
walletId: string;
|
||||
recurrenceId: string | null;
|
||||
}[]>;
|
||||
create(body: {
|
||||
create(req: RequestWithUser, body: {
|
||||
name: string;
|
||||
currency?: string;
|
||||
kind?: 'money' | 'asset';
|
||||
@@ -51,7 +56,7 @@ export declare class WalletsController {
|
||||
}, never, import("@prisma/client/runtime/library").DefaultArgs, import("@prisma/client").Prisma.PrismaClientOptions> | {
|
||||
error: string;
|
||||
};
|
||||
update(id: string, body: {
|
||||
update(req: RequestWithUser, id: string, body: {
|
||||
name?: string;
|
||||
currency?: string;
|
||||
kind?: 'money' | 'asset';
|
||||
@@ -71,7 +76,7 @@ export declare class WalletsController {
|
||||
pricePerUnit: import("@prisma/client/runtime/library").Decimal | null;
|
||||
deletedAt: Date | null;
|
||||
}, never, import("@prisma/client/runtime/library").DefaultArgs, import("@prisma/client").Prisma.PrismaClientOptions>;
|
||||
delete(id: string): import("@prisma/client").Prisma.Prisma__WalletClient<{
|
||||
delete(req: RequestWithUser, id: string): import("@prisma/client").Prisma.Prisma__WalletClient<{
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
@@ -85,3 +90,4 @@ export declare class WalletsController {
|
||||
deletedAt: Date | null;
|
||||
}, never, import("@prisma/client/runtime/library").DefaultArgs, import("@prisma/client").Prisma.PrismaClientOptions>;
|
||||
}
|
||||
export {};
|
||||
|
||||
45
apps/api/dist/wallets/wallets.controller.js
vendored
45
apps/api/dist/wallets/wallets.controller.js
vendored
@@ -16,6 +16,7 @@ exports.WalletsController = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const wallets_service_1 = require("./wallets.service");
|
||||
const transactions_service_1 = require("../transactions/transactions.service");
|
||||
const auth_guard_1 = require("../auth/auth.guard");
|
||||
let WalletsController = class WalletsController {
|
||||
wallets;
|
||||
transactions;
|
||||
@@ -23,62 +24,68 @@ let WalletsController = class WalletsController {
|
||||
this.wallets = wallets;
|
||||
this.transactions = transactions;
|
||||
}
|
||||
list() {
|
||||
return this.wallets.list();
|
||||
list(req) {
|
||||
return this.wallets.list(req.user.userId);
|
||||
}
|
||||
async getAllTransactions() {
|
||||
return this.transactions.listAll();
|
||||
async getAllTransactions(req) {
|
||||
return this.transactions.listAll(req.user.userId);
|
||||
}
|
||||
create(body) {
|
||||
create(req, body) {
|
||||
if (!body?.name) {
|
||||
return { error: 'name is required' };
|
||||
}
|
||||
return this.wallets.create(body);
|
||||
return this.wallets.create(req.user.userId, body);
|
||||
}
|
||||
update(id, body) {
|
||||
return this.wallets.update(id, body);
|
||||
update(req, id, body) {
|
||||
return this.wallets.update(req.user.userId, id, body);
|
||||
}
|
||||
delete(id) {
|
||||
return this.wallets.delete(id);
|
||||
delete(req, id) {
|
||||
return this.wallets.delete(req.user.userId, id);
|
||||
}
|
||||
};
|
||||
exports.WalletsController = WalletsController;
|
||||
__decorate([
|
||||
(0, common_1.Get)(),
|
||||
__param(0, (0, common_1.Req)()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", []),
|
||||
__metadata("design:paramtypes", [Object]),
|
||||
__metadata("design:returntype", void 0)
|
||||
], WalletsController.prototype, "list", null);
|
||||
__decorate([
|
||||
(0, common_1.Get)('transactions'),
|
||||
__param(0, (0, common_1.Req)()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", []),
|
||||
__metadata("design:paramtypes", [Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], WalletsController.prototype, "getAllTransactions", null);
|
||||
__decorate([
|
||||
(0, common_1.Post)(),
|
||||
__param(0, (0, common_1.Body)()),
|
||||
__param(0, (0, common_1.Req)()),
|
||||
__param(1, (0, common_1.Body)()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object]),
|
||||
__metadata("design:paramtypes", [Object, Object]),
|
||||
__metadata("design:returntype", void 0)
|
||||
], WalletsController.prototype, "create", null);
|
||||
__decorate([
|
||||
(0, common_1.Put)(':id'),
|
||||
__param(0, (0, common_1.Param)('id')),
|
||||
__param(1, (0, common_1.Body)()),
|
||||
__param(0, (0, common_1.Req)()),
|
||||
__param(1, (0, common_1.Param)('id')),
|
||||
__param(2, (0, common_1.Body)()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [String, Object]),
|
||||
__metadata("design:paramtypes", [Object, String, Object]),
|
||||
__metadata("design:returntype", void 0)
|
||||
], WalletsController.prototype, "update", null);
|
||||
__decorate([
|
||||
(0, common_1.Delete)(':id'),
|
||||
__param(0, (0, common_1.Param)('id')),
|
||||
__param(0, (0, common_1.Req)()),
|
||||
__param(1, (0, common_1.Param)('id')),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [String]),
|
||||
__metadata("design:paramtypes", [Object, String]),
|
||||
__metadata("design:returntype", void 0)
|
||||
], WalletsController.prototype, "delete", null);
|
||||
exports.WalletsController = WalletsController = __decorate([
|
||||
(0, common_1.Controller)('wallets'),
|
||||
(0, common_1.UseGuards)(auth_guard_1.AuthGuard),
|
||||
__metadata("design:paramtypes", [wallets_service_1.WalletsService,
|
||||
transactions_service_1.TransactionsService])
|
||||
], WalletsController);
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"wallets.controller.js","sourceRoot":"","sources":["../../src/wallets/wallets.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAAiF;AACjF,uDAAmD;AACnD,+EAA2E;AAGpE,IAAM,iBAAiB,GAAvB,MAAM,iBAAiB;IAET;IACA;IAFnB,YACmB,OAAuB,EACvB,YAAiC;QADjC,YAAO,GAAP,OAAO,CAAgB;QACvB,iBAAY,GAAZ,YAAY,CAAqB;IACjD,CAAC;IAGJ,IAAI;QACF,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;IAC7B,CAAC;IAGK,AAAN,KAAK,CAAC,kBAAkB;QACtB,OAAO,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,CAAC;IACrC,CAAC;IAGD,MAAM,CAAS,IAAiI;QAC9I,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC;YAChB,OAAO,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC;QACvC,CAAC;QACD,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACnC,CAAC;IAGD,MAAM,CAAc,EAAU,EAAU,IAAkI;QACxK,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IACvC,CAAC;IAGD,MAAM,CAAc,EAAU;QAC5B,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACjC,CAAC;CACF,CAAA;AAjCY,8CAAiB;AAO5B;IADC,IAAA,YAAG,GAAE;;;;6CAGL;AAGK;IADL,IAAA,YAAG,EAAC,cAAc,CAAC;;;;2DAGnB;AAGD;IADC,IAAA,aAAI,GAAE;IACC,WAAA,IAAA,aAAI,GAAE,CAAA;;;;+CAKb;AAGD;IADC,IAAA,YAAG,EAAC,KAAK,CAAC;IACH,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;IAAc,WAAA,IAAA,aAAI,GAAE,CAAA;;;;+CAEtC;AAGD;IADC,IAAA,eAAM,EAAC,KAAK,CAAC;IACN,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;;;;+CAElB;4BAhCU,iBAAiB;IAD7B,IAAA,mBAAU,EAAC,SAAS,CAAC;qCAGQ,gCAAc;QACT,0CAAmB;GAHzC,iBAAiB,CAiC7B"}
|
||||
{"version":3,"file":"wallets.controller.js","sourceRoot":"","sources":["../../src/wallets/wallets.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAUwB;AACxB,uDAAmD;AACnD,+EAA2E;AAC3E,mDAA+C;AAUxC,IAAM,iBAAiB,GAAvB,MAAM,iBAAiB;IAET;IACA;IAFnB,YACmB,OAAuB,EACvB,YAAiC;QADjC,YAAO,GAAP,OAAO,CAAgB;QACvB,iBAAY,GAAZ,YAAY,CAAqB;IACjD,CAAC;IAGJ,IAAI,CAAQ,GAAoB;QAC9B,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC5C,CAAC;IAGK,AAAN,KAAK,CAAC,kBAAkB,CAAQ,GAAoB;QAClD,OAAO,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACpD,CAAC;IAGD,MAAM,CACG,GAAoB,EAE3B,IAOC;QAED,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC;YAChB,OAAO,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC;QACvC,CAAC;QACD,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IACpD,CAAC;IAGD,MAAM,CACG,GAAoB,EACd,EAAU,EAEvB,IAOC;QAED,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,EAAE,IAAI,CAAC,CAAC;IACxD,CAAC;IAGD,MAAM,CAAQ,GAAoB,EAAe,EAAU;QACzD,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAClD,CAAC;CACF,CAAA;AAxDY,8CAAiB;AAO5B;IADC,IAAA,YAAG,GAAE;IACA,WAAA,IAAA,YAAG,GAAE,CAAA;;;;6CAEV;AAGK;IADL,IAAA,YAAG,EAAC,cAAc,CAAC;IACM,WAAA,IAAA,YAAG,GAAE,CAAA;;;;2DAE9B;AAGD;IADC,IAAA,aAAI,GAAE;IAEJ,WAAA,IAAA,YAAG,GAAE,CAAA;IACL,WAAA,IAAA,aAAI,GAAE,CAAA;;;;+CAcR;AAGD;IADC,IAAA,YAAG,EAAC,KAAK,CAAC;IAER,WAAA,IAAA,YAAG,GAAE,CAAA;IACL,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;IACX,WAAA,IAAA,aAAI,GAAE,CAAA;;;;+CAWR;AAGD;IADC,IAAA,eAAM,EAAC,KAAK,CAAC;IACN,WAAA,IAAA,YAAG,GAAE,CAAA;IAAwB,WAAA,IAAA,cAAK,EAAC,IAAI,CAAC,CAAA;;;;+CAE/C;4BAvDU,iBAAiB;IAF7B,IAAA,mBAAU,EAAC,SAAS,CAAC;IACrB,IAAA,kBAAS,EAAC,sBAAS,CAAC;qCAGS,gCAAc;QACT,0CAAmB;GAHzC,iBAAiB,CAwD7B"}
|
||||
9
apps/api/dist/wallets/wallets.service.d.ts
vendored
9
apps/api/dist/wallets/wallets.service.d.ts
vendored
@@ -2,8 +2,7 @@ import { PrismaService } from '../prisma/prisma.service';
|
||||
export declare class WalletsService {
|
||||
private prisma;
|
||||
constructor(prisma: PrismaService);
|
||||
private userId;
|
||||
list(): import("@prisma/client").Prisma.PrismaPromise<{
|
||||
list(userId: string): import("@prisma/client").Prisma.PrismaPromise<{
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
@@ -16,7 +15,7 @@ export declare class WalletsService {
|
||||
pricePerUnit: import("@prisma/client/runtime/library").Decimal | null;
|
||||
deletedAt: Date | null;
|
||||
}[]>;
|
||||
create(input: {
|
||||
create(userId: string, input: {
|
||||
name: string;
|
||||
currency?: string;
|
||||
kind?: 'money' | 'asset';
|
||||
@@ -36,7 +35,7 @@ export declare class WalletsService {
|
||||
pricePerUnit: import("@prisma/client/runtime/library").Decimal | null;
|
||||
deletedAt: Date | null;
|
||||
}, never, import("@prisma/client/runtime/library").DefaultArgs, import("@prisma/client").Prisma.PrismaClientOptions>;
|
||||
update(id: string, input: {
|
||||
update(userId: string, id: string, input: {
|
||||
name?: string;
|
||||
currency?: string;
|
||||
kind?: 'money' | 'asset';
|
||||
@@ -56,7 +55,7 @@ export declare class WalletsService {
|
||||
pricePerUnit: import("@prisma/client/runtime/library").Decimal | null;
|
||||
deletedAt: Date | null;
|
||||
}, never, import("@prisma/client/runtime/library").DefaultArgs, import("@prisma/client").Prisma.PrismaClientOptions>;
|
||||
delete(id: string): import("@prisma/client").Prisma.Prisma__WalletClient<{
|
||||
delete(userId: string, id: string): import("@prisma/client").Prisma.Prisma__WalletClient<{
|
||||
id: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
|
||||
22
apps/api/dist/wallets/wallets.service.js
vendored
22
apps/api/dist/wallets/wallets.service.js
vendored
@@ -12,36 +12,32 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.WalletsService = void 0;
|
||||
const common_1 = require("@nestjs/common");
|
||||
const prisma_service_1 = require("../prisma/prisma.service");
|
||||
const user_util_1 = require("../common/user.util");
|
||||
let WalletsService = class WalletsService {
|
||||
prisma;
|
||||
constructor(prisma) {
|
||||
this.prisma = prisma;
|
||||
}
|
||||
userId() {
|
||||
return (0, user_util_1.getTempUserId)();
|
||||
}
|
||||
list() {
|
||||
list(userId) {
|
||||
return this.prisma.wallet.findMany({
|
||||
where: { userId: this.userId(), deletedAt: null },
|
||||
where: { userId, deletedAt: null },
|
||||
orderBy: { createdAt: 'asc' },
|
||||
});
|
||||
}
|
||||
create(input) {
|
||||
create(userId, input) {
|
||||
const kind = input.kind ?? 'money';
|
||||
return this.prisma.wallet.create({
|
||||
data: {
|
||||
userId: this.userId(),
|
||||
userId,
|
||||
name: input.name,
|
||||
kind,
|
||||
currency: kind === 'money' ? (input.currency ?? 'IDR') : null,
|
||||
unit: kind === 'asset' ? (input.unit ?? null) : null,
|
||||
initialAmount: input.initialAmount || null,
|
||||
pricePerUnit: kind === 'asset' ? (input.pricePerUnit || null) : null,
|
||||
pricePerUnit: kind === 'asset' ? input.pricePerUnit || null : null,
|
||||
},
|
||||
});
|
||||
}
|
||||
update(id, input) {
|
||||
update(userId, id, input) {
|
||||
const updateData = {};
|
||||
if (input.name !== undefined)
|
||||
updateData.name = input.name;
|
||||
@@ -67,13 +63,13 @@ let WalletsService = class WalletsService {
|
||||
if (input.pricePerUnit !== undefined)
|
||||
updateData.pricePerUnit = input.pricePerUnit || null;
|
||||
return this.prisma.wallet.update({
|
||||
where: { id, userId: this.userId() },
|
||||
where: { id, userId },
|
||||
data: updateData,
|
||||
});
|
||||
}
|
||||
delete(id) {
|
||||
delete(userId, id) {
|
||||
return this.prisma.wallet.update({
|
||||
where: { id, userId: this.userId() },
|
||||
where: { id, userId },
|
||||
data: { deletedAt: new Date() },
|
||||
});
|
||||
}
|
||||
|
||||
2
apps/api/dist/wallets/wallets.service.js.map
vendored
2
apps/api/dist/wallets/wallets.service.js.map
vendored
@@ -1 +1 @@
|
||||
{"version":3,"file":"wallets.service.js","sourceRoot":"","sources":["../../src/wallets/wallets.service.ts"],"names":[],"mappings":"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"}
|
||||
{"version":3,"file":"wallets.service.js","sourceRoot":"","sources":["../../src/wallets/wallets.service.ts"],"names":[],"mappings":"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"}
|
||||
1772
apps/api/package-lock.json
generated
1772
apps/api/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -27,15 +27,23 @@
|
||||
"@nestjs/common": "^11.0.1",
|
||||
"@nestjs/config": "^4.0.2",
|
||||
"@nestjs/core": "^11.0.1",
|
||||
"@nestjs/jwt": "^11.0.0",
|
||||
"@nestjs/passport": "^11.0.5",
|
||||
"@nestjs/platform-express": "^11.0.1",
|
||||
"@nestjs/swagger": "^11.2.0",
|
||||
"@prisma/client": "^6.17.0",
|
||||
"axios": "^1.12.2",
|
||||
"bcrypt": "^6.0.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.2",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"firebase-admin": "^13.5.0",
|
||||
"jose": "^6.0.12",
|
||||
"otplib": "^12.0.1",
|
||||
"passport": "^0.7.0",
|
||||
"passport-google-oauth20": "^2.0.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"pg": "^8.16.3",
|
||||
"qrcode": "^1.5.4",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.1",
|
||||
"swagger-ui-express": "^5.0.1",
|
||||
@@ -47,9 +55,13 @@
|
||||
"@nestjs/cli": "^11.0.10",
|
||||
"@nestjs/schematics": "^11.0.0",
|
||||
"@nestjs/testing": "^11.0.1",
|
||||
"@types/bcrypt": "^6.0.0",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/node": "^22.10.7",
|
||||
"@types/passport-google-oauth20": "^2.0.16",
|
||||
"@types/passport-jwt": "^4.0.1",
|
||||
"@types/qrcode": "^1.5.5",
|
||||
"@types/supertest": "^6.0.2",
|
||||
"eslint": "^9.18.0",
|
||||
"eslint-config-prettier": "^10.0.1",
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- Made the column `email` on table `User` required. This step will fail if there are existing NULL values in that column.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "public"."User" ADD COLUMN "emailVerified" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN "otpEmailEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN "otpTotpEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN "otpTotpSecret" TEXT,
|
||||
ADD COLUMN "passwordHash" TEXT,
|
||||
ALTER COLUMN "email" SET NOT NULL;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "public"."Wallet" ADD COLUMN "initialAmount" DECIMAL(18,2),
|
||||
ADD COLUMN "pricePerUnit" DECIMAL(18,2);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "public"."Category" (
|
||||
"id" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "Category_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "Category_userId_idx" ON "public"."Category"("userId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Category_userId_name_key" ON "public"."Category"("userId", "name");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "public"."Category" ADD CONSTRAINT "Category_userId_fkey" FOREIGN KEY ("userId") REFERENCES "public"."User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- A unique constraint covering the columns `[phone]` on the table `User` will be added. If there are existing duplicate values, this will fail.
|
||||
|
||||
*/
|
||||
-- AlterTable
|
||||
ALTER TABLE "public"."User" ADD COLUMN "otpWhatsappEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
ADD COLUMN "phone" TEXT;
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_phone_key" ON "public"."User"("phone");
|
||||
@@ -5,7 +5,7 @@ generator client {
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
shadowDatabaseUrl = env("SHADOW_DATABASE_URL")
|
||||
shadowDatabaseUrl = env("DATABASE_URL_SHADOW")
|
||||
}
|
||||
|
||||
model User {
|
||||
@@ -13,11 +13,19 @@ model User {
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
status String @default("active")
|
||||
email String? @unique
|
||||
email String @unique
|
||||
emailVerified Boolean @default(false)
|
||||
passwordHash String?
|
||||
name String?
|
||||
avatarUrl String?
|
||||
phone String? @unique
|
||||
defaultCurrency String?
|
||||
timeZone String?
|
||||
// OTP/MFA fields
|
||||
otpEmailEnabled Boolean @default(false)
|
||||
otpWhatsappEnabled Boolean @default(false)
|
||||
otpTotpEnabled Boolean @default(false)
|
||||
otpTotpSecret String?
|
||||
authAccounts AuthAccount[]
|
||||
categories Category[]
|
||||
Recurrence Recurrence[]
|
||||
|
||||
BIN
apps/api/public/avatars/0197103d-340a-433e-8406-a15497dc8d8e.jpg
Normal file
BIN
apps/api/public/avatars/0197103d-340a-433e-8406-a15497dc8d8e.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
BIN
apps/api/public/avatars/0fe67776-9829-4e1d-9457-8f0419ff5105.jpg
Normal file
BIN
apps/api/public/avatars/0fe67776-9829-4e1d-9457-8f0419ff5105.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.4 KiB |
@@ -8,6 +8,7 @@ import { UsersModule } from './users/users.module';
|
||||
import { WalletsModule } from './wallets/wallets.module';
|
||||
import { TransactionsModule } from './transactions/transactions.module';
|
||||
import { CategoriesModule } from './categories/categories.module';
|
||||
import { OtpModule } from './otp/otp.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -24,8 +25,9 @@ import { CategoriesModule } from './categories/categories.module';
|
||||
WalletsModule,
|
||||
TransactionsModule,
|
||||
CategoriesModule,
|
||||
OtpModule,
|
||||
],
|
||||
controllers: [HealthController],
|
||||
providers: [],
|
||||
})
|
||||
export class AppModule {}
|
||||
export class AppModule {}
|
||||
|
||||
104
apps/api/src/auth/auth.controller.ts
Normal file
104
apps/api/src/auth/auth.controller.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import {
|
||||
Controller,
|
||||
Post,
|
||||
Get,
|
||||
Body,
|
||||
UseGuards,
|
||||
Req,
|
||||
Res,
|
||||
} from '@nestjs/common';
|
||||
import { AuthGuard as JwtAuthGuard } from './auth.guard';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { AuthService } from './auth.service';
|
||||
import type { Response } from 'express';
|
||||
|
||||
interface RequestWithUser {
|
||||
user: {
|
||||
userId: string;
|
||||
email: string;
|
||||
};
|
||||
}
|
||||
|
||||
@Controller('auth')
|
||||
export class AuthController {
|
||||
constructor(private authService: AuthService) {}
|
||||
|
||||
@Post('register')
|
||||
async register(
|
||||
@Body() body: { email: string; password: string; name?: string },
|
||||
) {
|
||||
return this.authService.register(body.email, body.password, body.name);
|
||||
}
|
||||
|
||||
@Post('login')
|
||||
async login(@Body() body: { email: string; password: string }) {
|
||||
return this.authService.login(body.email, body.password);
|
||||
}
|
||||
|
||||
@Post('verify-otp')
|
||||
async verifyOtp(
|
||||
@Body()
|
||||
body: {
|
||||
tempToken: string;
|
||||
otpCode: string;
|
||||
method: 'email' | 'totp';
|
||||
},
|
||||
) {
|
||||
return this.authService.verifyOtpAndLogin(
|
||||
body.tempToken,
|
||||
body.otpCode,
|
||||
body.method,
|
||||
);
|
||||
}
|
||||
|
||||
@Get('google')
|
||||
@UseGuards(AuthGuard('google'))
|
||||
async googleAuth() {
|
||||
// Initiates Google OAuth flow
|
||||
}
|
||||
|
||||
@Get('google/callback')
|
||||
@UseGuards(AuthGuard('google'))
|
||||
async googleAuthCallback(@Req() req: any, @Res() res: Response) {
|
||||
// Handle Google OAuth callback
|
||||
const result = await this.authService.googleLogin(req.user);
|
||||
|
||||
// Redirect to frontend with token or OTP requirement
|
||||
const frontendUrl = process.env.WEB_APP_URL || 'http://localhost:5174';
|
||||
|
||||
if (result.requiresOtp) {
|
||||
// Redirect to OTP page with temp token
|
||||
res.redirect(
|
||||
`${frontendUrl}/auth/otp?token=${result.tempToken}&methods=${JSON.stringify(result.availableMethods)}`,
|
||||
);
|
||||
} else {
|
||||
// Redirect to app with full token
|
||||
res.redirect(`${frontendUrl}/auth/callback?token=${result.token}`);
|
||||
}
|
||||
}
|
||||
|
||||
@Get('me')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
async getProfile(@Req() req: RequestWithUser) {
|
||||
return this.authService.getUserProfile(req.user.userId);
|
||||
}
|
||||
|
||||
@Post('change-password')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
async changePassword(
|
||||
@Req() req: RequestWithUser,
|
||||
@Body()
|
||||
body: {
|
||||
currentPassword: string;
|
||||
newPassword: string;
|
||||
isSettingPassword?: boolean;
|
||||
},
|
||||
) {
|
||||
return this.authService.changePassword(
|
||||
req.user.userId,
|
||||
body.currentPassword,
|
||||
body.newPassword,
|
||||
body.isSettingPassword,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,36 +1,24 @@
|
||||
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';
|
||||
import { FirebaseService } from './firebase.service';
|
||||
import { Injectable, ExecutionContext } from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { AuthGuard as PassportAuthGuard } from '@nestjs/passport';
|
||||
|
||||
@Injectable()
|
||||
export class AuthGuard implements CanActivate {
|
||||
constructor(private firebaseService: FirebaseService) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
|
||||
// If Firebase is not configured, allow all requests (development mode)
|
||||
if (!this.firebaseService.isFirebaseConfigured()) {
|
||||
console.warn('⚠️ Firebase not configured - allowing request without auth');
|
||||
return true;
|
||||
}
|
||||
|
||||
const token = this.extractTokenFromHeader(request);
|
||||
|
||||
if (!token) {
|
||||
throw new UnauthorizedException('No token provided');
|
||||
}
|
||||
|
||||
try {
|
||||
const decodedToken = await this.firebaseService.verifyIdToken(token);
|
||||
request.user = decodedToken;
|
||||
return true;
|
||||
} catch (error) {
|
||||
throw new UnauthorizedException('Invalid token');
|
||||
}
|
||||
export class AuthGuard extends PassportAuthGuard('jwt') {
|
||||
constructor(private reflector: Reflector) {
|
||||
super();
|
||||
}
|
||||
|
||||
private extractTokenFromHeader(request: any): string | undefined {
|
||||
const [type, token] = request.headers.authorization?.split(' ') ?? [];
|
||||
return type === 'Bearer' ? token : undefined;
|
||||
canActivate(context: ExecutionContext) {
|
||||
// Check if route is marked as public
|
||||
const isPublic = this.reflector.getAllAndOverride<boolean>('isPublic', [
|
||||
context.getHandler(),
|
||||
context.getClass(),
|
||||
]);
|
||||
|
||||
if (isPublic) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return super.canActivate(context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { FirebaseService } from './firebase.service';
|
||||
import { AuthGuard } from './auth.guard';
|
||||
import { Module, forwardRef } from '@nestjs/common';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
import { PassportModule } from '@nestjs/passport';
|
||||
import { AuthController } from './auth.controller';
|
||||
import { AuthService } from './auth.service';
|
||||
import { JwtStrategy } from './jwt.strategy';
|
||||
import { GoogleStrategy } from './google.strategy';
|
||||
import { PrismaModule } from '../prisma/prisma.module';
|
||||
import { OtpModule } from '../otp/otp.module';
|
||||
|
||||
@Module({
|
||||
providers: [FirebaseService, AuthGuard],
|
||||
exports: [FirebaseService, AuthGuard],
|
||||
imports: [
|
||||
PrismaModule,
|
||||
PassportModule,
|
||||
forwardRef(() => OtpModule),
|
||||
JwtModule.register({
|
||||
secret: process.env.JWT_SECRET || 'your-secret-key',
|
||||
signOptions: { expiresIn: '7d' },
|
||||
}),
|
||||
],
|
||||
controllers: [AuthController],
|
||||
providers: [AuthService, JwtStrategy, GoogleStrategy],
|
||||
exports: [AuthService],
|
||||
})
|
||||
export class AuthModule {}
|
||||
|
||||
479
apps/api/src/auth/auth.service.ts
Normal file
479
apps/api/src/auth/auth.service.ts
Normal file
@@ -0,0 +1,479 @@
|
||||
import {
|
||||
Injectable,
|
||||
UnauthorizedException,
|
||||
BadRequestException,
|
||||
ConflictException,
|
||||
Inject,
|
||||
forwardRef,
|
||||
} from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { OtpService } from '../otp/otp.service';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import axios from 'axios';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly jwtService: JwtService,
|
||||
@Inject(forwardRef(() => OtpService))
|
||||
private readonly otpService: OtpService,
|
||||
) {}
|
||||
|
||||
async register(email: string, password: string, name?: string) {
|
||||
// Check if user already exists
|
||||
const existing = await this.prisma.user.findUnique({ where: { email } });
|
||||
if (existing) {
|
||||
throw new ConflictException('Email already registered');
|
||||
}
|
||||
|
||||
// Hash password
|
||||
const passwordHash = await bcrypt.hash(password, 10);
|
||||
|
||||
// Create user
|
||||
const user = await this.prisma.user.create({
|
||||
data: {
|
||||
email,
|
||||
passwordHash,
|
||||
name,
|
||||
emailVerified: false, // Will be verified via OTP
|
||||
},
|
||||
});
|
||||
|
||||
// Generate JWT token
|
||||
const token = this.generateToken(user.id, user.email);
|
||||
|
||||
return {
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
avatarUrl: user.avatarUrl,
|
||||
emailVerified: user.emailVerified,
|
||||
},
|
||||
token,
|
||||
};
|
||||
}
|
||||
|
||||
async login(email: string, password: string) {
|
||||
// Find user
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { email },
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
passwordHash: true,
|
||||
name: true,
|
||||
avatarUrl: true,
|
||||
emailVerified: true,
|
||||
otpEmailEnabled: true,
|
||||
otpWhatsappEnabled: true,
|
||||
otpTotpEnabled: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user || !user.passwordHash) {
|
||||
throw new UnauthorizedException('Invalid credentials');
|
||||
}
|
||||
|
||||
// Verify password
|
||||
const isValid = await bcrypt.compare(password, user.passwordHash);
|
||||
if (!isValid) {
|
||||
throw new UnauthorizedException('Invalid credentials');
|
||||
}
|
||||
|
||||
// Check if OTP is required
|
||||
const requiresOtp =
|
||||
user.otpEmailEnabled || user.otpWhatsappEnabled || user.otpTotpEnabled;
|
||||
|
||||
if (requiresOtp) {
|
||||
// Send email OTP if enabled
|
||||
if (user.otpEmailEnabled) {
|
||||
try {
|
||||
await this.otpService.sendEmailOtp(user.id);
|
||||
} catch (error) {
|
||||
console.error('Failed to send email OTP during login:', error);
|
||||
// Continue anyway - user can request resend
|
||||
}
|
||||
}
|
||||
|
||||
// Send WhatsApp OTP if enabled (use 'live' mode for login)
|
||||
if (user.otpWhatsappEnabled) {
|
||||
try {
|
||||
await this.otpService.sendWhatsappOtp(user.id, 'live');
|
||||
} catch (error) {
|
||||
console.error('Failed to send WhatsApp OTP during login:', error);
|
||||
// Continue anyway - user can request resend
|
||||
}
|
||||
}
|
||||
|
||||
// Return temporary token that requires OTP verification
|
||||
return {
|
||||
requiresOtp: true,
|
||||
availableMethods: {
|
||||
email: user.otpEmailEnabled,
|
||||
whatsapp: user.otpWhatsappEnabled,
|
||||
totp: user.otpTotpEnabled,
|
||||
},
|
||||
tempToken: this.generateTempToken(user.id, user.email),
|
||||
};
|
||||
}
|
||||
|
||||
// Generate full JWT token
|
||||
const token = this.generateToken(user.id, user.email);
|
||||
|
||||
return {
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
avatarUrl: user.avatarUrl,
|
||||
emailVerified: user.emailVerified,
|
||||
},
|
||||
token,
|
||||
};
|
||||
}
|
||||
|
||||
async googleLogin(googleProfile: {
|
||||
googleId: string;
|
||||
email: string;
|
||||
name: string;
|
||||
avatarUrl?: string;
|
||||
}) {
|
||||
// Find or create user
|
||||
let user = await this.prisma.user.findUnique({
|
||||
where: { email: googleProfile.email },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
// Create new user from Google profile
|
||||
user = await this.prisma.user.create({
|
||||
data: {
|
||||
email: googleProfile.email,
|
||||
name: googleProfile.name,
|
||||
avatarUrl: googleProfile.avatarUrl,
|
||||
emailVerified: true, // Google emails are pre-verified
|
||||
authAccounts: {
|
||||
create: {
|
||||
provider: 'google',
|
||||
issuer: 'google.com',
|
||||
subject: googleProfile.googleId,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// Update existing user with Google account if not already linked
|
||||
const existingAuth = await this.prisma.authAccount.findUnique({
|
||||
where: {
|
||||
issuer_subject: {
|
||||
issuer: 'google.com',
|
||||
subject: googleProfile.googleId,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!existingAuth) {
|
||||
await this.prisma.authAccount.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
provider: 'google',
|
||||
issuer: 'google.com',
|
||||
subject: googleProfile.googleId,
|
||||
},
|
||||
});
|
||||
}
|
||||
// Update user info from Google (always update to get latest avatar)
|
||||
console.log('Updating user with Google profile:', {
|
||||
name: googleProfile.name,
|
||||
avatarUrl: googleProfile.avatarUrl,
|
||||
});
|
||||
|
||||
// Download and store avatar locally to avoid Google rate limits
|
||||
let avatarUrl = user.avatarUrl;
|
||||
if (googleProfile.avatarUrl) {
|
||||
try {
|
||||
avatarUrl = await this.downloadAndStoreAvatar(
|
||||
googleProfile.avatarUrl,
|
||||
user.id,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Failed to download avatar:', error);
|
||||
// Fallback to Google URL
|
||||
avatarUrl = googleProfile.avatarUrl;
|
||||
}
|
||||
}
|
||||
|
||||
user = await this.prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: {
|
||||
name: googleProfile.name || user.name,
|
||||
avatarUrl: avatarUrl || user.avatarUrl,
|
||||
emailVerified: true,
|
||||
},
|
||||
});
|
||||
|
||||
console.log('User updated, avatar:', user.avatarUrl);
|
||||
}
|
||||
|
||||
// Check if OTP is required
|
||||
const requiresOtp =
|
||||
user.otpEmailEnabled || user.otpWhatsappEnabled || user.otpTotpEnabled;
|
||||
|
||||
if (requiresOtp) {
|
||||
// Send email OTP if enabled
|
||||
if (user.otpEmailEnabled) {
|
||||
try {
|
||||
await this.otpService.sendEmailOtp(user.id);
|
||||
} catch (error) {
|
||||
console.error('Failed to send email OTP during Google login:', error);
|
||||
// Continue anyway - user can request resend
|
||||
}
|
||||
}
|
||||
|
||||
// Send WhatsApp OTP if enabled (use 'live' mode for login)
|
||||
if (user.otpWhatsappEnabled) {
|
||||
try {
|
||||
await this.otpService.sendWhatsappOtp(user.id, 'live');
|
||||
} catch (error) {
|
||||
console.error(
|
||||
'Failed to send WhatsApp OTP during Google login:',
|
||||
error,
|
||||
);
|
||||
// Continue anyway - user can request resend
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
requiresOtp: true,
|
||||
availableMethods: {
|
||||
email: user.otpEmailEnabled,
|
||||
whatsapp: user.otpWhatsappEnabled,
|
||||
totp: user.otpTotpEnabled,
|
||||
},
|
||||
tempToken: this.generateTempToken(user.id, user.email),
|
||||
};
|
||||
}
|
||||
|
||||
// Generate JWT token
|
||||
const token = this.generateToken(user.id, user.email);
|
||||
|
||||
return {
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
avatarUrl: user.avatarUrl,
|
||||
emailVerified: user.emailVerified,
|
||||
},
|
||||
token,
|
||||
};
|
||||
}
|
||||
|
||||
async verifyOtpAndLogin(
|
||||
tempToken: string,
|
||||
otpCode: string,
|
||||
method: 'email' | 'whatsapp' | 'totp',
|
||||
) {
|
||||
// Verify temp token
|
||||
let payload: {
|
||||
temp?: boolean;
|
||||
userId?: string;
|
||||
sub?: string;
|
||||
email?: string;
|
||||
};
|
||||
try {
|
||||
payload = this.jwtService.verify(tempToken);
|
||||
} catch {
|
||||
throw new UnauthorizedException('Invalid or expired token');
|
||||
}
|
||||
|
||||
if (!payload.temp) {
|
||||
throw new UnauthorizedException('Invalid token type');
|
||||
}
|
||||
|
||||
const userId = payload.userId || payload.sub;
|
||||
const email = payload.email;
|
||||
|
||||
if (!userId || !email) {
|
||||
throw new UnauthorizedException('Invalid token payload');
|
||||
}
|
||||
|
||||
// Get user to verify OTP
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new UnauthorizedException('User not found');
|
||||
}
|
||||
|
||||
// Verify OTP code based on method
|
||||
if (method === 'email') {
|
||||
// Verify email OTP using OTP service
|
||||
const isValid = this.otpService.verifyEmailOtpForLogin(userId, otpCode);
|
||||
if (!isValid) {
|
||||
throw new UnauthorizedException('Invalid or expired email OTP code');
|
||||
}
|
||||
} else if (method === 'whatsapp') {
|
||||
// Verify WhatsApp OTP using OTP service
|
||||
const isValid = this.otpService.verifyWhatsappOtpForLogin(
|
||||
userId,
|
||||
otpCode,
|
||||
);
|
||||
if (!isValid) {
|
||||
throw new UnauthorizedException('Invalid or expired WhatsApp OTP code');
|
||||
}
|
||||
} else if (method === 'totp') {
|
||||
// Verify TOTP
|
||||
if (!user.otpTotpSecret) {
|
||||
throw new UnauthorizedException('TOTP not set up');
|
||||
}
|
||||
|
||||
const { authenticator } = await import('otplib');
|
||||
const isValid = authenticator.verify({
|
||||
token: otpCode,
|
||||
secret: user.otpTotpSecret,
|
||||
});
|
||||
|
||||
if (!isValid) {
|
||||
throw new UnauthorizedException('Invalid TOTP code');
|
||||
}
|
||||
}
|
||||
|
||||
// Generate full JWT token
|
||||
const token = this.generateToken(userId, email);
|
||||
|
||||
return {
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
avatarUrl: user.avatarUrl,
|
||||
emailVerified: user.emailVerified,
|
||||
},
|
||||
token,
|
||||
};
|
||||
}
|
||||
|
||||
private generateToken(userId: string, email: string): string {
|
||||
return this.jwtService.sign({
|
||||
sub: userId,
|
||||
email,
|
||||
});
|
||||
}
|
||||
|
||||
private generateTempToken(userId: string, email: string): string {
|
||||
return this.jwtService.sign(
|
||||
{ userId, email, temp: true },
|
||||
{ expiresIn: '5m' }, // Temp token expires in 5 minutes
|
||||
);
|
||||
}
|
||||
|
||||
async getUserProfile(userId: string) {
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
avatarUrl: true,
|
||||
emailVerified: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new UnauthorizedException('User not found');
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
async changePassword(
|
||||
userId: string,
|
||||
currentPassword: string,
|
||||
newPassword: string,
|
||||
isSettingPassword?: boolean,
|
||||
) {
|
||||
// Get user with password hash
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: { passwordHash: true },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new BadRequestException('User not found');
|
||||
}
|
||||
|
||||
// If setting password for Google user (no existing password)
|
||||
if (isSettingPassword && !user.passwordHash) {
|
||||
// Hash new password
|
||||
const newPasswordHash = await bcrypt.hash(newPassword, 10);
|
||||
|
||||
// Set password
|
||||
await this.prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: { passwordHash: newPasswordHash },
|
||||
});
|
||||
|
||||
return { message: 'Password set successfully' };
|
||||
}
|
||||
|
||||
// Otherwise, changing existing password
|
||||
if (!user.passwordHash) {
|
||||
throw new BadRequestException('Cannot change password for this account');
|
||||
}
|
||||
|
||||
// Verify current password
|
||||
const isValid = await bcrypt.compare(currentPassword, user.passwordHash);
|
||||
if (!isValid) {
|
||||
throw new UnauthorizedException('Current password is incorrect');
|
||||
}
|
||||
|
||||
// Hash new password
|
||||
const newPasswordHash = await bcrypt.hash(newPassword, 10);
|
||||
|
||||
// Update password
|
||||
await this.prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: { passwordHash: newPasswordHash },
|
||||
});
|
||||
|
||||
return { message: 'Password changed successfully' };
|
||||
}
|
||||
|
||||
private async downloadAndStoreAvatar(
|
||||
avatarUrl: string,
|
||||
userId: string,
|
||||
): Promise<string> {
|
||||
try {
|
||||
// Create uploads directory if it doesn't exist
|
||||
const uploadsDir = path.join(process.cwd(), 'public', 'avatars');
|
||||
if (!fs.existsSync(uploadsDir)) {
|
||||
fs.mkdirSync(uploadsDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Download image
|
||||
const response = await axios.get(avatarUrl, {
|
||||
responseType: 'arraybuffer',
|
||||
});
|
||||
|
||||
// Generate filename
|
||||
const ext = 'jpg'; // Google avatars are usually JPG
|
||||
const filename = `${userId}.${ext}`;
|
||||
const filepath = path.join(uploadsDir, filename);
|
||||
|
||||
// Save file
|
||||
fs.writeFileSync(filepath, response.data);
|
||||
|
||||
// Return public URL
|
||||
return `/avatars/${filename}`;
|
||||
} catch (error) {
|
||||
console.error('Error downloading avatar:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import * as admin from 'firebase-admin';
|
||||
|
||||
@Injectable()
|
||||
export class FirebaseService {
|
||||
private app: admin.app.App | null = null;
|
||||
private isConfigured: boolean = false;
|
||||
|
||||
constructor() {
|
||||
// Only initialize Firebase if credentials are available
|
||||
const projectId = process.env.FIREBASE_PROJECT_ID;
|
||||
const clientEmail = process.env.FIREBASE_CLIENT_EMAIL;
|
||||
const privateKey = process.env.FIREBASE_PRIVATE_KEY;
|
||||
|
||||
if (projectId && clientEmail && privateKey) {
|
||||
try {
|
||||
if (!admin.apps.length) {
|
||||
this.app = admin.initializeApp({
|
||||
credential: admin.credential.cert({
|
||||
projectId,
|
||||
clientEmail,
|
||||
privateKey: privateKey.replace(/\\n/g, '\n'),
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
this.app = admin.app();
|
||||
}
|
||||
this.isConfigured = true;
|
||||
console.log('✅ Firebase Admin initialized successfully');
|
||||
} catch (error) {
|
||||
console.warn('⚠️ Firebase Admin initialization failed:', error.message);
|
||||
this.isConfigured = false;
|
||||
}
|
||||
} else {
|
||||
console.warn('⚠️ Firebase credentials not found. Auth will use fallback mode.');
|
||||
this.isConfigured = false;
|
||||
}
|
||||
}
|
||||
|
||||
async verifyIdToken(idToken: string): Promise<admin.auth.DecodedIdToken> {
|
||||
if (!this.isConfigured || !this.app) {
|
||||
throw new Error('Firebase not configured');
|
||||
}
|
||||
try {
|
||||
return await admin.auth().verifyIdToken(idToken);
|
||||
} catch (error) {
|
||||
throw new Error('Invalid token');
|
||||
}
|
||||
}
|
||||
|
||||
async getUser(uid: string): Promise<admin.auth.UserRecord> {
|
||||
if (!this.isConfigured || !this.app) {
|
||||
throw new Error('Firebase not configured');
|
||||
}
|
||||
try {
|
||||
return await admin.auth().getUser(uid);
|
||||
} catch (error) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
}
|
||||
|
||||
isFirebaseConfigured(): boolean {
|
||||
return this.isConfigured;
|
||||
}
|
||||
}
|
||||
35
apps/api/src/auth/google.strategy.ts
Normal file
35
apps/api/src/auth/google.strategy.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { Strategy, VerifyCallback } from 'passport-google-oauth20';
|
||||
|
||||
@Injectable()
|
||||
export class GoogleStrategy extends PassportStrategy(Strategy, 'google') {
|
||||
constructor() {
|
||||
super({
|
||||
clientID: process.env.GOOGLE_CLIENT_ID || '',
|
||||
clientSecret: process.env.GOOGLE_CLIENT_SECRET || '',
|
||||
callbackURL:
|
||||
process.env.GOOGLE_CALLBACK_URL ||
|
||||
'http://localhost:3001/api/auth/google/callback',
|
||||
scope: ['email', 'profile'],
|
||||
});
|
||||
}
|
||||
|
||||
async validate(
|
||||
accessToken: string,
|
||||
refreshToken: string,
|
||||
profile: any,
|
||||
done: VerifyCallback,
|
||||
): Promise<any> {
|
||||
const { id, name, emails, photos } = profile;
|
||||
|
||||
const user = {
|
||||
googleId: id,
|
||||
email: emails[0].value,
|
||||
name: name.givenName + ' ' + name.familyName,
|
||||
avatarUrl: photos[0]?.value,
|
||||
};
|
||||
|
||||
done(null, user);
|
||||
}
|
||||
}
|
||||
25
apps/api/src/auth/jwt.strategy.ts
Normal file
25
apps/api/src/auth/jwt.strategy.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||
|
||||
export interface JwtPayload {
|
||||
sub: string; // user ID
|
||||
email: string;
|
||||
iat?: number;
|
||||
exp?: number;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class JwtStrategy extends PassportStrategy(Strategy) {
|
||||
constructor() {
|
||||
super({
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
ignoreExpiration: false,
|
||||
secretOrKey: process.env.JWT_SECRET || 'your-secret-key-change-this',
|
||||
});
|
||||
}
|
||||
|
||||
async validate(payload: JwtPayload) {
|
||||
return { userId: payload.sub, email: payload.email };
|
||||
}
|
||||
}
|
||||
@@ -1,38 +1,34 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Body,
|
||||
Param,
|
||||
Delete,
|
||||
} from '@nestjs/common';
|
||||
import { Controller, Get, Post, Body, Param, Delete, Req, UseGuards } from '@nestjs/common';
|
||||
import { CategoriesService } from '../categories/categories.service';
|
||||
import { CreateCategoryDto } from '../categories/dto/create-category.dto';
|
||||
import { getTempUserId } from '../common/user.util';
|
||||
import { AuthGuard } from '../auth/auth.guard';
|
||||
|
||||
interface RequestWithUser {
|
||||
user: {
|
||||
userId: string;
|
||||
};
|
||||
}
|
||||
|
||||
@Controller('categories')
|
||||
@UseGuards(AuthGuard)
|
||||
export class CategoriesController {
|
||||
constructor(private readonly categoriesService: CategoriesService) {}
|
||||
|
||||
private userId(): string {
|
||||
return getTempUserId();
|
||||
}
|
||||
|
||||
@Post()
|
||||
create(@Body() createCategoryDto: CreateCategoryDto) {
|
||||
create(@Req() req: RequestWithUser, @Body() createCategoryDto: CreateCategoryDto) {
|
||||
return this.categoriesService.create({
|
||||
...createCategoryDto,
|
||||
userId: this.userId(),
|
||||
userId: req.user.userId,
|
||||
});
|
||||
}
|
||||
|
||||
@Get()
|
||||
findAll() {
|
||||
return this.categoriesService.findAll(this.userId());
|
||||
findAll(@Req() req: RequestWithUser) {
|
||||
return this.categoriesService.findAll(req.user.userId);
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
remove(@Param('id') id: string) {
|
||||
return this.categoriesService.remove(id, this.userId());
|
||||
remove(@Req() req: RequestWithUser, @Param('id') id: string) {
|
||||
return this.categoriesService.remove(id, req.user.userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
import { Injectable, NotFoundException, ConflictException } from '@nestjs/common';
|
||||
import {
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
ConflictException,
|
||||
} from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { CreateCategoryDto } from './dto/create-category.dto';
|
||||
|
||||
@@ -45,7 +49,7 @@ export class CategoriesService {
|
||||
|
||||
async findOrCreate(names: string[], userId: string) {
|
||||
const categories: any[] = [];
|
||||
|
||||
|
||||
for (const name of names) {
|
||||
let category = await this.prisma.category.findFirst({
|
||||
where: { name, userId },
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
export function getTempUserId(): string {
|
||||
const id = process.env.TEMP_USER_ID?.trim();
|
||||
if (!id) {
|
||||
throw new Error('TEMP_USER_ID is not set. Run the seed and set it in apps/api/.env');
|
||||
}
|
||||
return id;
|
||||
const id = process.env.TEMP_USER_ID?.trim();
|
||||
if (!id) {
|
||||
throw new Error(
|
||||
'TEMP_USER_ID is not set. Run the seed and set it in apps/api/.env',
|
||||
);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
export function getUserIdFromRequest(request: any): string {
|
||||
// If Firebase user is authenticated, use their UID
|
||||
if (request.user?.uid) {
|
||||
return request.user.uid;
|
||||
}
|
||||
|
||||
|
||||
// Fallback to temp user for development
|
||||
return getTempUserId();
|
||||
}
|
||||
@@ -21,4 +23,4 @@ export function createUserDecorator() {
|
||||
// This is a placeholder for a proper decorator implementation
|
||||
// In a real app, you'd create a proper parameter decorator
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,4 +16,4 @@ export class HealthController {
|
||||
await this.prisma.$queryRaw`SELECT 1`;
|
||||
return { db: 'connected' };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
import { NestExpressApplication } from '@nestjs/platform-express';
|
||||
import { join } from 'path';
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
const app = await NestFactory.create<NestExpressApplication>(AppModule);
|
||||
|
||||
// Serve static files from public directory
|
||||
app.useStaticAssets(join(__dirname, '..', 'public'));
|
||||
|
||||
// Allow web app to call API in dev
|
||||
const webOrigin = process.env.WEB_APP_URL ?? 'http://localhost:5173';
|
||||
@@ -16,7 +21,8 @@ async function bootstrap() {
|
||||
|
||||
const port = process.env.PORT ? Number(process.env.PORT) : 3000;
|
||||
await app.listen(port);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`API listening on http://localhost:${port}`);
|
||||
|
||||
console.log(`API listening on ${await app.getUrl()}`);
|
||||
}
|
||||
bootstrap();
|
||||
|
||||
void bootstrap();
|
||||
|
||||
72
apps/api/src/otp/otp-gate.guard.ts
Normal file
72
apps/api/src/otp/otp-gate.guard.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import {
|
||||
Injectable,
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { OtpService } from './otp.service';
|
||||
|
||||
interface RequestWithUser {
|
||||
user: {
|
||||
userId: string;
|
||||
};
|
||||
headers: Record<string, string>;
|
||||
body?: {
|
||||
otpCode?: string;
|
||||
otpMethod?: string;
|
||||
};
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class OtpGateGuard implements CanActivate {
|
||||
constructor(private otpService: OtpService) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request = context.switchToHttp().getRequest<RequestWithUser>();
|
||||
|
||||
// Get userId from JWT (set by AuthGuard)
|
||||
const userId = request.user?.userId;
|
||||
if (!userId) {
|
||||
// If no user, let AuthGuard handle it
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if user has OTP enabled
|
||||
const status = await this.otpService.getStatus(userId);
|
||||
|
||||
// If no OTP methods are enabled, allow access
|
||||
if (!status.emailEnabled && !status.totpEnabled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for OTP verification in headers or body
|
||||
const otpCode = request.headers['x-otp-code'] || request.body?.otpCode;
|
||||
const otpMethod = (request.headers['x-otp-method'] ||
|
||||
request.body?.otpMethod ||
|
||||
'totp') as 'email' | 'totp';
|
||||
|
||||
if (!otpCode) {
|
||||
throw new UnauthorizedException({
|
||||
message: 'OTP verification required',
|
||||
requiresOtp: true,
|
||||
availableMethods: {
|
||||
email: status.emailEnabled,
|
||||
totp: status.totpEnabled,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Verify the OTP
|
||||
const isValid = await this.otpService.verifyOtpGate(
|
||||
userId,
|
||||
otpCode,
|
||||
otpMethod,
|
||||
);
|
||||
|
||||
if (!isValid) {
|
||||
throw new UnauthorizedException('Invalid OTP code');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
150
apps/api/src/otp/otp.controller.ts
Normal file
150
apps/api/src/otp/otp.controller.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Body,
|
||||
UseGuards,
|
||||
Req,
|
||||
UnauthorizedException,
|
||||
SetMetadata,
|
||||
} from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { AuthGuard } from '../auth/auth.guard';
|
||||
import { OtpService } from './otp.service';
|
||||
|
||||
export const IS_PUBLIC_KEY = 'isPublic';
|
||||
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
|
||||
|
||||
interface RequestWithUser extends Request {
|
||||
user: {
|
||||
userId: string;
|
||||
email: string;
|
||||
};
|
||||
}
|
||||
|
||||
@Controller('otp')
|
||||
@UseGuards(AuthGuard)
|
||||
export class OtpController {
|
||||
constructor(
|
||||
private readonly otpService: OtpService,
|
||||
private readonly jwtService: JwtService,
|
||||
) {}
|
||||
|
||||
@Get('status')
|
||||
async getStatus(@Req() req: RequestWithUser) {
|
||||
return this.otpService.getStatus(req.user.userId);
|
||||
}
|
||||
|
||||
@Post('email/send')
|
||||
async sendEmailOtp(@Req() req: RequestWithUser) {
|
||||
return this.otpService.sendEmailOtp(req.user.userId);
|
||||
}
|
||||
|
||||
@Post('email/verify')
|
||||
async verifyEmailOtp(
|
||||
@Req() req: RequestWithUser,
|
||||
@Body() body: { code: string },
|
||||
) {
|
||||
return this.otpService.verifyEmailOtp(req.user.userId, body.code);
|
||||
}
|
||||
|
||||
@Post('email/disable')
|
||||
async disableEmailOtp(@Req() req: RequestWithUser) {
|
||||
return this.otpService.disableEmailOtp(req.user.userId);
|
||||
}
|
||||
|
||||
@Post('totp/setup')
|
||||
async setupTotp(@Req() req: RequestWithUser) {
|
||||
return this.otpService.setupTotp(req.user.userId);
|
||||
}
|
||||
|
||||
@Post('totp/verify')
|
||||
async verifyTotp(
|
||||
@Req() req: RequestWithUser,
|
||||
@Body() body: { code: string },
|
||||
) {
|
||||
return this.otpService.verifyTotp(req.user.userId, body.code);
|
||||
}
|
||||
|
||||
@Post('totp/disable')
|
||||
async disableTotp(@Req() req: RequestWithUser) {
|
||||
return this.otpService.disableTotp(req.user.userId);
|
||||
}
|
||||
|
||||
@Post('whatsapp/send')
|
||||
async sendWhatsappOtp(
|
||||
@Req() req: RequestWithUser,
|
||||
@Body() body: { mode?: 'test' | 'live' },
|
||||
) {
|
||||
return this.otpService.sendWhatsappOtp(
|
||||
req.user.userId,
|
||||
body.mode || 'test',
|
||||
);
|
||||
}
|
||||
|
||||
@Post('whatsapp/verify')
|
||||
async verifyWhatsappOtp(
|
||||
@Req() req: RequestWithUser,
|
||||
@Body() body: { code: string },
|
||||
) {
|
||||
return this.otpService.verifyWhatsappOtp(req.user.userId, body.code);
|
||||
}
|
||||
|
||||
@Post('whatsapp/disable')
|
||||
async disableWhatsappOtp(@Req() req: RequestWithUser) {
|
||||
return this.otpService.disableWhatsappOtp(req.user.userId);
|
||||
}
|
||||
|
||||
@Post('whatsapp/check')
|
||||
async checkWhatsappNumber(@Body() body: { phone: string }) {
|
||||
return this.otpService.checkWhatsappNumber(body.phone);
|
||||
}
|
||||
|
||||
@Public()
|
||||
@Post('email/resend')
|
||||
async resendEmailOtp(@Body() body: { tempToken: string }) {
|
||||
try {
|
||||
// Verify temp token
|
||||
const payload = this.jwtService.verify(body.tempToken);
|
||||
|
||||
if (!payload.temp) {
|
||||
throw new UnauthorizedException('Invalid token type');
|
||||
}
|
||||
|
||||
const userId = payload.userId || payload.sub;
|
||||
|
||||
if (!userId) {
|
||||
throw new UnauthorizedException('Invalid token payload');
|
||||
}
|
||||
|
||||
// Send OTP
|
||||
return this.otpService.sendEmailOtp(userId);
|
||||
} catch {
|
||||
throw new UnauthorizedException('Invalid or expired token');
|
||||
}
|
||||
}
|
||||
|
||||
@Public()
|
||||
@Post('whatsapp/resend')
|
||||
async resendWhatsappOtp(@Body() body: { tempToken: string }) {
|
||||
try {
|
||||
// Verify temp token
|
||||
const payload = this.jwtService.verify(body.tempToken);
|
||||
|
||||
if (!payload.temp) {
|
||||
throw new UnauthorizedException('Invalid token type');
|
||||
}
|
||||
|
||||
const userId = payload.userId || payload.sub;
|
||||
|
||||
if (!userId) {
|
||||
throw new UnauthorizedException('Invalid token payload');
|
||||
}
|
||||
|
||||
// Send WhatsApp OTP (live mode for login)
|
||||
return this.otpService.sendWhatsappOtp(userId, 'live');
|
||||
} catch {
|
||||
throw new UnauthorizedException('Invalid or expired token');
|
||||
}
|
||||
}
|
||||
}
|
||||
22
apps/api/src/otp/otp.module.ts
Normal file
22
apps/api/src/otp/otp.module.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Module, forwardRef } from '@nestjs/common';
|
||||
import { JwtModule } from '@nestjs/jwt';
|
||||
import { OtpController } from './otp.controller';
|
||||
import { OtpService } from './otp.service';
|
||||
import { OtpGateGuard } from './otp-gate.guard';
|
||||
import { AuthModule } from '../auth/auth.module';
|
||||
import { PrismaModule } from '../prisma/prisma.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
forwardRef(() => AuthModule),
|
||||
PrismaModule,
|
||||
JwtModule.register({
|
||||
secret: process.env.JWT_SECRET || 'your-secret-key',
|
||||
signOptions: { expiresIn: '7d' },
|
||||
}),
|
||||
],
|
||||
controllers: [OtpController],
|
||||
providers: [OtpService, OtpGateGuard],
|
||||
exports: [OtpService, OtpGateGuard],
|
||||
})
|
||||
export class OtpModule {}
|
||||
436
apps/api/src/otp/otp.service.ts
Normal file
436
apps/api/src/otp/otp.service.ts
Normal file
@@ -0,0 +1,436 @@
|
||||
import { Injectable, BadRequestException } from '@nestjs/common';
|
||||
import { authenticator } from 'otplib';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import axios from 'axios';
|
||||
import * as QRCode from 'qrcode';
|
||||
|
||||
@Injectable()
|
||||
export class OtpService {
|
||||
private emailOtpStore = new Map<string, { code: string; expiresAt: Date }>();
|
||||
private whatsappOtpStore = new Map<
|
||||
string,
|
||||
{ code: string; expiresAt: Date }
|
||||
>();
|
||||
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async sendEmailOtp(
|
||||
userId: string,
|
||||
): Promise<{ success: boolean; message: string }> {
|
||||
const user = await this.prisma.user.findUnique({ where: { id: userId } });
|
||||
if (!user) {
|
||||
throw new BadRequestException('User not found');
|
||||
}
|
||||
|
||||
const code = this.generateOtpCode();
|
||||
const expiresAt = new Date(Date.now() + 10 * 60 * 1000); // 10 minutes
|
||||
|
||||
// Store the code
|
||||
this.emailOtpStore.set(userId, { code, expiresAt });
|
||||
|
||||
// Send via webhook (you'll handle the actual email sending)
|
||||
try {
|
||||
await this.sendOtpViaWebhook(user.email, code);
|
||||
return { success: true, message: 'OTP sent to your email' };
|
||||
} catch (error: unknown) {
|
||||
console.error('Failed to send OTP via webhook:', error);
|
||||
// For development, log the code
|
||||
console.log(`📧 OTP Code for ${user.email}: ${code}`);
|
||||
return {
|
||||
success: true,
|
||||
message: 'OTP sent (check console for dev code)',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Verify email OTP for login (doesn't enable the feature)
|
||||
verifyEmailOtpForLogin(userId: string, code: string): boolean {
|
||||
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;
|
||||
}
|
||||
|
||||
// Clean up
|
||||
this.emailOtpStore.delete(userId);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Verify and enable email OTP (for setup)
|
||||
async verifyEmailOtp(
|
||||
userId: string,
|
||||
code: string,
|
||||
): Promise<{ success: boolean; message: string }> {
|
||||
const stored = this.emailOtpStore.get(userId);
|
||||
|
||||
if (!stored) {
|
||||
throw new BadRequestException('No OTP found. Please request a new one.');
|
||||
}
|
||||
|
||||
if (new Date() > stored.expiresAt) {
|
||||
this.emailOtpStore.delete(userId);
|
||||
throw new BadRequestException(
|
||||
'OTP has expired. Please request a new one.',
|
||||
);
|
||||
}
|
||||
|
||||
if (stored.code !== code) {
|
||||
throw new BadRequestException('Invalid OTP code.');
|
||||
}
|
||||
|
||||
// Enable email OTP in database
|
||||
await this.prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: { otpEmailEnabled: true },
|
||||
});
|
||||
|
||||
// Clean up
|
||||
this.emailOtpStore.delete(userId);
|
||||
|
||||
return { success: true, message: 'Email OTP enabled successfully' };
|
||||
}
|
||||
|
||||
async disableEmailOtp(
|
||||
userId: string,
|
||||
): Promise<{ success: boolean; message: string }> {
|
||||
await this.prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: { otpEmailEnabled: false },
|
||||
});
|
||||
|
||||
return { success: true, message: 'Email OTP disabled' };
|
||||
}
|
||||
|
||||
async setupTotp(userId: string): Promise<{ secret: string; qrCode: string }> {
|
||||
const user = await this.prisma.user.findUnique({ where: { id: userId } });
|
||||
if (!user) {
|
||||
throw new BadRequestException('User not found');
|
||||
}
|
||||
|
||||
const secret = authenticator.generateSecret();
|
||||
|
||||
// Store the secret in database (not yet enabled)
|
||||
await this.prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: { otpTotpSecret: secret },
|
||||
});
|
||||
|
||||
// Generate QR code URL for Google Authenticator
|
||||
const serviceName = 'Tabungin';
|
||||
const accountName = user.email;
|
||||
const otpauthUrl = authenticator.keyuri(accountName, serviceName, secret);
|
||||
|
||||
// Generate QR code as data URL
|
||||
const qrCodeDataUrl = await QRCode.toDataURL(otpauthUrl);
|
||||
|
||||
return {
|
||||
secret,
|
||||
qrCode: qrCodeDataUrl,
|
||||
};
|
||||
}
|
||||
|
||||
async verifyTotp(
|
||||
userId: string,
|
||||
code: string,
|
||||
): Promise<{ success: boolean; message: string }> {
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: { otpTotpSecret: true },
|
||||
});
|
||||
|
||||
if (!user?.otpTotpSecret) {
|
||||
throw new BadRequestException(
|
||||
'No TOTP setup found. Please setup TOTP first.',
|
||||
);
|
||||
}
|
||||
|
||||
const isValid = authenticator.verify({
|
||||
token: code,
|
||||
secret: user.otpTotpSecret,
|
||||
});
|
||||
|
||||
if (!isValid) {
|
||||
throw new BadRequestException('Invalid TOTP code.');
|
||||
}
|
||||
|
||||
// Enable TOTP in database
|
||||
await this.prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: { otpTotpEnabled: true },
|
||||
});
|
||||
|
||||
return { success: true, message: 'TOTP enabled successfully' };
|
||||
}
|
||||
|
||||
async disableTotp(
|
||||
userId: string,
|
||||
): Promise<{ success: boolean; message: string }> {
|
||||
await this.prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: {
|
||||
otpTotpEnabled: false,
|
||||
otpTotpSecret: null,
|
||||
},
|
||||
});
|
||||
|
||||
return { success: true, message: 'TOTP disabled' };
|
||||
}
|
||||
|
||||
async getStatus(userId: string) {
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
// OTP Gate - verify user's OTP during login
|
||||
async verifyOtpGate(
|
||||
userId: string,
|
||||
code: string,
|
||||
method: 'email' | 'totp',
|
||||
): Promise<boolean> {
|
||||
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) {
|
||||
// For login, we'd need to send a fresh OTP first
|
||||
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 authenticator.verify({ token: code, secret: user.otpTotpSecret });
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private generateOtpCode(): string {
|
||||
return Math.floor(100000 + Math.random() * 900000).toString();
|
||||
}
|
||||
|
||||
private async sendOtpViaWebhook(
|
||||
email: string,
|
||||
code: string,
|
||||
mode: 'test' | 'live' = 'test',
|
||||
): Promise<void> {
|
||||
// Use test webhook if available, otherwise use production webhook
|
||||
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.post(webhookUrl, {
|
||||
method: 'email',
|
||||
mode, // 'test' or 'live'
|
||||
to: email,
|
||||
subject: 'Tabungin - Your OTP Code',
|
||||
message: `Your OTP code is: ${code}. This code will expire in 10 minutes.`,
|
||||
code,
|
||||
});
|
||||
}
|
||||
|
||||
// WhatsApp OTP methods
|
||||
async sendWhatsappOtp(
|
||||
userId: string,
|
||||
mode: 'test' | 'live' = 'test',
|
||||
): Promise<{ success: boolean; message: string }> {
|
||||
const user = await this.prisma.user.findUnique({ where: { id: userId } });
|
||||
if (!user) {
|
||||
throw new BadRequestException('User not found');
|
||||
}
|
||||
|
||||
if (!user.phone) {
|
||||
throw new BadRequestException('Phone number not set');
|
||||
}
|
||||
|
||||
const code = this.generateOtpCode();
|
||||
const expiresAt = new Date(Date.now() + 10 * 60 * 1000); // 10 minutes
|
||||
|
||||
// Store the code
|
||||
this.whatsappOtpStore.set(userId, { code, expiresAt });
|
||||
|
||||
// Send via webhook
|
||||
try {
|
||||
await this.sendWhatsappOtpViaWebhook(user.phone, code, mode);
|
||||
return { success: true, message: 'OTP sent to your WhatsApp' };
|
||||
} catch (error: unknown) {
|
||||
console.error('Failed to send WhatsApp OTP via webhook:', error);
|
||||
// For development, log the code
|
||||
console.log(`📱 WhatsApp OTP Code for ${user.phone}: ${code}`);
|
||||
return {
|
||||
success: true,
|
||||
message: 'OTP sent (check console for dev code)',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async verifyWhatsappOtp(
|
||||
userId: string,
|
||||
code: string,
|
||||
): Promise<{ success: boolean; message: string }> {
|
||||
const stored = this.whatsappOtpStore.get(userId);
|
||||
|
||||
if (!stored) {
|
||||
throw new BadRequestException('No OTP found. Please request a new one.');
|
||||
}
|
||||
|
||||
if (new Date() > stored.expiresAt) {
|
||||
this.whatsappOtpStore.delete(userId);
|
||||
throw new BadRequestException(
|
||||
'OTP has expired. Please request a new one.',
|
||||
);
|
||||
}
|
||||
|
||||
if (stored.code !== code) {
|
||||
throw new BadRequestException('Invalid OTP code');
|
||||
}
|
||||
|
||||
// OTP is valid, enable WhatsApp OTP
|
||||
await this.prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: { otpWhatsappEnabled: true },
|
||||
});
|
||||
|
||||
// Clear the OTP
|
||||
this.whatsappOtpStore.delete(userId);
|
||||
|
||||
return { success: true, message: 'WhatsApp OTP enabled successfully' };
|
||||
}
|
||||
|
||||
verifyWhatsappOtpForLogin(userId: string, code: string): boolean {
|
||||
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;
|
||||
}
|
||||
|
||||
// Clear the OTP
|
||||
this.whatsappOtpStore.delete(userId);
|
||||
return true;
|
||||
}
|
||||
|
||||
async disableWhatsappOtp(
|
||||
userId: string,
|
||||
): Promise<{ success: boolean; message: string }> {
|
||||
await this.prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: { otpWhatsappEnabled: false },
|
||||
});
|
||||
|
||||
return { success: true, message: 'WhatsApp OTP disabled' };
|
||||
}
|
||||
|
||||
async checkWhatsappNumber(
|
||||
phone: string,
|
||||
): Promise<{ success: boolean; isRegistered: boolean; message: string }> {
|
||||
// Send check request to webhook
|
||||
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.post(webhookUrl, {
|
||||
method: 'whatsapp',
|
||||
mode: 'checknumber',
|
||||
phone,
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
isRegistered: response.data?.isRegistered || false,
|
||||
message: response.data?.message || 'Number checked',
|
||||
};
|
||||
} catch (error: unknown) {
|
||||
console.error('Failed to check WhatsApp number:', error);
|
||||
// For development, assume number is valid
|
||||
console.log(`📱 Checking WhatsApp number: ${phone} - Assumed valid`);
|
||||
return {
|
||||
success: true,
|
||||
isRegistered: true,
|
||||
message: 'Number is valid (dev mode)',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private async sendWhatsappOtpViaWebhook(
|
||||
phone: string,
|
||||
code: string,
|
||||
mode: 'test' | 'live' = 'test',
|
||||
): Promise<void> {
|
||||
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.post(webhookUrl, {
|
||||
method: 'whatsapp',
|
||||
mode, // 'test' or 'live'
|
||||
phone,
|
||||
message: `Your Tabungin OTP code is: ${code}. This code will expire in 10 minutes.`,
|
||||
code,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -6,4 +6,4 @@ import { PrismaService } from './prisma.service';
|
||||
providers: [PrismaService],
|
||||
exports: [PrismaService],
|
||||
})
|
||||
export class PrismaModule {}
|
||||
export class PrismaModule {}
|
||||
|
||||
@@ -13,4 +13,4 @@ export class PrismaService extends PrismaClient implements OnModuleInit {
|
||||
await app.close();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,26 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
const adminSeeder = {
|
||||
email: 'dwindi.ramadhana@gmail.com',
|
||||
password: 'tabungin2k25!@#',
|
||||
}
|
||||
|
||||
const TEMP_USER_ID =
|
||||
process.env.TEMP_USER_ID || '16b74848-daa3-4dc9-8de2-3cf59e08f8e3';
|
||||
|
||||
async function main() {
|
||||
const userId = '16b74848-daa3-4dc9-8de2-3cf59e08f8e3';
|
||||
const user = await prisma.user.upsert({
|
||||
where: { id: userId },
|
||||
where: { id: TEMP_USER_ID },
|
||||
update: {},
|
||||
create: {
|
||||
id: userId,
|
||||
id: TEMP_USER_ID,
|
||||
email: 'temp@example.com',
|
||||
},
|
||||
});
|
||||
|
||||
// create a sample money wallet if none
|
||||
const existing = await prisma.wallet.findFirst({
|
||||
where: { userId: user.id, kind: 'money' },
|
||||
});
|
||||
const existing = await prisma.wallet.findFirst({});
|
||||
|
||||
if (!existing) {
|
||||
await prisma.wallet.create({
|
||||
@@ -38,4 +43,4 @@ main()
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,9 +2,9 @@ import { z } from 'zod';
|
||||
|
||||
export const TransactionUpdateSchema = z.object({
|
||||
amount: z.number().positive().optional(),
|
||||
direction: z.enum(['in','out']).optional(),
|
||||
date: z.string().datetime().optional(), // ISO string
|
||||
direction: z.enum(['in', 'out']).optional(),
|
||||
date: z.string().datetime().optional(), // ISO string
|
||||
category: z.string().min(1).nullable().optional(),
|
||||
memo: z.string().min(1).nullable().optional(),
|
||||
});
|
||||
export type TransactionUpdateDto = z.infer<typeof TransactionUpdateSchema>;
|
||||
export type TransactionUpdateDto = z.infer<typeof TransactionUpdateSchema>;
|
||||
|
||||
@@ -1,39 +1,77 @@
|
||||
import { BadRequestException, Body, Controller, Get, Param, Post, Query, Res, Put, Delete } from '@nestjs/common';
|
||||
import {
|
||||
BadRequestException,
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Put,
|
||||
Param,
|
||||
Delete,
|
||||
UseGuards,
|
||||
Query,
|
||||
Res,
|
||||
Req,
|
||||
} from '@nestjs/common';
|
||||
import type { Response } from 'express';
|
||||
import { AuthGuard } from '../auth/auth.guard';
|
||||
import { TransactionsService } from './transactions.service';
|
||||
import { TransactionUpdateSchema } from './transaction.dto';
|
||||
|
||||
interface RequestWithUser {
|
||||
user: {
|
||||
userId: string;
|
||||
};
|
||||
}
|
||||
|
||||
@Controller('wallets/:walletId/transactions')
|
||||
@UseGuards(AuthGuard)
|
||||
export class TransactionsController {
|
||||
constructor(private readonly tx: TransactionsService) {}
|
||||
|
||||
@Get()
|
||||
list(@Param('walletId') walletId: string) {
|
||||
return this.tx.list(walletId);
|
||||
list(@Req() req: RequestWithUser, @Param('walletId') walletId: string) {
|
||||
return this.tx.list(req.user.userId, walletId);
|
||||
}
|
||||
|
||||
@Post()
|
||||
create(
|
||||
@Req() req: RequestWithUser,
|
||||
@Param('walletId') walletId: string,
|
||||
@Body() body: { amount: number | string; direction: 'in' | 'out'; date?: string; category?: string; memo?: string }
|
||||
@Body()
|
||||
body: {
|
||||
amount: number | string;
|
||||
direction: 'in' | 'out';
|
||||
date?: string;
|
||||
category?: string;
|
||||
memo?: string;
|
||||
},
|
||||
) {
|
||||
return this.tx.create(walletId, body);
|
||||
return this.tx.create(req.user.userId, walletId, body);
|
||||
}
|
||||
|
||||
@Get('export.csv')
|
||||
async exportCsv(
|
||||
@Req() req: RequestWithUser,
|
||||
@Param('walletId') walletId: string,
|
||||
@Query('from') from: string | undefined,
|
||||
@Query('to') to: string | undefined,
|
||||
@Query('category') category: string | undefined,
|
||||
@Query('direction') direction: 'in' | 'out' | undefined,
|
||||
@Res() res: Response
|
||||
@Res() res: Response,
|
||||
) {
|
||||
const rows = await this.tx.listWithFilters(walletId, { from, to, category, direction });
|
||||
const rows = await this.tx.listWithFilters(req.user.userId, walletId, {
|
||||
from,
|
||||
to,
|
||||
category,
|
||||
direction,
|
||||
});
|
||||
|
||||
// CSV headers
|
||||
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
||||
res.setHeader('Content-Disposition', `attachment; filename="transactions_${walletId}.csv"`);
|
||||
res.setHeader(
|
||||
'Content-Disposition',
|
||||
`attachment; filename="transactions_${walletId}.csv"`,
|
||||
);
|
||||
|
||||
// Write CSV header row
|
||||
res.write(`date,category,memo,direction,amount\n`);
|
||||
@@ -60,17 +98,27 @@ export class TransactionsController {
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
async update(@Param('walletId') walletId: string, @Param('id') id: string, @Body() body: unknown) {
|
||||
async update(
|
||||
@Req() req: RequestWithUser,
|
||||
@Param('walletId') walletId: string,
|
||||
@Param('id') id: string,
|
||||
@Body() body: unknown,
|
||||
) {
|
||||
try {
|
||||
const parsed = TransactionUpdateSchema.parse(body);
|
||||
return this.tx.update(walletId, id, parsed);
|
||||
} catch (e: any) {
|
||||
throw new BadRequestException(e?.errors ?? 'Invalid payload');
|
||||
return this.tx.update(req.user.userId, walletId, id, parsed);
|
||||
} catch (e) {
|
||||
const error = e as { errors?: unknown };
|
||||
throw new BadRequestException(error?.errors ?? 'Invalid payload');
|
||||
}
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
delete(@Param('walletId') walletId: string, @Param('id') id: string) {
|
||||
return this.tx.delete(walletId, id);
|
||||
delete(
|
||||
@Req() req: RequestWithUser,
|
||||
@Param('walletId') walletId: string,
|
||||
@Param('id') id: string,
|
||||
) {
|
||||
return this.tx.delete(req.user.userId, walletId, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,12 @@ import { Module } from '@nestjs/common';
|
||||
import { TransactionsService } from './transactions.service';
|
||||
import { TransactionsController } from './transactions.controller';
|
||||
import { PrismaModule } from '../prisma/prisma.module';
|
||||
import { OtpModule } from '../otp/otp.module';
|
||||
|
||||
@Module({
|
||||
imports: [PrismaModule],
|
||||
imports: [PrismaModule, OtpModule],
|
||||
providers: [TransactionsService],
|
||||
controllers: [TransactionsController],
|
||||
exports: [TransactionsService],
|
||||
})
|
||||
export class TransactionsModule {}
|
||||
export class TransactionsModule {}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { getTempUserId } from '../common/user.util';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import type { TransactionUpdateDto } from './transaction.dto';
|
||||
|
||||
@@ -8,35 +7,37 @@ import type { TransactionUpdateDto } from './transaction.dto';
|
||||
export class TransactionsService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
private userId(): string {
|
||||
return getTempUserId();
|
||||
}
|
||||
|
||||
list(walletId: string) {
|
||||
list(userId: string, walletId: string) {
|
||||
return this.prisma.transaction.findMany({
|
||||
where: { userId: this.userId(), walletId },
|
||||
where: { userId, walletId },
|
||||
orderBy: { date: 'desc' },
|
||||
take: 200,
|
||||
});
|
||||
}
|
||||
|
||||
listAll() {
|
||||
listAll(userId: string) {
|
||||
return this.prisma.transaction.findMany({
|
||||
where: { userId: this.userId() },
|
||||
where: { userId },
|
||||
orderBy: { date: 'desc' },
|
||||
take: 1000,
|
||||
});
|
||||
}
|
||||
|
||||
listWithFilters(
|
||||
userId: string,
|
||||
walletId: string,
|
||||
filters: { from?: string; to?: string; category?: string; direction?: 'in' | 'out' }
|
||||
filters: {
|
||||
from?: string;
|
||||
to?: string;
|
||||
category?: string;
|
||||
direction?: 'in' | 'out';
|
||||
},
|
||||
) {
|
||||
const where: Prisma.TransactionWhereInput = {
|
||||
userId: getTempUserId(),
|
||||
userId,
|
||||
walletId,
|
||||
};
|
||||
|
||||
|
||||
if (filters.direction) where.direction = filters.direction;
|
||||
if (filters.category) where.category = filters.category;
|
||||
if (filters.from || filters.to) {
|
||||
@@ -44,32 +45,39 @@ export class TransactionsService {
|
||||
if (filters.from) (where.date as any).gte = new Date(filters.from);
|
||||
if (filters.to) (where.date as any).lte = new Date(filters.to);
|
||||
}
|
||||
|
||||
|
||||
return this.prisma.transaction.findMany({
|
||||
where,
|
||||
orderBy: { date: 'desc' },
|
||||
});
|
||||
}
|
||||
|
||||
async create(walletId: string, input: {
|
||||
amount: string | number; direction: 'in' | 'out';
|
||||
date?: string; category?: string; memo?: string;
|
||||
}) {
|
||||
const amountNum = typeof input.amount === 'string' ? Number(input.amount) : input.amount;
|
||||
async create(
|
||||
userId: string,
|
||||
walletId: string,
|
||||
input: {
|
||||
amount: string | number;
|
||||
direction: 'in' | 'out';
|
||||
date?: string;
|
||||
category?: string;
|
||||
memo?: string;
|
||||
},
|
||||
) {
|
||||
const amountNum =
|
||||
typeof input.amount === 'string' ? Number(input.amount) : input.amount;
|
||||
if (!Number.isFinite(amountNum)) throw new Error('amount must be a number');
|
||||
|
||||
const date = input.date ? new Date(input.date) : new Date();
|
||||
|
||||
const wallet = await this.prisma.wallet.findFirst({
|
||||
where: { id: walletId, userId: this.userId(), deletedAt: null },
|
||||
where: { id: walletId, userId, deletedAt: null },
|
||||
select: { id: true },
|
||||
});
|
||||
if (!wallet) throw new Error('wallet not found');
|
||||
|
||||
|
||||
return this.prisma.transaction.create({
|
||||
data: {
|
||||
userId: this.userId(),
|
||||
userId,
|
||||
walletId,
|
||||
amount: amountNum,
|
||||
direction: input.direction,
|
||||
@@ -80,14 +88,18 @@ export class TransactionsService {
|
||||
});
|
||||
}
|
||||
|
||||
async update(walletId: string, id: string, dto: TransactionUpdateDto) {
|
||||
async update(
|
||||
userId: string,
|
||||
walletId: string,
|
||||
id: string,
|
||||
dto: TransactionUpdateDto,
|
||||
) {
|
||||
// ensure the row exists and belongs to the current user + wallet
|
||||
const existing = await this.prisma.transaction.findFirst({
|
||||
where: { id, walletId, userId: this.userId() },
|
||||
where: { id, walletId, userId },
|
||||
});
|
||||
if (!existing) throw new Error('transaction not found');
|
||||
|
||||
|
||||
// normalize inputs
|
||||
const data: any = {};
|
||||
if (dto.amount !== undefined) data.amount = Number(dto.amount);
|
||||
@@ -95,17 +107,17 @@ export class TransactionsService {
|
||||
if (dto.category !== undefined) data.category = dto.category || null;
|
||||
if (dto.memo !== undefined) data.memo = dto.memo || null;
|
||||
if (dto.date !== undefined) data.date = new Date(dto.date);
|
||||
|
||||
|
||||
return this.prisma.transaction.update({
|
||||
where: { id: existing.id },
|
||||
data,
|
||||
});
|
||||
}
|
||||
|
||||
async delete(walletId: string, id: string) {
|
||||
async delete(userId: string, walletId: string, id: string) {
|
||||
// ensure the row exists and belongs to the current user + wallet
|
||||
const existing = await this.prisma.transaction.findFirst({
|
||||
where: { id, walletId, userId: this.userId() },
|
||||
where: { id, walletId, userId },
|
||||
});
|
||||
if (!existing) throw new Error('transaction not found');
|
||||
|
||||
@@ -113,4 +125,4 @@ export class TransactionsService {
|
||||
where: { id: existing.id },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { Controller, Get, Put, Delete, Body, Req, UseGuards } from '@nestjs/common';
|
||||
import { AuthGuard } from '../auth/auth.guard';
|
||||
import { UsersService } from './users.service';
|
||||
|
||||
interface RequestWithUser extends Request {
|
||||
user: {
|
||||
userId: string;
|
||||
email: string;
|
||||
};
|
||||
}
|
||||
|
||||
@Controller('users')
|
||||
@UseGuards(AuthGuard)
|
||||
export class UsersController {
|
||||
constructor(private readonly users: UsersService) {}
|
||||
|
||||
@@ -9,4 +18,25 @@ export class UsersController {
|
||||
me() {
|
||||
return this.users.me();
|
||||
}
|
||||
}
|
||||
|
||||
@Put('profile')
|
||||
async updateProfile(
|
||||
@Req() req: RequestWithUser,
|
||||
@Body() body: { name?: string; phone?: string },
|
||||
) {
|
||||
return this.users.updateProfile(req.user.userId, body);
|
||||
}
|
||||
|
||||
@Get('auth-info')
|
||||
async getAuthInfo(@Req() req: RequestWithUser) {
|
||||
return this.users.getAuthInfo(req.user.userId);
|
||||
}
|
||||
|
||||
@Delete('account')
|
||||
async deleteAccount(
|
||||
@Req() req: RequestWithUser,
|
||||
@Body() body: { password: string },
|
||||
) {
|
||||
return this.users.deleteAccount(req.user.userId, body.password);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,4 +9,4 @@ import { PrismaModule } from '../prisma/prisma.module';
|
||||
controllers: [UsersController],
|
||||
exports: [UsersService],
|
||||
})
|
||||
export class UsersModule {}
|
||||
export class UsersModule {}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Injectable, BadRequestException, UnauthorizedException } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { getTempUserId } from '../common/user.util';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
|
||||
@Injectable()
|
||||
export class UsersService {
|
||||
@@ -10,4 +11,101 @@ export class UsersService {
|
||||
const userId = getTempUserId();
|
||||
return this.prisma.user.findUnique({ where: { id: userId } });
|
||||
}
|
||||
}
|
||||
|
||||
async updateProfile(userId: string, data: { name?: string; phone?: string }) {
|
||||
try {
|
||||
const user = await this.prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: {
|
||||
...(data.name !== undefined && { name: data.name }),
|
||||
...(data.phone !== undefined && { phone: data.phone }),
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
email: true,
|
||||
name: true,
|
||||
phone: true,
|
||||
avatarUrl: true,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Profile updated successfully',
|
||||
user,
|
||||
};
|
||||
} catch (error: any) {
|
||||
if (error.code === 'P2002') {
|
||||
throw new BadRequestException('Phone number already in use');
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getAuthInfo(userId: string) {
|
||||
// Get user with password hash and avatar
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: {
|
||||
passwordHash: true,
|
||||
avatarUrl: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Check if user has Google OAuth (avatar from Google or starts with /avatars/)
|
||||
const hasGoogleAuth =
|
||||
user?.avatarUrl?.includes('googleusercontent.com') ||
|
||||
user?.avatarUrl?.startsWith('/avatars/') ||
|
||||
false;
|
||||
|
||||
return {
|
||||
hasGoogleAuth,
|
||||
hasPassword: user?.passwordHash !== null,
|
||||
};
|
||||
}
|
||||
|
||||
async deleteAccount(userId: string, password: string) {
|
||||
// Get user with password hash
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: {
|
||||
passwordHash: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
throw new BadRequestException('User not found');
|
||||
}
|
||||
|
||||
if (!user.passwordHash) {
|
||||
throw new BadRequestException(
|
||||
'Cannot delete account without password. Please set a password first.',
|
||||
);
|
||||
}
|
||||
|
||||
// Verify password
|
||||
const isValid = await bcrypt.compare(password, user.passwordHash);
|
||||
if (!isValid) {
|
||||
throw new UnauthorizedException('Incorrect password');
|
||||
}
|
||||
|
||||
// Delete related data first (to avoid foreign key constraint errors)
|
||||
// Delete AuthAccount records
|
||||
await this.prisma.authAccount.deleteMany({
|
||||
where: { userId: userId },
|
||||
});
|
||||
|
||||
// Delete other related data if any
|
||||
// Add more deleteMany calls here for other tables that reference User
|
||||
|
||||
// Finally, delete the user
|
||||
await this.prisma.user.delete({
|
||||
where: { id: userId },
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message: 'Account deleted successfully',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user