diff --git a/.env.example b/.env.example index 5ccc57e..cf1aff3 100644 --- a/.env.example +++ b/.env.example @@ -2,8 +2,12 @@ HOST=127.0.0.1 PORT=4100 GC_ACCESS_BASE_URL=http://127.0.0.1:4100 GC_ACCESS_ISSUER=http://127.0.0.1:4100 +GC_ACCESS_STORAGE_BACKEND=memory +GC_ACCESS_DATABASE_URL= +GC_ACCESS_APPLY_SCHEMA_ON_STARTUP=1 GC_ACCESS_SESSION_TTL_SECONDS=3600 GC_ACCESS_STATE_TTL_SECONDS=600 +GC_ACCESS_COMPLETION_TTL_SECONDS=300 GC_ACCESS_SIGNING_PRIVATE_KEY_PEM= GC_ACCESS_SIGNING_PRIVATE_KEY_PATH= GC_ACCESS_SIGNING_PRIVATE_KEY_BOOTSTRAP=0 @@ -20,3 +24,6 @@ GC_ACCESS_PROVIDER_TWITCH_CLIENT_ID= GC_ACCESS_PROVIDER_TWITCH_CLIENT_SECRET= GC_ACCESS_PROVIDER_YOUTUBE_CLIENT_ID= GC_ACCESS_PROVIDER_YOUTUBE_CLIENT_SECRET= + +GC_ACCESS_APP_REPIXELIZER_DISCORD_GUILD_ID= +GC_ACCESS_APP_REPIXELIZER_DISCORD_ALLOWED_ROLE_IDS= diff --git a/docs/implementation-plan.md b/docs/implementation-plan.md index 44b1080..892e962 100644 --- a/docs/implementation-plan.md +++ b/docs/implementation-plan.md @@ -34,6 +34,8 @@ What exists right now is: - file-backed signing-key loading/bootstrap for stable service identity - AES-256-GCM token sealing for managed provider credentials at rest - a local verifier helper that app backends can use against Heimdall JWKS +- Repixelizer has consumed the direct backend callback flow in its hosted web + layer and verifies Heimdall Ed25519 access tokens locally - explicit state/doctrine scaffolding so the plan does not immediately turn into soup @@ -57,7 +59,7 @@ Goals: - keep app-domain data in app-owned stores - keep app profiles thin and explicit -### 2. Define the contract before the service grows teeth +### 2. Keep the contract tight while it goes live Goals: @@ -65,15 +67,31 @@ Goals: - define OAuth start/callback/link surfaces - define grant and entitlement persistence boundaries - define how app backends verify claims locally - This seam now exists in reference form and needs its first real consumer. + This now exists in reference form and Repixelizer has a Python consumer in + its hosted web layer. -### 3. Make Repixelizer the first auth-blank consumer +### 3. Launch Repixelizer with Discord first Goals: -- gate hosted access and queue submission through Heimdall-issued claims -- attach queue ownership to `account_id` and `session_id` -- preserve the existing hosted GUI and queue runtime +- deploy Heimdall on Yggdrasil with stable signing, encrypted token custody, + and persistent Postgres storage +- configure the Discord OAuth app redirect URI to + `https://heimdall.gamecult.org/v1/oauth/discord/callback` +- configure Heimdall's Discord client credentials, Repixelizer guild id, and + allowed role ids +- configure Repixelizer for `GC_ACCESS_MODE=heimdall` and + `GC_ACCESS_ALLOWED_PROVIDERS=discord` +- run the live browser OAuth flow and confirm Repixelizer receives the backend + callback, verifies the claim locally, adopts a local session, gates hosted + access, and stamps queued jobs with `account_id` / `session_id` + +Current Discord policy: + +- Heimdall grants Repixelizer `entitlement.app_access` only when the Discord + member has one of the configured allowed role ids +- if launch policy should be "any member of the GameCult Discord," change the + Discord entitlement rule before deploying ### 4. Migrate StreamPixels without flattening its good local seams @@ -125,11 +143,19 @@ Next in this phase: ### Phase 3: Repixelizer binding -- integrate Heimdall into the hosted demo -- consume the direct backend callback flow from the Repixelizer frontend/backend -- gate `POST /api/jobs` -- gate per-job read/cancel/event routes by local session ownership -- consume the landed verifier seam so Repixelizer can trust Heimdall JWTs locally +Landed in Repixelizer: + +- Heimdall hosted auth mode +- direct backend callback receipt +- local Ed25519 JWT verification against Heimdall JWKS +- httpOnly local session adoption +- hosted landing-page provider buttons +- hosted route and queue/job ownership checks + +Still to do for this phase: + +- deploy and configure both services +- verify the real Discord OAuth path end to end ### Phase 4: StreamPixels authority migration diff --git a/docs/service-contract.md b/docs/service-contract.md index 939f184..5ecf533 100644 --- a/docs/service-contract.md +++ b/docs/service-contract.md @@ -158,6 +158,14 @@ Failure mode right now: - `503 provider_not_configured` when the provider client id is missing +Launch note: + +- Discord is the only implemented provider runtime today +- Patreon, GitHub, Twitch, and YouTube are catalogued for start URLs, but their + callback exchange/identity/entitlement runtimes are not implemented yet +- Repixelizer launch should therefore configure only Discord on its provider + buttons until additional adapters land + ### `GET /v1/oauth/{provider}/callback` Purpose: @@ -458,12 +466,15 @@ The following are not landed yet: - refresh/revocation flows - admin/grant management surfaces +- provider callback runtimes beyond Discord ## Next implementation move -Consume the hardened slice in the first real app binding: +Deploy and verify the hardened slice in the first real app binding: -- the first Repixelizer consumer binding that trusts Heimdall claims locally -- the first Repixelizer backend callback endpoint and attempt-status bridge -- any missing app-local session bridge or verifier middleware sugar that makes - that integration less feral +- run Discord-only Repixelizer auth with real provider credentials +- confirm direct backend callback delivery to Repixelizer +- confirm Repixelizer verifies the Heimdall access token locally and gates + hosted routes from the adopted session +- decide whether Repixelizer launch policy is role-gated Discord access or any + guild member access before deployment diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index 48889bf..d574f3a 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -37,7 +37,6 @@ Do not trust this file for the exact live HEAD. Always check git. - app-domain data stays app-owned by default - host apps verify signed claims locally for routine auth instead of calling Heimdall on every request -- the landed skeleton already exposes: - the landed service now exposes: - `/.well-known/jwks.json` - `/.well-known/heimdall-configuration` @@ -59,7 +58,14 @@ Do not trust this file for the exact live HEAD. Always check git. being dumped into "encrypted" columns raw like an insult - `src/verifier.ts` now provides a small local JWT verifier seam for app backends -- Repixelizer is the first auth-blank binding target +- Repixelizer has consumed the backend-callback flow in its hosted web layer: + it creates local auth attempts, asks Heimdall to start OAuth, receives the + direct backend callback, verifies Heimdall Ed25519 access tokens locally, and + adopts httpOnly local sessions +- Repixelizer is no longer blocking Heimdall on auth refactoring +- the first live launch should be Discord-only +- Patreon, GitHub, Twitch, and YouTube are catalogued providers, but their + callback runtime adapters still return "not implemented" - StreamPixels is the migration target with useful existing auth seams that should not be flattened into mush - the intended first deployment shape is a Heimdall service on Yggdrasil behind @@ -79,17 +85,22 @@ Do not trust this file for the exact live HEAD. Always check git. Do not continue implementation automatically from a rehydrate-only request. -If the user asks to continue, the current next move is to harden and consume -the landed slice: +If the user asks to continue, the current next move is the live Discord-only +Repixelizer launch path: -- the first Repixelizer consumer integration that opens auth in a new browser - context, receives direct backend callback delivery from Heimdall, and trusts - Heimdall claims locally -- any small app-facing middleware sugar needed once that integration starts - colliding with real routes +- configure the Discord OAuth app redirect URI as + `https://heimdall.gamecult.org/v1/oauth/discord/callback` +- configure Heimdall with Discord client id/secret, Repixelizer guild id, + allowed role ids, Postgres storage, token encryption, and stable signing keys +- configure Repixelizer to use `GC_ACCESS_MODE=heimdall` and + `GC_ACCESS_ALLOWED_PROVIDERS=discord` +- run the real browser OAuth flow end to end and confirm Repixelizer receives + the backend callback, verifies the token, adopts a local session, and gates + hosted routes -without dragging app-domain data into the shared layer and without forcing -per-request auth round-trips. +Important wrinkle: Heimdall currently grants Repixelizer access from Discord +role matches, not bare guild membership. If launch policy is "any GameCult +Discord member can enter," update the Discord entitlement rule before deploy. ## Immediate Re-entry Instruction diff --git a/state/evidence.jsonl b/state/evidence.jsonl index d5b8bf7..5bdc1b5 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -9,3 +9,4 @@ {"ts": "2026-04-26T14:22:37+00:00", "type": "implementation", "status": "accepted", "note": "Browser-style Heimdall auth handoff now uses a short-lived one-time completion code: the callback page posts the result to the opener, tries to close itself, and the app backend redeems the code with Heimdall instead of receiving the final access token in a callback URL fragment."} {"ts": "2026-04-26T15:22:58+00:00", "type": "implementation", "status": "accepted", "note": "Heimdall now supports file-backed signing-key loading/bootstrap, AES-256-GCM token sealing for managed provider credentials at rest, and a local JWT verifier seam in src/verifier.ts; the next move shifts from custody hardening to the first real Repixelizer consumer integration."} {"ts": "2026-04-26T16:00:35+00:00", "type": "design", "status": "accepted", "note": "Heimdall now supports direct backend callback delivery as the preferred same-host or Yggdrasil-reachable app handoff path; the browser completion-code redeem flow remains as fallback, not the primary consumer contract."} +{"ts": "2026-04-29T10:51:08+00:00", "type": "design", "status": "accepted", "note": "Repixelizer has consumed Heimdall's direct backend callback flow locally, so Heimdall is no longer blocked on Repixelizer auth refactoring. The next Heimdall objective is Discord-only live launch configuration and end-to-end OAuth verification on Yggdrasil; other providers remain catalogued but callback runtimes are not implemented yet."} diff --git a/state/map.yaml b/state/map.yaml index 07c5656..c39eee3 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -1,7 +1,7 @@ project: name: Heimdall status: active - phase: first_provider_persistence + phase: discord_launch premise: > Build Heimdall as one reusable GameCult auth authority service for OAuth, linked identities, local sessions, grants, and capability claims without @@ -12,9 +12,9 @@ objective: Create a shared auth authority service that Repixelizer, StreamPixels, and future GameCult-hosted experiments can consume. current: > - Use the new stateful provider slice to land the first consumer verifier - seam and harden key/token custody before app integrations start breeding - fresh sludge. + Ship the first live Discord-backed Repixelizer auth flow by deploying and + configuring Heimdall on Yggdrasil, then verifying the real end-to-end OAuth + path with Repixelizer's landed backend callback consumer. constraints: - Heimdall owns auth/control-plane authority; app-domain data stays app-owned by default. @@ -56,7 +56,8 @@ active_subgoals: - Keep the shared architecture explicit and separate from app profiles. - Preserve the default boundary that app-domain data remains app-owned. - Align the plan to Heimdall as a Yggdrasil-hosted authority service. - - Define the claim-verification contract that Repixelizer and StreamPixels will consume locally. + - Launch the Discord-only Repixelizer auth path with real provider credentials, stable signing, persistent storage, and live callback verification. + - Keep Patreon/GitHub/Twitch/YouTube as catalogued but not implemented until their runtime adapters land. accepted_design: - `docs/architecture.md` is the canonical shared architecture note. @@ -70,6 +71,7 @@ accepted_design: - StreamPixels is the migration case that proves shared authority must not imply audience-data centralization. - The first service skeleton is a standalone TypeScript/Fastify app with Ed25519 JWT signing, JWKS discovery, signed OAuth `state`, and app-profile-driven shared capability evaluation. - The first stateful slice adds durable auth/control-plane storage plus a real Discord callback path for Repixelizer. + - Repixelizer has consumed the Heimdall backend-callback flow in its hosted web layer; Heimdall no longer needs to wait on Repixelizer refactoring before live Discord launch work. landed_baseline: - Shared architecture extracted into its own repo and renamed to Heimdall. @@ -80,6 +82,7 @@ landed_baseline: - The first real provider slice landed for Discord callback exchange, identity resolution, entitlement evaluation, and session/claim issuance for Repixelizer. - Signing-key persistence/bootstrap, managed token encryption at rest, and a local verifier seam landed for the first real consumer integration pass. - Direct backend callback handoff landed as the preferred same-host app-consumer path, with browser completion-code exchange retained as fallback. + - Repixelizer's hosted web layer now creates Heimdall auth attempts, receives direct backend callbacks, verifies Heimdall Ed25519 access tokens locally, and adopts httpOnly local sessions. rejected_paths: - name: per_app_auth_copy_paste @@ -90,5 +93,5 @@ rejected_paths: reason: reusing auth mechanics does not justify centralizing app-domain audience or product data current_status: - summary: Heimdall now has a hardened first provider slice plus a direct app-backend handoff path: TypeScript/Fastify runtime, Ed25519 JWT signing with file-backed key support, JWKS/discovery exposure, signed OAuth start-state handling, durable auth/control-plane persistence, a real Discord callback path, AES-256-GCM token sealing for managed provider credentials at rest, a local JWT verifier seam for app backends, direct backend callback delivery for same-host consumers, and the older one-time completion-code exchange retained as fallback. The key boundary still holds: Heimdall owns auth/control-plane authority and credential custody; app-domain bindings and runtime behavior remain app-owned. - next_action: Consume the hardened slice in the first real Repixelizer integration: open auth in a new browser context, let Heimdall deliver the result directly to a Repixelizer backend callback endpoint keyed by a local auth attempt, trust Heimdall claims locally through the verifier seam, and add any tiny app-local session bridge or attempt-status surface needed without dragging app-domain data into Heimdall. + summary: Heimdall has a hardened first provider slice plus a direct app-backend handoff path: TypeScript/Fastify runtime, Ed25519 JWT signing with file-backed key support, JWKS/discovery exposure, signed OAuth start-state handling, durable auth/control-plane persistence, a real Discord callback path, AES-256-GCM token sealing for managed provider credentials at rest, a local JWT verifier seam for app backends, direct backend callback delivery for same-host consumers, and the older one-time completion-code exchange retained as fallback. Repixelizer has now consumed that flow locally in its hosted web layer, so Heimdall is no longer blocked on Repixelizer refactoring. The live launch path should start Discord-only; Patreon, GitHub, Twitch, and YouTube are catalogued providers but their callback runtimes are still not implemented. The key boundary still holds: Heimdall owns auth/control-plane authority and credential custody; app-domain bindings and runtime behavior remain app-owned. + next_action: Prepare and verify the live Discord-only Repixelizer launch on Yggdrasil: configure the Discord OAuth app redirect to `https://heimdall.gamecult.org/v1/oauth/discord/callback`, set Heimdall env for Discord client credentials, Repixelizer guild/allowed role ids, Postgres storage, token encryption, and stable signing keys, set Repixelizer to `GC_ACCESS_ALLOWED_PROVIDERS=discord`, then run the real browser OAuth flow end to end. If access should allow any guild member rather than specific role ids, change Heimdall's Discord entitlement rule before deployment.