feat: implement auth/pre-registration conformance scenario (#1545)
This commit is contained in:
parent
8cbc658483
commit
f2d21458cc
3 changed files with 265 additions and 27 deletions
|
|
@ -1,14 +1,8 @@
|
|||
# Known conformance test failures for v1.x
|
||||
# These are tracked and should be removed as they're fixed.
|
||||
#
|
||||
# tools_call: conformance runner's test server reuses a single Server
|
||||
# instance across requests, triggering v1.26.0's "Already connected"
|
||||
# guard (GHSA-345p-7cg4-v4c7). Fixed in conformance repo (PR #141),
|
||||
# remove this entry once a new conformance release is published.
|
||||
#
|
||||
# auth/pre-registration: scenario added in conformance 0.1.11 that
|
||||
# requires a dedicated client handler for pre-registered credentials.
|
||||
# Needs to be implemented in both v1.x and main.
|
||||
# auth/cross-app-access-complete-flow: SEP-990 Enterprise Managed OAuth
|
||||
# scenario added in conformance 0.1.14. Requires implementing token
|
||||
# exchange (RFC 8693) and JWT bearer grant (RFC 7523) in the client.
|
||||
client:
|
||||
- tools_call
|
||||
- auth/pre-registration
|
||||
- auth/cross-app-access-complete-flow
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import { ElicitRequestSchema } from '../../../src/types.js';
|
|||
import { z } from 'zod';
|
||||
|
||||
import { logger } from './helpers/logger.js';
|
||||
import { ConformanceOAuthProvider } from './helpers/conformanceOAuthProvider.js';
|
||||
import { handle401, withOAuthRetry } from './helpers/withOAuthRetry.js';
|
||||
|
||||
/**
|
||||
|
|
@ -37,6 +38,11 @@ const ClientConformanceContextSchema = z.discriminatedUnion('name', [
|
|||
name: z.literal('auth/client-credentials-basic'),
|
||||
client_id: z.string(),
|
||||
client_secret: z.string()
|
||||
}),
|
||||
z.object({
|
||||
name: z.literal('auth/pre-registration'),
|
||||
client_id: z.string(),
|
||||
client_secret: z.string()
|
||||
})
|
||||
]);
|
||||
|
||||
|
|
@ -228,6 +234,43 @@ async function runClientCredentialsBasic(serverUrl: string): Promise<void> {
|
|||
|
||||
registerScenario('auth/client-credentials-basic', runClientCredentialsBasic);
|
||||
|
||||
// ============================================================================
|
||||
// Pre-registration scenario (no dynamic client registration)
|
||||
// ============================================================================
|
||||
|
||||
async function runPreRegistrationClient(serverUrl: string): Promise<void> {
|
||||
const ctx = parseContext();
|
||||
if (ctx.name !== 'auth/pre-registration') {
|
||||
throw new Error(`Expected pre-registration context, got ${ctx.name}`);
|
||||
}
|
||||
|
||||
// Create a provider pre-populated with registered credentials,
|
||||
// so the SDK skips dynamic client registration.
|
||||
const provider = new ConformanceOAuthProvider('http://localhost:3000/callback', {
|
||||
client_name: 'conformance-pre-registration',
|
||||
redirect_uris: ['http://localhost:3000/callback']
|
||||
});
|
||||
provider.saveClientInformation({
|
||||
client_id: ctx.client_id,
|
||||
client_secret: ctx.client_secret,
|
||||
redirect_uris: ['http://localhost:3000/callback']
|
||||
});
|
||||
|
||||
const oauthFetch = withOAuthRetry('conformance-pre-registration', new URL(serverUrl), handle401, undefined, provider)(fetch);
|
||||
|
||||
const client = new Client({ name: 'conformance-pre-registration', version: '1.0.0' }, { capabilities: {} });
|
||||
const transport = new StreamableHTTPClientTransport(new URL(serverUrl), {
|
||||
fetch: oauthFetch
|
||||
});
|
||||
|
||||
await client.connect(transport);
|
||||
await client.listTools();
|
||||
await client.callTool({ name: 'test-tool', arguments: {} });
|
||||
await transport.close();
|
||||
}
|
||||
|
||||
registerScenario('auth/pre-registration', runPreRegistrationClient);
|
||||
|
||||
// ============================================================================
|
||||
// Elicitation defaults scenario
|
||||
// ============================================================================
|
||||
|
|
|
|||
|
|
@ -38,16 +38,19 @@ export const withOAuthRetry = (
|
|||
clientName: string,
|
||||
baseUrl?: string | URL,
|
||||
handle401Fn: typeof handle401 = handle401,
|
||||
clientMetadataUrl?: string
|
||||
clientMetadataUrl?: string,
|
||||
existingProvider?: ConformanceOAuthProvider
|
||||
): Middleware => {
|
||||
const provider = new ConformanceOAuthProvider(
|
||||
'http://localhost:3000/callback',
|
||||
{
|
||||
client_name: clientName,
|
||||
redirect_uris: ['http://localhost:3000/callback']
|
||||
},
|
||||
clientMetadataUrl
|
||||
);
|
||||
const provider =
|
||||
existingProvider ??
|
||||
new ConformanceOAuthProvider(
|
||||
'http://localhost:3000/callback',
|
||||
{
|
||||
client_name: clientName,
|
||||
redirect_uris: ['http://localhost:3000/callback']
|
||||
},
|
||||
clientMetadataUrl
|
||||
);
|
||||
return (next: FetchLike) => {
|
||||
return async (input: string | URL, init?: RequestInit): Promise<Response> => {
|
||||
const makeRequest = async (): Promise<Response> => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue