diff --git a/docs/implementation-plan.md b/docs/implementation-plan.md index eb354d2..1b7a5d2 100644 --- a/docs/implementation-plan.md +++ b/docs/implementation-plan.md @@ -36,6 +36,12 @@ What exists right now is: - 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 +- Heimdall is deployed on Yggdrasil at `https://heimdall.gamecult.org` with + nginx/TLS, Postgres storage, stable file-backed signing, configured token + custody, and public health/discovery/JWKS checks passing +- Repixelizer is deployed on Yggdrasil at + `https://repixelizer.gamecult.org` with Heimdall mode enabled, Discord as the + only public provider, required access, and queue protection - explicit state/doctrine scaffolding so the plan does not immediately turn into soup @@ -86,6 +92,15 @@ Goals: callback, verifies the claim locally, adopts a local session, gates hosted access, and stamps queued jobs with `account_id` / `session_id` +Deployment/configuration status: + +- the deployment and service wiring are done +- public health/discovery/JWKS/config checks pass +- public Repixelizer auth-start returns a Discord authorization URL using the + Heimdall callback and Repixelizer backend handoff +- the Discord authorization URL requests only `identify guilds.members.read` +- the remaining launch check is the real interactive Discord browser flow + Current Discord policy: - Heimdall grants Repixelizer `entitlement.app_access` only when the Discord diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index bd174d7..0663361 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -62,7 +62,14 @@ Do not trust this file for the exact live HEAD. Always check git. 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 +- Heimdall is now deployed on Yggdrasil at `https://heimdall.gamecult.org` + behind nginx/TLS with Postgres storage, file-backed signing-key bootstrap, + configured token custody, and public health/discovery/JWKS checks passing +- Repixelizer is now deployed on Yggdrasil at + `https://repixelizer.gamecult.org` with Heimdall mode enabled, Discord as the + only public provider, required access enabled, and queue protection enabled +- Repixelizer public auth-start returns a Discord authorization URL using the + Heimdall callback and Repixelizer backend handoff - 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" @@ -85,19 +92,17 @@ 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 the live Discord-only -Repixelizer launch path: +If the user asks to continue, the current next move is real browser OAuth +verification: -- 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, - the Cultist role id in `GC_ACCESS_APP_REPIXELIZER_DISCORD_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 +- open `https://repixelizer.gamecult.org/app/` +- start Discord sign-in with an account that has the `KLTST` role +- confirm Heimdall receives the Discord callback and evaluates guild role + entitlement +- confirm Repixelizer receives the backend callback, verifies the Heimdall + token, adopts a local httpOnly session, and gates hosted/queue routes +- confirm the Discord authorization URL requests only `identify + guilds.members.read` Launch policy is role-gated on purpose: the GameCult Discord can remain public, but Repixelizer access is limited to members with the Cultist role. diff --git a/src/providers.ts b/src/providers.ts index e15bcb5..ff240ad 100644 --- a/src/providers.ts +++ b/src/providers.ts @@ -18,7 +18,7 @@ export const providerCatalog: Record = { key: "discord", displayName: "Discord", authorizationEndpoint: "https://discord.com/oauth2/authorize", - defaultScopes: ["identify", "email", "guilds.members.read"], + defaultScopes: ["identify", "guilds.members.read"], roles: ["identity", "entitlement"], clientIdEnv: "GC_ACCESS_PROVIDER_DISCORD_CLIENT_ID", clientSecretEnv: "GC_ACCESS_PROVIDER_DISCORD_CLIENT_SECRET", diff --git a/state/evidence.jsonl b/state/evidence.jsonl index 0a5c8fb..1637acf 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -11,3 +11,4 @@ {"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."} {"ts": "2026-04-29T10:53:44+00:00", "type": "design", "status": "accepted", "note": "Repixelizer Discord launch policy is intentionally role-gated: the GameCult guild can remain public, but Heimdall should grant Repixelizer app access only to members with the Cultist role configured in GC_ACCESS_APP_REPIXELIZER_DISCORD_ALLOWED_ROLE_IDS."} +{"ts": "2026-04-29T11:39:36+00:00", "type": "deployment", "status": "accepted", "note": "Heimdall is deployed on Yggdrasil behind nginx at https://heimdall.gamecult.org with Postgres storage, file-backed Ed25519 signing key bootstrap, configured token custody, and public JWKS/discovery/health checks passing. Repixelizer is deployed at https://repixelizer.gamecult.org in Heimdall mode with Discord as the only provider, required access enabled, and queue protection enabled; public auth-start returns a Discord authorization URL using the Heimdall callback and Repixelizer backend handoff."} diff --git a/state/map.yaml b/state/map.yaml index 6882791..247aed7 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -12,9 +12,9 @@ objective: Create a shared auth authority service that Repixelizer, StreamPixels, and future GameCult-hosted experiments can consume. current: > - 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. + Verify the first live Discord-backed Repixelizer auth flow in a real + browser session now that Heimdall and Repixelizer are deployed and wired on + Yggdrasil. constraints: - Heimdall owns auth/control-plane authority; app-domain data stays app-owned by default. @@ -56,7 +56,7 @@ 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. - - Launch the Discord-only Repixelizer auth path with real provider credentials, stable signing, persistent storage, and live callback verification. + - Verify the Discord-only Repixelizer auth path with real provider credentials, stable signing, persistent storage, and live browser callback verification. - Keep Patreon/GitHub/Twitch/YouTube as catalogued but not implemented until their runtime adapters land. accepted_design: @@ -72,6 +72,8 @@ accepted_design: - 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. + - Heimdall is deployed on Yggdrasil at `https://heimdall.gamecult.org` with nginx/TLS, Postgres storage, file-backed signing-key bootstrap, configured token custody, and passing public health/discovery/JWKS checks. + - Repixelizer is deployed on Yggdrasil at `https://repixelizer.gamecult.org` with `GC_ACCESS_MODE=heimdall`, `GC_ACCESS_ALLOWED_PROVIDERS=discord`, required access, and queue protection enabled. landed_baseline: - Shared architecture extracted into its own repo and renamed to Heimdall. @@ -93,5 +95,5 @@ rejected_paths: reason: reusing auth mechanics does not justify centralizing app-domain audience or product data current_status: - 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 id, the Cultist role id in `GC_ACCESS_APP_REPIXELIZER_DISCORD_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. The GameCult guild can remain public; Repixelizer access is intentionally limited to members with the Cultist role. + summary: Heimdall has a live first Yggdrasil deployment for the Discord-only Repixelizer path: TypeScript/Fastify runtime behind nginx/TLS at `https://heimdall.gamecult.org`, Ed25519 JWT signing with bootstrapped file-backed key support, JWKS/discovery exposure, signed OAuth start-state handling, Postgres-backed auth/control-plane persistence, a real Discord callback path, AES-256-GCM token sealing for managed provider credentials at rest, direct backend callback delivery for Repixelizer, and the older one-time completion-code exchange retained as fallback. Repixelizer is deployed at `https://repixelizer.gamecult.org` in Heimdall mode with Discord as the only public provider, required access enabled, and queue protection enabled. Public health/discovery/JWKS/config checks pass, and public auth-start returns a Discord authorization URL using the Heimdall callback plus Repixelizer backend handoff. 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: Run the real browser OAuth flow end to end from `https://repixelizer.gamecult.org/app/` with a Discord account that has the `KLTST` role, confirm Heimdall receives the Discord callback, confirms guild role entitlement, delivers the backend callback to Repixelizer, Repixelizer adopts a local httpOnly session, and hosted/queue routes are gated correctly. The public OAuth start URL should request only `identify guilds.members.read`. diff --git a/tests/app.test.ts b/tests/app.test.ts index b86d331..4466d59 100644 --- a/tests/app.test.ts +++ b/tests/app.test.ts @@ -47,7 +47,7 @@ function createMockDiscordRuntime(): OAuthProviderRuntime { accessToken: "discord-access-token", refreshToken: "discord-refresh-token", tokenType: "Bearer", - scope: ["identify", "email", "guilds.members.read"], + scope: ["identify", "guilds.members.read"], expiresAt: "2026-04-26T13:00:00.000Z", raw: { source: "test" }, };