Skip to content

Commit a7c8d10

Browse files
committed
feat: add missing metrics and logging to auth exceptions
Complete observability for all auth HTTPExceptions in refresh, verify, reset_password, and get_jwt_user flows. All 663 tests passing with 100% coverage.
1 parent aff15fe commit a7c8d10

File tree

2 files changed

+50
-1
lines changed

2 files changed

+50
-1
lines changed

app/managers/auth.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,10 @@ async def refresh(
179179
or not is_valid_jwt_format(refresh_token.refresh)
180180
):
181181
increment_auth_failure("invalid_token", "refresh_token")
182+
category_logger.warning(
183+
"Refresh attempted with invalid token format",
184+
LogCategory.AUTH,
185+
)
182186
raise HTTPException(
183187
status.HTTP_401_UNAUTHORIZED, ResponseMessages.INVALID_TOKEN
184188
)
@@ -226,6 +230,11 @@ async def refresh(
226230
user_data = await get_user_by_id_(user_id, session)
227231

228232
if not user_data:
233+
increment_auth_failure("user_not_found", "refresh_token")
234+
category_logger.warning(
235+
f"Refresh attempted with non-existent user ID: {user_id}",
236+
LogCategory.AUTH,
237+
)
229238
raise HTTPException(
230239
status.HTTP_404_NOT_FOUND, ResponseMessages.USER_NOT_FOUND
231240
)
@@ -273,6 +282,11 @@ async def verify(code: str, session: AsyncSession) -> None:
273282
or len(code) > MAX_JWT_TOKEN_LENGTH
274283
or not is_valid_jwt_format(code)
275284
):
285+
increment_auth_failure("invalid_token", "verify_email")
286+
category_logger.warning(
287+
"Email verification with invalid token format",
288+
LogCategory.AUTH,
289+
)
276290
raise HTTPException(
277291
status.HTTP_401_UNAUTHORIZED, ResponseMessages.INVALID_TOKEN
278292
)
@@ -320,17 +334,32 @@ async def verify(code: str, session: AsyncSession) -> None:
320334
user_data = await get_user_by_id_(user_id, session)
321335

322336
if not user_data:
337+
increment_auth_failure("user_not_found", "verify_email")
338+
category_logger.warning(
339+
f"Email verification with non-existent user ID: {user_id}",
340+
LogCategory.AUTH,
341+
)
323342
raise HTTPException(
324343
status.HTTP_404_NOT_FOUND, ResponseMessages.USER_NOT_FOUND
325344
)
326345

327346
# block a banned user
328347
if bool(user_data.banned):
348+
increment_auth_failure("banned_user", "verify_email")
349+
category_logger.warning(
350+
f"Banned user {user_data.id} attempted email verification",
351+
LogCategory.AUTH,
352+
)
329353
raise HTTPException(
330354
status.HTTP_401_UNAUTHORIZED, ResponseMessages.INVALID_TOKEN
331355
)
332356

333357
if bool(user_data.verified):
358+
increment_auth_failure("already_verified", "verify_email")
359+
category_logger.warning(
360+
f"User {user_data.id} verification when already verified",
361+
LogCategory.AUTH,
362+
)
334363
raise HTTPException(
335364
status.HTTP_401_UNAUTHORIZED, ResponseMessages.INVALID_TOKEN
336365
)
@@ -353,13 +382,15 @@ async def verify(code: str, session: AsyncSession) -> None:
353382
)
354383

355384
except jwt.ExpiredSignatureError as exc:
385+
increment_auth_failure("expired_token", "verify_email")
356386
category_logger.warning(
357387
"Expired verification token used", LogCategory.AUTH
358388
)
359389
raise HTTPException(
360390
status.HTTP_401_UNAUTHORIZED, ResponseMessages.EXPIRED_TOKEN
361391
) from exc
362392
except jwt.InvalidTokenError as exc:
393+
increment_auth_failure("invalid_token", "verify_email")
363394
category_logger.warning(
364395
"Invalid verification token used", LogCategory.AUTH
365396
)
@@ -425,6 +456,11 @@ async def reset_password(
425456
or len(code) > MAX_JWT_TOKEN_LENGTH
426457
or not is_valid_jwt_format(code)
427458
):
459+
increment_auth_failure("invalid_token", "password_reset")
460+
category_logger.warning(
461+
"Password reset with invalid token format",
462+
LogCategory.AUTH,
463+
)
428464
raise HTTPException(
429465
status.HTTP_401_UNAUTHORIZED, ResponseMessages.INVALID_TOKEN
430466
)
@@ -472,12 +508,22 @@ async def reset_password(
472508
user_data = await get_user_by_id_(user_id, session)
473509

474510
if not user_data:
511+
increment_auth_failure("user_not_found", "password_reset")
512+
category_logger.warning(
513+
f"Password reset with non-existent user ID: {user_id}",
514+
LogCategory.AUTH,
515+
)
475516
raise HTTPException(
476517
status.HTTP_404_NOT_FOUND, ResponseMessages.USER_NOT_FOUND
477518
)
478519

479520
# Block banned users from resetting password
480521
if bool(user_data.banned):
522+
increment_auth_failure("banned_user", "password_reset")
523+
category_logger.warning(
524+
f"Banned user {user_data.id} attempted password reset",
525+
LogCategory.AUTH,
526+
)
481527
raise HTTPException(
482528
status.HTTP_401_UNAUTHORIZED, ResponseMessages.INVALID_TOKEN
483529
)
@@ -499,13 +545,15 @@ async def reset_password(
499545
)
500546

501547
except jwt.ExpiredSignatureError as exc:
548+
increment_auth_failure("expired_token", "password_reset")
502549
category_logger.warning(
503550
"Expired password reset token used", LogCategory.AUTH
504551
)
505552
raise HTTPException(
506553
status.HTTP_401_UNAUTHORIZED, ResponseMessages.EXPIRED_TOKEN
507554
) from exc
508555
except jwt.InvalidTokenError as exc:
556+
increment_auth_failure("invalid_token", "password_reset")
509557
category_logger.warning(
510558
"Invalid password reset token used", LogCategory.AUTH
511559
)
@@ -636,6 +684,7 @@ async def get_jwt_user( # noqa: C901
636684

637685
# Check user validity - user must exist, be verified, and not banned
638686
if not user_data:
687+
increment_auth_failure("user_not_found", "jwt")
639688
category_logger.warning(
640689
"Authentication attempted with invalid user token",
641690
LogCategory.AUTH,

tests/unit/test_jwt_auth.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ async def test_jwt_auth_missing_sub_rejected(self, test_db, mocker) -> None:
134134

135135
async def test_jwt_auth_string_sub_claim(self, test_db, mocker) -> None:
136136
"""Test with a token containing string sub claim."""
137-
token, _ = await UserManager.register(self.test_user, test_db)
137+
await UserManager.register(self.test_user, test_db)
138138
# Create a JWT with string 'sub' instead of int
139139
token = jwt.encode(
140140
{

0 commit comments

Comments
 (0)