Skip to content

Add Gecko Suite SSO cookie, session endpoint, and CORS/CSP headroom for GAM - #191

Merged
davior merged 1 commit into
mainfrom
claude/beautiful-brown-cd118h
Sep 13, 2026
Merged

davior merged 1 commit into
mainfrom
claude/beautiful-brown-cd118h

Conversation

@davior

@davior davior commented Sep 13, 2026

Copy link
Copy Markdown
Owner

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 against main @ 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:

  1. backend/app/video/compose.py's FONT_DIR points at a path that doesn't exist in the image (renders silently fall back to DejaVuSans).
  2. /media/* is fully unauthenticated (on the public-path allowlist).
  3. AppConfig/configApi is declared twice (api/notes.ts vs api/config.ts) with different shapes.

GN-1 — Parent-domain session cookie

  • Login (POST /login, POST /login/2fa) now also sets an HttpOnly gecko_session cookie alongside the existing JSON token, so a sibling app on a different geckopico.com subdomain can share the session. Configured via AUTH_COOKIE_DOMAIN / AUTH_COOKIE_SECURE.
  • New POST /api/auth/logout clears the cookie — added to PUBLIC_PATHS so it still works with an expired token — and is now called from the frontend's logout() (best-effort, failure ignored).
  • jwt_auth_middleware accepts the cookie as a fallback behind the Authorization: Bearer header; the header always wins when both are present.
  • Since a cookie is an ambient credential the browser attaches on its own, a cookie-authenticated write now requires a known Origin/Referer (checked against the existing CORS_ORIGIN allowlist) 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.
  • New 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/session

Exchanges 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_PATHS on purpose — the middleware must authenticate it.

LoginView now honors a ?redirect= query param (in addition to the existing in-app location.state.from), validated against a *.geckopico.com allowlist 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_ORIGIN already accepted a comma-separated list — no code change needed, just a doc tweak in .env.example; the production value is a deployment-time .env change (CORS_ORIGIN=https://notes.geckopico.com,https://gam.geckopico.com).
  • Widened frontend/nginx.conf's CSP (connect-src, img-src, media-src) to allow https://gam.geckopico.com.

Testing

  • cd backend && python -m pytest tests/ — 1062 passed, 4 skipped (1052 passed before this change; the 10 new ones are test_auth_cookie.py).
  • cd frontend && npx tsc --noEmit — clean.
  • cd frontend && npx vitest run — 143 passed (no regressions).
  • Manually verified the JSON/header auth path is unchanged — Gecko Notes' own frontend never sends the cookie itself, only receives and clears it, per the spec's "ship GN-1 and verify Notes itself still works" note.

🤖 Generated with Claude Code

https://claude.ai/code/session_016VmcsDG4b6EAFiujtcEU9D


Generated by Claude Code

…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
davior marked this pull request as ready for review September 13, 2026 11:43
@davior
davior merged commit 95ed2ca into main Sep 13, 2026
2 checks passed
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.

2 participants