feat: check for PKCE of authorization server metadata (#212)
This commit is contained in:
parent
8d9f80a258
commit
70cc5efbad
2 changed files with 73 additions and 24 deletions
|
|
@ -8,27 +8,32 @@ vi.mock('undici', () => ({
|
|||
|
||||
const mockedRequest = vi.mocked(request);
|
||||
|
||||
describe('AuthorizationServerMetadataEndpointScenario (SUCCESS only)', () => {
|
||||
const SERVER_URL = 'https://example.com';
|
||||
const AUTHORIZATION_ENDPOINT = `${SERVER_URL}/auth`;
|
||||
const TOKEN_ENDPOINT = `${SERVER_URL}/token`;
|
||||
|
||||
const validMetadata = {
|
||||
issuer: SERVER_URL,
|
||||
authorization_endpoint: AUTHORIZATION_ENDPOINT,
|
||||
token_endpoint: TOKEN_ENDPOINT,
|
||||
response_types_supported: ['code'],
|
||||
code_challenge_methods_supported: ['plain', 'S256']
|
||||
};
|
||||
|
||||
function mockMetadataResponse(body: Record<string, unknown>) {
|
||||
mockedRequest.mockResolvedValue({
|
||||
statusCode: 200,
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: { json: async () => body }
|
||||
} as any);
|
||||
}
|
||||
|
||||
describe('AuthorizationServerMetadataEndpointScenario', () => {
|
||||
it('returns SUCCESS for valid authorization server metadata', async () => {
|
||||
const scenario = new AuthorizationServerMetadataEndpointScenario();
|
||||
const serverUrl = 'https://example.com';
|
||||
mockMetadataResponse(validMetadata);
|
||||
|
||||
mockedRequest.mockResolvedValue({
|
||||
statusCode: 200,
|
||||
headers: {
|
||||
'content-type': 'application/json'
|
||||
},
|
||||
body: {
|
||||
json: async () => ({
|
||||
issuer: 'https://example.com',
|
||||
authorization_endpoint: 'https://example.com/auth',
|
||||
token_endpoint: 'https://example.com/token',
|
||||
response_types_supported: ['code']
|
||||
})
|
||||
}
|
||||
} as any);
|
||||
|
||||
const checks = await scenario.run(serverUrl);
|
||||
const checks = await scenario.run(SERVER_URL);
|
||||
|
||||
expect(checks).toHaveLength(1);
|
||||
|
||||
|
|
@ -37,15 +42,50 @@ describe('AuthorizationServerMetadataEndpointScenario (SUCCESS only)', () => {
|
|||
expect(check.errorMessage).toBeUndefined();
|
||||
expect(check.details).toBeDefined();
|
||||
expect(check.details!.contentType).toContain('application/json');
|
||||
expect((check.details!.body as any).issuer).toBe('https://example.com');
|
||||
expect((check.details!.body as any).issuer).toBe(SERVER_URL);
|
||||
expect((check.details!.body as any).authorization_endpoint).toBe(
|
||||
'https://example.com/auth'
|
||||
);
|
||||
expect((check.details!.body as any).token_endpoint).toBe(
|
||||
'https://example.com/token'
|
||||
AUTHORIZATION_ENDPOINT
|
||||
);
|
||||
expect((check.details!.body as any).token_endpoint).toBe(TOKEN_ENDPOINT);
|
||||
expect((check.details!.body as any).response_types_supported).toEqual([
|
||||
'code'
|
||||
]);
|
||||
expect(
|
||||
(check.details!.body as any).code_challenge_methods_supported
|
||||
).toEqual(['plain', 'S256']);
|
||||
});
|
||||
|
||||
it('returns FAILURE when code_challenge_methods_supported is missing', async () => {
|
||||
const scenario = new AuthorizationServerMetadataEndpointScenario();
|
||||
mockMetadataResponse({
|
||||
issuer: validMetadata.issuer,
|
||||
authorization_endpoint: validMetadata.authorization_endpoint,
|
||||
token_endpoint: validMetadata.token_endpoint,
|
||||
response_types_supported: validMetadata.response_types_supported
|
||||
});
|
||||
|
||||
const checks = await scenario.run(SERVER_URL);
|
||||
|
||||
expect(checks).toHaveLength(1);
|
||||
|
||||
const check = checks[0];
|
||||
expect(check.status).toBe('FAILURE');
|
||||
expect(check.errorMessage).toContain('code_challenge_methods_supported');
|
||||
});
|
||||
|
||||
it('returns FAILURE when code_challenge_methods_supported does not include S256', async () => {
|
||||
const scenario = new AuthorizationServerMetadataEndpointScenario();
|
||||
mockMetadataResponse({
|
||||
...validMetadata,
|
||||
code_challenge_methods_supported: ['plain']
|
||||
});
|
||||
|
||||
const checks = await scenario.run(SERVER_URL);
|
||||
|
||||
expect(checks).toHaveLength(1);
|
||||
|
||||
const check = checks[0];
|
||||
expect(check.status).toBe('FAILURE');
|
||||
expect(check.errorMessage).toContain('code_challenge_methods_supported');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -142,6 +142,15 @@ export class AuthorizationServerMetadataEndpointScenario implements ClientScenar
|
|||
);
|
||||
}
|
||||
|
||||
if (
|
||||
!Array.isArray(body.code_challenge_methods_supported) ||
|
||||
!body.code_challenge_methods_supported.includes('S256')
|
||||
) {
|
||||
errors.push(
|
||||
'Response body does not include valid "code_challenge_methods_supported" claim'
|
||||
);
|
||||
}
|
||||
|
||||
if (body.issuer !== serverUrl) {
|
||||
errors.push(`Invalid issuer: ${body.issuer ?? '(missing)'}`);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue