fix: bug in monitoring service

This commit is contained in:
mahyargdz
2025-07-26 16:57:56 +03:30
parent c6defaa1d8
commit 00847a9449
8 changed files with 90 additions and 115 deletions
+5 -1
View File
@@ -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,
},
});
}
@@ -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 {
+7
View File
@@ -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" })
@@ -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,
};
}
@@ -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<string, any>) {
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<void> {
//==============================================
async checkUserEmails(userId: string, wildduckUserId: string): Promise<void> {
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
@@ -16,7 +16,6 @@ export interface TOTPSetupResponse {
success: true;
qrcode: string;
seed: string;
secret?: string;
}
export interface U2FRegistrationStartResponse {
@@ -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;
}
@@ -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<TOTPSetupResponse> {
setupTOTP(userId: string, data: TOTPSetupData): Observable<TOTPSetupResponse> {
return this.post<TOTPSetupResponse>(`/users/${userId}/2fa/totp/setup`, data);
}
/**
* Enable TOTP 2FA
*/
enableTOTP(
userId: string,
data: {
token: string;
sess?: string;
ip?: string;
},
): Observable<WildDuckSuccessResponse> {
enableTOTP(userId: string, data: TOTPVerificationData): Observable<WildDuckSuccessResponse> {
return this.post<WildDuckSuccessResponse>(`/users/${userId}/2fa/totp/enable`, data);
}
/**
* Check TOTP token
*/
checkTOTP(
userId: string,
data: {
token: string;
sess?: string;
ip?: string;
},
): Observable<WildDuckSuccessResponse> {
checkTOTP(userId: string, data: TOTPVerificationData): Observable<WildDuckSuccessResponse> {
return this.post<WildDuckSuccessResponse>(`/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<WildDuckSuccessResponse> {
completeU2FRegistration(userId: string, data: U2FRegistrationData): Observable<WildDuckSuccessResponse> {
return this.post<WildDuckSuccessResponse>(`/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<WildDuckSuccessResponse> {
completeU2FAuthentication(userId: string, data: U2FAuthenticationData): Observable<WildDuckSuccessResponse> {
return this.post<WildDuckSuccessResponse>(`/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<BackupCodesGenerateResponse> {
generateBackupCodes(userId: string, data?: BackupCodeData): Observable<BackupCodesGenerateResponse> {
return this.post<BackupCodesGenerateResponse>(`/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<WildDuckSuccessResponse> {
useBackupCode(userId: string, data: BackupCodeData): Observable<WildDuckSuccessResponse> {
return this.post<WildDuckSuccessResponse>(`/users/${userId}/2fa/backup/use`, data);
}
/**
* Disable all 2FA methods
*/
disableAll2FA(
userId: string,
params?: {
sess?: string;
ip?: string;
},
): Observable<WildDuckSuccessResponse> {
disableAll2FA(userId: string, params?: BaseTwoFactorData): Observable<WildDuckSuccessResponse> {
return this.delete<WildDuckSuccessResponse>(`/users/${userId}/2fa`, params);
}
}