imapflow 1.0.165 → 1.0.167

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.0.167](https://github.com/postalsys/imapflow/compare/v1.0.166...v1.0.167) (2024-11-07)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **auth:** Prefer AUTH=LOGIN and AUTH=PLAIN to LOGIN ([3efd98a](https://github.com/postalsys/imapflow/commit/3efd98a5033db29228023302fc4663316b2b28fb))
9
+
10
+ ## [1.0.166](https://github.com/postalsys/imapflow/compare/v1.0.165...v1.0.166) (2024-11-05)
11
+
12
+
13
+ ### Bug Fixes
14
+
15
+ * **login:** Always treat username and password as strings, not atom ([01b3471](https://github.com/postalsys/imapflow/commit/01b347145551c046e4400b8824be9be3c69d353b))
16
+
3
17
  ## [1.0.165](https://github.com/postalsys/imapflow/compare/v1.0.164...v1.0.165) (2024-11-01)
4
18
 
5
19
 
@@ -2,68 +2,161 @@
2
2
 
3
3
  const { getStatusCode, getErrorText } = require('../tools.js');
4
4
 
5
- // Authenticates user using LOGIN
6
- module.exports = async (connection, username, accessToken) => {
7
- if (connection.state !== connection.states.NOT_AUTHENTICATED) {
8
- // nothing to do here
9
- return;
5
+ async function authOauth(connection, username, accessToken) {
6
+ let oauthbearer;
7
+ let command;
8
+ let breaker;
9
+
10
+ if (connection.capabilities.has('AUTH=OAUTHBEARER')) {
11
+ oauthbearer = [`n,a=${username},`, `host=${connection.servername}`, `port=993`, `auth=Bearer ${accessToken}`, '', ''].join('\x01');
12
+ command = 'OAUTHBEARER';
13
+ breaker = 'AQ==';
14
+ } else if (connection.capabilities.has('AUTH=XOAUTH') || connection.capabilities.has('AUTH=XOAUTH2')) {
15
+ oauthbearer = [`user=${username}`, `auth=Bearer ${accessToken}`, '', ''].join('\x01');
16
+ command = 'XOAUTH2';
17
+ breaker = '';
10
18
  }
11
19
 
12
- // AUTH=OAUTHBEARER and AUTH=XOAUTH in the context of OAuth2 or very similar so we can handle these together
13
- if (connection.capabilities.has('AUTH=OAUTHBEARER') || connection.capabilities.has('AUTH=XOAUTH') || connection.capabilities.has('AUTH=XOAUTH2')) {
14
- let oauthbearer;
15
- let command;
16
- let breaker;
17
-
18
- if (connection.capabilities.has('AUTH=OAUTHBEARER')) {
19
- oauthbearer = [`n,a=${username},`, `host=${connection.servername}`, `port=993`, `auth=Bearer ${accessToken}`, '', ''].join('\x01');
20
- command = 'OAUTHBEARER';
21
- breaker = 'AQ==';
22
- } else if (connection.capabilities.has('AUTH=XOAUTH') || connection.capabilities.has('AUTH=XOAUTH2')) {
23
- oauthbearer = [`user=${username}`, `auth=Bearer ${accessToken}`, '', ''].join('\x01');
24
- command = 'XOAUTH2';
25
- breaker = '';
20
+ let errorResponse = false;
21
+ try {
22
+ let response = await connection.exec(
23
+ 'AUTHENTICATE',
24
+ [
25
+ { type: 'ATOM', value: command },
26
+ { type: 'ATOM', value: Buffer.from(oauthbearer).toString('base64'), sensitive: true }
27
+ ],
28
+ {
29
+ onPlusTag: async resp => {
30
+ if (resp.attributes && resp.attributes[0] && resp.attributes[0].type === 'TEXT') {
31
+ try {
32
+ errorResponse = JSON.parse(Buffer.from(resp.attributes[0].value, 'base64').toString());
33
+ } catch (err) {
34
+ connection.log.debug({ errorResponse: resp.attributes[0].value, err });
35
+ }
36
+ }
37
+
38
+ connection.log.debug({ src: 'c', msg: breaker, comment: `Error response for ${command}` });
39
+ connection.write(breaker);
40
+ }
41
+ }
42
+ );
43
+ response.next();
44
+
45
+ connection.authCapabilities.set(`AUTH=${command}`, true);
46
+
47
+ return username;
48
+ } catch (err) {
49
+ let errorCode = getStatusCode(err.response);
50
+ if (errorCode) {
51
+ err.serverResponseCode = errorCode;
52
+ }
53
+ err.authenticationFailed = true;
54
+ err.response = await getErrorText(err.response);
55
+ if (errorResponse) {
56
+ err.oauthError = errorResponse;
26
57
  }
58
+ throw err;
59
+ }
60
+ }
27
61
 
28
- let errorResponse = false;
29
- try {
30
- let response = await connection.exec(
31
- 'AUTHENTICATE',
32
- [
33
- { type: 'ATOM', value: command },
34
- { type: 'ATOM', value: Buffer.from(oauthbearer).toString('base64'), sensitive: true }
35
- ],
36
- {
37
- onPlusTag: async resp => {
38
- if (resp.attributes && resp.attributes[0] && resp.attributes[0].type === 'TEXT') {
39
- try {
40
- errorResponse = JSON.parse(Buffer.from(resp.attributes[0].value, 'base64').toString());
41
- } catch (err) {
42
- connection.log.debug({ errorResponse: resp.attributes[0].value, err });
43
- }
62
+ async function authLogin(connection, username, password) {
63
+ let errorResponse = false;
64
+ try {
65
+ let response = await connection.exec('AUTHENTICATE', [{ type: 'ATOM', value: 'LOGIN' }], {
66
+ onPlusTag: async resp => {
67
+ if (resp.attributes && resp.attributes[0] && resp.attributes[0].type === 'TEXT') {
68
+ let question = Buffer.from(resp.attributes[0].value, 'base64').toString();
69
+ switch (question.toLowerCase().replace(/:$/, '')) {
70
+ case 'username': {
71
+ let encodedUsername = Buffer.from(username).toString('base64');
72
+ connection.log.debug({ src: 'c', msg: encodedUsername, comment: `Encoded username for AUTH=LOGIN` });
73
+ connection.write(encodedUsername);
74
+ break;
75
+ }
76
+ case 'password':
77
+ connection.log.debug({ src: 'c', msg: '(* value hidden *)', comment: `Encoded password for AUTH=LOGIN` });
78
+ connection.write(Buffer.from(password).toString('base64'));
79
+ break;
80
+ default: {
81
+ let error = new Error(`Unknown LOGIN question "${question}"`);
82
+ throw error;
44
83
  }
45
-
46
- connection.log.debug({ src: 'c', msg: breaker, comment: `Error response for ${command}` });
47
- connection.write(breaker);
48
84
  }
49
85
  }
50
- );
51
- response.next();
86
+ }
87
+ });
52
88
 
53
- connection.authCapabilities.set(`AUTH=${command}`, true);
89
+ response.next();
54
90
 
55
- return username;
56
- } catch (err) {
57
- let errorCode = getStatusCode(err.response);
58
- if (errorCode) {
59
- err.serverResponseCode = errorCode;
60
- }
61
- err.authenticationFailed = true;
62
- err.response = await getErrorText(err.response);
63
- if (errorResponse) {
64
- err.oauthError = errorResponse;
91
+ connection.authCapabilities.set(`AUTH=LOGIN`, true);
92
+
93
+ return username;
94
+ } catch (err) {
95
+ let errorCode = getStatusCode(err.response);
96
+ if (errorCode) {
97
+ err.serverResponseCode = errorCode;
98
+ }
99
+ err.authenticationFailed = true;
100
+ err.response = await getErrorText(err.response);
101
+ if (errorResponse) {
102
+ err.oauthError = errorResponse;
103
+ }
104
+ throw err;
105
+ }
106
+ }
107
+
108
+ async function authPlain(connection, username, password) {
109
+ let errorResponse = false;
110
+ try {
111
+ let response = await connection.exec('AUTHENTICATE', [{ type: 'ATOM', value: 'PLAIN' }], {
112
+ onPlusTag: async () => {
113
+ let encodedResponse = Buffer.from(['', username, password].join('\x00')).toString('base64');
114
+ let loggedResponse = Buffer.from(['', username, '(* value hidden *)'].join('\x00')).toString('base64');
115
+ connection.log.debug({ src: 'c', msg: loggedResponse, comment: `Encoded response for AUTH=PLAIN` });
116
+ connection.write(encodedResponse);
65
117
  }
66
- throw err;
118
+ });
119
+
120
+ response.next();
121
+
122
+ connection.authCapabilities.set(`AUTH=PLAIN`, true);
123
+
124
+ return username;
125
+ } catch (err) {
126
+ let errorCode = getStatusCode(err.response);
127
+ if (errorCode) {
128
+ err.serverResponseCode = errorCode;
129
+ }
130
+ err.authenticationFailed = true;
131
+ err.response = await getErrorText(err.response);
132
+ if (errorResponse) {
133
+ err.oauthError = errorResponse;
134
+ }
135
+ throw err;
136
+ }
137
+ }
138
+
139
+ // Authenticates user using LOGIN
140
+ module.exports = async (connection, username, { accessToken, password }) => {
141
+ if (connection.state !== connection.states.NOT_AUTHENTICATED) {
142
+ // nothing to do here
143
+ return;
144
+ }
145
+
146
+ if (accessToken) {
147
+ // AUTH=OAUTHBEARER and AUTH=XOAUTH in the context of OAuth2 or very similar so we can handle these together
148
+ if (connection.capabilities.has('AUTH=OAUTHBEARER') || connection.capabilities.has('AUTH=XOAUTH') || connection.capabilities.has('AUTH=XOAUTH2')) {
149
+ return await authOauth(connection, username, accessToken);
150
+ }
151
+ }
152
+
153
+ if (password) {
154
+ if (connection.capabilities.has('AUTH=LOGIN')) {
155
+ return await authLogin(connection, username, password);
156
+ }
157
+
158
+ if (connection.capabilities.has('AUTH=PLAIN')) {
159
+ return await authPlain(connection, username, password);
67
160
  }
68
161
  }
69
162
 
@@ -11,8 +11,8 @@ module.exports = async (connection, username, password) => {
11
11
 
12
12
  try {
13
13
  let response = await connection.exec('LOGIN', [
14
- { type: 'ATOM', value: username },
15
- { type: 'ATOM', value: password, sensitive: true }
14
+ { type: 'STRING', value: username },
15
+ { type: 'STRING', value: password, sensitive: true }
16
16
  ]);
17
17
  response.next();
18
18
 
package/lib/imap-flow.js CHANGED
@@ -931,9 +931,13 @@ class ImapFlow extends EventEmitter {
931
931
  this.expectCapabilityUpdate = true;
932
932
 
933
933
  if (this.options.auth.accessToken) {
934
- this.authenticated = await this.run('AUTHENTICATE', this.options.auth.user, this.options.auth.accessToken);
934
+ this.authenticated = await this.run('AUTHENTICATE', this.options.auth.user, { accessToken: this.options.auth.accessToken });
935
935
  } else if (this.options.auth.pass) {
936
- this.authenticated = await this.run('LOGIN', this.options.auth.user, this.options.auth.pass);
936
+ if (this.capabilities.has('AUTH=LOGIN') || this.capabilities.has('AUTH=PLAIN')) {
937
+ this.authenticated = await this.run('AUTHENTICATE', this.options.auth.user, { password: this.options.auth.pass });
938
+ } else {
939
+ this.authenticated = await this.run('LOGIN', this.options.auth.user, this.options.auth.pass);
940
+ }
937
941
  }
938
942
 
939
943
  if (this.authenticated) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imapflow",
3
- "version": "1.0.165",
3
+ "version": "1.0.167",
4
4
  "description": "IMAP Client for Node",
5
5
  "main": "./lib/imap-flow.js",
6
6
  "scripts": {
@@ -32,7 +32,7 @@
32
32
  "@babel/eslint-plugin": "7.25.9",
33
33
  "@babel/plugin-syntax-class-properties": "7.12.13",
34
34
  "@babel/preset-env": "7.26.0",
35
- "@types/node": "22.8.6",
35
+ "@types/node": "22.9.0",
36
36
  "eslint": "8.57.0",
37
37
  "eslint-config-nodemailer": "1.2.0",
38
38
  "eslint-config-prettier": "9.1.0",