mirror of
https://github.com/davidkaya/aryx.git
synced 2026-08-06 19:58:43 +02:00
fix: correct well-known URL construction per RFC 9728
The .well-known/{suffix} segment was being appended to the full URL
path instead of inserted after the origin. For example:
Wrong: https://api.example.com/mcp/.well-known/oauth-protected-resource
Correct: https://api.example.com/.well-known/oauth-protected-resource/mcp/
- Extract buildWellKnownUrl() helper in mcpTokenStore (Electron-free)
- Fix all 3 call sites: requiresOAuth probe, PRM discovery, auth
server metadata
- Add 6 unit tests for URL construction with various path shapes
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -5,7 +5,7 @@ import { shell } from 'electron';
|
|||||||
|
|
||||||
import type { McpOauthStaticClientConfig } from '@shared/domain/mcpAuth';
|
import type { McpOauthStaticClientConfig } from '@shared/domain/mcpAuth';
|
||||||
|
|
||||||
import { storeToken, type McpOAuthToken } from './mcpTokenStore';
|
import { storeToken, buildWellKnownUrl, type McpOAuthToken } from './mcpTokenStore';
|
||||||
|
|
||||||
/* ── Public API ──────────────────────────────────────────────── */
|
/* ── Public API ──────────────────────────────────────────────── */
|
||||||
|
|
||||||
@@ -39,8 +39,7 @@ export async function requiresOAuth(serverUrl: string): Promise<boolean> {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const base = serverUrl.replace(/\/+$/, '');
|
const prmUrl = buildWellKnownUrl(serverUrl, 'oauth-protected-resource');
|
||||||
const prmUrl = `${base}/.well-known/oauth-protected-resource`;
|
|
||||||
console.log(`[aryx oauth] Checking PRM at ${prmUrl}…`);
|
console.log(`[aryx oauth] Checking PRM at ${prmUrl}…`);
|
||||||
const prmResponse = await fetch(prmUrl, {
|
const prmResponse = await fetch(prmUrl, {
|
||||||
signal: AbortSignal.timeout(5_000),
|
signal: AbortSignal.timeout(5_000),
|
||||||
@@ -128,8 +127,7 @@ interface AuthServerMetadata {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function discoverAuthorizationServer(serverUrl: string): Promise<string> {
|
async function discoverAuthorizationServer(serverUrl: string): Promise<string> {
|
||||||
const base = serverUrl.replace(/\/+$/, '');
|
const prmUrl = buildWellKnownUrl(serverUrl, 'oauth-protected-resource');
|
||||||
const prmUrl = `${base}/.well-known/oauth-protected-resource`;
|
|
||||||
|
|
||||||
const response = await fetch(prmUrl);
|
const response = await fetch(prmUrl);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@@ -146,8 +144,7 @@ async function discoverAuthorizationServer(serverUrl: string): Promise<string> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function fetchAuthServerMetadata(authServerUrl: string): Promise<AuthServerMetadata> {
|
async function fetchAuthServerMetadata(authServerUrl: string): Promise<AuthServerMetadata> {
|
||||||
const base = authServerUrl.replace(/\/+$/, '');
|
const metadataUrl = buildWellKnownUrl(authServerUrl, 'oauth-authorization-server');
|
||||||
const metadataUrl = `${base}/.well-known/oauth-authorization-server`;
|
|
||||||
|
|
||||||
const response = await fetch(metadataUrl);
|
const response = await fetch(metadataUrl);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|||||||
@@ -47,3 +47,16 @@ function normalizeUrl(url: string): string {
|
|||||||
return url.toLowerCase().replace(/\/+$/, '');
|
return url.toLowerCase().replace(/\/+$/, '');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a well-known URL per RFC 9728 Section 3.
|
||||||
|
* The `.well-known/{suffix}` segment is inserted between the origin and the path.
|
||||||
|
*
|
||||||
|
* Example: `buildWellKnownUrl('https://api.example.com/mcp/', 'oauth-protected-resource')`
|
||||||
|
* → `https://api.example.com/.well-known/oauth-protected-resource/mcp/`
|
||||||
|
*/
|
||||||
|
export function buildWellKnownUrl(baseUrl: string, wellKnownSuffix: string): string {
|
||||||
|
const parsed = new URL(baseUrl);
|
||||||
|
const path = parsed.pathname === '/' ? '' : parsed.pathname;
|
||||||
|
return `${parsed.origin}/.well-known/${wellKnownSuffix}${path}`;
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { describe, expect, test } from 'bun:test';
|
||||||
|
|
||||||
|
import { buildWellKnownUrl } from '@main/services/mcpTokenStore';
|
||||||
|
|
||||||
|
describe('buildWellKnownUrl', () => {
|
||||||
|
test('inserts well-known segment after origin for URL with path', () => {
|
||||||
|
expect(buildWellKnownUrl('https://api.githubcopilot.com/mcp/', 'oauth-protected-resource'))
|
||||||
|
.toBe('https://api.githubcopilot.com/.well-known/oauth-protected-resource/mcp/');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('inserts well-known segment for URL with multi-level path', () => {
|
||||||
|
expect(buildWellKnownUrl('https://example.com/v1/mcp/api', 'oauth-protected-resource'))
|
||||||
|
.toBe('https://example.com/.well-known/oauth-protected-resource/v1/mcp/api');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handles origin-only URL without path', () => {
|
||||||
|
expect(buildWellKnownUrl('https://auth.example.com', 'oauth-authorization-server'))
|
||||||
|
.toBe('https://auth.example.com/.well-known/oauth-authorization-server');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handles origin-only URL with trailing slash', () => {
|
||||||
|
expect(buildWellKnownUrl('https://auth.example.com/', 'oauth-authorization-server'))
|
||||||
|
.toBe('https://auth.example.com/.well-known/oauth-authorization-server');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handles URL with port', () => {
|
||||||
|
expect(buildWellKnownUrl('https://mcp.example.com:8443/v1/', 'oauth-protected-resource'))
|
||||||
|
.toBe('https://mcp.example.com:8443/.well-known/oauth-protected-resource/v1/');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('preserves path without trailing slash', () => {
|
||||||
|
expect(buildWellKnownUrl('https://example.com/mcp', 'oauth-protected-resource'))
|
||||||
|
.toBe('https://example.com/.well-known/oauth-protected-resource/mcp');
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user