imapflow 1.4.7 → 1.4.8

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.
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "1.4.7"
2
+ ".": "1.4.8"
3
3
  }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.4.8](https://github.com/postalsys/imapflow/compare/v1.4.7...v1.4.8) (2026-07-21)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **auth:** describe the real connection in the OAUTHBEARER payload ([aca7cb3](https://github.com/postalsys/imapflow/commit/aca7cb3ab7e67704cb692ad59f6db0792040a888))
9
+ * default a non-secure connection to IMAP port 143, not POP3 110 ([5896488](https://github.com/postalsys/imapflow/commit/589648821c5dd1b1750959fa2917b3571ba93db5))
10
+
3
11
  ## [1.4.7](https://github.com/postalsys/imapflow/compare/v1.4.6...v1.4.7) (2026-07-10)
4
12
 
5
13
 
@@ -40,7 +40,14 @@ async function authOauth(connection, username, accessToken) {
40
40
  // OAUTHBEARER payload per RFC 7628: fields separated by \x01 (SASL GS2 framing).
41
41
  // Format: "n,a=<user>," \x01 "host=..." \x01 "port=..." \x01 "auth=Bearer <token>" \x01 \x01
42
42
  // The trailing empty strings produce the required double-\x01 terminator.
43
- oauthbearer = [`n,a=${username},`, `host=${connection.servername}`, `port=993`, `auth=Bearer ${accessToken}`, '', ''].join('\x01');
43
+ // Both fields must describe the connection actually in use. The port was hardcoded to 993,
44
+ // so an OAuth2 server reached over 143/STARTTLS advertised a payload that did not match, and
45
+ // `servername` is set to false for a bare-IP host, which rendered as a literal "host=false".
46
+ // A server validating either field rejects with status `invalid_request` - a permanent
47
+ // failure that refreshing the access token can never clear.
48
+ oauthbearer = [`n,a=${username},`, `host=${connection.servername || connection.host}`, `port=${connection.port}`, `auth=Bearer ${accessToken}`, '', ''].join(
49
+ '\x01'
50
+ );
44
51
  command = 'OAUTHBEARER';
45
52
  // "AQ==" is base64 for \x01 -- sent as the error continuation to abort the SASL exchange
46
53
  breaker = 'AQ==';
package/lib/imap-flow.js CHANGED
@@ -277,12 +277,14 @@ class ImapFlow extends EventEmitter {
277
277
  */
278
278
  this.secureConnection = !!this.options.secure;
279
279
 
280
- this.port = Number(this.options.port) || (this.secureConnection ? 993 : 110);
280
+ // 993 is IMAPS, 143 is IMAP over cleartext/STARTTLS. The non-secure default used to be 110,
281
+ // which is POP3 - a client created without an explicit port could never connect.
282
+ this.port = Number(this.options.port) || (this.secureConnection ? 993 : 143);
281
283
  this.host = this.options.host || 'localhost';
282
284
  this.servername = this.options.servername ? this.options.servername : !net.isIP(this.host) ? this.host : false;
283
285
 
284
286
  if (typeof this.options.secure === 'undefined' && this.port === 993) {
285
- // if secure option is not set but port is 465, then default to secure
287
+ // if secure option is not set but port is 993, then default to secure
286
288
  this.secureConnection = true;
287
289
  }
288
290
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "imapflow",
3
- "version": "1.4.7",
3
+ "version": "1.4.8",
4
4
  "description": "IMAP Client for Node",
5
5
  "main": "lib/imap-flow.js",
6
6
  "types": "lib/imap-flow.d.ts",
@@ -28,18 +28,18 @@
28
28
  "homepage": "https://imapflow.com/",
29
29
  "devDependencies": {
30
30
  "@eslint/js": "10.0.1",
31
- "@types/node": "26.1.0",
32
- "c8": "11.0.0",
33
- "eslint": "10.6.0",
31
+ "@types/node": "26.1.1",
32
+ "c8": "12.0.0",
33
+ "eslint": "10.7.0",
34
34
  "eslint-config-nodemailer": "1.2.0",
35
35
  "eslint-config-prettier": "10.1.8",
36
36
  "grunt": "1.6.2",
37
37
  "grunt-cli": "1.5.0",
38
38
  "grunt-contrib-nodeunit": "5.0.0",
39
39
  "grunt-eslint": "26.0.0",
40
- "prettier": "3.9.4",
40
+ "prettier": "3.9.5",
41
41
  "proxyquire": "^2.1.3",
42
- "typescript": "6.0.3"
42
+ "typescript": "7.0.2"
43
43
  },
44
44
  "dependencies": {
45
45
  "@zone-eu/mailsplit": "5.4.14",
@@ -30,6 +30,9 @@ const createMockConnection = (overrides = {}) => {
30
30
  states,
31
31
  state: overrides.state || states.SELECTED,
32
32
  id: 'test-connection-id',
33
+ // Mirrors imap-flow.js, which always resolves a port before authenticating. Without a
34
+ // default the OAUTHBEARER payload builds `port=undefined`.
35
+ port: overrides.port || 993,
33
36
  capabilities: new Map(overrides.capabilities || [['IMAP4rev1', true]]),
34
37
  enabled: new Set(overrides.enabled || []),
35
38
  authCapabilities: new Map(),
@@ -58,6 +61,9 @@ const createMockConnection = (overrides = {}) => {
58
61
  };
59
62
  };
60
63
 
64
+ // Decodes the base64 SASL payload that authenticate() hands to exec().
65
+ const decodeSaslPayload = execArgs => Buffer.from(execArgs.args[1].value, 'base64').toString();
66
+
61
67
  // ============================================
62
68
  // CAPABILITY Command Tests
63
69
  // ============================================
@@ -7441,6 +7447,55 @@ module.exports['Commands: authenticate with OAUTHBEARER'] = async test => {
7441
7447
  test.equal(execArgs.cmd, 'AUTHENTICATE');
7442
7448
  test.equal(execArgs.args[0].value, 'OAUTHBEARER');
7443
7449
  test.ok(connection.authCapabilities.has('AUTH=OAUTHBEARER'));
7450
+ test.ok(decodeSaslPayload(execArgs).includes('port=993'), 'the payload should report the connection port');
7451
+ test.done();
7452
+ };
7453
+
7454
+ module.exports['Commands: OAUTHBEARER payload reports the port actually in use'] = async test => {
7455
+ // Regression: the port field was hardcoded to 993 - see lib/commands/authenticate.js.
7456
+ let execArgs = null;
7457
+ const connection = createMockConnection({
7458
+ state: 1,
7459
+ capabilities: new Map([['AUTH=OAUTHBEARER', true]]),
7460
+ servername: 'imap.example.com',
7461
+ port: 143,
7462
+ authCapabilities: new Map(),
7463
+ exec: async (cmd, args) => {
7464
+ execArgs = { cmd, args };
7465
+ return { next: () => {} };
7466
+ },
7467
+ write: () => {}
7468
+ });
7469
+
7470
+ await authenticateCommand(connection, 'user@example.com', { accessToken: 'token123' });
7471
+
7472
+ const payload = decodeSaslPayload(execArgs);
7473
+ test.ok(payload.includes('port=143'), `expected port=143 in the SASL payload, got: ${JSON.stringify(payload)}`);
7474
+ test.ok(payload.includes('host=imap.example.com'), 'the host field should still describe the connection');
7475
+ test.done();
7476
+ };
7477
+
7478
+ module.exports['Commands: OAUTHBEARER payload falls back to host when servername is false'] = async test => {
7479
+ // imap-flow.js sets servername = false for a bare-IP host, which rendered as "host=false".
7480
+ let execArgs = null;
7481
+ const connection = createMockConnection({
7482
+ state: 1,
7483
+ capabilities: new Map([['AUTH=OAUTHBEARER', true]]),
7484
+ servername: false,
7485
+ host: '198.51.100.7',
7486
+ authCapabilities: new Map(),
7487
+ exec: async (cmd, args) => {
7488
+ execArgs = { cmd, args };
7489
+ return { next: () => {} };
7490
+ },
7491
+ write: () => {}
7492
+ });
7493
+
7494
+ await authenticateCommand(connection, 'user@example.com', { accessToken: 'token123' });
7495
+
7496
+ const payload = decodeSaslPayload(execArgs);
7497
+ test.ok(payload.includes('host=198.51.100.7'), `expected the host as fallback, got: ${JSON.stringify(payload)}`);
7498
+ test.ok(!payload.includes('host=false'), 'the literal "host=false" must never be sent');
7444
7499
  test.done();
7445
7500
  };
7446
7501
 
@@ -143,7 +143,7 @@ module.exports['Connection Edge: Connection with default values'] = test => {
143
143
  });
144
144
 
145
145
  test.ok(client, 'Client should be created with defaults');
146
- test.equal(client.port, 110, 'Should use default port');
146
+ test.equal(client.port, 143, 'Should use default port');
147
147
  test.equal(client.secureConnection, false, 'Should default to non-secure');
148
148
  test.done();
149
149
  };
@@ -155,7 +155,7 @@ module.exports['Connection Edge: Port number handling'] = test => {
155
155
  secure: false,
156
156
  auth: { user: 'test', pass: 'test' }
157
157
  });
158
- test.equal(client1.port, 110, 'Default non-secure port');
158
+ test.equal(client1.port, 143, 'Default non-secure port');
159
159
 
160
160
  let client2 = new ImapFlow({
161
161
  host: 'imap.example.com',
@@ -20,7 +20,7 @@ module.exports['Connection: Default options'] = test => {
20
20
  auth: { user: 'test', pass: 'test' }
21
21
  });
22
22
 
23
- test.equal(client.port, 110);
23
+ test.equal(client.port, 143);
24
24
  test.equal(client.secureConnection, false);
25
25
  test.done();
26
26
  };
@@ -1,12 +0,0 @@
1
- name: "ImapFlow CodeQL config"
2
-
3
- # Exclude code that is not part of the published library runtime from analysis.
4
- # These paths generate only false-positive noise:
5
- # - test fixtures intentionally feed malformed/hostile input and exercise
6
- # edge cases that are not representative of production usage
7
- # - the example scripts hardcode demo credentials and connection details for
8
- # local experimentation and are not maintained as production code
9
- paths-ignore:
10
- - test
11
- - '**/test/**'
12
- - examples
@@ -1,102 +0,0 @@
1
- # For most projects, this workflow file will not need changing; you simply need
2
- # to commit it to your repository.
3
- #
4
- # You may wish to alter this file to override the set of languages analyzed,
5
- # or to provide custom queries or build logic.
6
- #
7
- # ******** NOTE ********
8
- # We have attempted to detect the languages in your repository. Please check
9
- # the `language` matrix defined below to confirm you have the correct set of
10
- # supported CodeQL languages.
11
- #
12
- name: "CodeQL Advanced"
13
-
14
- on:
15
- push:
16
- branches: [ "master" ]
17
- pull_request:
18
- branches: [ "master" ]
19
- schedule:
20
- - cron: '40 17 * * 6'
21
-
22
- jobs:
23
- analyze:
24
- name: Analyze (${{ matrix.language }})
25
- # Runner size impacts CodeQL analysis time. To learn more, please see:
26
- # - https://gh.io/recommended-hardware-resources-for-running-codeql
27
- # - https://gh.io/supported-runners-and-hardware-resources
28
- # - https://gh.io/using-larger-runners (GitHub.com only)
29
- # Consider using larger runners or machines with greater resources for possible analysis time improvements.
30
- runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
31
- permissions:
32
- # required for all workflows
33
- security-events: write
34
-
35
- # required to fetch internal or private CodeQL packs
36
- packages: read
37
-
38
- # only required for workflows in private repositories
39
- actions: read
40
- contents: read
41
-
42
- strategy:
43
- fail-fast: false
44
- matrix:
45
- include:
46
- - language: actions
47
- build-mode: none
48
- - language: javascript-typescript
49
- build-mode: none
50
- # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift'
51
- # Use `c-cpp` to analyze code written in C, C++ or both
52
- # Use 'java-kotlin' to analyze code written in Java, Kotlin or both
53
- # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
54
- # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis,
55
- # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning.
56
- # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how
57
- # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
58
- steps:
59
- - name: Checkout repository
60
- uses: actions/checkout@v6
61
-
62
- # Add any setup steps before running the `github/codeql-action/init` action.
63
- # This includes steps like installing compilers or runtimes (`actions/setup-node`
64
- # or others). This is typically only required for manual builds.
65
- # - name: Setup runtime (example)
66
- # uses: actions/setup-example@v1
67
-
68
- # Initializes the CodeQL tools for scanning.
69
- - name: Initialize CodeQL
70
- uses: github/codeql-action/init@v4
71
- with:
72
- languages: ${{ matrix.language }}
73
- build-mode: ${{ matrix.build-mode }}
74
- config-file: ./.github/codeql/codeql-config.yml
75
- # If you wish to specify custom queries, you can do so here or in a config file.
76
- # By default, queries listed here will override any specified in a config file.
77
- # Prefix the list here with "+" to use these queries and those in the config file.
78
-
79
- # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
80
- # queries: security-extended,security-and-quality
81
-
82
- # If the analyze step fails for one of the languages you are analyzing with
83
- # "We were unable to automatically build your code", modify the matrix above
84
- # to set the build mode to "manual" for that language. Then modify this step
85
- # to build your code.
86
- # ℹ️ Command-line programs to run using the OS shell.
87
- # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
88
- - name: Run manual build steps
89
- if: matrix.build-mode == 'manual'
90
- shell: bash
91
- run: |
92
- echo 'If you are using a "manual" build mode for one or more of the' \
93
- 'languages you are analyzing, replace this with the commands to build' \
94
- 'your code, for example:'
95
- echo ' make bootstrap'
96
- echo ' make release'
97
- exit 1
98
-
99
- - name: Perform CodeQL Analysis
100
- uses: github/codeql-action/analyze@v4
101
- with:
102
- category: "/language:${{matrix.language}}"