import { createContext, useCallback, useContext, useEffect, useMemo, useState, } from "react"; const STORAGE_KEY = "orcid_auth_token"; // Message type sent by AuthCallbackPage (runs in the OAuth popup) // to notify the parent window that authentication succeeded. export const AUTH_MESSAGE_TYPE = "ORCID_AUTH_TOKEN"; export const AUTH_ERROR_TYPE = "ORCID_AUTH_ERROR"; const AuthContext = createContext(null); /** * Provides JWT-based authentication state throughout the app. * * Authentication flow (OAuth 3-legged): * 1. User clicks "Iniciar sesión" → frontend opens popup at * GET /api/auth/orcid/authorize. * 2. Backend redirects to ORCID (sandbox or production). * 3. User authenticates at orcid.org. * 4. ORCID redirects to ORCID_REDIRECT_URI (= frontend /auth/callback). * 5. AuthCallbackPage exchanges the `code` for a JWT via the backend. * 6. Popup sends postMessage({ type: "ORCID_AUTH_TOKEN", token }) to * opener and closes itself. * 7. This provider's message listener stores the token and updates state. * * For development / sandbox bypass (VITE_AUTH_BYPASS=true), the token is * stored directly via storeToken() without going through ORCID. */ export function AuthProvider({ children }) { const [token, setToken] = useState(() => localStorage.getItem(STORAGE_KEY)); // Listen for messages from the OAuth popup window. useEffect(() => { function handleMessage(event) { // Only accept messages from the same origin (the React app itself, // running in the popup after the OAuth redirect lands there). if (event.origin !== window.location.origin) return; if (event.data?.type === AUTH_MESSAGE_TYPE && event.data?.token) { localStorage.setItem(STORAGE_KEY, event.data.token); setToken(event.data.token); } } window.addEventListener("message", handleMessage); return () => window.removeEventListener("message", handleMessage); }, []); /** * Stores a JWT directly (used by AuthCallbackPage and bypass mode). * Does NOT trigger any network request. */ const storeToken = useCallback((accessToken) => { localStorage.setItem(STORAGE_KEY, accessToken); setToken(accessToken); }, []); const logout = useCallback(() => { localStorage.removeItem(STORAGE_KEY); setToken(null); }, []); const value = useMemo( () => ({ token, isAuthenticated: Boolean(token), storeToken, logout }), [token, storeToken, logout], ); return {children}; } export function useAuth() { const ctx = useContext(AuthContext); if (!ctx) throw new Error("useAuth must be used inside "); return ctx; }