Archon/python/src/server/services/auth_service.py
Claude 65348bfed0
feat: Add Supabase authentication with login/signup pages
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
2025-11-15 04:13:17 +00:00

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()