OAuth SSO infrastructure + account app support

- OAuthCode model + migration for authorization code flow
- OAuth client blueprint (auto-registered for non-federation apps)
- Per-app first-party session cookies (fixes Safari ITP)
- /oauth/authorize endpoint support in URL helpers
- account_url() helper + Jinja global
- Templates: federation_url('/auth/...') → account_url('/...')
- Widget registry: account page links use account_url

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
giles
2026-02-23 09:55:27 +00:00
parent 326b380135
commit 46f44f6171
14 changed files with 236 additions and 27 deletions

View File

@@ -11,7 +11,7 @@ from shared.config import init_config, config, pretty
from shared.models import KV # ensure shared models imported
# Register all app model classes with SQLAlchemy so cross-domain
# relationship() string references resolve correctly.
for _mod in ("blog.models", "market.models", "cart.models", "events.models", "federation.models"):
for _mod in ("blog.models", "market.models", "cart.models", "events.models", "federation.models", "account.models"):
try:
__import__(_mod)
except ImportError:
@@ -80,11 +80,10 @@ def create_base_app(
app.secret_key = os.getenv("SECRET_KEY", "dev-secret-key-change-me-777")
# Session cookie shared across subdomains
cookie_domain = os.getenv("SESSION_COOKIE_DOMAIN") # e.g. ".rose-ash.com"
if cookie_domain:
app.config["SESSION_COOKIE_DOMAIN"] = cookie_domain
app.config["SESSION_COOKIE_NAME"] = "blog_session"
# Per-app first-party session cookie (no shared domain — avoids Safari ITP)
app.config["SESSION_COOKIE_NAME"] = f"{name}_session"
app.config["SESSION_COOKIE_SAMESITE"] = "Lax"
app.config["SESSION_COOKIE_SECURE"] = True
# Ghost / Redis config
app.config["GHOST_API_URL"] = os.getenv("GHOST_API_URL")
@@ -102,6 +101,11 @@ def create_base_app(
setup_jinja(app)
errors(app)
# Auto-register OAuth client blueprint for non-federation apps
if name != "federation":
from shared.infrastructure.oauth import create_oauth_blueprint
app.register_blueprint(create_oauth_blueprint(name))
# --- before-request hooks ---
@app.before_request
async def _route_log():