Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 84 additions & 22 deletions backend/routes/api_keys.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
"""API key management and metrics routes."""
from fastapi import APIRouter, Depends
from fastapi import APIRouter, HTTPException, Depends
from pydantic import BaseModel

from dependencies import api_key_manager, rate_limiter, metrics
from middleware.auth import require_auth, AuthContext
from services.observability import (
logger,
capture_exception,
track_time,
add_breadcrumb,
set_operation_context
)

router = APIRouter(prefix="", tags=["API Keys"])

Expand All @@ -18,7 +25,19 @@ async def get_performance_metrics(
auth: AuthContext = Depends(require_auth)
):
"""Get performance metrics and monitoring data."""
return metrics.get_metrics()
set_operation_context("get_metrics", user_id=auth.user_id)

logger.debug("Metrics requested", user_id=auth.user_id)

try:
with track_time("get_metrics"):
result = metrics.get_metrics()

return result
except Exception as e:
logger.error("Failed to get metrics", user_id=auth.user_id, error=str(e))
capture_exception(e, operation="get_metrics", user_id=auth.user_id)
raise HTTPException(status_code=500, detail="Failed to retrieve metrics")


@router.post("/keys/generate")
Expand All @@ -27,33 +46,76 @@ async def generate_api_key(
auth: AuthContext = Depends(require_auth)
):
"""Generate a new API key."""
new_key = api_key_manager.generate_key(
name=request.name,
tier=request.tier,
user_id=auth.user_id
set_operation_context("generate_api_key", user_id=auth.user_id, tier=request.tier)
add_breadcrumb("API key generation requested", category="api_keys", tier=request.tier)

logger.info(
"API key generation requested",
user_id=auth.user_id,
key_name=request.name,
tier=request.tier
)

return {
"api_key": new_key,
"tier": request.tier,
"name": request.name,
"message": "Save this key securely - it won't be shown again"
}
try:
with track_time("generate_api_key", tier=request.tier):
new_key = api_key_manager.generate_key(
name=request.name,
tier=request.tier,
user_id=auth.user_id
)

logger.info(
"API key generated successfully",
user_id=auth.user_id,
key_name=request.name,
tier=request.tier
)

return {
"api_key": new_key,
"tier": request.tier,
"name": request.name,
"message": "Save this key securely - it won't be shown again"
}
except Exception as e:
logger.error(
"API key generation failed",
user_id=auth.user_id,
key_name=request.name,
error=str(e)
)
capture_exception(
e,
operation="generate_api_key",
user_id=auth.user_id,
key_name=request.name
)
raise HTTPException(status_code=500, detail="Failed to generate API key")


@router.get("/keys/usage")
async def get_api_usage(
auth: AuthContext = Depends(require_auth)
):
"""Get current API usage stats."""
usage = rate_limiter.get_usage(auth.identifier)
set_operation_context("get_api_usage", user_id=auth.user_id, tier=auth.tier)

logger.debug("API usage requested", user_id=auth.user_id, tier=auth.tier)

return {
"tier": auth.tier,
"limits": {
"free": {"minute": 20, "hour": 200, "day": 1000},
"pro": {"minute": 100, "hour": 2000, "day": 20000},
"enterprise": {"minute": 500, "hour": 10000, "day": 100000}
}[auth.tier],
"usage": usage
}
try:
with track_time("get_api_usage"):
usage = rate_limiter.get_usage(auth.identifier)

return {
"tier": auth.tier,
"limits": {
"free": {"minute": 20, "hour": 200, "day": 1000},
"pro": {"minute": 100, "hour": 2000, "day": 20000},
"enterprise": {"minute": 500, "hour": 10000, "day": 100000}
}[auth.tier],
"usage": usage
}
except Exception as e:
logger.error("Failed to get API usage", user_id=auth.user_id, error=str(e))
capture_exception(e, operation="get_api_usage", user_id=auth.user_id)
raise HTTPException(status_code=500, detail="Failed to retrieve usage data")
116 changes: 101 additions & 15 deletions backend/routes/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@
from typing import Optional, Dict, Any
from services.auth import get_auth_service
from middleware.auth import get_current_user
from services.observability import (
logger,
capture_exception,
track_time,
add_breadcrumb,
set_operation_context
)

# Create router
router = APIRouter(prefix="/auth", tags=["Authentication"])
Expand Down Expand Up @@ -45,12 +52,31 @@ async def signup(request: SignupRequest):

Returns user data and session tokens (access_token, refresh_token)
"""
auth_service = get_auth_service()
return await auth_service.signup(
email=request.email,
password=request.password,
github_username=request.github_username
)
set_operation_context("auth_signup", email=request.email)
add_breadcrumb("Signup attempt", category="auth", email=request.email)

logger.info("Signup attempt", email=request.email, has_github=bool(request.github_username))

try:
auth_service = get_auth_service()

with track_time("auth_signup"):
result = await auth_service.signup(
email=request.email,
password=request.password,
github_username=request.github_username
)

logger.info("Signup successful", email=request.email)
return result

except HTTPException:
logger.warning("Signup failed (client error)", email=request.email)
raise
except Exception as e:
logger.error("Signup failed", email=request.email, error=str(e))
capture_exception(e, operation="auth_signup", email=request.email)
raise HTTPException(status_code=500, detail="Signup failed")


@router.post("/login", response_model=AuthResponse)
Expand All @@ -63,11 +89,30 @@ async def login(request: LoginRequest):

Returns user data and session tokens
"""
auth_service = get_auth_service()
return await auth_service.login(
email=request.email,
password=request.password
)
set_operation_context("auth_login", email=request.email)
add_breadcrumb("Login attempt", category="auth", email=request.email)

logger.info("Login attempt", email=request.email)

try:
auth_service = get_auth_service()

with track_time("auth_login"):
result = await auth_service.login(
email=request.email,
password=request.password
)

logger.info("Login successful", email=request.email)
return result

except HTTPException:
logger.warning("Login failed (invalid credentials)", email=request.email)
raise
except Exception as e:
logger.error("Login failed", email=request.email, error=str(e))
capture_exception(e, operation="auth_login", email=request.email)
raise HTTPException(status_code=500, detail="Login failed")


@router.post("/refresh")
Expand All @@ -79,8 +124,27 @@ async def refresh(request: RefreshRequest):

Returns new access token
"""
auth_service = get_auth_service()
return await auth_service.refresh_session(request.refresh_token)
set_operation_context("auth_refresh")
add_breadcrumb("Token refresh attempt", category="auth")

logger.debug("Token refresh attempt")

try:
auth_service = get_auth_service()

with track_time("auth_refresh"):
result = await auth_service.refresh_session(request.refresh_token)

logger.debug("Token refresh successful")
return result

except HTTPException:
logger.warning("Token refresh failed (invalid token)")
raise
except Exception as e:
logger.error("Token refresh failed", error=str(e))
capture_exception(e, operation="auth_refresh")
raise HTTPException(status_code=500, detail="Token refresh failed")


@router.post("/logout")
Expand All @@ -90,8 +154,25 @@ async def logout(user: Dict = Depends(get_current_user)):

Requires: Valid JWT token in Authorization header
"""
auth_service = get_auth_service()
return await auth_service.logout(token="") # Supabase handles session
user_id = user.get("id") or user.get("user_id")
set_operation_context("auth_logout", user_id=user_id)
add_breadcrumb("Logout attempt", category="auth", user_id=user_id)

logger.info("Logout attempt", user_id=user_id)

try:
auth_service = get_auth_service()

with track_time("auth_logout"):
result = await auth_service.logout(token="") # Supabase handles session

logger.info("Logout successful", user_id=user_id)
return result

except Exception as e:
logger.error("Logout failed", user_id=user_id, error=str(e))
capture_exception(e, operation="auth_logout", user_id=user_id)
raise HTTPException(status_code=500, detail="Logout failed")


@router.get("/me")
Expand All @@ -103,4 +184,9 @@ async def get_current_user_info(user: Dict = Depends(get_current_user)):

Returns user profile data
"""
user_id = user.get("id") or user.get("user_id")
set_operation_context("auth_me", user_id=user_id)

logger.debug("User info requested", user_id=user_id)

return {"user": user}
Loading