/** * Auth Context Provider * Provides convenient access to auth state throughout the app * * Security Note: * - Context is for UI display only (showing/hiding buttons, user email, etc.) * - Server endpoints ALWAYS validate independently from cookies * - Never trust client-side state for authorization decisions */ import { createContext, useContext, createSignal, onMount, onCleanup, createEffect, Accessor, ParentComponent } from "solid-js"; import { createAsync, revalidate } from "@solidjs/router"; import { getUserState, type UserState } from "~/lib/auth-query"; interface AuthContextType { /** Current user state (for UI display) */ userState: Accessor; /** Is user authenticated (convenience) */ isAuthenticated: Accessor; /** User email (null if not authenticated) */ email: Accessor; /** User display name (null if not set) */ displayName: Accessor; /** User ID (null if not authenticated) */ userId: Accessor; /** Is user admin (for UI display only - server still validates) */ isAdmin: Accessor; /** Is email verified */ isEmailVerified: Accessor; /** Refresh auth state from server */ refreshAuth: () => void; } const AuthContext = createContext(); export const AuthProvider: ParentComponent = (props) => { // Get server state using createAsync which works with cache() const serverAuth = createAsync(() => getUserState(), { deferStream: true }); // Refresh callback that forces re-fetch const refreshAuth = () => { // Manually trigger a re-fetch by calling the revalidate function revalidate(["user-auth-state"]); }; // Convenience accessors with safe defaults - MUST BE DEFINED BEFORE onMount const isAuthenticated = () => serverAuth()?.isAuthenticated ?? false; const email = () => serverAuth()?.email ?? null; const displayName = () => serverAuth()?.displayName ?? null; const userId = () => serverAuth()?.userId ?? null; const isAdmin = () => serverAuth()?.isAdmin ?? false; const isEmailVerified = () => serverAuth()?.emailVerified ?? false; // Server handles all token refresh logic // Client just displays the current auth state from server // Listen for auth refresh events from external sources (token refresh, etc.) onMount(() => { if (typeof window === "undefined") return; const handleAuthRefresh = () => { console.log("[AuthContext] Received auth refresh event"); refreshAuth(); }; window.addEventListener("auth-state-changed", handleAuthRefresh); onCleanup(() => { window.removeEventListener("auth-state-changed", handleAuthRefresh); }); }); const value: AuthContextType = { userState: serverAuth, isAuthenticated, email, displayName, userId, isAdmin, isEmailVerified, refreshAuth }; return ( {props.children} ); }; /** * Hook to access auth state anywhere in the app * * @example * ```tsx * function MyComponent() { * const { isAuthenticated, email, refreshAuth } = useAuth(); * * return ( * *

Welcome, {email()}!

* *
* ); * } * ``` */ export function useAuth() { const context = useContext(AuthContext); if (!context) { throw new Error("useAuth must be used within AuthProvider"); } return context; }