@interop/was-react 0.12.0 → 0.13.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 +23 -25
- package/dist/auth/loginFlow.d.ts +8 -5
- package/dist/auth/loginFlow.d.ts.map +1 -1
- package/dist/auth/loginFlow.js +46 -16
- package/dist/auth/loginFlow.js.map +1 -1
- package/dist/auth/loginRequest.d.ts +27 -81
- package/dist/auth/loginRequest.d.ts.map +1 -1
- package/dist/auth/loginRequest.js +94 -41
- package/dist/auth/loginRequest.js.map +1 -1
- package/dist/auth/verifyResponse.d.ts +3 -3
- package/dist/auth/verifyResponse.d.ts.map +1 -1
- package/dist/auth/verifyResponse.js +16 -13
- package/dist/auth/verifyResponse.js.map +1 -1
- package/dist/auth/walletRequestTypes.d.ts +8 -6
- package/dist/auth/walletRequestTypes.d.ts.map +1 -1
- package/dist/config.d.ts +19 -13
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +0 -0
- package/dist/config.js.map +1 -1
- package/dist/identity/seedCredential.d.ts +39 -38
- package/dist/identity/seedCredential.d.ts.map +1 -1
- package/dist/identity/seedCredential.js +79 -77
- package/dist/identity/seedCredential.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/react/documentApp.d.ts +6 -6
- package/dist/react/documentApp.d.ts.map +1 -1
- package/dist/react/documentApp.js +7 -6
- package/dist/react/documentApp.js.map +1 -1
- package/dist/react/hooks.d.ts +1 -1
- package/dist/react/hooks.js +1 -1
- package/dist/session/authStore.d.ts +1 -1
- package/dist/session/authStore.js +6 -6
- package/dist/session/authStore.js.map +1 -1
- package/dist/storage/adopt.d.ts +3 -3
- package/dist/storage/adopt.js +5 -5
- package/dist/storage/entityStore.d.ts +1 -1
- package/dist/storage/localStore.d.ts +2 -2
- package/dist/storage/localStore.js +2 -2
- package/dist/storage/storageManager.d.ts +8 -3
- package/dist/storage/storageManager.d.ts.map +1 -1
- package/dist/storage/storageManager.js +19 -12
- package/dist/storage/storageManager.js.map +1 -1
- package/dist/sync/lww.d.ts +4 -4
- package/dist/sync/lww.js +7 -7
- package/dist/sync/lwwConflictHandler.d.ts +4 -4
- package/dist/sync/lwwConflictHandler.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -88,22 +88,19 @@ import type { WasAppConfig } from '@interop/was-react'
|
|
|
88
88
|
export const config: WasAppConfig = {
|
|
89
89
|
appName: 'Notes',
|
|
90
90
|
appOrigin: 'https://notes.example',
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
credentialType: 'NotesAppKey',
|
|
94
|
-
vocabBase: 'urn:notes-app:vocab#'
|
|
95
|
-
}
|
|
91
|
+
appUrl: 'https://notes.example/app',
|
|
92
|
+
collections: [{ key: 'notes', id: 'notes' }]
|
|
96
93
|
}
|
|
97
94
|
```
|
|
98
95
|
|
|
99
96
|
`collections` maps each app-side `key` (the local RxDB collection handle) to a
|
|
100
97
|
WAS collection `id` (a deliberately unprefixed, generic name shared across
|
|
101
|
-
interoperable apps). `
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
fields (`mediatorBase`, `dbName`,
|
|
106
|
-
optional with documented defaults.
|
|
98
|
+
interoperable apps). `appUrl` is this application's canonical URL -- absolute,
|
|
99
|
+
fragment-less, and same-origin with the browser origin the app runs on -- and
|
|
100
|
+
identifies it among the applications on that origin: the app-key identity is
|
|
101
|
+
scoped to the triple (user, origin, `appUrl`). Everything downstream compares
|
|
102
|
+
the parsed URL's serialization. All other fields (`mediatorBase`, `dbName`,
|
|
103
|
+
`storageKeyPrefix`, `sync`, `expiry`) are optional with documented defaults.
|
|
107
104
|
|
|
108
105
|
A collection may also declare `visibility: 'public'`
|
|
109
106
|
(`{ key: 'posts', id: 'microblog-posts', visibility: 'public' }`); the default
|
|
@@ -112,7 +109,7 @@ no encryption key is involved at all, payloads are stored as-is locally and
|
|
|
112
109
|
remotely, and the stored resource id is the payload's own logical `id`, so a
|
|
113
110
|
public document keeps a stable, shareable resource URL across edits. Be aware
|
|
114
111
|
that everything in a public payload is world-readable -- including the LWW
|
|
115
|
-
bookkeeping fields `updatedAt` and `
|
|
112
|
+
bookkeeping fields `updatedAt` and `writerId` (`writerId` is a random
|
|
116
113
|
per-install identifier, but still a linkability handle across a user's public
|
|
117
114
|
documents). Changing a collection's visibility after first use is a
|
|
118
115
|
data-migration event, not a config tweak: rows written in the other mode stop
|
|
@@ -120,7 +117,7 @@ being readable. A registry that maps one WAS collection id to both visibilities
|
|
|
120
117
|
is rejected at store open. Two payload constraints on public collections: a
|
|
121
118
|
top-level object-valued `jwe` field is reserved (the read path uses it to
|
|
122
119
|
recognize a stray encrypted envelope and refuses the row), and payloads should
|
|
123
|
-
carry the `updatedAt` / `
|
|
120
|
+
carry the `updatedAt` / `writerId` LWW fields like any other collection --
|
|
124
121
|
without them a concurrent multi-device edit falls back to server-wins.
|
|
125
122
|
|
|
126
123
|
At login, a public collection is requested from the wallet with the distinct
|
|
@@ -210,7 +207,7 @@ export interface Note {
|
|
|
210
207
|
body: string
|
|
211
208
|
createdAt: string
|
|
212
209
|
updatedAt: string
|
|
213
|
-
|
|
210
|
+
writerId: string
|
|
214
211
|
}
|
|
215
212
|
|
|
216
213
|
export const useNotes = createEntityStore<Note>('notes')
|
|
@@ -278,7 +275,7 @@ import { useNotes } from './stores.js'
|
|
|
278
275
|
export function Notes() {
|
|
279
276
|
const notes = useNotes(state => [...state.byId.values()])
|
|
280
277
|
const insert = useNotes(state => state.insert)
|
|
281
|
-
const {
|
|
278
|
+
const { writerId } = useSession()
|
|
282
279
|
|
|
283
280
|
async function addNote() {
|
|
284
281
|
const now = new Date().toISOString()
|
|
@@ -288,7 +285,7 @@ export function Notes() {
|
|
|
288
285
|
body: '',
|
|
289
286
|
createdAt: now,
|
|
290
287
|
updatedAt: now,
|
|
291
|
-
|
|
288
|
+
writerId
|
|
292
289
|
})
|
|
293
290
|
}
|
|
294
291
|
|
|
@@ -305,12 +302,12 @@ export function Notes() {
|
|
|
305
302
|
}
|
|
306
303
|
```
|
|
307
304
|
|
|
308
|
-
Entity payloads MUST carry `updatedAt` and `
|
|
305
|
+
Entity payloads MUST carry `updatedAt` and `writerId`, stamped on EVERY insert
|
|
309
306
|
and update: they are the last-write-wins pair that settles concurrent
|
|
310
307
|
multi-device edits of the same entity. A payload without them loses every sync
|
|
311
|
-
conflict. Read the id from `useSession().
|
|
308
|
+
conflict. Read the id from `useSession().writerId` (resolved once per session,
|
|
312
309
|
honoring `storageKeyPrefix`); outside React, call
|
|
313
|
-
`
|
|
310
|
+
`getWriterId({ storageKeyPrefix })` with the same prefix your config declares.
|
|
314
311
|
|
|
315
312
|
A public collection can additionally answer server-side equality queries.
|
|
316
313
|
Declare the queryable content attributes in the collection config:
|
|
@@ -393,8 +390,8 @@ a progress line (`connecting` to `verifying`):
|
|
|
393
390
|
1. **CHAPI polyfill loads lazily.** The `credential-handler-polyfill` is loaded
|
|
394
391
|
on demand the first time a wallet request is made, not at import time.
|
|
395
392
|
2. **One popup.** A VPR carrying a `DIDAuthentication` query plus one
|
|
396
|
-
`AppConnectQuery` -- the app's display name
|
|
397
|
-
`
|
|
393
|
+
`AppConnectQuery` -- the app's display name and canonical `appUrl`, a
|
|
394
|
+
`capabilityQuery` entry per configured collection, and a
|
|
398
395
|
`https://w3id.org/byoe#shared-wallet-collection` entry per shared collection
|
|
399
396
|
-- is sent via `navigator.credentials.get` (`buildAppConnectVpr`). A `null`
|
|
400
397
|
response is a user cancel (`LoginCancelledError`), not an error.
|
|
@@ -402,8 +399,9 @@ a progress line (`connecting` to `verifying`):
|
|
|
402
399
|
32-byte seed itself, self-issues the origin-bound app-key credential, and
|
|
403
400
|
stores it in its own credential store under the same consent; on a returning
|
|
404
401
|
visit it matches the stored credential by the shared `AppKeyCredential`
|
|
405
|
-
marker type, the
|
|
406
|
-
seed-to-subject binding. The app never mints, and there is no second
|
|
402
|
+
marker type, the `credentialSubject.appUrl` claim, the requesting origin, and
|
|
403
|
+
the seed-to-subject binding. The app never mints, and there is no second
|
|
404
|
+
popup.
|
|
407
405
|
4. **Verify the response.** `verifyLoginPresentation` checks the VP and embedded
|
|
408
406
|
proofs (purpose `authentication`, matching domain and challenge). A
|
|
409
407
|
presentation that verifies but carries no seed credential means the wallet
|
|
@@ -489,8 +487,8 @@ Hooks:
|
|
|
489
487
|
zcaps). Pull is driven by the WAS `changes` feed; a low-frequency periodic
|
|
490
488
|
re-sync (`sync.pollMs`, default 15s) keeps open sessions converging.
|
|
491
489
|
- **Conflict resolution.** Last-writer-wins on the payload's own
|
|
492
|
-
`(updatedAt,
|
|
493
|
-
`
|
|
490
|
+
`(updatedAt, writerId)` (ISO lexical compare, with a per-install random
|
|
491
|
+
`writerId` tiebreaker). Updates re-encrypt in place under the same envelope id
|
|
494
492
|
with `sequence`+1 (a mutable-head model); deletes are soft-delete tombstones.
|
|
495
493
|
- **Status.** `useSyncStatus()` rolls the per-collection replication states up
|
|
496
494
|
to an aggregate: `offline` (no replication running / local-only), or
|
package/dist/auth/loginFlow.d.ts
CHANGED
|
@@ -24,7 +24,6 @@
|
|
|
24
24
|
*/
|
|
25
25
|
import type { IZcap } from '@interop/data-integrity-core';
|
|
26
26
|
import type { DocumentLoader } from '../identity/documentLoader.js';
|
|
27
|
-
import { type SeedCredentialConfig } from '../identity/seedCredential.js';
|
|
28
27
|
import type { IdentityAgents } from '../identity/agents.js';
|
|
29
28
|
import { type GrantRequestCollection } from './loginRequest.js';
|
|
30
29
|
import { type CheckedGrants } from './verifyResponse.js';
|
|
@@ -43,6 +42,14 @@ export interface LoginConfig {
|
|
|
43
42
|
* Human-readable app name, used in the wallet consent reason lines.
|
|
44
43
|
*/
|
|
45
44
|
appName: string;
|
|
45
|
+
/**
|
|
46
|
+
* This app's canonical URL: the application's identity among the
|
|
47
|
+
* applications on its origin. It must be an absolute URL, carry no fragment,
|
|
48
|
+
* and be same-origin with `appOrigin`; the flow serializes it once and uses
|
|
49
|
+
* that serialization for the request, the credential lookup, and the parse
|
|
50
|
+
* check alike.
|
|
51
|
+
*/
|
|
52
|
+
appUrl: string;
|
|
46
53
|
/**
|
|
47
54
|
* The collections to request read/write grants for (WAS collection id +
|
|
48
55
|
* visibility; `'public'` selects the `https://w3id.org/byoe#public-collection` descriptor).
|
|
@@ -54,10 +61,6 @@ export interface LoginConfig {
|
|
|
54
61
|
* Read-only; never replicated.
|
|
55
62
|
*/
|
|
56
63
|
sharedCollections?: string[];
|
|
57
|
-
/**
|
|
58
|
-
* The seed-credential type name + vocabulary namespace.
|
|
59
|
-
*/
|
|
60
|
-
credential: SeedCredentialConfig;
|
|
61
64
|
/**
|
|
62
65
|
* The JSON-LD document loader (see `createDocumentLoader`).
|
|
63
66
|
*/
|
|
@@ -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;
|
|
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;AAErC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAA;AAMnE,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;;;;;;OAMG;IACH,MAAM,EAAE,MAAM,CAAA;IACd;;;OAGG;IACH,WAAW,EAAE,sBAAsB,EAAE,CAAA;IACrC;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC5B;;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;AAkKD;;;;;;;;;;;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,3 +1,4 @@
|
|
|
1
|
+
import { serializedAppUrl } from '@interop/wallet-core/request';
|
|
1
2
|
import { findSeedCredential, parseSeedCredential } from '../identity/seedCredential.js';
|
|
2
3
|
import { NO_EXPIRY_MS } from '../identity/appSession.js';
|
|
3
4
|
import { chapiGet } from './chapi.js';
|
|
@@ -82,6 +83,26 @@ function checkGrantsForCollections({ presentation, controllerDid, collections, s
|
|
|
82
83
|
collections
|
|
83
84
|
});
|
|
84
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* This app's live browser origin: the value sent as the request `domain`, the
|
|
88
|
+
* origin the `appUrl` is validated against, and the origin the returned
|
|
89
|
+
* credential's `origin` claim is checked against. The App Connect spec requires
|
|
90
|
+
* all three to be the same value, and requires it to be the origin the app is
|
|
91
|
+
* actually running on rather than one taken from configuration -- so a
|
|
92
|
+
* configured `appOrigin` that drifts from it is warned about here and never
|
|
93
|
+
* used as the bind.
|
|
94
|
+
*
|
|
95
|
+
* @param config {LoginConfig}
|
|
96
|
+
* @returns {string}
|
|
97
|
+
*/
|
|
98
|
+
function liveOrigin(config) {
|
|
99
|
+
const origin = window.location.origin;
|
|
100
|
+
if (config.appOrigin !== origin) {
|
|
101
|
+
console.warn(`Configured appOrigin "${config.appOrigin}" differs from this app's ` +
|
|
102
|
+
`live browser origin "${origin}"; the live origin is what binds.`);
|
|
103
|
+
}
|
|
104
|
+
return origin;
|
|
105
|
+
}
|
|
85
106
|
/**
|
|
86
107
|
* Runs one App Connect round trip: builds the VPR, opens the single CHAPI `get`
|
|
87
108
|
* popup, and verifies the response presentation (cryptographically, plus the
|
|
@@ -89,20 +110,29 @@ function checkGrantsForCollections({ presentation, controllerDid, collections, s
|
|
|
89
110
|
* -- the same request either way; they differ only in what they take from the
|
|
90
111
|
* verified presentation, and in the step a user cancel names.
|
|
91
112
|
*
|
|
113
|
+
* The `challenge` is a fresh, unpredictable nonce per call (never reused), and
|
|
114
|
+
* is retained for the response check. The serialized `appUrl` actually sent is
|
|
115
|
+
* returned alongside the presentation, so the credential lookup and the parse
|
|
116
|
+
* check compare against the very same string.
|
|
117
|
+
*
|
|
92
118
|
* @param options {object}
|
|
93
119
|
* @param options.config {LoginConfig}
|
|
94
120
|
* @param options.cancelStep {string} the step a cancel is reported against
|
|
95
121
|
* @param [options.onPhase] {Function}
|
|
96
|
-
* @returns {Promise<IVerifiablePresentation
|
|
122
|
+
* @returns {Promise<{ presentation: IVerifiablePresentation, origin: string,
|
|
123
|
+
* appUrl: string }>}
|
|
97
124
|
*/
|
|
98
125
|
async function runAppConnect({ config, cancelStep, onPhase }) {
|
|
99
126
|
onPhase?.('connecting');
|
|
100
127
|
const challenge = newChallenge();
|
|
128
|
+
const origin = liveOrigin(config);
|
|
129
|
+
// Serialized once, here, and threaded to every comparison downstream.
|
|
130
|
+
const appUrl = serializedAppUrl({ appUrl: config.appUrl, origin });
|
|
101
131
|
const vpr = buildAppConnectVpr({
|
|
102
132
|
challenge,
|
|
103
|
-
domain:
|
|
133
|
+
domain: origin,
|
|
104
134
|
appName: config.appName,
|
|
105
|
-
|
|
135
|
+
appUrl,
|
|
106
136
|
collections: config.collections,
|
|
107
137
|
...(config.sharedCollections && {
|
|
108
138
|
sharedCollections: config.sharedCollections
|
|
@@ -121,10 +151,10 @@ async function runAppConnect({ config, cancelStep, onPhase }) {
|
|
|
121
151
|
await verifyLoginPresentation({
|
|
122
152
|
presentation,
|
|
123
153
|
challenge,
|
|
124
|
-
domain:
|
|
154
|
+
domain: origin,
|
|
125
155
|
documentLoader: config.documentLoader
|
|
126
156
|
});
|
|
127
|
-
return presentation;
|
|
157
|
+
return { presentation, origin, appUrl };
|
|
128
158
|
}
|
|
129
159
|
/**
|
|
130
160
|
* Re-requests storage grants for `identity` over a fresh App Connect popup and
|
|
@@ -139,7 +169,7 @@ async function runAppConnect({ config, cancelStep, onPhase }) {
|
|
|
139
169
|
* @returns {Promise<CheckedGrants>}
|
|
140
170
|
*/
|
|
141
171
|
export async function requestGrants({ identity, config, onPhase }) {
|
|
142
|
-
const presentation = await runAppConnect({
|
|
172
|
+
const { presentation } = await runAppConnect({
|
|
143
173
|
config,
|
|
144
174
|
cancelStep: 'storage grants',
|
|
145
175
|
...(onPhase && { onPhase })
|
|
@@ -168,7 +198,7 @@ export async function requestGrants({ identity, config, onPhase }) {
|
|
|
168
198
|
* @returns {Promise<LoginOutcome>}
|
|
169
199
|
*/
|
|
170
200
|
export async function loginWithWallet({ config, onPhase }) {
|
|
171
|
-
const presentation = await runAppConnect({
|
|
201
|
+
const { presentation, origin, appUrl } = await runAppConnect({
|
|
172
202
|
config,
|
|
173
203
|
cancelStep: 'wallet login',
|
|
174
204
|
...(onPhase && { onPhase })
|
|
@@ -176,20 +206,20 @@ export async function loginWithWallet({ config, onPhase }) {
|
|
|
176
206
|
// The wallet mints the app key on first run, so a response with no app-key
|
|
177
207
|
// credential is not first run -- it is a wallet that could not satisfy
|
|
178
208
|
// `AppConnectQuery` at all. Fail closed, legibly, rather than as a generic
|
|
179
|
-
// verification error.
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
});
|
|
209
|
+
// verification error. Located by the `appUrl` claim alone; a returned
|
|
210
|
+
// credential that is wrong in any other way throws from the parse below
|
|
211
|
+
// rather than reading here as "nothing returned".
|
|
212
|
+
const credential = findSeedCredential({ presentation, appUrl });
|
|
184
213
|
if (!credential) {
|
|
185
214
|
throw new WalletUnsupportedError();
|
|
186
215
|
}
|
|
187
|
-
// Recover the seed, enforcing
|
|
188
|
-
//
|
|
216
|
+
// Recover the seed, enforcing the marker type, the appUrl match, self-issue,
|
|
217
|
+
// the origin bind, and the seed-to-DID binding (the same contract whether the
|
|
218
|
+
// wallet matched or minted the credential).
|
|
189
219
|
const parsedCredential = await parseSeedCredential({
|
|
190
220
|
credential,
|
|
191
|
-
origin
|
|
192
|
-
|
|
221
|
+
origin,
|
|
222
|
+
appUrl
|
|
193
223
|
});
|
|
194
224
|
const seed = parsedCredential.seed;
|
|
195
225
|
const firstRun = appConnectFirstRun(presentation);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loginFlow.js","sourceRoot":"","sources":["../../src/auth/loginFlow.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"loginFlow.js","sourceRoot":"","sources":["../../src/auth/loginFlow.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,69 +1,11 @@
|
|
|
1
|
-
/*!
|
|
2
|
-
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
-
*/
|
|
4
|
-
/**
|
|
5
|
-
* RP-side VPR construction for Login With Wallet: the one-popup App Connect
|
|
6
|
-
* request.
|
|
7
|
-
*
|
|
8
|
-
* A single CHAPI `get` carries DIDAuthentication plus an `AppConnectQuery` that
|
|
9
|
-
* names the app (for the wallet's consent screen), the seed-credential naming
|
|
10
|
-
* the wallet needs to MATCH an existing app key or MINT a fresh one, and one
|
|
11
|
-
* collection-scoped capabilityQuery per requested collection. The wallet
|
|
12
|
-
* responds -- in the same round -- with the app-key credential and the
|
|
13
|
-
* delegated zcaps embedded in the response VP, so first-run and returning are a
|
|
14
|
-
* single request/response with no store popup and no separate grants popup.
|
|
15
|
-
*
|
|
16
|
-
* Only collection-scoped capabilities are requested (no whole-space grant),
|
|
17
|
-
* and each request stays within the ACTION CEILING of the descriptor class it
|
|
18
|
-
* uses (the App Connect spec's "Action ceilings" table): a private collection
|
|
19
|
-
* asks for the full vocabulary ({@link RW_ACTIONS}), a public collection at
|
|
20
|
-
* most the add-only set ({@link PUBLIC_ACTIONS}). A configured action set
|
|
21
|
-
* naming an action above a requested collection's ceiling is a configuration
|
|
22
|
-
* error thrown here, at build time -- a conformant wallet can never grant it,
|
|
23
|
-
* so letting it ride would surface later as a failed login instead.
|
|
24
|
-
*
|
|
25
|
-
* A `visibility: 'public'` collection is requested with the distinct descriptor
|
|
26
|
-
* type `https://w3id.org/byoe#public-collection` (the wallet provisions it plaintext with a
|
|
27
|
-
* collection-level public-read policy and renders a world-readable consent
|
|
28
|
-
* warning); wallets that predate the type render it UNSATISFIABLE, which is the
|
|
29
|
-
* intended fail-closed behavior -- an older wallet must not silently provision a
|
|
30
|
-
* private collection the app believes is public. The App Connect query as a
|
|
31
|
-
* whole is likewise unsatisfiable on a wallet that predates it, so an old wallet
|
|
32
|
-
* fails closed rather than degrading into a partial generic flow.
|
|
33
|
-
*
|
|
34
|
-
* A SHARED collection -- one the wallet already owns and encrypts, which the app
|
|
35
|
-
* asks to read and decrypt -- uses the
|
|
36
|
-
* `https://w3id.org/byoe#shared-wallet-collection` descriptor type, and the
|
|
37
|
-
* actions are read-only by construction ({@link SHARED_ACTIONS}):
|
|
38
|
-
* the app never requests writes on a wallet collection. Exactly like
|
|
39
|
-
* `https://w3id.org/byoe#public-collection`, a wallet that predates the type resolves the
|
|
40
|
-
* descriptor UNSATISFIABLE and fails closed -- which is the point here. A share
|
|
41
|
-
* fuses two axes (a read zcap AND an entry in the collection's key-epoch
|
|
42
|
-
* roster), so a wallet that granted only the zcap would hand the app ciphertext
|
|
43
|
-
* it cannot decrypt; that would surface as corrupt data rather than as a wallet
|
|
44
|
-
* that needs updating.
|
|
45
|
-
*
|
|
46
|
-
* `domain` must host-match the CHAPI requesting origin or the wallet refuses
|
|
47
|
-
* to sign; `challenge` must be fresh per request (echoed into the DIDAuth
|
|
48
|
-
* proof and checked in verifyResponse).
|
|
49
|
-
*/
|
|
50
|
-
import type { SeedCredentialConfig } from '../identity/seedCredential.js';
|
|
51
1
|
import type { IVPRDetails } from './walletRequestTypes.js';
|
|
52
2
|
/**
|
|
53
|
-
*
|
|
54
|
-
* the
|
|
55
|
-
*
|
|
3
|
+
* The full WAS action vocabulary, in the order of the App Connect spec's
|
|
4
|
+
* "Allowed actions" table -- also the allowed-action set of BOTH collection
|
|
5
|
+
* classes, `https://w3id.org/byoe#private-collection` and
|
|
6
|
+
* `https://w3id.org/byoe#public-collection`.
|
|
56
7
|
*/
|
|
57
8
|
export declare const RW_ACTIONS: string[];
|
|
58
|
-
/**
|
|
59
|
-
* The `https://w3id.org/byoe#public-collection` class ceiling: add-only, reads
|
|
60
|
-
* plus `POST`, never `PUT` or `DELETE`. A write to a plaintext world-readable
|
|
61
|
-
* collection is publication under the user's identity and irreversible in
|
|
62
|
-
* practice, so a conformant wallet caps a public grant here no matter what was
|
|
63
|
-
* asked -- and the App Connect spec forbids an application from requiring an
|
|
64
|
-
* action above it.
|
|
65
|
-
*/
|
|
66
|
-
export declare const PUBLIC_ACTIONS: string[];
|
|
67
9
|
/**
|
|
68
10
|
* The actions requested on a SHARED (wallet-owned) collection: read-only, and
|
|
69
11
|
* not configurable. A shared collection belongs to the wallet, so an app never
|
|
@@ -71,17 +13,18 @@ export declare const PUBLIC_ACTIONS: string[];
|
|
|
71
13
|
*/
|
|
72
14
|
export declare const SHARED_ACTIONS: string[];
|
|
73
15
|
/**
|
|
74
|
-
* The
|
|
75
|
-
* (the App Connect spec's "
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
16
|
+
* The allowed actions of the descriptor class a collection is requested with
|
|
17
|
+
* (the App Connect spec's "Allowed actions" table). Both collection classes --
|
|
18
|
+
* public and private -- allow the full action vocabulary: published content is
|
|
19
|
+
* still the application's own data, and un-publishing and revision are data
|
|
20
|
+
* management like any other write, so an app may `PUT` or `DELETE` what it
|
|
21
|
+
* published. Shares have their own fixed {@link SHARED_ACTIONS} and never
|
|
22
|
+
* consult a configured action set.
|
|
80
23
|
*
|
|
81
|
-
* @param [
|
|
24
|
+
* @param [_visibility] {'private' | 'public'}
|
|
82
25
|
* @returns {string[]}
|
|
83
26
|
*/
|
|
84
|
-
export declare function actionCeiling(
|
|
27
|
+
export declare function actionCeiling(_visibility?: 'private' | 'public'): string[];
|
|
85
28
|
/**
|
|
86
29
|
* One collection to request a grant for: the WAS collection id plus its
|
|
87
30
|
* declared visibility (`'private'`, the default, or `'public'`).
|
|
@@ -105,9 +48,12 @@ export declare function newChallenge(): string;
|
|
|
105
48
|
/**
|
|
106
49
|
* The one-popup App Connect VPR: DIDAuthentication + a single `AppConnectQuery`.
|
|
107
50
|
*
|
|
108
|
-
* The `app` block names the app
|
|
109
|
-
* the
|
|
110
|
-
*
|
|
51
|
+
* The `app` block names the app -- `name` for the wallet's consent screen, and
|
|
52
|
+
* `appUrl`, the application's canonical URL, which the wallet needs to MATCH an
|
|
53
|
+
* existing app key or MINT a fresh one. The `appUrl` is validated and
|
|
54
|
+
* serialized here against `domain` (this app's own live browser origin): it
|
|
55
|
+
* must be an absolute, fragment-less, same-origin URL, and the serialization is
|
|
56
|
+
* what everything downstream stores and compares. `capabilityQuery` holds one
|
|
111
57
|
* collection-scoped grant request per app collection -- the existing capability
|
|
112
58
|
* shape MINUS `controller` (the wallet fills it with the app-key subject DID)
|
|
113
59
|
* and MINUS `reason` (the App Connect consent screen supersedes per-grant
|
|
@@ -120,8 +66,8 @@ export declare function newChallenge(): string;
|
|
|
120
66
|
* @param options.domain {string}
|
|
121
67
|
* @param options.appName {string} human-readable app name for the consent
|
|
122
68
|
* screen
|
|
123
|
-
* @param options.
|
|
124
|
-
*
|
|
69
|
+
* @param options.appUrl {string} the application's canonical URL, same-origin
|
|
70
|
+
* with `domain` and fragment-less; emitted in its serialized form
|
|
125
71
|
* @param options.collections {GrantRequestCollection[]} the collections to
|
|
126
72
|
* request (WAS collection id + visibility)
|
|
127
73
|
* @param [options.sharedCollections] {string[]} WAS collection ids of
|
|
@@ -129,17 +75,17 @@ export declare function newChallenge(): string;
|
|
|
129
75
|
* a `https://w3id.org/byoe#shared-wallet-collection` descriptor with
|
|
130
76
|
* {@link SHARED_ACTIONS}
|
|
131
77
|
* @param [options.actions] {string[]} the action set to request on each app
|
|
132
|
-
* collection; when omitted each collection requests exactly its class
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
78
|
+
* collection; when omitted each collection requests exactly its class's
|
|
79
|
+
* allowed actions ({@link actionCeiling}). An explicit set naming an action
|
|
80
|
+
* outside them throws (a configuration error, surfaced at build time rather
|
|
81
|
+
* than as a failed login)
|
|
136
82
|
* @returns {IVPRDetails}
|
|
137
83
|
*/
|
|
138
|
-
export declare function buildAppConnectVpr({ challenge, domain, appName,
|
|
84
|
+
export declare function buildAppConnectVpr({ challenge, domain, appName, appUrl, collections, sharedCollections, actions }: {
|
|
139
85
|
challenge: string;
|
|
140
86
|
domain: string;
|
|
141
87
|
appName: string;
|
|
142
|
-
|
|
88
|
+
appUrl: string;
|
|
143
89
|
collections: GrantRequestCollection[];
|
|
144
90
|
sharedCollections?: string[];
|
|
145
91
|
actions?: string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loginRequest.d.ts","sourceRoot":"","sources":["../../src/auth/loginRequest.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"loginRequest.d.ts","sourceRoot":"","sources":["../../src/auth/loginRequest.ts"],"names":[],"mappings":"AAsDA,OAAO,KAAK,EAEV,WAAW,EACZ,MAAM,yBAAyB,CAAA;AAEhC;;;;;GAKG;AACH,eAAO,MAAM,UAAU,UAA2C,CAAA;AAElE;;;;GAIG;AACH,eAAO,MAAM,cAAc,UAAkB,CAAA;AAE7C;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,WAAW,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,MAAM,EAAE,CAI1E;AAED;;;GAGG;AACH,MAAM,WAAW,sBAAsB;IACrC;;OAEG;IACH,EAAE,EAAE,MAAM,CAAA;IACV;;;;OAIG;IACH,UAAU,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAA;CAClC;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAiDD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,SAAS,EACT,MAAM,EACN,OAAO,EACP,MAAM,EACN,WAAW,EACX,iBAAsB,EACtB,OAAO,EACR,EAAE;IACD,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,sBAAsB,EAAE,CAAA;IACrC,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC5B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;CACnB,GAAG,WAAW,CA0Cd"}
|