@interop/was-react 0.8.2 → 0.9.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/README.md +55 -44
- package/dist/auth/chapi.d.ts.map +1 -1
- package/dist/auth/chapi.js +16 -5
- package/dist/auth/chapi.js.map +1 -1
- package/dist/auth/loginFlow.d.ts.map +1 -1
- package/dist/auth/loginFlow.js +55 -49
- package/dist/auth/loginFlow.js.map +1 -1
- package/dist/auth/verifyResponse.d.ts.map +1 -1
- package/dist/auth/verifyResponse.js +27 -10
- package/dist/auth/verifyResponse.js.map +1 -1
- package/dist/auth/walletRequestTypes.d.ts +9 -90
- package/dist/auth/walletRequestTypes.d.ts.map +1 -1
- package/dist/config.d.ts +9 -9
- package/dist/config.js +0 -0
- package/dist/dev/provisionDevGrants.d.ts.map +1 -1
- package/dist/dev/provisionDevGrants.js +18 -19
- package/dist/dev/provisionDevGrants.js.map +1 -1
- package/dist/grants.d.ts +19 -9
- package/dist/grants.d.ts.map +1 -1
- package/dist/grants.js +17 -2
- package/dist/grants.js.map +1 -1
- package/dist/identity/agents.d.ts +1 -1
- package/dist/identity/agents.d.ts.map +1 -1
- package/dist/identity/agents.js +10 -25
- package/dist/identity/agents.js.map +1 -1
- package/dist/identity/appSession.d.ts +27 -0
- package/dist/identity/appSession.d.ts.map +1 -1
- package/dist/identity/appSession.js +51 -13
- package/dist/identity/appSession.js.map +1 -1
- package/dist/identity/seedCredential.d.ts +7 -0
- package/dist/identity/seedCredential.d.ts.map +1 -1
- package/dist/identity/seedCredential.js +11 -14
- package/dist/identity/seedCredential.js.map +1 -1
- package/dist/identity/seedStore.d.ts +19 -0
- package/dist/identity/seedStore.d.ts.map +1 -1
- package/dist/identity/seedStore.js +40 -13
- package/dist/identity/seedStore.js.map +1 -1
- package/dist/index.d.ts +4 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -4
- package/dist/index.js.map +1 -1
- package/dist/jsonLd.d.ts +19 -0
- package/dist/jsonLd.d.ts.map +1 -0
- package/dist/jsonLd.js +24 -0
- package/dist/jsonLd.js.map +1 -0
- package/dist/mui/AdoptDialog.d.ts.map +1 -1
- package/dist/mui/AdoptDialog.js +7 -3
- package/dist/mui/AdoptDialog.js.map +1 -1
- package/dist/mui/ClearDataDialog.d.ts.map +1 -1
- package/dist/mui/ClearDataDialog.js +12 -11
- package/dist/mui/ClearDataDialog.js.map +1 -1
- package/dist/mui/ConfirmDialog.d.ts +35 -0
- package/dist/mui/ConfirmDialog.d.ts.map +1 -0
- package/dist/mui/ConfirmDialog.js +18 -0
- package/dist/mui/ConfirmDialog.js.map +1 -0
- package/dist/mui/LogoutDialog.d.ts.map +1 -1
- package/dist/mui/LogoutDialog.js +7 -3
- package/dist/mui/LogoutDialog.js.map +1 -1
- package/dist/react/documentApp.d.ts +29 -0
- package/dist/react/documentApp.d.ts.map +1 -1
- package/dist/react/documentApp.js +9 -38
- package/dist/react/documentApp.js.map +1 -1
- package/dist/react/hooks.d.ts +13 -7
- package/dist/react/hooks.d.ts.map +1 -1
- package/dist/react/hooks.js +17 -11
- package/dist/react/hooks.js.map +1 -1
- package/dist/session/authStore.d.ts +9 -9
- package/dist/session/authStore.d.ts.map +1 -1
- package/dist/session/authStore.js +203 -112
- package/dist/session/authStore.js.map +1 -1
- package/dist/storage/adopt.d.ts +5 -1
- package/dist/storage/adopt.d.ts.map +1 -1
- package/dist/storage/adopt.js +8 -24
- package/dist/storage/adopt.js.map +1 -1
- package/dist/storage/entityStore.d.ts.map +1 -1
- package/dist/storage/entityStore.js +27 -20
- package/dist/storage/entityStore.js.map +1 -1
- package/dist/storage/localStore.d.ts +12 -6
- package/dist/storage/localStore.d.ts.map +1 -1
- package/dist/storage/localStore.js +146 -77
- package/dist/storage/localStore.js.map +1 -1
- package/dist/storage/rehydrate.d.ts.map +1 -1
- package/dist/storage/rehydrate.js +20 -4
- package/dist/storage/rehydrate.js.map +1 -1
- package/dist/storage/sharedCollectionReader.d.ts +1 -1
- package/dist/storage/sharedCollectionReader.d.ts.map +1 -1
- package/dist/storage/sharedCollectionReader.js +79 -64
- package/dist/storage/sharedCollectionReader.js.map +1 -1
- package/dist/storage/storageManager.d.ts +3 -1
- package/dist/storage/storageManager.d.ts.map +1 -1
- package/dist/storage/storageManager.js +20 -6
- package/dist/storage/storageManager.js.map +1 -1
- package/dist/storage/syncController.d.ts +1 -16
- package/dist/storage/syncController.d.ts.map +1 -1
- package/dist/storage/syncController.js +48 -37
- package/dist/storage/syncController.js.map +1 -1
- package/dist/storage/syncStatusStore.d.ts +8 -6
- package/dist/storage/syncStatusStore.d.ts.map +1 -1
- package/dist/storage/syncStatusStore.js +6 -6
- package/dist/storage/syncStatusStore.js.map +1 -1
- package/dist/storage/wasRemoteStore.d.ts +34 -2
- package/dist/storage/wasRemoteStore.d.ts.map +1 -1
- package/dist/storage/wasRemoteStore.js +45 -7
- package/dist/storage/wasRemoteStore.js.map +1 -1
- package/dist/storage/wasSync.d.ts.map +1 -1
- package/dist/storage/wasSync.js +47 -38
- package/dist/storage/wasSync.js.map +1 -1
- package/dist/sync/changesQuery.d.ts.map +1 -1
- package/dist/sync/changesQuery.js +2 -9
- package/dist/sync/changesQuery.js.map +1 -1
- package/dist/sync/docCipher.d.ts +23 -10
- package/dist/sync/docCipher.d.ts.map +1 -1
- package/dist/sync/docCipher.js +17 -11
- package/dist/sync/docCipher.js.map +1 -1
- package/dist/sync/feedMasterPort.d.ts +6 -0
- package/dist/sync/feedMasterPort.d.ts.map +1 -1
- package/dist/sync/feedMasterPort.js +82 -37
- package/dist/sync/feedMasterPort.js.map +1 -1
- package/dist/sync/index.d.ts +6 -5
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +6 -3
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/lww.d.ts +27 -18
- package/dist/sync/lww.d.ts.map +1 -1
- package/dist/sync/lww.js +35 -8
- package/dist/sync/lww.js.map +1 -1
- package/dist/sync/lwwConflictHandler.d.ts +15 -14
- package/dist/sync/lwwConflictHandler.d.ts.map +1 -1
- package/dist/sync/lwwConflictHandler.js +37 -26
- package/dist/sync/lwwConflictHandler.js.map +1 -1
- package/dist/sync/pushWrites.d.ts +9 -9
- package/dist/sync/pushWrites.d.ts.map +1 -1
- package/dist/sync/pushWrites.js +102 -68
- package/dist/sync/pushWrites.js.map +1 -1
- package/dist/sync/syncedDocSchema.d.ts.map +1 -1
- package/dist/sync/syncedDocSchema.js +3 -0
- package/dist/sync/syncedDocSchema.js.map +1 -1
- package/dist/sync/types.d.ts +115 -15
- package/dist/sync/types.d.ts.map +1 -1
- package/dist/sync/types.js +36 -18
- package/dist/sync/types.js.map +1 -1
- package/dist/sync/wasSyncPort.d.ts +0 -11
- package/dist/sync/wasSyncPort.d.ts.map +1 -1
- package/dist/sync/wasSyncPort.js +20 -36
- package/dist/sync/wasSyncPort.js.map +1 -1
- package/package.json +6 -6
- package/dist/identity/initAppSession.d.ts +0 -27
- package/dist/identity/initAppSession.d.ts.map +0 -1
- package/dist/identity/initAppSession.js +0 -30
- package/dist/identity/initAppSession.js.map +0 -1
package/README.md
CHANGED
|
@@ -64,6 +64,11 @@ pnpm add react zustand rxdb
|
|
|
64
64
|
`@interop/was-react/mui` entry additionally needs `@mui/material`,
|
|
65
65
|
`@mui/icons-material`, and `react-router`; the core entry never imports them.
|
|
66
66
|
|
|
67
|
+
No particular bundler is required: the library reads `import.meta.env.MODE` (a
|
|
68
|
+
Vite convention) only defensively, so webpack / Rspack / Parcel builds and Node
|
|
69
|
+
SSR imports work; without it, non-production-only affordances (the e2e CHAPI
|
|
70
|
+
bridge) simply stay off.
|
|
71
|
+
|
|
67
72
|
```
|
|
68
73
|
pnpm add @mui/material @mui/icons-material @emotion/react @emotion/styled react-router
|
|
69
74
|
```
|
|
@@ -96,9 +101,9 @@ WAS collection `id` (a deliberately unprefixed, generic name shared across
|
|
|
96
101
|
interoperable apps). `credential` names the self-issued seed credential the
|
|
97
102
|
first-run flow mints; its `vocabBase` namespaces the app's own type term only,
|
|
98
103
|
since every app key also carries the shared `AppKeyCredential` marker type and
|
|
99
|
-
its `seed` / `origin` claims under `
|
|
100
|
-
(`mediatorBase`, `dbName`, `storageKeyPrefix`, `sync`, `expiry`) are
|
|
101
|
-
with documented defaults.
|
|
104
|
+
its `seed` / `origin` claims under `https://w3id.org/byoe#` IRIs. All other
|
|
105
|
+
fields (`mediatorBase`, `dbName`, `storageKeyPrefix`, `sync`, `expiry`) are
|
|
106
|
+
optional with documented defaults.
|
|
102
107
|
|
|
103
108
|
A collection may also declare `visibility: 'public'`
|
|
104
109
|
(`{ key: 'posts', id: 'microblog-posts', visibility: 'public' }`); the default
|
|
@@ -119,11 +124,11 @@ carry the `updatedAt` / `clientId` LWW fields like any other collection --
|
|
|
119
124
|
without them a concurrent multi-device edit falls back to server-wins.
|
|
120
125
|
|
|
121
126
|
At login, a public collection is requested from the wallet with the distinct
|
|
122
|
-
`
|
|
123
|
-
`
|
|
124
|
-
policy, shows a world-readable consent warning, and delegates the
|
|
125
|
-
read/write capability (public covers only unauthenticated reads; writes
|
|
126
|
-
capability-only). A wallet that predates the descriptor reports the request
|
|
127
|
+
`https://w3id.org/byoe#public-collection` descriptor type (private collections
|
|
128
|
+
use `https://w3id.org/byoe#collection`): the wallet provisions it plaintext with
|
|
129
|
+
a public-read policy, shows a world-readable consent warning, and delegates the
|
|
130
|
+
usual read/write capability (public covers only unauthenticated reads; writes
|
|
131
|
+
stay capability-only). A wallet that predates the descriptor reports the request
|
|
127
132
|
unsatisfiable rather than silently provisioning a private collection, so the
|
|
128
133
|
feature fails closed with older wallets. Publicness is granted at consent time
|
|
129
134
|
by the wallet -- the app itself can never escalate an existing private
|
|
@@ -168,14 +173,14 @@ the same `key` or `id` in each is rejected at store open
|
|
|
168
173
|
(`validateSharedCollections`).
|
|
169
174
|
|
|
170
175
|
At login each shared collection is requested with the distinct
|
|
171
|
-
`
|
|
172
|
-
`SHARED_ACTIONS` (`GET`/`HEAD`) -- an app never asks to write a
|
|
173
|
-
collection. As with `
|
|
174
|
-
reports the request unsatisfiable and the feature fails
|
|
175
|
-
point: a share fuses two axes -- the read zcap AND an entry
|
|
176
|
-
key-epoch roster -- and a wallet that granted only the zcap
|
|
177
|
-
ciphertext it cannot decrypt, surfacing as corrupt data
|
|
178
|
-
that needs updating.
|
|
176
|
+
`https://w3id.org/byoe#shared-collection` descriptor type and the read-only
|
|
177
|
+
action set `SHARED_ACTIONS` (`GET`/`HEAD`) -- an app never asks to write a
|
|
178
|
+
wallet collection. As with `https://w3id.org/byoe#public-collection`, a wallet
|
|
179
|
+
that predates the type reports the request unsatisfiable and the feature fails
|
|
180
|
+
closed, which is the point: a share fuses two axes -- the read zcap AND an entry
|
|
181
|
+
in the collection's key-epoch roster -- and a wallet that granted only the zcap
|
|
182
|
+
would hand the app ciphertext it cannot decrypt, surfacing as corrupt data
|
|
183
|
+
rather than as a wallet that needs updating.
|
|
179
184
|
|
|
180
185
|
Decryption uses the app's **identity** key-agreement key: the X25519 twin of its
|
|
181
186
|
`did:key` controller (`IdentityAgents.keyAgreementKey`), which is exactly what
|
|
@@ -249,8 +254,7 @@ export function Root() {
|
|
|
249
254
|
import { useLogin } from '@interop/was-react'
|
|
250
255
|
|
|
251
256
|
export function LoginPage() {
|
|
252
|
-
const { login,
|
|
253
|
-
const busy = status === 'authenticating'
|
|
257
|
+
const { login, authenticating: busy, phase, error } = useLogin()
|
|
254
258
|
|
|
255
259
|
return (
|
|
256
260
|
<div>
|
|
@@ -268,12 +272,13 @@ export function LoginPage() {
|
|
|
268
272
|
|
|
269
273
|
```tsx
|
|
270
274
|
import { uuidv7 } from 'uuidv7'
|
|
271
|
-
import {
|
|
275
|
+
import { useSession } from '@interop/was-react'
|
|
272
276
|
import { useNotes } from './stores.js'
|
|
273
277
|
|
|
274
278
|
export function Notes() {
|
|
275
279
|
const notes = useNotes(state => [...state.byId.values()])
|
|
276
280
|
const insert = useNotes(state => state.insert)
|
|
281
|
+
const { clientId } = useSession()
|
|
277
282
|
|
|
278
283
|
async function addNote() {
|
|
279
284
|
const now = new Date().toISOString()
|
|
@@ -283,7 +288,7 @@ export function Notes() {
|
|
|
283
288
|
body: '',
|
|
284
289
|
createdAt: now,
|
|
285
290
|
updatedAt: now,
|
|
286
|
-
clientId
|
|
291
|
+
clientId
|
|
287
292
|
})
|
|
288
293
|
}
|
|
289
294
|
|
|
@@ -300,10 +305,12 @@ export function Notes() {
|
|
|
300
305
|
}
|
|
301
306
|
```
|
|
302
307
|
|
|
303
|
-
Entity payloads MUST carry `updatedAt` and `clientId
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
308
|
+
Entity payloads MUST carry `updatedAt` and `clientId`, stamped on EVERY insert
|
|
309
|
+
and update: they are the last-write-wins pair that settles concurrent
|
|
310
|
+
multi-device edits of the same entity. A payload without them loses every sync
|
|
311
|
+
conflict. Read the id from `useSession().clientId` (resolved once per session,
|
|
312
|
+
honoring `storageKeyPrefix`); outside React, call
|
|
313
|
+
`getClientId({ storageKeyPrefix })` with the same prefix your config declares.
|
|
307
314
|
|
|
308
315
|
A public collection can additionally answer server-side equality queries.
|
|
309
316
|
Declare the queryable content attributes in the collection config:
|
|
@@ -388,9 +395,9 @@ a progress line (`connecting` to `verifying`):
|
|
|
388
395
|
2. **One popup.** A VPR carrying a `DIDAuthentication` query plus one
|
|
389
396
|
`AppConnectQuery` -- the app's display name, `credentialType`, and
|
|
390
397
|
`vocabBase`, a `capabilityQuery` entry per configured collection, and a
|
|
391
|
-
`
|
|
392
|
-
`navigator.credentials.get` (`buildAppConnectVpr`). A `null`
|
|
393
|
-
user cancel (`LoginCancelledError`), not an error.
|
|
398
|
+
`https://w3id.org/byoe#shared-collection` entry per shared collection -- is
|
|
399
|
+
sent via `navigator.credentials.get` (`buildAppConnectVpr`). A `null`
|
|
400
|
+
response is a user cancel (`LoginCancelledError`), not an error.
|
|
394
401
|
3. **The wallet matches or mints.** On a first run the wallet generates the
|
|
395
402
|
32-byte seed itself, self-issues the origin-bound app-key credential, and
|
|
396
403
|
stores it in its own credential store under the same consent; on a returning
|
|
@@ -407,8 +414,7 @@ a progress line (`connecting` to `verifying`):
|
|
|
407
414
|
and seed-to-DID bound, then recovers the seed.
|
|
408
415
|
6. **Derive identity.** The stable `did:key` controller and its signer are
|
|
409
416
|
derived deterministically from the seed via `CapabilityAgent.fromSeed`
|
|
410
|
-
(`
|
|
411
|
-
on every device.
|
|
417
|
+
(`deriveIdentity`). The same seed yields the same identity on every device.
|
|
412
418
|
7. **Check grants.** The delegated zcaps ride in the same presentation's
|
|
413
419
|
top-level `zcap` array. `checkGrants` asserts every zcap is controlled by the
|
|
414
420
|
app DID, shares one space on a single WAS host, covers each configured
|
|
@@ -424,10 +430,11 @@ a progress line (`connecting` to `verifying`):
|
|
|
424
430
|
## Session lifecycle
|
|
425
431
|
|
|
426
432
|
The session is owned by a zustand auth store built once per app by the provider
|
|
427
|
-
(`createAuthStore`). Its `status` is `
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
433
|
+
(`createAuthStore`). Its `status` is `boot` to `local` / `connected` /
|
|
434
|
+
`reconnect`, and it is the router gate: a protected route waits for the restore
|
|
435
|
+
attempt to settle before choosing between the app and the login page. A login in
|
|
436
|
+
flight is NOT a status -- it is the `phase` field, which `useSession()` /
|
|
437
|
+
`useLogin()` also surface as the derived boolean `authenticating`.
|
|
431
438
|
|
|
432
439
|
- **Restore (zero popups).** On mount, `restore()` reads the persisted session
|
|
433
440
|
from IndexedDB and, if present and consistent, re-derives the identity, opens
|
|
@@ -436,7 +443,8 @@ and the login page.
|
|
|
436
443
|
- **Login.** `useLogin().login()` runs the full [login flow](#login-flow).
|
|
437
444
|
- **Reconnect.** Grants are expiry-only. The store watches the earliest grant
|
|
438
445
|
expiry and, once within the warning window (`expiry.warningMs`, default 1h) or
|
|
439
|
-
after a live 401/403,
|
|
446
|
+
after a live 401/403, moves the status to `reconnect` (which `useReconnect()`
|
|
447
|
+
surfaces as the derived `accessExpired`). `useReconnect().reconnect()`
|
|
440
448
|
re-requests grants with the existing seed (one wallet popup, same identity,
|
|
441
449
|
same data) and restarts sync.
|
|
442
450
|
- **Logout.** `useLogout()` stops sync, closes and forgets the local store,
|
|
@@ -447,15 +455,15 @@ and the login page.
|
|
|
447
455
|
|
|
448
456
|
Hooks:
|
|
449
457
|
|
|
450
|
-
| Hook | Returns
|
|
451
|
-
| ----------------- |
|
|
452
|
-
| `useSession()` | `status`, `phase`, `error`, `controllerDid`, `expires`, `accessExpired`, `reconnecting` |
|
|
453
|
-
| `useLogin()` | `{ login, status, phase, error }`
|
|
454
|
-
| `useLogout()` | `() => Promise<void>`
|
|
455
|
-
| `useReconnect()` | `{ accessExpired, reconnecting, reconnect }`
|
|
456
|
-
| `useSyncStatus()` | `{ state, label, title }` (see below)
|
|
457
|
-
| `useAppReady()` | `{ ready, error }` -- the hydration gate
|
|
458
|
-
| `useAuthStore()` | the bound vanilla store (for `getState().restore()`, etc.)
|
|
458
|
+
| Hook | Returns |
|
|
459
|
+
| ----------------- | --------------------------------------------------------------------------------------------------------- |
|
|
460
|
+
| `useSession()` | `status`, `phase`, `authenticating`, `error`, `controllerDid`, `expires`, `accessExpired`, `reconnecting` |
|
|
461
|
+
| `useLogin()` | `{ login, authenticating, status, phase, error }` |
|
|
462
|
+
| `useLogout()` | `() => Promise<void>` |
|
|
463
|
+
| `useReconnect()` | `{ accessExpired, reconnecting, reconnect }` |
|
|
464
|
+
| `useSyncStatus()` | `{ state, label, title }` (see below) |
|
|
465
|
+
| `useAppReady()` | `{ ready, error }` -- the hydration gate |
|
|
466
|
+
| `useAuthStore()` | the bound vanilla store (for `getState().restore()`, etc.) |
|
|
459
467
|
|
|
460
468
|
## Sync architecture
|
|
461
469
|
|
|
@@ -486,7 +494,10 @@ Hooks:
|
|
|
486
494
|
with `sequence`+1 (a mutable-head model); deletes are soft-delete tombstones.
|
|
487
495
|
- **Status.** `useSyncStatus()` rolls the per-collection replication states up
|
|
488
496
|
to an aggregate: `offline` (no replication running / local-only), or
|
|
489
|
-
`error > syncing > synced`. The `SyncStatusChip` MUI component renders it.
|
|
497
|
+
`error > syncing > synced`. The `SyncStatusChip` MUI component renders it. The
|
|
498
|
+
underlying `useSyncStatusStore().statuses` map is keyed by the registry's
|
|
499
|
+
logical collection `key`, like every other layer (two entries may share one
|
|
500
|
+
WAS collection `id`, so the id is not a unique status key).
|
|
490
501
|
|
|
491
502
|
## Entry points
|
|
492
503
|
|
package/dist/auth/chapi.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chapi.d.ts","sourceRoot":"","sources":["../../src/auth/chapi.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAA;AAC3E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAE1D;;GAEG;AACH,eAAO,MAAM,qBAAqB,sCAAsC,CAAA;
|
|
1
|
+
{"version":3,"file":"chapi.d.ts","sourceRoot":"","sources":["../../src/auth/chapi.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAA;AAC3E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAE1D;;GAEG;AACH,eAAO,MAAM,qBAAqB,sCAAsC,CAAA;AAgExE;;;;;;;GAOG;AACH,wBAAsB,SAAS,CAAC,EAC9B,YAAoC,EACrC,GAAE;IACD,YAAY,CAAC,EAAE,MAAM,CAAA;CACjB,GAAG,OAAO,CAAC,IAAI,CAAC,CAMrB;AAED;;;;;;;;GAQG;AACH,wBAAsB,QAAQ,CAAC,EAC7B,GAAG,EACH,YAAY,EACb,EAAE;IACD,GAAG,EAAE,WAAW,CAAA;IAChB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,GAAG,OAAO,CAAC,uBAAuB,GAAG,IAAI,CAAC,CAgB1C"}
|
package/dist/auth/chapi.js
CHANGED
|
@@ -22,20 +22,31 @@ import { loadOnce } from 'credential-handler-polyfill';
|
|
|
22
22
|
* The default CHAPI mediator base; the requesting origin is appended at load.
|
|
23
23
|
*/
|
|
24
24
|
export const DEFAULT_MEDIATOR_BASE = 'https://authn.io/mediator?origin=';
|
|
25
|
+
/**
|
|
26
|
+
* The build mode, read defensively: `import.meta.env` exists only under Vite
|
|
27
|
+
* (a devDependency here, not a required consumer toolchain), so a webpack /
|
|
28
|
+
* Rspack / Parcel bundle -- or a Node SSR import -- has no `env` object at
|
|
29
|
+
* all. Treat that as a production build (the safe direction: the e2e bridge
|
|
30
|
+
* stays off).
|
|
31
|
+
*/
|
|
32
|
+
function buildMode() {
|
|
33
|
+
return import.meta.env?.MODE ?? 'production';
|
|
34
|
+
}
|
|
25
35
|
function e2eBridgeActive() {
|
|
26
|
-
return (
|
|
36
|
+
return (buildMode() !== 'production' &&
|
|
27
37
|
window.__WAS_REACT_E2E_CHAPI__ === true);
|
|
28
38
|
}
|
|
29
39
|
let e2eRequestId = 0;
|
|
30
40
|
/**
|
|
31
|
-
* Queues a request for the e2e harness and polls for its response
|
|
41
|
+
* Queues a `get` request for the e2e harness and polls for its response (the
|
|
42
|
+
* one-popup flow rides `get` alone, so that is the only queued request type).
|
|
32
43
|
*/
|
|
33
|
-
async function e2eRoundTrip(
|
|
44
|
+
async function e2eRoundTrip(body) {
|
|
34
45
|
const win = window;
|
|
35
46
|
win.__WAS_REACT_E2E_CHAPI_REQUESTS__ ??= [];
|
|
36
47
|
win.__WAS_REACT_E2E_CHAPI_RESPONSES__ ??= {};
|
|
37
48
|
const id = ++e2eRequestId;
|
|
38
|
-
win.__WAS_REACT_E2E_CHAPI_REQUESTS__.push({ id, type, body });
|
|
49
|
+
win.__WAS_REACT_E2E_CHAPI_REQUESTS__.push({ id, type: 'get', body });
|
|
39
50
|
const deadline = Date.now() + 120_000;
|
|
40
51
|
while (Date.now() < deadline) {
|
|
41
52
|
if (id in win.__WAS_REACT_E2E_CHAPI_RESPONSES__) {
|
|
@@ -75,7 +86,7 @@ export async function loadChapi({ mediatorBase = DEFAULT_MEDIATOR_BASE } = {}) {
|
|
|
75
86
|
export async function chapiGet({ vpr, mediatorBase }) {
|
|
76
87
|
let wire;
|
|
77
88
|
if (e2eBridgeActive()) {
|
|
78
|
-
wire = await e2eRoundTrip(
|
|
89
|
+
wire = await e2eRoundTrip(vpr);
|
|
79
90
|
}
|
|
80
91
|
else {
|
|
81
92
|
await loadChapi(mediatorBase !== undefined ? { mediatorBase } : {});
|
package/dist/auth/chapi.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chapi.js","sourceRoot":"","sources":["../../src/auth/chapi.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,qCAAqC;AACrC;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,6BAA6B,CAAA;AAItD;;GAEG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,mCAAmC,CAAA;AAoBxE,SAAS,
|
|
1
|
+
{"version":3,"file":"chapi.js","sourceRoot":"","sources":["../../src/auth/chapi.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loginFlow.d.ts","sourceRoot":"","sources":["../../src/auth/loginFlow.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,EAEV,KAAK,EACN,MAAM,8BAA8B,CAAA;AACrC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAA;AACnE,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,+BAA+B,CAAA;AAEtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAE3D,OAAO,EAGL,KAAK,sBAAsB,EAC5B,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAIL,KAAK,aAAa,EACnB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAEhD;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B;;OAEG;IACH,SAAS,EAAE,MAAM,CAAA;IACjB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAA;IACf;;;OAGG;IACH,WAAW,EAAE,sBAAsB,EAAE,CAAA;IACrC;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC5B;;OAEG;IACH,UAAU,EAAE,oBAAoB,CAAA;IAChC;;OAEG;IACH,cAAc,EAAE,cAAc,CAAA;IAC9B;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG,WAAW,CAAA;AAEnD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,UAAU,CAAA;IAChB,QAAQ,EAAE,cAAc,CAAA;IACxB,MAAM,EAAE,KAAK,EAAE,CAAA;IACf,MAAM,EAAE,YAAY,CAAA;IACpB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAA;IACf;;OAEG;IACH,QAAQ,EAAE,OAAO,CAAA;CAClB;AAED;;GAEG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,IAAI,EAAE,MAAM;CAIzB;AAED;;;;;GAKG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;;CAQhD;
|
|
1
|
+
{"version":3,"file":"loginFlow.d.ts","sourceRoot":"","sources":["../../src/auth/loginFlow.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,EAEV,KAAK,EACN,MAAM,8BAA8B,CAAA;AACrC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAA;AACnE,OAAO,EAGL,KAAK,oBAAoB,EAC1B,MAAM,+BAA+B,CAAA;AAEtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAE3D,OAAO,EAGL,KAAK,sBAAsB,EAC5B,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAIL,KAAK,aAAa,EACnB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAEhD;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B;;OAEG;IACH,SAAS,EAAE,MAAM,CAAA;IACjB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAA;IACf;;;OAGG;IACH,WAAW,EAAE,sBAAsB,EAAE,CAAA;IACrC;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC5B;;OAEG;IACH,UAAU,EAAE,oBAAoB,CAAA;IAChC;;OAEG;IACH,cAAc,EAAE,cAAc,CAAA;IAC9B;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG,WAAW,CAAA;AAEnD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,UAAU,CAAA;IAChB,QAAQ,EAAE,cAAc,CAAA;IACxB,MAAM,EAAE,KAAK,EAAE,CAAA;IACf,MAAM,EAAE,YAAY,CAAA;IACpB;;OAEG;IACH,OAAO,EAAE,MAAM,CAAA;IACf;;OAEG;IACH,QAAQ,EAAE,OAAO,CAAA;CAClB;AAED;;GAEG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,IAAI,EAAE,MAAM;CAIzB;AAED;;;;;GAKG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;;CAQhD;AA4HD;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CAAC,EAClC,QAAQ,EACR,MAAM,EACN,OAAO,EACR,EAAE;IACD,QAAQ,EAAE,cAAc,CAAA;IACxB,MAAM,EAAE,WAAW,CAAA;IACnB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAA;CACtC,GAAG,OAAO,CAAC,aAAa,CAAC,CAczB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,eAAe,CAAC,EACpC,MAAM,EACN,OAAO,EACR,EAAE;IACD,MAAM,EAAE,WAAW,CAAA;IACnB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAA;CACtC,GAAG,OAAO,CAAC,YAAY,CAAC,CAkDxB"}
|
package/dist/auth/loginFlow.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { findSeedCredential, parseSeedCredential } from '../identity/seedCredential.js';
|
|
2
|
-
import {
|
|
2
|
+
import { NO_EXPIRY_MS } from '../identity/appSession.js';
|
|
3
3
|
import { chapiGet } from './chapi.js';
|
|
4
4
|
import { buildAppConnectVpr, newChallenge } from './loginRequest.js';
|
|
5
5
|
import { checkGrants, grantsOf, verifyLoginPresentation } from './verifyResponse.js';
|
|
@@ -25,11 +25,6 @@ export class WalletUnsupportedError extends Error {
|
|
|
25
25
|
this.name = 'WalletUnsupportedError';
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
-
/**
|
|
29
|
-
* A nominal far-future expiry (ms) used when an app requests no collections (so
|
|
30
|
-
* there are no grants and thus no earliest-expiry to report).
|
|
31
|
-
*/
|
|
32
|
-
const NO_GRANTS_EXPIRY_MS = 100 * 365 * 24 * 60 * 60 * 1000;
|
|
33
28
|
/**
|
|
34
29
|
* Reads the wallet-provided `presentation.appConnect.firstRun` boolean. Anything
|
|
35
30
|
* other than boolean `true` (including an absent member -- a returning login) is
|
|
@@ -42,10 +37,14 @@ function appConnectFirstRun(presentation) {
|
|
|
42
37
|
}
|
|
43
38
|
/**
|
|
44
39
|
* Structurally validates the grants embedded in the wallet response against the
|
|
45
|
-
* requested collections. When the app requested
|
|
46
|
-
*
|
|
47
|
-
* rejects an empty grant set) is skipped and an empty grant set with a
|
|
48
|
-
* far-future expiry is returned instead.
|
|
40
|
+
* requested collections. When the app requested no APP-OWNED collections and
|
|
41
|
+
* the wallet returned no grants there is nothing to validate: `checkGrants`
|
|
42
|
+
* (which rejects an empty grant set) is skipped and an empty grant set with a
|
|
43
|
+
* far-future expiry is returned instead. That covers both the app that
|
|
44
|
+
* requests nothing at all and the shared-only app (`collections: []`, some
|
|
45
|
+
* `sharedCollections`) whose user declined every share -- a declined share is
|
|
46
|
+
* not a login failure, so such a login completes with no remote storage and
|
|
47
|
+
* the missing readers are warned about downstream.
|
|
49
48
|
*
|
|
50
49
|
* Only the app-owned collections are REQUIRED to be covered. A shared
|
|
51
50
|
* collection the wallet declined to grant is not a login failure -- the reader
|
|
@@ -62,32 +61,39 @@ function appConnectFirstRun(presentation) {
|
|
|
62
61
|
*/
|
|
63
62
|
function checkGrantsForCollections({ presentation, controllerDid, collections, sharedCollections = [] }) {
|
|
64
63
|
const collectionIds = collections.map(collection => collection.id);
|
|
65
|
-
|
|
64
|
+
const grants = grantsOf(presentation);
|
|
65
|
+
if (collectionIds.length === 0 && grants.length === 0) {
|
|
66
|
+
if (sharedCollections.length > 0) {
|
|
67
|
+
console.warn('The wallet returned no storage grants; the requested shared ' +
|
|
68
|
+
`collection(s) [${sharedCollections.join(', ')}] were declined, so ` +
|
|
69
|
+
'no shared reader will be opened.');
|
|
70
|
+
}
|
|
66
71
|
return {
|
|
67
72
|
grants: [],
|
|
68
73
|
parsed: { serverUrl: '', spaceId: '', byCollectionId: {} },
|
|
69
|
-
expires: new Date(Date.now() +
|
|
74
|
+
expires: new Date(Date.now() + NO_EXPIRY_MS).toISOString()
|
|
70
75
|
};
|
|
71
76
|
}
|
|
72
77
|
return checkGrants({
|
|
73
|
-
grants
|
|
78
|
+
grants,
|
|
74
79
|
controllerDid,
|
|
75
80
|
collections: collectionIds
|
|
76
81
|
});
|
|
77
82
|
}
|
|
78
83
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
84
|
+
* Runs one App Connect round trip: builds the VPR, opens the single CHAPI `get`
|
|
85
|
+
* popup, and verifies the response presentation (cryptographically, plus the
|
|
86
|
+
* challenge/domain binds). Shared by the full login and the reconnect re-grant
|
|
87
|
+
* -- the same request either way; they differ only in what they take from the
|
|
88
|
+
* verified presentation, and in the step a user cancel names.
|
|
83
89
|
*
|
|
84
90
|
* @param options {object}
|
|
85
|
-
* @param options.identity {IdentityAgents}
|
|
86
91
|
* @param options.config {LoginConfig}
|
|
92
|
+
* @param options.cancelStep {string} the step a cancel is reported against
|
|
87
93
|
* @param [options.onPhase] {Function}
|
|
88
|
-
* @returns {Promise<
|
|
94
|
+
* @returns {Promise<IVerifiablePresentation>}
|
|
89
95
|
*/
|
|
90
|
-
|
|
96
|
+
async function runAppConnect({ config, cancelStep, onPhase }) {
|
|
91
97
|
onPhase?.('connecting');
|
|
92
98
|
const challenge = newChallenge();
|
|
93
99
|
const vpr = buildAppConnectVpr({
|
|
@@ -107,7 +113,7 @@ export async function requestGrants({ identity, config, onPhase }) {
|
|
|
107
113
|
})
|
|
108
114
|
});
|
|
109
115
|
if (!presentation) {
|
|
110
|
-
throw new LoginCancelledError(
|
|
116
|
+
throw new LoginCancelledError(cancelStep);
|
|
111
117
|
}
|
|
112
118
|
onPhase?.('verifying');
|
|
113
119
|
await verifyLoginPresentation({
|
|
@@ -116,6 +122,26 @@ export async function requestGrants({ identity, config, onPhase }) {
|
|
|
116
122
|
domain: window.location.origin,
|
|
117
123
|
documentLoader: config.documentLoader
|
|
118
124
|
});
|
|
125
|
+
return presentation;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Re-requests storage grants for `identity` over a fresh App Connect popup and
|
|
129
|
+
* validates them. The expired-access reconnect path: the seed already exists, so
|
|
130
|
+
* only the grants need renewing. The wallet matches the same app key and
|
|
131
|
+
* re-delegates; the returned credential/`appConnect` marker are ignored here.
|
|
132
|
+
*
|
|
133
|
+
* @param options {object}
|
|
134
|
+
* @param options.identity {IdentityAgents}
|
|
135
|
+
* @param options.config {LoginConfig}
|
|
136
|
+
* @param [options.onPhase] {Function}
|
|
137
|
+
* @returns {Promise<CheckedGrants>}
|
|
138
|
+
*/
|
|
139
|
+
export async function requestGrants({ identity, config, onPhase }) {
|
|
140
|
+
const presentation = await runAppConnect({
|
|
141
|
+
config,
|
|
142
|
+
cancelStep: 'storage grants',
|
|
143
|
+
...(onPhase && { onPhase })
|
|
144
|
+
});
|
|
119
145
|
return checkGrantsForCollections({
|
|
120
146
|
presentation,
|
|
121
147
|
controllerDid: identity.controllerDid,
|
|
@@ -140,33 +166,10 @@ export async function requestGrants({ identity, config, onPhase }) {
|
|
|
140
166
|
* @returns {Promise<LoginOutcome>}
|
|
141
167
|
*/
|
|
142
168
|
export async function loginWithWallet({ config, onPhase }) {
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
domain: window.location.origin,
|
|
148
|
-
appName: config.appName,
|
|
149
|
-
credential: config.credential,
|
|
150
|
-
collections: config.collections,
|
|
151
|
-
...(config.sharedCollections && {
|
|
152
|
-
sharedCollections: config.sharedCollections
|
|
153
|
-
})
|
|
154
|
-
});
|
|
155
|
-
const presentation = await chapiGet({
|
|
156
|
-
vpr,
|
|
157
|
-
...(config.mediatorBase !== undefined && {
|
|
158
|
-
mediatorBase: config.mediatorBase
|
|
159
|
-
})
|
|
160
|
-
});
|
|
161
|
-
if (!presentation) {
|
|
162
|
-
throw new LoginCancelledError('wallet login');
|
|
163
|
-
}
|
|
164
|
-
onPhase?.('verifying');
|
|
165
|
-
await verifyLoginPresentation({
|
|
166
|
-
presentation,
|
|
167
|
-
challenge,
|
|
168
|
-
domain: window.location.origin,
|
|
169
|
-
documentLoader: config.documentLoader
|
|
169
|
+
const presentation = await runAppConnect({
|
|
170
|
+
config,
|
|
171
|
+
cancelStep: 'wallet login',
|
|
172
|
+
...(onPhase && { onPhase })
|
|
170
173
|
});
|
|
171
174
|
// The wallet mints the app key on first run, so a response with no app-key
|
|
172
175
|
// credential is not first run -- it is a wallet that could not satisfy
|
|
@@ -188,7 +191,10 @@ export async function loginWithWallet({ config, onPhase }) {
|
|
|
188
191
|
});
|
|
189
192
|
const seed = parsedCredential.seed;
|
|
190
193
|
const firstRun = appConnectFirstRun(presentation);
|
|
191
|
-
|
|
194
|
+
// The master identity comes off the parse, which already derived it from this
|
|
195
|
+
// seed to check the seed-to-DID binding (the same `deriveIdentity` call, and
|
|
196
|
+
// the same 32-byte seed rule, every other session path applies).
|
|
197
|
+
const identity = parsedCredential.identity;
|
|
192
198
|
// Grants ride in the SAME response; validate them against the app-key subject
|
|
193
199
|
// DID the wallet delegated to.
|
|
194
200
|
const checked = checkGrantsForCollections({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loginFlow.js","sourceRoot":"","sources":["../../src/auth/loginFlow.ts"],"names":[],"mappings":"AA6BA,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EAEpB,MAAM,+BAA+B,CAAA;AACtC,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"loginFlow.js","sourceRoot":"","sources":["../../src/auth/loginFlow.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verifyResponse.d.ts","sourceRoot":"","sources":["../../src/auth/verifyResponse.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EACV,uBAAuB,EACvB,KAAK,EACN,MAAM,8BAA8B,CAAA;AACrC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAA;AACnE,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"verifyResponse.d.ts","sourceRoot":"","sources":["../../src/auth/verifyResponse.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EACV,uBAAuB,EACvB,KAAK,EACN,MAAM,8BAA8B,CAAA;AACrC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAA;AACnE,OAAO,EAAe,KAAK,YAAY,EAAE,MAAM,cAAc,CAAA;AAK7D;;;;;;;;;;GAUG;AACH,wBAAsB,uBAAuB,CAAC,EAC5C,YAAY,EACZ,SAAS,EACT,MAAM,EACN,cAAc,EACf,EAAE;IACD,YAAY,EAAE,uBAAuB,CAAA;IACrC,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,cAAc,EAAE,cAAc,CAAA;CAC/B,GAAG,OAAO,CAAC,IAAI,CAAC,CA0EhB;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,YAAY,EAAE,uBAAuB,GAAG,KAAK,EAAE,CAMvE;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,YAAY,CAAA;IACpB,MAAM,EAAE,KAAK,EAAE,CAAA;IACf;;OAEG;IACH,OAAO,EAAE,MAAM,CAAA;CAChB;AASD;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,EAC1B,MAAM,EACN,aAAa,EACb,WAAW,EACX,eAA4B,EAC7B,EAAE;IACD,MAAM,EAAE,KAAK,EAAE,CAAA;IACf,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,EAAE,MAAM,EAAE,CAAA;IACrB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAA;CAC3B,GAAG,aAAa,CAyChB"}
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
import { verifyPresentation } from '@interop/verifier-core';
|
|
29
29
|
import { parseGrants } from '../grants.js';
|
|
30
30
|
import { earliestExpiry, isExpired } from '../identity/appSession.js';
|
|
31
|
+
import { asArray } from '../jsonLd.js';
|
|
31
32
|
import { RW_ACTIONS } from './loginRequest.js';
|
|
32
33
|
/**
|
|
33
34
|
* Verifies a login response VP cryptographically and structurally (steps 1-2
|
|
@@ -69,17 +70,34 @@ export async function verifyLoginPresentation({ presentation, challenge, domain,
|
|
|
69
70
|
.join(', ');
|
|
70
71
|
throw new Error(`Wallet presentation failed verification (${summary || 'unknown check'}).`);
|
|
71
72
|
}
|
|
72
|
-
|
|
73
|
-
const proofs =
|
|
74
|
-
|
|
75
|
-
if (!authProof) {
|
|
73
|
+
// The presentation-level proofs (a VP may carry one object or several).
|
|
74
|
+
const proofs = asArray(presentation.proof);
|
|
75
|
+
if (proofs.length === 0) {
|
|
76
76
|
throw new Error('Wallet presentation carries no authentication proof.');
|
|
77
77
|
}
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
// Belt-and-suspenders against a mixed proof set: the crypto layer selects ONE
|
|
79
|
+
// purpose for the whole set and skips non-matching proofs, so a proof under
|
|
80
|
+
// any OTHER purpose is one this verification may never have signature-checked
|
|
81
|
+
// -- and the challenge/domain checks below are only meaningful on a proof
|
|
82
|
+
// that provably verified. Requiring every presentation-level proof to be an
|
|
83
|
+
// authentication proof keeps this library failing closed even against an
|
|
84
|
+
// older `@interop/verifier-core` whose purpose selection reads `proof[0]`
|
|
85
|
+
// alone (a VP ordered `[assertionMethod, authentication]` would otherwise
|
|
86
|
+
// verify under AssertionProofPurpose with the authentication proof -- the
|
|
87
|
+
// only freshness bind -- never touched by a signature check).
|
|
88
|
+
if (proofs.some(proof => proof.proofPurpose !== 'authentication')) {
|
|
89
|
+
throw new Error('Wallet presentation carries a non-authentication proof; every ' +
|
|
90
|
+
'presentation-level proof must be an authentication proof.');
|
|
80
91
|
}
|
|
81
|
-
|
|
82
|
-
|
|
92
|
+
// Every proof in the set was verified under AuthenticationProofPurpose, so
|
|
93
|
+
// require the fresh challenge and this app's domain on each of them.
|
|
94
|
+
for (const proof of proofs) {
|
|
95
|
+
if (proof.challenge !== challenge) {
|
|
96
|
+
throw new Error('Wallet presentation challenge does not match.');
|
|
97
|
+
}
|
|
98
|
+
if (proof.domain !== domain) {
|
|
99
|
+
throw new Error(`Wallet presentation domain "${proof.domain ?? ''}" does not match "${domain}".`);
|
|
100
|
+
}
|
|
83
101
|
}
|
|
84
102
|
}
|
|
85
103
|
/**
|
|
@@ -96,8 +114,7 @@ export function grantsOf(presentation) {
|
|
|
96
114
|
* The actions a grant allows, normalized to an array.
|
|
97
115
|
*/
|
|
98
116
|
function actionsOf(zcap) {
|
|
99
|
-
|
|
100
|
-
return Array.isArray(allowed) ? allowed : allowed ? [allowed] : [];
|
|
117
|
+
return asArray(zcap.allowedAction);
|
|
101
118
|
}
|
|
102
119
|
/**
|
|
103
120
|
* Structural validation of the granted zcaps (step 3 above). Throws on any
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verifyResponse.js","sourceRoot":"","sources":["../../src/auth/verifyResponse.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAM3D,OAAO,EAAE,WAAW,EAAqB,MAAM,cAAc,CAAA;AAC7D,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAA;AACrE,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;
|
|
1
|
+
{"version":3,"file":"verifyResponse.js","sourceRoot":"","sources":["../../src/auth/verifyResponse.ts"],"names":[],"mappings":"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"}
|