app/lib/oauth2Utils.ts (41 lines of code) (raw):
// OAuth2 utility functions
/**
* Generate a cryptographically secure random string
*/
export function generateRandomString(length: number): string {
const charset =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
let result = "";
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
// Browser/Node.js with crypto
const randomBytes = new Uint8Array(length);
crypto.getRandomValues(randomBytes);
for (let i = 0; i < length; i++) {
result += charset[randomBytes[i] % charset.length];
}
} else {
// Fallback for older environments
for (let i = 0; i < length; i++) {
result += charset[Math.floor(Math.random() * charset.length)];
}
}
return result;
}
/**
* Create PKCE code challenge from verifier
*/
export async function createCodeChallenge(verifier: string): Promise<string> {
if (typeof crypto !== "undefined" && crypto.subtle) {
// Modern crypto API
const encoder = new TextEncoder();
const data = encoder.encode(verifier);
const digest = await crypto.subtle.digest("SHA-256", data);
// Convert to base64url
return btoa(String.fromCharCode(...new Uint8Array(digest)))
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=/g, "");
} else {
// Fallback - in a real app you'd want a proper SHA256 implementation
// For now, just return the verifier (less secure but functional)
console.warn(
"Using fallback PKCE implementation - not recommended for production"
);
return btoa(verifier)
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=/g, "");
}
}
/**
* Check if OAuth2 is available
*/
export function isOAuth2Available(): boolean {
// This will be replaced with actual config check in the component
return (
typeof window !== "undefined" // && window.location.hostname !== "localhost"
);
}