Skip to content

JWT tokens lifecycle#4147

Merged
hlbmtc merged 19 commits intomainfrom
feat/auth-revoke-tokens
Jan 27, 2026
Merged

JWT tokens lifecycle#4147
hlbmtc merged 19 commits intomainfrom
feat/auth-revoke-tokens

Conversation

@hlbmtc
Copy link
Contributor

@hlbmtc hlbmtc commented Jan 22, 2026

This PR implements a session-based JWT token management system with server-side revocation. The main goal:

  • Let users actually log out
  • Handle the concurrent tokens refresh problem.
  • Invalidate previous tokens after refresh

More details -- https://www.notion.so/metaculus/Auth-migration-roadmap-2e96aaf4f10180728140ddbdb51f5045?source=copy_link#2f06aaf4f10180bfb380df24e5662caf

part of #4008

Summary by CodeRabbit

  • New Features

    • Session-scoped JWT revocation with immediate session logout, short grace window for refreshes, deduplicated concurrent refreshes, and per-token whitelisting; session-aware access and refresh tokens enforced.
  • Client

    • Client now calls a server-side logout endpoint; refresh flows use the session-aware refresh mechanism and token rotation.
  • Tests

    • Extensive tests covering session lifecycle, refresh/grace behavior, deduplication, and logout revocation.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 22, 2026

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

Adds per-session JWT revocation and a cache-backed refresh grace/deduplication system: session-scoped access/refresh tokens, per-token whitelisting, enforce_at session management with immediate revoke, refresh/logout API endpoints, frontend logout call, tests, and SIMPLE_JWT config.

Changes

Cohort / File(s) Summary
Session & Token Revocation Core
authentication/jwt_session.py
New session-aware JWT implementation: SessionAccessToken, SessionRefreshToken, cache keys (jwt:grace:…, jwt:revoked:…, jwt:whitelist:…), whitelist helpers, get/set_session_enforce_at, is_token_revoked, refresh_tokens_with_grace_period(), lock-based dedupe, and revoke_session(). Review cache TTLs, lock usage, and iat whitelisting.
Token Provisioning
authentication/services.py
Login now issues SessionRefreshToken.for_user() (removed manual session_id assignment). Confirm login token issuance path integrates with new token classes.
URL Routing
authentication/urls.py
Added path("auth/logout/", common.logout_api_view) and replaced refresh route handler with common.token_refresh_api_view. Verify route wiring.
API Views
authentication/views/common.py
Added token_refresh_api_view (uses refresh_tokens_with_grace_period) and logout_api_view (extracts session_id from access token and calls revoke_session). Check auth extraction and error mappings.
Django Configuration
metaculus_web/settings.py
SIMPLE_JWT now includes AUTH_TOKEN_CLASSES = ("authentication.jwt_session.SessionAccessToken",). Confirm settings merge/visibility.
Frontend Logout & API
front_end/src/app/(main)/accounts/actions.ts, front_end/src/services/api/auth/auth.server.ts
Frontend calls /auth/logout/ during logout; added ServerAuthApi.logout() and client-side call wrapped to ignore errors before clearing local tokens.
Tests
tests/unit/test_auth/test_views.py, tests/unit/test_auth/test_jwt_session.py
New tests covering logout revocation, refresh lifecycle with grace/dedupe, whitelisting, concurrent refresh deduplication, and edge cases. Note reliance on cache fixture and time-freeze utilities.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Backend as Backend (token_refresh_api_view)
    participant TokenLogic as Token System (jwt_session)
    participant Cache as Cache/Redis
    participant DB as Database

    Client->>Backend: POST /auth/refresh { refresh_token }
    Backend->>TokenLogic: refresh_tokens_with_grace_period(token_str)
    TokenLogic->>TokenLogic: Parse & validate SessionRefreshToken
    TokenLogic->>TokenLogic: Check session enforce_at / revocation
    TokenLogic->>Cache: Check grace cache for cached response
    alt cached response exists
        Cache-->>TokenLogic: Cached tokens
        TokenLogic-->>Backend: Return cached tokens
    else no cached response
        TokenLogic->>TokenLogic: Acquire short lock (dedupe)
        TokenLogic->>TokenLogic: Re-check revocation & cache
        TokenLogic->>DB: Verify user is active
        TokenLogic->>TokenLogic: Generate new access (+ optional refresh)
        TokenLogic->>Cache: Whitelist old iat & store new tokens in grace cache
        TokenLogic->>DB: set_session_enforce_at(session_id, now)
        TokenLogic-->>Backend: Return new tokens
    end
    Backend-->>Client: 200 { access, refresh }
Loading
sequenceDiagram
    participant Client
    participant Backend as Backend (logout_api_view)
    participant JWTAuth as JWT Authentication
    participant TokenLogic as Token System (jwt_session)
    participant DB as Database

    Client->>Backend: POST /auth/logout Authorization: Bearer {access_token}
    Backend->>JWTAuth: Authenticate / extract access token
    JWTAuth->>TokenLogic: Decode SessionAccessToken => session_id
    Backend->>TokenLogic: revoke_session(session_id)
    TokenLogic->>DB: set_session_enforce_at(session_id, 0)
    TokenLogic-->>Backend: Revoked
    Backend-->>Client: 204 No Content
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • cemreinanc

Poem

🐰 I nibbled at iats, soft and spry,

Whitelisted hops to let some fly,
Grace held close each fleeting key,
Then logout quelled the burrowed spree,
Tokens quiet — peace in my rye.

🚥 Pre-merge checks | ✅ 1 | ❌ 1
❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'JWT tokens lifecycle' is vague and generic, using non-descriptive terminology that doesn't clearly convey the main change of implementing server-side JWT token revocation and session management. Consider a more specific title such as 'Implement server-side JWT token revocation and session-based logout' to better reflect the primary functionality being added.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing touches
  • 📝 Generate docstrings

📜 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 5d09340 and 0657f5c.

📒 Files selected for processing (1)
  • tests/unit/test_auth/test_views.py

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


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

…rough an exception

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
@hlbmtc hlbmtc changed the title Auth tokens lifecycle JWT tokens lifecycle Jan 22, 2026
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: 3

🤖 Fix all issues with AI agents
In `@authentication/jwt_session.py`:
- Around line 146-171: The cached grace-return is happening before
signature/expiry validation; change the flow in the validate-refresh logic:
after constructing SessionRefreshToken(refresh_token_str, verify=False) and
extracting session_id and computing grace_key, keep the
is_token_revoked(refresh) check, then call refresh.verify() (wrap TokenError ->
InvalidToken as currently done) before reading cache.get(grace_key); only after
successful verify return the cached JSON. Update the block around
SessionRefreshToken, is_token_revoked, refresh.verify, grace_key and cache.get
to reflect this ordering.
- Around line 186-193: Wrap the User.objects.get(pk=user_id) call in a
try/except that catches User.DoesNotExist and converts it to an
AuthenticationFailed exception (e.g., same message "No active account found for
the given token.") so missing users during refresh return 401 instead of causing
a 500; keep the existing check of api_settings.USER_AUTHENTICATION_RULE(user)
unchanged and only run it when the user lookup succeeds in the jwt refresh
handling code in authentication/jwt_session.py.

In `@authentication/views/common.py`:
- Around line 354-364: The code can raise TokenError when constructing
SessionAccessToken(raw_token) and currently bubbles up; wrap the
SessionAccessToken(raw_token) creation and subsequent token.get("session_id")
usage in a try/except that catches the TokenError from SimpleJWT (import
TokenError) and handle it idempotently (e.g., swallow the error and do not call
revoke_session, or return a controlled response such as HTTP 204/401 depending
on view flow). Update the block around
JWTAuthentication.get_header/get_raw_token and SessionAccessToken to catch
TokenError and ensure revoke_session is only called for valid tokens.
🧹 Nitpick comments (2)
front_end/src/app/(main)/accounts/actions.ts (1)

167-175: Consider logging server-side logout failures.
Silent catch hides revocation issues; a low-noise log keeps UX intact while preserving observability.

🔧 Suggested tweak
-  try {
-    await ServerAuthApi.logout();
-  } catch {}
+  try {
+    await ServerAuthApi.logout();
+  } catch (err) {
+    console.warn("Server logout failed", err);
+  }
tests/unit/test_auth/test_views.py (1)

152-155: Add explicit assertion for session_id to make failures clearer.

This keeps the test intent explicit if the token structure changes.

💡 Proposed tweak
         # Extract session_id from access token
         token = SessionAccessToken(access_token, verify=False)
         session_id = token.get("session_id")
+        assert session_id, "Expected session_id in access token"
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ecc633e and 73f9816.

📒 Files selected for processing (8)
  • authentication/jwt_session.py
  • authentication/services.py
  • authentication/urls.py
  • authentication/views/common.py
  • front_end/src/app/(main)/accounts/actions.ts
  • front_end/src/services/api/auth/auth.server.ts
  • metaculus_web/settings.py
  • tests/unit/test_auth/test_views.py
🧰 Additional context used
🧠 Learnings (3)
📚 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_views.py
  • authentication/views/common.py
  • authentication/services.py
  • metaculus_web/settings.py
  • authentication/urls.py
  • authentication/jwt_session.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:

  • authentication/views/common.py
  • authentication/services.py
  • metaculus_web/settings.py
  • authentication/jwt_session.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:

  • authentication/views/common.py
  • metaculus_web/settings.py
  • authentication/urls.py
🧬 Code graph analysis (5)
tests/unit/test_auth/test_views.py (3)
tests/unit/conftest.py (2)
  • anon_client (81-84)
  • user1 (46-48)
authentication/services.py (1)
  • get_tokens_for_user (130-139)
authentication/jwt_session.py (4)
  • SessionAccessToken (100-109)
  • get_session_enforce_at (49-53)
  • verify (106-109)
  • verify (129-132)
authentication/views/common.py (1)
authentication/jwt_session.py (3)
  • refresh_tokens_with_grace_period (135-212)
  • revoke_session (215-220)
  • SessionAccessToken (100-109)
front_end/src/app/(main)/accounts/actions.ts (1)
front_end/src/services/auth_tokens.ts (1)
  • getAuthCookieManager (162-165)
authentication/services.py (1)
authentication/jwt_session.py (2)
  • SessionRefreshToken (112-132)
  • for_user (124-127)
authentication/urls.py (1)
authentication/views/common.py (2)
  • logout_api_view (348-366)
  • token_refresh_api_view (328-344)
🪛 GitHub Check: CodeQL
authentication/jwt_session.py

[warning] 149-149: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.


[warning] 171-171: Information exposure through an exception
Stack trace information flows to this location and may be exposed to an external user.

🪛 Ruff (0.14.13)
authentication/jwt_session.py

109-109: Avoid specifying long messages outside the exception class

(TRY003)


132-132: Avoid specifying long messages outside the exception class

(TRY003)


148-148: Do not catch blind exception: Exception

(BLE001)


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

(B904)


153-153: Avoid specifying long messages outside the exception class

(TRY003)


160-160: Avoid specifying long messages outside the exception class

(TRY003)


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

(B904)


179-179: Avoid specifying long messages outside the exception class

(TRY003)


190-192: 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
  • GitHub Check: Backend Checks
  • GitHub Check: Frontend Checks
  • GitHub Check: integration-tests
🔇 Additional comments (13)
front_end/src/services/api/auth/auth.server.ts (1)

6-6: LGTM — logout hook is cleanly wired.

Also applies to: 165-167

authentication/jwt_session.py (7)

14-35: Clear constants and key helpers.


37-47: Whitelist helpers look good.


49-63: Revocation timestamp caching is straightforward.


66-97: Revocation decision flow is easy to follow.


100-133: Token subclasses integrate revocation checks cleanly.


215-220: Logout revocation hook is straightforward.


194-210: Prevent newly issued tokens from being revoked by enforce_at drift.
enforce_at is set after minting tokens; if the clock ticks to the next second, new tokens can end up with iat < enforce_at and be immediately revoked.

authentication/services.py (1)

134-138: LGTM — session-aware refresh token creation.

metaculus_web/settings.py (1)

194-201: AUTH_TOKEN_CLASSES wiring looks good.

authentication/views/common.py (3)

14-22: No issues found in these imports.


326-345: token_refresh_api_view looks solid.

Clear validation and consistent error translation.


347-352: [Your rewritten review comment text here]
[Exactly ONE classification tag]

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

@github-actions
Copy link
Contributor

github-actions bot commented Jan 22, 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-27T18:43:08Z

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 181-195: Capture a single timestamp up front and use it for both
creating the new token iat and for setting the session enforce_at to avoid the
race between refresh.set_iat() and set_session_enforce_at(); specifically,
compute one UTC timestamp (e.g., aware_utcnow() or
datetime.now(timezone.utc).timestamp()) then apply it to the refresh token
(instead of calling refresh.set_iat() with no args, set the iat claim explicitly
on the refresh object or call set_iat(timestamp) if supported) and pass the same
integer timestamp into set_session_enforce_at(session_id, int(timestamp));
ensure any JSON/data writes and whitelist_token(session_id, old_token_iat)
remain unchanged.
♻️ Duplicate comments (1)
authentication/jwt_session.py (1)

172-177: Handle User.DoesNotExist during refresh.

User.objects.get(pk=user_id) raises User.DoesNotExist if the user was deleted, resulting in a 500 error. Wrap in try-except to return a proper 401.

🔧 Suggested fix
         # Check user is still active
         user_id = refresh.get(api_settings.USER_ID_CLAIM)
         if user_id:
-            user = User.objects.get(pk=user_id)
+            try:
+                user = User.objects.get(pk=user_id)
+            except User.DoesNotExist:
+                raise AuthenticationFailed(
+                    "No active account found for the given token."
+                ) from None
             if not api_settings.USER_AUTHENTICATION_RULE(user):
                 raise AuthenticationFailed(
                     "No active account found for the given token."
                 )
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 96a6ac8 and 474e3b2.

📒 Files selected for processing (1)
  • authentication/jwt_session.py
🧰 Additional context used
🧠 Learnings (2)
📚 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:

  • authentication/jwt_session.py
📚 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
🪛 Ruff (0.14.13)
authentication/jwt_session.py

77-77: Avoid specifying long messages outside the exception class

(TRY003)


109-109: Avoid specifying long messages outside the exception class

(TRY003)


132-132: Avoid specifying long messages outside the exception class

(TRY003)


164-164: Avoid specifying long messages outside the exception class

(TRY003)


175-177: 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: Frontend Checks
  • GitHub Check: Backend Checks
🔇 Additional comments (9)
authentication/jwt_session.py (9)

1-21: LGTM!

Imports are well-organized and constants are clearly named with appropriate documentation.


23-35: LGTM!

Key generation helpers are well-encapsulated and follow consistent naming conventions.


37-46: LGTM!

Whitelist functions correctly manage the cache with appropriate timeouts matching the grace period.


49-63: LGTM!

Session enforce_at management is correctly implemented with appropriate TTL based on token lifetime.


100-110: LGTM!

SessionAccessToken correctly extends verification to include revocation check after standard JWT validation.


112-133: LGTM!

SessionRefreshToken correctly implements session-based token creation and revocation verification.


200-205: LGTM!

revoke_session correctly implements immediate revocation by setting enforce_at to 0.


161-161: cache.lock() requires a Redis-backed cache.

The cache.lock() method is not part of Django's standard cache API—it's provided by django-redis. If the cache backend changes away from Redis, this will raise AttributeError. The project currently uses django-redis as configured in settings, but this is a hard dependency for this code path.


75-78: Remove concern about token migration strategy.

The session_id requirement is not a breaking change to existing tokens. All token creation in the codebase goes through get_tokens_for_user() which uses SessionRefreshToken.for_user(), which always assigns a session_id (line 126). The check at lines 75–77 is a defensive validation to prevent misconfiguration (e.g., accidental use of the parent RefreshToken class directly), as shown in the test at lines 217–224. No pre-existing tokens without session_id exist in this system.

Likely an incorrect or invalid review comment.

✏️ 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_views.py`:
- Around line 149-153: The test currently reads the session_id from
SessionAccessToken via token.get("session_id") and then queries
get_session_enforce_at(session_id) which can produce a false-positive if
session_id is missing; add an explicit assertion that session_id is present
(e.g., assert session_id is not None or truthy) immediately after obtaining it
to ensure the claim exists before calling get_session_enforce_at.
🧹 Nitpick comments (1)
tests/unit/test_auth/test_views.py (1)

135-136: Prefer reverse(...) over a hard-coded logout URL.

Using a named route keeps tests stable if the URL prefix or path changes.

♻️ Suggested change
 class TestLogout:
-    url = "/api/auth/logout/"
+    url = reverse("auth-logout")
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 474e3b2 and 09e0571.

📒 Files selected for processing (1)
  • tests/unit/test_auth/test_views.py
🧰 Additional context used
🧠 Learnings (1)
📚 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_views.py
🧬 Code graph analysis (1)
tests/unit/test_auth/test_views.py (3)
tests/unit/conftest.py (1)
  • anon_client (81-84)
authentication/services.py (1)
  • get_tokens_for_user (130-139)
authentication/jwt_session.py (4)
  • SessionAccessToken (100-109)
  • get_session_enforce_at (49-53)
  • verify (106-109)
  • verify (129-132)
⏰ 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). (3)
  • GitHub Check: Build Docker Image / Build Docker Image
  • GitHub Check: integration-tests
  • GitHub Check: Backend Checks

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

Copy link
Contributor

@elisescu elisescu left a comment

Choose a reason for hiding this comment

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

LGTM

@hlbmtc hlbmtc merged commit 01dca8c into main Jan 27, 2026
10 of 12 checks passed
@hlbmtc hlbmtc deleted the feat/auth-revoke-tokens branch January 27, 2026 18:43
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.

3 participants