import { tap, expect } from '@git.zone/tstest/tapbundle'; import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js'; import { connectToSmtp, waitForGreeting, sendSmtpCommand, closeSmtpConnection } from '../../helpers/utils.js'; let testServer: ITestServer; tap.test('setup - start SMTP server with authentication', async () => { testServer = await startTestServer({ port: 2530, hostname: 'localhost', authRequired: true }); expect(testServer).toBeInstanceOf(Object); }); tap.test('SEC-01: Authentication - server advertises AUTH capability', async () => { const socket = await connectToSmtp(testServer.hostname, testServer.port); try { await waitForGreeting(socket); // Send EHLO to get capabilities const ehloResponse = await sendSmtpCommand(socket, 'EHLO test.example.com', '250'); // Parse capabilities const lines = ehloResponse.split('\r\n').filter(line => line.length > 0); const capabilities = lines.map(line => line.substring(4).trim()); // Check for AUTH capability const authCapability = capabilities.find(cap => cap.startsWith('AUTH')); expect(authCapability).toBeDefined(); // Extract supported mechanisms const supportedMechanisms = authCapability?.substring(5).split(' ') || []; console.log('📋 Supported AUTH mechanisms:', supportedMechanisms); // Common mechanisms should be supported expect(supportedMechanisms).toContain('PLAIN'); expect(supportedMechanisms).toContain('LOGIN'); console.log('✅ AUTH capability test passed'); } finally { await closeSmtpConnection(socket); } }); tap.test('SEC-01: AUTH PLAIN mechanism - correct credentials', async () => { const socket = await connectToSmtp(testServer.hostname, testServer.port); try { await waitForGreeting(socket); await sendSmtpCommand(socket, 'EHLO test.example.com', '250'); // Create AUTH PLAIN credentials // Format: base64(NULL + username + NULL + password) const username = 'testuser'; const password = 'testpass'; const authString = Buffer.from(`\0${username}\0${password}`).toString('base64'); // Send AUTH PLAIN command try { const authResponse = await sendSmtpCommand(socket, `AUTH PLAIN ${authString}`); // Server might accept (235) or reject (535) based on configuration expect(authResponse).toMatch(/^(235|535)/); if (authResponse.startsWith('235')) { console.log('✅ AUTH PLAIN accepted (test mode)'); } else { console.log('✅ AUTH PLAIN properly rejected (production mode)'); } } catch (error) { // Auth failure is expected in test environment console.log('✅ AUTH PLAIN handled:', error.message); } } finally { await closeSmtpConnection(socket); } }); tap.test('SEC-01: AUTH LOGIN mechanism - interactive authentication', async () => { const socket = await connectToSmtp(testServer.hostname, testServer.port); try { await waitForGreeting(socket); await sendSmtpCommand(socket, 'EHLO test.example.com', '250'); // Start AUTH LOGIN try { const authStartResponse = await sendSmtpCommand(socket, 'AUTH LOGIN', '334'); expect(authStartResponse).toInclude('334'); // Server should prompt for username (base64 "Username:") const usernamePrompt = Buffer.from( authStartResponse.substring(4).trim(), 'base64' ).toString(); console.log('Server prompt:', usernamePrompt); // Send username const username = Buffer.from('testuser').toString('base64'); const passwordPromptResponse = await sendSmtpCommand(socket, username, '334'); // Send password const password = Buffer.from('testpass').toString('base64'); const authResult = await sendSmtpCommand(socket, password); // Check result (235 = success, 535 = failure) expect(authResult).toMatch(/^(235|535)/); } catch (error) { // Auth failure is expected in test environment console.log('✅ AUTH LOGIN handled:', error.message); } } finally { await closeSmtpConnection(socket); } }); tap.test('SEC-01: Authentication required - reject commands without auth', async () => { const socket = await connectToSmtp(testServer.hostname, testServer.port); try { await waitForGreeting(socket); await sendSmtpCommand(socket, 'EHLO test.example.com', '250'); // Try to send email without authentication try { const mailResponse = await sendSmtpCommand(socket, 'MAIL FROM:'); // Server should reject with 530 (authentication required) or similar if (mailResponse.startsWith('530') || mailResponse.startsWith('503')) { console.log('✅ Server properly requires authentication'); } else if (mailResponse.startsWith('250')) { console.log('⚠️ Server accepted mail without auth (test mode)'); } } catch (error) { // Command rejection is expected console.log('✅ Server rejected unauthenticated command:', error.message); } } finally { await closeSmtpConnection(socket); } }); tap.test('SEC-01: Invalid authentication attempts - rate limiting', async () => { const socket = await connectToSmtp(testServer.hostname, testServer.port); try { await waitForGreeting(socket); await sendSmtpCommand(socket, 'EHLO test.example.com', '250'); // Try multiple failed authentication attempts const maxAttempts = 5; let failedAttempts = 0; for (let i = 0; i < maxAttempts; i++) { try { // Send invalid credentials const invalidAuth = Buffer.from('\0invalid\0wrong').toString('base64'); await sendSmtpCommand(socket, `AUTH PLAIN ${invalidAuth}`); } catch (error) { failedAttempts++; console.log(`Failed attempt ${i + 1}: ${error.message}`); // Check if server closed connection or rate limited if (error.message.includes('closed') || error.message.includes('too many')) { console.log('✅ Server enforces auth attempt limits'); break; } } } expect(failedAttempts).toBeGreaterThan(0); console.log(`✅ Handled ${failedAttempts} failed auth attempts`); } finally { if (!socket.destroyed) { socket.destroy(); } } }); tap.test('cleanup - stop SMTP server', async () => { await stopTestServer(testServer); console.log('✅ Test server stopped'); }); tap.start();