mcp-twake-mail 0.1.0 → 2.0.0

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 (86) hide show
  1. package/README.md +168 -74
  2. package/build/cli/commands/setup.js +54 -12
  3. package/build/cli/commands/setup.js.map +1 -1
  4. package/build/cli/prompts/setup-wizard.d.ts +33 -1
  5. package/build/cli/prompts/setup-wizard.js +134 -2
  6. package/build/cli/prompts/setup-wizard.js.map +1 -1
  7. package/build/cli/prompts/setup-wizard.test.d.ts +1 -0
  8. package/build/cli/prompts/setup-wizard.test.js +161 -0
  9. package/build/cli/prompts/setup-wizard.test.js.map +1 -0
  10. package/build/config/schema.d.ts +2 -0
  11. package/build/config/schema.js +3 -0
  12. package/build/config/schema.js.map +1 -1
  13. package/build/discovery/dns-srv.d.ts +18 -0
  14. package/build/discovery/dns-srv.js +60 -0
  15. package/build/discovery/dns-srv.js.map +1 -0
  16. package/build/discovery/dns-srv.test.d.ts +4 -0
  17. package/build/discovery/dns-srv.test.js +79 -0
  18. package/build/discovery/dns-srv.test.js.map +1 -0
  19. package/build/discovery/index.d.ts +9 -0
  20. package/build/discovery/index.js +13 -0
  21. package/build/discovery/index.js.map +1 -0
  22. package/build/discovery/oauth-discovery.d.ts +34 -0
  23. package/build/discovery/oauth-discovery.js +160 -0
  24. package/build/discovery/oauth-discovery.js.map +1 -0
  25. package/build/discovery/oauth-discovery.test.d.ts +1 -0
  26. package/build/discovery/oauth-discovery.test.js +198 -0
  27. package/build/discovery/oauth-discovery.test.js.map +1 -0
  28. package/build/discovery/orchestrator.d.ts +31 -0
  29. package/build/discovery/orchestrator.js +87 -0
  30. package/build/discovery/orchestrator.js.map +1 -0
  31. package/build/discovery/orchestrator.test.d.ts +4 -0
  32. package/build/discovery/orchestrator.test.js +242 -0
  33. package/build/discovery/orchestrator.test.js.map +1 -0
  34. package/build/discovery/types.d.ts +18 -0
  35. package/build/discovery/types.js +15 -0
  36. package/build/discovery/types.js.map +1 -0
  37. package/build/discovery/well-known.d.ts +21 -0
  38. package/build/discovery/well-known.js +52 -0
  39. package/build/discovery/well-known.js.map +1 -0
  40. package/build/discovery/well-known.test.d.ts +4 -0
  41. package/build/discovery/well-known.test.js +120 -0
  42. package/build/discovery/well-known.test.js.map +1 -0
  43. package/build/jmap/client.js +1 -1
  44. package/build/jmap/client.js.map +1 -1
  45. package/build/mcp/server.d.ts +5 -3
  46. package/build/mcp/server.js +11 -5
  47. package/build/mcp/server.js.map +1 -1
  48. package/build/mcp/tools/batch-operations.d.ts +10 -0
  49. package/build/mcp/tools/batch-operations.js +609 -0
  50. package/build/mcp/tools/batch-operations.js.map +1 -0
  51. package/build/mcp/tools/batch-operations.test.d.ts +1 -0
  52. package/build/mcp/tools/batch-operations.test.js +875 -0
  53. package/build/mcp/tools/batch-operations.test.js.map +1 -0
  54. package/build/mcp/tools/email-operations.js +355 -1
  55. package/build/mcp/tools/email-operations.js.map +1 -1
  56. package/build/mcp/tools/email-operations.test.js +5 -3
  57. package/build/mcp/tools/email-operations.test.js.map +1 -1
  58. package/build/mcp/tools/email-sending.d.ts +10 -1
  59. package/build/mcp/tools/email-sending.js +481 -14
  60. package/build/mcp/tools/email-sending.js.map +1 -1
  61. package/build/mcp/tools/index.d.ts +10 -1
  62. package/build/mcp/tools/index.js +7 -3
  63. package/build/mcp/tools/index.js.map +1 -1
  64. package/build/mcp/tools/mailbox.js +433 -1
  65. package/build/mcp/tools/mailbox.js.map +1 -1
  66. package/build/mcp/tools/mailbox.test.js +5 -2
  67. package/build/mcp/tools/mailbox.test.js.map +1 -1
  68. package/build/signature/converter.d.ts +2 -0
  69. package/build/signature/converter.js +23 -0
  70. package/build/signature/converter.js.map +1 -0
  71. package/build/signature/converter.test.d.ts +1 -0
  72. package/build/signature/converter.test.js +84 -0
  73. package/build/signature/converter.test.js.map +1 -0
  74. package/build/signature/index.d.ts +2 -0
  75. package/build/signature/index.js +3 -0
  76. package/build/signature/index.js.map +1 -0
  77. package/build/signature/loader.d.ts +6 -0
  78. package/build/signature/loader.js +31 -0
  79. package/build/signature/loader.js.map +1 -0
  80. package/build/signature/loader.test.d.ts +1 -0
  81. package/build/signature/loader.test.js +85 -0
  82. package/build/signature/loader.test.js.map +1 -0
  83. package/build/types/jmap.d.ts +23 -0
  84. package/docs/auto-discovery.md +210 -0
  85. package/docs/oidc-configuration.md +261 -0
  86. package/package.json +3 -1
@@ -3,6 +3,88 @@
3
3
  * Uses @inquirer/prompts for modern, TypeScript-first prompts.
4
4
  */
5
5
  import { input, select, password, confirm } from '@inquirer/prompts';
6
+ import { homedir } from 'node:os';
7
+ import { access, constants } from 'node:fs/promises';
8
+ /**
9
+ * Prompt for setup mode: auto-discover or manual configuration.
10
+ */
11
+ export async function promptSetupMode() {
12
+ return select({
13
+ message: 'Setup mode:',
14
+ choices: [
15
+ { value: 'auto', name: 'Auto-discover from email address (recommended)' },
16
+ { value: 'manual', name: 'Manual configuration' },
17
+ ],
18
+ });
19
+ }
20
+ /**
21
+ * Prompt for email address for auto-discovery.
22
+ */
23
+ export async function promptEmail() {
24
+ return input({
25
+ message: 'Email address:',
26
+ validate: (value) => {
27
+ // Basic email validation
28
+ if (!value.includes('@') || !value.split('@')[1]?.includes('.')) {
29
+ return 'Please enter a valid email address';
30
+ }
31
+ return true;
32
+ },
33
+ });
34
+ }
35
+ /**
36
+ * Prompt to confirm or edit discovered settings.
37
+ */
38
+ export async function promptConfirmDiscovery(settings) {
39
+ console.log('\nDiscovered settings:');
40
+ console.log(` JMAP URL: ${settings.jmapUrl}`);
41
+ if (settings.oidcIssuer) {
42
+ console.log(` OIDC Issuer: ${settings.oidcIssuer}`);
43
+ }
44
+ else {
45
+ console.log(' OIDC Issuer: Not discovered (will need manual entry)');
46
+ }
47
+ console.log('');
48
+ const useDiscovered = await confirm({
49
+ message: 'Use these settings?',
50
+ default: true,
51
+ });
52
+ if (useDiscovered) {
53
+ return { confirmed: true, ...settings };
54
+ }
55
+ // Allow editing
56
+ const jmapUrl = await input({
57
+ message: 'JMAP Session URL:',
58
+ default: settings.jmapUrl,
59
+ validate: (value) => {
60
+ try {
61
+ new URL(value);
62
+ return true;
63
+ }
64
+ catch {
65
+ return 'Please enter a valid URL';
66
+ }
67
+ },
68
+ });
69
+ const oidcIssuer = settings.oidcIssuer
70
+ ? await input({
71
+ message: 'OIDC Issuer URL:',
72
+ default: settings.oidcIssuer,
73
+ validate: (value) => {
74
+ if (!value)
75
+ return true; // Optional
76
+ try {
77
+ new URL(value);
78
+ return true;
79
+ }
80
+ catch {
81
+ return 'Please enter a valid URL';
82
+ }
83
+ },
84
+ })
85
+ : undefined;
86
+ return { confirmed: true, jmapUrl, oidcIssuer };
87
+ }
6
88
  /**
7
89
  * Prompt for JMAP session URL with validation.
8
90
  */
@@ -61,10 +143,10 @@ export async function promptBearerToken() {
61
143
  /**
62
144
  * Prompt for OIDC configuration.
63
145
  */
64
- export async function promptOidcAuth() {
146
+ export async function promptOidcAuth(defaultIssuer) {
65
147
  const issuer = await input({
66
148
  message: 'OIDC Issuer URL:',
67
- default: 'https://sso.linagora.com',
149
+ default: defaultIssuer || 'https://sso.linagora.com',
68
150
  validate: (value) => {
69
151
  try {
70
152
  new URL(value);
@@ -118,4 +200,54 @@ export async function promptServerName() {
118
200
  validate: (value) => /^[a-z0-9-]+$/.test(value) || 'Use lowercase letters, numbers, and hyphens only',
119
201
  });
120
202
  }
203
+ /**
204
+ * Prompt for default sender email address.
205
+ * Returns undefined if user skips configuration.
206
+ */
207
+ export async function promptDefaultFrom() {
208
+ const shouldConfigure = await confirm({
209
+ message: 'Configure default sender email address?',
210
+ default: true,
211
+ });
212
+ if (!shouldConfigure)
213
+ return undefined;
214
+ return input({
215
+ message: 'Default "from" email address:',
216
+ validate: (value) => {
217
+ // Simple email regex - Zod will do full validation
218
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
219
+ return emailRegex.test(value) || 'Please enter a valid email address';
220
+ },
221
+ });
222
+ }
223
+ /**
224
+ * Prompt for signature file path.
225
+ * Validates file exists and is readable.
226
+ * Returns undefined if user skips configuration.
227
+ */
228
+ export async function promptSignaturePath() {
229
+ const shouldConfigure = await confirm({
230
+ message: 'Configure email signature file?',
231
+ default: false,
232
+ });
233
+ if (!shouldConfigure)
234
+ return undefined;
235
+ return input({
236
+ message: 'Path to signature file (Markdown format):',
237
+ default: '~/.mcp-twake-mail/signature.md',
238
+ validate: async (value) => {
239
+ if (!value)
240
+ return 'Signature path cannot be empty';
241
+ // Expand ~ to home directory for validation
242
+ const expandedPath = value.replace(/^~/, homedir());
243
+ try {
244
+ await access(expandedPath, constants.R_OK);
245
+ return true;
246
+ }
247
+ catch {
248
+ return `File not found or not readable: ${expandedPath}`;
249
+ }
250
+ },
251
+ });
252
+ }
121
253
  //# sourceMappingURL=setup-wizard.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"setup-wizard.js","sourceRoot":"","sources":["../../../src/cli/prompts/setup-wizard.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"setup-wizard.js","sourceRoot":"","sources":["../../../src/cli/prompts/setup-wizard.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Tests for setup wizard prompts.
3
+ */
4
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
5
+ import { confirm, input } from '@inquirer/prompts';
6
+ import { access } from 'node:fs/promises';
7
+ import { promptDefaultFrom, promptSignaturePath } from './setup-wizard.js';
8
+ // Mock @inquirer/prompts
9
+ vi.mock('@inquirer/prompts', () => ({
10
+ confirm: vi.fn(),
11
+ input: vi.fn(),
12
+ select: vi.fn(),
13
+ password: vi.fn(),
14
+ }));
15
+ // Mock node:fs/promises
16
+ vi.mock('node:fs/promises', () => ({
17
+ access: vi.fn(),
18
+ constants: {
19
+ R_OK: 4,
20
+ },
21
+ }));
22
+ // Mock node:os
23
+ vi.mock('node:os', () => ({
24
+ homedir: vi.fn(() => '/home/testuser'),
25
+ }));
26
+ describe('promptDefaultFrom', () => {
27
+ beforeEach(() => {
28
+ vi.clearAllMocks();
29
+ });
30
+ it('returns undefined when user declines to configure', async () => {
31
+ vi.mocked(confirm).mockResolvedValueOnce(false);
32
+ const result = await promptDefaultFrom();
33
+ expect(result).toBeUndefined();
34
+ expect(confirm).toHaveBeenCalledWith({
35
+ message: 'Configure default sender email address?',
36
+ default: true,
37
+ });
38
+ expect(input).not.toHaveBeenCalled();
39
+ });
40
+ it('returns email when user configures and provides valid email', async () => {
41
+ vi.mocked(confirm).mockResolvedValueOnce(true);
42
+ vi.mocked(input).mockResolvedValueOnce('user@example.com');
43
+ const result = await promptDefaultFrom();
44
+ expect(result).toBe('user@example.com');
45
+ expect(confirm).toHaveBeenCalledOnce();
46
+ expect(input).toHaveBeenCalledWith({
47
+ message: 'Default "from" email address:',
48
+ validate: expect.any(Function),
49
+ });
50
+ });
51
+ it('validates email format - rejects invalid emails', async () => {
52
+ vi.mocked(confirm).mockResolvedValueOnce(true);
53
+ // Mock input to get the validate function
54
+ let validateFn;
55
+ vi.mocked(input).mockImplementationOnce((options) => {
56
+ validateFn = options.validate;
57
+ return Promise.resolve('valid@example.com');
58
+ });
59
+ await promptDefaultFrom();
60
+ expect(validateFn).toBeDefined();
61
+ if (validateFn) {
62
+ // Test invalid emails
63
+ expect(validateFn('invalid')).toBe('Please enter a valid email address');
64
+ expect(validateFn('invalid@')).toBe('Please enter a valid email address');
65
+ expect(validateFn('@example.com')).toBe('Please enter a valid email address');
66
+ expect(validateFn('user@example')).toBe('Please enter a valid email address');
67
+ // Test valid emails
68
+ expect(validateFn('user@example.com')).toBe(true);
69
+ expect(validateFn('test.user+tag@sub.example.org')).toBe(true);
70
+ }
71
+ });
72
+ });
73
+ describe('promptSignaturePath', () => {
74
+ beforeEach(() => {
75
+ vi.clearAllMocks();
76
+ });
77
+ it('returns undefined when user declines to configure', async () => {
78
+ vi.mocked(confirm).mockResolvedValueOnce(false);
79
+ const result = await promptSignaturePath();
80
+ expect(result).toBeUndefined();
81
+ expect(confirm).toHaveBeenCalledWith({
82
+ message: 'Configure email signature file?',
83
+ default: false,
84
+ });
85
+ expect(input).not.toHaveBeenCalled();
86
+ });
87
+ it('returns path when user configures and file exists', async () => {
88
+ vi.mocked(confirm).mockResolvedValueOnce(true);
89
+ vi.mocked(access).mockResolvedValueOnce(undefined);
90
+ vi.mocked(input).mockResolvedValueOnce('/path/to/signature.md');
91
+ const result = await promptSignaturePath();
92
+ expect(result).toBe('/path/to/signature.md');
93
+ expect(confirm).toHaveBeenCalledOnce();
94
+ expect(input).toHaveBeenCalledWith({
95
+ message: 'Path to signature file (Markdown format):',
96
+ default: '~/.mcp-twake-mail/signature.md',
97
+ validate: expect.any(Function),
98
+ });
99
+ });
100
+ it('validates file exists - shows error for missing file', async () => {
101
+ vi.mocked(confirm).mockResolvedValueOnce(true);
102
+ // Mock input to capture the validate function and test it separately
103
+ let validateFn;
104
+ vi.mocked(input).mockImplementationOnce((options) => {
105
+ validateFn = options.validate;
106
+ return Promise.resolve('/valid/path.md');
107
+ });
108
+ await promptSignaturePath();
109
+ expect(validateFn).toBeDefined();
110
+ if (validateFn) {
111
+ // Set up mock to reject for missing file
112
+ vi.mocked(access).mockReset();
113
+ vi.mocked(access).mockRejectedValue(new Error('ENOENT'));
114
+ // Test missing file
115
+ const result = await validateFn('/missing/file.md');
116
+ expect(typeof result).toBe('string');
117
+ expect(result).toMatch(/File not found or not readable/);
118
+ expect(access).toHaveBeenCalledTimes(1);
119
+ expect(access).toHaveBeenCalledWith('/missing/file.md', 4); // R_OK = 4
120
+ }
121
+ });
122
+ it('validates empty path is rejected', async () => {
123
+ vi.mocked(confirm).mockResolvedValueOnce(true);
124
+ // Mock input to get the validate function
125
+ let validateFn;
126
+ vi.mocked(input).mockImplementationOnce((options) => {
127
+ validateFn = options.validate;
128
+ return Promise.resolve('valid.md');
129
+ });
130
+ await promptSignaturePath();
131
+ expect(validateFn).toBeDefined();
132
+ if (validateFn) {
133
+ const result = await validateFn('');
134
+ expect(result).toBe('Signature path cannot be empty');
135
+ expect(access).not.toHaveBeenCalled();
136
+ }
137
+ });
138
+ it('expands ~ in path for validation', async () => {
139
+ vi.mocked(confirm).mockResolvedValueOnce(true);
140
+ // Mock input to capture the validate function
141
+ let validateFn;
142
+ vi.mocked(input).mockImplementationOnce((options) => {
143
+ validateFn = options.validate;
144
+ return Promise.resolve('~/signature.md');
145
+ });
146
+ await promptSignaturePath();
147
+ expect(validateFn).toBeDefined();
148
+ if (validateFn) {
149
+ // Clear previous calls and set up mock for the validation call
150
+ vi.mocked(access).mockClear();
151
+ vi.mocked(access).mockResolvedValueOnce(undefined);
152
+ await validateFn('~/signature.md');
153
+ // Verify access was called with expanded path (not with ~)
154
+ expect(access).toHaveBeenCalledTimes(1);
155
+ const calledPath = vi.mocked(access).mock.calls[0][0];
156
+ expect(calledPath).not.toContain('~');
157
+ expect(calledPath).toMatch(/^\/.*\/signature\.md$/);
158
+ }
159
+ });
160
+ });
161
+ //# sourceMappingURL=setup-wizard.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup-wizard.test.js","sourceRoot":"","sources":["../../../src/cli/prompts/setup-wizard.test.ts"],"names":[],"mappings":"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"}
@@ -23,6 +23,8 @@ export declare const envSchema: z.ZodObject<{
23
23
  debug: "debug";
24
24
  trace: "trace";
25
25
  }>>;
26
+ JMAP_DEFAULT_FROM: z.ZodOptional<z.ZodString>;
27
+ JMAP_SIGNATURE_PATH: z.ZodOptional<z.ZodString>;
26
28
  }, z.core.$strip>;
27
29
  export type Config = z.infer<typeof envSchema>;
28
30
  export declare function loadConfig(): Config;
@@ -27,6 +27,9 @@ export const envSchema = z
27
27
  JMAP_OIDC_LOCAL_PORT: z.coerce.number().optional(),
28
28
  JMAP_REQUEST_TIMEOUT: z.coerce.number().default(30000),
29
29
  LOG_LEVEL: z.enum(['fatal', 'error', 'warn', 'info', 'debug', 'trace']).default('info'),
30
+ // Email identity and signature configuration
31
+ JMAP_DEFAULT_FROM: z.string().email('JMAP_DEFAULT_FROM must be a valid email address').optional(),
32
+ JMAP_SIGNATURE_PATH: z.string().optional(),
30
33
  })
31
34
  .superRefine((data, ctx) => {
32
35
  // Conditional validation: basic auth requires username+password
@@ -1 +1 @@
1
- {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * DNS SRV resolution for JMAP server discovery
3
+ * RFC 8620 Section 2.2 - Service Discovery via DNS SRV
4
+ */
5
+ interface SrvRecord {
6
+ hostname: string;
7
+ port: number;
8
+ }
9
+ /**
10
+ * Resolve DNS SRV record for JMAP service
11
+ * Queries _jmap._tcp.{domain} and returns hostname/port of highest priority server
12
+ *
13
+ * @param domain - Email domain to query (e.g., "example.com")
14
+ * @param timeout - Timeout in milliseconds (default: 3000)
15
+ * @returns SrvRecord with hostname/port, or null if no record found
16
+ */
17
+ export declare function resolveSrvRecord(domain: string, timeout?: number): Promise<SrvRecord | null>;
18
+ export {};
@@ -0,0 +1,60 @@
1
+ /**
2
+ * DNS SRV resolution for JMAP server discovery
3
+ * RFC 8620 Section 2.2 - Service Discovery via DNS SRV
4
+ */
5
+ import { promises as dns } from 'node:dns';
6
+ /**
7
+ * Resolve DNS SRV record for JMAP service
8
+ * Queries _jmap._tcp.{domain} and returns hostname/port of highest priority server
9
+ *
10
+ * @param domain - Email domain to query (e.g., "example.com")
11
+ * @param timeout - Timeout in milliseconds (default: 3000)
12
+ * @returns SrvRecord with hostname/port, or null if no record found
13
+ */
14
+ export async function resolveSrvRecord(domain, timeout = 3000) {
15
+ const query = `_jmap._tcp.${domain}`;
16
+ try {
17
+ // Race DNS query against timeout
18
+ const records = await Promise.race([
19
+ dns.resolveSrv(query),
20
+ new Promise((_, reject) => setTimeout(() => reject(new Error('DNS timeout')), timeout)),
21
+ ]);
22
+ if (!records || records.length === 0) {
23
+ return null;
24
+ }
25
+ // Sort by priority (ascending) then weight (descending)
26
+ // Lower priority value = higher priority
27
+ // Higher weight value = more preferred among same priority
28
+ const sorted = records.sort((a, b) => {
29
+ if (a.priority !== b.priority) {
30
+ return a.priority - b.priority;
31
+ }
32
+ return b.weight - a.weight;
33
+ });
34
+ // Return first (highest priority/weight) record
35
+ const record = sorted[0];
36
+ return {
37
+ hostname: record.name,
38
+ port: record.port,
39
+ };
40
+ }
41
+ catch (error) {
42
+ // Handle DNS-specific errors gracefully
43
+ if (error &&
44
+ typeof error === 'object' &&
45
+ 'code' in error &&
46
+ (error.code === 'ENOTFOUND' || error.code === 'ENODATA')) {
47
+ // No DNS record exists - this is expected for many domains
48
+ return null;
49
+ }
50
+ // Log timeout and other errors, but return null for graceful fallback
51
+ if (error instanceof Error && error.message === 'DNS timeout') {
52
+ console.warn(`DNS SRV query timeout for ${query}`);
53
+ return null;
54
+ }
55
+ // Unexpected error - log but still return null to allow fallback
56
+ console.warn(`DNS SRV query failed for ${query}:`, error);
57
+ return null;
58
+ }
59
+ }
60
+ //# sourceMappingURL=dns-srv.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dns-srv.js","sourceRoot":"","sources":["../../src/discovery/dns-srv.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,IAAI,GAAG,EAAE,MAAM,UAAU,CAAC;AAO3C;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,MAAc,EACd,OAAO,GAAG,IAAI;IAEd,MAAM,KAAK,GAAG,cAAc,MAAM,EAAE,CAAC;IAErC,IAAI,CAAC;QACH,iCAAiC;QACjC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC;YACjC,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC;YACrB,IAAI,OAAO,CAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAC/B,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,aAAa,CAAC,CAAC,EAAE,OAAO,CAAC,CAC5D;SACF,CAAC,CAAC;QAEH,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACrC,OAAO,IAAI,CAAC;QACd,CAAC;QAED,wDAAwD;QACxD,yCAAyC;QACzC,2DAA2D;QAC3D,MAAM,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;YACnC,IAAI,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,QAAQ,EAAE,CAAC;gBAC9B,OAAO,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC;YACjC,CAAC;YACD,OAAO,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAAC;QAC7B,CAAC,CAAC,CAAC;QAEH,gDAAgD;QAChD,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QACzB,OAAO;YACL,QAAQ,EAAE,MAAM,CAAC,IAAI;YACrB,IAAI,EAAE,MAAM,CAAC,IAAI;SAClB,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,wCAAwC;QACxC,IACE,KAAK;YACL,OAAO,KAAK,KAAK,QAAQ;YACzB,MAAM,IAAI,KAAK;YACf,CAAC,KAAK,CAAC,IAAI,KAAK,WAAW,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,EACxD,CAAC;YACD,2DAA2D;YAC3D,OAAO,IAAI,CAAC;QACd,CAAC;QAED,sEAAsE;QACtE,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,OAAO,KAAK,aAAa,EAAE,CAAC;YAC9D,OAAO,CAAC,IAAI,CAAC,6BAA6B,KAAK,EAAE,CAAC,CAAC;YACnD,OAAO,IAAI,CAAC;QACd,CAAC;QAED,iEAAiE;QACjE,OAAO,CAAC,IAAI,CAAC,4BAA4B,KAAK,GAAG,EAAE,KAAK,CAAC,CAAC;QAC1D,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,4 @@
1
+ /**
2
+ * DNS SRV resolution tests
3
+ */
4
+ export {};
@@ -0,0 +1,79 @@
1
+ /**
2
+ * DNS SRV resolution tests
3
+ */
4
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
5
+ import { promises as dns } from 'node:dns';
6
+ import { resolveSrvRecord } from './dns-srv.js';
7
+ vi.mock('node:dns', () => ({
8
+ promises: {
9
+ resolveSrv: vi.fn(),
10
+ },
11
+ }));
12
+ describe('resolveSrvRecord', () => {
13
+ beforeEach(() => {
14
+ vi.clearAllMocks();
15
+ });
16
+ afterEach(() => {
17
+ vi.restoreAllMocks();
18
+ });
19
+ it('resolves DNS SRV record and returns hostname/port', async () => {
20
+ const mockRecords = [
21
+ { name: 'jmap.example.com', port: 443, priority: 10, weight: 10 },
22
+ ];
23
+ vi.mocked(dns.resolveSrv).mockResolvedValue(mockRecords);
24
+ const result = await resolveSrvRecord('example.com');
25
+ expect(result).toEqual({
26
+ hostname: 'jmap.example.com',
27
+ port: 443,
28
+ });
29
+ expect(dns.resolveSrv).toHaveBeenCalledWith('_jmap._tcp.example.com');
30
+ });
31
+ it('sorts by priority (ascending) then weight (descending)', async () => {
32
+ const mockRecords = [
33
+ { name: 'low-weight.example.com', port: 443, priority: 10, weight: 5 },
34
+ { name: 'high-priority.example.com', port: 443, priority: 20, weight: 10 },
35
+ { name: 'best.example.com', port: 443, priority: 10, weight: 10 },
36
+ ];
37
+ vi.mocked(dns.resolveSrv).mockResolvedValue(mockRecords);
38
+ const result = await resolveSrvRecord('example.com');
39
+ // Should return priority 10, weight 10 (best)
40
+ expect(result).toEqual({
41
+ hostname: 'best.example.com',
42
+ port: 443,
43
+ });
44
+ });
45
+ it('returns null when DNS record not found (ENOTFOUND)', async () => {
46
+ const error = new Error('DNS query failed');
47
+ error.code = 'ENOTFOUND';
48
+ vi.mocked(dns.resolveSrv).mockRejectedValue(error);
49
+ const result = await resolveSrvRecord('nonexistent.example.com');
50
+ expect(result).toBeNull();
51
+ });
52
+ it('returns null when DNS record has no data (ENODATA)', async () => {
53
+ const error = new Error('DNS query failed');
54
+ error.code = 'ENODATA';
55
+ vi.mocked(dns.resolveSrv).mockRejectedValue(error);
56
+ const result = await resolveSrvRecord('nodns.example.com');
57
+ expect(result).toBeNull();
58
+ });
59
+ it('returns null on timeout', async () => {
60
+ // Mock a slow DNS response (never resolves)
61
+ vi.mocked(dns.resolveSrv).mockImplementation(() => new Promise((resolve) => {
62
+ // Never resolves - will timeout
63
+ setTimeout(() => resolve([]), 10000);
64
+ }));
65
+ const result = await resolveSrvRecord('slow.example.com', 100);
66
+ expect(result).toBeNull();
67
+ });
68
+ it('returns null when no records returned', async () => {
69
+ vi.mocked(dns.resolveSrv).mockResolvedValue([]);
70
+ const result = await resolveSrvRecord('empty.example.com');
71
+ expect(result).toBeNull();
72
+ });
73
+ it('returns null on unexpected DNS error', async () => {
74
+ vi.mocked(dns.resolveSrv).mockRejectedValue(new Error('Network unreachable'));
75
+ const result = await resolveSrvRecord('error.example.com');
76
+ expect(result).toBeNull();
77
+ });
78
+ });
79
+ //# sourceMappingURL=dns-srv.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dns-srv.test.js","sourceRoot":"","sources":["../../src/discovery/dns-srv.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * JMAP server discovery module
3
+ * Exports DNS SRV, .well-known/jmap, OAuth discovery, and high-level orchestration
4
+ */
5
+ export { JmapDiscoveryResult, OidcDiscoveryResult, DiscoveryError, } from './types.js';
6
+ export { resolveSrvRecord } from './dns-srv.js';
7
+ export { fetchWellKnownJmap, verifyJmapUrl } from './well-known.js';
8
+ export { discoverOAuthFromResource, parseWwwAuthenticate, } from './oauth-discovery.js';
9
+ export { discoverFromEmail, extractDomain, FullDiscoveryResult, } from './orchestrator.js';
@@ -0,0 +1,13 @@
1
+ /**
2
+ * JMAP server discovery module
3
+ * Exports DNS SRV, .well-known/jmap, OAuth discovery, and high-level orchestration
4
+ */
5
+ // Types
6
+ export { DiscoveryError, } from './types.js';
7
+ // Low-level discovery functions
8
+ export { resolveSrvRecord } from './dns-srv.js';
9
+ export { fetchWellKnownJmap, verifyJmapUrl } from './well-known.js';
10
+ export { discoverOAuthFromResource, parseWwwAuthenticate, } from './oauth-discovery.js';
11
+ // High-level orchestration
12
+ export { discoverFromEmail, extractDomain, } from './orchestrator.js';
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/discovery/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,QAAQ;AACR,OAAO,EAGL,cAAc,GACf,MAAM,YAAY,CAAC;AAEpB,gCAAgC;AAChC,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACpE,OAAO,EACL,yBAAyB,EACzB,oBAAoB,GACrB,MAAM,sBAAsB,CAAC;AAE9B,2BAA2B;AAC3B,OAAO,EACL,iBAAiB,EACjB,aAAa,GAEd,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * OAuth/OIDC discovery from JMAP resource server
3
+ * Implements RFC 9728 Protected Resource Metadata discovery
4
+ */
5
+ import type { OidcDiscoveryResult } from './types.js';
6
+ /**
7
+ * Parse WWW-Authenticate header to extract OAuth metadata.
8
+ * Supports Bearer scheme with realm, scope, and issuer parameters.
9
+ *
10
+ * Example header:
11
+ * Bearer realm="example", scope="openid email", issuer="https://auth.example.com"
12
+ *
13
+ * @param header WWW-Authenticate header value
14
+ * @returns Parsed metadata or null if not Bearer auth
15
+ */
16
+ export declare function parseWwwAuthenticate(header: string): {
17
+ issuer?: string;
18
+ realm?: string;
19
+ scope?: string;
20
+ } | null;
21
+ /**
22
+ * Discover OAuth authorization server from JMAP resource URL.
23
+ * Implements RFC 9728 Protected Resource Metadata discovery with fallbacks.
24
+ *
25
+ * Discovery order:
26
+ * 1. Try /.well-known/oauth-protected-resource at resource origin
27
+ * 2. If that fails, try fetching JMAP URL to trigger 401 with WWW-Authenticate
28
+ * 3. If that fails, try common SSO subdomain patterns (sso., auth., login.)
29
+ *
30
+ * @param jmapUrl The JMAP session or API URL
31
+ * @param timeout Request timeout in ms (default 10000)
32
+ * @returns OidcDiscoveryResult or null if no OAuth info found
33
+ */
34
+ export declare function discoverOAuthFromResource(jmapUrl: string, timeout?: number): Promise<OidcDiscoveryResult | null>;