UNPKG

@narangcia-oss/cryptic-auth-client-plain-ts

Version:

A TypeScript client for interacting with a cryptic-auth host web server, crafted by Narangcia OSS.

177 lines (153 loc) 5.03 kB
import type { AuthTokens } from "../types/index"; export interface OAuth2FragmentResult { success: boolean; tokens?: AuthTokens; error?: string; errorDescription?: string; } /** * OAuth2 Fragment Handler for processing tokens from URL fragments * This handles the new OAuth2 flow where the backend redirects to frontend with tokens */ export class OAuth2FragmentHandler { private static hasProcessed = false; private static currentFragment = ""; /** * Checks if the current URL contains OAuth2 fragment parameters */ static isOAuth2Fragment(): boolean { const fragment = window.location.hash.substring(1); const params = new URLSearchParams(fragment); const hasFragment = params.has("access_token") || params.has("error"); console.log( "[OAuth2FragmentHandler] isOAuth2Fragment:", hasFragment, fragment ); return hasFragment; } /** * Resets the processing state (for testing or manual reset) */ static resetProcessingState(): void { this.hasProcessed = false; this.currentFragment = ""; } /** * Processes OAuth2 tokens from URL fragment */ static processFragment(): OAuth2FragmentResult { const fragment = window.location.hash.substring(1); // Prevent processing the same fragment multiple times if (this.hasProcessed && this.currentFragment === fragment) { console.log( "[OAuth2FragmentHandler] Fragment already processed, skipping" ); return { success: false, error: "already_processed", errorDescription: "This OAuth2 fragment has already been processed", }; } this.currentFragment = fragment; this.hasProcessed = true; const params = new URLSearchParams(fragment); console.log( "[OAuth2FragmentHandler] processFragment: fragment =", fragment ); // Check for error first if (params.has("error")) { const error = params.get("error") || "Unknown OAuth error"; const errorDescription = params.get("error_description") || "No description provided"; console.warn( "[OAuth2FragmentHandler] OAuth2 error detected:", error, errorDescription ); return { success: false, error, errorDescription, }; } // Extract tokens const accessToken = params.get("access_token"); const refreshToken = params.get("refresh_token"); const userId = params.get("user_id"); const tokenType = params.get("token_type"); // Usually "Bearer" const expiresIn = params.get("expires_in"); // Token expiration in seconds console.log("[OAuth2FragmentHandler] Extracted tokens:", { accessToken, refreshToken, userId, tokenType, expiresIn, }); if (!accessToken || !refreshToken || !userId) { console.error( "[OAuth2FragmentHandler] Missing required authentication parameters" ); return { success: false, error: "incomplete_auth_data", errorDescription: "Missing required authentication parameters", }; } return { success: true, tokens: { access_token: accessToken, refresh_token: refreshToken, // Store additional metadata if needed user_id: userId, token_type: tokenType || "Bearer", expires_in: expiresIn ? parseInt(expiresIn) : undefined, }, }; } /** * Clears OAuth2 parameters from URL fragment for security */ static clearFragment(): void { console.log("[OAuth2FragmentHandler] Clearing OAuth2 fragment from URL"); try { // Remove the fragment from URL without triggering navigation const newUrl = window.location.pathname + window.location.search; window.history.replaceState(null, "", newUrl); // Additional cleanup - ensure hash is completely removed if (window.location.hash) { window.location.hash = ""; } // Reset processing state after clearing this.resetProcessingState(); } catch (error) { console.error("[OAuth2FragmentHandler] Error clearing fragment:", error); // Fallback: reload the page without the fragment window.location.href = window.location.pathname + window.location.search; } } /** * Complete OAuth2 fragment processing - process and clean up */ static processAndClear(): OAuth2FragmentResult { console.log("[OAuth2FragmentHandler] processAndClear called"); const result = this.processFragment(); this.clearFragment(); console.log("[OAuth2FragmentHandler] processAndClear result:", result); return result; } } /** * Utility function to check if current page is an OAuth2 callback */ export function isOAuth2Callback(): boolean { return OAuth2FragmentHandler.isOAuth2Fragment(); } /** * Utility function to extract OAuth2 tokens from URL fragment */ export function extractOAuth2Tokens(): OAuth2FragmentResult { return OAuth2FragmentHandler.processAndClear(); }