dcrouter/test/suite/smtpclient_commands/test.ccmd-05.auth-mechanisms.ts

311 lines
8.7 KiB
TypeScript
Raw Normal View History

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();