fix: Resolve Supabase auth loop and implement secure authentication system

This commit fixes the persistent login/redirect loop issue and implements
a robust authentication system for the Docker/localhost environment.

Key Changes:
- Environment-aware cookie configuration in supabase-ssr.ts
- New AuthLoader component to prevent content flashing during auth checks
- Cleaned up login page client-side auth logic to prevent redirect loops
- Updated dashboard to use AuthLoader for smooth authentication experience

Technical Details:
- Cookies now use environment-appropriate security settings
- Server-side auth verification eliminates client-side timing issues
- Loading states provide better UX during auth transitions
- Unified authentication pattern across all protected pages

Fixes:
- Dashboard no longer flashes before auth redirect
- Login page loads cleanly without auth checking loops
- Cookie configuration works correctly in Docker localhost
- No more redirect loops between login and dashboard pages

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-07-12 21:40:41 -06:00
parent 83470449e8
commit 57b23a304c
4 changed files with 300 additions and 107 deletions

View File

@@ -1,6 +1,7 @@
---
import Layout from '../layouts/Layout.astro';
import Navigation from '../components/Navigation.astro';
import AuthLoader from '../components/AuthLoader.astro';
import { verifyAuth } from '../lib/auth';
// Enable server-side rendering for auth checks
@@ -14,6 +15,7 @@ if (!auth) {
---
<Layout title="Dashboard - Black Canyon Tickets">
<AuthLoader message="Loading your dashboard...">
<style>
@keyframes fadeInUp {
0% {
@@ -829,4 +831,6 @@ if (!auth) {
// Load events directly (auth already verified server-side)
loadEvents();
</script>
</script>
</AuthLoader>

View File

@@ -293,11 +293,7 @@ const csrfToken = generateCSRFToken();
const errorMessage = document.getElementById('error-message') as HTMLDivElement;
// Debug logging
console.log('[LOGIN] Page loaded, checking auth state...');
// Authentication state
let authCheckInProgress = false;
let redirectInProgress = false;
console.log('[LOGIN] Page loaded, login form ready');
let isSignUpMode = false;
@@ -387,103 +383,12 @@ const csrfToken = generateCSRFToken();
mainContent.style.display = 'flex';
}
// Safe redirect function with debouncing
function safeRedirect(path: string, delay = 500) {
if (redirectInProgress) {
console.log('[LOGIN] Redirect already in progress, ignoring');
return;
}
redirectInProgress = true;
console.log(`[LOGIN] Redirecting to ${path} in ${delay}ms...`);
setTimeout(() => {
window.location.pathname = path;
}, delay);
}
// Enhanced auth check with better error handling
async function checkAuthState() {
if (authCheckInProgress) {
console.log('[LOGIN] Auth check already in progress');
return;
}
// Note: Auth checking has been moved to server-side only
// The login page now focuses solely on the login/signup flow
authCheckInProgress = true;
console.log('[LOGIN] Starting auth check...');
try {
// Get current session with timeout
const authPromise = supabase.auth.getSession();
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('Auth check timeout')), 5000)
);
const { data: { session }, error } = await Promise.race([authPromise, timeoutPromise]) as any;
if (error) {
console.log('[LOGIN] Auth error:', error.message);
hideLoading();
authCheckInProgress = false;
return;
}
if (!session) {
console.log('[LOGIN] No active session, showing login form');
hideLoading();
authCheckInProgress = false;
return;
}
console.log('[LOGIN] Active session found, checking organization...');
// Check if user has an organization with timeout
const userPromise = supabase
.from('users')
.select('organization_id')
.eq('id', session.user.id)
.single();
const { data: userData, error: userError } = await Promise.race([
userPromise,
new Promise((_, reject) =>
setTimeout(() => reject(new Error('User data timeout')), 3000)
)
]) as any;
if (userError) {
console.log('[LOGIN] User data error:', userError.message);
hideLoading();
authCheckInProgress = false;
return;
}
// Check for return URL parameter (support both 'returnTo' and 'redirect')
const urlParams = new URLSearchParams(window.location.search);
const returnTo = urlParams.get('returnTo') || urlParams.get('redirect');
if (!userData?.organization_id) {
console.log('[LOGIN] No organization found, redirecting to onboarding');
safeRedirect('/onboarding/organization');
} else {
console.log('[LOGIN] Organization found, redirecting to', returnTo || '/dashboard');
safeRedirect(returnTo || '/dashboard');
}
} catch (error) {
console.error('[LOGIN] Auth check failed:', error);
hideLoading();
authCheckInProgress = false;
}
}
// Skip auth check on login page - let the form handle login flow
// Initial auth check with delay to prevent flashing
// setTimeout(() => {
// checkAuthState();
// }, 100);
// Just hide loading and show form immediately on login page
// On login page, immediately show the form since auth is handled by the login flow
// No need for auth checking on this page - users should be able to login regardless
hideLoading();
</script>