Skip to content

Commit 7e65265

Browse files
committed
feat(backend): add Supabase authentication system
- SupabaseAuthService for JWT verification and user management - Auth middleware for protected routes (get_current_user, get_optional_user) - Auth routes: signup, login, refresh, logout, get user info - PyJWT dependency for token verification - Environment variables: SUPABASE_ANON_KEY, SUPABASE_JWT_SECRET Features: - JWT token verification with Supabase - User signup with email/password - Session management with refresh tokens - Logout with token cleanup - Protected endpoint decorator support Endpoints: - POST /api/auth/signup - Create new user account - POST /api/auth/login - Authenticate and get tokens - POST /api/auth/refresh - Refresh access token - POST /api/auth/logout - End session - GET /api/auth/me - Get current user info Ready for frontend integration with multi-tenant repository access
1 parent f137877 commit 7e65265

8 files changed

Lines changed: 381 additions & 1 deletion

File tree

backend/.env.example

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ PINECONE_INDEX_NAME=codeintel
55

66
# Supabase
77
SUPABASE_URL=https://your-project.supabase.co
8-
SUPABASE_KEY=your_supabase_anon_key_here
8+
SUPABASE_ANON_KEY=your_supabase_anon_key_here
9+
SUPABASE_JWT_SECRET=your_jwt_secret_here
910

1011
# Backend API
1112
BACKEND_API_URL=http://localhost:8000

backend/main.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,18 @@
2525
from services.supabase_service import get_supabase_service
2626
from services.input_validator import InputValidator, CostController
2727

28+
# Import routers
29+
from routes.auth import router as auth_router
30+
2831
app = FastAPI(
2932
title="CodeIntel API",
3033
description="Codebase Intelligence API for MCP",
3134
version="0.2.0"
3235
)
3336

37+
# Include routers
38+
app.include_router(auth_router)
39+
3440
# CORS middleware
3541
app.add_middleware(
3642
CORSMiddleware,

backend/middleware/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
"""Authentication middleware package"""
2+
from .auth import get_current_user, get_optional_user
3+
4+
__all__ = ["get_current_user", "get_optional_user"]

backend/middleware/auth.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"""
2+
Authentication Middleware
3+
Protects routes requiring authentication
4+
"""
5+
from fastapi import Depends, HTTPException, status
6+
from fastapi.security import HTTPBearer
7+
from fastapi.security.http import HTTPAuthorizationCredentials
8+
from typing import Dict, Any, Optional
9+
from services.auth import get_auth_service
10+
11+
# HTTP Bearer token scheme
12+
security = HTTPBearer()
13+
14+
15+
async def get_current_user(
16+
credentials: HTTPAuthorizationCredentials = Depends(security)
17+
) -> Dict[str, Any]:
18+
"""
19+
Dependency to get current authenticated user from JWT token
20+
21+
Usage:
22+
@app.get("/protected")
23+
async def protected_route(user: Dict = Depends(get_current_user)):
24+
return {"user_id": user["user_id"]}
25+
26+
Returns:
27+
Dict with user_id, email, and metadata
28+
29+
Raises:
30+
HTTPException: 401 if token invalid
31+
"""
32+
auth_service = get_auth_service()
33+
return auth_service.verify_jwt(credentials.credentials)
34+
35+
36+
async def get_optional_user(
37+
credentials: Optional[HTTPAuthorizationCredentials] = Depends(HTTPBearer(auto_error=False))
38+
) -> Optional[Dict[str, Any]]:
39+
"""
40+
Optional authentication - returns None if no token provided
41+
42+
Usage:
43+
@app.get("/optional-auth")
44+
async def route(user: Optional[Dict] = Depends(get_optional_user)):
45+
if user:
46+
return {"message": f"Hello {user['email']}"}
47+
return {"message": "Hello guest"}
48+
"""
49+
if not credentials:
50+
return None
51+
52+
try:
53+
auth_service = get_auth_service()
54+
return auth_service.verify_jwt(credentials.credentials)
55+
except:
56+
return None

backend/requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ httpx>=0.27.0
55
python-dotenv>=1.0.0
66
pydantic>=2.0.0
77
pydantic-settings>=2.0.0
8+
email-validator>=2.0.0 # Required for EmailStr validation
89

910
# WebSocket support
1011
websockets>=13.0
@@ -29,6 +30,7 @@ aiofiles>=24.0.0
2930

3031
# Supabase (postgrest-py handled as dependency)
3132
supabase>=2.0.0
33+
pyjwt>=2.8.0 # JWT token verification for Supabase Auth
3234

3335
# Testing
3436
pytest>=8.0.0

backend/routes/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
"""API Routes package"""
2+
from .auth import router as auth_router
3+
4+
__all__ = ["auth_router"]

backend/routes/auth.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
"""
2+
Authentication Routes
3+
Handles user signup, login, and session management
4+
"""
5+
from fastapi import APIRouter, HTTPException, Depends, status
6+
from pydantic import BaseModel, EmailStr
7+
from typing import Optional, Dict, Any
8+
from services.auth import get_auth_service
9+
from middleware.auth import get_current_user
10+
11+
# Create router
12+
router = APIRouter(prefix="/api/auth", tags=["Authentication"])
13+
14+
15+
# Request/Response Models
16+
class SignupRequest(BaseModel):
17+
email: EmailStr
18+
password: str
19+
github_username: Optional[str] = None
20+
21+
22+
class LoginRequest(BaseModel):
23+
email: EmailStr
24+
password: str
25+
26+
27+
class RefreshRequest(BaseModel):
28+
refresh_token: str
29+
30+
31+
class AuthResponse(BaseModel):
32+
user: Dict[str, Any]
33+
session: Dict[str, Any]
34+
35+
36+
# Routes
37+
@router.post("/signup", response_model=AuthResponse)
38+
async def signup(request: SignupRequest):
39+
"""
40+
Sign up a new user with Supabase Auth
41+
42+
- **email**: Valid email address
43+
- **password**: Password (min 6 characters recommended)
44+
- **github_username**: Optional GitHub username for profile
45+
46+
Returns user data and session tokens (access_token, refresh_token)
47+
"""
48+
auth_service = get_auth_service()
49+
return await auth_service.signup(
50+
email=request.email,
51+
password=request.password,
52+
github_username=request.github_username
53+
)
54+
55+
56+
@router.post("/login", response_model=AuthResponse)
57+
async def login(request: LoginRequest):
58+
"""
59+
Login with email and password
60+
61+
- **email**: Registered email address
62+
- **password**: User password
63+
64+
Returns user data and session tokens
65+
"""
66+
auth_service = get_auth_service()
67+
return await auth_service.login(
68+
email=request.email,
69+
password=request.password
70+
)
71+
72+
73+
@router.post("/refresh")
74+
async def refresh(request: RefreshRequest):
75+
"""
76+
Refresh access token using refresh token
77+
78+
- **refresh_token**: Valid refresh token from login/signup
79+
80+
Returns new access token
81+
"""
82+
auth_service = get_auth_service()
83+
return await auth_service.refresh_session(request.refresh_token)
84+
85+
86+
@router.post("/logout")
87+
async def logout(user: Dict = Depends(get_current_user)):
88+
"""
89+
Logout current user and invalidate session
90+
91+
Requires: Valid JWT token in Authorization header
92+
"""
93+
auth_service = get_auth_service()
94+
return await auth_service.logout(token="") # Supabase handles session
95+
96+
97+
@router.get("/me")
98+
async def get_current_user_info(user: Dict = Depends(get_current_user)):
99+
"""
100+
Get current authenticated user information
101+
102+
Requires: Valid JWT token in Authorization header
103+
104+
Returns user profile data
105+
"""
106+
return {"user": user}

0 commit comments

Comments
 (0)