| 29 | } |
| 30 | |
| 31 | async function main() { |
| 32 | console.log(`\n🔑 REST OAuth Test — ${BASE_URL}/api\n`); |
| 33 | |
| 34 | // Step 1: Discover PRM for /api |
| 35 | console.log('1️⃣ Fetching Protected Resource Metadata for /api...'); |
| 36 | const prmRes = await fetch(`${BASE_URL}/.well-known/oauth-protected-resource/api`); |
| 37 | if (!prmRes.ok) { |
| 38 | throw new Error(`PRM fetch failed: ${prmRes.status} ${await prmRes.text()}`); |
| 39 | } |
| 40 | const prm = await prmRes.json(); |
| 41 | console.log(` Resource: ${prm.resource}`); |
| 42 | console.log(` Auth server: ${prm.authorization_servers[0]}`); |
| 43 | console.log(` Bearer methods: ${prm.bearer_methods_supported?.join(', ')}`); |
| 44 | |
| 45 | // Step 2: Discover AS metadata |
| 46 | const asUrl = prm.authorization_servers[0]; |
| 47 | console.log('\n2️⃣ Fetching AS Metadata...'); |
| 48 | const asRes = await fetch(`${asUrl}/.well-known/oauth-authorization-server`); |
| 49 | const as = await asRes.json(); |
| 50 | console.log(` Token endpoint: ${as.token_endpoint}`); |
| 51 | console.log(` Registration: ${as.registration_endpoint}`); |
| 52 | |
| 53 | // Step 3: Dynamic client registration |
| 54 | console.log('\n3️⃣ Registering dynamic client...'); |
| 55 | const regRes = await fetch(as.registration_endpoint, { |
| 56 | method: 'POST', |
| 57 | headers: { 'Content-Type': 'application/json' }, |
| 58 | body: JSON.stringify({ |
| 59 | client_name: 'REST OAuth Test Script', |
| 60 | redirect_uris: [REDIRECT_URI], |
| 61 | grant_types: ['authorization_code', 'refresh_token'], |
| 62 | response_types: ['code'], |
| 63 | token_endpoint_auth_method: 'none', |
| 64 | }), |
| 65 | }); |
| 66 | const client = await regRes.json(); |
| 67 | if (!client.client_id) { |
| 68 | console.error(' ❌ Registration failed:', JSON.stringify(client)); |
| 69 | process.exit(1); |
| 70 | } |
| 71 | console.log(` Client ID: ${client.client_id}`); |
| 72 | |
| 73 | // Step 4: PKCE + authorization URL |
| 74 | const codeVerifier = base64url(crypto.randomBytes(32)); |
| 75 | const codeChallenge = base64url(crypto.createHash('sha256').update(codeVerifier).digest()); |
| 76 | const state = base64url(crypto.randomBytes(16)); |
| 77 | |
| 78 | const authUrl = new URL(as.authorization_endpoint); |
| 79 | authUrl.searchParams.set('client_id', client.client_id); |
| 80 | authUrl.searchParams.set('redirect_uri', REDIRECT_URI); |
| 81 | authUrl.searchParams.set('response_type', 'code'); |
| 82 | authUrl.searchParams.set('scope', 'openid profile email'); |
| 83 | authUrl.searchParams.set('code_challenge', codeChallenge); |
| 84 | authUrl.searchParams.set('code_challenge_method', 'S256'); |
| 85 | authUrl.searchParams.set('state', state); |
| 86 | |
| 87 | // Step 5: Start callback server and open browser |
| 88 | console.log('\n4️⃣ Starting callback server and opening browser...'); |