fix(ui): redirect authenticated users from login page to dashboard#3461
fix(ui): redirect authenticated users from login page to dashboard#3461crivetimihai merged 8 commits intomainfrom
Conversation
Lang-Akshay
left a comment
There was a problem hiding this comment.
Thanks for the PR @marekdano . Great work. Please address the following findings.
Findings Summary
| # | File | Line | Severity | CWE | Description |
|---|---|---|---|---|---|
| 1 | mcpgateway/admin.py |
3731–3733 | Low | CWE-754 | Bare except Exception: pass silently swallows all exceptions from JWT verification, including unexpected failures (key-loading errors, cryptographic faults). Masks operational issues. |
| 2 | mcpgateway/admin.py |
3724 | Low | CWE-287 | Accepting tokens from two cookie names (jwt_token and access_token) widens the attack surface. If one cookie has weaker SameSite/Secure/Path attributes, it may be injectable via a different origin. |
| 3 | mcpgateway/admin.py |
3729–3730 | Informational | CWE-613 | When a token is invalid/expired, the code falls through without clearing the stale cookie. The browser continues sending the bad token on every subsequent request. |
| 4 | tests/unit/mcpgateway/test_admin.py |
13660 | CWE-1164 | The assertion assert "secure cookies enabled" in (context.get("secure_cookie_warning") or "").lower() was deleted from test_admin_login_page_secure_cookie_warning_in_development. The test now grabs the context dict but never validates its content, eliminating coverage for the security warning feature. |
|
| 5 | tests/unit/mcpgateway/test_admin.py |
13625–13657 | Low | CWE-1164 | Pre-existing tests test_admin_login_page_email_auth_enabled and test_admin_login_page_secure_cookie_warning_in_development do not set request.cookies = {}. With MagicMock(spec=Request), request.cookies.get("jwt_token") returns a truthy MagicMock, unintentionally triggering the new JWT-check code path. Tests pass only because the MagicMock isn't a valid JWT and the exception is swallowed. |
Suggestions :
- Finding 1 — Narrow the exception handler to catch only expected types (
HTTPException,jwt.PyJWTError) instead of blanketException. This preserves intent while allowing unexpected errors to propagate and be logged. Effort: trivial. Ref: CWE-754, OWASP Error Handling. - Finding 2 — Either gate
access_tokenacceptance behind a feature flag (e.g., SSO flow), document that both cookies MUST share identicalSecure/HttpOnly/SameSiteattributes, or simplify to only readjwt_token(the cookie the gateway itself sets). Effort: low. - Finding 3 — Consider clearing the stale cookie on verification failure via
response.delete_cookie(). Effort: low (requires minor flow restructuring). - Finding 4 —
⚠️ Restore the deleted assertion. Addrequest.cookies = {}to the test to prevent the new JWT-check path from interfering, then re-add:assert "secure cookies enabled" in context["secure_cookie_warning"].lower(). Effort: trivial. This is the highest-priority fix. - Finding 5 — Add explicit
request.cookies = {}to all pre-existing tests that don't intend to test the JWT cookie path. Effort: trivial.
Deny-Path Test Coverage
| Deny-path scenario | Covered? | Notes |
|---|---|---|
| No cookie present → show login form | Implicit | Works via existing test, but request.cookies should be explicitly set to {} |
| Valid JWT → redirect to dashboard | ✅ Yes | test_admin_login_page_redirects_authenticated_user() (tests/unit/mcpgateway/test_admin.py:13663) |
| Invalid/expired JWT → show login form | ✅ Yes | test_admin_login_page_shows_form_for_invalid_token() (tests/unit/mcpgateway/test_admin.py:13686) |
verify_jwt_token_cached() (mcpgateway/utils/verify_credentials.py:229) returns falsy/None → show login form |
❌ No | The if payload: branch (mcpgateway/admin.py:3729) has no test for None/{} return |
access_token cookie with valid JWT → redirect |
❌ No | Only jwt_token tested; the or request.cookies.get("access_token") branch (mcpgateway/admin.py:3724) has zero coverage |
access_token cookie with invalid JWT → show login form |
❌ No | Same gap as above (mcpgateway/admin.py:3724) |
|
Thanks @marekdano — good UX improvement for #3430. Checking JWT/access_token cookies before rendering the login page is the right approach. LGTM. |
4c4b9e3 to
c7860d0
Compare
gcgoncalves
left a comment
There was a problem hiding this comment.
Code looks good. Tested and works as expected.
156ba79 to
cf5a923
Compare
Signed-off-by: Marek Dano <Marek.Dano@ibm.com>
Signed-off-by: Marek Dano <Marek.Dano@ibm.com>
Signed-off-by: Marek Dano <Marek.Dano@ibm.com>
Signed-off-by: Marek Dano <Marek.Dano@ibm.com>
Signed-off-by: Marek Dano <Marek.Dano@ibm.com>
- Remove stray @pytest.mark.asyncio decorator that was separated from test_admin_login_handler_missing_fields by new test insertions - Add missing password_reset_enabled mock to access_token and None payload test variants for consistency - Add HTTPException test case (the real-world error path from verify_jwt_token_cached) - Simplify Playwright test: remove unnecessary try/except around not_to_be_visible assertion and redundant wait_for_timeout - Apply black/isort formatting Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
a88047d to
281b734
Compare
crivetimihai
left a comment
There was a problem hiding this comment.
Reviewed and rebased onto latest main (clean, no conflicts).
Feature review: The login page redirect logic is correct and well-designed — validates JWT cookies server-side via verify_jwt_token_cached, redirects authenticated users to the dashboard (303), and falls through to the login form for invalid/expired tokens. No security or performance concerns.
Review fixes applied (commit 281b734):
- Fixed stray
@pytest.mark.asynciodecorator that was separated fromtest_admin_login_handler_missing_fieldsby the new test insertions - Added missing
password_reset_enabledmock toaccess_tokenandNone-payload test variants for consistency with thejwt_tokenvariant - Added
HTTPExceptiontest case —verify_jwt_token_cachedraisesHTTPException(notjwt.PyJWTError) for expired/invalid tokens, so this covers the primary real-world error path - Simplified Playwright test: removed unnecessary
try/exceptaroundnot_to_be_visible()and redundantwait_for_timeout(1000) - Applied
black/isortformatting
The LoginPage.email_input locator (input[name="email"]) matches multiple elements on the admin dashboard (create-user and invite-user forms), causing a strict mode violation. Use is_on_login_page() URL check instead, which is both more reliable and semantically correct. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
|
Fixed the Playwright test failure ( The Commit: |
The @lru_cache on get_jwt_public_key_or_secret() in jwt_config_helper causes intermittent doctest failures under pytest-xdist (-n 4). When another test runs first and caches the real settings key, subsequent doctests that assign DummySettings get a stale cached key, causing InvalidSignatureError. Adding jch.clear_jwt_caches() after each DummySettings assignment ensures cache isolation. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
…3461) * fix: redirecting authenticated users on /admin/login page to /admin one. Signed-off-by: Marek Dano <Marek.Dano@ibm.com> * fix: failing pytest in test_admin.py Signed-off-by: Marek Dano <Marek.Dano@ibm.com> * fix: pylint issues Signed-off-by: Marek Dano <Marek.Dano@ibm.com> * fix: cwe issues and tests Signed-off-by: Marek Dano <Marek.Dano@ibm.com> * fix: unit tests in test_admin.py Signed-off-by: Marek Dano <Marek.Dano@ibm.com> * fix: review fixes for login page redirect tests - Remove stray @pytest.mark.asyncio decorator that was separated from test_admin_login_handler_missing_fields by new test insertions - Add missing password_reset_enabled mock to access_token and None payload test variants for consistency - Add HTTPException test case (the real-world error path from verify_jwt_token_cached) - Simplify Playwright test: remove unnecessary try/except around not_to_be_visible assertion and redundant wait_for_timeout - Apply black/isort formatting Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> * fix: use URL check instead of email locator in redirect test The LoginPage.email_input locator (input[name="email"]) matches multiple elements on the admin dashboard (create-user and invite-user forms), causing a strict mode violation. Use is_on_login_page() URL check instead, which is both more reliable and semantically correct. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> * fix: clear JWT lru_cache in doctests to prevent intermittent failures The @lru_cache on get_jwt_public_key_or_secret() in jwt_config_helper causes intermittent doctest failures under pytest-xdist (-n 4). When another test runs first and caches the real settings key, subsequent doctests that assign DummySettings get a stale cached key, causing InvalidSignatureError. Adding jch.clear_jwt_caches() after each DummySettings assignment ensures cache isolation. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> --------- Signed-off-by: Marek Dano <Marek.Dano@ibm.com> Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> Co-authored-by: Marek Dano <Marek.Dano@ibm.com> Co-authored-by: Mihai Criveti <crivetimihai@gmail.com>
Fix two regressions from recent merges: 1. pagination_controls.html references pagination.page_items but the 5 inline pagination dicts in admin.html did not include it. Jinja2 rendered Undefined as empty string producing invalid JS: `pageItems: ,`. Add 'page_items': None to all inline pagination dicts. (from #3238) 2. admin_login_page() redirected any authenticated user to /admin without checking admin privileges, causing an infinite redirect loop for non-admin users. Now checks is_admin in JWT payload and skips the redirect when error query params are present. (from #3461) 3. MANIFEST.in excluded gitignored plugins_rust/Cargo.lock that was matched by recursive-include *.lock, fixing make verify. Closes #3238, closes #3461 Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
…3645) Fix two regressions from recent merges: 1. pagination_controls.html references pagination.page_items but the 5 inline pagination dicts in admin.html did not include it. Jinja2 rendered Undefined as empty string producing invalid JS: `pageItems: ,`. Add 'page_items': None to all inline pagination dicts. (from #3238) 2. admin_login_page() redirected any authenticated user to /admin without checking admin privileges, causing an infinite redirect loop for non-admin users. Now checks is_admin in JWT payload and skips the redirect when error query params are present. (from #3461) 3. MANIFEST.in excluded gitignored plugins_rust/Cargo.lock that was matched by recursive-include *.lock, fixing make verify. Closes #3238, closes #3461 Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
…3645) Fix two regressions from recent merges: 1. pagination_controls.html references pagination.page_items but the 5 inline pagination dicts in admin.html did not include it. Jinja2 rendered Undefined as empty string producing invalid JS: `pageItems: ,`. Add 'page_items': None to all inline pagination dicts. (from #3238) 2. admin_login_page() redirected any authenticated user to /admin without checking admin privileges, causing an infinite redirect loop for non-admin users. Now checks is_admin in JWT payload and skips the redirect when error query params are present. (from #3461) 3. MANIFEST.in excluded gitignored plugins_rust/Cargo.lock that was matched by recursive-include *.lock, fixing make verify. Closes #3238, closes #3461 Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
…3461) * fix: redirecting authenticated users on /admin/login page to /admin one. Signed-off-by: Marek Dano <Marek.Dano@ibm.com> * fix: failing pytest in test_admin.py Signed-off-by: Marek Dano <Marek.Dano@ibm.com> * fix: pylint issues Signed-off-by: Marek Dano <Marek.Dano@ibm.com> * fix: cwe issues and tests Signed-off-by: Marek Dano <Marek.Dano@ibm.com> * fix: unit tests in test_admin.py Signed-off-by: Marek Dano <Marek.Dano@ibm.com> * fix: review fixes for login page redirect tests - Remove stray @pytest.mark.asyncio decorator that was separated from test_admin_login_handler_missing_fields by new test insertions - Add missing password_reset_enabled mock to access_token and None payload test variants for consistency - Add HTTPException test case (the real-world error path from verify_jwt_token_cached) - Simplify Playwright test: remove unnecessary try/except around not_to_be_visible assertion and redundant wait_for_timeout - Apply black/isort formatting Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> * fix: use URL check instead of email locator in redirect test The LoginPage.email_input locator (input[name="email"]) matches multiple elements on the admin dashboard (create-user and invite-user forms), causing a strict mode violation. Use is_on_login_page() URL check instead, which is both more reliable and semantically correct. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> * fix: clear JWT lru_cache in doctests to prevent intermittent failures The @lru_cache on get_jwt_public_key_or_secret() in jwt_config_helper causes intermittent doctest failures under pytest-xdist (-n 4). When another test runs first and caches the real settings key, subsequent doctests that assign DummySettings get a stale cached key, causing InvalidSignatureError. Adding jch.clear_jwt_caches() after each DummySettings assignment ensures cache isolation. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> --------- Signed-off-by: Marek Dano <Marek.Dano@ibm.com> Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> Co-authored-by: Marek Dano <Marek.Dano@ibm.com> Co-authored-by: Mihai Criveti <crivetimihai@gmail.com> Signed-off-by: Yosief Eyob <yosiefogbazion@gmail.com>
…3645) Fix two regressions from recent merges: 1. pagination_controls.html references pagination.page_items but the 5 inline pagination dicts in admin.html did not include it. Jinja2 rendered Undefined as empty string producing invalid JS: `pageItems: ,`. Add 'page_items': None to all inline pagination dicts. (from #3238) 2. admin_login_page() redirected any authenticated user to /admin without checking admin privileges, causing an infinite redirect loop for non-admin users. Now checks is_admin in JWT payload and skips the redirect when error query params are present. (from #3461) 3. MANIFEST.in excluded gitignored plugins_rust/Cargo.lock that was matched by recursive-include *.lock, fixing make verify. Closes #3238, closes #3461 Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> Signed-off-by: Yosief Eyob <yosiefogbazion@gmail.com>
…BM#3645) Fix two regressions from recent merges: 1. pagination_controls.html references pagination.page_items but the 5 inline pagination dicts in admin.html did not include it. Jinja2 rendered Undefined as empty string producing invalid JS: `pageItems: ,`. Add 'page_items': None to all inline pagination dicts. (from IBM#3238) 2. admin_login_page() redirected any authenticated user to /admin without checking admin privileges, causing an infinite redirect loop for non-admin users. Now checks is_admin in JWT payload and skips the redirect when error query params are present. (from IBM#3461) 3. MANIFEST.in excluded gitignored plugins_rust/Cargo.lock that was matched by recursive-include *.lock, fixing make verify. Closes IBM#3238, closes IBM#3461 Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> Signed-off-by: KRISHNAN, SANTHANA <sk8069@exo.att.com>
🐛 Bug-fix PR
Closes: #3430
📌 Summary
Fixed issue where authenticated users could still access the login page instead of being redirected to the dashboard.
🔁 Reproduction Steps
🐞 Root Cause
When a logged-in user navigated to /admin/login by typing it directly in the address bar, they would see the login form again instead of being redirected to the dashboard. This created a confusing user experience.
💡 Fix Description
Added authentication check in the admin_login_page endpoint to detect already-authenticated users and redirect them to the dashboard.
Changes
Modified admin_login_page() function (line ~3681):
Added two test cases to TestAdminLoginPage class:
test_admin_login_page_redirects_authenticated_usertest_admin_login_page_shows_form_for_invalid_tokenAdded test_logged_in_user_redirected_from_login_page to TestAuthentication class:
Behavior
Before:
After:
🧪 Verification
make lintmake testmake coverage📐 MCP Compliance (if relevant)
✅ Checklist
make black isort pre-commit)