Add Gecko Suite SSO cookie, session endpoint, and CORS/CSP headroom for GAM - #191
Merged
Merged
Conversation
…or GAM Prep work for integrating Gecko Asset Manager (gam.geckopico.com) and the future Gecko Video Creator as sibling apps under the suite, with Gecko Notes remaining the identity provider. Implements GN-1 through GN-3 of the integration spec; GN-4/GN-5 need no code, and GN-6's unrelated findings are left for a separate pass. GN-1 — parent-domain session cookie: - Login now also sets an HttpOnly `gecko_session` cookie (in addition to the existing JSON token) so a sibling subdomain can share the session. AUTH_COOKIE_DOMAIN/AUTH_COOKIE_SECURE control it via env. - New POST /api/auth/logout clears the cookie (added to PUBLIC_PATHS so it works even with an expired token); wired into the frontend's logout(). - jwt_auth_middleware accepts the cookie as a fallback behind the Authorization header (header always wins). - A cookie-authenticated write must come from an allowed Origin/Referer (checked against CORS_ORIGIN); header-authenticated requests are exempt, since a cross-site request can't attach a Bearer header. - New backend/tests/test_auth_cookie.py covers cookie issuance, precedence, logout, and the CSRF guard. GN-2 — GET /api/auth/session exchanges a valid cookie or header session for a fresh token, letting a sibling SPA bootstrap signed in. LoginView now honors a `?redirect=` param (validated against *.geckopico.com) so GAM can send the browser back after login. GN-3 — CORS_ORIGIN already supported the needed sibling origin via its existing comma-separated list; widened the CSP's connect-src/img-src/media-src to allow gam.geckopico.com. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016VmcsDG4b6EAFiujtcEU9D
davior
marked this pull request as ready for review
September 13, 2026 11:43
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
Prep work for integrating Gecko Asset Manager (
gam.geckopico.com, davior/gam) — and, later, Gecko Video Creator — as sibling apps under the suite, with Gecko Notes remaining the identity provider for all of them. Implements GN-1 through GN-3 of the attached integration spec (written againstmain@ff3799f). GN-4 and GN-5 are decisions that need no code (documented in the spec). GN-6 lists three unrelated findings the spec explicitly marks "offered, not required" — I left those out of this PR; happy to open a separate one if wanted:backend/app/video/compose.py'sFONT_DIRpoints at a path that doesn't exist in the image (renders silently fall back to DejaVuSans)./media/*is fully unauthenticated (on the public-path allowlist).AppConfig/configApiis declared twice (api/notes.tsvsapi/config.ts) with different shapes.GN-1 — Parent-domain session cookie
POST /login,POST /login/2fa) now also sets an HttpOnlygecko_sessioncookie alongside the existing JSON token, so a sibling app on a differentgeckopico.comsubdomain can share the session. Configured viaAUTH_COOKIE_DOMAIN/AUTH_COOKIE_SECURE.POST /api/auth/logoutclears the cookie — added toPUBLIC_PATHSso it still works with an expired token — and is now called from the frontend'slogout()(best-effort, failure ignored).jwt_auth_middlewareaccepts the cookie as a fallback behind theAuthorization: Bearerheader; the header always wins when both are present.Origin/Referer(checked against the existingCORS_ORIGINallowlist) and fails closed with neither header present. Header-authenticated requests are exempt (a cross-site page can't attach a Bearer header), and safe methods (GET/HEAD/OPTIONS) are exempt too.backend/tests/test_auth_cookie.py(10 cases) covers cookie issuance (HttpOnly, Max-Age, Domain attribute present/absent), header-over-cookie precedence, logout, and all four CSRF combinations.GN-2 —
GET /api/auth/sessionExchanges a valid session (cookie or header) for a fresh token + user, so a sibling SPA can boot up already signed in without a login form. Left out of
PUBLIC_PATHSon purpose — the middleware must authenticate it.LoginViewnow honors a?redirect=query param (in addition to the existing in-applocation.state.from), validated against a*.geckopico.comallowlist so it can't become an open redirect, so GAM can send the browser to.../login?redirect=<gam-url>and land back there after login.GN-3 — CORS and CSP headroom
CORS_ORIGINalready accepted a comma-separated list — no code change needed, just a doc tweak in.env.example; the production value is a deployment-time.envchange (CORS_ORIGIN=https://notes.geckopico.com,https://gam.geckopico.com).frontend/nginx.conf's CSP (connect-src,img-src,media-src) to allowhttps://gam.geckopico.com.Testing
cd backend && python -m pytest tests/— 1062 passed, 4 skipped (1052 passed before this change; the 10 new ones aretest_auth_cookie.py).cd frontend && npx tsc --noEmit— clean.cd frontend && npx vitest run— 143 passed (no regressions).🤖 Generated with Claude Code
https://claude.ai/code/session_016VmcsDG4b6EAFiujtcEU9D
Generated by Claude Code