/** * OIDC/OAuth2 helpers */ import fetch from 'node-fetch'; export interface OIDCConfig { issuer: string; clientId: string; clientSecret: string; redirectUri: string; } export interface TokenResponse { access_token: string; token_type: string; expires_in?: number; refresh_token?: string; id_token?: string; } export class OIDCProvider { constructor(private config: OIDCConfig) {} getAuthorizationUrl(state: string): string { const params = new URLSearchParams({ client_id: this.config.clientId, redirect_uri: this.config.redirectUri, response_type: 'code', scope: 'openid profile email', state, }); return `${this.config.issuer}/authorize?${params.toString()}`; } async exchangeCodeForToken(code: string): Promise { const tokenEndpoint = `${this.config.issuer}/token`; const params = new URLSearchParams({ grant_type: 'authorization_code', code, redirect_uri: this.config.redirectUri, client_id: this.config.clientId, client_secret: this.config.clientSecret, }); const response = await fetch(tokenEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: params.toString(), }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Token exchange failed: ${response.status} ${errorText}`); } const tokenData = (await response.json()) as TokenResponse; return tokenData.access_token; } }