[auth] Rename basic-dcr scenarios to metadata variants (#41)
* merge metadata tests * [auth] Rename basic-dcr scenarios to metadata variants Consolidate auth metadata discovery scenarios with consistent naming: - AuthBasicDCRScenario → AuthMetadataDefaultScenario (auth/metadata-default) - AuthBasicMetadataVar1Scenario → AuthMetadataVar1Scenario (auth/metadata-var1) - AuthBasicMetadataVar2Scenario → AuthMetadataVar2Scenario (auth/metadata-var2) - AuthBasicMetadataVar3Scenario → AuthMetadataVar3Scenario (auth/metadata-var3) Also updates descriptions to use consistent markdown formatting with bold headers for Registration, PRM, and OAuth metadata fields. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
eb4d67a4a9
commit
f14895ca0e
6 changed files with 228 additions and 317 deletions
|
|
@ -1,93 +0,0 @@
|
||||||
import type { Scenario, ConformanceCheck } from '../../../types.js';
|
|
||||||
import { ScenarioUrls } from '../../../types.js';
|
|
||||||
import { createAuthServer } from './helpers/createAuthServer.js';
|
|
||||||
import { createServer } from './helpers/createServer.js';
|
|
||||||
import { ServerLifecycle } from './helpers/serverLifecycle.js';
|
|
||||||
import { Request, Response } from 'express';
|
|
||||||
import { SpecReferences } from './spec-references.js';
|
|
||||||
|
|
||||||
export class AuthBasicDCRScenario implements Scenario {
|
|
||||||
name = 'auth/basic-dcr';
|
|
||||||
description =
|
|
||||||
'Tests Basic OAuth flow with DCR, PRM at path-based location, OAuth metadata at root location, and no scopes required';
|
|
||||||
private authServer = new ServerLifecycle();
|
|
||||||
private server = new ServerLifecycle();
|
|
||||||
private checks: ConformanceCheck[] = [];
|
|
||||||
|
|
||||||
async start(): Promise<ScenarioUrls> {
|
|
||||||
this.checks = [];
|
|
||||||
|
|
||||||
const authApp = createAuthServer(this.checks, this.authServer.getUrl);
|
|
||||||
await this.authServer.start(authApp);
|
|
||||||
|
|
||||||
const app = createServer(
|
|
||||||
this.checks,
|
|
||||||
this.server.getUrl,
|
|
||||||
this.authServer.getUrl
|
|
||||||
);
|
|
||||||
|
|
||||||
// For this scenario, reject PRM requests at root location since we have the path-based PRM.
|
|
||||||
app.get(
|
|
||||||
'/.well-known/oauth-protected-resource',
|
|
||||||
(req: Request, res: Response) => {
|
|
||||||
this.checks.push({
|
|
||||||
id: 'prm-priority-order',
|
|
||||||
name: 'PRM Priority Order',
|
|
||||||
description:
|
|
||||||
'Client requested PRM metadata at root location on a server with path-based PRM',
|
|
||||||
status: 'FAILURE',
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
specReferences: [
|
|
||||||
SpecReferences.RFC_PRM_DISCOVERY,
|
|
||||||
SpecReferences.MCP_PRM_DISCOVERY
|
|
||||||
],
|
|
||||||
details: {
|
|
||||||
url: req.url,
|
|
||||||
path: req.path
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Return 404 to indicate PRM is not available at root location
|
|
||||||
res.status(404).json({
|
|
||||||
error: 'not_found',
|
|
||||||
error_description: 'PRM metadata not available at root location'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
await this.server.start(app);
|
|
||||||
|
|
||||||
return { serverUrl: `${this.server.getUrl()}/mcp` };
|
|
||||||
}
|
|
||||||
|
|
||||||
async stop() {
|
|
||||||
await this.authServer.stop();
|
|
||||||
await this.server.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
getChecks(): ConformanceCheck[] {
|
|
||||||
const expectedSlugs = [
|
|
||||||
'prm-pathbased-requested',
|
|
||||||
'authorization-server-metadata',
|
|
||||||
'client-registration',
|
|
||||||
'authorization-request',
|
|
||||||
'token-request'
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const slug of expectedSlugs) {
|
|
||||||
if (!this.checks.find((c) => c.id === slug)) {
|
|
||||||
this.checks.push({
|
|
||||||
id: slug,
|
|
||||||
// TODO: these are redundant...
|
|
||||||
name: `Expected Check Missing: ${slug}`,
|
|
||||||
description: `Expected Check Missing: ${slug}`,
|
|
||||||
status: 'FAILURE',
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
// TODO: ideally we'd add the spec references
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.checks;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,210 +0,0 @@
|
||||||
import type { Scenario, ConformanceCheck } from '../../../types.js';
|
|
||||||
import { ScenarioUrls } from '../../../types.js';
|
|
||||||
import { createAuthServer } from './helpers/createAuthServer.js';
|
|
||||||
import { createServer } from './helpers/createServer.js';
|
|
||||||
import { ServerLifecycle } from './helpers/serverLifecycle.js';
|
|
||||||
import { SpecReferences } from './spec-references.js';
|
|
||||||
|
|
||||||
export class AuthBasicMetadataVar1Scenario implements Scenario {
|
|
||||||
name = 'auth/basic-metadata-var1';
|
|
||||||
description =
|
|
||||||
'Tests Basic OAuth flow with DCR, PRM at root location, OAuth metadata at OpenID discovery path, and no scopes required';
|
|
||||||
private authServer = new ServerLifecycle();
|
|
||||||
private server = new ServerLifecycle();
|
|
||||||
private checks: ConformanceCheck[] = [];
|
|
||||||
|
|
||||||
async start(): Promise<ScenarioUrls> {
|
|
||||||
this.checks = [];
|
|
||||||
|
|
||||||
const authApp = createAuthServer(this.checks, this.authServer.getUrl, {
|
|
||||||
metadataPath: '/.well-known/openid-configuration',
|
|
||||||
isOpenIdConfiguration: true
|
|
||||||
});
|
|
||||||
await this.authServer.start(authApp);
|
|
||||||
|
|
||||||
const app = createServer(
|
|
||||||
this.checks,
|
|
||||||
this.server.getUrl,
|
|
||||||
this.authServer.getUrl,
|
|
||||||
{
|
|
||||||
prmPath: '/.well-known/oauth-protected-resource'
|
|
||||||
}
|
|
||||||
);
|
|
||||||
await this.server.start(app);
|
|
||||||
|
|
||||||
return { serverUrl: `${this.server.getUrl()}/mcp` };
|
|
||||||
}
|
|
||||||
|
|
||||||
async stop() {
|
|
||||||
await this.authServer.stop();
|
|
||||||
await this.server.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
getChecks(): ConformanceCheck[] {
|
|
||||||
const expectedSlugs = [
|
|
||||||
'authorization-server-metadata',
|
|
||||||
'client-registration',
|
|
||||||
'authorization-request',
|
|
||||||
'token-request'
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const slug of expectedSlugs) {
|
|
||||||
if (!this.checks.find((c) => c.id === slug)) {
|
|
||||||
this.checks.push({
|
|
||||||
id: slug,
|
|
||||||
name: `Expected Check Missing: ${slug}`,
|
|
||||||
description: `Expected Check Missing: ${slug}`,
|
|
||||||
status: 'FAILURE',
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.checks;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AuthBasicMetadataVar2Scenario implements Scenario {
|
|
||||||
name = 'auth/basic-metadata-var2';
|
|
||||||
description =
|
|
||||||
'Tests Basic OAuth flow with DCR, PRM at root location, OAuth metadata at path-based OAuth discovery path';
|
|
||||||
private authServer = new ServerLifecycle();
|
|
||||||
private server = new ServerLifecycle();
|
|
||||||
private checks: ConformanceCheck[] = [];
|
|
||||||
|
|
||||||
async start(): Promise<ScenarioUrls> {
|
|
||||||
this.checks = [];
|
|
||||||
|
|
||||||
const authApp = createAuthServer(this.checks, this.authServer.getUrl, {
|
|
||||||
metadataPath: '/tenant1/.well-known/openid-configuration',
|
|
||||||
isOpenIdConfiguration: true,
|
|
||||||
routePrefix: '/tenant1'
|
|
||||||
});
|
|
||||||
|
|
||||||
authApp.get('/.well-known/oauth-authorization-server', (req, res) => {
|
|
||||||
this.checks.push({
|
|
||||||
id: 'authorization-server-metadata-wrong-path',
|
|
||||||
name: 'AuthorizationServerMetadataWrongPath',
|
|
||||||
description:
|
|
||||||
'Client requested authorization server at the root path when the AS URL has a path-based location',
|
|
||||||
status: 'FAILURE',
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
specReferences: [
|
|
||||||
SpecReferences.RFC_AUTH_SERVER_METADATA_REQUEST,
|
|
||||||
SpecReferences.MCP_AUTH_DISCOVERY
|
|
||||||
],
|
|
||||||
details: {
|
|
||||||
url: req.url
|
|
||||||
}
|
|
||||||
});
|
|
||||||
res.status(404).send('Not Found');
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.authServer.start(authApp);
|
|
||||||
|
|
||||||
const app = createServer(
|
|
||||||
this.checks,
|
|
||||||
this.server.getUrl,
|
|
||||||
() => `${this.authServer.getUrl()}/tenant1`,
|
|
||||||
{
|
|
||||||
prmPath: '/.well-known/oauth-protected-resource'
|
|
||||||
}
|
|
||||||
);
|
|
||||||
await this.server.start(app);
|
|
||||||
|
|
||||||
return { serverUrl: `${this.server.getUrl()}/mcp` };
|
|
||||||
}
|
|
||||||
|
|
||||||
async stop() {
|
|
||||||
await this.authServer.stop();
|
|
||||||
await this.server.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
getChecks(): ConformanceCheck[] {
|
|
||||||
const expectedSlugs = [
|
|
||||||
'authorization-server-metadata',
|
|
||||||
'client-registration',
|
|
||||||
'authorization-request',
|
|
||||||
'token-request'
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const slug of expectedSlugs) {
|
|
||||||
if (!this.checks.find((c) => c.id === slug)) {
|
|
||||||
this.checks.push({
|
|
||||||
id: slug,
|
|
||||||
name: `Expected Check Missing: ${slug}`,
|
|
||||||
description: `Expected Check Missing: ${slug}`,
|
|
||||||
status: 'FAILURE',
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.checks;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class AuthBasicMetadataVar3Scenario implements Scenario {
|
|
||||||
name = 'auth/basic-metadata-var3';
|
|
||||||
description =
|
|
||||||
'Tests Basic OAuth flow with DCR, PRM at custom location listed in WWW-Authenticate header, OAuth metadata is at nested OpenID discovery path, and no scopes required';
|
|
||||||
private authServer = new ServerLifecycle();
|
|
||||||
private server = new ServerLifecycle();
|
|
||||||
private checks: ConformanceCheck[] = [];
|
|
||||||
|
|
||||||
async start(): Promise<ScenarioUrls> {
|
|
||||||
this.checks = [];
|
|
||||||
|
|
||||||
const authApp = createAuthServer(this.checks, this.authServer.getUrl, {
|
|
||||||
metadataPath: '/tenant1/.well-known/openid-configuration',
|
|
||||||
isOpenIdConfiguration: true,
|
|
||||||
routePrefix: '/tenant1'
|
|
||||||
});
|
|
||||||
await this.authServer.start(authApp);
|
|
||||||
|
|
||||||
const app = createServer(
|
|
||||||
this.checks,
|
|
||||||
this.server.getUrl,
|
|
||||||
() => {
|
|
||||||
return `${this.authServer.getUrl()}/tenant1`;
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// This is a custom path, so unable to get via probing, it's only available
|
|
||||||
// via following the `resource_metadata_url` in the WWW-Authenticate header.
|
|
||||||
// The resource must match the original request URL per RFC 9728.
|
|
||||||
prmPath: '/custom/metadata/location.json'
|
|
||||||
}
|
|
||||||
);
|
|
||||||
await this.server.start(app);
|
|
||||||
|
|
||||||
return { serverUrl: `${this.server.getUrl()}/mcp` };
|
|
||||||
}
|
|
||||||
|
|
||||||
async stop() {
|
|
||||||
await this.authServer.stop();
|
|
||||||
await this.server.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
getChecks(): ConformanceCheck[] {
|
|
||||||
const expectedSlugs = [
|
|
||||||
'authorization-server-metadata',
|
|
||||||
'client-registration',
|
|
||||||
'authorization-request',
|
|
||||||
'token-request'
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const slug of expectedSlugs) {
|
|
||||||
if (!this.checks.find((c) => c.id === slug)) {
|
|
||||||
this.checks.push({
|
|
||||||
id: slug,
|
|
||||||
name: `Expected Check Missing: ${slug}`,
|
|
||||||
description: `Expected Check Missing: ${slug}`,
|
|
||||||
status: 'FAILURE',
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.checks;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
219
src/scenarios/client/auth/discovery-metadata.ts
Normal file
219
src/scenarios/client/auth/discovery-metadata.ts
Normal file
|
|
@ -0,0 +1,219 @@
|
||||||
|
/**
|
||||||
|
* OAuth Metadata Discovery Scenarios
|
||||||
|
*
|
||||||
|
* These scenarios test different combinations of PRM and OAuth metadata locations.
|
||||||
|
* The configurations are defined in SCENARIO_CONFIGS below and scenarios are
|
||||||
|
* generated from them.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Scenario, ConformanceCheck } from '../../../types.js';
|
||||||
|
import { ScenarioUrls } from '../../../types.js';
|
||||||
|
import { createAuthServer } from './helpers/createAuthServer.js';
|
||||||
|
import { createServer } from './helpers/createServer.js';
|
||||||
|
import { ServerLifecycle } from './helpers/serverLifecycle.js';
|
||||||
|
import { SpecReferences } from './spec-references.js';
|
||||||
|
import { Request, Response } from 'express';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration for a metadata discovery scenario.
|
||||||
|
*/
|
||||||
|
interface MetadataScenarioConfig {
|
||||||
|
name: string;
|
||||||
|
prmLocation: string;
|
||||||
|
inWwwAuth: boolean;
|
||||||
|
oauthMetadataLocation: string;
|
||||||
|
/** Route prefix for the auth server (e.g., '/tenant1') */
|
||||||
|
authRoutePrefix?: string;
|
||||||
|
/** If true, add a trap for root PRM requests */
|
||||||
|
trapRootPrm?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scenario configurations table:
|
||||||
|
*
|
||||||
|
* | Scenario | PRM Location | In WWW-Auth | OAuth Metadata Location |
|
||||||
|
* |------------------|-------------------------------------------|-------------|------------------------------------------------|
|
||||||
|
* | metadata-default | /.well-known/oauth-protected-resource/mcp | Yes | /.well-known/oauth-authorization-server |
|
||||||
|
* | metadata-var1 | /.well-known/oauth-protected-resource/mcp | No | /.well-known/openid-configuration |
|
||||||
|
* | metadata-var2 | /.well-known/oauth-protected-resource | No | /.well-known/oauth-authorization-server/tenant1|
|
||||||
|
* | metadata-var3 | /custom/metadata/location.json | Yes | /tenant1/.well-known/openid-configuration |
|
||||||
|
*/
|
||||||
|
const SCENARIO_CONFIGS: MetadataScenarioConfig[] = [
|
||||||
|
{
|
||||||
|
name: 'metadata-default',
|
||||||
|
prmLocation: '/.well-known/oauth-protected-resource/mcp',
|
||||||
|
inWwwAuth: true,
|
||||||
|
oauthMetadataLocation: '/.well-known/oauth-authorization-server',
|
||||||
|
trapRootPrm: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'metadata-var1',
|
||||||
|
prmLocation: '/.well-known/oauth-protected-resource/mcp',
|
||||||
|
inWwwAuth: false,
|
||||||
|
oauthMetadataLocation: '/.well-known/openid-configuration'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'metadata-var2',
|
||||||
|
prmLocation: '/.well-known/oauth-protected-resource',
|
||||||
|
inWwwAuth: false,
|
||||||
|
oauthMetadataLocation: '/.well-known/oauth-authorization-server/tenant1',
|
||||||
|
authRoutePrefix: '/tenant1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'metadata-var3',
|
||||||
|
prmLocation: '/custom/metadata/location.json',
|
||||||
|
inWwwAuth: true,
|
||||||
|
oauthMetadataLocation: '/tenant1/.well-known/openid-configuration',
|
||||||
|
authRoutePrefix: '/tenant1'
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a metadata discovery scenario from configuration.
|
||||||
|
*/
|
||||||
|
function createMetadataScenario(config: MetadataScenarioConfig): Scenario {
|
||||||
|
const authServer = new ServerLifecycle();
|
||||||
|
const server = new ServerLifecycle();
|
||||||
|
let checks: ConformanceCheck[] = [];
|
||||||
|
|
||||||
|
const routePrefix = config.authRoutePrefix || '';
|
||||||
|
const isOpenIdConfiguration = config.oauthMetadataLocation.includes(
|
||||||
|
'openid-configuration'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Determine if PRM is at path-based location
|
||||||
|
const isPathBasedPrm =
|
||||||
|
config.prmLocation === '/.well-known/oauth-protected-resource/mcp';
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: `auth/${config.name}`,
|
||||||
|
description: `Tests Basic OAuth metadata discovery flow.
|
||||||
|
|
||||||
|
**PRM:** ${config.prmLocation}${config.inWwwAuth ? '' : ' (not in WWW-Authenticate)'}
|
||||||
|
**OAuth metadata:** ${config.oauthMetadataLocation}
|
||||||
|
`,
|
||||||
|
|
||||||
|
async start(): Promise<ScenarioUrls> {
|
||||||
|
checks = [];
|
||||||
|
|
||||||
|
const authApp = createAuthServer(checks, authServer.getUrl, {
|
||||||
|
metadataPath: config.oauthMetadataLocation,
|
||||||
|
isOpenIdConfiguration,
|
||||||
|
...(routePrefix && { routePrefix })
|
||||||
|
});
|
||||||
|
|
||||||
|
// If path-based OAuth metadata, trap root requests
|
||||||
|
if (routePrefix) {
|
||||||
|
authApp.get('/.well-known/oauth-authorization-server', (req, res) => {
|
||||||
|
checks.push({
|
||||||
|
id: 'authorization-server-metadata-wrong-path',
|
||||||
|
name: 'AuthorizationServerMetadataWrongPath',
|
||||||
|
description:
|
||||||
|
'Client requested authorization server at the root path when the AS URL has a path-based location',
|
||||||
|
status: 'FAILURE',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
specReferences: [
|
||||||
|
SpecReferences.RFC_AUTH_SERVER_METADATA_REQUEST,
|
||||||
|
SpecReferences.MCP_AUTH_DISCOVERY
|
||||||
|
],
|
||||||
|
details: {
|
||||||
|
url: req.url
|
||||||
|
}
|
||||||
|
});
|
||||||
|
res.status(404).send('Not Found');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await authServer.start(authApp);
|
||||||
|
|
||||||
|
const getAuthServerUrl = routePrefix
|
||||||
|
? () => `${authServer.getUrl()}${routePrefix}`
|
||||||
|
: authServer.getUrl;
|
||||||
|
|
||||||
|
const app = createServer(checks, server.getUrl, getAuthServerUrl, {
|
||||||
|
prmPath: config.prmLocation,
|
||||||
|
includePrmInWwwAuth: config.inWwwAuth
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add trap for root PRM requests if configured
|
||||||
|
if (config.trapRootPrm) {
|
||||||
|
app.get(
|
||||||
|
'/.well-known/oauth-protected-resource',
|
||||||
|
(req: Request, res: Response) => {
|
||||||
|
checks.push({
|
||||||
|
id: 'prm-priority-order',
|
||||||
|
name: 'PRM Priority Order',
|
||||||
|
description:
|
||||||
|
'Client requested PRM metadata at root location on a server with path-based PRM',
|
||||||
|
status: 'FAILURE',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
specReferences: [
|
||||||
|
SpecReferences.RFC_PRM_DISCOVERY,
|
||||||
|
SpecReferences.MCP_PRM_DISCOVERY
|
||||||
|
],
|
||||||
|
details: {
|
||||||
|
url: req.url,
|
||||||
|
path: req.path
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(404).json({
|
||||||
|
error: 'not_found',
|
||||||
|
error_description: 'PRM metadata not available at root location'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await server.start(app);
|
||||||
|
|
||||||
|
return { serverUrl: `${server.getUrl()}/mcp` };
|
||||||
|
},
|
||||||
|
|
||||||
|
async stop() {
|
||||||
|
await authServer.stop();
|
||||||
|
await server.stop();
|
||||||
|
},
|
||||||
|
|
||||||
|
getChecks(): ConformanceCheck[] {
|
||||||
|
const expectedSlugs = [
|
||||||
|
...(isPathBasedPrm ? ['prm-pathbased-requested'] : []),
|
||||||
|
'authorization-server-metadata',
|
||||||
|
'client-registration',
|
||||||
|
'authorization-request',
|
||||||
|
'token-request'
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const slug of expectedSlugs) {
|
||||||
|
if (!checks.find((c) => c.id === slug)) {
|
||||||
|
checks.push({
|
||||||
|
id: slug,
|
||||||
|
name: `Expected Check Missing: ${slug}`,
|
||||||
|
description: `Expected Check Missing: ${slug}`,
|
||||||
|
status: 'FAILURE',
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return checks;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate scenario instances from configurations
|
||||||
|
export const AuthMetadataDefaultScenario = createMetadataScenario(
|
||||||
|
SCENARIO_CONFIGS[0]
|
||||||
|
);
|
||||||
|
export const AuthMetadataVar1Scenario = createMetadataScenario(
|
||||||
|
SCENARIO_CONFIGS[1]
|
||||||
|
);
|
||||||
|
export const AuthMetadataVar2Scenario = createMetadataScenario(
|
||||||
|
SCENARIO_CONFIGS[2]
|
||||||
|
);
|
||||||
|
export const AuthMetadataVar3Scenario = createMetadataScenario(
|
||||||
|
SCENARIO_CONFIGS[3]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Export all scenarios as an array for convenience
|
||||||
|
export const metadataScenarios = SCENARIO_CONFIGS.map(createMetadataScenario);
|
||||||
|
|
@ -18,6 +18,7 @@ export interface ServerOptions {
|
||||||
prmPath?: string | null;
|
prmPath?: string | null;
|
||||||
requiredScopes?: string[];
|
requiredScopes?: string[];
|
||||||
scopesSupported?: string[];
|
scopesSupported?: string[];
|
||||||
|
includePrmInWwwAuth?: boolean;
|
||||||
includeScopeInWwwAuth?: boolean;
|
includeScopeInWwwAuth?: boolean;
|
||||||
authMiddleware?: express.RequestHandler;
|
authMiddleware?: express.RequestHandler;
|
||||||
tokenVerifier?: MockTokenVerifier;
|
tokenVerifier?: MockTokenVerifier;
|
||||||
|
|
@ -33,6 +34,7 @@ export function createServer(
|
||||||
prmPath = '/.well-known/oauth-protected-resource/mcp',
|
prmPath = '/.well-known/oauth-protected-resource/mcp',
|
||||||
requiredScopes = [],
|
requiredScopes = [],
|
||||||
scopesSupported,
|
scopesSupported,
|
||||||
|
includePrmInWwwAuth = true,
|
||||||
includeScopeInWwwAuth = false,
|
includeScopeInWwwAuth = false,
|
||||||
tokenVerifier
|
tokenVerifier
|
||||||
} = options;
|
} = options;
|
||||||
|
|
@ -136,9 +138,10 @@ export function createServer(
|
||||||
verifier,
|
verifier,
|
||||||
// Only pass requiredScopes if we want them in the WWW-Authenticate header
|
// Only pass requiredScopes if we want them in the WWW-Authenticate header
|
||||||
requiredScopes: includeScopeInWwwAuth ? requiredScopes : [],
|
requiredScopes: includeScopeInWwwAuth ? requiredScopes : [],
|
||||||
...(prmPath !== null && {
|
...(includePrmInWwwAuth &&
|
||||||
resourceMetadataUrl: `${getBaseUrl()}${prmPath}`
|
prmPath !== null && {
|
||||||
})
|
resourceMetadataUrl: `${getBaseUrl()}${prmPath}`
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
authMiddleware(req, res, async (err?: any) => {
|
authMiddleware(req, res, async (err?: any) => {
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ describe('Client Auth Scenarios', () => {
|
||||||
describe('Negative tests', () => {
|
describe('Negative tests', () => {
|
||||||
test('bad client requests root PRM location', async () => {
|
test('bad client requests root PRM location', async () => {
|
||||||
const runner = new InlineClientRunner(badPrmClient);
|
const runner = new InlineClientRunner(badPrmClient);
|
||||||
await runClientAgainstScenario(runner, 'auth/basic-dcr', [
|
await runClientAgainstScenario(runner, 'auth/metadata-default', [
|
||||||
'prm-priority-order'
|
'prm-priority-order'
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,5 @@
|
||||||
import { Scenario } from '../../../types';
|
import { Scenario } from '../../../types';
|
||||||
import { AuthBasicDCRScenario } from './basic-dcr.js';
|
import { metadataScenarios } from './discovery-metadata.js';
|
||||||
import {
|
|
||||||
AuthBasicMetadataVar1Scenario,
|
|
||||||
AuthBasicMetadataVar2Scenario,
|
|
||||||
AuthBasicMetadataVar3Scenario
|
|
||||||
} from './basic-metadata.js';
|
|
||||||
import {
|
import {
|
||||||
Auth20250326OAuthMetadataBackcompatScenario,
|
Auth20250326OAuthMetadataBackcompatScenario,
|
||||||
Auth20250326OEndpointFallbackScenario
|
Auth20250326OEndpointFallbackScenario
|
||||||
|
|
@ -17,10 +12,7 @@ import {
|
||||||
} from './scope-handling.js';
|
} from './scope-handling.js';
|
||||||
|
|
||||||
export const authScenariosList: Scenario[] = [
|
export const authScenariosList: Scenario[] = [
|
||||||
new AuthBasicDCRScenario(),
|
...metadataScenarios,
|
||||||
new AuthBasicMetadataVar1Scenario(),
|
|
||||||
new AuthBasicMetadataVar2Scenario(),
|
|
||||||
new AuthBasicMetadataVar3Scenario(),
|
|
||||||
new Auth20250326OAuthMetadataBackcompatScenario(),
|
new Auth20250326OAuthMetadataBackcompatScenario(),
|
||||||
new Auth20250326OEndpointFallbackScenario(),
|
new Auth20250326OEndpointFallbackScenario(),
|
||||||
new ScopeFromWwwAuthenticateScenario(),
|
new ScopeFromWwwAuthenticateScenario(),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue