@remit/mailbox-service 0.0.111 → 0.0.113

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.
Files changed (298) hide show
  1. package/dist/account-credentials.d.ts +83 -0
  2. package/dist/account-credentials.js +104 -0
  3. package/dist/attribute-mapper.d.ts +36 -0
  4. package/dist/attribute-mapper.js +119 -0
  5. package/dist/body-parse.d.ts +28 -0
  6. package/{src/body-parse.ts → dist/body-parse.js} +18 -26
  7. package/dist/body-part-mapper.d.ts +92 -0
  8. package/dist/body-part-mapper.js +301 -0
  9. package/dist/body-sync-queue.d.ts +59 -0
  10. package/dist/body-sync-queue.js +56 -0
  11. package/dist/body-sync-terminal.d.ts +58 -0
  12. package/dist/body-sync-terminal.js +95 -0
  13. package/dist/body-sync.d.ts +351 -0
  14. package/dist/body-sync.js +1204 -0
  15. package/dist/calendar-parts.d.ts +23 -0
  16. package/dist/calendar-parts.js +18 -0
  17. package/dist/classification-backfill.d.ts +91 -0
  18. package/dist/classification-backfill.js +180 -0
  19. package/dist/classify-message.d.ts +31 -0
  20. package/dist/classify-message.js +89 -0
  21. package/dist/connection-factory.d.ts +54 -0
  22. package/dist/connection-factory.js +54 -0
  23. package/dist/connection-test.d.ts +41 -0
  24. package/dist/connection-test.js +152 -0
  25. package/dist/external-move.d.ts +94 -0
  26. package/dist/external-move.js +173 -0
  27. package/dist/filter-operations.d.ts +42 -0
  28. package/dist/filter-operations.js +211 -0
  29. package/dist/filters/filter-config.d.ts +14 -0
  30. package/dist/filters/filter-config.js +10 -0
  31. package/{src/filters/list-id.ts → dist/filters/list-id.d.ts} +2 -16
  32. package/dist/filters/list-id.js +26 -0
  33. package/dist/filters/match.d.ts +69 -0
  34. package/dist/filters/match.js +165 -0
  35. package/dist/filters/pipeline.d.ts +64 -0
  36. package/dist/filters/pipeline.js +100 -0
  37. package/{src/flag-push-terminal.ts → dist/flag-push-terminal.d.ts} +19 -77
  38. package/dist/flag-push-terminal.js +63 -0
  39. package/dist/flag-push.d.ts +69 -0
  40. package/dist/flag-push.js +111 -0
  41. package/dist/flag-queue.d.ts +175 -0
  42. package/dist/flag-queue.js +291 -0
  43. package/dist/folder-operations.d.ts +39 -0
  44. package/dist/folder-operations.js +112 -0
  45. package/dist/folder-placement.d.ts +13 -0
  46. package/dist/folder-placement.js +23 -0
  47. package/dist/heuristics/authenticationResults.d.ts +10 -0
  48. package/dist/heuristics/authenticationResults.js +187 -0
  49. package/dist/heuristics/classifyByHeaders.d.ts +82 -0
  50. package/dist/heuristics/classifyByHeaders.js +365 -0
  51. package/dist/heuristics/classifyPlacement.d.ts +67 -0
  52. package/dist/heuristics/classifyPlacement.js +153 -0
  53. package/dist/heuristics/machineSenders.d.ts +21 -0
  54. package/{src/heuristics/machineSenders.ts → dist/heuristics/machineSenders.js} +34 -40
  55. package/dist/heuristics/senderMismatch.d.ts +81 -0
  56. package/dist/heuristics/senderMismatch.js +192 -0
  57. package/dist/heuristics/socialDomains.d.ts +14 -0
  58. package/{src/heuristics/socialDomains.ts → dist/heuristics/socialDomains.js} +9 -9
  59. package/dist/heuristics/transactionalDomains.d.ts +18 -0
  60. package/{src/heuristics/transactionalDomains.ts → dist/heuristics/transactionalDomains.js} +8 -8
  61. package/dist/imapflow-connection.d.ts +377 -0
  62. package/dist/imapflow-connection.js +1147 -0
  63. package/dist/index.d.ts +63 -0
  64. package/dist/index.js +65 -0
  65. package/dist/list-id-backfill.d.ts +83 -0
  66. package/dist/list-id-backfill.js +132 -0
  67. package/dist/mailbox-cursor-rebuild.d.ts +74 -0
  68. package/dist/mailbox-cursor-rebuild.js +71 -0
  69. package/{src/mailbox-cursor.ts → dist/mailbox-cursor.d.ts} +20 -98
  70. package/dist/mailbox-cursor.js +102 -0
  71. package/dist/mailbox-intent.d.ts +41 -0
  72. package/{src/mailbox-intent.ts → dist/mailbox-intent.js} +39 -63
  73. package/dist/mailbox-management.d.ts +240 -0
  74. package/dist/mailbox-management.js +576 -0
  75. package/dist/mailbox-presence.d.ts +52 -0
  76. package/{src/mailbox-presence.ts → dist/mailbox-presence.js} +19 -30
  77. package/dist/mailbox-queue.d.ts +126 -0
  78. package/dist/mailbox-queue.js +214 -0
  79. package/dist/mailbox-sync.d.ts +93 -0
  80. package/dist/mailbox-sync.js +490 -0
  81. package/dist/mailbox-upstream.d.ts +38 -0
  82. package/{src/mailbox-upstream.ts → dist/mailbox-upstream.js} +37 -47
  83. package/dist/message-category.d.ts +51 -0
  84. package/{src/message-category.ts → dist/message-category.js} +28 -71
  85. package/dist/message-move.d.ts +285 -0
  86. package/dist/message-move.js +858 -0
  87. package/dist/message-parser.d.ts +14 -0
  88. package/dist/message-parser.js +17 -0
  89. package/{src/message-presence.ts → dist/message-presence.d.ts} +1 -13
  90. package/dist/message-presence.js +29 -0
  91. package/dist/message-read.d.ts +51 -0
  92. package/dist/message-read.js +31 -0
  93. package/dist/message-sync.d.ts +345 -0
  94. package/dist/message-sync.js +1358 -0
  95. package/dist/mime-walker.d.ts +102 -0
  96. package/dist/mime-walker.js +198 -0
  97. package/dist/organize-job.d.ts +20 -0
  98. package/dist/organize-job.js +37 -0
  99. package/dist/outbox-attachment-content.d.ts +37 -0
  100. package/dist/outbox-attachment-content.js +50 -0
  101. package/dist/outbox-attachment-filename.d.ts +19 -0
  102. package/dist/outbox-attachment-filename.js +64 -0
  103. package/dist/outbox-attachment.d.ts +148 -0
  104. package/dist/outbox-attachment.js +273 -0
  105. package/dist/outbox-queue.d.ts +73 -0
  106. package/dist/outbox-queue.js +284 -0
  107. package/{src/outbox-status.ts → dist/outbox-status.d.ts} +1 -6
  108. package/dist/outbox-status.js +18 -0
  109. package/dist/ownership.d.ts +24 -0
  110. package/dist/ownership.js +83 -0
  111. package/dist/placement-move-terminal.d.ts +67 -0
  112. package/dist/placement-move-terminal.js +88 -0
  113. package/dist/placement-move.d.ts +83 -0
  114. package/dist/placement-move.js +216 -0
  115. package/{src/placement-settled.ts → dist/placement-settled.d.ts} +19 -50
  116. package/dist/placement-settled.js +75 -0
  117. package/dist/quarantine.d.ts +95 -0
  118. package/dist/quarantine.js +131 -0
  119. package/dist/refusal.d.ts +31 -0
  120. package/dist/refusal.js +3 -0
  121. package/{src/restore-source-placement.ts → dist/restore-source-placement.d.ts} +29 -101
  122. package/dist/restore-source-placement.js +79 -0
  123. package/dist/search-text-backfill.d.ts +15 -0
  124. package/dist/search-text-backfill.js +33 -0
  125. package/dist/snippet.d.ts +61 -0
  126. package/dist/snippet.js +129 -0
  127. package/dist/spam-report.d.ts +86 -0
  128. package/dist/spam-report.js +241 -0
  129. package/dist/sync-warning.d.ts +21 -0
  130. package/dist/sync-warning.js +26 -0
  131. package/dist/sync-watermarks.d.ts +138 -0
  132. package/dist/sync-watermarks.js +205 -0
  133. package/dist/test-helpers/fake-imap.d.ts +151 -0
  134. package/dist/test-helpers/fake-imap.js +508 -0
  135. package/dist/test-helpers/folder-roles.d.ts +34 -0
  136. package/dist/test-helpers/folder-roles.js +47 -0
  137. package/dist/test-helpers/mime-tree-generator.d.ts +82 -0
  138. package/dist/test-helpers/mime-tree-generator.js +329 -0
  139. package/dist/test-helpers/test-store.d.ts +69 -0
  140. package/dist/test-helpers/test-store.js +193 -0
  141. package/dist/text/normalizer.d.ts +20 -0
  142. package/dist/text/normalizer.js +102 -0
  143. package/dist/thread-message-rows.d.ts +69 -0
  144. package/dist/thread-message-rows.js +40 -0
  145. package/dist/thread-root.d.ts +7 -0
  146. package/dist/thread-root.js +25 -0
  147. package/dist/types.d.ts +343 -0
  148. package/dist/types.js +19 -0
  149. package/package.json +86 -73
  150. package/src/account-credentials.ts +0 -210
  151. package/src/adversarial-mime.e2e.test.ts +0 -307
  152. package/src/attribute-mapper.test.ts +0 -104
  153. package/src/attribute-mapper.ts +0 -148
  154. package/src/body-parse.test.ts +0 -55
  155. package/src/body-part-mapper.property.test.ts +0 -354
  156. package/src/body-part-mapper.ts +0 -360
  157. package/src/body-sync-auto-read-writeonce.test.ts +0 -219
  158. package/src/body-sync-calendar-suggestion.test.ts +0 -433
  159. package/src/body-sync-category-denormalize.test.ts +0 -352
  160. package/src/body-sync-category-override.test.ts +0 -501
  161. package/src/body-sync-category-writeonce.test.ts +0 -234
  162. package/src/body-sync-filter.test.ts +0 -237
  163. package/src/body-sync-message-gone.test.ts +0 -139
  164. package/src/body-sync-placement-flags.test.ts +0 -347
  165. package/src/body-sync-placement-writeonce.test.ts +0 -337
  166. package/src/body-sync-quarantine.test.ts +0 -276
  167. package/src/body-sync-queue.test.ts +0 -124
  168. package/src/body-sync-queue.ts +0 -131
  169. package/src/body-sync-resync.test.ts +0 -605
  170. package/src/body-sync-terminal.test.ts +0 -310
  171. package/src/body-sync-terminal.ts +0 -176
  172. package/src/body-sync-unclassifiable-once.test.ts +0 -260
  173. package/src/body-sync-unsubscribed-auto-read.test.ts +0 -176
  174. package/src/body-sync.ts +0 -1970
  175. package/src/calendar-parts.ts +0 -41
  176. package/src/classification-backfill.test.ts +0 -615
  177. package/src/classification-backfill.ts +0 -363
  178. package/src/classify-message.test.ts +0 -205
  179. package/src/classify-message.ts +0 -116
  180. package/src/connection-factory.ts +0 -102
  181. package/src/connection-test.ts +0 -208
  182. package/src/credentials.test.ts +0 -181
  183. package/src/external-move.test.ts +0 -369
  184. package/src/external-move.ts +0 -272
  185. package/src/filters/anchor-drift.ts +0 -55
  186. package/src/filters/filter-config.test.ts +0 -229
  187. package/src/filters/filter-config.ts +0 -69
  188. package/src/filters/list-id.test.ts +0 -50
  189. package/src/filters/match.test.ts +0 -429
  190. package/src/filters/match.ts +0 -274
  191. package/src/filters/message-embedder.test.ts +0 -39
  192. package/src/filters/message-embedder.ts +0 -33
  193. package/src/filters/pipeline.test.ts +0 -330
  194. package/src/filters/pipeline.ts +0 -284
  195. package/src/flag-push-terminal.test.ts +0 -209
  196. package/src/flag-push.test.ts +0 -408
  197. package/src/flag-push.ts +0 -198
  198. package/src/flag-queue-stars.test.ts +0 -171
  199. package/src/flag-queue.ts +0 -509
  200. package/src/folder-placement.ts +0 -56
  201. package/src/heuristics/authenticationResults.test.ts +0 -189
  202. package/src/heuristics/authenticationResults.ts +0 -212
  203. package/src/heuristics/classifyByHeaders.test.ts +0 -889
  204. package/src/heuristics/classifyByHeaders.ts +0 -467
  205. package/src/heuristics/classifyPlacement.test.ts +0 -366
  206. package/src/heuristics/classifyPlacement.ts +0 -211
  207. package/src/heuristics/classifyRealMail.test.ts +0 -242
  208. package/src/heuristics/machineSenders.test.ts +0 -101
  209. package/src/heuristics/senderMismatch.test.ts +0 -346
  210. package/src/heuristics/senderMismatch.ts +0 -259
  211. package/src/imapflow-connect-retry.test.ts +0 -134
  212. package/src/imapflow-connection.e2e.test.ts +0 -335
  213. package/src/imapflow-connection.integ.test.ts +0 -316
  214. package/src/imapflow-connection.test.ts +0 -469
  215. package/src/imapflow-connection.ts +0 -1475
  216. package/src/index.ts +0 -350
  217. package/src/list-id-backfill.test.ts +0 -573
  218. package/src/list-id-backfill.ts +0 -274
  219. package/src/mailbox-cursor-rebuild.test.ts +0 -171
  220. package/src/mailbox-cursor-rebuild.ts +0 -146
  221. package/src/mailbox-cursor.test.ts +0 -249
  222. package/src/mailbox-delete-intent.test.ts +0 -246
  223. package/src/mailbox-management.test.ts +0 -356
  224. package/src/mailbox-management.ts +0 -839
  225. package/src/mailbox-presence.test.ts +0 -72
  226. package/src/mailbox-queue.ts +0 -415
  227. package/src/mailbox-rename-intent.test.ts +0 -389
  228. package/src/mailbox-rename-subtree.test.ts +0 -664
  229. package/src/mailbox-sync.test.ts +0 -848
  230. package/src/mailbox-sync.ts +0 -724
  231. package/src/message-delete-junk-reconcile.test.ts +0 -117
  232. package/src/message-delete-no-trash.test.ts +0 -245
  233. package/src/message-delete-unsettled-placement.test.ts +0 -396
  234. package/src/message-move-copy.test.ts +0 -562
  235. package/src/message-move-empty-trash.test.ts +0 -326
  236. package/src/message-move-same-mailbox.test.ts +0 -139
  237. package/src/message-move-unsettled-placement.test.ts +0 -378
  238. package/src/message-move.ts +0 -1348
  239. package/src/message-parser.ts +0 -26
  240. package/src/message-presence.test.ts +0 -50
  241. package/src/message-sync-address-spoof.test.ts +0 -293
  242. package/src/message-sync-category.test.ts +0 -142
  243. package/src/message-sync-changedsince.test.ts +0 -840
  244. package/src/message-sync-flags.test.ts +0 -158
  245. package/src/message-sync-junk-harvest.test.ts +0 -244
  246. package/src/message-sync-quarantine.test.ts +0 -416
  247. package/src/message-sync-repoint.test.ts +0 -419
  248. package/src/message-sync.ts +0 -1963
  249. package/src/mime-walker.test.ts +0 -311
  250. package/src/mime-walker.ts +0 -305
  251. package/src/outbox-attachment-content.test.ts +0 -153
  252. package/src/outbox-attachment-content.ts +0 -104
  253. package/src/outbox-attachment-filename.test.ts +0 -132
  254. package/src/outbox-attachment-filename.ts +0 -76
  255. package/src/outbox-attachment.test.ts +0 -738
  256. package/src/outbox-attachment.ts +0 -536
  257. package/src/outbox-failed-recovery.test.ts +0 -370
  258. package/src/outbox-queue-settled-row.test.ts +0 -140
  259. package/src/outbox-queue.test.ts +0 -291
  260. package/src/outbox-queue.ts +0 -508
  261. package/src/pass-through-unit-of-work.ts +0 -17
  262. package/src/placement-move-terminal.test.ts +0 -300
  263. package/src/placement-move-terminal.ts +0 -148
  264. package/src/placement-move-unsettled.test.ts +0 -275
  265. package/src/placement-move.ts +0 -336
  266. package/src/placement-settled.test.ts +0 -147
  267. package/src/placement-transition-race.test.ts +0 -482
  268. package/src/quarantine.test.ts +0 -191
  269. package/src/quarantine.ts +0 -228
  270. package/src/snippet.test.ts +0 -213
  271. package/src/snippet.ts +0 -152
  272. package/src/spam-report.test.ts +0 -801
  273. package/src/spam-report.ts +0 -357
  274. package/src/stale-message-reconcile.test.ts +0 -82
  275. package/src/stale-message-reconcile.ts +0 -55
  276. package/src/sync-watermarks.test.ts +0 -409
  277. package/src/sync-watermarks.ts +0 -319
  278. package/src/system-flag-wire-format.test.ts +0 -77
  279. package/src/test-helpers/folder-roles.ts +0 -66
  280. package/src/test-helpers/isolated-mailbox.ts +0 -44
  281. package/src/test-helpers/mailfuzz-connection.ts +0 -27
  282. package/src/test-helpers/mime-tree-generator.ts +0 -461
  283. package/src/text/normalizer.test.ts +0 -223
  284. package/src/text/normalizer.ts +0 -140
  285. package/src/thread-message-rows.ts +0 -77
  286. package/src/types/mailparser-augment.d.ts +0 -15
  287. package/src/types/natural-porter-stemmer.d.ts +0 -50
  288. package/src/types/stopword.d.ts +0 -69
  289. package/src/types.ts +0 -371
  290. package/test/fixtures/mime-adversarial-e2e/01-octet-stream-pdf.eml +0 -21
  291. package/test/fixtures/mime-adversarial-e2e/02-nested-related-cid-image.eml +0 -31
  292. package/test/fixtures/mime-adversarial-e2e/03-calendar-invite.eml +0 -33
  293. package/test/fixtures/mime-adversarial-e2e/04-forwarded-rfc822-with-attachments.eml +0 -42
  294. package/test/fixtures/mime-adversarial-e2e/05-tnef-winmail.eml +0 -25
  295. package/test/fixtures/mime-adversarial-e2e/06-8bit-non-ascii.eml +0 -15
  296. package/test/fixtures/mime-adversarial-e2e/07-quoted-printable-soft-breaks.eml +0 -17
  297. package/test/fixtures/mime-adversarial-e2e/08-alternative-empty-text.eml +0 -18
  298. package/tsconfig.json +0 -8
@@ -1,102 +0,0 @@
1
- /**
2
- * Connection factory for IMAP implementations
3
- *
4
- * Uses ImapFlow for all IMAP connections.
5
- */
6
-
7
- import {
8
- createImapFlowConnectionFromAccount,
9
- createImapFlowConnectionWithCredentials,
10
- ImapFlowConnection,
11
- } from "./imapflow-connection.js";
12
- import type {
13
- IImapConnection,
14
- ImapConnectionConfig,
15
- MailCredentials,
16
- } from "./types.js";
17
-
18
- /**
19
- * Create an IMAP connection
20
- */
21
- export const createConnection = (
22
- config: ImapConnectionConfig,
23
- ): IImapConnection => {
24
- return new ImapFlowConnection(config);
25
- };
26
-
27
- /**
28
- * Create an IMAP connection from account data using password credentials.
29
- */
30
- export const createConnectionFromAccount = (
31
- account: {
32
- imapHost: string;
33
- imapPort: number;
34
- imapTls: boolean;
35
- username: string;
36
- },
37
- password: string,
38
- ): IImapConnection => {
39
- return createImapFlowConnectionFromAccount(account, password);
40
- };
41
-
42
- /**
43
- * Create an IMAP connection from account data using a MailCredentials union.
44
- * Use this instead of createConnectionFromAccount for OAuth accounts.
45
- */
46
- export const createConnectionWithCredentials = (
47
- account: {
48
- imapHost: string;
49
- imapPort: number;
50
- imapTls: boolean;
51
- username: string;
52
- },
53
- credentials: MailCredentials,
54
- ): IImapConnection => {
55
- return createImapFlowConnectionWithCredentials(account, credentials);
56
- };
57
-
58
- /**
59
- * Managed connection factory that caches and reuses a single connection.
60
- *
61
- * The factory handles connection lifecycle - callers should not disconnect.
62
- * Call close() on the factory to disconnect when done.
63
- */
64
- export interface ManagedConnectionFactory {
65
- /** Get the cached connection (creates on first call) */
66
- getConnection(): IImapConnection;
67
- /** Disconnect and cleanup */
68
- close(): Promise<void>;
69
- }
70
-
71
- /**
72
- * Create a managed connection factory that caches a single connection.
73
- *
74
- * @example
75
- * ```typescript
76
- * const factory = createManagedConnectionFactory(config);
77
- * const conn = factory.getConnection();
78
- * await conn.connect();
79
- * // ... reuse conn via factory.getConnection() ...
80
- * await factory.close(); // Disconnect when done
81
- * ```
82
- */
83
- export const createManagedConnectionFactory = (
84
- config: ImapConnectionConfig,
85
- ): ManagedConnectionFactory => {
86
- let connection: ImapFlowConnection | null = null;
87
-
88
- return {
89
- getConnection: () => {
90
- if (!connection) {
91
- connection = new ImapFlowConnection(config);
92
- }
93
- return connection;
94
- },
95
- close: async () => {
96
- if (connection) {
97
- await connection.disconnect();
98
- connection = null;
99
- }
100
- },
101
- };
102
- };
@@ -1,208 +0,0 @@
1
- /**
2
- * Connection testing utilities for IMAP and SMTP servers
3
- *
4
- * Used to validate credentials before saving account configuration.
5
- */
6
-
7
- import { createConnection as createNetConnection } from "node:net";
8
- import { connect as tlsConnect } from "node:tls";
9
- import { createTransport } from "nodemailer";
10
- import type { MailCredentials } from "./types.js";
11
- import { MailConnectionError } from "./types.js";
12
-
13
- export interface TestResult {
14
- success: boolean;
15
- error?: string;
16
- }
17
-
18
- export interface ImapTestConfig {
19
- host: string;
20
- port: number;
21
- secure: boolean;
22
- startTls?: boolean;
23
- credentials: MailCredentials;
24
- user: string;
25
- }
26
-
27
- export interface SmtpTestConfig {
28
- host: string;
29
- port: number;
30
- secure: boolean;
31
- credentials: MailCredentials;
32
- user: string;
33
- }
34
-
35
- // TODO(#468): Wire OAuth testConnection once authType/oauthRefreshTokenHash are
36
- // available on Account. For OAuth accounts, mint a token via MailOAuthService
37
- // before calling testImapConnection / testSmtpConnection, and map
38
- // RefreshTokenError(reauth-required) to MailConnectionError("auth", ...).
39
-
40
- const TEST_TIMEOUT_MS = 12_000;
41
-
42
- /**
43
- * Build nodemailer auth object from credentials.
44
- */
45
- const buildSmtpAuth = (
46
- user: string,
47
- credentials: MailCredentials,
48
- ):
49
- | { user: string; pass: string }
50
- | { type: "OAuth2"; user: string; accessToken: string } => {
51
- if (credentials.kind === "password") {
52
- return { user, pass: credentials.password };
53
- }
54
- return {
55
- type: "OAuth2" as const,
56
- user,
57
- accessToken: credentials.accessToken,
58
- };
59
- };
60
-
61
- /**
62
- * Test IMAP connection with provided credentials.
63
- *
64
- * Uses a raw socket to send AUTH PLAIN directly — bypasses the CAPABILITY
65
- * round-trip that IMAP clients typically perform first, avoiding deadlocks
66
- * with servers that pipeline their tagged responses.
67
- *
68
- * Supports plaintext, direct TLS (secure: true), and STARTTLS (startTls: true).
69
- */
70
- export const testImapConnection = async (
71
- config: ImapTestConfig,
72
- ): Promise<TestResult> => {
73
- if (config.credentials.kind !== "password") {
74
- return { success: false, error: "OAuth not supported for connection test" };
75
- }
76
-
77
- const { user, credentials } = config;
78
- const authPlain = Buffer.from(
79
- `\x00${user}\x00${credentials.password}`,
80
- ).toString("base64");
81
-
82
- return new Promise((resolve) => {
83
- let settled = false;
84
- const done = (result: TestResult) => {
85
- if (settled) return;
86
- settled = true;
87
- clearTimeout(timeoutId);
88
- resolve(result);
89
- };
90
-
91
- const timeoutId = setTimeout(
92
- () => done({ success: false, error: "Connection timed out" }),
93
- TEST_TIMEOUT_MS,
94
- );
95
-
96
- let buf = "";
97
- let phase: "greeting" | "starttls" | "tls-upgrade" | "auth" | "done" =
98
- "greeting";
99
- // biome-ignore lint/suspicious/noExplicitAny: socket type varies between net.Socket and tls.TLSSocket
100
- let sock: any;
101
-
102
- const onData = (chunk: Buffer) => {
103
- buf += chunk.toString();
104
- const lines = buf.split("\r\n");
105
- buf = lines.pop() ?? "";
106
-
107
- for (const line of lines) {
108
- if (phase === "greeting" && /^\* OK/i.test(line)) {
109
- if (config.startTls) {
110
- phase = "starttls";
111
- sock.write("A001 STARTTLS\r\n");
112
- } else {
113
- phase = "auth";
114
- sock.write(`A001 AUTHENTICATE PLAIN ${authPlain}\r\n`);
115
- }
116
- } else if (phase === "starttls" && /^A001 OK/i.test(line)) {
117
- phase = "tls-upgrade";
118
- // Upgrade socket to TLS in place
119
- const tlsSock = tlsConnect({
120
- socket: sock,
121
- host: config.host,
122
- rejectUnauthorized: false,
123
- });
124
- tlsSock.on("data", onData);
125
- tlsSock.on("error", onError);
126
- tlsSock.once("secureConnect", () => {
127
- phase = "auth";
128
- sock = tlsSock;
129
- sock.write(`A002 AUTHENTICATE PLAIN ${authPlain}\r\n`);
130
- });
131
- } else if (
132
- phase === "auth" &&
133
- (/^A00[12] OK/i.test(line) || /^A001 OK/i.test(line))
134
- ) {
135
- phase = "done";
136
- sock.write("A099 LOGOUT\r\n");
137
- done({ success: true });
138
- } else if (
139
- phase === "auth" &&
140
- (/^A00[12] NO/i.test(line) || /^A00[12] BAD/i.test(line))
141
- ) {
142
- phase = "done";
143
- sock.destroy();
144
- done({ success: false, error: "Authentication failed" });
145
- }
146
- }
147
- };
148
-
149
- const onError = (err: Error) => {
150
- done({ success: false, error: err.message });
151
- };
152
-
153
- if (config.secure) {
154
- sock = tlsConnect({
155
- host: config.host,
156
- port: config.port,
157
- rejectUnauthorized: false,
158
- });
159
- } else {
160
- sock = createNetConnection({ host: config.host, port: config.port });
161
- }
162
-
163
- sock.on("data", onData);
164
- sock.on("error", onError);
165
- });
166
- };
167
-
168
- /**
169
- * Test SMTP connection with provided credentials
170
- *
171
- * Uses nodemailer's verify() method to test authentication.
172
- */
173
- export const testSmtpConnection = async (
174
- config: SmtpTestConfig,
175
- ): Promise<TestResult> => {
176
- const transport = createTransport({
177
- host: config.host,
178
- port: config.port,
179
- secure: config.secure,
180
- auth: buildSmtpAuth(config.user, config.credentials),
181
- connectionTimeout: TEST_TIMEOUT_MS,
182
- greetingTimeout: TEST_TIMEOUT_MS,
183
- socketTimeout: TEST_TIMEOUT_MS,
184
- });
185
-
186
- const attempt = transport
187
- .verify()
188
- .then(() => ({ success: true as const }))
189
- .catch((error: unknown) => {
190
- if (error instanceof MailConnectionError) {
191
- return { success: false as const, error: error.message };
192
- }
193
- return {
194
- success: false as const,
195
- error: error instanceof Error ? error.message : "Connection failed",
196
- };
197
- })
198
- .finally(() => transport.close());
199
-
200
- const timeout = new Promise<TestResult>((resolve) =>
201
- setTimeout(() => {
202
- transport.close();
203
- resolve({ success: false, error: "Connection timed out" });
204
- }, TEST_TIMEOUT_MS),
205
- );
206
-
207
- return Promise.race([attempt, timeout]);
208
- };
@@ -1,181 +0,0 @@
1
- /**
2
- * Unit tests for MailCredentials union and MailConnectionError.
3
- *
4
- * Covers:
5
- * 1. Auth object construction — imapflow auth shape for each credential kind
6
- * 2. Exhaustiveness — TypeScript catches missing cases via `never`
7
- * 3. Token-leak assertion — accessToken never in serialized error
8
- * 4. Error classification — createImapFlowConnectionFromAccount wires password
9
- */
10
-
11
- import assert from "node:assert/strict";
12
- import { describe, it } from "node:test";
13
- import { inspect } from "node:util";
14
- import { MailConnectionError, type MailCredentials } from "./types.js";
15
-
16
- // ---------------------------------------------------------------------------
17
- // Helper: build the same imapflow auth object that imapflow-connection.ts uses
18
- // (extracted here so the test doesn't import the whole class)
19
- // ---------------------------------------------------------------------------
20
-
21
- type ImapAuth =
22
- | { user: string; pass: string }
23
- | { user: string; accessToken: string };
24
-
25
- const buildImapAuth = (
26
- user: string,
27
- credentials: MailCredentials,
28
- ): ImapAuth => {
29
- if (credentials.kind === "password") {
30
- return { user, pass: credentials.password };
31
- }
32
- if (credentials.kind === "accessToken") {
33
- return { user, accessToken: credentials.accessToken };
34
- }
35
- // Exhaustiveness check — TypeScript will error here if a new union member
36
- // is added without updating this function.
37
- const _exhaustive: never = credentials;
38
- throw new Error(`Unknown credential kind: ${JSON.stringify(_exhaustive)}`);
39
- };
40
-
41
- // ---------------------------------------------------------------------------
42
- // Helper: build the same nodemailer auth object that smtp-client.ts uses
43
- // ---------------------------------------------------------------------------
44
-
45
- type SmtpAuth =
46
- | { user: string; pass: string }
47
- | { type: "OAuth2"; user: string; accessToken: string };
48
-
49
- const buildSmtpAuth = (
50
- user: string,
51
- credentials: MailCredentials,
52
- ): SmtpAuth => {
53
- if (credentials.kind === "password") {
54
- return { user, pass: credentials.password };
55
- }
56
- if (credentials.kind === "accessToken") {
57
- return {
58
- type: "OAuth2" as const,
59
- user,
60
- accessToken: credentials.accessToken,
61
- };
62
- }
63
- // Exhaustiveness check
64
- const _exhaustive: never = credentials;
65
- throw new Error(`Unknown credential kind: ${JSON.stringify(_exhaustive)}`);
66
- };
67
-
68
- // ---------------------------------------------------------------------------
69
- // Tests
70
- // ---------------------------------------------------------------------------
71
-
72
- describe("MailCredentials — imapflow auth shape", () => {
73
- it("password kind → { user, pass }", () => {
74
- const creds: MailCredentials = { kind: "password", password: "s3cr3t" };
75
- const auth = buildImapAuth("alice@example.com", creds);
76
-
77
- assert.deepEqual(auth, { user: "alice@example.com", pass: "s3cr3t" });
78
- assert.ok(
79
- !("accessToken" in auth),
80
- "no accessToken field for password auth",
81
- );
82
- });
83
-
84
- it("accessToken kind → { user, accessToken }", () => {
85
- const token = "ya29.super-secret-token";
86
- const creds: MailCredentials = { kind: "accessToken", accessToken: token };
87
- const auth = buildImapAuth("alice@example.com", creds);
88
-
89
- assert.deepEqual(auth, {
90
- user: "alice@example.com",
91
- accessToken: token,
92
- });
93
- assert.ok(!("pass" in auth), "no pass field for accessToken auth");
94
- });
95
- });
96
-
97
- describe("MailCredentials — nodemailer auth shape", () => {
98
- it("password kind → { user, pass }", () => {
99
- const creds: MailCredentials = { kind: "password", password: "s3cr3t" };
100
- const auth = buildSmtpAuth("alice@example.com", creds);
101
-
102
- assert.deepEqual(auth, { user: "alice@example.com", pass: "s3cr3t" });
103
- assert.ok(!("type" in auth), "no OAuth2 type field for password auth");
104
- });
105
-
106
- it("accessToken kind → { type: 'OAuth2', user, accessToken }", () => {
107
- const token = "ya29.super-secret-token";
108
- const creds: MailCredentials = { kind: "accessToken", accessToken: token };
109
- const auth = buildSmtpAuth("alice@example.com", creds);
110
-
111
- assert.deepEqual(auth, {
112
- type: "OAuth2",
113
- user: "alice@example.com",
114
- accessToken: token,
115
- });
116
- assert.ok(!("pass" in auth), "no pass field for OAuth2 auth");
117
- });
118
- });
119
-
120
- describe("MailConnectionError", () => {
121
- it("stores kind='auth'", () => {
122
- const err = new MailConnectionError("auth", "IMAP authentication failed");
123
- assert.equal(err.kind, "auth");
124
- assert.equal(err.name, "MailConnectionError");
125
- assert.equal(err.message, "IMAP authentication failed");
126
- });
127
-
128
- it("stores kind='network'", () => {
129
- const err = new MailConnectionError(
130
- "network",
131
- "IMAP connection failed: ECONNREFUSED",
132
- );
133
- assert.equal(err.kind, "network");
134
- });
135
-
136
- it("token-leak: accessToken must NOT appear in inspected MailConnectionError", () => {
137
- const secretToken = "ya29.A0ARrdaM_very_secret_access_token_12345";
138
-
139
- // Simulate what connection code does: auth fails for an OAuth account.
140
- // The error message must NOT mention the token, only "auth failed".
141
- const err = new MailConnectionError(
142
- "auth",
143
- "IMAP authentication failed",
144
- // The cause should also never include the token
145
- );
146
-
147
- // util.inspect walks the full error including the cause chain, so this
148
- // proves no token leaks through name/message/stack OR cause.
149
- const serialized = inspect(err, { depth: null });
150
-
151
- assert.ok(
152
- !serialized.includes(secretToken),
153
- `MailConnectionError must not contain the access token — got: ${serialized.slice(0, 200)}`,
154
- );
155
- });
156
-
157
- it("token-leak: accessToken in cause does NOT bubble into error.message", () => {
158
- // Even if the underlying library error somehow includes the token text,
159
- // our MailConnectionError message must not re-echo it.
160
- const secretToken = "ya29.A0ARrdaM_very_secret_access_token_CAUSE";
161
- const underlyingError = new Error(`AUTH failed token=${secretToken}`);
162
-
163
- const err = new MailConnectionError(
164
- "auth",
165
- "IMAP authentication failed",
166
- underlyingError,
167
- );
168
-
169
- // The message itself must be token-free
170
- assert.ok(
171
- !err.message.includes(secretToken),
172
- "error.message must not contain the access token",
173
- );
174
- });
175
-
176
- it("is instanceof Error", () => {
177
- const err = new MailConnectionError("auth", "test");
178
- assert.ok(err instanceof Error);
179
- assert.ok(err instanceof MailConnectionError);
180
- });
181
- });