@remit/mailbox-service 0.0.84 → 0.0.85

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remit/mailbox-service",
3
- "version": "0.0.84",
3
+ "version": "0.0.85",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -13,6 +13,10 @@
13
13
  "types": "./src/body-sync-queue.ts",
14
14
  "default": "./src/body-sync-queue.ts"
15
15
  },
16
+ "./imapflow-connection": {
17
+ "types": "./src/imapflow-connection.ts",
18
+ "default": "./src/imapflow-connection.ts"
19
+ },
16
20
  "./account-credentials": {
17
21
  "types": "./src/account-credentials.ts",
18
22
  "default": "./src/account-credentials.ts"
@@ -105,4 +105,30 @@ describe("ImapFlowConnection.connect() retry policy", () => {
105
105
  assert.equal(attempt.mock.callCount(), 1);
106
106
  assert.equal(sleep.mock.callCount(), 0);
107
107
  });
108
+
109
+ it("carries the server's own refusal, which is not always a re-auth", async () => {
110
+ const connection = new ImapFlowConnection(baseConfig);
111
+ const priv = connection as unknown as WithPrivates;
112
+
113
+ // imapflow's shape for a tagged NO: the message is its own, the server's
114
+ // words are on `responseText`. Signing in again fixes nothing here — the
115
+ // tenant has IMAP switched off — so the text has to reach the user.
116
+ mock.method(priv, "attemptConnect", async () => {
117
+ throw Object.assign(new Error("Command failed"), {
118
+ authenticationFailed: true,
119
+ responseText:
120
+ "LOGIN failed. User is authenticated but not connected. SmtpClientAuthentication is disabled for the Tenant.",
121
+ });
122
+ });
123
+
124
+ await assert.rejects(
125
+ () => connection.connect(),
126
+ (error: unknown) => {
127
+ assert.ok(error instanceof MailConnectionError);
128
+ assert.equal(error.kind, "auth");
129
+ assert.match(error.message, /SmtpClientAuthentication is disabled/);
130
+ return true;
131
+ },
132
+ );
133
+ });
108
134
  });
@@ -1391,6 +1391,20 @@ const buildImapAuth = (
1391
1391
  throw new Error(`Unknown credential kind: ${JSON.stringify(_exhaustive)}`);
1392
1392
  };
1393
1393
 
1394
+ /**
1395
+ * The server's own words for the failure: the text of the tagged NO/BAD, which
1396
+ * imapflow hangs off the error as `responseText`. It is the server's side of
1397
+ * the exchange only — never the command we sent, so no credential can ride out
1398
+ * on it. "SmtpClientAuthentication is disabled" and "User is authenticated but
1399
+ * not connected" arrive this way, and neither is fixed by re-authenticating, so
1400
+ * dropping the text leaves the account card telling the user to do the one
1401
+ * thing that cannot help.
1402
+ */
1403
+ const serverResponseText = (error: Error): string => {
1404
+ const text = (error as { responseText?: unknown }).responseText;
1405
+ return typeof text === "string" ? text.trim() : "";
1406
+ };
1407
+
1394
1408
  /**
1395
1409
  * Classify a raw IMAP error into a MailConnectionError, or return null if
1396
1410
  * the error is not recognisable (let it bubble as-is).
@@ -1416,7 +1430,13 @@ const classifyImapError = (
1416
1430
  msg.includes("AUTHENTICATE") ||
1417
1431
  (error as { authenticationFailed?: boolean }).authenticationFailed === true
1418
1432
  ) {
1419
- return new MailConnectionError("auth", "IMAP authentication failed");
1433
+ const serverText = serverResponseText(error);
1434
+ return new MailConnectionError(
1435
+ "auth",
1436
+ serverText
1437
+ ? `IMAP authentication failed: ${serverText}`
1438
+ : "IMAP authentication failed",
1439
+ );
1420
1440
  }
1421
1441
 
1422
1442
  // Network-level failures — NEVER include the original message as it may