diff --git a/www/app/(auth)/userInfo.tsx b/www/app/(auth)/userInfo.tsx index 24b8f15e..bf6a5b62 100644 --- a/www/app/(auth)/userInfo.tsx +++ b/www/app/(auth)/userInfo.tsx @@ -8,9 +8,10 @@ export default function UserInfo() { const status = auth.status; const isLoading = status === "loading"; const isAuthenticated = status === "authenticated"; + const isRefreshing = status === "refreshing"; return isLoading ? ( - ) : !isAuthenticated ? ( + ) : !isAuthenticated && !isRefreshing ? ( { - console.warn( - "illegal state: authenticated but have no session/or access token. ignoring", - ); - return { status: "unauthenticated" as const }; - })() - : { status: "unauthenticated" as const }), + : status === "loading" && customSession + ? { status: "refreshing" as const } + : status === "authenticated" && customSession?.accessToken + ? { + status, + accessToken: customSession.accessToken, + accessTokenExpires: customSession.accessTokenExpires, + user: customSession.user, + } + : status === "authenticated" && !customSession?.accessToken + ? (() => { + console.warn( + "illegal state: authenticated but have no session/or access token. ignoring", + ); + return { status: "unauthenticated" as const }; + })() + : { status: "unauthenticated" as const }), update, signIn, signOut, diff --git a/www/app/lib/SessionAutoRefresh.tsx b/www/app/lib/SessionAutoRefresh.tsx index 9f4e2658..1b7b05f0 100644 --- a/www/app/lib/SessionAutoRefresh.tsx +++ b/www/app/lib/SessionAutoRefresh.tsx @@ -18,15 +18,17 @@ export function SessionAutoRefresh({ const accessTokenExpires = auth.status === "authenticated" ? auth.accessTokenExpires : null; + const refreshIntervalMs = refreshInterval * 1000; + useEffect(() => { const interval = setInterval(() => { - if (accessTokenExpires) { + if (accessTokenExpires !== null) { const timeLeft = accessTokenExpires - Date.now(); - if (timeLeft < refreshInterval * 1000) { + if (timeLeft < refreshIntervalMs) { auth.update(); } } - }, refreshInterval * 1000); + }, refreshIntervalMs); return () => clearInterval(interval); }, [accessTokenExpires, refreshInterval, auth.update]); diff --git a/www/app/lib/auth.ts b/www/app/lib/auth.ts index 51a7e909..1d2cc953 100644 --- a/www/app/lib/auth.ts +++ b/www/app/lib/auth.ts @@ -8,16 +8,14 @@ import { parseMaybeNonEmptyString, } from "./utils"; -const PRETIMEOUT = 60; // seconds before token expires to refresh it +const PRETIMEOUT = 600; -// Simple in-memory cache for tokens (in production, consider using a proper cache solution) const tokenCache = new Map< string, { token: JWTWithAccessToken; timestamp: number } >(); const TOKEN_CACHE_TTL = 60 * 60 * 24 * 30 * 1000; // 30 days in milliseconds -// Simple lock mechanism to prevent concurrent token refreshes const refreshLocks = new Map>(); const CLIENT_ID = assertExistsAndNonEmptyString( @@ -100,32 +98,25 @@ async function lockedRefreshAccessToken( ): Promise { const lockKey = `${token.sub}-refresh`; - // Check if there's already a refresh in progress const existingRefresh = refreshLocks.get(lockKey); if (existingRefresh) { - return existingRefresh; + return await existingRefresh; } - // Create a new refresh promise const refreshPromise = (async () => { try { - // Check cache for recent token const cached = tokenCache.get(`token:${token.sub}`); if (cached) { - // Clean up old cache entries if (Date.now() - cached.timestamp > TOKEN_CACHE_TTL) { tokenCache.delete(`token:${token.sub}`); } else if (Date.now() < cached.token.accessTokenExpires) { - // Token is still valid return cached.token; } } - // Refresh the token const currentToken = cached?.token || (token as JWTWithAccessToken); const newToken = await refreshAccessToken(currentToken); - // Update cache tokenCache.set(`token:${token.sub}`, { token: newToken, timestamp: Date.now(), @@ -133,7 +124,6 @@ async function lockedRefreshAccessToken( return newToken; } finally { - // Clean up the lock after a short delay setTimeout(() => refreshLocks.delete(lockKey), 100); } })();