test(auth): pin proxy_user > jwt_user precedence in current_active_user#22
Merged
Merged
Conversation
Regression-guard for SurfSense's architectural immunity to the cross-app stale-session-on-user-switch class of bug. SurfSense doesn't need an explicit Rule-2-style "compare upstream identity vs session identity → flush on mismatch" middleware (like Plane MODSetter#29, Outline #19, Penpot #18, Twenty #8 do) because `current_active_user` in app.users already resolves to the upstream identity (proxy_user) over the persisted session (jwt_user) whenever both are present. That precedence IS the contract. If a future refactor flips it (or removes the proxy_user check during a "simplify auth" pass) the stale-session bug class is silently re-introduced — and type-checks pass, so it would ship. These five tests pin the contract: 1. proxy_user wins when both proxy_user and jwt_user are present with different identities (the user-switch scenario) 2. Falls back to jwt_user when proxy_user is absent (header-absent is NOT a logout signal — internal calls, OPTIONS preflight, direct backend hits at 127.0.0.1 legitimately arrive without a proxy header) 3. Raises 401 when neither is present (sanity) 4. Same precedence for current_optional_user 5. current_optional_user returns None (does not raise) when neither is present Cross-app contract: awais786/sso-rules-moneta:openspec/specs/proxy-auth-middleware/spec.md SurfSense's architectural-immunity reasoning: awais786/sso-rules-moneta:surfsense-security.md Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
There was a problem hiding this comment.
Pull request overview
Adds regression tests to pin current_active_user / current_optional_user precedence so proxy-auth identity wins over stale JWT identity.
Changes:
- Adds five async unit tests covering proxy-vs-JWT precedence, JWT fallback, unauthenticated rejection, and optional-user behavior.
- Uses lightweight request/user helpers and mocks SMB auto-join side effects where needed.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| return user | ||
|
|
||
|
|
||
| @pytest.mark.asyncio |
There was a problem hiding this comment.
Applied in commit 27a1bc3: added a module-level pytestmark = pytest.mark.unit to surfsense_backend/tests/unit/test_current_active_user_proxy_precedence.py so these tests are included by the existing pytest -m unit CI selection.
Agent-Logs-Url: https://github.com/Pressingly/SurfSense/sessions/7cb36524-6c58-452f-8b60-9d6256a6caaa Co-authored-by: awais786 <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
SurfSense is architecturally immune to the cross-app stale-session-on-user-switch bug class that affected Pressingly/plane#29 / Pressingly/outline#19 / Pressingly/penpot#18 / Pressingly/twenty#8. The reason:
current_active_userinapp.usersresolves the upstream identity (request.state.proxy_user, set per-request byProxyAuthMiddlewarefromX-Auth-Request-Email) before falling back to the persisted JWT identity. The other apps had to add an explicit "compare upstream vs session, flush on mismatch" middleware. SurfSense's existing precedence already gives the same property for free.That precedence IS the contract. This PR adds 5 unit tests that pin it so a future refactor cannot silently re-introduce the bug.
The contract
If a future "simplify auth to JWT-only" change removes the
proxy_userbranch, type-checks pass and the regression ships. These tests catch it.Cross-app reference
awais786/sso-rules-moneta:openspec/specs/proxy-auth-middleware/spec.mdawais786/sso-rules-moneta:surfsense-security.md§"Post-audit finding — stale-session class of bug is N/A here"Test plan
pytest surfsense_backend/tests/unit/test_current_active_user_proxy_precedence.py -v— 5 cases, all passing:test_proxy_user_wins_when_both_proxy_and_jwt_present— the user-switch scenario: proxy says alice, JWT says bob (stale), result MUST be alicetest_falls_back_to_jwt_when_proxy_user_absent— header-absent is NOT a logout signal per spec; JWT serves the requesttest_raises_401_when_neither_proxy_nor_jwt_present— sanitytest_optional_user_returns_proxy_when_both_present— same precedence in the optional varianttest_optional_user_returns_none_when_neither_present— optional variant returns None instead of raisingWhy this is a test-only PR
No production-code change. SurfSense's
current_active_useralready implements the correct precedence; the architectural-immunity audit insurfsense-security.mdconfirms it. This PR's contribution is the regression guard — a test suite that fails if anyone flips the precedence or removes theproxy_usercheck.Out of scope
This is the SurfSense leg of the cross-app stale-session-on-user-switch family. The other four apps' fixes:
django.contrib.auth.logout(request)on mismatchclearCookie('tokenPair')before guard returns false🤖 Generated with Claude Code