Implements complete authentication system using Supabase Auth SDK following Archon's vertical slice architecture. Frontend Changes: - Install @supabase/supabase-js dependency - Create auth feature in vertical slice pattern: * AuthContext and Provider for global auth state * authService with Supabase Auth methods (signIn, signUp, signOut, etc.) * Auth query hooks with TanStack Query integration * TypeScript types for User, Session, AuthState * ProtectedRoute component for route guards - Add LoginPage and SignUpPage with Tron-themed design - Update App.tsx with AuthProvider and protected routes - Configure Supabase client with environment variables Backend Changes: - Create auth_service.py for JWT token validation - Create auth_middleware.py for protecting API routes (optional, commented by default) - Create auth_api.py with endpoints: * POST /api/auth/verify - Verify JWT token * GET /api/auth/user - Get current user * GET /api/auth/health - Auth service health check - Register auth router in main.py - Add middleware configuration (disabled by default) Configuration: - Update .env.example with VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY - Add comprehensive AUTHENTICATION_SETUP.md documentation Features: - Email/password authentication - Persistent sessions with localStorage - Auto token refresh - Route protection with loading states - Integration with existing TanStack Query patterns - Optional backend middleware for API protection - Row Level Security (RLS) ready Architecture follows CLAUDE.md guidelines: - Vertical slice architecture for auth feature - TanStack Query for state management - No backwards compatibility needed (beta) - KISS principle - Fail fast with detailed errors Notes: - Auth middleware is commented out by default to avoid breaking existing installations - Users can enable it when ready by uncommenting in main.py - Frontend auth works independently of backend middleware - Comprehensive setup guide included in AUTHENTICATION_SETUP.md
98 lines
2.7 KiB
Python
98 lines
2.7 KiB
Python
"""
|
|
Authentication service for validating JWT tokens and managing user sessions.
|
|
"""
|
|
|
|
from typing import Optional
|
|
|
|
from fastapi import HTTPException, Request
|
|
from supabase import Client
|
|
|
|
from ..utils import get_supabase_client
|
|
|
|
|
|
class AuthService:
|
|
"""Service for handling authentication operations."""
|
|
|
|
def __init__(self):
|
|
self.supabase: Client = get_supabase_client()
|
|
|
|
async def verify_token(self, token: str) -> dict:
|
|
"""
|
|
Verify a JWT token and return the user information.
|
|
|
|
Args:
|
|
token: JWT token from Authorization header
|
|
|
|
Returns:
|
|
dict: User information from Supabase
|
|
|
|
Raises:
|
|
HTTPException: If token is invalid or expired
|
|
"""
|
|
try:
|
|
response = self.supabase.auth.get_user(token)
|
|
|
|
if not response.user:
|
|
raise HTTPException(status_code=401, detail="Invalid authentication token")
|
|
|
|
return {
|
|
"id": response.user.id,
|
|
"email": response.user.email,
|
|
"user_metadata": response.user.user_metadata,
|
|
"app_metadata": response.user.app_metadata,
|
|
}
|
|
except Exception as e:
|
|
raise HTTPException(status_code=401, detail=f"Authentication failed: {str(e)}")
|
|
|
|
async def get_user_by_id(self, user_id: str) -> Optional[dict]:
|
|
"""
|
|
Get user information by user ID.
|
|
|
|
Args:
|
|
user_id: User ID from Supabase
|
|
|
|
Returns:
|
|
dict: User information or None if not found
|
|
"""
|
|
try:
|
|
response = self.supabase.auth.admin.get_user_by_id(user_id)
|
|
if not response.user:
|
|
return None
|
|
|
|
return {
|
|
"id": response.user.id,
|
|
"email": response.user.email,
|
|
"user_metadata": response.user.user_metadata,
|
|
"app_metadata": response.user.app_metadata,
|
|
}
|
|
except Exception:
|
|
return None
|
|
|
|
async def require_auth(self, request: Request) -> dict:
|
|
"""
|
|
Extract and verify authentication from request.
|
|
|
|
Args:
|
|
request: FastAPI request object
|
|
|
|
Returns:
|
|
dict: User information
|
|
|
|
Raises:
|
|
HTTPException: If authentication fails
|
|
"""
|
|
auth_header = request.headers.get("Authorization")
|
|
|
|
if not auth_header:
|
|
raise HTTPException(status_code=401, detail="Missing authorization header")
|
|
|
|
parts = auth_header.split()
|
|
if len(parts) != 2 or parts[0].lower() != "bearer":
|
|
raise HTTPException(status_code=401, detail="Invalid authorization header format")
|
|
|
|
token = parts[1]
|
|
return await self.verify_token(token)
|
|
|
|
|
|
auth_service = AuthService()
|