mcp-twake-mail 0.1.0 → 1.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.
- package/README.md +77 -48
- package/build/cli/commands/setup.js +54 -12
- package/build/cli/commands/setup.js.map +1 -1
- package/build/cli/prompts/setup-wizard.d.ts +33 -1
- package/build/cli/prompts/setup-wizard.js +134 -2
- package/build/cli/prompts/setup-wizard.js.map +1 -1
- package/build/cli/prompts/setup-wizard.test.d.ts +1 -0
- package/build/cli/prompts/setup-wizard.test.js +161 -0
- package/build/cli/prompts/setup-wizard.test.js.map +1 -0
- package/build/config/schema.d.ts +2 -0
- package/build/config/schema.js +3 -0
- package/build/config/schema.js.map +1 -1
- package/build/discovery/dns-srv.d.ts +18 -0
- package/build/discovery/dns-srv.js +60 -0
- package/build/discovery/dns-srv.js.map +1 -0
- package/build/discovery/dns-srv.test.d.ts +4 -0
- package/build/discovery/dns-srv.test.js +79 -0
- package/build/discovery/dns-srv.test.js.map +1 -0
- package/build/discovery/index.d.ts +9 -0
- package/build/discovery/index.js +13 -0
- package/build/discovery/index.js.map +1 -0
- package/build/discovery/oauth-discovery.d.ts +34 -0
- package/build/discovery/oauth-discovery.js +160 -0
- package/build/discovery/oauth-discovery.js.map +1 -0
- package/build/discovery/oauth-discovery.test.d.ts +1 -0
- package/build/discovery/oauth-discovery.test.js +198 -0
- package/build/discovery/oauth-discovery.test.js.map +1 -0
- package/build/discovery/orchestrator.d.ts +31 -0
- package/build/discovery/orchestrator.js +87 -0
- package/build/discovery/orchestrator.js.map +1 -0
- package/build/discovery/orchestrator.test.d.ts +4 -0
- package/build/discovery/orchestrator.test.js +242 -0
- package/build/discovery/orchestrator.test.js.map +1 -0
- package/build/discovery/types.d.ts +18 -0
- package/build/discovery/types.js +15 -0
- package/build/discovery/types.js.map +1 -0
- package/build/discovery/well-known.d.ts +21 -0
- package/build/discovery/well-known.js +52 -0
- package/build/discovery/well-known.js.map +1 -0
- package/build/discovery/well-known.test.d.ts +4 -0
- package/build/discovery/well-known.test.js +120 -0
- package/build/discovery/well-known.test.js.map +1 -0
- package/build/mcp/server.d.ts +5 -3
- package/build/mcp/server.js +11 -5
- package/build/mcp/server.js.map +1 -1
- package/build/mcp/tools/email-sending.d.ts +10 -1
- package/build/mcp/tools/email-sending.js +60 -15
- package/build/mcp/tools/email-sending.js.map +1 -1
- package/build/mcp/tools/index.d.ts +10 -1
- package/build/mcp/tools/index.js +4 -3
- package/build/mcp/tools/index.js.map +1 -1
- package/build/signature/converter.d.ts +2 -0
- package/build/signature/converter.js +23 -0
- package/build/signature/converter.js.map +1 -0
- package/build/signature/converter.test.d.ts +1 -0
- package/build/signature/converter.test.js +84 -0
- package/build/signature/converter.test.js.map +1 -0
- package/build/signature/index.d.ts +2 -0
- package/build/signature/index.js +3 -0
- package/build/signature/index.js.map +1 -0
- package/build/signature/loader.d.ts +6 -0
- package/build/signature/loader.js +31 -0
- package/build/signature/loader.js.map +1 -0
- package/build/signature/loader.test.d.ts +1 -0
- package/build/signature/loader.test.js +85 -0
- package/build/signature/loader.test.js.map +1 -0
- package/docs/auto-discovery.md +210 -0
- package/docs/oidc-configuration.md +261 -0
- package/package.json +3 -1
|
@@ -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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"}
|
package/build/config/schema.d.ts
CHANGED
|
@@ -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;
|
package/build/config/schema.js
CHANGED
|
@@ -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":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC;KACvB,MAAM,CAAC;IACN,gBAAgB,EAAE,CAAC;SAChB,MAAM,EAAE;SACR,GAAG,CAAC,sCAAsC,CAAC;SAC3C,MAAM,CACL,CAAC,GAAG,EAAE,EAAE;QACN,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,OAAO,CACL,MAAM,CAAC,QAAQ,KAAK,QAAQ;YAC5B,MAAM,CAAC,QAAQ,KAAK,WAAW;YAC/B,MAAM,CAAC,QAAQ,KAAK,WAAW,CAChC,CAAC;IACJ,CAAC,EACD,EAAE,OAAO,EAAE,uEAAuE,EAAE,CACrF;IACH,gBAAgB,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC;IACtE,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACpC,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACpC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACjC,4BAA4B;IAC5B,gBAAgB,EAAE,CAAC;SAChB,MAAM,EAAE;SACR,GAAG,CAAC,sCAAsC,CAAC;SAC3C,QAAQ,EAAE;IACb,mBAAmB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC1C,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,6BAA6B,CAAC;IAClE,+DAA+D;IAC/D,sBAAsB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,gCAAgC,CAAC;IAClF,sFAAsF;IACtF,oBAAoB,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAClD,oBAAoB,EAAE,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IACtD,SAAS,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC;
|
|
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,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":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACzE,OAAO,EAAE,QAAQ,IAAI,GAAG,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEhD,EAAE,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,EAAE,CAAC,CAAC;IACzB,QAAQ,EAAE;QACR,UAAU,EAAE,EAAE,CAAC,EAAE,EAAE;KACpB;CACF,CAAC,CAAC,CAAC;AAEJ,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,UAAU,CAAC,GAAG,EAAE;QACd,EAAE,CAAC,aAAa,EAAE,CAAC;IACrB,CAAC,CAAC,CAAC;IAEH,SAAS,CAAC,GAAG,EAAE;QACb,EAAE,CAAC,eAAe,EAAE,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,mDAAmD,EAAE,KAAK,IAAI,EAAE;QACjE,MAAM,WAAW,GAAG;YAClB,EAAE,IAAI,EAAE,kBAAkB,EAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE;SAClE,CAAC;QACF,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC;QAEzD,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,aAAa,CAAC,CAAC;QAErD,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC;YACrB,QAAQ,EAAE,kBAAkB;YAC5B,IAAI,EAAE,GAAG;SACV,CAAC,CAAC;QACH,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,oBAAoB,CAAC,wBAAwB,CAAC,CAAC;IACxE,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wDAAwD,EAAE,KAAK,IAAI,EAAE;QACtE,MAAM,WAAW,GAAG;YAClB,EAAE,IAAI,EAAE,wBAAwB,EAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE;YACtE,EAAE,IAAI,EAAE,2BAA2B,EAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE;YAC1E,EAAE,IAAI,EAAE,kBAAkB,EAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE;SAClE,CAAC;QACF,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,iBAAiB,CAAC,WAAW,CAAC,CAAC;QAEzD,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,aAAa,CAAC,CAAC;QAErD,8CAA8C;QAC9C,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC;YACrB,QAAQ,EAAE,kBAAkB;YAC5B,IAAI,EAAE,GAAG;SACV,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,KAAK,IAAI,EAAE;QAClE,MAAM,KAAK,GAA0B,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC;QACnE,KAAK,CAAC,IAAI,GAAG,WAAW,CAAC;QACzB,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC;QAEnD,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,yBAAyB,CAAC,CAAC;QAEjE,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,KAAK,IAAI,EAAE;QAClE,MAAM,KAAK,GAA0B,IAAI,KAAK,CAAC,kBAAkB,CAAC,CAAC;QACnE,KAAK,CAAC,IAAI,GAAG,SAAS,CAAC;QACvB,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,iBAAiB,CAAC,KAAK,CAAC,CAAC;QAEnD,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,mBAAmB,CAAC,CAAC;QAE3D,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,yBAAyB,EAAE,KAAK,IAAI,EAAE;QACvC,4CAA4C;QAC5C,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,kBAAkB,CAC1C,GAAG,EAAE,CACH,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YACtB,gCAAgC;YAChC,UAAU,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC;QACvC,CAAC,CAAC,CACL,CAAC;QAEF,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,kBAAkB,EAAE,GAAG,CAAC,CAAC;QAE/D,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,KAAK,IAAI,EAAE;QACrD,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,iBAAiB,CAAC,EAAE,CAAC,CAAC;QAEhD,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,mBAAmB,CAAC,CAAC;QAE3D,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sCAAsC,EAAE,KAAK,IAAI,EAAE;QACpD,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,iBAAiB,CAAC,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC,CAAC;QAE9E,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,mBAAmB,CAAC,CAAC;QAE3D,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC;IAC5B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -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>;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth/OIDC discovery from JMAP resource server
|
|
3
|
+
* Implements RFC 9728 Protected Resource Metadata discovery
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Parse WWW-Authenticate header to extract OAuth metadata.
|
|
7
|
+
* Supports Bearer scheme with realm, scope, and issuer parameters.
|
|
8
|
+
*
|
|
9
|
+
* Example header:
|
|
10
|
+
* Bearer realm="example", scope="openid email", issuer="https://auth.example.com"
|
|
11
|
+
*
|
|
12
|
+
* @param header WWW-Authenticate header value
|
|
13
|
+
* @returns Parsed metadata or null if not Bearer auth
|
|
14
|
+
*/
|
|
15
|
+
export function parseWwwAuthenticate(header) {
|
|
16
|
+
// Check if header starts with "Bearer " (case-insensitive)
|
|
17
|
+
if (!header.match(/^Bearer\s+/i)) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
// Extract the part after "Bearer "
|
|
21
|
+
const params = header.replace(/^Bearer\s+/i, '');
|
|
22
|
+
// Parse key="value" pairs using regex
|
|
23
|
+
const paramRegex = /(\w+)="([^"]+)"/g;
|
|
24
|
+
const result = {};
|
|
25
|
+
let match;
|
|
26
|
+
while ((match = paramRegex.exec(params)) !== null) {
|
|
27
|
+
const [, key, value] = match;
|
|
28
|
+
if (key === 'issuer' || key === 'realm' || key === 'scope') {
|
|
29
|
+
result[key] = value;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// Return null if no metadata found
|
|
33
|
+
if (Object.keys(result).length === 0) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Try common SSO subdomain patterns for OIDC discovery.
|
|
40
|
+
* Many organizations use predictable subdomain patterns for their SSO.
|
|
41
|
+
*
|
|
42
|
+
* @param domain The base domain to check
|
|
43
|
+
* @param timeout Request timeout in ms
|
|
44
|
+
* @returns OidcDiscoveryResult or null if no valid OIDC endpoint found
|
|
45
|
+
*/
|
|
46
|
+
async function tryCommonSsoPatterns(domain, timeout) {
|
|
47
|
+
// Extract base domain (remove subdomain like 'jmap.' or 'mail.')
|
|
48
|
+
const parts = domain.split('.');
|
|
49
|
+
const baseDomain = parts.length > 2 ? parts.slice(-2).join('.') : domain;
|
|
50
|
+
// Common SSO subdomain patterns
|
|
51
|
+
const ssoPatterns = ['sso', 'auth', 'login', 'id', 'accounts'];
|
|
52
|
+
for (const pattern of ssoPatterns) {
|
|
53
|
+
const issuerUrl = `https://${pattern}.${baseDomain}`;
|
|
54
|
+
const discoveryUrl = `${issuerUrl}/.well-known/openid-configuration`;
|
|
55
|
+
try {
|
|
56
|
+
const controller = new AbortController();
|
|
57
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
58
|
+
const response = await fetch(discoveryUrl, {
|
|
59
|
+
signal: controller.signal,
|
|
60
|
+
});
|
|
61
|
+
clearTimeout(timeoutId);
|
|
62
|
+
if (response.ok) {
|
|
63
|
+
const config = await response.json();
|
|
64
|
+
// Verify it's a valid OIDC config with issuer field
|
|
65
|
+
if (config.issuer) {
|
|
66
|
+
return {
|
|
67
|
+
issuer: config.issuer,
|
|
68
|
+
method: 'well-known-oidc',
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
// This pattern didn't work, try next
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Discover OAuth authorization server from JMAP resource URL.
|
|
81
|
+
* Implements RFC 9728 Protected Resource Metadata discovery with fallbacks.
|
|
82
|
+
*
|
|
83
|
+
* Discovery order:
|
|
84
|
+
* 1. Try /.well-known/oauth-protected-resource at resource origin
|
|
85
|
+
* 2. If that fails, try fetching JMAP URL to trigger 401 with WWW-Authenticate
|
|
86
|
+
* 3. If that fails, try common SSO subdomain patterns (sso., auth., login.)
|
|
87
|
+
*
|
|
88
|
+
* @param jmapUrl The JMAP session or API URL
|
|
89
|
+
* @param timeout Request timeout in ms (default 10000)
|
|
90
|
+
* @returns OidcDiscoveryResult or null if no OAuth info found
|
|
91
|
+
*/
|
|
92
|
+
export async function discoverOAuthFromResource(jmapUrl, timeout = 10000) {
|
|
93
|
+
try {
|
|
94
|
+
// Parse jmapUrl to get origin
|
|
95
|
+
const url = new URL(jmapUrl);
|
|
96
|
+
const origin = `${url.protocol}//${url.host}`;
|
|
97
|
+
// Try RFC 9728 Protected Resource Metadata first
|
|
98
|
+
const metadataUrl = `${origin}/.well-known/oauth-protected-resource`;
|
|
99
|
+
try {
|
|
100
|
+
const controller = new AbortController();
|
|
101
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
102
|
+
const metadataResponse = await fetch(metadataUrl, {
|
|
103
|
+
signal: controller.signal,
|
|
104
|
+
});
|
|
105
|
+
clearTimeout(timeoutId);
|
|
106
|
+
if (metadataResponse.ok) {
|
|
107
|
+
const metadata = await metadataResponse.json();
|
|
108
|
+
// Extract issuer from authorization_servers array
|
|
109
|
+
if (metadata.authorization_servers &&
|
|
110
|
+
Array.isArray(metadata.authorization_servers) &&
|
|
111
|
+
metadata.authorization_servers.length > 0) {
|
|
112
|
+
return {
|
|
113
|
+
issuer: metadata.authorization_servers[0],
|
|
114
|
+
method: 'protected-resource',
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// Protected resource metadata failed, try WWW-Authenticate fallback
|
|
121
|
+
}
|
|
122
|
+
// Fallback 1: Try fetching JMAP URL directly to get 401 with WWW-Authenticate
|
|
123
|
+
try {
|
|
124
|
+
const controller = new AbortController();
|
|
125
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
126
|
+
const jmapResponse = await fetch(jmapUrl, {
|
|
127
|
+
signal: controller.signal,
|
|
128
|
+
});
|
|
129
|
+
clearTimeout(timeoutId);
|
|
130
|
+
// Check for 401 response with WWW-Authenticate header
|
|
131
|
+
if (jmapResponse.status === 401) {
|
|
132
|
+
const wwwAuth = jmapResponse.headers.get('WWW-Authenticate');
|
|
133
|
+
if (wwwAuth) {
|
|
134
|
+
const parsed = parseWwwAuthenticate(wwwAuth);
|
|
135
|
+
if (parsed?.issuer) {
|
|
136
|
+
return {
|
|
137
|
+
issuer: parsed.issuer,
|
|
138
|
+
method: 'www-authenticate',
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
// WWW-Authenticate fallback also failed
|
|
146
|
+
}
|
|
147
|
+
// Fallback 2: Try common SSO subdomain patterns
|
|
148
|
+
const ssoResult = await tryCommonSsoPatterns(url.hostname, timeout);
|
|
149
|
+
if (ssoResult) {
|
|
150
|
+
return ssoResult;
|
|
151
|
+
}
|
|
152
|
+
// No OAuth info found
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
// URL parsing or other error
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=oauth-discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth-discovery.js","sourceRoot":"","sources":["../../src/discovery/oauth-discovery.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|