deno.land / x / oauth4webapi@v1.2.2 / test / introspection.test.ts
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478import anyTest, { type TestFn } from 'ava'import setup, { type Context, teardown, issuer, endpoint, client, getResponse, UA,} from './_setup.js'import * as jose from 'jose'import * as lib from '../src/index.js'
const test = anyTest as TestFn<Context & { es256: CryptoKeyPair; rs256: CryptoKeyPair }>
test.before(setup)test.after(teardown)
test.before(async (t) => { t.context.es256 = await lib.generateKeyPair('ES256') t.context.rs256 = await lib.generateKeyPair('RS256')
t.context .intercept({ path: '/jwks', method: 'GET', }) .reply(200, { keys: [ { ...(await jose.exportJWK( ( await crypto.subtle.generateKey({ name: 'ECDH', namedCurve: 'P-256' }, false, [ 'deriveBits', ]) ).publicKey, )), use: 'enc', }, { ...(await jose.exportJWK( ( await crypto.subtle.generateKey({ name: 'ECDSA', namedCurve: 'P-256' }, false, [ 'sign', 'verify', ]) ).publicKey, )), key_ops: [], }, await jose.exportJWK( ( await crypto.subtle.generateKey({ name: 'ECDSA', namedCurve: 'P-384' }, false, [ 'sign', 'verify', ]) ).publicKey, ), await jose.exportJWK(t.context.es256.publicKey), await jose.exportJWK(t.context.rs256.publicKey), ], })})
const tClient: lib.Client = { ...client, client_secret: 'foo' }
test('introspectionRequest()', async (t) => { await t.throwsAsync(lib.introspectionRequest(issuer, tClient, 'token'), { message: '"as.introspection_endpoint" must be a string', })
await t.throwsAsync(lib.introspectionRequest(issuer, tClient, <any>null), { message: '"token" must be a non-empty string', })
const tIssuer: lib.AuthorizationServer = { ...issuer, introspection_endpoint: endpoint('introspect-1'), }
t.context .intercept({ path: '/introspect-1', method: 'POST', headers: { accept: 'application/json', 'user-agent': UA, }, body(body) { return new URLSearchParams(body).get('token') === 'token' }, }) .reply(200, { active: false })
await t.notThrowsAsync(lib.introspectionRequest(tIssuer, tClient, 'token'))})
test('introspectionRequest() w/ Extra Parameters', async (t) => { const tIssuer: lib.AuthorizationServer = { ...issuer, introspection_endpoint: endpoint('introspect-2'), }
t.context .intercept({ path: '/introspect-2', method: 'POST', body(body) { return new URLSearchParams(body).get('token_type_hint') === 'access_token' }, }) .reply(200, { access_token: 'token', token_type: 'Bearer' })
await t.notThrowsAsync( lib.introspectionRequest(tIssuer, tClient, 'token', { additionalParameters: new URLSearchParams('token_type_hint=access_token'), }), )})
test('introspectionRequest() w/ Custom Headers', async (t) => { const tIssuer: lib.AuthorizationServer = { ...issuer, introspection_endpoint: endpoint('introspect-headers'), }
t.context .intercept({ path: '/introspect-headers', method: 'POST', headers: { accept: 'application/json', 'user-agent': 'foo', foo: 'bar', }, }) .reply(200, { access_token: 'token', token_type: 'Bearer' })
await t.notThrowsAsync( lib.introspectionRequest(tIssuer, tClient, 'token', { headers: new Headers([ ['accept', 'will be overwritten'], ['user-agent', 'foo'], ['foo', 'bar'], ]), }), )})
test('introspectionRequest() forced jwt', async (t) => { const tIssuer: lib.AuthorizationServer = { ...issuer, introspection_endpoint: endpoint('introspect-3'), }
t.context .intercept({ path: '/introspect-3', method: 'POST', headers: { accept: 'application/token-introspection+jwt', }, body(body) { return new URLSearchParams(body).get('token') === 'token' }, }) .reply(200, { active: false })
await t.notThrowsAsync( lib.introspectionRequest(tIssuer, tClient, 'token', { requestJwtResponse: true }), )})
test('introspectionRequest() jwt through client metadata', async (t) => { const tIssuer: lib.AuthorizationServer = { ...issuer, introspection_endpoint: endpoint('introspect-3'), }
t.context .intercept({ path: '/introspect-3', method: 'POST', headers: { accept: 'application/token-introspection+jwt', }, body(body) { return new URLSearchParams(body).get('token') === 'token' }, }) .reply(200, { active: false })
await t.notThrowsAsync( lib.introspectionRequest( tIssuer, { ...tClient, introspection_signed_response_alg: 'ES256' }, 'token', ), )})
test('introspectionRequest() forced json', async (t) => { const tIssuer: lib.AuthorizationServer = { ...issuer, introspection_endpoint: endpoint('introspect-4'), }
t.context .intercept({ path: '/introspect-4', method: 'POST', headers: { accept: 'application/json', }, body(body) { return new URLSearchParams(body).get('token') === 'token' }, }) .reply(200, { active: false })
await t.notThrowsAsync( lib.introspectionRequest( tIssuer, { ...tClient, introspection_signed_response_alg: 'ES256' }, 'token', { requestJwtResponse: false }, ), )})
test('processIntrospectionResponse()', async (t) => { await t.throwsAsync(lib.processIntrospectionResponse(issuer, client, <any>null), { message: '"response" must be an instance of Response', }) await t.throwsAsync( lib.processIntrospectionResponse(issuer, client, getResponse('', { status: 404 })), { message: '"response" is not a conform Introspection Endpoint response', }, ) await t.throwsAsync(lib.processIntrospectionResponse(issuer, client, getResponse('{"')), { message: 'failed to parse "response" body as JSON', }) await t.throwsAsync(lib.processIntrospectionResponse(issuer, client, getResponse('null')), { message: '"response" body must be a top level object', }) await t.throwsAsync(lib.processIntrospectionResponse(issuer, client, getResponse('[]')), { message: '"response" body must be a top level object', })
await t.throwsAsync( lib.processIntrospectionResponse( issuer, client, getResponse(JSON.stringify({ token_type: 'Bearer' })), ), { message: '"response" body "active" property must be a boolean', }, )
t.deepEqual( await lib.processIntrospectionResponse( issuer, client, getResponse(JSON.stringify({ active: false })), ), { active: false, }, )
t.true( lib.isOAuth2Error( await lib.processIntrospectionResponse( issuer, client, getResponse(JSON.stringify({ error: 'invalid_client' }), { status: 401 }), ), ), )
t.false( lib.isOAuth2Error( await lib.processIntrospectionResponse( issuer, client, getResponse(JSON.stringify({ active: false })), ), ), )})
test('processIntrospectionResponse() - alg signalled', async (t) => { const tIssuer: lib.AuthorizationServer = { ...issuer, jwks_uri: endpoint('jwks'), introspection_signing_alg_values_supported: ['ES256'], }
await t.notThrowsAsync( lib.processIntrospectionResponse( tIssuer, client, getResponse( await new jose.SignJWT({ token_introspection: { active: false } }) .setProtectedHeader({ alg: 'ES256', typ: 'token-introspection+jwt' }) .setIssuer(issuer.issuer) .setAudience(client.client_id) .setIssuedAt() .sign(t.context.es256.privateKey), { headers: new Headers({ 'content-type': 'application/token-introspection+jwt', }), }, ), ), )})
test('processIntrospectionResponse() - alg defined', async (t) => { const tIssuer: lib.AuthorizationServer = { ...issuer, jwks_uri: endpoint('jwks'), }
await t.notThrowsAsync( lib.processIntrospectionResponse( tIssuer, { ...client, introspection_signed_response_alg: 'ES256' }, getResponse( await new jose.SignJWT({ token_introspection: { active: false } }) .setProtectedHeader({ alg: 'ES256', typ: 'token-introspection+jwt' }) .setIssuer(issuer.issuer) .setAudience(client.client_id) .setIssuedAt() .sign(t.context.es256.privateKey), { headers: new Headers({ 'content-type': 'application/token-introspection+jwt', }), }, ), ), )})
test('processIntrospectionResponse() - alg default', async (t) => { const tIssuer: lib.AuthorizationServer = { ...issuer, jwks_uri: endpoint('jwks'), }
await t.notThrowsAsync( lib.processIntrospectionResponse( tIssuer, client, getResponse( await new jose.SignJWT({ token_introspection: { active: false } }) .setProtectedHeader({ alg: 'RS256', typ: 'token-introspection+jwt' }) .setIssuer(issuer.issuer) .setAudience(client.client_id) .setIssuedAt() .sign(t.context.rs256.privateKey), { headers: new Headers({ 'content-type': 'application/token-introspection+jwt', }), }, ), ), )})
test('processIntrospectionResponse() - alg mismatches', async (t) => { const tIssuer: lib.AuthorizationServer = { ...issuer, jwks_uri: endpoint('jwks'), }
await t.throwsAsync( lib.processIntrospectionResponse( tIssuer, client, getResponse( await new jose.SignJWT({ token_introspection: { active: false } }) .setProtectedHeader({ alg: 'ES256', typ: 'token-introspection+jwt' }) .setIssuer(issuer.issuer) .setAudience(client.client_id) .setIssuedAt() .sign(t.context.es256.privateKey), { headers: new Headers({ 'content-type': 'application/token-introspection+jwt', }), }, ), ), { message: 'unexpected JWT "alg" header parameter' }, )
await t.throwsAsync( lib.processIntrospectionResponse( { ...tIssuer, introspection_signing_alg_values_supported: ['RS256'], }, client, getResponse( await new jose.SignJWT({ token_introspection: { active: false } }) .setProtectedHeader({ alg: 'ES256', typ: 'token-introspection+jwt' }) .setIssuer(issuer.issuer) .setAudience(client.client_id) .setIssuedAt() .sign(t.context.es256.privateKey), { headers: new Headers({ 'content-type': 'application/token-introspection+jwt', }), }, ), ), { message: 'unexpected JWT "alg" header parameter' }, )
await t.throwsAsync( lib.processIntrospectionResponse( tIssuer, { ...client, introspection_signed_response_alg: 'RS256', }, getResponse( await new jose.SignJWT({ token_introspection: { active: false } }) .setProtectedHeader({ alg: 'ES256', typ: 'token-introspection+jwt' }) .setIssuer(issuer.issuer) .setAudience(client.client_id) .setIssuedAt() .sign(t.context.es256.privateKey), { headers: new Headers({ 'content-type': 'application/token-introspection+jwt', }), }, ), ), { message: 'unexpected JWT "alg" header parameter' }, )})
test('processIntrospectionResponse() - typ w/ application/ prefix', async (t) => { const tIssuer: lib.AuthorizationServer = { ...issuer, jwks_uri: endpoint('jwks'), }
await t.notThrowsAsync( lib.processIntrospectionResponse( tIssuer, client, getResponse( await new jose.SignJWT({ token_introspection: { active: false } }) .setProtectedHeader({ alg: 'RS256', typ: 'application/token-introspection+jwt' }) .setIssuer(issuer.issuer) .setAudience(client.client_id) .setIssuedAt() .sign(t.context.rs256.privateKey), { headers: new Headers({ 'content-type': 'application/token-introspection+jwt', }), }, ), ), )})
Version Info