From 7722007e02d80e0689ae5494f19251006ea5bf84 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 29 Apr 2026 14:36:03 +0100 Subject: [PATCH] Send Patreon tier access policy --- src/repixelizer/access.py | 9 ++++++++ tests/test_gui.py | 44 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/src/repixelizer/access.py b/src/repixelizer/access.py index 6cf4618..2614358 100644 --- a/src/repixelizer/access.py +++ b/src/repixelizer/access.py @@ -199,6 +199,7 @@ class AccessRuntimeConfig: jwks_cache_seconds: int = 300 discord_access_guild_id: str | None = None discord_access_role_ids: tuple[str, ...] = () + patreon_required_tier_title: str | None = None @property def enabled(self) -> bool: @@ -464,6 +465,7 @@ class AccessController: start_endpoint = None discord_access_guild_id = None discord_access_role_ids: tuple[str, ...] = () + patreon_required_tier_title = None login_url = _env_text("GC_ACCESS_LOGIN_URL") logout_url = _env_text("GC_ACCESS_LOGOUT_URL") verifier: HeimdallVerifier | None = None @@ -504,6 +506,7 @@ class AccessController: start_endpoint = _AUTH_START_PATH discord_access_guild_id = _env_text("REPIXELIZER_ACCESS_DISCORD_GUILD_ID") discord_access_role_ids = tuple(_parse_csv(_env_text("REPIXELIZER_ACCESS_DISCORD_ALLOWED_ROLE_IDS"))) + patreon_required_tier_title = _env_text("REPIXELIZER_ACCESS_PATREON_TIER_TITLE") or "Inner Sanctum" verifier = HeimdallVerifier( issuer=heimdall_issuer, app_slug=app_slug, @@ -534,6 +537,7 @@ class AccessController: jwks_cache_seconds=jwks_cache_seconds, discord_access_guild_id=discord_access_guild_id, discord_access_role_ids=discord_access_role_ids, + patreon_required_tier_title=patreon_required_tier_title, ) return cls(runtime, verifier=verifier) @@ -628,6 +632,11 @@ class AccessController: "guildId": self.runtime.discord_access_guild_id, "allowedRoleIds": list(self.runtime.discord_access_role_ids), } + elif provider_slug == "patreon": + request_payload["entitlementPolicy"] = { + "kind": "patreon_membership_access", + "requiredTierTitle": self.runtime.patreon_required_tier_title or "Inner Sanctum", + } start_url = urljoin(f"{heimdall_base_url}/", _AUTH_START_ENDPOINT_TEMPLATE.format(provider=provider_slug).lstrip("/")) try: response_payload = _post_json( diff --git a/tests/test_gui.py b/tests/test_gui.py index 1069d30..78adc70 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -899,6 +899,50 @@ def test_gui_heimdall_callback_flow_adopts_local_cookie_session(monkeypatch, tmp assert app_headers["cache-control"] == "no-store, no-cache, must-revalidate, max-age=0" +def test_gui_heimdall_patreon_auth_start_sends_membership_policy(monkeypatch, tmp_path: Path) -> None: + from repixelizer.gui import create_app + + start_calls: list[tuple[str, dict[str, object], float]] = [] + + def fake_post_json(url: str, payload: dict[str, object], *, timeout_seconds: float): + start_calls.append((url, payload, timeout_seconds)) + return { + "authorizationUrl": "https://www.patreon.com/oauth2/authorize?state=test-state", + "stateExpiresAt": "2026-04-27T12:15:00.000Z", + } + + monkeypatch.setenv("REPIXELIZER_HOSTED_DEMO", "1") + monkeypatch.setenv("REPIXELIZER_SPOOL_DIR", str(tmp_path / "spool")) + monkeypatch.setenv("GC_ACCESS_MODE", "heimdall") + monkeypatch.setenv("GC_ACCESS_HEIMDALL_BASE_URL", "https://heimdall.gamecult.org") + monkeypatch.setenv("GC_ACCESS_APP_PUBLIC_BASE_URL", "https://repixelizer.gamecult.org") + monkeypatch.setenv("GC_ACCESS_ALLOWED_PROVIDERS", "discord,patreon") + monkeypatch.setenv("REPIXELIZER_ACCESS_PATREON_TIER_TITLE", "Inner Sanctum") + monkeypatch.setattr("repixelizer.access._post_json", fake_post_json) + + app = create_app() + + start_status, _start_headers, start_body = asyncio.run( + _get_response( + app, + "/api/auth/heimdall/start", + method="POST", + headers={"content-type": "application/json"}, + body=_json_bytes({"provider": "patreon"}), + ) + ) + start_payload = _response_json(type("Response", (), {"body": start_body})()) + + assert start_status == 201 + assert start_payload["authorizationUrl"] == "https://www.patreon.com/oauth2/authorize?state=test-state" + assert start_calls + assert start_calls[0][0] == "https://heimdall.gamecult.org/v1/oauth/patreon/start" + assert start_calls[0][1]["entitlementPolicy"] == { + "kind": "patreon_membership_access", + "requiredTierTitle": "Inner Sanctum", + } + + def test_hosted_job_options_use_autocorr_and_allow_rerank() -> None: from repixelizer.gui import HostedDemoConfig, _normalize_job_options