···99 const { isAuthenticated, loading, session, checkAuthStatus } = useAuth();
1010 const location = useLocation();
1111 const [redirecting, setRedirecting] = useState(false);
1212+ const [checkingStatus, setCheckingStatus] = useState(false);
1213 const checkCount = useRef(0);
1314 const maxChecks = 3; // Maximum number of checks to prevent infinite loops
14151616+ // Perform an immediate auth check when the component mounts
1517 useEffect(() => {
1616- // Prevent excessive auth checks
1717- if (checkCount.current >= maxChecks) {
1818- console.error("Maximum auth check attempts reached. Stopping to prevent infinite loop.");
1919- return;
2020- }
1818+ const checkAuth = async () => {
1919+ if (checkCount.current >= maxChecks) {
2020+ console.error("Maximum auth check attempts reached. Stopping to prevent infinite loop.");
2121+ return;
2222+ }
2323+2424+ // Only proceed if not already checking, not already redirecting, and not loading
2525+ if (!isAuthenticated && !checkingStatus && !redirecting && !loading) {
2626+ try {
2727+ console.log("ProtectedRoute: Checking authentication status");
2828+ setCheckingStatus(true);
2929+ checkCount.current += 1;
3030+ await checkAuthStatus();
3131+ } catch (error) {
3232+ console.error("ProtectedRoute: Auth check failed:", error);
3333+ } finally {
3434+ setCheckingStatus(false);
3535+ }
3636+ }
3737+ };
21382222- // Only check if not already authenticated and not already redirecting
2323- if (!isAuthenticated && !redirecting && !loading) {
2424- checkCount.current += 1;
2525- checkAuthStatus();
2626- }
3939+ // Call immediately on mount or when dependency values change
4040+ checkAuth();
27412828- // Set up interval for periodic checks - but only if authenticated
2929- // This prevents constantly checking while unauthenticated
4242+ // Set up interval for periodic checks only if authenticated
3043 let interval;
3131- if (isAuthenticated) {
3232- interval = setInterval(checkAuthStatus, 30000); // Check every 30 seconds
4444+ if (isAuthenticated && session) {
4545+ console.log("ProtectedRoute: Setting up periodic auth checks");
4646+ interval = setInterval(() => {
4747+ checkAuthStatus().catch(err => {
4848+ console.error("Error in periodic auth check:", err);
4949+ });
5050+ }, 30000); // Check every 30 seconds
3351 }
34523553 return () => {
3636- if (interval) clearInterval(interval);
5454+ if (interval) {
5555+ console.log("ProtectedRoute: Clearing periodic auth checks");
5656+ clearInterval(interval);
5757+ }
3758 };
3838- }, [isAuthenticated, checkAuthStatus, redirecting, loading]);
5959+ }, [isAuthenticated, checkAuthStatus, redirecting, loading, checkingStatus, session]);
39604061 // Show loading state while authentication is being checked
4141- if (loading) {
6262+ if (loading || checkingStatus) {
4263 return <Loading message="Checking authentication..." />;
4364 }
44654566 // If not authenticated, redirect to login with return URL
4667 if (!isAuthenticated && !redirecting) {
6868+ console.log("ProtectedRoute: Not authenticated, redirecting to login");
4769 setRedirecting(true); // Prevent multiple redirects
4870 const returnUrl = encodeURIComponent(location.pathname);
4971 return <Navigate to={`/login?returnUrl=${returnUrl}`} replace />;
5072 }
51735274 // Check if user is allowed
5353- // Only check if we have detailed user info
5454- // If we're using server-side sessions, we might not need this check
5575 if (session && session.handle && !isAccountAllowed(session)) {
7676+ console.log("ProtectedRoute: User not in allowlist, redirecting to supporter page");
5677 return <Navigate to="/supporter" replace />;
5778 }
5879···6081 checkCount.current = 0;
61826283 // Render children if authenticated and allowed
8484+ console.log("ProtectedRoute: Authentication successful, rendering protected content");
6385 return children;
6486};
6587
+31-9
src/config/allowlist.js
···6677// Helper function to check if an account is allowed
88export const isAccountAllowed = (session) => {
99- if (!session) return false;
99+ console.log('Checking if account is allowed:', session);
10101111- // For Bluesky OAuth session
1212- if (session.sub && session.handle) {
1313- return ALLOWED_ACCOUNTS.includes(session.sub) ||
1414- ALLOWED_ACCOUNTS.includes(session.handle);
1111+ if (!session) {
1212+ console.log('No session provided, denying access');
1313+ return false;
1514 }
16151717- // For server-side session
1818- if (session.did && session.handle) {
1919- return ALLOWED_ACCOUNTS.includes(session.did) ||
2020- ALLOWED_ACCOUNTS.includes(session.handle);
1616+ // Extract DID from various possible session formats
1717+ const did = session.did || session.sub || null;
1818+1919+ // Extract handle from various possible session formats
2020+ const handle = session.handle || null;
2121+2222+ console.log(`Checking permissions for DID: ${did}, handle: ${handle}`);
2323+2424+ // Check if either did or handle is in the allowlist
2525+ if (did && ALLOWED_ACCOUNTS.includes(did)) {
2626+ console.log('DID is in allowlist, granting access');
2727+ return true;
2128 }
22293030+ if (handle && ALLOWED_ACCOUNTS.includes(handle)) {
3131+ console.log('Handle is in allowlist, granting access');
3232+ return true;
3333+ }
3434+3535+ // Also check if the handle (without domain) is in the allowlist
3636+ if (handle && handle.includes('.')) {
3737+ const handleWithoutDomain = handle.split('.')[0];
3838+ if (ALLOWED_ACCOUNTS.includes(handleWithoutDomain)) {
3939+ console.log('Handle (without domain) is in allowlist, granting access');
4040+ return true;
4141+ }
4242+ }
4343+4444+ console.log('Account not in allowlist, denying access');
2345 return false;
2446};
+121-24
src/contexts/AuthContext.js
···3232 const [error, setError] = useState(null);
3333 const lastAuthCheck = useRef(0);
3434 const authCheckInProgress = useRef(false);
3535+ const didInitialCheck = useRef(false);
35363637 // Initialize the OAuth client
3738 useEffect(() => {
3839 const initializeAuth = async () => {
4040+ if (didInitialCheck.current) return;
4141+ didInitialCheck.current = true;
4242+3943 try {
4044 // First check server-side authentication status
4545+ console.log('Checking server authentication status');
4146 const serverAuthResponse = await fetch('/api/auth/status', {
4247 credentials: 'include'
4348 });
44494545- const serverAuthData = await serverAuthResponse.json();
4646-4747- if (serverAuthData.isAuthenticated) {
4848- setSession(serverAuthData.user);
4949- setLoading(false);
5050- return;
5050+ if (!serverAuthResponse.ok) {
5151+ console.error('Server auth check failed with status:', serverAuthResponse.status);
5252+ } else {
5353+ const serverAuthData = await serverAuthResponse.json();
5454+ console.log('Server auth status:', serverAuthData);
5555+5656+ if (serverAuthData.isAuthenticated || serverAuthData.authenticated) {
5757+ console.log('Already authenticated on server, setting session');
5858+ setSession(serverAuthData.user);
5959+ setLoading(false);
6060+ return;
6161+ }
5162 }
52635364 // If not authenticated on the server, check client OAuth
6565+ console.log('Not authenticated on server, initializing OAuth client');
5466 const oauthClient = new BrowserOAuthClient({
5567 clientMetadata,
5668 handleResolver: 'https://bsky.social',
···6577 if (result?.session) {
6678 console.log('Found existing OAuth session:', result.session);
67796868- // If client has session but server doesn't, we need to sync them
8080+ // Check if atproto_session exists in localStorage as a backup
8181+ const atprotoSession = localStorage.getItem('atproto_session');
8282+ console.log('atproto_session in localStorage:', atprotoSession ? 'exists' : 'not found');
8383+8484+ // Format session data for our internal use and sync with server
8585+ const sessionData = {
8686+ did: result.session.sub,
8787+ handle: result.session.handle
8888+ };
8989+9090+ console.log('Syncing session with server:', sessionData);
9191+9292+ // Try to sync with server
6993 try {
7094 const syncResponse = await fetch('/api/sync-session', {
7195 method: 'POST',
7296 headers: {
7397 'Content-Type': 'application/json'
7498 },
7575- body: JSON.stringify({
7676- did: result.session.sub,
7777- handle: result.session.handle
7878- }),
9999+ body: JSON.stringify(sessionData),
79100 credentials: 'include'
80101 });
8110282103 if (syncResponse.ok) {
83104 const syncData = await syncResponse.json();
8484- console.log('Session sync successful:', syncData);
8585- // Use the server session data which may have more info
105105+ console.log('Initial session sync successful:', syncData);
86106 setSession(syncData.user);
87107 } else {
8888- console.warn('Session sync failed, using client session');
8989- // Still use the client session if sync fails
9090- setSession(result.session);
108108+ console.warn('Initial session sync failed:', await syncResponse.text());
109109+110110+ // If sync fails, use client session in our internal format
111111+ console.log('Using client session as fallback');
112112+ setSession({
113113+ did: result.session.sub,
114114+ handle: result.session.handle,
115115+ displayName: result.session.handle
116116+ });
91117 }
92118 } catch (syncError) {
9393- console.error('Error syncing session:', syncError);
9494- // If sync fails, still use the client session
9595- setSession(result.session);
119119+ console.error('Error syncing initial session:', syncError);
120120+121121+ // If sync fails, still use client session in our internal format
122122+ setSession({
123123+ did: result.session.sub,
124124+ handle: result.session.handle,
125125+ displayName: result.session.handle
126126+ });
96127 }
128128+ } else {
129129+ console.log('No existing OAuth session found');
97130 }
9813199132 // Listen for session deletion events
100133 oauthClient.addEventListener('deleted', (event) => {
101101- if (event.data.did === session?.sub || event.data.did === session?.did) {
134134+ console.log('Session deletion event received:', event.data);
135135+136136+ // Get current session DID at the time of event
137137+ const currentSession = session;
138138+ const sessionDid = currentSession?.did || currentSession?.sub;
139139+140140+ if (event.data.did === sessionDid) {
141141+ console.log('Current session was deleted, logging out');
102142 setSession(null);
143143+103144 // Also logout from server
104145 fetch('/api/logout', {
105146 method: 'POST',
106147 credentials: 'include'
148148+ }).catch(err => {
149149+ console.error('Error during server logout after deletion:', err);
107150 });
108151 }
109152 });
···224267 const response = await fetch('/api/auth/status', {
225268 credentials: 'include'
226269 });
270270+271271+ if (!response.ok) {
272272+ console.error('Auth status check failed with status:', response.status);
273273+ authCheckInProgress.current = false;
274274+ return !!session; // Return current state on error
275275+ }
276276+227277 const data = await response.json();
278278+ console.log('Auth status check response:', data);
228279229229- if (data.isAuthenticated) {
230230- setSession(data.user);
280280+ const isAuthenticated = data.isAuthenticated || data.authenticated;
281281+282282+ if (isAuthenticated && data.user) {
283283+ // If server session is different from current session, update it
284284+ const currentSessionJSON = session ? JSON.stringify(session) : '';
285285+ const newSessionJSON = JSON.stringify(data.user);
286286+287287+ if (currentSessionJSON !== newSessionJSON) {
288288+ console.log('Updating session from server data');
289289+ setSession(data.user);
290290+ }
291291+231292 authCheckInProgress.current = false;
232293 return true;
233294 } else {
295295+ // If server says not authenticated but we have a client session,
296296+ // try to synchronize sessions
297297+ if (session && client) {
298298+ try {
299299+ console.log('Server says not authenticated but we have a client session, trying to sync');
300300+301301+ // Format session data properly
302302+ const sessionData = {
303303+ did: session.did || session.sub,
304304+ handle: session.handle
305305+ };
306306+307307+ // Try to sync one more time
308308+ const syncResponse = await fetch('/api/sync-session', {
309309+ method: 'POST',
310310+ headers: {
311311+ 'Content-Type': 'application/json'
312312+ },
313313+ body: JSON.stringify(sessionData),
314314+ credentials: 'include'
315315+ });
316316+317317+ if (syncResponse.ok) {
318318+ console.log('Session sync successful during status check');
319319+ const syncData = await syncResponse.json();
320320+ setSession(syncData.user);
321321+ authCheckInProgress.current = false;
322322+ return true;
323323+ }
324324+ } catch (syncError) {
325325+ console.error('Error syncing during status check:', syncError);
326326+ }
327327+ }
328328+329329+ // If all attempts failed and the server says we're not authenticated
330330+ console.log('Server says not authenticated, clearing session');
234331 setSession(null);
235332 authCheckInProgress.current = false;
236333 return false;
···238335 } catch (err) {
239336 console.error('Error checking auth status:', err);
240337 authCheckInProgress.current = false;
241241- return false;
338338+ return !!session; // Fall back to current session state
242339 }
243243- }, [session]);
340340+ }, [session, client]);
244341245342 return (
246343 <AuthContext.Provider