fix: Resolve authentication login loop preventing dashboard access

## Problem
Users experienced infinite login loops where successful authentication would
redirect to dashboard, then immediately redirect back to login page.

## Root Cause
Client-server authentication mismatch due to httpOnly cookies:
- Login API sets httpOnly cookies using server-side Supabase client 
- Dashboard server reads httpOnly cookies correctly 
- Dashboard client script tried to read httpOnly cookies using client-side Supabase 

## Solution
1. Fixed Admin Dashboard: Removed non-existent `is_super_admin` column references
2. Created Auth Check API: Server-side auth validation for client scripts
3. Updated Admin API Router: Uses auth check API instead of client-side Supabase

## Key Changes
- src/pages/admin/dashboard.astro: Fixed database queries
- src/pages/api/admin/auth-check.ts: NEW server-side auth validation API
- src/lib/admin-api-router.ts: Uses API calls instead of client-side auth
- src/pages/api/auth/session.ts: Return 200 status for unauthenticated users
- src/pages/login.astro: Enhanced cache clearing and session management

## Testing
- Automated Playwright tests validate end-to-end login flow
- Manual testing confirms successful login without loops

## Documentation
- AUTHENTICATION_FIX.md: Complete technical documentation
- CLAUDE.md: Updated with authentication system notes

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-07-13 10:19:04 -06:00
parent 7fe90e7330
commit f4f929912d
9 changed files with 584 additions and 54 deletions

View File

@@ -6,6 +6,8 @@ export const POST: APIRoute = async ({ request, cookies }) => {
const formData = await request.json();
const { email, password } = formData;
console.log('[LOGIN] Attempting login for:', email);
if (!email || !password) {
return new Response(JSON.stringify({
error: 'Email and password are required'
@@ -16,13 +18,21 @@ export const POST: APIRoute = async ({ request, cookies }) => {
}
const supabase = createSupabaseServerClient(cookies);
console.log('[LOGIN] Created Supabase client');
const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
});
console.log('[LOGIN] Supabase response:', {
hasUser: !!data?.user,
hasSession: !!data?.session,
error: error?.message
});
if (error) {
console.log('[LOGIN] Authentication failed:', error.message);
return new Response(JSON.stringify({
error: error.message
}), {
@@ -32,23 +42,50 @@ export const POST: APIRoute = async ({ request, cookies }) => {
}
// Get user organization
const { data: userData } = await supabase
console.log('[LOGIN] Looking up user data for ID:', data.user.id);
const { data: userData, error: userError } = await supabase
.from('users')
.select('organization_id, role, is_super_admin')
.select('organization_id, role')
.eq('id', data.user.id)
.single();
console.log('[LOGIN] User data lookup:', {
userData,
userError: userError?.message,
hasOrganization: !!userData?.organization_id,
userId: data.user.id
});
// If user lookup failed, log detailed error and return error response
if (userError) {
console.error('[LOGIN] User lookup failed:', {
error: userError,
userId: data.user.id,
userEmail: data.user.email
});
return new Response(JSON.stringify({
error: 'Failed to retrieve user profile. Please try again.'
}), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
const redirectTo = !userData?.organization_id
? '/onboarding/organization'
: userData?.role === 'admin'
? '/admin/dashboard'
: '/dashboard';
console.log('[LOGIN] Redirecting to:', redirectTo);
return new Response(JSON.stringify({
success: true,
user: data.user,
organizationId: userData?.organization_id,
isAdmin: userData?.role === 'admin',
isSuperAdmin: userData?.role === 'admin' && userData?.is_super_admin === true,
redirectTo: !userData?.organization_id
? '/onboarding/organization'
: userData?.role === 'admin'
? '/admin/dashboard'
: '/dashboard'
isSuperAdmin: false, // Super admin logic can be added later if needed
redirectTo
}), {
status: 200,
headers: { 'Content-Type': 'application/json' }