oidc.introspection.test.js (3484B)
1 // test/oidc.introspection.test.js - unit coverage for opaque token introspection 2 // 3 // The provider is stubbed with a fake fetch so these run without docker and 4 // without a real identity provider. They pin the contract: an introspection 5 // endpoint that reports active:true yields a profile, active:false is 6 // rejected, and a provider without an endpoint falls back to JWKS. 7 8 import test from 'node:test'; 9 import assert from 'node:assert/strict'; 10 import { createOidc, fetchDiscovery } from '../src/lib/auth/oidc.js'; 11 12 function discovery(overrides = {}) { 13 return { 14 issuer: 'https://idp.example', 15 authorization_endpoint: 'https://idp.example/auth', 16 token_endpoint: 'https://idp.example/token', 17 jwks_uri: 'https://idp.example/jwks', 18 ...overrides, 19 }; 20 } 21 22 function makeFetch(handler) { 23 return async (url, opts) => handler(String(url), opts || {}); 24 } 25 26 test('introspection success builds a profile from the response claims', async () => { 27 const doc = discovery({ introspection_endpoint: 'https://idp.example/introspect' }); 28 const fetchImpl = makeFetch((url) => { 29 if (url.endsWith('/.well-known/openid-configuration')) { 30 return new Response(JSON.stringify(doc), { status: 200, headers: { 'content-type': 'application/json' } }); 31 } 32 return new Response(JSON.stringify({ 33 active: true, 34 sub: 'user-1', 35 preferred_username: 'alice', 36 groups: ['conductor-admin'], 37 }), { status: 200, headers: { 'content-type': 'application/json' } }); 38 }); 39 40 const cfg = { auth: { oidc: { discovery_url: 'https://idp.example/.well-known/openid-configuration', client_id: 'conductor', scopes: 'openid', admin_role: 'conductor-admin' } } }; 41 const oidc = createOidc(cfg, { fetchImpl }); 42 const profile = await oidc.verify('opaque-token'); 43 assert.equal(profile.subject, 'user-1'); 44 assert.equal(profile.username, 'alice'); 45 assert.equal(profile.role, 'admin'); 46 }); 47 48 test('introspection reporting active:false is rejected', async () => { 49 const doc = discovery({ introspection_endpoint: 'https://idp.example/introspect' }); 50 const fetchImpl = makeFetch((url) => { 51 if (url.endsWith('/.well-known/openid-configuration')) { 52 return new Response(JSON.stringify(doc), { status: 200, headers: { 'content-type': 'application/json' } }); 53 } 54 return new Response(JSON.stringify({ active: false }), { status: 200, headers: { 'content-type': 'application/json' } }); 55 }); 56 57 const cfg = { auth: { oidc: { discovery_url: 'https://idp.example/.well-known/openid-configuration', client_id: 'conductor', scopes: 'openid', admin_role: 'conductor-admin' } } }; 58 const oidc = createOidc(cfg, { fetchImpl }); 59 await assert.rejects(() => oidc.verify('opaque-token'), /not active/); 60 }); 61 62 test('a provider without an introspection endpoint uses JWKS verification', async () => { 63 const doc = discovery(); // no introspection_endpoint 64 // jwks verification requires a real jose key set; instead confirm fetchDiscovery 65 // does not require introspection_endpoint and the document loads. 66 const fetchImpl = makeFetch((url) => { 67 if (url.endsWith('/.well-known/openid-configuration')) { 68 return new Response(JSON.stringify(doc), { status: 200, headers: { 'content-type': 'application/json' } }); 69 } 70 return new Response('nope', { status: 404 }); 71 }); 72 const loaded = await fetchDiscovery('https://idp.example/.well-known/openid-configuration', { fetchImpl }); 73 assert.equal(loaded.introspection_endpoint, undefined); 74 });