// OAuth 2.0 PKCE client — SuperOAuth consumer for Clickerz const OAUTH_URL = import.meta.env.VITE_OAUTH_URL || ''; const OAUTH_CLIENT_ID = import.meta.env.VITE_OAUTH_CLIENT_ID || ''; const SESSION_KEY_VERIFIER = 'clkz_pkce_verifier'; function base64UrlEncode(buffer) { return btoa(String.fromCharCode(...new Uint8Array(buffer))) .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/=/g, ''); } export function generateCodeVerifier() { const array = new Uint8Array(32); crypto.getRandomValues(array); return base64UrlEncode(array.buffer); } export async function generateCodeChallenge(verifier) { const data = new TextEncoder().encode(verifier); const digest = await crypto.subtle.digest('SHA-256', data); return base64UrlEncode(digest); } export async function buildAuthUrl(redirectUri, provider, scope = 'openid profile email', clientId = OAUTH_CLIENT_ID) { const verifier = generateCodeVerifier(); const challenge = await generateCodeChallenge(verifier); const state = base64UrlEncode(crypto.getRandomValues(new Uint8Array(16)).buffer); const params = new URLSearchParams({ response_type: 'code', client_id: clientId, redirect_uri: redirectUri, scope, state, provider, code_challenge: challenge, code_challenge_method: 'S256', }); return { url: `${OAUTH_URL}/oauth/authorize?${params.toString()}`, verifier, }; } export async function exchangeCode(code, verifier, redirectUri, clientId = OAUTH_CLIENT_ID) { const response = await fetch(`${OAUTH_URL}/oauth/token`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'authorization_code', client_id: clientId, code, code_verifier: verifier, redirect_uri: redirectUri, }).toString(), }); if (!response.ok) { const text = await response.text().catch(() => ''); throw new Error(`OAuth token exchange failed (${response.status}): ${text}`); } const data = await response.json(); if (!data.access_token) throw new Error('No access_token in OAuth response'); return data; } export function saveVerifier(verifier) { localStorage.setItem(SESSION_KEY_VERIFIER, verifier); } export function loadVerifier() { return localStorage.getItem(SESSION_KEY_VERIFIER); } export function clearVerifier() { localStorage.removeItem(SESSION_KEY_VERIFIER); }