📝 Reformat code for improved readability and consistency in the OAuth composable

This commit is contained in:
2026-01-29 15:21:20 +00:00
parent 0a5e1b9251
commit 8093cc8d2a

View File

@@ -1,8 +1,8 @@
/** /**
* Unified OAuth Composable * Unified OAuth Composable
* *
* Handles OAuth flow for multiple providers (Challonge, Discord, etc.) * Handles OAuth flow for multiple providers (Challonge, Discord, etc.)
* *
* Features: * Features:
* - Multi-provider token storage with localStorage persistence * - Multi-provider token storage with localStorage persistence
* - Authorization URL generation with return_to support * - Authorization URL generation with return_to support
@@ -11,7 +11,7 @@
* - Automatic token refresh with 5-minute expiry buffer * - Automatic token refresh with 5-minute expiry buffer
* - Token validation and cleanup * - Token validation and cleanup
* - Comprehensive error handling * - Comprehensive error handling
* *
* Usage: * Usage:
* const oauth = useOAuth('challonge'); * const oauth = useOAuth('challonge');
* oauth.login({ scope: 'tournaments:read tournaments:write', return_to: '/challonge-test' }); * oauth.login({ scope: 'tournaments:read tournaments:write', return_to: '/challonge-test' });
@@ -111,20 +111,24 @@ export function useOAuth(provider = 'challonge') {
/** /**
* Generate authorization URL for OAuth flow * Generate authorization URL for OAuth flow
* *
* @param {string|Object} scopeOrOptions - Scope string or options object * @param {string|Object} scopeOrOptions - Scope string or options object
* @param {Object} options - Additional options (scope, return_to) * @param {Object} options - Additional options (scope, return_to)
* @returns {Object} {authUrl, state, returnTo} * @returns {Object} {authUrl, state, returnTo}
* @throws {Error} If OAuth credentials not configured * @throws {Error} If OAuth credentials not configured
*/ */
function getAuthorizationUrl(scopeOrOptions, options = {}) { function getAuthorizationUrl(scopeOrOptions, options = {}) {
const clientId = import.meta.env[`VITE_${provider.toUpperCase()}_CLIENT_ID`]; const clientId = import.meta.env[
const redirectUri = import.meta.env[`VITE_${provider.toUpperCase()}_REDIRECT_URI`]; `VITE_${provider.toUpperCase()}_CLIENT_ID`
];
const redirectUri = import.meta.env[
`VITE_${provider.toUpperCase()}_REDIRECT_URI`
];
if (!clientId || !redirectUri) { if (!clientId || !redirectUri) {
throw new Error( throw new Error(
`OAuth credentials not configured for ${provider}. ` + `OAuth credentials not configured for ${provider}. ` +
`Check VITE_${provider.toUpperCase()}_CLIENT_ID and VITE_${provider.toUpperCase()}_REDIRECT_URI in .env` `Check VITE_${provider.toUpperCase()}_CLIENT_ID and VITE_${provider.toUpperCase()}_REDIRECT_URI in .env`
); );
} }
@@ -167,7 +171,7 @@ export function useOAuth(provider = 'challonge') {
/** /**
* Start OAuth authorization flow * Start OAuth authorization flow
* Redirects user to OAuth provider * Redirects user to OAuth provider
* *
* @param {Object} options - Options including scope and return_to * @param {Object} options - Options including scope and return_to
* @throws {Error} If OAuth credentials missing * @throws {Error} If OAuth credentials missing
*/ */
@@ -182,7 +186,10 @@ export function useOAuth(provider = 'challonge') {
sessionStorage.setItem('oauth_return_to', returnTo); sessionStorage.setItem('oauth_return_to', returnTo);
} }
console.log(`🔐 Starting ${provider} OAuth flow with state:`, state.substring(0, 8) + '...'); console.log(
`🔐 Starting ${provider} OAuth flow with state:`,
state.substring(0, 8) + '...'
);
// Redirect to OAuth provider // Redirect to OAuth provider
window.location.href = authUrl; window.location.href = authUrl;
@@ -196,7 +203,7 @@ export function useOAuth(provider = 'challonge') {
/** /**
* Exchange authorization code for access token * Exchange authorization code for access token
* Called from OAuth callback page * Called from OAuth callback page
* *
* @param {string} code - Authorization code from OAuth provider * @param {string} code - Authorization code from OAuth provider
* @param {string} stateParam - State parameter for CSRF validation * @param {string} stateParam - State parameter for CSRF validation
* @returns {Promise<Object>} Tokens object {access_token, refresh_token, expires_at, ...} * @returns {Promise<Object>} Tokens object {access_token, refresh_token, expires_at, ...}
@@ -214,7 +221,9 @@ export function useOAuth(provider = 'challonge') {
} }
if (storedProvider !== provider) { if (storedProvider !== provider) {
const err = new Error(`Provider mismatch: expected ${storedProvider}, got ${provider}`); const err = new Error(
`Provider mismatch: expected ${storedProvider}, got ${provider}`
);
state.error.value = err.message; state.error.value = err.message;
throw err; throw err;
} }
@@ -268,7 +277,9 @@ export function useOAuth(provider = 'challonge') {
sessionStorage.removeItem('oauth_provider'); sessionStorage.removeItem('oauth_provider');
sessionStorage.removeItem('oauth_return_to'); sessionStorage.removeItem('oauth_return_to');
console.log(`${provider} OAuth authentication successful, expires in ${data.expires_in}s`); console.log(
`${provider} OAuth authentication successful, expires in ${data.expires_in}s`
);
return tokens; return tokens;
} catch (err) { } catch (err) {
state.error.value = err.message; state.error.value = err.message;
@@ -282,7 +293,7 @@ export function useOAuth(provider = 'challonge') {
/** /**
* Refresh access token using refresh token * Refresh access token using refresh token
* Called when token is expired or about to expire * Called when token is expired or about to expire
* *
* @returns {Promise<Object>} Updated tokens object * @returns {Promise<Object>} Updated tokens object
* @throws {Error} If no refresh token available or refresh fails * @throws {Error} If no refresh token available or refresh fails
*/ */
@@ -331,7 +342,9 @@ export function useOAuth(provider = 'challonge') {
state.tokens.value = tokens; state.tokens.value = tokens;
localStorage.setItem(state.storageKey, JSON.stringify(tokens)); localStorage.setItem(state.storageKey, JSON.stringify(tokens));
console.log(`${provider} token refreshed, new expiry in ${data.expires_in}s`); console.log(
`${provider} token refreshed, new expiry in ${data.expires_in}s`
);
return tokens; return tokens;
} catch (err) { } catch (err) {
state.error.value = err.message; state.error.value = err.message;
@@ -348,7 +361,7 @@ export function useOAuth(provider = 'challonge') {
/** /**
* Get valid access token, refreshing if necessary * Get valid access token, refreshing if necessary
* Automatically refreshes tokens expiring within 5 minutes * Automatically refreshes tokens expiring within 5 minutes
* *
* @returns {Promise<string>} Valid access token * @returns {Promise<string>} Valid access token
* @throws {Error} If not authenticated * @throws {Error} If not authenticated
*/ */
@@ -363,7 +376,9 @@ export function useOAuth(provider = 'challonge') {
// Refresh if expired or expiring within 5 minutes // Refresh if expired or expiring within 5 minutes
if (expiresIn < fiveMinutes) { if (expiresIn < fiveMinutes) {
console.log(`🔄 ${provider} token expiring in ${Math.floor(expiresIn / 1000)}s, refreshing...`); console.log(
`🔄 ${provider} token expiring in ${Math.floor(expiresIn / 1000)}s, refreshing...`
);
await refreshTokenFn(); await refreshTokenFn();
} }
@@ -386,13 +401,15 @@ export function useOAuth(provider = 'challonge') {
/** /**
* Generate random state for CSRF protection * Generate random state for CSRF protection
* Uses crypto.getRandomValues for secure randomness * Uses crypto.getRandomValues for secure randomness
* *
* @returns {string} 64-character hex string * @returns {string} 64-character hex string
*/ */
function generateState() { function generateState() {
const array = new Uint8Array(32); const array = new Uint8Array(32);
crypto.getRandomValues(array); crypto.getRandomValues(array);
return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join(''); return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join(
''
);
} }
return { return {