Files
buzz/docs/CORPORATE_IDENTITY.md
T

79 lines
3.7 KiB
Markdown

# Corporate identity
Corporate identity is an optional relay policy enabled with
`BUZZ_REQUIRE_CORPORATE_IDENTITY=true`. The relay verifies an asymmetric JWT
after the request proves control of a Nostr key, then admits the request only
when the existing community policy also succeeds.
## Required JWT policy
- `BUZZ_CORPORATE_IDENTITY_JWKS_URI` must be HTTPS and contain no credentials.
- JWTs must have a supported asymmetric algorithm, a `kid`, and valid `exp`,
`iss`, and `aud` claims. A present `nbf` claim is enforced.
- `BUZZ_CORPORATE_IDENTITY_NPUB_CLAIM`, when configured, is mandatory and must
equal the authenticated Nostr key. Leaving it unset enables first-use
uid-to-key enrollment in the private binding table.
- JWKS requests have connect and total timeouts, reject redirects, cap the
response at 1 MiB, cache keys for five minutes, and coalesce refreshes.
`BUZZ_REQUIRE_CORPORATE_IDENTITY` and
`BUZZ_ALLOW_CORPORATE_IDENTITY_DELEGATION` are strict booleans. Misspellings and
non-UTF-8 values stop configuration loading instead of silently disabling a
gate.
## Binding and revocation lifecycle
JWT validation is read-only. The relay creates or refreshes a binding only
after admission, allowlist, role, and community membership checks succeed.
Invite claims commit the binding, membership, policy evidence, and invite use
in one PostgreSQL transaction.
Revocation has three explicit meanings:
- `principal` disables every key for an issuer-qualified uid. Normal
authentication cannot re-enroll the principal with another key.
- `key` revokes one key but does not silently authorize a replacement.
- `rotation` is the audit state written by an explicit atomic old-key to
new-key rotation.
WebSocket and audio sessions revalidate the authoritative binding at least
every 30 seconds. Direct sessions also close at JWT expiry. Delegated sessions
check the owner's binding, so disabling an owner evicts the owner's agents as
well as the direct owner session.
Corporate NIP-OA delegation is transport-wide and therefore accepts only an
empty conditions string. Conditional tags must be evaluated for a specific
operation and are not treated as blanket corporate identity authority.
## Privacy and public assertions
`BUZZ_CORPORATE_IDENTITY_DISPLAY_CLAIM` is private. Its default (`email`) is
stored only in the community-scoped binding table and audit data; it is not
published to Nostr.
Public projection is separately opt-in with
`BUZZ_CORPORATE_IDENTITY_PUBLIC_DISPLAY_CLAIM`. When set, that claim is
published as a relay-signed NIP-85 label. Assertions carry both `active=true`
and an `expiration` no later than one hour or the JWT's expiry, whichever comes
first. Clients require the relay signature, active status, and a future
expiration. Removing the opt-in publishes an inactive replacement only when a
prior public assertion exists.
NIP-85 events are replaceable relay events and may also remain in downstream
caches or archives after replacement. Operators must choose a non-sensitive,
user-approved public label and account for that retention when configuring the
public claim.
## Route policy
Corporate identity applies to authenticated WebSocket and audio connections,
the NIP-98 event/query/count bridge, moderation reads, invite mint and claim,
Git smart HTTP, media uploads, and protected media reads.
Intentional exemptions are public media reads when media GET authentication is
disabled, health/readiness/metrics endpoints, NIP-11 and NIP-05 discovery,
operator and admin control planes with their own authentication, secret-backed
workflow hooks, public join-policy documents, invite policy-acceptance
callbacks, and static local web callbacks. These exemptions must remain in the
central route-policy test matrix when routes change.