fix: bug in monitoring service
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user