Comparing version 1.0.166 to 1.0.167
# Changelog | ||
## [1.0.167](https://github.com/postalsys/imapflow/compare/v1.0.166...v1.0.167) (2024-11-07) | ||
### Bug Fixes | ||
* **auth:** Prefer AUTH=LOGIN and AUTH=PLAIN to LOGIN ([3efd98a](https://github.com/postalsys/imapflow/commit/3efd98a5033db29228023302fc4663316b2b28fb)) | ||
## [1.0.166](https://github.com/postalsys/imapflow/compare/v1.0.165...v1.0.166) (2024-11-05) | ||
@@ -4,0 +11,0 @@ |
@@ -5,68 +5,161 @@ 'use strict'; | ||
// Authenticates user using LOGIN | ||
module.exports = async (connection, username, accessToken) => { | ||
if (connection.state !== connection.states.NOT_AUTHENTICATED) { | ||
// nothing to do here | ||
return; | ||
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 = ''; | ||
} | ||
// 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')) { | ||
let oauthbearer; | ||
let command; | ||
let 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 }); | ||
} | ||
} | ||
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 = ''; | ||
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; | ||
} | ||
} | ||
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 }); | ||
} | ||
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(/:$/, '')) { | ||
case 'username': { | ||
let encodedUsername = Buffer.from(username).toString('base64'); | ||
connection.log.debug({ src: 'c', msg: encodedUsername, comment: `Encoded username for AUTH=LOGIN` }); | ||
connection.write(encodedUsername); | ||
break; | ||
} | ||
connection.log.debug({ src: 'c', msg: breaker, comment: `Error response for ${command}` }); | ||
connection.write(breaker); | ||
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=${command}`, true); | ||
response.next(); | ||
return username; | ||
} catch (err) { | ||
let errorCode = getStatusCode(err.response); | ||
if (errorCode) { | ||
err.serverResponseCode = errorCode; | ||
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); | ||
} | ||
err.authenticationFailed = true; | ||
err.response = await getErrorText(err.response); | ||
if (errorResponse) { | ||
err.oauthError = errorResponse; | ||
} | ||
throw err; | ||
}); | ||
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 }) => { | ||
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 (connection.capabilities.has('AUTH=LOGIN')) { | ||
return await authLogin(connection, username, password); | ||
} | ||
if (connection.capabilities.has('AUTH=PLAIN')) { | ||
return await authPlain(connection, username, password); | ||
} | ||
} | ||
throw new Error('Unsupported authentication mechanism'); | ||
}; |
{ | ||
"name": "imapflow", | ||
"version": "1.0.166", | ||
"version": "1.0.167", | ||
"description": "IMAP Client for Node", | ||
@@ -5,0 +5,0 @@ "main": "./lib/imap-flow.js", |
Sorry, the diff of this file is too big to display
597317
12999