/** * OAuth flow helpers for e2e tests */ import { randomBytes } from 'node:crypto'; import { DpopClient } from './dpop.js'; const BASE = 'http://localhost:8787'; /** * Get an OAuth token with a specific scope via full PAR -> authorize -> token flow * @param {string} scope - The scope to request * @param {string} did - The DID to authenticate as * @param {string} password - The password for authentication * @returns {Promise<{accessToken: string, refreshToken: string, dpop: DpopClient}>} */ export async function getOAuthTokenWithScope(scope, did, password) { const dpop = await DpopClient.create(); const clientId = 'http://localhost:3000'; const redirectUri = 'http://localhost:3000/callback'; const codeVerifier = randomBytes(32).toString('base64url'); const challengeBuffer = await crypto.subtle.digest( 'SHA-256', new TextEncoder().encode(codeVerifier), ); const codeChallenge = Buffer.from(challengeBuffer).toString('base64url'); // PAR request const parProof = await dpop.createProof('POST', `${BASE}/oauth/par`); const parRes = await fetch(`${BASE}/oauth/par`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', DPoP: parProof, }, body: new URLSearchParams({ client_id: clientId, redirect_uri: redirectUri, response_type: 'code', scope: scope, code_challenge: codeChallenge, code_challenge_method: 'S256', login_hint: did, }).toString(), }); const parData = await parRes.json(); // Authorize const authRes = await fetch(`${BASE}/oauth/authorize`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ request_uri: parData.request_uri, client_id: clientId, password: password, }).toString(), redirect: 'manual', }); const location = authRes.headers.get('location'); const authCode = new URL(location).searchParams.get('code'); // Token exchange const tokenProof = await dpop.createProof('POST', `${BASE}/oauth/token`); const tokenRes = await fetch(`${BASE}/oauth/token`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', DPoP: tokenProof, }, body: new URLSearchParams({ grant_type: 'authorization_code', code: authCode, client_id: clientId, redirect_uri: redirectUri, code_verifier: codeVerifier, }).toString(), }); const tokenData = await tokenRes.json(); return { accessToken: tokenData.access_token, refreshToken: tokenData.refresh_token, dpop, }; }