2025-05-24 16:19:19 +00:00
|
|
|
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
|
|
|
|
import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js';
|
|
|
|
|
import { createSmtpClient } from '../../../ts/mail/delivery/smtpclient/index.js';
|
|
|
|
|
import type { SmtpClient } from '../../../ts/mail/delivery/smtpclient/smtp-client.js';
|
|
|
|
|
import { Email } from '../../../ts/mail/core/classes.email.js';
|
|
|
|
|
|
|
|
|
|
let authServer: ITestServer;
|
|
|
|
|
|
|
|
|
|
tap.test('setup - start SMTP server with authentication', async () => {
|
|
|
|
|
authServer = await startTestServer({
|
|
|
|
|
port: 2580,
|
2025-05-25 11:18:12 +00:00
|
|
|
|
tlsEnabled: true, // Enable STARTTLS capability
|
2025-05-24 16:19:19 +00:00
|
|
|
|
authRequired: true
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(authServer.port).toEqual(2580);
|
|
|
|
|
expect(authServer.config.authRequired).toBeTrue();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
tap.test('CCMD-05: AUTH - should fail without credentials', async () => {
|
|
|
|
|
let errorCaught = false;
|
2025-05-25 11:18:12 +00:00
|
|
|
|
let noAuthClient: SmtpClient | null = null;
|
2025-05-24 16:19:19 +00:00
|
|
|
|
|
|
|
|
|
try {
|
2025-05-25 11:18:12 +00:00
|
|
|
|
noAuthClient = createSmtpClient({
|
2025-05-24 16:19:19 +00:00
|
|
|
|
host: authServer.hostname,
|
|
|
|
|
port: authServer.port,
|
2025-05-25 11:18:12 +00:00
|
|
|
|
secure: false, // Start plain, upgrade with STARTTLS
|
|
|
|
|
tls: {
|
|
|
|
|
rejectUnauthorized: false // Accept self-signed certs for testing
|
|
|
|
|
},
|
2025-05-24 16:19:19 +00:00
|
|
|
|
connectionTimeout: 5000
|
|
|
|
|
// No auth provided
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const email = new Email({
|
|
|
|
|
from: 'sender@example.com',
|
|
|
|
|
to: 'recipient@example.com',
|
|
|
|
|
subject: 'No Auth Test',
|
|
|
|
|
text: 'Should fail without authentication'
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await noAuthClient.sendMail(email);
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
errorCaught = true;
|
|
|
|
|
expect(error).toBeInstanceOf(Error);
|
|
|
|
|
console.log('✅ Authentication required error:', error.message);
|
2025-05-25 11:18:12 +00:00
|
|
|
|
} finally {
|
|
|
|
|
// Ensure client is closed even if test fails
|
|
|
|
|
if (noAuthClient) {
|
|
|
|
|
await noAuthClient.close();
|
|
|
|
|
}
|
2025-05-24 16:19:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
expect(errorCaught).toBeTrue();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
tap.test('CCMD-05: AUTH - should authenticate with PLAIN mechanism', async () => {
|
|
|
|
|
const plainAuthClient = createSmtpClient({
|
|
|
|
|
host: authServer.hostname,
|
|
|
|
|
port: authServer.port,
|
2025-05-25 11:18:12 +00:00
|
|
|
|
secure: false, // Start plain, upgrade with STARTTLS
|
|
|
|
|
tls: {
|
|
|
|
|
rejectUnauthorized: false // Accept self-signed certs for testing
|
|
|
|
|
},
|
2025-05-24 16:19:19 +00:00
|
|
|
|
connectionTimeout: 5000,
|
|
|
|
|
auth: {
|
|
|
|
|
user: 'testuser',
|
|
|
|
|
pass: 'testpass',
|
|
|
|
|
method: 'PLAIN'
|
|
|
|
|
},
|
|
|
|
|
debug: true
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const isConnected = await plainAuthClient.verify();
|
|
|
|
|
expect(isConnected).toBeTrue();
|
|
|
|
|
|
|
|
|
|
const email = new Email({
|
|
|
|
|
from: 'sender@example.com',
|
|
|
|
|
to: 'recipient@example.com',
|
|
|
|
|
subject: 'PLAIN Auth Test',
|
|
|
|
|
text: 'Sent with PLAIN authentication'
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const result = await plainAuthClient.sendMail(email);
|
|
|
|
|
expect(result.success).toBeTrue();
|
|
|
|
|
|
|
|
|
|
await plainAuthClient.close();
|
|
|
|
|
console.log('✅ PLAIN authentication successful');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
tap.test('CCMD-05: AUTH - should authenticate with LOGIN mechanism', async () => {
|
|
|
|
|
const loginAuthClient = createSmtpClient({
|
|
|
|
|
host: authServer.hostname,
|
|
|
|
|
port: authServer.port,
|
2025-05-25 11:18:12 +00:00
|
|
|
|
secure: false, // Start plain, upgrade with STARTTLS
|
|
|
|
|
tls: {
|
|
|
|
|
rejectUnauthorized: false // Accept self-signed certs for testing
|
|
|
|
|
},
|
2025-05-24 16:19:19 +00:00
|
|
|
|
connectionTimeout: 5000,
|
|
|
|
|
auth: {
|
|
|
|
|
user: 'testuser',
|
|
|
|
|
pass: 'testpass',
|
|
|
|
|
method: 'LOGIN'
|
|
|
|
|
},
|
|
|
|
|
debug: true
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const isConnected = await loginAuthClient.verify();
|
|
|
|
|
expect(isConnected).toBeTrue();
|
|
|
|
|
|
|
|
|
|
const email = new Email({
|
|
|
|
|
from: 'sender@example.com',
|
|
|
|
|
to: 'recipient@example.com',
|
|
|
|
|
subject: 'LOGIN Auth Test',
|
|
|
|
|
text: 'Sent with LOGIN authentication'
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const result = await loginAuthClient.sendMail(email);
|
|
|
|
|
expect(result.success).toBeTrue();
|
|
|
|
|
|
|
|
|
|
await loginAuthClient.close();
|
|
|
|
|
console.log('✅ LOGIN authentication successful');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
tap.test('CCMD-05: AUTH - should auto-select authentication method', async () => {
|
|
|
|
|
const autoAuthClient = createSmtpClient({
|
|
|
|
|
host: authServer.hostname,
|
|
|
|
|
port: authServer.port,
|
2025-05-25 11:18:12 +00:00
|
|
|
|
secure: false, // Start plain, upgrade with STARTTLS
|
|
|
|
|
tls: {
|
|
|
|
|
rejectUnauthorized: false // Accept self-signed certs for testing
|
|
|
|
|
},
|
2025-05-24 16:19:19 +00:00
|
|
|
|
connectionTimeout: 5000,
|
|
|
|
|
auth: {
|
|
|
|
|
user: 'testuser',
|
|
|
|
|
pass: 'testpass'
|
|
|
|
|
// No method specified - should auto-select
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const isConnected = await autoAuthClient.verify();
|
|
|
|
|
expect(isConnected).toBeTrue();
|
|
|
|
|
|
|
|
|
|
await autoAuthClient.close();
|
|
|
|
|
console.log('✅ Auto-selected authentication method');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
tap.test('CCMD-05: AUTH - should handle invalid credentials', async () => {
|
|
|
|
|
let authFailed = false;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const badAuthClient = createSmtpClient({
|
|
|
|
|
host: authServer.hostname,
|
|
|
|
|
port: authServer.port,
|
2025-05-25 11:18:12 +00:00
|
|
|
|
secure: false, // Start plain, upgrade with STARTTLS
|
|
|
|
|
tls: {
|
|
|
|
|
rejectUnauthorized: false // Accept self-signed certs for testing
|
|
|
|
|
},
|
2025-05-24 16:19:19 +00:00
|
|
|
|
connectionTimeout: 5000,
|
|
|
|
|
auth: {
|
|
|
|
|
user: 'wronguser',
|
|
|
|
|
pass: 'wrongpass'
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await badAuthClient.verify();
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
authFailed = true;
|
|
|
|
|
expect(error).toBeInstanceOf(Error);
|
|
|
|
|
console.log('✅ Invalid credentials rejected:', error.message);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
expect(authFailed).toBeTrue();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
tap.test('CCMD-05: AUTH - should handle special characters in credentials', async () => {
|
|
|
|
|
const specialAuthClient = createSmtpClient({
|
|
|
|
|
host: authServer.hostname,
|
|
|
|
|
port: authServer.port,
|
2025-05-25 11:18:12 +00:00
|
|
|
|
secure: false, // Start plain, upgrade with STARTTLS
|
|
|
|
|
tls: {
|
|
|
|
|
rejectUnauthorized: false // Accept self-signed certs for testing
|
|
|
|
|
},
|
2025-05-24 16:19:19 +00:00
|
|
|
|
connectionTimeout: 5000,
|
|
|
|
|
auth: {
|
|
|
|
|
user: 'user@domain.com',
|
|
|
|
|
pass: 'p@ssw0rd!#$%'
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Server might accept or reject based on implementation
|
|
|
|
|
try {
|
|
|
|
|
await specialAuthClient.verify();
|
|
|
|
|
await specialAuthClient.close();
|
|
|
|
|
console.log('✅ Special characters in credentials handled');
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.log('ℹ️ Test server rejected special character credentials');
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
tap.test('CCMD-05: AUTH - should prefer secure auth over TLS', async () => {
|
|
|
|
|
// Start TLS-enabled server
|
|
|
|
|
const tlsAuthServer = await startTestServer({
|
|
|
|
|
port: 2581,
|
|
|
|
|
tlsEnabled: true,
|
|
|
|
|
authRequired: true
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const tlsAuthClient = createSmtpClient({
|
|
|
|
|
host: tlsAuthServer.hostname,
|
|
|
|
|
port: tlsAuthServer.port,
|
2025-05-25 11:18:12 +00:00
|
|
|
|
secure: false, // Use STARTTLS
|
2025-05-24 16:19:19 +00:00
|
|
|
|
connectionTimeout: 5000,
|
|
|
|
|
auth: {
|
|
|
|
|
user: 'testuser',
|
|
|
|
|
pass: 'testpass'
|
|
|
|
|
},
|
|
|
|
|
tls: {
|
|
|
|
|
rejectUnauthorized: false
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const isConnected = await tlsAuthClient.verify();
|
|
|
|
|
expect(isConnected).toBeTrue();
|
|
|
|
|
|
|
|
|
|
await tlsAuthClient.close();
|
|
|
|
|
await stopTestServer(tlsAuthServer);
|
|
|
|
|
console.log('✅ Secure authentication over TLS');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
tap.test('CCMD-05: AUTH - should maintain auth state across multiple sends', async () => {
|
|
|
|
|
const persistentAuthClient = createSmtpClient({
|
|
|
|
|
host: authServer.hostname,
|
|
|
|
|
port: authServer.port,
|
2025-05-25 11:18:12 +00:00
|
|
|
|
secure: false, // Start plain, upgrade with STARTTLS
|
|
|
|
|
tls: {
|
|
|
|
|
rejectUnauthorized: false // Accept self-signed certs for testing
|
|
|
|
|
},
|
2025-05-24 16:19:19 +00:00
|
|
|
|
connectionTimeout: 5000,
|
|
|
|
|
auth: {
|
|
|
|
|
user: 'testuser',
|
|
|
|
|
pass: 'testpass'
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await persistentAuthClient.verify();
|
|
|
|
|
|
|
|
|
|
// Send multiple emails without re-authenticating
|
|
|
|
|
for (let i = 0; i < 3; i++) {
|
|
|
|
|
const email = new Email({
|
|
|
|
|
from: 'sender@example.com',
|
|
|
|
|
to: 'recipient@example.com',
|
|
|
|
|
subject: `Persistent Auth Test ${i + 1}`,
|
|
|
|
|
text: `Email ${i + 1} using same auth session`
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const result = await persistentAuthClient.sendMail(email);
|
|
|
|
|
expect(result.success).toBeTrue();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await persistentAuthClient.close();
|
|
|
|
|
console.log('✅ Authentication state maintained across sends');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
tap.test('CCMD-05: AUTH - should handle auth with connection pooling', async () => {
|
|
|
|
|
const pooledAuthClient = createSmtpClient({
|
|
|
|
|
host: authServer.hostname,
|
|
|
|
|
port: authServer.port,
|
2025-05-25 11:18:12 +00:00
|
|
|
|
secure: false, // Start plain, upgrade with STARTTLS
|
|
|
|
|
tls: {
|
|
|
|
|
rejectUnauthorized: false // Accept self-signed certs for testing
|
|
|
|
|
},
|
2025-05-24 16:19:19 +00:00
|
|
|
|
pool: true,
|
|
|
|
|
maxConnections: 3,
|
|
|
|
|
connectionTimeout: 5000,
|
|
|
|
|
auth: {
|
|
|
|
|
user: 'testuser',
|
|
|
|
|
pass: 'testpass'
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Send concurrent emails with pooled authenticated connections
|
|
|
|
|
const promises = [];
|
|
|
|
|
for (let i = 0; i < 5; i++) {
|
|
|
|
|
const email = new Email({
|
|
|
|
|
from: 'sender@example.com',
|
|
|
|
|
to: `recipient${i}@example.com`,
|
|
|
|
|
subject: `Pooled Auth Test ${i}`,
|
|
|
|
|
text: 'Testing auth with connection pooling'
|
|
|
|
|
});
|
|
|
|
|
promises.push(pooledAuthClient.sendMail(email));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const results = await Promise.all(promises);
|
|
|
|
|
results.forEach(result => {
|
|
|
|
|
expect(result.success).toBeTrue();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const poolStatus = pooledAuthClient.getPoolStatus();
|
|
|
|
|
console.log('📊 Auth pool status:', poolStatus);
|
|
|
|
|
|
|
|
|
|
await pooledAuthClient.close();
|
|
|
|
|
console.log('✅ Authentication works with connection pooling');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
tap.test('cleanup - stop auth server', async () => {
|
|
|
|
|
await stopTestServer(authServer);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
tap.start();
|