Skip to content

Commit dd11dde

Browse files
committed
Implement authentication request tracking and update metrics
- Added a new function to track authentication requests using Prometheus metrics in auth.py. - Updated login, registration, and email validation endpoints to call the tracking function. - Refactored Prometheus middleware to handle error responses and record metrics accurately. - Modified Grafana dashboard configuration to reflect changes in authentication metrics tracking.
1 parent 7d49a11 commit dd11dde

File tree

4 files changed

+100
-24
lines changed

4 files changed

+100
-24
lines changed

app/api/auth.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,38 @@
3636
from app.services.dynamodb import DynamoDBService
3737
from app.services.ses import SESService
3838
from app.api.teams import register_team
39+
from app.middleware.prometheus import auth_requests_total
40+
41+
def track_auth_request(request: Request, identifier: Optional[str] = None) -> None:
42+
"""
43+
Track authentication requests using Prometheus metrics.
44+
45+
Args:
46+
request: The FastAPI request object
47+
identifier: Optional identifier (username/email) for the request
48+
"""
49+
# Get the path from the request
50+
path = request.url.path
51+
52+
# If no identifier provided, try to extract from request
53+
if not identifier:
54+
try:
55+
# Try to get from form data
56+
form_data = request.form()
57+
identifier = form_data.get("username") or form_data.get("email")
58+
except:
59+
try:
60+
# Try to get from JSON body
61+
body = request.json()
62+
identifier = body.get("username") or body.get("email")
63+
except:
64+
identifier = "unknown"
65+
66+
# Increment the counter
67+
auth_requests_total.labels(
68+
endpoint=path,
69+
identifier=identifier or "unknown"
70+
).inc()
3971

4072
router = APIRouter(
4173
tags=["Authentication"]
@@ -156,6 +188,9 @@ async def login(
156188
detail="Invalid login data. Please provide username and password in either form data or JSON format."
157189
)
158190

191+
# Track the auth request
192+
track_auth_request(request, login_data.username)
193+
159194
user = db.query(DBUser).filter(DBUser.email == login_data.username).first()
160195
if not user or not verify_password(login_data.password, user.hashed_password):
161196
raise HTTPException(
@@ -243,7 +278,11 @@ async def update_user_me(
243278
return current_user
244279

245280
@router.post("/register", response_model=User)
246-
async def register(user: UserCreate, db: Session = Depends(get_db)):
281+
async def register(
282+
request: Request,
283+
user: UserCreate,
284+
db: Session = Depends(get_db)
285+
):
247286
"""
248287
Register a new user account.
249288
@@ -252,6 +291,9 @@ async def register(user: UserCreate, db: Session = Depends(get_db)):
252291
253292
After registration, you'll need to login to get an access token.
254293
"""
294+
# Track the auth request
295+
track_auth_request(request, user.email)
296+
255297
# Check if user with this email exists
256298
db_user = db.query(DBUser).filter(DBUser.email == user.email).first()
257299
if db_user:
@@ -309,6 +351,9 @@ async def validate_email(
309351
detail="Email is required"
310352
)
311353

354+
# Track the auth request
355+
track_auth_request(request, email)
356+
312357
try:
313358
email_validator.validate_email(email, check_deliverability=False)
314359
except EmailNotValidError as e:
@@ -442,6 +487,9 @@ async def sign_in(
442487
detail="Invalid sign in data. Please provide username and verification code in either form data or JSON format."
443488
)
444489

490+
# Track the auth request
491+
track_auth_request(request, sign_in_data.username)
492+
445493
# Verify the code using DynamoDB first
446494
dynamodb_service = DynamoDBService()
447495
stored_code = dynamodb_service.read_validation_code(sign_in_data.username)

app/main.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
from fastapi import FastAPI, Depends, HTTPException
2-
from fastapi.security import OAuth2PasswordBearer
3-
from sqlalchemy.orm import Session
1+
from fastapi import FastAPI
42
from fastapi.middleware.cors import CORSMiddleware
53
from fastapi.middleware.trustedhost import TrustedHostMiddleware
64
from starlette.middleware.base import BaseHTTPMiddleware

app/middleware/prometheus.py

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from prometheus_client import Counter, Histogram, Gauge
22
from prometheus_fastapi_instrumentator import Instrumentator, metrics
33
from prometheus_fastapi_instrumentator.metrics import Info
4-
from fastapi import Request
4+
from fastapi import Request, HTTPException, status
55
from starlette.middleware.base import BaseHTTPMiddleware
66
from app.core.security import get_current_user_from_auth
77
from app.db.database import get_db
@@ -45,25 +45,47 @@
4545
["user_id", "method", "endpoint"]
4646
)
4747

48+
# Auth Metrics
49+
auth_requests_total = Counter(
50+
"auth_requests_total",
51+
"Total number of authentication requests",
52+
["endpoint", "identifier"]
53+
)
54+
4855
class PrometheusMiddleware(BaseHTTPMiddleware):
4956
async def dispatch(self, request: Request, call_next):
5057
# Skip metrics for certain paths
5158
if request.url.path in ["/metrics", "/health", "/docs", "/openapi.json"]:
5259
return await call_next(request)
5360

5461
start_time = time.time()
62+
response = None
63+
duration = 0
64+
is_error = False
5565

56-
# Process the request
57-
response = await call_next(request)
58-
59-
# Calculate duration
60-
duration = time.time() - start_time
66+
try:
67+
# Process the request
68+
response = await call_next(request)
69+
duration = time.time() - start_time
70+
except Exception as e:
71+
logger.warning(f"Request failed: {e}")
72+
# Record the actual duration of the failed request
73+
duration = time.time() - start_time
74+
# Capture the error response to be raised later
75+
is_error = True
76+
if not isinstance(e, HTTPException):
77+
error_response = HTTPException(
78+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
79+
detail=str(e)
80+
)
81+
else:
82+
error_response = e
6183

6284
# Record RED metrics
6385
http_requests_total.labels(
6486
method=request.method,
6587
endpoint=request.url.path,
66-
status_code=response.status_code
88+
status_code=response.status_code if response else 500
6789
).inc()
6890

6991
http_request_duration_seconds.labels(
@@ -86,14 +108,19 @@ async def dispatch(self, request: Request, call_next):
86108
access_token = parts[1]
87109

88110
if access_token:
89-
db = next(get_db())
90-
user = await get_current_user_from_auth(
91-
access_token=access_token if access_token else None,
92-
authorization=auth_header if auth_header else None,
93-
db=db
94-
)
95-
user_id = str(user.id) if user else "anonymous"
96-
db.close()
111+
try:
112+
db = next(get_db())
113+
user = await get_current_user_from_auth(
114+
access_token=access_token if access_token else None,
115+
authorization=auth_header if auth_header else None,
116+
db=db
117+
)
118+
user_id = str(user.id) if user else "anonymous"
119+
except Exception as e:
120+
logger.debug(f"Could not get user for metrics: {str(e)}")
121+
user_id = "anonymous"
122+
finally:
123+
db.close()
97124
except Exception as e:
98125
logger.debug(f"Could not get user for metrics: {str(e)}")
99126
user_id = "anonymous"
@@ -105,4 +132,7 @@ async def dispatch(self, request: Request, call_next):
105132
endpoint=request.url.path
106133
).inc()
107134

108-
return response
135+
if is_error:
136+
raise error_response
137+
else:
138+
return response

grafana/provisioning/dashboards/fastapi.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -621,8 +621,8 @@
621621
"type": "prometheus",
622622
"uid": "prometheus"
623623
},
624-
"expr": "rate(auth_attempts_total[5m])",
625-
"legendFormat": "{{action}} - {{endpoint}} ({{status}})",
624+
"expr": "rate(auth_requests_total[5m])",
625+
"legendFormat": "{{endpoint}} - {{identifier}} ({{status_code}})",
626626
"refId": "A"
627627
}
628628
],
@@ -643,8 +643,8 @@
643643
},
644644
"timepicker": {},
645645
"timezone": "",
646-
"title": "FastAPI Dashboard",
647-
"uid": "fastapi",
646+
"title": "amazee.ai Backend",
647+
"uid": "amazeeai-backend",
648648
"version": 1,
649649
"weekStart": ""
650650
}

0 commit comments

Comments
 (0)