170 lines
6.3 KiB
JavaScript
170 lines
6.3 KiB
JavaScript
'use strict';
|
|
|
|
const { getStatusCode, getErrorText } = require('../tools.js');
|
|
|
|
async function authOauth(connection, username, accessToken) {
|
|
let oauthbearer;
|
|
let command;
|
|
let breaker;
|
|
|
|
if (connection.capabilities.has('AUTH=OAUTHBEARER')) {
|
|
oauthbearer = [`n,a=${username},`, `host=${connection.servername}`, `port=993`, `auth=Bearer ${accessToken}`, '', ''].join('\x01');
|
|
command = 'OAUTHBEARER';
|
|
breaker = 'AQ==';
|
|
} else if (connection.capabilities.has('AUTH=XOAUTH') || connection.capabilities.has('AUTH=XOAUTH2')) {
|
|
oauthbearer = [`user=${username}`, `auth=Bearer ${accessToken}`, '', ''].join('\x01');
|
|
command = 'XOAUTH2';
|
|
breaker = '';
|
|
}
|
|
|
|
let errorResponse = false;
|
|
try {
|
|
let response = await connection.exec(
|
|
'AUTHENTICATE',
|
|
[
|
|
{ type: 'ATOM', value: command },
|
|
{ type: 'ATOM', value: Buffer.from(oauthbearer).toString('base64'), sensitive: true }
|
|
],
|
|
{
|
|
onPlusTag: async resp => {
|
|
if (resp.attributes && resp.attributes[0] && resp.attributes[0].type === 'TEXT') {
|
|
try {
|
|
errorResponse = JSON.parse(Buffer.from(resp.attributes[0].value, 'base64').toString());
|
|
} catch (err) {
|
|
connection.log.debug({ errorResponse: resp.attributes[0].value, err });
|
|
}
|
|
}
|
|
|
|
connection.log.debug({ src: 'c', msg: breaker, comment: `Error response for ${command}` });
|
|
connection.write(breaker);
|
|
}
|
|
}
|
|
);
|
|
response.next();
|
|
|
|
connection.authCapabilities.set(`AUTH=${command}`, true);
|
|
|
|
return username;
|
|
} catch (err) {
|
|
let errorCode = getStatusCode(err.response);
|
|
if (errorCode) {
|
|
err.serverResponseCode = errorCode;
|
|
}
|
|
err.authenticationFailed = true;
|
|
err.response = await getErrorText(err.response);
|
|
if (errorResponse) {
|
|
err.oauthError = errorResponse;
|
|
}
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
async function authLogin(connection, username, password) {
|
|
let errorResponse = false;
|
|
try {
|
|
let response = await connection.exec('AUTHENTICATE', [{ type: 'ATOM', value: 'LOGIN' }], {
|
|
onPlusTag: async resp => {
|
|
if (resp.attributes && resp.attributes[0] && resp.attributes[0].type === 'TEXT') {
|
|
let question = Buffer.from(resp.attributes[0].value, 'base64').toString();
|
|
switch (
|
|
question.toLowerCase().replace(/[:\x00]*$/, '') // eslint-disable-line no-control-regex
|
|
) {
|
|
case 'username':
|
|
case 'user name': {
|
|
let encodedUsername = Buffer.from(username).toString('base64');
|
|
connection.log.debug({ src: 'c', msg: encodedUsername, comment: `Encoded username for AUTH=LOGIN` });
|
|
connection.write(encodedUsername);
|
|
break;
|
|
}
|
|
|
|
case 'password':
|
|
connection.log.debug({ src: 'c', msg: '(* value hidden *)', comment: `Encoded password for AUTH=LOGIN` });
|
|
connection.write(Buffer.from(password).toString('base64'));
|
|
break;
|
|
|
|
default: {
|
|
let error = new Error(`Unknown LOGIN question "${question}"`);
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
response.next();
|
|
|
|
connection.authCapabilities.set(`AUTH=LOGIN`, true);
|
|
|
|
return username;
|
|
} catch (err) {
|
|
let errorCode = getStatusCode(err.response);
|
|
if (errorCode) {
|
|
err.serverResponseCode = errorCode;
|
|
}
|
|
err.authenticationFailed = true;
|
|
err.response = await getErrorText(err.response);
|
|
if (errorResponse) {
|
|
err.oauthError = errorResponse;
|
|
}
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
async function authPlain(connection, username, password) {
|
|
let errorResponse = false;
|
|
try {
|
|
let response = await connection.exec('AUTHENTICATE', [{ type: 'ATOM', value: 'PLAIN' }], {
|
|
onPlusTag: async () => {
|
|
let encodedResponse = Buffer.from(['', username, password].join('\x00')).toString('base64');
|
|
let loggedResponse = Buffer.from(['', username, '(* value hidden *)'].join('\x00')).toString('base64');
|
|
connection.log.debug({ src: 'c', msg: loggedResponse, comment: `Encoded response for AUTH=PLAIN` });
|
|
connection.write(encodedResponse);
|
|
}
|
|
});
|
|
|
|
response.next();
|
|
|
|
connection.authCapabilities.set(`AUTH=PLAIN`, true);
|
|
|
|
return username;
|
|
} catch (err) {
|
|
let errorCode = getStatusCode(err.response);
|
|
if (errorCode) {
|
|
err.serverResponseCode = errorCode;
|
|
}
|
|
err.authenticationFailed = true;
|
|
err.response = await getErrorText(err.response);
|
|
if (errorResponse) {
|
|
err.oauthError = errorResponse;
|
|
}
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
// Authenticates user using LOGIN
|
|
module.exports = async (connection, username, { accessToken, password, loginMethod }) => {
|
|
if (connection.state !== connection.states.NOT_AUTHENTICATED) {
|
|
// nothing to do here
|
|
return;
|
|
}
|
|
|
|
if (accessToken) {
|
|
// AUTH=OAUTHBEARER and AUTH=XOAUTH in the context of OAuth2 or very similar so we can handle these together
|
|
if (connection.capabilities.has('AUTH=OAUTHBEARER') || connection.capabilities.has('AUTH=XOAUTH') || connection.capabilities.has('AUTH=XOAUTH2')) {
|
|
return await authOauth(connection, username, accessToken);
|
|
}
|
|
}
|
|
|
|
if (password) {
|
|
if ((!loginMethod && connection.capabilities.has('AUTH=PLAIN')) || loginMethod === 'AUTH=PLAIN') {
|
|
return await authPlain(connection, username, password);
|
|
}
|
|
|
|
if ((!loginMethod && connection.capabilities.has('AUTH=LOGIN')) || loginMethod === 'AUTH=LOGIN') {
|
|
return await authLogin(connection, username, password);
|
|
}
|
|
}
|
|
|
|
throw new Error('Unsupported authentication mechanism');
|
|
};
|