Clarify Heimdall provider roles
This commit is contained in:
parent
2648bfab6a
commit
e4eab9ed10
2 changed files with 80 additions and 21 deletions
|
|
@ -7,8 +7,9 @@ authority service for GameCult-hosted experiments.
|
|||
|
||||
Its basic promise is:
|
||||
|
||||
- let people sign in with Discord and/or Patreon
|
||||
- decide access from GameCult/community entitlements and local grants
|
||||
- let people sign in through app-relevant providers such as Discord, Patreon,
|
||||
GitHub, Twitch, or YouTube
|
||||
- decide access from provider entitlements, local grants, and per-app policy
|
||||
- issue signed local claims that hosted apps can verify without trusting raw
|
||||
provider tokens
|
||||
|
||||
|
|
@ -23,7 +24,7 @@ If every experiment rebuilds:
|
|||
- entitlement refresh
|
||||
- session cookies
|
||||
- grant handling
|
||||
- "Discord role OR Patreon tier" logic
|
||||
- provider-specific gate logic
|
||||
|
||||
then we get five near-identical auth pits and deserve what happens next.
|
||||
|
||||
|
|
@ -56,14 +57,14 @@ bind its own policy and runtime seams onto it.
|
|||
- `LinkedIdentity`
|
||||
one external provider identity attached to exactly one local account
|
||||
- `EntitlementSnapshot`
|
||||
cached result from a provider or grant source saying whether some claim is
|
||||
cached result from an entitlement source saying whether some claim is
|
||||
currently true
|
||||
- `CapabilityGrant`
|
||||
a reusable local grant such as `global_member`, `app_access`, or
|
||||
`admin_access`, scoped either globally or to one app
|
||||
- `AppAccessProfile`
|
||||
the per-app binding that names capabilities, allowed provider sources, and
|
||||
policy rules
|
||||
the per-app binding that names capabilities, relevant identity providers,
|
||||
entitlement sources, and policy rules
|
||||
- `Session`
|
||||
signed local app/browser session; the browser gets this, not provider tokens
|
||||
- `AccessClaim`
|
||||
|
|
@ -71,14 +72,39 @@ bind its own policy and runtime seams onto it.
|
|||
- `AuditEvent`
|
||||
durable record for login, link, unlink, refresh, denial, and admin overrides
|
||||
|
||||
## Provider taxonomy
|
||||
|
||||
Heimdall should classify outside systems by what role they play, not by whether
|
||||
their brand name appears somewhere in an app.
|
||||
|
||||
- `Identity provider`
|
||||
proves who the user is and can supply one `LinkedIdentity`
|
||||
- `Entitlement source`
|
||||
answers whether some access-relevant claim is currently true
|
||||
- `Local grant source`
|
||||
records app or operator-issued overrides inside Heimdall
|
||||
- `App-owned connector OAuth`
|
||||
authenticates an app's operational integration with a third-party platform
|
||||
and does **not** become Heimdall territory just because it uses OAuth
|
||||
|
||||
One provider can play different roles in different apps:
|
||||
|
||||
- Repixelizer can treat Discord and Patreon as both identity and entitlement
|
||||
sources
|
||||
- Bifrost can treat GitHub as identity proof while keeping membership approval,
|
||||
roles, and governance state app-local
|
||||
- StreamPixels can reuse shared viewer identity patterns while keeping
|
||||
creator-side Twitch/YouTube connector credentials and runtime plumbing
|
||||
StreamPixels-owned
|
||||
|
||||
## System shape
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["Browser / app web UI"] --> B["Hosted experiment web surface"]
|
||||
B -->|start auth / link| C["Heimdall"]
|
||||
C --> D["Discord OAuth + guild role checks"]
|
||||
C --> E["Patreon OAuth + campaign/tier checks"]
|
||||
C --> D["Shared identity providers"]
|
||||
C --> E["Shared entitlement sources"]
|
||||
C --> F["Local auth/control-plane store"]
|
||||
C --> G["Signed sessions / claims"]
|
||||
B -->|verify claims locally| H["Hosted experiment runtime"]
|
||||
|
|
@ -90,6 +116,8 @@ The important split:
|
|||
sessions, claims, grants, and audit surfaces
|
||||
- the host experiment owns its workload, queue, editor, render path, viewer
|
||||
profile model, creator model, or other product-specific machinery
|
||||
- app-owned operational connector OAuth stays with the app unless there is a
|
||||
very deliberate reason to centralize it
|
||||
|
||||
Do not smear provider logic directly through every host runtime.
|
||||
|
||||
|
|
@ -190,25 +218,42 @@ Important invariants:
|
|||
- one provider identity belongs to one local account only
|
||||
- provider tokens stay server-side only
|
||||
- session cookies represent local sessions, not provider trust directly
|
||||
- app-specific capability checks read signed local claims, not Discord or
|
||||
Patreon on every route
|
||||
- app-specific capability checks read signed local claims, not upstream
|
||||
providers on every route
|
||||
|
||||
## Provider adapters
|
||||
|
||||
Shared provider/gate adapters should be boring and reusable:
|
||||
Shared adapters should be boring and reusable:
|
||||
|
||||
- `DiscordRoleEntitlementProvider`
|
||||
- `DiscordIdentityProvider`
|
||||
- login via Discord OAuth
|
||||
- `PatreonIdentityProvider`
|
||||
- login via Patreon OAuth
|
||||
- `GitHubIdentityProvider`
|
||||
- login via GitHub OAuth for apps that use GitHub identity as the local
|
||||
person-key
|
||||
- `TwitchIdentityProvider`
|
||||
- optional shared login identity when an app wants Twitch-backed sign-in
|
||||
- `YouTubeIdentityProvider`
|
||||
- optional shared login identity when an app wants YouTube-backed sign-in
|
||||
- `DiscordRoleEntitlementProvider`
|
||||
- entitlement check via guild membership and role ids, ideally through the
|
||||
GameCult bot token server-side
|
||||
- `PatreonMembershipEntitlementProvider`
|
||||
- login via Patreon OAuth
|
||||
- entitlement check via campaign membership and tier ids
|
||||
- `ManualGrantProvider`
|
||||
- local override for staff, migrations, experiments, or emergency access
|
||||
- `InviteGrantProvider`
|
||||
- reusable invite-issued app or role grants where that pattern makes sense
|
||||
|
||||
Important rule:
|
||||
|
||||
- a provider being used anywhere in an app does not automatically make all uses
|
||||
of that provider Heimdall-owned
|
||||
- creator/operational connector OAuth such as StreamPixels broadcaster
|
||||
connectors stays app-owned unless it is deliberately promoted into the shared
|
||||
auth authority
|
||||
|
||||
## Policy model
|
||||
|
||||
The shared authority should not hard-code one app's access rules. It should
|
||||
|
|
@ -235,6 +280,10 @@ queue_submit = app_access
|
|||
admin_access = grant.operator || grant.admin_access
|
||||
```
|
||||
|
||||
The important part is not the provider names in the example. It is that app
|
||||
profiles choose which identity facts, entitlement snapshots, and local grants
|
||||
feed each capability.
|
||||
|
||||
## Request path discipline
|
||||
|
||||
This is where shared authority stays useful instead of becoming a nuisance.
|
||||
|
|
@ -350,18 +399,27 @@ Suggested generic env surface:
|
|||
- `GC_ACCESS_BASE_URL=https://heimdall.gamecult.org`
|
||||
- `GC_ACCESS_INTERNAL_URL=http://127.0.0.1:4100`
|
||||
- `GC_ACCESS_SESSION_SECRET=...`
|
||||
- `GC_ACCESS_DISCORD_CLIENT_ID=...`
|
||||
- `GC_ACCESS_DISCORD_CLIENT_SECRET=...`
|
||||
- `GC_ACCESS_DISCORD_BOT_TOKEN=...`
|
||||
- `GC_ACCESS_DISCORD_GUILD_ID=...`
|
||||
- `GC_ACCESS_PATREON_CLIENT_ID=...`
|
||||
- `GC_ACCESS_PATREON_CLIENT_SECRET=...`
|
||||
- `GC_ACCESS_PATREON_CAMPAIGN_ID=...`
|
||||
- `GC_ACCESS_PROVIDER_DISCORD_CLIENT_ID=...`
|
||||
- `GC_ACCESS_PROVIDER_DISCORD_CLIENT_SECRET=...`
|
||||
- `GC_ACCESS_PROVIDER_DISCORD_BOT_TOKEN=...`
|
||||
- `GC_ACCESS_PROVIDER_DISCORD_GUILD_ID=...`
|
||||
- `GC_ACCESS_PROVIDER_PATREON_CLIENT_ID=...`
|
||||
- `GC_ACCESS_PROVIDER_PATREON_CLIENT_SECRET=...`
|
||||
- `GC_ACCESS_PROVIDER_PATREON_CAMPAIGN_ID=...`
|
||||
- `GC_ACCESS_PROVIDER_GITHUB_CLIENT_ID=...`
|
||||
- `GC_ACCESS_PROVIDER_GITHUB_CLIENT_SECRET=...`
|
||||
- `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_ENTITLEMENT_CACHE_TTL_SECONDS=900`
|
||||
- `GC_ACCESS_PROVIDER_FAILURE_GRACE_SECONDS=3600`
|
||||
|
||||
Only configure the providers Heimdall actually owns for a given deployment.
|
||||
|
||||
Per-app binding should live in code or profile config, not in cloned provider
|
||||
env namespaces.
|
||||
env namespaces. App-owned connector OAuth should stay under the app's own env
|
||||
surface even when the upstream provider name overlaps.
|
||||
|
||||
## Audit Result
|
||||
|
||||
|
|
|
|||
|
|
@ -2,3 +2,4 @@
|
|||
{"ts":"2026-04-26T00:00:00Z","type":"design","status":"superseded","note":"The initial first implementation hypothesis was an embedded shared gamecult_access package. This was later superseded by the Heimdall service-first shape after the cross-runtime split and Yggdrasil topology review."}
|
||||
{"ts":"2026-04-26T00:00:00Z","type":"boundary","status":"accepted","note":"StreamPixels can reuse the same auth/control-plane backbone, but streamer-audience data, creator memberships, connector credentials, and runtime state should remain StreamPixels-owned."}
|
||||
{"ts":"2026-04-26T06:00:00Z","type":"design","status":"accepted","note":"The cross-runtime split and current Yggdrasil topology changed the first implementation shape from an embedded shared package to Heimdall: a shared auth authority service whose signed claims are verified locally by each app."}
|
||||
{"ts": "2026-04-26T11:00:02+00:00", "type": "design", "status": "accepted", "note": "Heimdall's provider model splits shared identity providers and entitlement sources from app-owned connector OAuth. Different provider mixes change adapter inventory and claim vocabulary, not the core service boundary."}
|
||||
|
|
|
|||
Loading…
Reference in a new issue