Skip to content

feat(auth): add GET /auth/jwt/portal-logout for cross-app logout chain#24

Open
awais786 wants to merge 1 commit into
foss-mainfrom
feat/portal-logout-endpoint
Open

feat(auth): add GET /auth/jwt/portal-logout for cross-app logout chain#24
awais786 wants to merge 1 commit into
foss-mainfrom
feat/portal-logout-endpoint

Conversation

@awais786
Copy link
Copy Markdown

Summary

Adds `GET /auth/jwt/portal-logout?next=` so the foss-server-bundle portal's "Log out of all apps" button can include SurfSense in its cross-origin redirect chain.

Same pattern as Plane MODSetter#35 / Outline #24 / Penpot #24. Reuses SurfSense's existing `revoke_all_user_tokens` primitive (the same one `/auth/jwt/logout-all` uses).

Behaviour

`GET /auth/jwt/portal-logout?next=<absolute_url>`:

  • Resolves user from oauth2-proxy `X-Auth-Request-Email` headers via the existing `ProxyAuthMiddleware` (`request.state.proxy_user`).
  • Calls `revoke_all_user_tokens(user.id)` — invalidates every refresh token in the DB for this user (same effect as `/auth/jwt/logout-all`).
  • Validates `?next=` against `PLATFORM_DOMAIN` (scheme http/https + suffix-match with dot boundary).
  • Returns 302 to `?next=` if allowlisted; otherwise 200 with cookies still cleared.
  • Defensively clears the short-lived `surfsense_sso_token` + `surfsense_sso_refresh_token` cookies (they're already 60s TTL but harmless to expire eagerly).

Files

`2 files changed, 76 insertions(+), 1 deletion(-)`:

  • `surfsense_backend/app/routes/auth_routes.py` — new `portal_logout` handler + `_is_allowed_next` validator
  • `surfsense_backend/app/config/init.py` — `PLATFORM_DOMAIN` env var

Force-logout risk

GET endpoints are reachable via ``. For SurfSense the impact is revoking every refresh token for the user, which kicks them out of all devices — not just the current browser. Higher impact than Outline/Penpot (cookie-only clear).

Tradeoff accepted because:

  • ForwardAuth blocks the unauthenticated requests at the edge anyway
  • Refresh-token revocation is the canonical "logout" for SurfSense's JWT model — anything weaker would not actually log the user out
  • Cognito session still controls real access; portal "Logout all" is the explicit "log out everywhere" UX

Out of scope

  • Portal-side button wiring — separate PR in foss-server-bundle / foss-server-bundle-devstack
  • Devstack env passthrough — separate small PR
  • Tests — SurfSense's test infra is heavier; integration tests verified manually in the FOSS bundle

Coordination

Sister PRs:

@awais786 awais786 force-pushed the feat/portal-logout-endpoint branch from e3a1744 to 17bd7d7 Compare May 18, 2026 18:00
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