Docs: Bifrost issues GameCult identity, Heimdall authenticates; oidc-issuer.md
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
b4dcd1de89
commit
e0640bdaa0
3 changed files with 93 additions and 4 deletions
|
|
@ -18,8 +18,10 @@ The product center is the Labor Platform loop: eligible participants express pol
|
|||
- source of deployment and infrastructure conventions
|
||||
- documents Yggdrasil, nginx, systemd, and local-Postgres patterns
|
||||
- `E:\Projects\Heimdall`
|
||||
- owns OAuth, linked accounts, token custody, signed claims, grants, consent, and revocation
|
||||
- Bifrost consumes Heimdall identity/capability facts instead of becoming the credential vault
|
||||
- authenticates: owns OAuth, linked accounts, token custody, signed claims, grants, consent, and revocation
|
||||
- does not issue GameCult identity: its account id (`HeimdallAccountId`) is an authenticator handle, never who a person is on GameCult
|
||||
- Bifrost consumes Heimdall authentication/capability facts instead of becoming the credential vault
|
||||
- remaining scar: Heimdall still mints its own account id in code (`src/app.ts` `createAccount`); chat does not need that to move, so it has not
|
||||
- `E:\Projects\VoidBot`
|
||||
- observes Discord, packages consensus, and runs repo Persona heartbeats
|
||||
- should call Bifrost for governed GitHub/Discord/Reddit/intake crossings instead of becoming hidden transport authority
|
||||
|
|
@ -55,7 +57,7 @@ After bootstrap, the next session should start implementing the first vertical s
|
|||
|
||||
- member alpha first
|
||||
- invite-only onboarding
|
||||
- GitHub as the only auth provider in v1
|
||||
- Bifrost is the GameCult identity issuer: `UserAccount.Id` is the subject, and Bifrost is the OIDC provider for chat (`docs/oidc-issuer.md`); GitHub, native, and Heimdall-linked sign-in only authenticate into it
|
||||
- GitHub issues and internal tasks both supported
|
||||
- app-native governance and voting
|
||||
- priority and reward pressure should be transparent and auditable
|
||||
|
|
|
|||
|
|
@ -103,6 +103,8 @@ This keeps Reddit useful without making subreddit mechanics the secret constitut
|
|||
- signed identity and capability claims
|
||||
- grants, consent, revocation, and capability evaluation
|
||||
|
||||
Heimdall authenticates; it does not issue GameCult identity. Bifrost owns the identity (`UserAccount.Id`, `NormalizedBifrostIdentity`) and issues it to chat as an OIDC provider (`docs/oidc-issuer.md`). `HeimdallAccountId` is an authenticator handle that links a Heimdall account to a Bifrost identity.
|
||||
|
||||
Bifrost consumes Heimdall claims. It does not become the key vault. The split is bridge versus gate: Heimdall decides who may cross under which grant; Bifrost moves authorized work across public protocols and records where it landed.
|
||||
|
||||
### Patron Collection Crossing
|
||||
|
|
@ -153,7 +155,7 @@ Repo Personas may generate bridge requests. They do not own the bridge itself.
|
|||
|
||||
| Concern | Owner | Reason |
|
||||
| --- | --- | --- |
|
||||
| Who is this actor? | Heimdall | Identity and account custody must stay with the auth authority. |
|
||||
| Who is this actor? | Bifrost (identity) with Heimdall (authentication) | Bifrost issues the identity; account custody and credential proof stay with the auth authority. |
|
||||
| Is this actor allowed to request this crossing? | Heimdall plus Bifrost | Heimdall issues grants; Bifrost applies bridge-domain policy. |
|
||||
| What work is being requested? | Bifrost | The request shape, priority, provenance, and lifecycle are governance concerns. |
|
||||
| How does interest become reward pressure? | Bifrost | Priority, bounty growth, vote weight, and reward allocation are Labor Platform concerns. |
|
||||
|
|
@ -205,6 +207,7 @@ This is not the final hosted control plane. It is the current bridge actuator an
|
|||
- Do not add Bifrost-scoped Discord transport or dispatch receipts to arbitrary repo Persona scripts. Call Bifrost.
|
||||
- Do not add Bifrost-scoped Reddit posting or Persona flair handling to arbitrary repo Persona scripts. Call Bifrost.
|
||||
- Do not put OAuth credentials or account custody into Bifrost helper scripts as the long-term plan. Use Heimdall claims.
|
||||
- Do not hand Heimdall ids, GitHub ids, or display names out as the GameCult subject. The OIDC `sub` is `UserAccount.Id`, and the localpart is `NormalizedBifrostIdentity`, minted once.
|
||||
- Do not make CultCache documents decide policy. They store packets; Bifrost gives those packets meaning.
|
||||
- Do not let a repo Persona turn a proposal into endless Aquarium pressure once it has enough shape. Route it to Bifrost as a reviewable artifact.
|
||||
- Do not expose bridge debug noise to public channels. Public receipts should say what happened, which Bifrost topic or work item owns it, where it landed, and what comes next.
|
||||
|
|
|
|||
84
docs/oidc-issuer.md
Normal file
84
docs/oidc-issuer.md
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
# Bifrost OIDC issuer
|
||||
|
||||
Bifrost is the GameCult identity issuer. It runs an OpenID Connect provider on its public host so that Matrix
|
||||
Authentication Service (MAS) can sign chat users in with their Bifrost account. Heimdall authenticates people into
|
||||
Bifrost; it does not issue the identity chat sees.
|
||||
|
||||
The provider is OpenIddict (`OpenIddict.AspNetCore` and `OpenIddict.EntityFrameworkCore`, 7.7.1). Bifrost owns two
|
||||
decisions at the authorize endpoint: who the chat user is, and whether they may get a session.
|
||||
|
||||
## Endpoints
|
||||
|
||||
The issuer is `Host:PublicBaseUrl`, normalized by .NET into an absolute URI. For a bare host that adds a trailing
|
||||
slash, so for `https://bifrost.gamecult.org` the issuer string is `https://bifrost.gamecult.org/`. Configure MAS with
|
||||
that exact string.
|
||||
|
||||
| Endpoint | Path |
|
||||
| --- | --- |
|
||||
| Discovery | `/.well-known/openid-configuration` |
|
||||
| JWKS | `/.well-known/jwks` |
|
||||
| Authorization | `/connect/authorize` |
|
||||
| Token | `/connect/token` |
|
||||
| Userinfo | `/connect/userinfo` |
|
||||
|
||||
Only the authorization code flow exists, and PKCE (S256) is required. There is no implicit, hybrid, password,
|
||||
client-credentials or device flow, no refresh token, and no dynamic client registration. The token endpoint takes
|
||||
`client_secret_basic` only. Scopes are `openid` and `profile`.
|
||||
|
||||
## The one client
|
||||
|
||||
Exactly one client is registered, from configuration, and seeded or updated at startup. Consent is implicit
|
||||
(first-party). Any other `client_id`, an unregistered `redirect_uri`, or a request without PKCE is refused by the
|
||||
provider before any sign-in redirect.
|
||||
|
||||
## Configuration
|
||||
|
||||
The section is `Bifrost:Oidc`. Absent, the provider surface is not mapped at all and discovery answers 404. Present,
|
||||
it must be complete: an unreadable or wrong-shaped file stops startup with a message that names the setting and never
|
||||
echoes its value.
|
||||
|
||||
| Setting | Meaning |
|
||||
| --- | --- |
|
||||
| `Bifrost:Oidc:SigningKeyPath` | PEM file with an EC P-256 private key. Tokens and id_tokens are signed ES256; JWKS publishes exactly that key. A non-P-256 or public-only key stops startup. |
|
||||
| `Bifrost:Oidc:EncryptionKeyPath` | File holding 32 random bytes as base64 (`openssl rand -base64 32`). Encrypts the access token and the parked authorize request. |
|
||||
| `Bifrost:Oidc:Clients:Mas:ClientId` | The client id MAS presents. |
|
||||
| `Bifrost:Oidc:Clients:Mas:ClientSecretPath` | File holding the client secret. |
|
||||
| `Bifrost:Oidc:Clients:Mas:RedirectUri` | MAS's callback URL, registered exactly. |
|
||||
|
||||
There are no development or ephemeral keys in the host. Key files are provisioned like the other
|
||||
`[workload.secret_files]` paths.
|
||||
|
||||
## Claims
|
||||
|
||||
| Claim | Value |
|
||||
| --- | --- |
|
||||
| `sub` | `UserAccount.Id`, formatted `D` |
|
||||
| `preferred_username` | `NormalizedBifrostIdentity` (minted once; there is no rename path) |
|
||||
| `name` | `DisplayName`, else `BifrostIdentity` |
|
||||
|
||||
No e-mail, no Heimdall id, no GitHub id, and no membership detail leaves Bifrost.
|
||||
|
||||
## Who may sign in
|
||||
|
||||
The authorize endpoint reads the cookie session and the same actor facts the `/App` policy reads.
|
||||
|
||||
- No session: challenge to `/auth/sign-in`, which resumes the original request after sign-in. OpenIddict parks the
|
||||
full request (its request token, in the OpenIddict token table, expiring after ten minutes, spent when used) and
|
||||
the return path carries only a short `request_uri`, so `LocalReturnPath.MaxLength` stays 256.
|
||||
- Signed in, membership not `Active`: redirect to the client's `redirect_uri` with `error=access_denied` and
|
||||
`error_description=The account is not an active GameCult member.`
|
||||
- Signed in, `Active`, but `NormalizedBifrostIdentity` empty: `error=access_denied` with
|
||||
`error_description=The account has no Bifrost identity.`
|
||||
- Signed in and `Active`: a code is issued.
|
||||
|
||||
## MAS configuration
|
||||
|
||||
Configure MAS's upstream provider with the issuer above, the client id and secret, the token endpoint auth method
|
||||
`client_secret_basic`, the id_token signing algorithm `ES256`, scope `openid profile`, and a localpart claim import
|
||||
from `preferred_username`. MAS mints the Matrix localpart on first sign-in, so the claim must never change for an
|
||||
account.
|
||||
|
||||
## Remaining scar
|
||||
|
||||
Heimdall still mints its own account id in code (`src/app.ts` `createAccount`). That id is an authenticator handle
|
||||
and chat does not need it to move.
|
||||
Loading…
Reference in a new issue