Some checks failed
CI/CD — Build & Deploy / Build & Deploy (push) Failing after 21s
84 lines
2.4 KiB
JavaScript
84 lines
2.4 KiB
JavaScript
// 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);
|
|
}
|