feat(auth): mPass SSO via oauth2-proxy ForwardAuth with cookie-handoff - #1
Conversation
efe87b4 to
7cdf5ee
Compare
testing backend test CI workflow
8b6e755 to
603caf9
Compare
There was a problem hiding this comment.
Pull request overview
Adds an oauth2-proxy/Traefik ForwardAuth-based SSO flow with backend-side user JIT provisioning and a frontend cookie-handoff to establish the existing JWT session model.
Changes:
- Introduces backend
ProxyAuthMiddleware+/auth/jwt/proxy-loginto resolve/provision users fromX-Auth-Request-Emailand hand off JWTs via short-lived cookies. - Updates frontend login/logout behavior to use proxy-login + SSO cookies, and removes the
/auth/callbackpage/TokenHandler flow. - Adds local/dev infra bits (compose/Traefik configs) and unit tests for the new middleware + proxy-login endpoint.
Reviewed changes
Copilot reviewed 26 out of 30 changed files in this pull request and generated 13 comments.
Show a summary per file
| File | Description |
|---|---|
| surfsense_web/lib/env-config.ts | Adds SSO auth mode helper; changes default auth type. |
| surfsense_web/lib/auth-utils.ts | Adds SSO cookie read/clear helpers; updates logout flow. |
| surfsense_web/lib/apis/base-api.service.ts | Makes Authorization header conditional; removes client-side “missing token” preflight. |
| surfsense_web/Dockerfile | Adds NEXT_PUBLIC_OAUTH2_PROXY_URL build arg/env. |
| surfsense_web/docker-entrypoint.js | Adds runtime placeholder replacement for NEXT_PUBLIC_OAUTH2_PROXY_URL. |
| surfsense_web/components/TokenHandler.tsx | Comments out native OAuth callback token extraction component. |
| surfsense_web/components/homepage/hero-section.tsx | Routes “Get Started” to proxy-login for Google/SSO modes. |
| surfsense_web/components/auth/sign-in-button.tsx | Routes “Sign In” to proxy-login for Google/SSO modes. |
| surfsense_web/components/assistant-ui/image.tsx | Reorders named exports. |
| surfsense_web/atoms/user/user-query.atoms.ts | Forces /users/me query to run unconditionally. |
| surfsense_web/app/auth/callback/page.tsx | Removes auth callback page. |
| surfsense_web/app/auth/callback/loading.tsx | Removes auth callback loading component. |
| surfsense_web/app/api/zero/query/route.ts | Prefers internal backend URL for server-side auth fetches. |
| surfsense_web/app/(home)/page.tsx | Adds SSO cookie-handoff on homepage; redirects to proxy-login. |
| surfsense_web/app/(home)/login/LocalLoginForm.tsx | Stores JWT directly and routes to dashboard (no callback). |
| surfsense_web/app/(home)/login/GoogleLoginButton.tsx | Redirects to proxy-login (including auto-redirect on mount). |
| surfsense_web/.env.example | Documents new OIDC/oauth2-proxy env vars for logout. |
| surfsense_backend/tests/unit/routes/test_proxy_login.py | Adds unit tests for /auth/jwt/proxy-login. |
| surfsense_backend/tests/unit/routes/init.py | Adds unit-test package marker. |
| surfsense_backend/tests/unit/middleware/test_proxy_auth.py | Adds unit tests for ProxyAuthMiddleware. |
| surfsense_backend/scripts/docker/entrypoint.sh | Adds a role-based container entrypoint with migrations + Celery roles. |
| surfsense_backend/app/users.py | Adds “proxy-user first” current_active_user/current_optional_user deps. |
| surfsense_backend/app/routes/auth_routes.py | Adds /auth/jwt/proxy-login endpoint and cookie handoff. |
| surfsense_backend/app/middleware/proxy_auth.py | Adds ForwardAuth header-based user resolution/JIT provisioning middleware. |
| surfsense_backend/app/middleware/init.py | Adds middleware package marker. |
| surfsense_backend/app/config/init.py | Adds bypass-path config; attempts lazy embedding model init + chunker sizing change. |
| surfsense_backend/app/app.py | Registers proxy middleware, adds /users/me override routes, removes fastapi-users auth router registrations. |
| surfsense_backend/.env.example | Documents MPASS_BYPASS_PATHS. |
| docker-compose.local.yml | Adds local DB/Redis/backend compose setup. |
| docker-compose.devstack.yml | Adds external mpass-net overlay wiring for backend. |
| config/traefik/traefik.yml | Adds Traefik static config for local ForwardAuth setup. |
| config/traefik/dynamic.yml | Adds Traefik dynamic routers/middleware for oauth2-proxy ForwardAuth. |
| .gitignore | Ignores local env/IDE files. |
| .github/workflows/docker-build.yml | Expands workflow branches; adjusts tagging/latest behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| Security / trust model | ||
| ---------------------- | ||
| This middleware trusts X-Auth-Request-Email unconditionally. That is safe | ||
| because: | ||
| 1. Traefik ForwardAuth overwrites X-Auth-Request-* headers on every | ||
| request, so they cannot be spoofed by a browser or external client. | ||
| 2. In production the app container does not expose its port externally — | ||
| only Traefik is public-facing, so there is no direct path to the app | ||
| that bypasses header rewriting. | ||
|
|
||
| A shared-secret header (set by oauth2-proxy, forwarded via Traefik | ||
| authResponseHeaders, checked here) would add defense-in-depth against a | ||
| misconfigured ingress but is not required given the network topology above. | ||
| Add it if the threat model ever changes (e.g. the app port becomes reachable | ||
| inside a zero-trust network where internal callers could forge headers). | ||
| """ |
There was a problem hiding this comment.
ProxyAuthMiddleware trusts X-Auth-Request-Email unconditionally and will JIT-provision/inject a user whenever the header is present. Because this middleware is part of the app and does not check any enable-flag or shared-secret header, a client that can reach the backend directly could spoof this header to impersonate or create users. Please gate this middleware behind an explicit config flag (disabled by default) and/or validate an additional secret header set by the trusted proxy before resolving users.
| # Starlette executes middleware in reverse registration order (last added = first to | ||
| # run on the request). Request-path execution order: | ||
| # | ||
| # CORSMiddleware → ProxyHeadersMiddleware → SlowAPIMiddleware | ||
| # → ProxyAuthMiddleware → RequestPerfMiddleware → route handler | ||
| # | ||
| # SlowAPIMiddleware wraps ProxyAuthMiddleware so rate limiting fires before any DB | ||
| # lookup — abusive traffic is shed at the limiter before we touch the database. | ||
| # ProxyAuthMiddleware runs after ProxyHeadersMiddleware so the client IP/scheme | ||
| # are already normalised when we resolve the user. | ||
|
|
||
| # Innermost: reads X-Auth-Request-Email, resolves/creates user, sets request.state.proxy_user. | ||
| app.add_middleware(ProxyAuthMiddleware) | ||
|
|
There was a problem hiding this comment.
ProxyAuthMiddleware is added unconditionally. In deployments where the backend is reachable without Traefik ForwardAuth header rewriting, this creates an auth bypass via a spoofed X-Auth-Request-Email header. Consider only registering this middleware when proxy-auth is explicitly enabled (e.g. AUTH_TYPE==SSO or a dedicated flag) and otherwise not adding it at all.
| app.include_router( | ||
| fastapi_users.get_users_router(UserRead, UserUpdate), | ||
| prefix="/users", | ||
| tags=["users"], | ||
| ) | ||
|
|
||
|
|
||
| # Include custom auth routes (refresh token, logout) | ||
| app.include_router(auth_router) | ||
|
|
There was a problem hiding this comment.
The app no longer includes the fastapi-users auth/register/reset/verify (and Google OAuth) routers, but the frontend still calls /auth/jwt/login and /auth/register (e.g. LocalLoginForm/AuthApiService). This will break LOCAL/GOOGLE authentication flows and likely any existing deployments relying on those endpoints. Please restore these routers conditionally (based on AUTH_TYPE) or update the frontend + docs to remove those flows entirely.
| useEffect(() => { | ||
| if (getBearerToken()) { | ||
| router.replace("/dashboard"); | ||
| return; | ||
| } | ||
|
|
||
| // Check for SSO handoff cookies set by /auth/jwt/proxy-login after Cognito login. | ||
| // The backend sets short-lived cookies (60s TTL) and redirects here instead of | ||
| // to /auth/callback, avoiding any Traefik path-split between frontend and backend. | ||
| const { token, refreshToken } = getSSOCookieTokens(); | ||
| if (token) { | ||
| setBearerToken(token); | ||
| if (refreshToken) setRefreshToken(refreshToken); | ||
| clearSSOCookies(); | ||
| router.replace("/dashboard"); | ||
| return; | ||
| } | ||
|
|
||
| // No JWT anywhere — trigger SSO flow. | ||
| window.location.href = `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/auth/jwt/proxy-login`; | ||
| }, [router]); |
There was a problem hiding this comment.
This effect always redirects unauthenticated users to /auth/jwt/proxy-login. That makes the homepage unusable for LOCAL/GOOGLE auth modes (and can create redirect loops / hard failures when oauth2-proxy ForwardAuth isn’t configured). Gate the SSO cookie-handoff logic + redirect behind isSSOAuth() (or AUTH_TYPE) and keep the homepage public in other modes.
| email = request.headers.get("x-auth-request-email") | ||
| if not email: | ||
| raise HTTPException( | ||
| status_code=status.HTTP_401_UNAUTHORIZED, | ||
| detail="No proxy auth header — request did not pass through oauth2-proxy ForwardAuth", | ||
| ) | ||
|
|
||
| email = email.strip().lower() | ||
|
|
There was a problem hiding this comment.
Email normalisation here is only strip().lower(), while the proxy-auth middleware uses NFKC normalisation to prevent Unicode homoglyph lookalikes. To avoid inconsistent identity matching (and potential account confusion), reuse the same normalisation logic in both places (e.g. a shared helper).
| // Validate the bearer token — skip check when no token is present; | ||
| // the backend proxy-auth middleware handles authentication via | ||
| // the X-Auth-Request-Email header set by oauth2-proxy/Traefik. | ||
| const isNoAuthEndpoint = | ||
| this.noAuthEndpoints.includes(url) || | ||
| this.noAuthPrefixes.some((prefix) => url.startsWith(prefix)) || | ||
| /^\/api\/v1\/invites\/[^/]+\/info$/.test(url); | ||
| if (!this.bearerToken && !isNoAuthEndpoint) { | ||
| throw new AuthenticationError("You are not authenticated. Please login again."); | ||
| } | ||
|
|
There was a problem hiding this comment.
isNoAuthEndpoint is computed but no longer used after removing the preflight bearer-token check. This is dead code now and can be removed to avoid confusion (or reintroduced as part of a different auth-mode gate if needed).
| export const currentUserAtom = atomWithQuery(() => { | ||
| return { | ||
| queryKey: USER_QUERY_KEY, | ||
| staleTime: 5 * 60 * 1000, | ||
| enabled: !!getBearerToken(), | ||
| enabled: true, | ||
| queryFn: userQueryFn, | ||
| }; |
There was a problem hiding this comment.
Setting enabled: true makes the app fetch /users/me even when there is no JWT (LOCAL/GOOGLE unauthenticated state) or on public pages, which can cause noisy 401s/retries and unnecessary load. Consider enabling this query only when you expect authentication (e.g. !!getBearerToken() for JWT modes, or isSSOAuth() for proxy-auth).
| github.ref == 'refs/heads/feat/mpass-proxy-auth' || | ||
| github.event_name == 'workflow_dispatch' |
There was a problem hiding this comment.
This workflow now (a) creates/pushes version tags when building feat/mpass-proxy-auth, and (b) marks images from that feature branch as latest. Both are high-risk: feature-branch pushes can overwrite the stable latest tag and pollute version tag history with non-release builds. Please limit tagging and latest publication to the default/release branches only.
| github.ref == 'refs/heads/feat/mpass-proxy-auth' || | |
| github.event_name == 'workflow_dispatch' | |
| ( | |
| github.event_name == 'workflow_dispatch' && | |
| ( | |
| github.event.inputs.branch == '' || | |
| github.event.inputs.branch == github.event.repository.default_branch || | |
| github.event.inputs.branch == 'foss-main' | |
| ) | |
| ) |
| # mPass patch: defer embedding model loading to first use so the container | ||
| # starts without the PyTorch/sentence-transformers memory spike. | ||
| # Routes that use embeddings (search, indexing) will trigger lazy init on | ||
| # first request. Auth/SSO routes are unaffected. | ||
| _embedding_kwargs = embedding_kwargs | ||
| _embedding_model_instance = None | ||
|
|
||
| @classmethod | ||
| def _get_embedding_model(cls): | ||
| if cls._embedding_model_instance is None: | ||
| cls._embedding_model_instance = AutoEmbeddings.get_embeddings( | ||
| cls.EMBEDDING_MODEL, | ||
| **cls._embedding_kwargs, | ||
| ) | ||
| return cls._embedding_model_instance | ||
|
|
||
| embedding_model_instance = property(lambda self: self.__class__._get_embedding_model()) | ||
| is_local_embedding_model = "://" not in (EMBEDDING_MODEL or "") | ||
| chunker_instance = RecursiveChunker( | ||
| chunk_size=getattr(embedding_model_instance, "max_seq_length", 512) | ||
| ) | ||
| code_chunker_instance = CodeChunker( | ||
| chunk_size=getattr(embedding_model_instance, "max_seq_length", 512) | ||
| ) | ||
| chunker_instance = RecursiveChunker(chunk_size=512) | ||
| code_chunker_instance = CodeChunker(chunk_size=512) |
There was a problem hiding this comment.
The new lazy-init approach makes embedding_model_instance a property, but this breaks existing module-level validation (hasattr(embedding_model_instance, 'dimension') now checks the property object) and won’t actually prevent eager model loading because other modules (e.g. app/db.py) access config.embedding_model_instance.dimension at import time. Consider switching to an explicit getter method (or cached property on the instance) and refactoring places that need dimension to avoid forcing model init during import.
| // Auth type: | ||
| // "LOCAL" — email/password login form | ||
| // "GOOGLE" — Google OAuth (native fastapi-users Google flow) | ||
| // "SSO" — Cognito/oauth2-proxy ForwardAuth (our devstack pattern) | ||
| // Placeholder: __NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE__ | ||
| export const AUTH_TYPE = process.env.NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE || "GOOGLE"; | ||
| export const AUTH_TYPE = process.env.NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE || "SSO"; | ||
|
|
There was a problem hiding this comment.
Defaulting AUTH_TYPE to "SSO" changes the out-of-box behavior for environments that don’t set NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE (including many local/self-hosted setups). Given .env.example only documents LOCAL/GOOGLE, this default is likely to break existing deployments. Consider keeping the previous default or updating the documented examples and any auth-mode gates accordingly.
0fbba78 to
8c3ff62
Compare
This fork integrates SurfSense with mPass (AWS Cognito-based OIDC) via
oauth2-proxy as a centralized authentication gateway, replacing
SurfSense's local fastapi-users authentication. The changes span
backend middleware, frontend redirect logic, and tests guarding the
SSO contract.
Backend
- Add Starlette ProxyAuthMiddleware (app/middleware/proxy_auth.py)
that reads X-Auth-Request-Email injected by oauth2-proxy via
Traefik ForwardAuth, JIT-provisions a local user on first SSO
login, and injects request.state.proxy_user so the FastAPI
dependency tree sees a fully authenticated user without needing
a JWT cookie.
- Add proxy_login GET endpoint at /auth/jwt/proxy-login
(app/routes/auth_routes.py) that issues a JWT after the SSO
header is validated and delivers it to the frontend via
short-lived surfsense_sso_token + surfsense_sso_refresh_token
cookies, then 302-redirects to / where the home-route splash
completes the cookie handoff to localStorage.
- Local fastapi-users routes (POST /auth/jwt/login, /auth/register,
/auth/forgot-password, /auth/reset-password,
/auth/request-verify-token, /auth/verify) are NOT registered, so
no code path can authenticate or create accounts without going
through Cognito.
- Refresh token machinery (POST /auth/jwt/refresh, /auth/jwt/revoke,
/auth/jwt/logout-all) is preserved as the SSO logout / token
rotation surface.
Frontend
- app/(home)/page.tsx is a neutral splash that runs the cookie
handoff in useEffect and routes to /dashboard. The upstream
marketing JSX is removed so SSO users never flash the homepage.
- app/(home)/layout.tsx hides the navbar + footer on the splash
route so the splash is fully blank during the redirect dance.
- app/(home)/login/page.tsx and app/(home)/register/page.tsx fall
back to a splash + window.location.replace() to oauth2-proxy/
sign_in when isSSOAuth() is true. The original LocalLoginForm
and registration form code is preserved unchanged for non-SSO
deployments.
- lib/auth-utils.ts handleUnauthorized() redirects to oauth2-proxy/
sign_in (instead of the dead /login route) when an in-app API
call returns 401, completing the SSO loop without flashing the
local form.
- lib/auth-utils.ts logout() implements the 3-layer logout flow:
revoke refresh token -> clear localStorage -> redirect to
oauth2-proxy/sign_out -> Cognito/logout -> back to /.
Tests
- tests/unit/routes/test_proxy_login.py adds:
- TestProxyLoginRouteRegistration: positive guards that
/auth/jwt/proxy-login is registered, accepts GET, and
dispatches to the proxy_login function.
- TestProxyLogin: behaviour tests for the 401/302/JIT-provision/
inactive-user paths, with a corrected SQLAlchemy mock chain
that previously skipped result.unique() and silently masked
the new-user provisioning bug.
- TestLocalAuthRoutesAreNotRegistered: negative guard that
asserts none of the standard fastapi-users local-auth
endpoints exist on the FastAPI app, catching accidental
re-introduction during a future upstream sync.
Configuration
- Reads NEXT_PUBLIC_FASTAPI_BACKEND_AUTH_TYPE (defaults to "SSO"
via lib/env-config.ts) so the same fork can be deployed in
LOCAL or GOOGLE auth modes without code changes — only the
splash redirects fire when isSSOAuth() returns true.
Known issue (deferred): direct visits to /login or /register can
flash the original form for ~100-200ms before the SSO redirect
fires, due to a Suspense streaming or useGlobalLoadingEffect
interaction with the SSR pre-render. Cosmetic only — the normal
SSO flow does not route through these pages directly.
- Default AUTH_TYPE to SSO everywhere (config, .env.example, compose files). Both .env.example files warn not to change the value since LOCAL/GOOGLE backend routes are not registered in this fork. - Refactor proxy_login to read request.state.proxy_user (set by ProxyAuthMiddleware) instead of querying the database directly. All user provisioning (JIT creation, on_after_register side effects) is now owned by the middleware. proxy_login only issues the JWT and sets the cookie-handoff cookies. - Remove RuntimeError guard that crashed the app on AUTH_TYPE != SSO. The SSO contract is enforced by the middleware + missing backend routes + frontend isSSOAuth() conditionals + the negative test guard — a hard crash on startup is redundant and prevents graceful fallback. - Remove unused imports (SECRET, auth_backend, PasswordHelper, uuid, secrets) from app.py and auth_routes.py. - Rewrite test_proxy_login.py to match the refactored proxy_login that reads request.state.proxy_user instead of touching the DB.
The logout flow's logout_uri was set to window.location.origin (https://foss-research.local.moneta.dev) which is behind ForwardAuth. After Cognito cleared the session, the user would bounce back to Cognito login instead of seeing the landing page. Now reads NEXT_PUBLIC_LOGOUT_REDIRECT_URL from the container env (set in docker-compose.yml to the platform landing page). Falls back to window.location.origin for non-devstack deployments.
…nstance crash ProxyAuthMiddleware resolves the user in its own async session which closes before the route handler runs. The User object left on request.state.proxy_user is detached — any downstream handler that tries session.refresh(user) or accesses lazy-loaded relationships in a new session gets "Instance is not persistent within this Session". Fix: current_active_user() and current_optional_user() now call _refetch_proxy_user() which re-fetches the user by ID in a fresh session then expunges it cleanly. Every route handler gets a User object that can be safely merged into any session context. Adds ~1-2ms overhead per authenticated request (one SELECT by ID). Verified: zero InvalidRequestError in backend logs since the fix.
logout() sets window.location.href to the oauth2-proxy sign_out chain, but callers immediately overwrote it with "/" before the browser navigated, sending users back to the dashboard via ForwardAuth re-auth. Also replaced encodeURIComponent with single-encoding to match Plane's pattern — double-encoding caused Cognito to reject the logout_uri as unregistered, stranding users on the Cognito page.
…tch helper Move session re-attachment to the two endpoints that modify user data (update_current_user_me, complete_task) and remove the _refetch_proxy_user helper. Fixes DetachedInstanceError when SSO proxy users hit these routes.
Pushes to feat/mpass-proxy-auth only triggered docker-build.yml; backend-tests and code-quality were gated on main/dev and never ran against the fork. Retarget both to foss-main so fork PRs exercise the unit/integration/quality gates before merge.
Unit: ProxyAuthMiddleware calls result.unique().scalar_one_or_none(), but tests stubbed scalar_one_or_none one level off. The unmocked .unique() returned a fresh MagicMock, so scalar_one_or_none() on it returned MagicMock (not None), skipping the insert branch and cascading into a TypeError when (now - user.last_login) ran against a mock. Mock the full chain: result.unique.return_value.scalar_one_or_none. Integration: /auth/register and /auth/jwt/login were removed in the SSO refactor (96a9ed6), so the test bootstrap 404'd before any test ran. Replace password-based register+login with the production path: GET /auth/jwt/proxy-login with X-Auth-Request-Email header, read JWT from the surfsense_sso_token cookie. Same code path ProxyAuthMiddleware serves real oauth2-proxy traffic. Also collapses the duplicate _authenticate_test_user in test_stripe_page_purchases.py onto the shared helper.
…ABLED The SSO auth refactor accidentally dropped TEST_EMAIL from the imports even though it's still used by the webhook and reconciliation assertions to look up the test user's page limit in the DB. The create-checkout-session tests also relied on the process-level default STRIPE_PAGE_BUYING_ENABLED=TRUE. That's fine in CI (no .env loaded) but breaks locally when .env sets it to FALSE, returning 503. Monkeypatch it to True alongside the other Stripe config overrides so the tests are hermetic.
The dict(**kwargs) → {...} rewrite is a ruff unsafe-fix because in
general dict() accepts non-string keys that literals can't express.
Here every key is a string literal so the rewrite would be safe, but
the change adds no runtime value and touches a load-bearing SSO
cookie site. Noqa the one site instead.
Biome reported format drift on two files: lib/auth-utils.ts — inside the logout() SSO branch the landing-page redirect block (comment + logoutRedirect + cognitoUrl.searchParams.set) was one tab short of the surrounding if-body. Re-tabbed to align. Also normalised three regex .replace() arguments from single to double quotes to match biome's configured quote style. lib/env-config.ts — removed a stray blank line between BACKEND_URL and the ETL placeholder comment. No behaviour change.
Code Quality CI runs ruff --fix, ruff format, and biome check on every
PR and fails when the hook rewrites files. Apply the rewrites upfront:
Python (ruff):
- app/app.py — drop unused UserCreate import
- app/middleware/proxy_auth.py, app/routes/auth_routes.py,
tests/unit/routes/test_proxy_login.py, tests/utils/helpers.py —
wrap long log/assert lines to satisfy ruff format
Web (biome):
- app/(home)/login/{LocalLoginForm,page}.tsx,
app/(home)/register/page.tsx, lib/apis/base-api.service.ts,
lib/auth-utils.ts — quote style + whitespace
No behaviour change.
Mirror the Plane middleware behavior: when oauth2-proxy forwards a bare
username in X-Auth-Request-Email (cognito:username claim with no email),
synthesize {username}@{SMB_NAME}.com so provisioning can proceed. Fall
back to X-Auth-Request-User if the email header is empty.
Also set display_name to the email local part on user creation so the UI
has a readable label without the caller having to fill it in.
1f0be57 to
8eb58df
Compare
Summary
x-auth-request-emailfrom oauth2-proxy ForwardAuth, finds/creates user, establishes JWT session via cookie handoff/dashboardTokenHandlercomponent (not deleted, can be restored)FASTAPI_BACKEND_INTERNAL_URLfor server-side auth fetchBranch strategy
foss-main— tracks the latest stable upstream SurfSense release. Currently pinned to0.0.14.4. When a new upstream release is tagged,foss-mainis updated to that tag and feature branches are rebased.feat/mpass-proxy-auth— this PR branch, based onfoss-main(0.0.14.4).Traefik configuration (in devstack repo)
The following Traefik routers are configured in
foss-server-bundle-devstack/docker-compose.yml(not in this repo):mpass-auth+mpass-signinmiddleware onsurfsense-api-secureandsurfsense-securerouters (priority 1)surfsense-publicrouter for/health,/docs,/openapi.json(priority 20, no auth)surfsense-staticrouter for/_next/static(priority 20, no auth)surfsense-zero-securerouter for/zero(no ForwardAuth — uses bearer token auth)Commits
feat(auth): mPass SSO via oauth2-proxy ForwardAuth with cookie-handofftest(auth): add unit tests for proxy_login endpointfeat(devstack): add mpass network overlay and alembic migrationsfix(zero): use FASTAPI_BACKEND_INTERNAL_URL for server-side auth fetchrefactor(auth): comment out TokenHandler instead of deletingTest plan
/health,/docs,/openapi.jsonaccessible without auth/_next/static/*) served without auth prompt