From 00847a944952063f258df272986389e6f4878d96 Mon Sep 17 00:00:00 2001 From: mahyargdz Date: Sat, 26 Jul 2025 16:57:56 +0330 Subject: [PATCH] fix: bug in monitoring service --- src/configs/swagger.config.ts | 6 +- src/modules/auth/DTO/create-two-factor.dto.ts | 10 +- src/modules/auth/auth.controller.ts | 7 ++ .../auth/services/two-factor.service.ts | 4 +- .../services/email-monitoring.service.ts | 52 +++++++---- .../two-factor-response.interface.ts | 1 - .../interfaces/two-factor.interface.ts | 34 +++---- .../services/wildduck-two-factor.service.ts | 91 ++++--------------- 8 files changed, 90 insertions(+), 115 deletions(-) diff --git a/src/configs/swagger.config.ts b/src/configs/swagger.config.ts index 96659dc..9888beb 100755 --- a/src/configs/swagger.config.ts +++ b/src/configs/swagger.config.ts @@ -20,5 +20,9 @@ export function getSwaggerDocument(app: NestFastifyApplication) { .build(); const swaggerDocument = SwaggerModule.createDocument(app, swaggerConfig); - SwaggerModule.setup("api-docs", app, swaggerDocument); + SwaggerModule.setup("api-docs", app, swaggerDocument, { + swaggerOptions: { + persistAuthorization: true, + }, + }); } diff --git a/src/modules/auth/DTO/create-two-factor.dto.ts b/src/modules/auth/DTO/create-two-factor.dto.ts index 0362cd0..44bfd9b 100644 --- a/src/modules/auth/DTO/create-two-factor.dto.ts +++ b/src/modules/auth/DTO/create-two-factor.dto.ts @@ -1,16 +1,16 @@ import { ApiProperty, ApiPropertyOptional } from "@nestjs/swagger"; -import { IsBoolean, IsOptional, IsString } from "class-validator"; +import { IsOptional, IsString } from "class-validator"; export class Setup2FADto { - @ApiPropertyOptional({ description: "TOTP issuer name", example: "DMail Service" }) @IsOptional() @IsString() + @ApiPropertyOptional({ description: "TOTP issuer name", example: "DMail Service" }) issuer?: string; - @ApiPropertyOptional({ description: "Generate fresh secret", default: false }) @IsOptional() - @IsBoolean() - fresh?: boolean; + @IsString() + @ApiPropertyOptional({ description: "TOTP label", example: "DMail Service" }) + label?: string; } export class Enable2FADto { diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index 68ce22d..7b16cc6 100644 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -54,6 +54,7 @@ export class AuthController { return this.authService.changePassword(userId, changePasswordDto); } + @AuthGuards() @Post("setup") @ApiOperation({ summary: "Setup TOTP 2FA for user" }) @ApiResponse({ status: 200, description: "2FA setup initiated successfully" }) @@ -62,6 +63,7 @@ export class AuthController { return this.twoFactorService.setup2FA(wildduckUserId, setup2FADto); } + @AuthGuards() @Post("enable") @ApiOperation({ summary: "Enable TOTP 2FA after verification" }) @ApiResponse({ status: 200, description: "2FA enabled successfully" }) @@ -70,6 +72,7 @@ export class AuthController { return this.twoFactorService.enable2FA(wildduckUserId, enable2FADto); } + @AuthGuards() @Post("verify") @ApiOperation({ summary: "Verify TOTP token" }) @ApiResponse({ status: 200, description: "2FA token verification result" }) @@ -77,6 +80,7 @@ export class AuthController { return this.twoFactorService.verify2FA(wildduckUserId, verify2FADto); } + @AuthGuards() @Get("status") @ApiOperation({ summary: "Get 2FA status for user" }) @ApiResponse({ status: 200, description: "2FA status retrieved successfully" }) @@ -85,6 +89,7 @@ export class AuthController { return this.twoFactorService.get2FAStatus(wildduckUserId); } + @AuthGuards() @Delete("disable") @ApiOperation({ summary: "Disable 2FA for user" }) @ApiResponse({ status: 200, description: "2FA disabled successfully" }) @@ -93,6 +98,7 @@ export class AuthController { return this.twoFactorService.disable2FA(wildduckUserId); } + @AuthGuards() @Post("backup-codes/generate") @ApiOperation({ summary: "Generate new backup codes" }) @ApiResponse({ status: 200, description: "Backup codes generated successfully" }) @@ -101,6 +107,7 @@ export class AuthController { return this.twoFactorService.generateBackupCodes(wildduckUserId); } + @AuthGuards() @Post("backup-codes/use") @ApiOperation({ summary: "Use backup code for authentication" }) @ApiResponse({ status: 200, description: "Backup code verification result" }) diff --git a/src/modules/auth/services/two-factor.service.ts b/src/modules/auth/services/two-factor.service.ts index 370b021..0db5fa3 100644 --- a/src/modules/auth/services/two-factor.service.ts +++ b/src/modules/auth/services/two-factor.service.ts @@ -21,14 +21,14 @@ export class TwoFactorService { const response = await firstValueFrom( this.mailServerService.twoFactor.setupTOTP(wildduckUserId, { issuer: setup2FADto.issuer || "DMail Service", - fresh: setup2FADto.fresh || false, + label: setup2FADto.label || "DMail Service", }), ); return { message: TwoFactorMessage.TWO_FACTOR_SETUP_INITIATED, qr: response.qrcode, - secret: response.seed, + seed: response.seed, }; } diff --git a/src/modules/email-utils/services/email-monitoring.service.ts b/src/modules/email-utils/services/email-monitoring.service.ts index d6531da..e4d50eb 100644 --- a/src/modules/email-utils/services/email-monitoring.service.ts +++ b/src/modules/email-utils/services/email-monitoring.service.ts @@ -33,10 +33,7 @@ export class EmailMonitoringService implements OnModuleInit { await this.startMonitoring(); } - //****************************************************** */ - // MONITORING SCHEDULER FUNCTIONALITY - //****************************************************** */ - + //============================================== async startMonitoring() { if (this.isMonitoringActive) { this.logger.warn("Email monitoring is already running"); @@ -104,9 +101,7 @@ export class EmailMonitoringService implements OnModuleInit { await this.startMonitoring(); } - //****************************************************** */ - // CORE MONITORING FUNCTIONALITY - //****************************************************** */ + //============================================== async scheduleEmailCheck() { try { @@ -155,6 +150,8 @@ export class EmailMonitoringService implements OnModuleInit { } } + //============================================== + async processUserEmailCheck(job: EmailMonitoringJob) { const { userId, lastCheckTimestamp } = job; @@ -202,6 +199,8 @@ export class EmailMonitoringService implements OnModuleInit { } } + //============================================== + private async processNewEmailMessage(userId: string, _wildduckUserId: string, message: Record) { try { if (message.seen) { @@ -221,11 +220,21 @@ export class EmailMonitoringService implements OnModuleInit { timestamp: message.timestamp, }; - await this.notificationQueue.addNewEmailNotification(userId, { - fromEmail: message.from[0].address, - fromName: message.from[0].name, - subject: message.subject, - }); + // Check if message.from exists and has at least one element + if (message.from && Array.isArray(message.from) && message.from.length > 0 && message.from[0]) { + await this.notificationQueue.addNewEmailNotification(userId, { + fromEmail: message.from[0].address || "unknown@example.com", + fromName: message.from[0].name || "Unknown Sender", + subject: message.subject || "No Subject", + }); + } else { + this.logger.warn(`Email message ${message.id} for user ${userId} has invalid 'from' field`); + await this.notificationQueue.addNewEmailNotification(userId, { + fromEmail: "unknown@example.com", + fromName: "Unknown Sender", + subject: message.subject || "No Subject", + }); + } await this.emailGateway.notifyNewEmail(userId, emailPayload); } catch (error) { @@ -233,6 +242,8 @@ export class EmailMonitoringService implements OnModuleInit { } } + //============================================== + private async updateUnreadCount(userId: string, wildduckUserId: string, userEmailAddress?: string) { try { const inboxMailboxId = await this.mailboxResolver.getInboxMailboxId(wildduckUserId); @@ -264,12 +275,15 @@ export class EmailMonitoringService implements OnModuleInit { } } - async checkUserEmails(userId: string): Promise { + //============================================== + + async checkUserEmails(userId: string, wildduckUserId: string): Promise { await this.monitoringQueue.add( EMAIL_QUEUE_CONSTANTS.EMAIL_QUEUE_PROCESSING, { userId, - lastCheckTimestamp: new Date(Date.now() - 60000), // Check emails from last minute + wildduckUserId: wildduckUserId, + lastCheckTimestamp: new Date(Date.now() - 60000), }, { attempts: 3, @@ -279,9 +293,7 @@ export class EmailMonitoringService implements OnModuleInit { ); } - //****************************************************** */ - // STATUS AND MANAGEMENT FUNCTIONALITY - //****************************************************** */ + //============================================== async getMonitoringStatus() { try { @@ -308,6 +320,8 @@ export class EmailMonitoringService implements OnModuleInit { } } + //============================================== + async getQueueStatus() { const [waiting, active, completed, failed] = await Promise.all([ this.monitoringQueue.getWaiting(), @@ -324,6 +338,8 @@ export class EmailMonitoringService implements OnModuleInit { }; } + //============================================== + async getDetailedQueueInfo() { try { const [waiting, active, completed, failed, repeatableJobs] = await Promise.all([ @@ -364,6 +380,8 @@ export class EmailMonitoringService implements OnModuleInit { } } + //============================================== + async cleanupJobs() { try { // Clean completed jobs older than 30 minutes diff --git a/src/modules/mail-server/interfaces/two-factor-response.interface.ts b/src/modules/mail-server/interfaces/two-factor-response.interface.ts index 76581da..598602a 100644 --- a/src/modules/mail-server/interfaces/two-factor-response.interface.ts +++ b/src/modules/mail-server/interfaces/two-factor-response.interface.ts @@ -16,7 +16,6 @@ export interface TOTPSetupResponse { success: true; qrcode: string; seed: string; - secret?: string; } export interface U2FRegistrationStartResponse { diff --git a/src/modules/mail-server/interfaces/two-factor.interface.ts b/src/modules/mail-server/interfaces/two-factor.interface.ts index d946b10..194354b 100644 --- a/src/modules/mail-server/interfaces/two-factor.interface.ts +++ b/src/modules/mail-server/interfaces/two-factor.interface.ts @@ -1,3 +1,8 @@ +export interface BaseTwoFactorData { + sess?: string; + ip?: string; +} + export interface TwoFactorTOTP { enabled: boolean; issuer?: string; @@ -18,35 +23,32 @@ export interface BackupCode { used_time?: string; } -export interface TOTPSetupData { +export interface TOTPSetupData extends BaseTwoFactorData { issuer?: string; - fresh?: boolean; - sess?: string; - ip?: string; + label?: string; } -export interface TOTPVerificationData { +export interface TOTPVerificationData extends BaseTwoFactorData { token: string; - sess?: string; - ip?: string; } -export interface U2FRegistrationData { +export interface U2FRegistrationData extends BaseTwoFactorData { requestId: string; response: any; - sess?: string; - ip?: string; } -export interface U2FAuthenticationData { +export interface U2FAuthenticationData extends BaseTwoFactorData { requestId: string; response: any; - sess?: string; - ip?: string; } -export interface BackupCodeData { +export interface BackupCodeData extends BaseTwoFactorData { code: string; - sess?: string; - ip?: string; +} + +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface TwoFactorDisableData extends BaseTwoFactorData {} + +export interface BackupCodesGenerateData extends BaseTwoFactorData { + count?: number; } diff --git a/src/modules/mail-server/services/wildduck-two-factor.service.ts b/src/modules/mail-server/services/wildduck-two-factor.service.ts index a5593d9..45f4a94 100644 --- a/src/modules/mail-server/services/wildduck-two-factor.service.ts +++ b/src/modules/mail-server/services/wildduck-two-factor.service.ts @@ -8,6 +8,14 @@ import { TOTPSetupResponse, TwoFactorStatusResponse, } from "../interfaces/two-factor-response.interface"; +import { + BackupCodeData, + BaseTwoFactorData, + TOTPSetupData, + TOTPVerificationData, + U2FAuthenticationData, + U2FRegistrationData, +} from "../interfaces/two-factor.interface"; import { WildDuckSuccessResponse } from "../interfaces/wildduck-response.interface"; @Injectable() @@ -23,43 +31,21 @@ export class WildDuckTwoFactorService extends WildDuckBaseService { /** * Setup TOTP 2FA */ - setupTOTP( - userId: string, - data?: { - issuer?: string; - fresh?: boolean; - sess?: string; - ip?: string; - }, - ): Observable { + setupTOTP(userId: string, data: TOTPSetupData): Observable { return this.post(`/users/${userId}/2fa/totp/setup`, data); } /** * Enable TOTP 2FA */ - enableTOTP( - userId: string, - data: { - token: string; - sess?: string; - ip?: string; - }, - ): Observable { + enableTOTP(userId: string, data: TOTPVerificationData): Observable { return this.post(`/users/${userId}/2fa/totp/enable`, data); } /** * Check TOTP token */ - checkTOTP( - userId: string, - data: { - token: string; - sess?: string; - ip?: string; - }, - ): Observable { + checkTOTP(userId: string, data: TOTPVerificationData): Observable { return this.post(`/users/${userId}/2fa/totp/check`, data); } @@ -82,10 +68,7 @@ export class WildDuckTwoFactorService extends WildDuckBaseService { */ startU2FRegistration( userId: string, - data?: { - sess?: string; - ip?: string; - }, + data?: U2FRegistrationData, ): Observable< WildDuckSuccessResponse<{ requestId: string; @@ -103,15 +86,7 @@ export class WildDuckTwoFactorService extends WildDuckBaseService { /** * Complete U2F registration */ - completeU2FRegistration( - userId: string, - data: { - requestId: string; - response: any; - sess?: string; - ip?: string; - }, - ): Observable { + completeU2FRegistration(userId: string, data: U2FRegistrationData): Observable { return this.post(`/users/${userId}/2fa/u2f/enable`, data); } @@ -120,10 +95,7 @@ export class WildDuckTwoFactorService extends WildDuckBaseService { */ startU2FAuthentication( userId: string, - data?: { - sess?: string; - ip?: string; - }, + data?: U2FAuthenticationData, ): Observable< WildDuckSuccessResponse<{ requestId: string; @@ -141,15 +113,7 @@ export class WildDuckTwoFactorService extends WildDuckBaseService { /** * Complete U2F authentication */ - completeU2FAuthentication( - userId: string, - data: { - requestId: string; - response: any; - sess?: string; - ip?: string; - }, - ): Observable { + completeU2FAuthentication(userId: string, data: U2FAuthenticationData): Observable { return this.post(`/users/${userId}/2fa/u2f/check`, data); } @@ -170,13 +134,7 @@ export class WildDuckTwoFactorService extends WildDuckBaseService { /** * Generate backup codes */ - generateBackupCodes( - userId: string, - data?: { - sess?: string; - ip?: string; - }, - ): Observable { + generateBackupCodes(userId: string, data?: BackupCodeData): Observable { return this.post(`/users/${userId}/2fa/backup/generate`, data); } @@ -190,27 +148,14 @@ export class WildDuckTwoFactorService extends WildDuckBaseService { /** * Use backup code */ - useBackupCode( - userId: string, - data: { - code: string; - sess?: string; - ip?: string; - }, - ): Observable { + useBackupCode(userId: string, data: BackupCodeData): Observable { return this.post(`/users/${userId}/2fa/backup/use`, data); } /** * Disable all 2FA methods */ - disableAll2FA( - userId: string, - params?: { - sess?: string; - ip?: string; - }, - ): Observable { + disableAll2FA(userId: string, params?: BaseTwoFactorData): Observable { return this.delete(`/users/${userId}/2fa`, params); } }