Skip to content

Comments

Global tokens invalidation#4199

Merged
hlbmtc merged 10 commits intomainfrom
feat/auth-user-global-tokens-invalidation
Jan 30, 2026
Merged

Global tokens invalidation#4199
hlbmtc merged 10 commits intomainfrom
feat/auth-user-global-tokens-invalidation

Conversation

@hlbmtc
Copy link
Contributor

@hlbmtc hlbmtc commented Jan 29, 2026

https://www.notion.so/metaculus/2f76aaf4f1018041a6d6cdc06ea4b9f1?v=2f76aaf4f1018099baa0000cdc0a6471

Summary by CodeRabbit

  • New Features

    • Session-aware JWT auth with per-user revocation and "log out everywhere"
    • Password and email change flows now return and refresh authentication tokens in-session
    • Added per-user timestamp to track token revocation across sessions
  • Security Improvements

    • Tokens issued before a user-wide revocation (password change or logout-everywhere) are invalidated
    • Centralized password-change handling that revokes existing sessions
  • Frontend

    • Forms now reset/clear errors and synchronize new tokens into session state after email/password changes
  • Tests

    • Expanded tests covering user-level revocation and token behavior across sessions

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 29, 2026

📝 Walkthrough

Walkthrough

Adds per-user JWT revocation and session-aware authentication: introduces SessionJWTAuthentication and user.auth_revoked_at, adds revoke_all_user_tokens/is_user_global_token_revoked and updates refresh/grace logic; password/email flows now rotate and return tokens which frontend persists; migration and tests added.

Changes

Cohort / File(s) Summary
Backend Authentication
authentication/auth.py, metaculus_web/settings.py
Add SessionJWTAuthentication that checks per-user revocation; switch DRF auth class to it; remove CHECK_REVOKE_TOKEN SIMPLE_JWT flag; adjust ApiKey import path.
JWT Session Logic
authentication/jwt_session.py
Add is_user_global_token_revoked(user, token_iat) and revoke_all_user_tokens(user); enforce global revocation checks in refresh and grace-period flows; add timezone/timedelta usage and simplify redundant user-active checks.
User Model & Migration
users/models.py, users/migrations/0018_add_auth_revoked_at.py
Add nullable auth_revoked_at: DateTimeField to User and migration to persist it (tokens issued before this timestamp are invalid).
Password / Email Services & Views
users/services/common.py, users/views.py, authentication/views/common.py
Introduce change_user_password(user, new_password) that validates, saves, and calls revoke_all_user_tokens; update password/email confirm flows to call helper and return fresh JWT tokens.
Frontend API & Handlers
front_end/src/services/api/profile/profile.server.ts, front_end/src/app/(main)/accounts/change-email/route.ts, front_end/src/app/(main)/accounts/settings/actions.tsx, front_end/src/app/(main)/accounts/settings/components/change_email.tsx
changePassword and changeEmailConfirm now return AuthTokens; route/actions obtain tokens and call authManager.setAuthTokens(tokens); change-email modal resets form and clears submit errors on close.
Tests
tests/unit/test_auth/test_jwt_session.py
Add tests for revoke_all_user_tokens and user-level revocation across sessions: revocation sets auth_revoked_at, rejects old tokens, accepts new tokens, and enforces API access rejection.
Auth view helpers / imports
authentication/views/common.py, authentication/auth.py
Replace inline password set/save with change_user_password; add JWT alias imports and adjust import surfaces.

Sequence Diagram

sequenceDiagram
    actor User
    participant Frontend as Frontend
    participant AuthMgr as Auth Manager
    participant Server as Backend API
    participant DB as Database

    User->>Frontend: Submit password change / email-confirm
    Frontend->>Server: POST /change_password or /confirm_email
    Server->>DB: Update user and set auth_revoked_at = now()
    Server->>Server: Issue new JWT AuthTokens
    Server-->>Frontend: 200 OK + AuthTokens
    Frontend->>AuthMgr: setAuthTokens(tokens)
    AuthMgr->>AuthMgr: Persist tokens (cookies/session)

    rect rgba(200,100,150,0.5)
    Frontend->>Server: Subsequent request with old token
    Server->>DB: Fetch user and auth_revoked_at
    Server->>Server: Check token.iat < auth_revoked_at?
    Server-->>Frontend: 401 Unauthorized
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • elisescu
  • cemreinanc
  • lsabor
  • ncarazon

Poem

🐇 I twitch my whiskers at tokens old,

I mark the hour when sessions fold.
New JWTs leap, all bright and clean,
Cookies snug and logged-out scenes.
Hooray — we hop to a safer green!

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Global tokens invalidation' directly and clearly describes the main objective of the PR, which implements user-level token revocation functionality across multiple authentication and password management files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/auth-user-global-tokens-invalidation

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d5ff849 and 58dbc83.

📒 Files selected for processing (1)
  • tests/unit/test_auth/test_jwt_session.py
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: hlbmtc
Repo: Metaculus/metaculus PR: 4198
File: front_end/src/app/(main)/accounts/social/[provider]/page.tsx:0-0
Timestamp: 2026-01-29T18:15:08.473Z
Learning: In the OAuth flow implementation for this codebase, CSRF protection uses token rotation (generating a new token after each use) rather than implementing separate state parameter expiration, and relies on Session cookie expiration for timeout protection.
Learnt from: hlbmtc
Repo: Metaculus/metaculus PR: 4087
File: metaculus_web/settings.py:150-155
Timestamp: 2026-01-19T16:13:59.519Z
Learning: In the Metaculus codebase `metaculus_web/settings.py`, `SessionAuthentication` is intentionally listed before `JWTAuthentication` in `REST_FRAMEWORK["DEFAULT_AUTHENTICATION_CLASSES"]` because session auth is expected to be used specifically for the Django admin dashboard, while JWT handles the primary web user authentication.
📚 Learning: 2026-01-15T19:29:58.940Z
Learnt from: hlbmtc
Repo: Metaculus/metaculus PR: 4075
File: authentication/urls.py:24-26
Timestamp: 2026-01-15T19:29:58.940Z
Learning: In this codebase, DRF is configured to use IsAuthenticated as the default in REST_FRAMEWORK['DEFAULT_PERMISSION_CLASSES'] within metaculus_web/settings.py. Therefore, explicit permission_classes([IsAuthenticated]) decorators are unnecessary on DRF views unless a view needs to override the default. When reviewing Python files, verify that views relying on the default are not redundantly decorated, and flag cases where permissions are being over-specified or when a non-default permission is explicitly required.

Applied to files:

  • tests/unit/test_auth/test_jwt_session.py
🧬 Code graph analysis (1)
tests/unit/test_auth/test_jwt_session.py (1)
authentication/jwt_session.py (5)
  • SessionRefreshToken (125-145)
  • is_token_revoked (67-98)
  • refresh_tokens_with_grace_period (148-214)
  • revoke_session (217-222)
  • revoke_all_user_tokens (225-236)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build Docker Image / Build Docker Image
  • GitHub Check: Backend Checks
  • GitHub Check: Frontend Checks
  • GitHub Check: integration-tests
🔇 Additional comments (6)
tests/unit/test_auth/test_jwt_session.py (6)

1-20: LGTM!

Import additions are appropriate and necessary for the new TestUserLevelRevocation test class.


234-244: LGTM!

Good test structure: verifies initial state, calls the function, refreshes from DB, and asserts the expected outcome with a reasonable time tolerance.


246-262: LGTM!

Correctly tests the core revocation logic with clear time progression using freeze_time. The expected error message "invalidated" aligns with the InvalidToken("Token has been invalidated") raised in refresh_tokens_with_grace_period.


264-281: LGTM!

Important complementary test ensuring tokens issued after revocation continue to work. This verifies the is_user_global_token_revoked check correctly compares iat >= auth_revoked_at.


283-304: LGTM!

Excellent test coverage for the multi-session revocation scenario. This validates that auth_revoked_at operates at the user level, affecting all sessions regardless of their session_id.


306-331: Status code assertion and nested timing could be clarified.

The 401 vs 403 concern was previously raised. The comment on line 328 acknowledges ambiguity, but the hardcoded 403 assertion may cause flaky tests if DRF configuration changes.

Additionally, the nested freeze_time blocks make the timing logic harder to follow. Consider adding a brief comment explaining the timeline:

  • Tokens created at 12:00:03 (iat = 12:00:03)
  • Revocation at 12:00:05 sets auth_revoked_at = 12:00:04 (due to -1 second offset)
  • Token iat (12:00:03) < auth_revoked_at (12:00:04) → rejected

[duplicate_comment, suggest_optional_refactor]

📝 Suggested clarity improvements
     `@freeze_time`("2024-01-01 12:00:05")
     def test_revoked_access_token_rejected_by_api_endpoint(self):
         """Access token is rejected by API endpoint after user-level revocation."""
         user = factory_user(is_active=True)

-        # Get tokens
+        # Get tokens at 12:00:03 (iat will be this time)
         with freeze_time("2024-01-01 12:00:03"):
             tokens = get_tokens_for_user(user)

         access_token = tokens["access"]

         client = APIClient()
         client.credentials(HTTP_AUTHORIZATION=f"Bearer {access_token}")

         # Request should succeed before revocation
         response = client.get("/api/users/me/")
         assert response.status_code == 200
         assert response.data["id"] == user.id

-        # Revoke all user tokens
+        # Revoke all user tokens at 12:00:05
+        # This sets auth_revoked_at = 12:00:04 (now - 1 second)
+        # Token iat (12:00:03) < auth_revoked_at (12:00:04), so token is revoked
         revoke_all_user_tokens(user)

-        # Same access token should now be rejected (401 or 403 depending on DRF config)
+        # Same access token should now be rejected
+        # Note: DRF returns 401 for AuthenticationFailed, but middleware/config may map to 403
         response = client.get("/api/users/me/")
-        assert response.status_code == 403
+        assert response.status_code in (401, 403)
         assert "invalidated" in str(response.data).lower()

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Contributor

github-actions bot commented Jan 29, 2026

🧹 Preview Environment Cleaned Up

The preview environment for this PR has been destroyed.

Resource Status
🌐 Preview App ✅ Deleted
🗄️ PostgreSQL Branch ✅ Deleted
⚡ Redis Database ✅ Deleted
🔧 GitHub Deployments ✅ Removed
📦 Docker Image ⚠️ Retained (auto-cleanup via GHCR policies)

Cleanup triggered by PR close at 2026-01-30T20:22:37Z

# Conflicts:
#	authentication/jwt_session.py
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@authentication/jwt_session.py`:
- Around line 101-110: The function is_user_global_token_revoked (and its caller
that reads old_token_iat from refresh.get("iat")) must guard against a missing
iat to avoid TypeError; change the refresh token code path to default the iat
(e.g. old_token_iat = refresh.get("iat", 0)) or validate/convert the value
before calling is_user_global_token_revoked, and update
is_user_global_token_revoked to handle None/invalid types (coerce to int or
return True/False safely) so comparisons like token_iat < revoked_at_ts never
raise.
🧹 Nitpick comments (1)
tests/unit/test_auth/test_jwt_session.py (1)

232-242: Make the revocation timestamp test deterministic.
Wrapping the assertions in freeze_time removes any wall‑clock flakiness.

♻️ Suggested tweak
-        revoke_all_user_tokens(user)
-        user.refresh_from_db()
-
-        assert user.auth_revoked_at is not None
-        # Should be recent (within last minute)
-        assert (timezone.now() - user.auth_revoked_at).total_seconds() < 60
+        with freeze_time("2024-01-01 12:00:00"):
+            revoke_all_user_tokens(user)
+            user.refresh_from_db()
+
+            assert user.auth_revoked_at is not None
+            # Should be recent (within last minute)
+            assert (timezone.now() - user.auth_revoked_at).total_seconds() < 60
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f93cbb3 and d04acfa.

📒 Files selected for processing (12)
  • authentication/auth.py
  • authentication/jwt_session.py
  • authentication/views/common.py
  • front_end/src/app/(main)/accounts/change-email/route.ts
  • front_end/src/app/(main)/accounts/settings/actions.tsx
  • front_end/src/services/api/profile/profile.server.ts
  • metaculus_web/settings.py
  • tests/unit/test_auth/test_jwt_session.py
  • users/migrations/0018_add_auth_revoked_at.py
  • users/models.py
  • users/services/common.py
  • users/views.py
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: hlbmtc
Repo: Metaculus/metaculus PR: 4198
File: front_end/src/app/(main)/accounts/social/[provider]/page.tsx:0-0
Timestamp: 2026-01-29T18:15:08.473Z
Learning: In the OAuth flow implementation for this codebase, CSRF protection uses token rotation (generating a new token after each use) rather than implementing separate state parameter expiration, and relies on Session cookie expiration for timeout protection.
📚 Learning: 2026-01-15T19:29:58.940Z
Learnt from: hlbmtc
Repo: Metaculus/metaculus PR: 4075
File: authentication/urls.py:24-26
Timestamp: 2026-01-15T19:29:58.940Z
Learning: In this codebase, DRF is configured to use IsAuthenticated as the default in REST_FRAMEWORK['DEFAULT_PERMISSION_CLASSES'] within metaculus_web/settings.py. Therefore, explicit permission_classes([IsAuthenticated]) decorators are unnecessary on DRF views unless a view needs to override the default. When reviewing Python files, verify that views relying on the default are not redundantly decorated, and flag cases where permissions are being over-specified or when a non-default permission is explicitly required.

Applied to files:

  • authentication/jwt_session.py
  • users/migrations/0018_add_auth_revoked_at.py
  • users/models.py
  • metaculus_web/settings.py
  • users/services/common.py
  • authentication/views/common.py
  • tests/unit/test_auth/test_jwt_session.py
  • users/views.py
  • authentication/auth.py
📚 Learning: 2026-01-19T16:13:59.519Z
Learnt from: hlbmtc
Repo: Metaculus/metaculus PR: 4087
File: metaculus_web/settings.py:150-155
Timestamp: 2026-01-19T16:13:59.519Z
Learning: In the Metaculus codebase `metaculus_web/settings.py`, `SessionAuthentication` is intentionally listed before `JWTAuthentication` in `REST_FRAMEWORK["DEFAULT_AUTHENTICATION_CLASSES"]` because session auth is expected to be used specifically for the Django admin dashboard, while JWT handles the primary web user authentication.

Applied to files:

  • metaculus_web/settings.py
  • authentication/auth.py
📚 Learning: 2026-01-16T20:30:29.385Z
Learnt from: hlbmtc
Repo: Metaculus/metaculus PR: 4087
File: authentication/views/social.py:47-55
Timestamp: 2026-01-16T20:30:29.385Z
Learning: In the Metaculus codebase, `SocialTokenOnlyAuthView` from the `rest_social_auth` library uses `AllowAny` permission by default, so views inheriting from it (like `SocialCodeAuth` in `authentication/views/social.py`) do not need to explicitly set `permission_classes = (AllowAny,)` for OAuth code exchange to work with unauthenticated requests.

Applied to files:

  • metaculus_web/settings.py
🧬 Code graph analysis (9)
front_end/src/app/(main)/accounts/settings/actions.tsx (1)
front_end/src/services/auth_tokens.ts (1)
  • getAuthCookieManager (146-149)
front_end/src/services/api/profile/profile.server.ts (1)
front_end/src/types/auth.ts (1)
  • AuthTokens (9-12)
authentication/jwt_session.py (1)
users/models.py (1)
  • User (21-285)
front_end/src/app/(main)/accounts/change-email/route.ts (1)
front_end/src/services/auth_tokens.ts (1)
  • getAuthCookieManager (146-149)
users/services/common.py (5)
authentication/serializers.py (1)
  • validate_password (77-80)
authentication/jwt_session.py (1)
  • revoke_all_user_tokens (225-236)
users/models.py (2)
  • User (21-285)
  • UserCampaignRegistration (288-311)
utils/email.py (1)
  • send_email_with_template (13-50)
utils/frontend.py (1)
  • build_frontend_email_change_url (79-80)
authentication/views/common.py (1)
users/services/common.py (2)
  • register_user_to_campaign (156-189)
  • change_user_password (68-77)
tests/unit/test_auth/test_jwt_session.py (2)
authentication/jwt_session.py (2)
  • revoke_all_user_tokens (225-236)
  • refresh_tokens_with_grace_period (148-214)
authentication/services.py (1)
  • get_tokens_for_user (130-139)
users/views.py (2)
users/services/common.py (1)
  • change_user_password (68-77)
authentication/services.py (1)
  • get_tokens_for_user (130-139)
authentication/auth.py (2)
authentication/models.py (1)
  • ApiKey (8-34)
authentication/jwt_session.py (1)
  • is_user_global_token_revoked (101-110)
🪛 Ruff (0.14.14)
authentication/jwt_session.py

[warning] 174-174: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


[warning] 174-174: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 177-177: Avoid specifying long messages outside the exception class

(TRY003)


[warning] 179-179: Avoid specifying long messages outside the exception class

(TRY003)

users/migrations/0018_add_auth_revoked_at.py

[warning] 8-10: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)


[warning] 12-22: Mutable class attributes should be annotated with typing.ClassVar

(RUF012)

authentication/auth.py

[warning] 29-32: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build Docker Image / Build Docker Image
  • GitHub Check: integration-tests
  • GitHub Check: Backend Checks
  • GitHub Check: Frontend Checks
🔇 Additional comments (18)
users/models.py (1)

145-150: Looks good: user-level revocation timestamp.
Adds the needed metadata for global JWT invalidation without impacting existing users.

front_end/src/services/api/profile/profile.server.ts (2)

68-72: LGTM: changePassword now returns AuthTokens.


86-89: LGTM: changeEmailConfirm now returns AuthTokens.

authentication/auth.py (1)

12-34: LGTM: user-level revocation enforced during authentication.

metaculus_web/settings.py (1)

149-155: LGTM: custom SessionJWTAuthentication is now the default JWT auth class.

front_end/src/app/(main)/accounts/change-email/route.ts (1)

12-15: No action needed — setAuthTokens is synchronous.

The method signature is setAuthTokens(tokens: AuthTokens): void, which is synchronous and does not return a Promise. The missing await is not a concern.

Likely an incorrect or invalid review comment.

authentication/views/common.py (2)

40-40: LGTM — centralized password helper import.

This keeps the reset flow aligned with the shared password-change logic.


255-257: LGTM — password reset now uses the centralized helper.

Good consolidation for validation + token revocation in one place.

users/services/common.py (3)

3-21: LGTM — imports align with new password/email flows.

No issues with the expanded import surface.


68-77: LGTM — consolidated password change + revocation.

Nice centralization of validation, update, and token invalidation.


136-136: LGTM — revoke tokens after email change.

This matches the global revocation behavior.

users/migrations/0018_add_auth_revoked_at.py (1)

1-22: LGTM — migration adds auth_revoked_at for revocation tracking.

Looks correct and consistent with the intended auth flow.

users/views.py (3)

29-36: LGTM — shared password helper import.

Clean wiring for the new flow.


170-173: LGTM — password change returns fresh tokens.

Consistent with the global revocation model.


206-207: LGTM — email change confirmation returns fresh tokens.

Keeps clients in sync after revocation.

authentication/jwt_session.py (3)

3-7: LGTM — time utilities for revocation timestamps.

Imports are appropriate for the new logic.


168-179: LGTM — active-user + global revocation checks before grace logic.

This sequencing looks solid.


225-236: LGTM — user-wide revocation timestamp setter.

Matches the intended “logout everywhere” semantics.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@tests/unit/test_auth/test_jwt_session.py`:
- Around line 304-329: The test
test_revoked_access_token_rejected_by_api_endpoint incorrectly asserts a 403;
when SessionJWTAuthentication (which raises
JWTAuthenticationFailed/rest_framework_simplejwt.exceptions.AuthenticationFailed)
rejects a revoked token the response should be 401, so change the assertion to
assert response.status_code == 401 (or to assert response.status_code in (401,
403) if you want to accept both behaviors); also add a brief comment near the
nested freeze_time calls clarifying that tokens are created at 12:00:03,
revoke_all_user_tokens sets auth_revoked_at to 12:00:04, and the final check at
12:00:05 verifies the token is considered revoked.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2baf468 and e0e0a51.

📒 Files selected for processing (1)
  • tests/unit/test_auth/test_jwt_session.py
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: hlbmtc
Repo: Metaculus/metaculus PR: 4087
File: metaculus_web/settings.py:150-155
Timestamp: 2026-01-19T16:13:59.519Z
Learning: In the Metaculus codebase `metaculus_web/settings.py`, `SessionAuthentication` is intentionally listed before `JWTAuthentication` in `REST_FRAMEWORK["DEFAULT_AUTHENTICATION_CLASSES"]` because session auth is expected to be used specifically for the Django admin dashboard, while JWT handles the primary web user authentication.
📚 Learning: 2026-01-15T19:29:58.940Z
Learnt from: hlbmtc
Repo: Metaculus/metaculus PR: 4075
File: authentication/urls.py:24-26
Timestamp: 2026-01-15T19:29:58.940Z
Learning: In this codebase, DRF is configured to use IsAuthenticated as the default in REST_FRAMEWORK['DEFAULT_PERMISSION_CLASSES'] within metaculus_web/settings.py. Therefore, explicit permission_classes([IsAuthenticated]) decorators are unnecessary on DRF views unless a view needs to override the default. When reviewing Python files, verify that views relying on the default are not redundantly decorated, and flag cases where permissions are being over-specified or when a non-default permission is explicitly required.

Applied to files:

  • tests/unit/test_auth/test_jwt_session.py
🧬 Code graph analysis (1)
tests/unit/test_auth/test_jwt_session.py (2)
authentication/jwt_session.py (5)
  • revoke_all_user_tokens (225-236)
  • get_session_enforce_at (50-54)
  • _get_whitelist_key (33-35)
  • _get_grace_key (24-26)
  • refresh_tokens_with_grace_period (148-214)
authentication/services.py (1)
  • get_tokens_for_user (130-139)
🔇 Additional comments (5)
tests/unit/test_auth/test_jwt_session.py (5)

7-17: LGTM on imports.

The new imports properly support the user-level revocation test cases with revoke_all_user_tokens, time utilities, and API testing capabilities.


232-242: LGTM.

Good test structure with proper refresh_from_db() call to verify the database state change.


244-260: LGTM.

Clear timeline testing the core revocation behavior.


262-279: LGTM.

Good test verifying that new tokens work after revocation.


281-302: LGTM.

Good coverage for the multi-session revocation scenario.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@front_end/src/app/`(main)/accounts/settings/components/change_email.tsx:
- Around line 51-63: The onSubmit callback calls handleClose(true) after a
successful email change which contradicts the modal's onClose semantics; update
the success branch in the onSubmit function to call handleClose(false) instead
of handleClose(true) so the modal closes correctly (locate the onSubmit function
in change_email.tsx and replace the handleClose(true) call).
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e0e0a51 and eab07d5.

📒 Files selected for processing (1)
  • front_end/src/app/(main)/accounts/settings/components/change_email.tsx
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: hlbmtc
Repo: Metaculus/metaculus PR: 4087
File: metaculus_web/settings.py:150-155
Timestamp: 2026-01-19T16:13:59.519Z
Learning: In the Metaculus codebase `metaculus_web/settings.py`, `SessionAuthentication` is intentionally listed before `JWTAuthentication` in `REST_FRAMEWORK["DEFAULT_AUTHENTICATION_CLASSES"]` because session auth is expected to be used specifically for the Django admin dashboard, while JWT handles the primary web user authentication.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build Docker Image / Build Docker Image
  • GitHub Check: integration-tests
🔇 Additional comments (3)
front_end/src/app/(main)/accounts/settings/components/change_email.tsx (3)

33-38: Good addition of reset for form cleanup.
This aligns with the new close behavior and keeps the modal state consistent.


42-49: Solid close handler cleanup.
Resetting the form and clearing submit errors on close is the right UX for this modal.


67-71: LGTM: BaseModal now closes via handleClose.
Ensures form reset + error cleanup on all close paths.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@hlbmtc hlbmtc merged commit 47dfd2e into main Jan 30, 2026
7 checks passed
@hlbmtc hlbmtc deleted the feat/auth-user-global-tokens-invalidation branch January 30, 2026 20:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant