imapflow 1.0.194 → 1.0.196
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/.github/workflows/test.yml +1 -1
- package/CHANGELOG.md +20 -0
- package/README.md +25 -0
- package/lib/commands/authenticate.js +12 -7
- package/lib/commands/fetch.js +17 -4
- package/lib/handler/imap-stream.js +7 -0
- package/lib/imap-flow.d.ts +2 -0
- package/lib/imap-flow.js +48 -8
- package/package.json +8 -8
- package/test/authentication-test.js +25 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.0.196](https://github.com/postalsys/imapflow/compare/v1.0.195...v1.0.196) (2025-09-14)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* Added authzid option for AUTH=PLAIN authentication ([541c43a](https://github.com/postalsys/imapflow/commit/541c43ab0f15608c7d28f19c810e1a14bc8519bc))
|
|
9
|
+
|
|
10
|
+
## [1.0.195](https://github.com/postalsys/imapflow/compare/v1.0.194...v1.0.195) (2025-08-26)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* Add async yielding to compression readNext() to prevent CPU blocking ([18e806d](https://github.com/postalsys/imapflow/commit/18e806d5485defe6d4a0ef3eb502b17f3b2d8c2d))
|
|
16
|
+
* Add async yielding to lock processing loop ([fbf1d9a](https://github.com/postalsys/imapflow/commit/fbf1d9a3aa93b4733a123553d8be0b1785f4135e))
|
|
17
|
+
* Add async yielding to processInput() to prevent CPU blocking ([6b619ce](https://github.com/postalsys/imapflow/commit/6b619ced453c3ee8556e18646ad476ad82a68fbf))
|
|
18
|
+
* Add async yielding to reader() method to prevent CPU blocking ([40574e3](https://github.com/postalsys/imapflow/commit/40574e322be27d8b3cdd1b32416e8395d7e395b5))
|
|
19
|
+
* Add exponential backoff to FETCH retry logic ([fb99e2d](https://github.com/postalsys/imapflow/commit/fb99e2d26c9cb29e69b6d33083ef101264b4b039))
|
|
20
|
+
* Add rate limiting for compression operations ([f54c1d0](https://github.com/postalsys/imapflow/commit/f54c1d0caf1bd8cc41a0779e12c915adbb55f346))
|
|
21
|
+
* Correct log source indicator for client commands ([bb3ea09](https://github.com/postalsys/imapflow/commit/bb3ea09c3135c131fc8bedd733311aa3fe176261))
|
|
22
|
+
|
|
3
23
|
## [1.0.194](https://github.com/postalsys/imapflow/compare/v1.0.193...v1.0.194) (2025-08-13)
|
|
4
24
|
|
|
5
25
|
|
package/README.md
CHANGED
|
@@ -73,6 +73,31 @@ const main = async () => {
|
|
|
73
73
|
main().catch(err => console.error(err));
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
+
### Admin Impersonation / Delegation (SASL PLAIN with authzid)
|
|
77
|
+
|
|
78
|
+
ImapFlow supports admin impersonation for mail systems like Zimbra that allow administrators to access user mailboxes. This is done using the SASL PLAIN mechanism with an authorization identity (`authzid`).
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
const { ImapFlow } = require('imapflow');
|
|
82
|
+
const client = new ImapFlow({
|
|
83
|
+
host: 'mail.example.com',
|
|
84
|
+
port: 993,
|
|
85
|
+
secure: true,
|
|
86
|
+
auth: {
|
|
87
|
+
user: 'admin@example.com', // Admin credentials (authentication identity)
|
|
88
|
+
pass: 'adminpassword',
|
|
89
|
+
authzid: 'user@example.com', // User to impersonate (authorization identity)
|
|
90
|
+
loginMethod: 'AUTH=PLAIN' // Must use PLAIN mechanism for authzid
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Connection will authenticate as admin but authorize as the specified user
|
|
95
|
+
await client.connect();
|
|
96
|
+
// Now operating on user@example.com's mailbox as admin
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Note:** The `authzid` parameter only works with the `AUTH=PLAIN` mechanism. The server must support admin delegation/impersonation for this to work.
|
|
100
|
+
|
|
76
101
|
## Documentation
|
|
77
102
|
|
|
78
103
|
[API reference](https://imapflow.com/module-imapflow-ImapFlow.html).
|
|
@@ -110,14 +110,18 @@ async function authLogin(connection, username, password) {
|
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
async function authPlain(connection, username, password) {
|
|
113
|
+
async function authPlain(connection, username, password, authzid) {
|
|
114
114
|
let errorResponse = false;
|
|
115
115
|
try {
|
|
116
116
|
let response = await connection.exec('AUTHENTICATE', [{ type: 'ATOM', value: 'PLAIN' }], {
|
|
117
117
|
onPlusTag: async () => {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
// SASL PLAIN format: [authzid]\x00authcid\x00password
|
|
119
|
+
// authzid: authorization identity (who to impersonate)
|
|
120
|
+
// authcid: authentication identity (who is authenticating)
|
|
121
|
+
let authzidValue = authzid || '';
|
|
122
|
+
let encodedResponse = Buffer.from([authzidValue, username, password].join('\x00')).toString('base64');
|
|
123
|
+
let loggedResponse = Buffer.from([authzidValue, username, '(* value hidden *)'].join('\x00')).toString('base64');
|
|
124
|
+
connection.log.debug({ src: 'c', msg: loggedResponse, comment: `Encoded response for AUTH=PLAIN${authzid ? ' with authzid' : ''}` });
|
|
121
125
|
connection.write(encodedResponse);
|
|
122
126
|
}
|
|
123
127
|
});
|
|
@@ -126,7 +130,8 @@ async function authPlain(connection, username, password) {
|
|
|
126
130
|
|
|
127
131
|
connection.authCapabilities.set(`AUTH=PLAIN`, true);
|
|
128
132
|
|
|
129
|
-
|
|
133
|
+
// Return the identity we're authorized as (authzid if provided, otherwise username)
|
|
134
|
+
return authzid || username;
|
|
130
135
|
} catch (err) {
|
|
131
136
|
let errorCode = getStatusCode(err.response);
|
|
132
137
|
if (errorCode) {
|
|
@@ -142,7 +147,7 @@ async function authPlain(connection, username, password) {
|
|
|
142
147
|
}
|
|
143
148
|
|
|
144
149
|
// Authenticates user using LOGIN
|
|
145
|
-
module.exports = async (connection, username, { accessToken, password, loginMethod }) => {
|
|
150
|
+
module.exports = async (connection, username, { accessToken, password, loginMethod, authzid }) => {
|
|
146
151
|
if (connection.state !== connection.states.NOT_AUTHENTICATED) {
|
|
147
152
|
// nothing to do here
|
|
148
153
|
return;
|
|
@@ -157,7 +162,7 @@ module.exports = async (connection, username, { accessToken, password, loginMeth
|
|
|
157
162
|
|
|
158
163
|
if (password) {
|
|
159
164
|
if ((!loginMethod && connection.capabilities.has('AUTH=PLAIN')) || loginMethod === 'AUTH=PLAIN') {
|
|
160
|
-
return await authPlain(connection, username, password);
|
|
165
|
+
return await authPlain(connection, username, password, authzid);
|
|
161
166
|
}
|
|
162
167
|
|
|
163
168
|
if ((!loginMethod && connection.capabilities.has('AUTH=LOGIN')) || loginMethod === 'AUTH=LOGIN') {
|
package/lib/commands/fetch.js
CHANGED
|
@@ -16,7 +16,10 @@ module.exports = async (connection, range, query, options) => {
|
|
|
16
16
|
const commandKey = connection.capabilities.has('BINARY') && options.binary && !connection.disableBinary ? 'BINARY' : 'BODY';
|
|
17
17
|
|
|
18
18
|
let retryCount = 0;
|
|
19
|
-
|
|
19
|
+
const maxRetries = 4;
|
|
20
|
+
const baseDelay = 1000; // Start with 1 second delay
|
|
21
|
+
|
|
22
|
+
while (retryCount < maxRetries) {
|
|
20
23
|
let messages = {
|
|
21
24
|
count: 0,
|
|
22
25
|
list: []
|
|
@@ -189,15 +192,25 @@ module.exports = async (connection, range, query, options) => {
|
|
|
189
192
|
return messages;
|
|
190
193
|
} catch (err) {
|
|
191
194
|
if (err.code === 'ETHROTTLE') {
|
|
192
|
-
//
|
|
195
|
+
// Calculate exponential backoff delay
|
|
196
|
+
const backoffDelay = Math.min(baseDelay * Math.pow(2, retryCount), 30000); // Cap at 30 seconds
|
|
197
|
+
|
|
198
|
+
// Use throttle reset time if provided and longer than backoff
|
|
199
|
+
const delay = err.throttleReset && err.throttleReset > backoffDelay ? err.throttleReset : backoffDelay;
|
|
200
|
+
|
|
193
201
|
connection.log.warn({
|
|
194
|
-
msg: 'Retrying throttled request',
|
|
202
|
+
msg: 'Retrying throttled request with exponential backoff',
|
|
195
203
|
cid: connection.id,
|
|
196
204
|
code: err.code,
|
|
197
205
|
response: err.responseText,
|
|
198
206
|
throttleReset: err.throttleReset,
|
|
199
|
-
retryCount
|
|
207
|
+
retryCount,
|
|
208
|
+
delayMs: delay
|
|
200
209
|
});
|
|
210
|
+
|
|
211
|
+
// Wait before retrying
|
|
212
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
213
|
+
|
|
201
214
|
retryCount++;
|
|
202
215
|
continue;
|
|
203
216
|
}
|
|
@@ -201,10 +201,17 @@ class ImapStream extends Transform {
|
|
|
201
201
|
|
|
202
202
|
async processInput() {
|
|
203
203
|
let data;
|
|
204
|
+
let processedCount = 0;
|
|
204
205
|
while ((data = this.inputQueue.shift())) {
|
|
205
206
|
await this.processInputChunk(data.chunk);
|
|
206
207
|
// mark chunk as processed
|
|
207
208
|
data.next();
|
|
209
|
+
|
|
210
|
+
// Yield to event loop every 10 chunks to prevent CPU blocking
|
|
211
|
+
processedCount++;
|
|
212
|
+
if (processedCount % 10 === 0) {
|
|
213
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
214
|
+
}
|
|
208
215
|
}
|
|
209
216
|
}
|
|
210
217
|
|
package/lib/imap-flow.d.ts
CHANGED
|
@@ -23,6 +23,8 @@ export interface ImapFlowOptions {
|
|
|
23
23
|
accessToken?: string;
|
|
24
24
|
/** Optional login method override. Set to 'LOGIN', 'AUTH=LOGIN' or 'AUTH=PLAIN' to use specific method */
|
|
25
25
|
loginMethod?: string;
|
|
26
|
+
/** Authorization identity for SASL PLAIN (used for admin impersonation/delegation). When set, authenticates as `user` but authorizes as `authzid` */
|
|
27
|
+
authzid?: string;
|
|
26
28
|
};
|
|
27
29
|
/** Client identification info sent to the server if server supports ID extension */
|
|
28
30
|
clientInfo?: IdInfoObject;
|
package/lib/imap-flow.js
CHANGED
|
@@ -158,6 +158,12 @@ class ImapFlow extends EventEmitter {
|
|
|
158
158
|
* Optional login method for password-based authentication (e.g., "LOGIN", "AUTH=LOGIN", or "AUTH=PLAIN").
|
|
159
159
|
* If not set, ImapFlow chooses based on available mechanisms.
|
|
160
160
|
*
|
|
161
|
+
* @property {String} [auth.authzid]
|
|
162
|
+
* Authorization identity for SASL PLAIN authentication (used for admin impersonation/delegation).
|
|
163
|
+
* When set, authenticates as `auth.user` but authorizes as `auth.authzid`.
|
|
164
|
+
* This is typically used in mail systems like Zimbra for admin users to access other users' mailboxes.
|
|
165
|
+
* Only works with AUTH=PLAIN mechanism.
|
|
166
|
+
*
|
|
161
167
|
* @property {IdInfoObject} [clientInfo]
|
|
162
168
|
* Client identification info sent to the server (via the ID command).
|
|
163
169
|
*
|
|
@@ -495,7 +501,7 @@ class ImapFlow extends EventEmitter {
|
|
|
495
501
|
|
|
496
502
|
let options = data.options || {};
|
|
497
503
|
|
|
498
|
-
this.log.debug({ src: '
|
|
504
|
+
this.log.debug({ src: 'c', msg: logCompiled.toString(), cid: this.id, comment: options.comment });
|
|
499
505
|
|
|
500
506
|
this.write(this.commandParts.shift());
|
|
501
507
|
|
|
@@ -572,6 +578,7 @@ class ImapFlow extends EventEmitter {
|
|
|
572
578
|
|
|
573
579
|
async reader() {
|
|
574
580
|
let data;
|
|
581
|
+
let processedCount = 0;
|
|
575
582
|
while ((data = this.streamer.read()) !== null) {
|
|
576
583
|
let parsed;
|
|
577
584
|
|
|
@@ -737,6 +744,12 @@ class ImapFlow extends EventEmitter {
|
|
|
737
744
|
}
|
|
738
745
|
|
|
739
746
|
data.next();
|
|
747
|
+
|
|
748
|
+
// Yield to event loop every 10 processed messages to prevent CPU blocking
|
|
749
|
+
processedCount++;
|
|
750
|
+
if (processedCount % 10 === 0) {
|
|
751
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
752
|
+
}
|
|
740
753
|
}
|
|
741
754
|
}
|
|
742
755
|
|
|
@@ -885,11 +898,17 @@ class ImapFlow extends EventEmitter {
|
|
|
885
898
|
return; // was not able to negotiate compression
|
|
886
899
|
}
|
|
887
900
|
|
|
888
|
-
// create deflate/inflate streams
|
|
901
|
+
// create deflate/inflate streams with rate limiting options
|
|
889
902
|
this._deflate = zlib.createDeflateRaw({
|
|
890
|
-
windowBits: 15
|
|
903
|
+
windowBits: 15,
|
|
904
|
+
level: zlib.constants.Z_DEFAULT_COMPRESSION, // Use default compression level (6)
|
|
905
|
+
memLevel: 8, // Memory usage level (8 is default)
|
|
906
|
+
strategy: zlib.constants.Z_DEFAULT_STRATEGY,
|
|
907
|
+
chunkSize: 16 * 1024 // Process in 16KB chunks to prevent CPU blocking
|
|
908
|
+
});
|
|
909
|
+
this._inflate = zlib.createInflateRaw({
|
|
910
|
+
chunkSize: 16 * 1024 // Process in 16KB chunks to prevent CPU blocking
|
|
891
911
|
});
|
|
892
|
-
this._inflate = zlib.createInflateRaw();
|
|
893
912
|
|
|
894
913
|
// route incoming socket via inflate stream
|
|
895
914
|
this.socket.unpipe(this.streamer);
|
|
@@ -899,8 +918,10 @@ class ImapFlow extends EventEmitter {
|
|
|
899
918
|
this.streamer.emit('error', err);
|
|
900
919
|
});
|
|
901
920
|
|
|
902
|
-
// route outgoing socket via deflate stream
|
|
903
|
-
this.writeSocket = new PassThrough(
|
|
921
|
+
// route outgoing socket via deflate stream with rate limiting
|
|
922
|
+
this.writeSocket = new PassThrough({
|
|
923
|
+
highWaterMark: 64 * 1024 // 64KB buffer limit to prevent excessive memory usage
|
|
924
|
+
});
|
|
904
925
|
|
|
905
926
|
this.writeSocket.destroySoon = () => {
|
|
906
927
|
try {
|
|
@@ -921,15 +942,23 @@ class ImapFlow extends EventEmitter {
|
|
|
921
942
|
// we need to force flush deflated data to socket so we can't
|
|
922
943
|
// use normal pipes for this.writeSocket -> this._deflate -> this.socket
|
|
923
944
|
let reading = false;
|
|
924
|
-
let
|
|
945
|
+
let processedChunks = 0;
|
|
946
|
+
let readNext = async () => {
|
|
925
947
|
try {
|
|
926
948
|
reading = true;
|
|
949
|
+
processedChunks = 0;
|
|
927
950
|
|
|
928
951
|
let chunk;
|
|
929
952
|
while ((chunk = this.writeSocket.read()) !== null) {
|
|
930
953
|
if (this._deflate && this._deflate.write(chunk) === false) {
|
|
931
954
|
return this._deflate.once('drain', readNext);
|
|
932
955
|
}
|
|
956
|
+
|
|
957
|
+
// Yield to event loop every 100 chunks to prevent CPU blocking
|
|
958
|
+
processedChunks++;
|
|
959
|
+
if (processedChunks % 100 === 0) {
|
|
960
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
961
|
+
}
|
|
933
962
|
}
|
|
934
963
|
|
|
935
964
|
// flush data to socket
|
|
@@ -1117,7 +1146,11 @@ class ImapFlow extends EventEmitter {
|
|
|
1117
1146
|
this.authenticated = await this.run('AUTHENTICATE', this.options.auth.user, { accessToken: this.options.auth.accessToken });
|
|
1118
1147
|
} else if (this.options.auth.pass) {
|
|
1119
1148
|
if ((this.capabilities.has('AUTH=LOGIN') || this.capabilities.has('AUTH=PLAIN')) && loginMethod !== 'LOGIN') {
|
|
1120
|
-
this.authenticated = await this.run('AUTHENTICATE', this.options.auth.user, {
|
|
1149
|
+
this.authenticated = await this.run('AUTHENTICATE', this.options.auth.user, {
|
|
1150
|
+
password: this.options.auth.pass,
|
|
1151
|
+
loginMethod,
|
|
1152
|
+
authzid: this.options.auth.authzid
|
|
1153
|
+
});
|
|
1121
1154
|
} else {
|
|
1122
1155
|
if (this.capabilities.has('LOGINDISABLED')) {
|
|
1123
1156
|
throw new AuthenticationFailure('Login is disabled');
|
|
@@ -3236,6 +3269,7 @@ class ImapFlow extends EventEmitter {
|
|
|
3236
3269
|
|
|
3237
3270
|
try {
|
|
3238
3271
|
// Process all locks in queue until empty
|
|
3272
|
+
let processedCount = 0;
|
|
3239
3273
|
while (this.locks.length > 0) {
|
|
3240
3274
|
if (!this.locks.length) {
|
|
3241
3275
|
this.log.trace({
|
|
@@ -3245,6 +3279,12 @@ class ImapFlow extends EventEmitter {
|
|
|
3245
3279
|
return;
|
|
3246
3280
|
}
|
|
3247
3281
|
|
|
3282
|
+
// Yield to event loop periodically to prevent CPU blocking
|
|
3283
|
+
processedCount++;
|
|
3284
|
+
if (processedCount % 5 === 0) {
|
|
3285
|
+
await new Promise(resolve => setImmediate(resolve));
|
|
3286
|
+
}
|
|
3287
|
+
|
|
3248
3288
|
const release = () => {
|
|
3249
3289
|
if (this.currentLock) {
|
|
3250
3290
|
this.log.trace({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "imapflow",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.196",
|
|
4
4
|
"description": "IMAP Client for Node",
|
|
5
5
|
"main": "lib/imap-flow.js",
|
|
6
6
|
"types": "lib/imap-flow.d.ts",
|
|
@@ -28,11 +28,11 @@
|
|
|
28
28
|
},
|
|
29
29
|
"homepage": "https://imapflow.com/",
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"@babel/eslint-parser": "7.28.
|
|
31
|
+
"@babel/eslint-parser": "7.28.4",
|
|
32
32
|
"@babel/eslint-plugin": "7.27.1",
|
|
33
33
|
"@babel/plugin-syntax-class-properties": "7.12.13",
|
|
34
|
-
"@babel/preset-env": "7.28.
|
|
35
|
-
"@types/node": "24.
|
|
34
|
+
"@babel/preset-env": "7.28.3",
|
|
35
|
+
"@types/node": "24.4.0",
|
|
36
36
|
"eslint": "8.57.0",
|
|
37
37
|
"eslint-config-nodemailer": "1.2.0",
|
|
38
38
|
"eslint-config-prettier": "9.1.0",
|
|
@@ -42,18 +42,18 @@
|
|
|
42
42
|
"grunt-eslint": "24.3.0",
|
|
43
43
|
"imapflow-jsdoc-template": "3.4.0-imapflow.3",
|
|
44
44
|
"jsdoc": "4.0.4",
|
|
45
|
-
"st": "3.0.
|
|
45
|
+
"st": "3.0.3",
|
|
46
46
|
"typescript": "5.9.2"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"encoding-japanese": "2.2.0",
|
|
50
|
-
"iconv-lite": "0.
|
|
50
|
+
"iconv-lite": "0.7.0",
|
|
51
51
|
"libbase64": "1.3.0",
|
|
52
52
|
"libmime": "5.3.7",
|
|
53
53
|
"libqp": "2.1.1",
|
|
54
54
|
"mailsplit": "5.4.6",
|
|
55
|
-
"nodemailer": "7.0.
|
|
56
|
-
"pino": "9.
|
|
55
|
+
"nodemailer": "7.0.6",
|
|
56
|
+
"pino": "9.9.5",
|
|
57
57
|
"socks": "2.8.7"
|
|
58
58
|
}
|
|
59
59
|
}
|
|
@@ -11,7 +11,7 @@ module.exports['Authentication: Password auth configuration'] = test => {
|
|
|
11
11
|
pass: 'testpass'
|
|
12
12
|
}
|
|
13
13
|
});
|
|
14
|
-
|
|
14
|
+
|
|
15
15
|
test.equal(client.options.auth.user, 'testuser');
|
|
16
16
|
test.equal(client.options.auth.pass, 'testpass');
|
|
17
17
|
test.done();
|
|
@@ -25,7 +25,7 @@ module.exports['Authentication: OAuth2 auth configuration'] = test => {
|
|
|
25
25
|
accessToken: 'oauth2_token_here'
|
|
26
26
|
}
|
|
27
27
|
});
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
test.equal(client.options.auth.user, 'testuser');
|
|
30
30
|
test.equal(client.options.auth.accessToken, 'oauth2_token_here');
|
|
31
31
|
test.done();
|
|
@@ -40,14 +40,32 @@ module.exports['Authentication: Login method specification'] = test => {
|
|
|
40
40
|
loginMethod: 'AUTH=PLAIN'
|
|
41
41
|
}
|
|
42
42
|
});
|
|
43
|
-
|
|
43
|
+
|
|
44
|
+
test.equal(client.options.auth.loginMethod, 'AUTH=PLAIN');
|
|
45
|
+
test.done();
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
module.exports['Authentication: SASL PLAIN with authzid for impersonation'] = test => {
|
|
49
|
+
let client = new ImapFlow({
|
|
50
|
+
host: 'imap.example.com',
|
|
51
|
+
auth: {
|
|
52
|
+
user: 'admin@example.com',
|
|
53
|
+
pass: 'adminpass',
|
|
54
|
+
authzid: 'user@example.com',
|
|
55
|
+
loginMethod: 'AUTH=PLAIN'
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test.equal(client.options.auth.user, 'admin@example.com');
|
|
60
|
+
test.equal(client.options.auth.pass, 'adminpass');
|
|
61
|
+
test.equal(client.options.auth.authzid, 'user@example.com');
|
|
44
62
|
test.equal(client.options.auth.loginMethod, 'AUTH=PLAIN');
|
|
45
63
|
test.done();
|
|
46
64
|
};
|
|
47
65
|
|
|
48
66
|
module.exports['Authentication: AuthenticationFailure error structure'] = test => {
|
|
49
67
|
let error = new AuthenticationFailure('Invalid credentials');
|
|
50
|
-
|
|
68
|
+
|
|
51
69
|
test.ok(error instanceof Error);
|
|
52
70
|
test.equal(error.constructor.name, 'AuthenticationFailure');
|
|
53
71
|
test.equal(error.message, 'Invalid credentials');
|
|
@@ -63,7 +81,7 @@ module.exports['Authentication: Verify-only mode'] = test => {
|
|
|
63
81
|
},
|
|
64
82
|
verifyOnly: true
|
|
65
83
|
});
|
|
66
|
-
|
|
84
|
+
|
|
67
85
|
test.equal(client.options.verifyOnly, true);
|
|
68
86
|
test.done();
|
|
69
87
|
};
|
|
@@ -77,7 +95,7 @@ module.exports['Authentication: Disable auto IDLE'] = test => {
|
|
|
77
95
|
},
|
|
78
96
|
disableAutoIdle: true
|
|
79
97
|
});
|
|
80
|
-
|
|
98
|
+
|
|
81
99
|
test.equal(client.options.disableAutoIdle, true);
|
|
82
100
|
test.done();
|
|
83
|
-
};
|
|
101
|
+
};
|