diff --git a/src/components/ProtectedRoute.js b/src/components/ProtectedRoute.js
--- a/src/components/ProtectedRoute.js
+++ b/src/components/ProtectedRoute.js
@@ -9,50 +9,71 @@
const { isAuthenticated, loading, session, checkAuthStatus } = useAuth();
const location = useLocation();
const [redirecting, setRedirecting] = useState(false);
+ const [checkingStatus, setCheckingStatus] = useState(false);
const checkCount = useRef(0);
const maxChecks = 3; // Maximum number of checks to prevent infinite loops
+ // Perform an immediate auth check when the component mounts
useEffect(() => {
- // Prevent excessive auth checks
- if (checkCount.current >= maxChecks) {
- console.error("Maximum auth check attempts reached. Stopping to prevent infinite loop.");
- return;
- }
+ const checkAuth = async () => {
+ if (checkCount.current >= maxChecks) {
+ console.error("Maximum auth check attempts reached. Stopping to prevent infinite loop.");
+ return;
+ }
+
+ // Only proceed if not already checking, not already redirecting, and not loading
+ if (!isAuthenticated && !checkingStatus && !redirecting && !loading) {
+ try {
+ console.log("ProtectedRoute: Checking authentication status");
+ setCheckingStatus(true);
+ checkCount.current += 1;
+ await checkAuthStatus();
+ } catch (error) {
+ console.error("ProtectedRoute: Auth check failed:", error);
+ } finally {
+ setCheckingStatus(false);
+ }
+ }
+ };
- // Only check if not already authenticated and not already redirecting
- if (!isAuthenticated && !redirecting && !loading) {
- checkCount.current += 1;
- checkAuthStatus();
- }
+ // Call immediately on mount or when dependency values change
+ checkAuth();
- // Set up interval for periodic checks - but only if authenticated
- // This prevents constantly checking while unauthenticated
+ // Set up interval for periodic checks only if authenticated
let interval;
- if (isAuthenticated) {
- interval = setInterval(checkAuthStatus, 30000); // Check every 30 seconds
+ if (isAuthenticated && session) {
+ console.log("ProtectedRoute: Setting up periodic auth checks");
+ interval = setInterval(() => {
+ checkAuthStatus().catch(err => {
+ console.error("Error in periodic auth check:", err);
+ });
+ }, 30000); // Check every 30 seconds
}
return () => {
- if (interval) clearInterval(interval);
+ if (interval) {
+ console.log("ProtectedRoute: Clearing periodic auth checks");
+ clearInterval(interval);
+ }
};
- }, [isAuthenticated, checkAuthStatus, redirecting, loading]);
+ }, [isAuthenticated, checkAuthStatus, redirecting, loading, checkingStatus, session]);
// Show loading state while authentication is being checked
- if (loading) {
+ if (loading || checkingStatus) {
return ;
}
// If not authenticated, redirect to login with return URL
if (!isAuthenticated && !redirecting) {
+ console.log("ProtectedRoute: Not authenticated, redirecting to login");
setRedirecting(true); // Prevent multiple redirects
const returnUrl = encodeURIComponent(location.pathname);
return ;
}
// Check if user is allowed
- // Only check if we have detailed user info
- // If we're using server-side sessions, we might not need this check
if (session && session.handle && !isAccountAllowed(session)) {
+ console.log("ProtectedRoute: User not in allowlist, redirecting to supporter page");
return ;
}
@@ -60,6 +81,7 @@
checkCount.current = 0;
// Render children if authenticated and allowed
+ console.log("ProtectedRoute: Authentication successful, rendering protected content");
return children;
};
diff --git a/src/config/allowlist.js b/src/config/allowlist.js
--- a/src/config/allowlist.js
+++ b/src/config/allowlist.js
@@ -6,19 +6,41 @@
// Helper function to check if an account is allowed
export const isAccountAllowed = (session) => {
- if (!session) return false;
+ console.log('Checking if account is allowed:', session);
- // For Bluesky OAuth session
- if (session.sub && session.handle) {
- return ALLOWED_ACCOUNTS.includes(session.sub) ||
- ALLOWED_ACCOUNTS.includes(session.handle);
+ if (!session) {
+ console.log('No session provided, denying access');
+ return false;
}
- // For server-side session
- if (session.did && session.handle) {
- return ALLOWED_ACCOUNTS.includes(session.did) ||
- ALLOWED_ACCOUNTS.includes(session.handle);
+ // Extract DID from various possible session formats
+ const did = session.did || session.sub || null;
+
+ // Extract handle from various possible session formats
+ const handle = session.handle || null;
+
+ console.log(`Checking permissions for DID: ${did}, handle: ${handle}`);
+
+ // Check if either did or handle is in the allowlist
+ if (did && ALLOWED_ACCOUNTS.includes(did)) {
+ console.log('DID is in allowlist, granting access');
+ return true;
}
+ if (handle && ALLOWED_ACCOUNTS.includes(handle)) {
+ console.log('Handle is in allowlist, granting access');
+ return true;
+ }
+
+ // Also check if the handle (without domain) is in the allowlist
+ if (handle && handle.includes('.')) {
+ const handleWithoutDomain = handle.split('.')[0];
+ if (ALLOWED_ACCOUNTS.includes(handleWithoutDomain)) {
+ console.log('Handle (without domain) is in allowlist, granting access');
+ return true;
+ }
+ }
+
+ console.log('Account not in allowlist, denying access');
return false;
};
\ No newline at end of file
diff --git a/src/contexts/AuthContext.js b/src/contexts/AuthContext.js
--- a/src/contexts/AuthContext.js
+++ b/src/contexts/AuthContext.js
@@ -32,25 +32,37 @@
const [error, setError] = useState(null);
const lastAuthCheck = useRef(0);
const authCheckInProgress = useRef(false);
+ const didInitialCheck = useRef(false);
// Initialize the OAuth client
useEffect(() => {
const initializeAuth = async () => {
+ if (didInitialCheck.current) return;
+ didInitialCheck.current = true;
+
try {
// First check server-side authentication status
+ console.log('Checking server authentication status');
const serverAuthResponse = await fetch('/api/auth/status', {
credentials: 'include'
});
- const serverAuthData = await serverAuthResponse.json();
-
- if (serverAuthData.isAuthenticated) {
- setSession(serverAuthData.user);
- setLoading(false);
- return;
+ if (!serverAuthResponse.ok) {
+ console.error('Server auth check failed with status:', serverAuthResponse.status);
+ } else {
+ const serverAuthData = await serverAuthResponse.json();
+ console.log('Server auth status:', serverAuthData);
+
+ if (serverAuthData.isAuthenticated || serverAuthData.authenticated) {
+ console.log('Already authenticated on server, setting session');
+ setSession(serverAuthData.user);
+ setLoading(false);
+ return;
+ }
}
// If not authenticated on the server, check client OAuth
+ console.log('Not authenticated on server, initializing OAuth client');
const oauthClient = new BrowserOAuthClient({
clientMetadata,
handleResolver: 'https://bsky.social',
@@ -65,45 +77,76 @@
if (result?.session) {
console.log('Found existing OAuth session:', result.session);
- // If client has session but server doesn't, we need to sync them
+ // Check if atproto_session exists in localStorage as a backup
+ const atprotoSession = localStorage.getItem('atproto_session');
+ console.log('atproto_session in localStorage:', atprotoSession ? 'exists' : 'not found');
+
+ // Format session data for our internal use and sync with server
+ const sessionData = {
+ did: result.session.sub,
+ handle: result.session.handle
+ };
+
+ console.log('Syncing session with server:', sessionData);
+
+ // Try to sync with server
try {
const syncResponse = await fetch('/api/sync-session', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
- body: JSON.stringify({
- did: result.session.sub,
- handle: result.session.handle
- }),
+ body: JSON.stringify(sessionData),
credentials: 'include'
});
if (syncResponse.ok) {
const syncData = await syncResponse.json();
- console.log('Session sync successful:', syncData);
- // Use the server session data which may have more info
+ console.log('Initial session sync successful:', syncData);
setSession(syncData.user);
} else {
- console.warn('Session sync failed, using client session');
- // Still use the client session if sync fails
- setSession(result.session);
+ console.warn('Initial session sync failed:', await syncResponse.text());
+
+ // If sync fails, use client session in our internal format
+ console.log('Using client session as fallback');
+ setSession({
+ did: result.session.sub,
+ handle: result.session.handle,
+ displayName: result.session.handle
+ });
}
} catch (syncError) {
- console.error('Error syncing session:', syncError);
- // If sync fails, still use the client session
- setSession(result.session);
+ console.error('Error syncing initial session:', syncError);
+
+ // If sync fails, still use client session in our internal format
+ setSession({
+ did: result.session.sub,
+ handle: result.session.handle,
+ displayName: result.session.handle
+ });
}
+ } else {
+ console.log('No existing OAuth session found');
}
// Listen for session deletion events
oauthClient.addEventListener('deleted', (event) => {
- if (event.data.did === session?.sub || event.data.did === session?.did) {
+ console.log('Session deletion event received:', event.data);
+
+ // Get current session DID at the time of event
+ const currentSession = session;
+ const sessionDid = currentSession?.did || currentSession?.sub;
+
+ if (event.data.did === sessionDid) {
+ console.log('Current session was deleted, logging out');
setSession(null);
+
// Also logout from server
fetch('/api/logout', {
method: 'POST',
credentials: 'include'
+ }).catch(err => {
+ console.error('Error during server logout after deletion:', err);
});
}
});
@@ -224,13 +267,67 @@
const response = await fetch('/api/auth/status', {
credentials: 'include'
});
- const data = await response.json();
- if (data.isAuthenticated) {
- setSession(data.user);
+ if (!response.ok) {
+ console.error('Auth status check failed with status:', response.status);
+ authCheckInProgress.current = false;
+ return !!session; // Return current state on error
+ }
+
+ const data = await response.json();
+ console.log('Auth status check response:', data);
+
+ const isAuthenticated = data.isAuthenticated || data.authenticated;
+
+ if (isAuthenticated && data.user) {
+ // If server session is different from current session, update it
+ const currentSessionJSON = session ? JSON.stringify(session) : '';
+ const newSessionJSON = JSON.stringify(data.user);
+
+ if (currentSessionJSON !== newSessionJSON) {
+ console.log('Updating session from server data');
+ setSession(data.user);
+ }
+
authCheckInProgress.current = false;
return true;
} else {
+ // If server says not authenticated but we have a client session,
+ // try to synchronize sessions
+ if (session && client) {
+ try {
+ console.log('Server says not authenticated but we have a client session, trying to sync');
+
+ // Format session data properly
+ const sessionData = {
+ did: session.did || session.sub,
+ handle: session.handle
+ };
+
+ // Try to sync one more time
+ const syncResponse = await fetch('/api/sync-session', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(sessionData),
+ credentials: 'include'
+ });
+
+ if (syncResponse.ok) {
+ console.log('Session sync successful during status check');
+ const syncData = await syncResponse.json();
+ setSession(syncData.user);
+ authCheckInProgress.current = false;
+ return true;
+ }
+ } catch (syncError) {
+ console.error('Error syncing during status check:', syncError);
+ }
+ }
+
+ // If all attempts failed and the server says we're not authenticated
+ console.log('Server says not authenticated, clearing session');
setSession(null);
authCheckInProgress.current = false;
return false;
@@ -238,9 +335,9 @@
} catch (err) {
console.error('Error checking auth status:', err);
authCheckInProgress.current = false;
- return false;
+ return !!session; // Fall back to current session state
}
- }, [session]);
+ }, [session, client]);
return (