port-nimara-client-portal/server/api/email/test-connection.ts

164 lines
5.7 KiB
TypeScript

import { requireAuth } from '~/server/utils/auth';
import nodemailer from 'nodemailer';
import Imap from 'imap';
import { encryptCredentials, storeCredentialsInSession } from '~/server/utils/encryption';
export default defineEventHandler(async (event) => {
// Check authentication (x-tag header OR Keycloak session)
await requireAuth(event);
try {
const body = await readBody(event);
const { email, password, imapHost, smtpHost, sessionId } = body;
console.log('[test-connection] Testing email connection for:', email);
if (!email || !password || !sessionId) {
throw createError({
statusCode: 400,
statusMessage: "Email, password, and sessionId are required"
});
}
// Use provided hosts or defaults from environment
const imapHostToUse = imapHost || process.env.NUXT_EMAIL_IMAP_HOST || 'mail.portnimara.com';
const smtpHostToUse = smtpHost || process.env.NUXT_EMAIL_SMTP_HOST || 'mail.portnimara.com';
const imapPort = parseInt(process.env.NUXT_EMAIL_IMAP_PORT || '993');
const smtpPort = parseInt(process.env.NUXT_EMAIL_SMTP_PORT || '587');
console.log('[test-connection] Using IMAP:', imapHostToUse, ':', imapPort);
console.log('[test-connection] Using SMTP:', smtpHostToUse, ':', smtpPort);
// Test SMTP connection
console.log('[test-connection] Testing SMTP connection...');
const transporter = nodemailer.createTransport({
host: smtpHostToUse,
port: smtpPort,
secure: false, // false for STARTTLS
auth: {
user: email,
pass: password
},
tls: {
rejectUnauthorized: false // Allow self-signed certificates
}
});
try {
await transporter.verify();
console.log('[test-connection] SMTP connection successful');
} catch (smtpError: any) {
console.error('[test-connection] SMTP connection failed:', smtpError);
throw new Error(`SMTP connection failed: ${smtpError.message || smtpError}`);
}
// Test IMAP connection
const imapConfig = {
user: email,
password: password,
host: imapHostToUse,
port: imapPort,
tls: true,
tlsOptions: {
rejectUnauthorized: false // Allow self-signed certificates
}
};
const testImapConnection = (retryCount = 0): Promise<boolean> => {
return new Promise((resolve, reject) => {
console.log(`[test-connection] Testing IMAP connection... (Attempt ${retryCount + 1}/3)`);
const imap = new Imap(imapConfig);
// Add a timeout to prevent hanging
const timeout = setTimeout(() => {
console.error('[test-connection] IMAP connection timeout');
imap.end();
// Retry on timeout if we haven't exceeded max retries
if (retryCount < 2) {
console.log('[test-connection] Retrying IMAP connection after timeout...');
setTimeout(() => {
testImapConnection(retryCount + 1)
.then(resolve)
.catch(reject);
}, (retryCount + 1) * 1000); // Exponential backoff
} else {
reject(new Error('IMAP connection timeout after 15 seconds and 3 attempts'));
}
}, 15000); // 15 second timeout per attempt
imap.once('ready', () => {
console.log('[test-connection] IMAP connection successful');
clearTimeout(timeout);
imap.end();
resolve(true);
});
imap.once('error', (err: Error) => {
console.error('[test-connection] IMAP connection error:', err);
clearTimeout(timeout);
// Retry on certain errors if we haven't exceeded max retries
const shouldRetry = retryCount < 2 && (
err.message.includes('ECONNRESET') ||
err.message.includes('ETIMEDOUT') ||
err.message.includes('ENOTFOUND') ||
err.message.includes('socket hang up')
);
if (shouldRetry) {
console.log(`[test-connection] Retrying IMAP connection after error: ${err.message}`);
setTimeout(() => {
testImapConnection(retryCount + 1)
.then(resolve)
.catch(reject);
}, (retryCount + 1) * 1000); // Exponential backoff
} else {
reject(err);
}
});
imap.connect();
});
};
try {
await testImapConnection();
} catch (imapError: any) {
console.error('[test-connection] IMAP connection failed after all retries:', imapError);
throw new Error(`IMAP connection failed: ${imapError.message || imapError}`);
}
// If both connections successful, encrypt and store credentials
console.log('[test-connection] Both connections successful, storing credentials');
const encryptedCredentials = encryptCredentials(email, password);
storeCredentialsInSession(sessionId, encryptedCredentials);
return {
success: true,
message: "Email connection tested successfully",
email: email
};
} catch (error) {
console.error('Email connection test failed:', error);
if (error instanceof Error) {
// Check for common authentication errors
if (error.message.includes('Authentication') || error.message.includes('AUTHENTICATIONFAILED')) {
throw createError({
statusCode: 401,
statusMessage: "Invalid email or password"
});
}
throw createError({
statusCode: 500,
statusMessage: `Connection failed: ${error.message}`
});
} else {
throw createError({
statusCode: 500,
statusMessage: "An unexpected error occurred",
});
}
}
});