@powerhousedao/switchboard 6.2.2-dev.9 → 6.2.2-staging.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Auth.md CHANGED
@@ -112,7 +112,7 @@ interface VerifiableCredential {
112
112
  1. **Token Decoding**: Extract credential information from JWT
113
113
  2. **Credential Validation**: Verify against W3C standards
114
114
  3. **Issuer Verification**: Check credential issuer authenticity
115
- 4. **Renown API Check**: Validate credential still exists and is valid
115
+ 4. **Credential Existence Check**: Validate credential still exists and is valid — against a remote Renown/Switchboard or this switchboard's own read model (see [Which Renown Instance Is Used](#which-renown-instance-is-used))
116
116
  5. **User Extraction**: Create user object from verified credentials
117
117
 
118
118
  ### 5. **Authorization**
@@ -173,6 +173,54 @@ export AUTH_ENABLED=true
173
173
  export ADMINS="0x111,0x222,0x333"
174
174
  ```
175
175
 
176
+ `AUTH_ENABLED` does two things: it selects the authorization policy
177
+ (`ADMIN_ONLY` instead of `OPEN`) **and** it makes the middleware verify the
178
+ bearer so `ctx.user` is populated. `RESOLVE_CALLER_IDENTITY` separates the
179
+ second from the first:
180
+
181
+ ```bash
182
+ # Read the bearer and populate ctx.user, whatever the policy is
183
+ export RESOLVE_CALLER_IDENTITY=true
184
+ ```
185
+
186
+ | | `AUTH_ENABLED` unset | `AUTH_ENABLED=true` |
187
+ | -------------------------------- | -------------------------- | ------------------------------- |
188
+ | `RESOLVE_CALLER_IDENTITY` unset | no user, `OPEN` | user resolved, `ADMIN_ONLY` |
189
+ | `RESOLVE_CALLER_IDENTITY=true` | **user resolved, `OPEN`** | user resolved, `ADMIN_ONLY` |
190
+
191
+ It defaults to whatever `AUTH_ENABLED` is, so a deployment that never sets it
192
+ behaves exactly as before. The bold cell is the combination `AUTH_ENABLED`
193
+ alone cannot express, and the one a custom subgraph needs when it does its own
194
+ authorization: its resolvers learn who is calling without every non-admin
195
+ being locked out of switchboard.
196
+
197
+ It **resolves** an identity and enforces nothing — a request with no token is
198
+ still admitted, with no user. Verification is otherwise identical to
199
+ `AUTH_ENABLED=true`, Renown credential check included, so an invalid token is
200
+ still a 401.
201
+
202
+ That last property is the hole `REQUIRE_AUTHENTICATED_CALLER` closes: it
203
+ turns the same resolved identity into enforcement.
204
+
205
+ ```bash
206
+ # Admit authenticated callers, reject anonymous ones with a 401
207
+ export REQUIRE_AUTHENTICATED_CALLER=true
208
+ ```
209
+
210
+ It defaults to off, so nothing changes for existing deployments. When on,
211
+ every GraphQL request without a resolved caller — subgraphs, the supergraph,
212
+ and the SSE subscription endpoint alike — is answered with a `401`
213
+ (`{"error": "Authentication required"}`) before any resolver runs. It is the
214
+ one switch that expresses "authenticated callers allowed, anonymous not":
215
+ under `OPEN` the policy itself answers `true` to everything, and
216
+ `ADMIN_ONLY` locks out every non-admin, so neither can do this on its own.
217
+ CORS preflights (`OPTIONS`) are still admitted, as they never carry a token.
218
+
219
+ It requires a caller to be resolvable at all, so it refuses to boot without
220
+ `RESOLVE_CALLER_IDENTITY=true` or `AUTH_ENABLED=true` — with identity
221
+ resolution off, no bearer is ever read and it would reject every caller,
222
+ including authenticated ones.
223
+
176
224
  #### Configuration File Method
177
225
 
178
226
  ```json
@@ -184,6 +232,64 @@ export ADMINS="0x111,0x222,0x333"
184
232
  }
185
233
  ```
186
234
 
235
+ #### Which Renown Instance Is Used
236
+
237
+ Step 4 of the verification process re-checks that the signer's Renown credential
238
+ still exists. `auth.renown` says which instance answers that question:
239
+
240
+ ```json
241
+ {
242
+ "auth": {
243
+ "enabled": true,
244
+ "admins": ["0x111"],
245
+ "renown": {
246
+ "source": "remote",
247
+ "url": "https://renown.acme.io",
248
+ "switchboardUrl": "https://sb.acme.io/graphql"
249
+ }
250
+ }
251
+ }
252
+ ```
253
+
254
+ | Field | Env override | Meaning |
255
+ | --- | --- | --- |
256
+ | `source` | `RENOWN_SOURCE` | `remote` (default) queries another instance; `self` reads this switchboard's own `renown-read-model` subgraph in-process. |
257
+ | `url` | `RENOWN_URL` | Renown base URL, used for discovery and the REST fallback. Defaults to `https://www.renown.id`. |
258
+ | `switchboardUrl` | `SWITCHBOARD_URL` | A switchboard's GraphQL endpoint to read credentials from directly, skipping discovery. |
259
+
260
+ Env vars win over the config file field by field; a blank value counts as unset.
261
+ With `source: "remote"` the order is `switchboardUrl`, then discovery via `url`,
262
+ then the Renown REST API at `url`. With `source: "self"` both URLs are ignored
263
+ for verification — but `url` still applies to this switchboard's own identity
264
+ (below).
265
+
266
+ Either way the credential's EIP-712 proof is re-verified and expiry and
267
+ delegation binding are re-checked, so a locally stored credential is held to the
268
+ same standard as a remote one. Successful checks are cached per identity for
269
+ `CREDENTIAL_VERIFICATION_CACHE_TTL_MS` (60s default) in both modes, so
270
+ revocation still lags by up to that TTL.
271
+
272
+ `self` requires a loaded package that provides the `renown-read-model` subgraph
273
+ (`@powerhousedao/renown-package`). Startup fails if none does, rather than booting
274
+ a switchboard that rejects every authenticated request.
275
+
276
+ Where the pieces live: `@renown/sdk` owns the read-model contract
277
+ (`RENOWN_READ_MODEL_SUBGRAPH`, `createLocalCredentialVerifier`), this app wires
278
+ it to the running reactor after the API is up, and `reactor-api` only supplies
279
+ the generic `GraphQLManager.executeSubgraphQuery` plus an injectable
280
+ `verifyCredential` — core has no knowledge of the renown package. A host that
281
+ sets `source: "self"` without injecting a verifier is refused at boot rather
282
+ than silently verified against a remote Renown.
283
+
284
+ #### The Switchboard's Own Identity
285
+
286
+ Separately from verifying incoming credentials, a switchboard has its own
287
+ identity — the `ph login` keypair it uses to authenticate *outbound* to remote
288
+ drives and services. It authenticates against `auth.renown.url` too, so one
289
+ setting covers both directions. Pass `identity.baseUrl` when starting the server
290
+ to point it somewhere else; unset everywhere, it falls back to
291
+ `https://www.renown.id`.
292
+
187
293
  ### 2. **Frontend Integration**
188
294
 
189
295
  #### Using the useAuth Hook