190 lines
16 KiB
JavaScript
190 lines
16 KiB
JavaScript
/**
|
|
* SMTP Client Authentication Handler
|
|
* Authentication mechanisms implementation
|
|
*/
|
|
import { AUTH_METHODS } from './constants.js';
|
|
import { encodeAuthPlain, encodeAuthLogin, generateOAuth2String, isSuccessCode } from './utils/helpers.js';
|
|
import { logAuthentication, logDebug } from './utils/logging.js';
|
|
export class AuthHandler {
|
|
options;
|
|
commandHandler;
|
|
constructor(options, commandHandler) {
|
|
this.options = options;
|
|
this.commandHandler = commandHandler;
|
|
}
|
|
/**
|
|
* Authenticate using the configured method
|
|
*/
|
|
async authenticate(connection) {
|
|
if (!this.options.auth) {
|
|
logDebug('No authentication configured', this.options);
|
|
return;
|
|
}
|
|
const authOptions = this.options.auth;
|
|
const capabilities = connection.capabilities;
|
|
if (!capabilities || capabilities.authMethods.size === 0) {
|
|
throw new Error('Server does not support authentication');
|
|
}
|
|
// Determine authentication method
|
|
const method = this.selectAuthMethod(authOptions, capabilities.authMethods);
|
|
logAuthentication('start', method, this.options);
|
|
try {
|
|
switch (method) {
|
|
case AUTH_METHODS.PLAIN:
|
|
await this.authenticatePlain(connection, authOptions);
|
|
break;
|
|
case AUTH_METHODS.LOGIN:
|
|
await this.authenticateLogin(connection, authOptions);
|
|
break;
|
|
case AUTH_METHODS.OAUTH2:
|
|
await this.authenticateOAuth2(connection, authOptions);
|
|
break;
|
|
default:
|
|
throw new Error(`Unsupported authentication method: ${method}`);
|
|
}
|
|
logAuthentication('success', method, this.options);
|
|
}
|
|
catch (error) {
|
|
logAuthentication('failure', method, this.options, { error });
|
|
throw error;
|
|
}
|
|
}
|
|
/**
|
|
* Authenticate using AUTH PLAIN
|
|
*/
|
|
async authenticatePlain(connection, auth) {
|
|
if (!auth.user || !auth.pass) {
|
|
throw new Error('Username and password required for PLAIN authentication');
|
|
}
|
|
const credentials = encodeAuthPlain(auth.user, auth.pass);
|
|
const response = await this.commandHandler.sendAuth(connection, AUTH_METHODS.PLAIN, credentials);
|
|
if (!isSuccessCode(response.code)) {
|
|
throw new Error(`PLAIN authentication failed: ${response.message}`);
|
|
}
|
|
}
|
|
/**
|
|
* Authenticate using AUTH LOGIN
|
|
*/
|
|
async authenticateLogin(connection, auth) {
|
|
if (!auth.user || !auth.pass) {
|
|
throw new Error('Username and password required for LOGIN authentication');
|
|
}
|
|
// Step 1: Send AUTH LOGIN
|
|
let response = await this.commandHandler.sendAuth(connection, AUTH_METHODS.LOGIN);
|
|
if (response.code !== 334) {
|
|
throw new Error(`LOGIN authentication initiation failed: ${response.message}`);
|
|
}
|
|
// Step 2: Send username
|
|
const encodedUser = encodeAuthLogin(auth.user);
|
|
response = await this.commandHandler.sendCommand(connection, encodedUser);
|
|
if (response.code !== 334) {
|
|
throw new Error(`LOGIN username failed: ${response.message}`);
|
|
}
|
|
// Step 3: Send password
|
|
const encodedPass = encodeAuthLogin(auth.pass);
|
|
response = await this.commandHandler.sendCommand(connection, encodedPass);
|
|
if (!isSuccessCode(response.code)) {
|
|
throw new Error(`LOGIN password failed: ${response.message}`);
|
|
}
|
|
}
|
|
/**
|
|
* Authenticate using OAuth2
|
|
*/
|
|
async authenticateOAuth2(connection, auth) {
|
|
if (!auth.oauth2) {
|
|
throw new Error('OAuth2 configuration required for OAUTH2 authentication');
|
|
}
|
|
let accessToken = auth.oauth2.accessToken;
|
|
// Refresh token if needed
|
|
if (!accessToken || this.isTokenExpired(auth.oauth2)) {
|
|
accessToken = await this.refreshOAuth2Token(auth.oauth2);
|
|
}
|
|
const authString = generateOAuth2String(auth.oauth2.user, accessToken);
|
|
const response = await this.commandHandler.sendAuth(connection, AUTH_METHODS.OAUTH2, authString);
|
|
if (!isSuccessCode(response.code)) {
|
|
throw new Error(`OAUTH2 authentication failed: ${response.message}`);
|
|
}
|
|
}
|
|
/**
|
|
* Select appropriate authentication method
|
|
*/
|
|
selectAuthMethod(auth, serverMethods) {
|
|
// If method is explicitly specified, use it
|
|
if (auth.method && auth.method !== 'AUTO') {
|
|
const method = auth.method === 'OAUTH2' ? AUTH_METHODS.OAUTH2 : auth.method;
|
|
if (serverMethods.has(method)) {
|
|
return method;
|
|
}
|
|
throw new Error(`Requested authentication method ${auth.method} not supported by server`);
|
|
}
|
|
// Auto-select based on available credentials and server support
|
|
if (auth.oauth2 && serverMethods.has(AUTH_METHODS.OAUTH2)) {
|
|
return AUTH_METHODS.OAUTH2;
|
|
}
|
|
if (auth.user && auth.pass) {
|
|
// Prefer PLAIN over LOGIN for simplicity
|
|
if (serverMethods.has(AUTH_METHODS.PLAIN)) {
|
|
return AUTH_METHODS.PLAIN;
|
|
}
|
|
if (serverMethods.has(AUTH_METHODS.LOGIN)) {
|
|
return AUTH_METHODS.LOGIN;
|
|
}
|
|
}
|
|
throw new Error('No compatible authentication method found');
|
|
}
|
|
/**
|
|
* Check if OAuth2 token is expired
|
|
*/
|
|
isTokenExpired(oauth2) {
|
|
if (!oauth2.expires) {
|
|
return false; // No expiry information, assume valid
|
|
}
|
|
const now = Date.now();
|
|
const buffer = 300000; // 5 minutes buffer
|
|
return oauth2.expires < (now + buffer);
|
|
}
|
|
/**
|
|
* Refresh OAuth2 access token
|
|
*/
|
|
async refreshOAuth2Token(oauth2) {
|
|
// This is a simplified implementation
|
|
// In a real implementation, you would make an HTTP request to the OAuth2 provider
|
|
logDebug('OAuth2 token refresh required', this.options);
|
|
if (!oauth2.refreshToken) {
|
|
throw new Error('Refresh token required for OAuth2 token refresh');
|
|
}
|
|
// TODO: Implement actual OAuth2 token refresh
|
|
// For now, throw an error to indicate this needs to be implemented
|
|
throw new Error('OAuth2 token refresh not implemented. Please provide a valid access token.');
|
|
}
|
|
/**
|
|
* Validate authentication configuration
|
|
*/
|
|
validateAuthConfig(auth) {
|
|
const errors = [];
|
|
if (auth.method === 'OAUTH2' || auth.oauth2) {
|
|
if (!auth.oauth2) {
|
|
errors.push('OAuth2 configuration required when using OAUTH2 method');
|
|
}
|
|
else {
|
|
if (!auth.oauth2.user)
|
|
errors.push('OAuth2 user required');
|
|
if (!auth.oauth2.clientId)
|
|
errors.push('OAuth2 clientId required');
|
|
if (!auth.oauth2.clientSecret)
|
|
errors.push('OAuth2 clientSecret required');
|
|
if (!auth.oauth2.refreshToken && !auth.oauth2.accessToken) {
|
|
errors.push('OAuth2 refreshToken or accessToken required');
|
|
}
|
|
}
|
|
}
|
|
else if (auth.method === 'PLAIN' || auth.method === 'LOGIN' || (!auth.method && (auth.user || auth.pass))) {
|
|
if (!auth.user)
|
|
errors.push('Username required for basic authentication');
|
|
if (!auth.pass)
|
|
errors.push('Password required for basic authentication');
|
|
}
|
|
return errors;
|
|
}
|
|
}
|
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXV0aC1oYW5kbGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vdHMvbWFpbC9kZWxpdmVyeS9zbXRwY2xpZW50L2F1dGgtaGFuZGxlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7O0dBR0c7QUFFSCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFROUMsT0FBTyxFQUNMLGVBQWUsRUFDZixlQUFlLEVBQ2Ysb0JBQW9CLEVBQ3BCLGFBQWEsRUFDZCxNQUFNLG9CQUFvQixDQUFDO0FBQzVCLE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxRQUFRLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUdqRSxNQUFNLE9BQU8sV0FBVztJQUNkLE9BQU8sQ0FBcUI7SUFDNUIsY0FBYyxDQUFpQjtJQUV2QyxZQUFZLE9BQTJCLEVBQUUsY0FBOEI7UUFDckUsSUFBSSxDQUFDLE9BQU8sR0FBRyxPQUFPLENBQUM7UUFDdkIsSUFBSSxDQUFDLGNBQWMsR0FBRyxjQUFjLENBQUM7SUFDdkMsQ0FBQztJQUVEOztPQUVHO0lBQ0ksS0FBSyxDQUFDLFlBQVksQ0FBQyxVQUEyQjtRQUNuRCxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FBQztZQUN2QixRQUFRLENBQUMsOEJBQThCLEVBQUUsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFDO1lBQ3ZELE9BQU87UUFDVCxDQUFDO1FBRUQsTUFBTSxXQUFXLEdBQUcsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUM7UUFDdEMsTUFBTSxZQUFZLEdBQUcsVUFBVSxDQUFDLFlBQVksQ0FBQztRQUU3QyxJQUFJLENBQUMsWUFBWSxJQUFJLFlBQVksQ0FBQyxXQUFXLENBQUMsSUFBSSxLQUFLLENBQUMsRUFBRSxDQUFDO1lBQ3pELE1BQU0sSUFBSSxLQUFLLENBQUMsd0NBQXdDLENBQUMsQ0FBQztRQUM1RCxDQUFDO1FBRUQsa0NBQWtDO1FBQ2xDLE1B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
|