Phase 2 of the full modernization: - App factory pattern with create_app() - Settings via dataclass with env vars - Dependency injection container - Router stubs for auth, storage, api, recipes, cache, runs - Service layer stubs for run, recipe, cache - Repository layer placeholder Routes are stubs that import from legacy server.py during migration. Next: Migrate each router fully with templates. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
120 lines
3.4 KiB
Python
120 lines
3.4 KiB
Python
"""
|
|
Authentication routes for L1 server.
|
|
|
|
L1 doesn't handle login directly - users log in at their L2 server.
|
|
Token is passed via URL from L2 redirect, then L1 sets its own cookie.
|
|
"""
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Request
|
|
from fastapi.responses import RedirectResponse
|
|
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
|
from pydantic import BaseModel
|
|
|
|
# Import auth utilities from existing server module
|
|
# TODO: Move these to a service
|
|
import sys
|
|
import os
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
|
|
|
router = APIRouter()
|
|
security = HTTPBearer(auto_error=False)
|
|
|
|
|
|
class RevokeUserRequest(BaseModel):
|
|
"""Request to revoke all tokens for a user."""
|
|
username: str
|
|
l2_server: str
|
|
|
|
|
|
@router.get("")
|
|
async def auth_callback(auth_token: str = None):
|
|
"""
|
|
Receive auth token from L2 redirect and set local cookie.
|
|
|
|
This enables cross-subdomain auth on iOS Safari which blocks shared cookies.
|
|
L2 redirects here with ?auth_token=... after user logs in.
|
|
"""
|
|
# Import here to avoid circular imports
|
|
from server import get_verified_user_context, register_user_token
|
|
|
|
if not auth_token:
|
|
return RedirectResponse(url="/", status_code=302)
|
|
|
|
# Verify the token is valid
|
|
ctx = await get_verified_user_context(auth_token)
|
|
if not ctx:
|
|
return RedirectResponse(url="/", status_code=302)
|
|
|
|
# Register token for this user (for revocation by username later)
|
|
register_user_token(ctx.username, auth_token)
|
|
|
|
# Set local first-party cookie and redirect to runs
|
|
response = RedirectResponse(url="/runs", status_code=302)
|
|
response.set_cookie(
|
|
key="auth_token",
|
|
value=auth_token,
|
|
httponly=True,
|
|
max_age=60 * 60 * 24 * 30, # 30 days
|
|
samesite="lax",
|
|
secure=True
|
|
)
|
|
return response
|
|
|
|
|
|
@router.get("/logout")
|
|
async def logout():
|
|
"""
|
|
Logout - clear local cookie and redirect to home.
|
|
|
|
Note: This only logs out of L1. User should also logout from L2.
|
|
"""
|
|
response = RedirectResponse(url="/", status_code=302)
|
|
response.delete_cookie("auth_token")
|
|
return response
|
|
|
|
|
|
@router.post("/revoke")
|
|
async def revoke_token(
|
|
credentials: HTTPAuthorizationCredentials = Depends(security),
|
|
):
|
|
"""
|
|
Revoke a token. Called by L2 when user logs out.
|
|
|
|
The token to revoke is passed in the Authorization header.
|
|
"""
|
|
from server import get_user_context_from_token, revoke_token as do_revoke_token
|
|
|
|
if not credentials:
|
|
raise HTTPException(401, "No token provided")
|
|
|
|
token = credentials.credentials
|
|
|
|
# Verify token is valid before revoking (ensures caller has the token)
|
|
ctx = get_user_context_from_token(token)
|
|
if not ctx:
|
|
raise HTTPException(401, "Invalid token")
|
|
|
|
# Revoke the token
|
|
newly_revoked = do_revoke_token(token)
|
|
|
|
return {"revoked": True, "newly_revoked": newly_revoked}
|
|
|
|
|
|
@router.post("/revoke-user")
|
|
async def revoke_user_tokens(request: RevokeUserRequest):
|
|
"""
|
|
Revoke all tokens for a user. Called by L2 when user logs out.
|
|
|
|
This handles the case where L2 issued scoped tokens that differ from L2's own token.
|
|
"""
|
|
from server import revoke_all_user_tokens
|
|
|
|
# Revoke all tokens registered for this user
|
|
count = revoke_all_user_tokens(request.username)
|
|
|
|
return {
|
|
"revoked": True,
|
|
"tokens_revoked": count,
|
|
"username": request.username
|
|
}
|