@dotrino/identity 0.115.0 → 0.117.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.
Files changed (82) hide show
  1. package/package.json +4 -1
  2. package/src/node.js +4 -180
  3. package/vault/content.js +28 -1
  4. package/vault/core.js +66 -7
  5. package/vault/facade.js +200 -0
  6. package/vault/index.html +10 -0
  7. package/vault/remote.js +45 -5
  8. package/vault/vault.js +29 -58
  9. package/vault/vendor/compat/VERSION.txt +3 -0
  10. package/vault/vendor/compat/advisory.js +141 -0
  11. package/vault/vendor/compat/index.js +188 -0
  12. package/vault/vendor/compat/ranges.js +93 -0
  13. package/vault/vendor/opaque/VERSION.txt +1 -1
  14. package/vault/vendor/passmanager/VERSION.txt +3 -0
  15. package/vault/vendor/passmanager/crypto.js +167 -0
  16. package/vault/vendor/passmanager/fields.js +183 -0
  17. package/vault/vendor/passmanager/generate.js +74 -0
  18. package/vault/vendor/passmanager/import.js +156 -0
  19. package/vault/vendor/passmanager/index.js +27 -0
  20. package/vault/vendor/passmanager/match.js +137 -0
  21. package/vault/vendor/passmanager/model.js +332 -0
  22. package/vault/vendor/passmanager/pubkey.js +33 -0
  23. package/vault/vendor/passmanager/sealed/convert.js +72 -0
  24. package/vault/vendor/passmanager/sealed/device.js +211 -0
  25. package/vault/vendor/passmanager/sealed/entry.js +209 -0
  26. package/vault/vendor/passmanager/sealed/index.js +20 -0
  27. package/vault/vendor/passmanager/sealed/keys.js +120 -0
  28. package/vault/vendor/passmanager/sealed/recovery.js +136 -0
  29. package/vault/vendor/passmanager/sealed/store.js +508 -0
  30. package/vault/vendor/passmanager/session-cache.js +60 -0
  31. package/vault/vendor/passmanager/totp.js +111 -0
  32. package/vault/vendor/passmanager/transport/protocol.js +83 -0
  33. package/vault/vendor/passmanager/transport/proxy.js +112 -0
  34. package/vault/vendor/passmanager/transport/sealed.js +74 -0
  35. package/vault/vendor/passmanager/vault/approval.js +63 -0
  36. package/vault/vendor/passmanager/vault/errors.js +34 -0
  37. package/vault/vendor/passmanager/vault/guard.js +114 -0
  38. package/vault/vendor/passmanager/vault/interface.js +34 -0
  39. package/vault/vendor/passmanager/vault/local.js +213 -0
  40. package/vault/vendor/passmanager/vault/remote.js +85 -0
  41. package/vault/vendor/passmanager/vault/responder.js +192 -0
  42. package/vault/vendor/passmanager/vault/sealed-local.js +199 -0
  43. package/vault/vendor/passmanager/vault/sealed-responder.js +159 -0
  44. package/vault/vendor/passmanager/vault/sealed.js +362 -0
  45. package/vault/vendor/passmanager/webauthn.js +221 -0
  46. package/vault/vendor/proxy-client/VERSION.txt +1 -1
  47. package/vault/vendor/proxy-client/client.js +136 -11
  48. package/vault/vendor/proxy-client/index.js +1 -0
  49. package/vault/vendor/proxy-client/parts.js +127 -0
  50. package/vault/vendor/proxy-client/webrtc.js +23 -0
  51. package/vault/vendor/roadmap/VERSION.txt +3 -0
  52. package/vault/vendor/roadmap/index.js +108 -0
  53. package/vault/vendor/roadmap/manifests.js +802 -0
  54. package/vault/vendor/store/VERSION.txt +3 -0
  55. package/vault/vendor/store/core.js +309 -0
  56. package/vault/vendor/vault/VERSION.txt +4 -1
  57. package/vault/vendor/vault/admin.js +437 -0
  58. package/vault/vendor/vault/browser.js +167 -0
  59. package/vault/vendor/vault/core/approvals.js +204 -0
  60. package/vault/vendor/vault/core/blocked.js +68 -0
  61. package/vault/vendor/vault/core/browserDisk.js +109 -0
  62. package/vault/vendor/vault/core/commKey.js +68 -0
  63. package/vault/vendor/vault/core/disk.js +65 -0
  64. package/vault/vendor/vault/core/libVersion.js +8 -0
  65. package/vault/vendor/vault/core/machines.js +66 -0
  66. package/vault/vendor/vault/core/notices.js +71 -0
  67. package/vault/vendor/vault/core/notifiable.js +73 -0
  68. package/vault/vendor/vault/core/passwords.js +125 -0
  69. package/vault/vendor/vault/core/sealKey.js +77 -0
  70. package/vault/vendor/vault/core/sealer.js +172 -0
  71. package/vault/vendor/vault/core/sealers.js +159 -0
  72. package/vault/vendor/vault/core/secretsStore.js +1326 -0
  73. package/vault/vendor/vault/core/sha256.js +69 -0
  74. package/vault/vendor/vault/core/store.js +68 -0
  75. package/vault/vendor/vault/core/subacta.js +101 -0
  76. package/vault/vendor/vault/core/threadStore.js +273 -0
  77. package/vault/vendor/vault/core/transport.js +270 -0
  78. package/vault/vendor/vault/core/vault.js +4414 -0
  79. package/vault/vendor/vault/enroll.js +8 -4
  80. package/vault/vendor/vault/procContext.js +97 -0
  81. package/vault/vendor/vault/revocation.js +67 -0
  82. package/vault/vendor/vault/sealed.js +84 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotrino/identity",
3
- "version": "0.115.0",
3
+ "version": "0.117.0",
4
4
  "description": "Identidad y rating de usuarios compartidos entre apps de Dotrino (vault iframe + postMessage)",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -20,6 +20,9 @@
20
20
  "./avatar": {
21
21
  "import": "./vault/avatar.js"
22
22
  },
23
+ "./facade": {
24
+ "import": "./vault/facade.js"
25
+ },
23
26
  "./vault/core.js": "./vault/core.js",
24
27
  "./vault/remote.js": "./vault/remote.js",
25
28
  "./machine": {
package/src/node.js CHANGED
@@ -19,6 +19,7 @@ import fs from 'node:fs'
19
19
  import path from 'node:path'
20
20
  import os from 'node:os'
21
21
  import { createIdentityCore } from '../vault/core.js'
22
+ import { IdentityFacade } from '../vault/facade.js'
22
23
 
23
24
  /**
24
25
  * kv síncrono respaldado por un archivo JSON (estilo localStorage).
@@ -93,21 +94,20 @@ function filePeers (filePath) {
93
94
 
94
95
  const DEFAULT_DIR = path.join(os.homedir(), '.dotrino', 'identity')
95
96
 
96
- export class Identity {
97
+ export class Identity extends IdentityFacade {
97
98
  /**
98
99
  * @param {Object} [options]
99
100
  * @param {string} [options.dir] Directorio de persistencia de esta identidad.
100
101
  * Cada directorio = un usuario distinto. Default: ~/.dotrino/identity
101
102
  */
102
103
  constructor (options = {}) {
104
+ super(null)
103
105
  this._dir = options.dir || DEFAULT_DIR
104
106
  this._atRest = options.atRest || null
105
107
  // CANDADO DE LA MAESTRA: `{ seal(jwkStr), open(blob) }`. Quien lo provee (el daemon
106
108
  // del vault) es el único que tiene la llave que sale de la contraseña. Sin candado,
107
109
  // la maestra se guarda como siempre — un perfil sin contraseña no cambia.
108
110
  this._keyLock = options.keyLock || null
109
- this._core = null
110
- this._listeners = new Map()
111
111
  }
112
112
 
113
113
  /**
@@ -133,186 +133,10 @@ export class Identity {
133
133
  // apuntando a algo que ya no existe. Se borra el enlace y el acta, nada más.
134
134
  removeAccountOnExpulsion: false
135
135
  })
136
- this._core.onSyncStatus((payload) => this._emit('sync', payload))
137
- this._core.onVaultEvent((payload) => this._emit('vault', payload))
136
+ this._wire()
138
137
  return this
139
138
  }
140
139
 
141
- destroy () { this._core = null }
142
-
143
- get me () { return this._core?.me || null }
144
-
145
- // ----- candado de la maestra -----
146
- /** `true` si la maestra está sellada: esta identidad NO puede firmar nada. */
147
- get masterLocked () { return !!this._core?.masterLocked }
148
- /** Sella la maestra que ya existe (se llama al abrir el perfil, con la llave en la mano). */
149
- sealMasterKey () { return this._core?.sealMasterKey?.() }
150
- /** Vuelve a cargar el par tras abrir el candado, sin reabrir la identidad. */
151
- reloadMasterKey () { return this._core?.reloadMasterKey?.() }
152
- /** Ver `core.js`: al abrir, los aparatos muertos salen del acta en una sola. */
153
- pruneExpiredDevices (now) { return this._core?.pruneExpiredDevices?.(now) }
154
-
155
- _h (method, params = {}) {
156
- if (!this._core) throw new Error('Identity not ready — call ready()/connect() first')
157
- return this._core.handlers[method](params)
158
- }
159
-
160
- // ----- API pública (espeja src/index.js) -----
161
-
162
- makeChallenge () { return this._h('makeChallenge') }
163
- signChallenge (nonce) { return this._h('signChallenge', { nonce }) }
164
- verifyResponse ({ nonce, publickey, signature, encryptionPubkey }) {
165
- return this._h('verifyResponse', { nonce, publickey, signature, encryptionPubkey })
166
- }
167
- getPeer (publickey) { return this._h('getPeer', { publickey }) }
168
- setNickname (publickey, nickname) { return this._h('setNickname', { publickey, nickname }) }
169
- setBlocked (publickey, blocked) { return this._h('setBlocked', { publickey, blocked }) }
170
- setRating (publickey, rating, notes) { return this._h('setRating', { publickey, rating, notes }) }
171
- listPeers () { return this._h('listPeers') }
172
- forgetPeer (publickey) { return this._h('forgetPeer', { publickey }) }
173
- addContact ({ publickey, nickname, encryptionPubkey, lastToken, notes } = {}) {
174
- return this._h('addContact', { publickey, nickname, encryptionPubkey, lastToken, notes })
175
- }
176
- updateContact (publickey, patch) { return this._h('updateContact', { publickey, patch }) }
177
- removeContact (publickey) { return this._h('removeContact', { publickey }) }
178
- listContacts () { return this._h('listContacts') }
179
- signData (data) { return this._h('signData', { data }) }
180
- /**
181
- * Prueba firmada con destinatario y vigencia (ver `vault/assertion.js`). Un servicio en
182
- * Node la pide igual que una app: `{ audience, nonce, scopes?, ttlMs? }`.
183
- */
184
- async requestAssertion ({ audience, nonce, scopes, ttlMs } = {}) {
185
- const { assertion } = await this._h('requestAssertion', { audience, nonce, scopes, ttlMs })
186
- return assertion
187
- }
188
- // Delegación de capacidad (sub-clave de dispositivo con scope/exp/revocación)
189
- signDelegation (sub, scope, opts = {}) { return this._h('signDelegation', { sub, scope, ...opts }) }
190
- revokeDelegation (nonce) { return this._h('revokeDelegation', { nonce }) }
191
- /** Quitar el DISPOSITIVO: retira todos sus certificados vigentes, no solo uno. */
192
- revokeDevice (sub) { return this._h('revokeDevice', { sub }) }
193
- listDelegations () { return this._h('listDelegations') }
194
- // Acta de perfil (qué llaves son del perfil y qué puede cada una; ver acta-de-perfil.md)
195
- profileActa () { return this._h('profileActa') }
196
- /** La cadena de selladores, para mandarla con una firma (ver `vault/acta.js`). */
197
- sealerChain () { return this._h('sealerChain') }
198
- profileMembers () { return this._h('profileMembers') }
199
- /**
200
- * Quien SELLA SOBRES (la bóveda) registra aquí cómo estrenar su llave de sellado: se
201
- * llama al sellar cada acta, porque esa llave rota con el acta (§8.9 de
202
- * `dotrino-vault/docs/secretos-sellados.md`). Va por aquí y no por el iframe porque
203
- * este puente es en-proceso: al navegador no se le puede pasar una función, y tampoco
204
- * la necesita — no sella secretos de nadie.
205
- */
206
- /** Abre un sobre sellado A ESTE aparato (envoltura + sobre). Ver `core.js`. */
207
- openSealedValue (wrap, envelope) { return this._h('openSealedValue', { wrap, envelope }) }
208
- /** Re-envuelve para otro miembro una llave que este aparato ya puede abrir. */
209
- rewrapFor (wrap, encPub) { return this._h('rewrapFor', { wrap, encPub }) }
210
- setSealKeyProvider (provider) { return this._h('setSealKeyProvider', { provider }) }
211
- /** Estrena la llave de sellado en un acta nueva, sin tocar a ningún miembro. */
212
- rotateSealKey () { return this._h('rotateSealKey') }
213
- /** Con qué llave se comprueban los sobres que se firmaron con el acta `seq`. */
214
- sealKeyAt (seq) { return this._h('sealKeyAt', { seq }) }
215
- myMembership () { return this._h('myMembership') }
216
- /**
217
- * La CADENA de actas desde `sinceSeq` (para que un dispositivo que estuvo apagado
218
- * compruebe el encadenamiento en vez de tragarse un salto a ciegas).
219
- *
220
- * Vivía en el núcleo pero NO estaba aquí, así que `identity.actaHistory` era
221
- * `undefined` en el daemon: al pedir la lista de dispositivos con un `sinceSeq`
222
- * —que es lo que manda SIEMPRE un navegador ya emparejado— reventaba con
223
- * «is not a function», la bóveda respondía error, y el aparato NUNCA volvía a
224
- * sincronizar su acta. Se quedaba con la copia del día que emparejó.
225
- */
226
- actaHistory (opts) { return this._h('actaHistory', opts || {}) }
227
- isMaster () { return this._h('isMaster') }
228
- admitMember (member) { return this._h('admitMember', member) }
229
- /** Registra la llave de cifrado de un miembro ya admitido (evita re-enrolarlo). */
230
- setMemberEncPub (args) { return this._h('setMemberEncPub', args) }
231
- setCaps (pub, caps) { return this._h('setCaps', { pub, caps }) }
232
- setCapsMany (changes) { return this._h('setCapsMany', { changes }) }
233
- setLabel (pub, label) { return this._h('setLabel', { pub, label }) }
234
- removeMember (pub) { return this._h('removeMember', { pub }) }
235
- handoverMaster (to, member = null) { return this._h('handoverMaster', { to, member }) }
236
- renounceCaps (caps) { return this._h('renounceCaps', { caps }) }
237
- absorbRenounce (record) { return this._h('absorbRenounce', { record }) }
238
- adoptActa (acta) { return this._h('adoptActa', { acta }) }
239
- /** Une ESTE perfil a la cuenta de otro (solo si nació para eso: `createProfile(n, { forVault: true })`). */
240
- joinProfile (acta) { return this._h('joinProfile', { acta }) }
241
- /** Marca este perfil como nacido para ADOPTAR la cuenta de otro (camino A: lo usa la bóveda). */
242
- prepareForAdoption () { return this._h('prepareForAdoption') }
243
- /** MI tarjeta de perfil: lo mínimo que un contacto necesita para cifrarme a todos mis
244
- * dispositivos (perfil, versión y llaves). Sin etiquetas ni permisos. */
245
- profileCard () { return this._h('profileCard') }
246
- /** Guarda la tarjeta de otra persona en su ficha de contacto (verificándola). */
247
- adoptPeerCard (card) { return this._h('adoptPeerCard', { card }) }
248
- /** La clave de contenido del perfil, abierta con la llave de cifrado de este dispositivo. */
249
- contentKey () { return this._h('contentKey') }
250
- /** Cifra con la clave de contenido del perfil (la privada de cifrado no sale del vault). */
251
- sealContent (plaintext) { return this._h('sealContent', { plaintext }) }
252
- /** Abre un sobre de contenido con el llavero del perfil. */
253
- openContent (envelope) { return this._h('openContent', { envelope }) }
254
- /** Rota la clave de contenido (corta el acceso al contenido FUTURO de quien ya no está). */
255
- rotateContentKey () { return this._h('rotateContentKey') }
256
- // Emparejar ESTE dispositivo con el vault del usuario (Fase 1)
257
- enrollDevice (qr, { label = '', join = 'current' } = {}) { return this._h('vaultPair', { qr, label, join }) }
258
- vaultStatus () { return this._h('vaultStatus') }
259
- unpairDevice () { return this._h('vaultUnpair') }
260
- vaultSign (payload) { return this._h('vaultSign', { payload }) }
261
- vaultStore (method, args) { return this._h('vaultStore', { method, args }) }
262
- listVaultDevices () { return this._h('listVaultDevices') }
263
- getVaultCert () { return this._h('getVaultCert') }
264
- vaultApprovals (op, args) { return this._h('vaultApprovals', { op, ...(args || {}) }) }
265
- /** Los pedidos de TODAS las cuentas que aprueban, sin cambiar la activa. */
266
- vaultApprovalsAll (args) { return this._h('vaultApprovalsAll', args || {}) }
267
- onVault (handler) { return this.on('vault', handler) }
268
- // Multi-perfil por dispositivo (crear/cambiar reinicializa con el nuevo perfil activo).
269
- listProfiles () { return this._h('listProfiles') }
270
- currentProfile () { return this._h('currentProfile') }
271
- createProfile (name, { forVault = false } = {}) { return this._h('createProfile', { name, forVault }) }
272
- switchProfile (id) { return this._h('switchProfile', { id }) }
273
- renameProfile (id, name) { return this._h('renameProfile', { id, name }) }
274
- deleteProfile (id) { return this._h('deleteProfile', { id }) }
275
- mergeEndorsements (subject, endorsements, askerPubkey) {
276
- return this._h('mergeEndorsements', { subject, endorsements, askerPubkey })
277
- }
278
- getRatingsForSubject (subject) { return this._h('getRatingsForSubject', { subject }) }
279
- recordQuery (askerPubkey, subject) { return this._h('recordQuery', { askerPubkey, subject }) }
280
- async setMyNickname (nickname) {
281
- const result = await this._h('setMyNickname', { nickname })
282
- return result
283
- }
284
- async updateMe (patch) { return this._h('updateMe', { patch }) }
285
- getMe () { return this._h('getMe') }
286
- publicMe () { return this._h('publicMe') }
287
- /** Cómo fue el último empujón del perfil a la bóveda: `{ ok, at, error }`. */
288
- profilePushState () { return this._h('profilePushState') }
289
- getEncryptionPubkey () { return this._h('getEncryptionPubkey') }
290
- encrypt (recipients, plaintext) { return this._h('encrypt', { recipients, plaintext }) }
291
- decrypt (senderEncryptionPubkey, myToken, envelope) {
292
- return this._h('decrypt', { senderEncryptionPubkey, myToken, envelope })
293
- }
294
- exportIdentity () { return this._h('exportIdentity') }
295
- async importIdentity (blob) { return this._h('importIdentity', blob || {}) }
296
-
297
- // Sync (Google Drive) no disponible headless — los handlers responden acorde.
298
- syncConnect (clientId) { return this._h('syncConnect', { clientId }) }
299
- syncDisconnect () { return this._h('syncDisconnect') }
300
- syncUnlock (passphrase) { return this._h('syncUnlock', { passphrase }) }
301
- syncLock () { return this._h('syncLock') }
302
- syncStatus () { return this._h('syncStatus') }
303
- syncNow () { return this._h('syncNow') }
304
-
305
- onSync (handler) { return this.on('sync', handler) }
306
- on (event, handler) {
307
- if (!this._listeners.has(event)) this._listeners.set(event, new Set())
308
- this._listeners.get(event).add(handler)
309
- return () => this._listeners.get(event)?.delete(handler)
310
- }
311
- _emit (event, payload) {
312
- const set = this._listeners.get(event)
313
- if (!set) return
314
- for (const h of set) { try { h(payload) } catch (e) { console.error(e) } }
315
- }
316
140
  }
317
141
 
318
142
  export default Identity
package/vault/content.js CHANGED
@@ -133,6 +133,33 @@ export async function encryptWithCek ({ cek, gen, plaintext }) {
133
133
  return { gen, iv: b64(iv), ct: b64(ct) }
134
134
  }
135
135
 
136
+ /**
137
+ * Cifra BYTES con una clave simétrica, y devuelve bytes: `{ iv, ct }` como `Uint8Array`.
138
+ *
139
+ * Es la hermana binaria de `encryptWithCek`, para lo que no tiene por qué pasar por texto:
140
+ * un archivo de 5 MB cifrado y en base64 ocupa 6,7 MB, y si además iba dentro de un JSON en
141
+ * base64, 8,9 MB. Así viaja con lo que ocupa. Lo usa el transporte para los mensajes con
142
+ * archivos: el archivo se cifra con una CEK nueva y solo la CEK pasa por el sellado de
143
+ * siempre (`wrapForMember`, o la bóveda).
144
+ *
145
+ * @param {{ cek: string, bytes: Uint8Array }} o
146
+ * @returns {Promise<{ iv: Uint8Array, ct: Uint8Array }>}
147
+ */
148
+ export async function encryptBytesWithCek ({ cek, bytes }) {
149
+ if (!(bytes instanceof Uint8Array)) throw new Error('encryptBytesWithCek: bytes must be a Uint8Array')
150
+ const k = await subtle.importKey('raw', fromB64(cek), { name: 'AES-GCM' }, false, ['encrypt'])
151
+ const iv = globalThis.crypto.getRandomValues(new Uint8Array(12))
152
+ const ct = new Uint8Array(await subtle.encrypt({ name: 'AES-GCM', iv }, k, bytes))
153
+ return { iv, ct }
154
+ }
155
+
156
+ /** El inverso de `encryptBytesWithCek`. Lanza si los bytes no son de esa clave o se tocaron. */
157
+ export async function decryptBytesWithCek ({ cek, iv, ct }) {
158
+ if (!(iv instanceof Uint8Array) || !(ct instanceof Uint8Array)) throw new Error('decryptBytesWithCek: iv and ct must be Uint8Array')
159
+ const k = await subtle.importKey('raw', fromB64(cek), { name: 'AES-GCM' }, false, ['decrypt'])
160
+ return new Uint8Array(await subtle.decrypt({ name: 'AES-GCM', iv }, k, ct))
161
+ }
162
+
136
163
  /** Cierra con la LLAVE DE LA CUENTA (generación `gen`): el sobre lleva la marca. */
137
164
  export async function sealAccount ({ cek, gen, plaintext }) {
138
165
  if (!Number.isInteger(gen) || gen < 1) throw new Error('sealAccount: the account key generation starts at 1')
@@ -243,5 +270,5 @@ export async function resealStale (value, { gen, reseal, now = Date.now() }) {
243
270
 
244
271
  export default {
245
272
  makeContentKey, wrapForMember, openWrap, makeGeneration, myContentKey,
246
- encryptWithCek, sealAccount, decryptWithCek, decryptWithKeyring, CEK_ENVELOPE, isCekEnvelope, isLegacyCekEnvelope, LEGACY_UNMARKED_UNTIL, resealStale
273
+ encryptWithCek, encryptBytesWithCek, decryptBytesWithCek, sealAccount, decryptWithCek, decryptWithKeyring, CEK_ENVELOPE, isCekEnvelope, isLegacyCekEnvelope, LEGACY_UNMARKED_UNTIL, resealStale
247
274
  }
package/vault/core.js CHANGED
@@ -24,7 +24,7 @@ import * as Content from './content.js'
24
24
  import { syncPeers, stampOf } from './peerSync.js'
25
25
  import { assertionBody, cleanScopes, claimsAllowed, ASSERTION_DEFAULT_TTL_MS, ASSERTION_MAX_TTL_MS } from './assertion.js'
26
26
  import { pubkeyId as pubkeyIdOf, signWithDevice } from './capabilities.js'
27
- import { enrollDevice as remoteEnroll, requestSign as remoteSign, requestStore as remoteStore, requestDevices as remoteDevices, requestRenew as remoteRenew, requestAdmin as remoteAdmin, requestApproval as remoteApproval, requestRenounce as remoteRenounce, checkMembership as remoteCheck } from './remote.js'
27
+ import { enrollDevice as remoteEnroll, requestSign as remoteSign, requestStore as remoteStore, requestDevices as remoteDevices, requestRenew as remoteRenew, requestAdmin as remoteAdmin, requestApproval as remoteApproval, requestRenounce as remoteRenounce, checkMembership as remoteCheck, registerLocalVault } from './remote.js'
28
28
 
29
29
  export const KEY_STORAGE = 'dotrino.identity.keypair'
30
30
  export const ENC_KEY_STORAGE = 'dotrino.identity.enc-keypair'
@@ -791,6 +791,33 @@ export async function createIdentityCore ({ kv: hostKv, peers, makeSync = null,
791
791
  const encKeyId = async (encPub) => (await pubkeyIdOf(encPub)).slice(0, 16)
792
792
 
793
793
  const loadVaultCert = () => { try { return JSON.parse(kv.getItem(VAULT_CERT_STORAGE) || 'null') } catch (_) { return null } }
794
+
795
+ /**
796
+ * LA BÓVEDA QUE CORRE AQUÍ MISMO. Cuando esta pestaña o esta extensión ES la bóveda de la
797
+ * cuenta (`startBrowserVault` de `@dotrino/vault`), administrarla y aprobar sus pedidos se
798
+ * hace con el MISMO protocolo que contra el binario, firmado con la llave del perfil y con
799
+ * el papel que la bóveda se da a sí misma; el viaje lo hace el camino local de
800
+ * `remote.js`, sin proxio.
801
+ *
802
+ * Vive en memoria y NO se guarda como emparejamiento: este navegador no tiene «una bóveda
803
+ * aparte», y fingirlo haría creer, por ejemplo, que lo suyo está respaldado en otro sitio.
804
+ * Lo pone quien levanta la bóveda (`setSelfVault`) y lo quita al pararla.
805
+ */
806
+ let selfLink = null
807
+ let selfLinkOff = null
808
+ /** El enlace con el que administrar: el emparejamiento si lo hay, y si no, la bóveda local. */
809
+ const adminLink = () => {
810
+ const v = loadVaultCert(); const device = loadVaultDevice()
811
+ if (v?.cert && device) return { v, device, self: false }
812
+ if (selfLink && publickeyJwkStr && masterKey()) {
813
+ return {
814
+ v: { cert: selfLink.cert, master: publickeyJwkStr, proxy: selfLink.proxy },
815
+ device: { publickey: publickeyJwkStr, privateKey: masterKey(), ...(pageMachine ? { machine: pageMachine } : {}) },
816
+ self: true
817
+ }
818
+ }
819
+ return null
820
+ }
794
821
  // DÓNDE CORRE ESTE APARATO (`machine.js`). Lo dice la PÁGINA al conectar (`setMachine`),
795
822
  // porque el iframe no ve si está dentro de una extensión o de la app nativa, y viaja con
796
823
  // cada pedido firmado a la bóveda para que su lista lo enseñe al día. Sin página que lo
@@ -3097,8 +3124,10 @@ export async function createIdentityCore ({ kv: hostKv, peers, makeSync = null,
3097
3124
  * master, en su máquina.
3098
3125
  */
3099
3126
  async vaultAdmin ({ op, ...rest } = {}) {
3100
- const v = loadVaultCert(); const device = loadVaultDevice()
3101
- if (!v?.cert || !device) throw new Error('this device is not paired with a vault')
3127
+ const link = adminLink()
3128
+ if (!link) throw new Error('this device is not paired with a vault')
3129
+ const { v, device } = link
3130
+ if (link.self) return remoteAdmin({ master: v.master, proxy: v.proxy, device, cert: v.cert, op, ...rest })
3102
3131
  maybeRenewVaultCert()
3103
3132
  try { return await remoteAdmin({ master: v.master, proxy: v.proxy, device, cert: v.cert, op, ...rest, onRevoked: wipeVaultLink }) }
3104
3133
  catch (e) { return handleVaultError(e) }
@@ -3114,8 +3143,14 @@ export async function createIdentityCore ({ kv: hostKv, peers, makeSync = null,
3114
3143
  */
3115
3144
  async vaultApprovals ({ op, id, profile = null, notify } = {}) {
3116
3145
  if (profile && profile !== currentPid) return approvalsDeOtroPerfil(profile, { op, id })
3117
- const v = loadVaultCert(); const device = loadVaultDevice()
3118
- if (!v?.cert || !device) throw new Error('this device is not paired with a vault')
3146
+ const link = adminLink()
3147
+ if (!link) throw new Error('this device is not paired with a vault')
3148
+ const { v, device } = link
3149
+ if (link.self) {
3150
+ const r = await remoteApproval({ master: v.master, proxy: v.proxy, device, cert: v.cert, op, id, notify })
3151
+ if (Array.isArray(r?.items)) return { ...r, items: await abrirContextos(r.items, currentPid) }
3152
+ return r
3153
+ }
3119
3154
  maybeRenewVaultCert()
3120
3155
  const ask = (cert) => remoteApproval({ master: v.master, proxy: v.proxy, device, cert, op, id, notify, onRevoked: wipeVaultLink })
3121
3156
  try {
@@ -3164,12 +3199,14 @@ export async function createIdentityCore ({ kv: hostKv, peers, makeSync = null,
3164
3199
  try { maybeRenewVaultCert() } catch (_) {}
3165
3200
  const perfiles = loadProfiles()
3166
3201
  const salida = await Promise.all(perfiles.map(async (p) => {
3167
- const v = p.id === currentPid ? loadVaultCert() : vaultCertOf(p.id)
3202
+ // La activa: su emparejamiento, o la bóveda que corre aquí mismo si no tiene otro.
3203
+ const propia = p.id === currentPid && !loadVaultCert() ? adminLink() : null
3204
+ const v = p.id === currentPid ? (loadVaultCert() || propia?.v || null) : vaultCertOf(p.id)
3168
3205
  if (!v?.cert) return null // sin bóveda: no es asunto suyo
3169
3206
  if (!(v.cert.scope || []).includes('vault:approve')) return null // no aprueba: tampoco
3170
3207
  const base = { profile: p.id, name: p.name || '', current: p.id === currentPid }
3171
3208
  let device = null
3172
- try { device = p.id === currentPid ? loadVaultDevice() : await signerForProfile(p.id) } catch (e) {
3209
+ try { device = p.id === currentPid ? (propia?.device || loadVaultDevice()) : await signerForProfile(p.id) } catch (e) {
3173
3210
  return { ...base, items: [], error: e?.code || 'no-key' }
3174
3211
  }
3175
3212
  if (!device) return { ...base, items: [], error: 'no-key' }
@@ -3210,6 +3247,7 @@ export async function createIdentityCore ({ kv: hostKv, peers, makeSync = null,
3210
3247
 
3211
3248
  /** ¿Puede ESTE dispositivo aprobar pedidos? Mismo criterio que `canAdminVault`. */
3212
3249
  async canApproveVault () {
3250
+ if (!loadVaultCert() && selfLink) return (adminLink()?.v.cert.scope || []).includes('vault:approve')
3213
3251
  if (certDesfasadoDelActa()) { try { await renovarCert() } catch (_) {} }
3214
3252
  const v = loadVaultCert()
3215
3253
  return !!v?.cert && (v.cert.scope || []).includes('vault:approve')
@@ -3225,6 +3263,7 @@ export async function createIdentityCore ({ kv: hostKv, peers, makeSync = null,
3225
3263
  // renovarlo aquí mismo. Disparar la renovación y contestar «no» dejaba la consola
3226
3264
  // escondida hasta la siguiente visita, y el dueño —que acababa de dar el permiso— no
3227
3265
  // tenía forma de saber que solo faltaba recargar.
3266
+ if (!loadVaultCert() && selfLink) return (adminLink()?.v.cert.scope || []).includes('vault:admin')
3228
3267
  if (certDesfasadoDelActa()) { try { await renovarCert() } catch (_) {} }
3229
3268
  const v = loadVaultCert()
3230
3269
  return !!v?.cert && (v.cert.scope || []).includes('vault:admin')
@@ -3233,6 +3272,13 @@ export async function createIdentityCore ({ kv: hostKv, peers, makeSync = null,
3233
3272
  // Lista (solo lectura) de dispositivos enrolados en tu vault.
3234
3273
  async listVaultDevices () {
3235
3274
  const v = loadVaultCert(); const device = loadVaultDevice()
3275
+ // ESTE NAVEGADOR ES LA BÓVEDA: la lista sale de ella, por el camino local, con lo mismo
3276
+ // que da el binario (qué versión corre cada aparato, dónde corre, si le llegan avisos).
3277
+ // El acta es la nuestra: no hay nada que adoptar.
3278
+ if ((!v?.cert || !device) && adminLink()?.self) {
3279
+ const self = adminLink()
3280
+ return remoteDevices({ master: self.v.master, proxy: self.v.proxy, device: self.device, cert: self.v.cert, sinceSeq: loadActa()?.seq ?? 0 })
3281
+ }
3236
3282
  if (!v?.cert || !device) {
3237
3283
  // SOLO SE AVISA SI ES RARO. Un aparato que manda en su propia cuenta —o que aún no
3238
3284
  // entró en ninguna bóveda— no está paired y no pasa absolutamente nada: avisar ahí
@@ -3640,6 +3686,19 @@ export async function createIdentityCore ({ kv: hostKv, peers, makeSync = null,
3640
3686
  * `handlers`) porque ninguna aplicación tiene por qué poder instalar una identidad.
3641
3687
  */
3642
3688
  adoptLogin,
3689
+ /**
3690
+ * La bóveda que corre aquí mismo se presenta (`startBrowserVault`): `{ cert, proxy, open }`
3691
+ * —`open` es su canal local, ver `registerLocalVault` en `remote.js`—, o `null` al
3692
+ * pararse (ver `adminLink`). En el núcleo y no en `handlers`: ninguna
3693
+ * aplicación tiene por qué poder decir dónde está la bóveda de esta cuenta.
3694
+ */
3695
+ setSelfVault (self = null) {
3696
+ if (self && (!self.cert || typeof self.proxy !== 'string' || typeof self.open !== 'function')) throw new Error('setSelfVault: cert, proxy and open are required')
3697
+ try { selfLinkOff?.() } catch (_) {}
3698
+ selfLinkOff = null
3699
+ selfLink = self ? { cert: self.cert, proxy: self.proxy } : null
3700
+ if (self && publickeyJwkStr) selfLinkOff = registerLocalVault(publickeyJwkStr, self.open)
3701
+ },
3643
3702
  onSyncStatus (fn) { if (sync) sync.onStatus(fn) },
3644
3703
  onVaultEvent (fn) { vaultListeners.add(fn); return () => vaultListeners.delete(fn) }
3645
3704
  }
@@ -0,0 +1,200 @@
1
+ /**
2
+ * LA IDENTIDAD COMO OBJETO, sobre un core ya creado (`createIdentityCore`).
3
+ *
4
+ * Es la misma API que usa el binario (`@dotrino/identity/node`, que la extiende creando el
5
+ * core sobre archivos) y la que necesita la bóveda del navegador: allí el core ya lo creó el
6
+ * iframe o la extensión, y la bóveda lo envuelve con esto. Una sola envoltura, para que la
7
+ * bóveda llame a lo mismo en las tres versiones.
8
+ */
9
+ export class IdentityFacade {
10
+ /** @param {object} [core] el resultado de `createIdentityCore` */
11
+ constructor (core = null) {
12
+ this._core = core
13
+ this._listeners = new Map()
14
+ if (core) this._wire()
15
+ }
16
+
17
+ /** Engancha los eventos del core a `on()`. Lo llama quien pone el core. */
18
+ _wire () {
19
+ this._core.onSyncStatus?.((payload) => this._emit('sync', payload))
20
+ this._core.onVaultEvent?.((payload) => this._emit('vault', payload))
21
+ }
22
+
23
+ destroy () { this._core = null }
24
+
25
+ get me () { return this._core?.me || null }
26
+
27
+ // ----- candado de la maestra -----
28
+ /** `true` si la maestra está sellada: esta identidad NO puede firmar nada. */
29
+ get masterLocked () { return !!this._core?.masterLocked }
30
+ /** Sella la maestra que ya existe (se llama al abrir el perfil, con la llave en la mano). */
31
+ sealMasterKey () { return this._core?.sealMasterKey?.() }
32
+ /** Vuelve a cargar el par tras abrir el candado, sin reabrir la identidad. */
33
+ reloadMasterKey () { return this._core?.reloadMasterKey?.() }
34
+ /** Ver `core.js`: al abrir, los aparatos muertos salen del acta en una sola. */
35
+ pruneExpiredDevices (now) { return this._core?.pruneExpiredDevices?.(now) }
36
+
37
+ _h (method, params = {}) {
38
+ if (!this._core) throw new Error('Identity not ready — call ready()/connect() first')
39
+ return this._core.handlers[method](params)
40
+ }
41
+
42
+ // ----- API pública (espeja src/index.js) -----
43
+
44
+ makeChallenge () { return this._h('makeChallenge') }
45
+ signChallenge (nonce) { return this._h('signChallenge', { nonce }) }
46
+ verifyResponse ({ nonce, publickey, signature, encryptionPubkey }) {
47
+ return this._h('verifyResponse', { nonce, publickey, signature, encryptionPubkey })
48
+ }
49
+ getPeer (publickey) { return this._h('getPeer', { publickey }) }
50
+ setNickname (publickey, nickname) { return this._h('setNickname', { publickey, nickname }) }
51
+ setBlocked (publickey, blocked) { return this._h('setBlocked', { publickey, blocked }) }
52
+ setRating (publickey, rating, notes) { return this._h('setRating', { publickey, rating, notes }) }
53
+ listPeers () { return this._h('listPeers') }
54
+ forgetPeer (publickey) { return this._h('forgetPeer', { publickey }) }
55
+ addContact ({ publickey, nickname, encryptionPubkey, lastToken, notes } = {}) {
56
+ return this._h('addContact', { publickey, nickname, encryptionPubkey, lastToken, notes })
57
+ }
58
+ updateContact (publickey, patch) { return this._h('updateContact', { publickey, patch }) }
59
+ removeContact (publickey) { return this._h('removeContact', { publickey }) }
60
+ listContacts () { return this._h('listContacts') }
61
+ signData (data) { return this._h('signData', { data }) }
62
+ /**
63
+ * Prueba firmada con destinatario y vigencia (ver `vault/assertion.js`). Un servicio en
64
+ * Node la pide igual que una app: `{ audience, nonce, scopes?, ttlMs? }`.
65
+ */
66
+ async requestAssertion ({ audience, nonce, scopes, ttlMs } = {}) {
67
+ const { assertion } = await this._h('requestAssertion', { audience, nonce, scopes, ttlMs })
68
+ return assertion
69
+ }
70
+ // Delegación de capacidad (sub-clave de dispositivo con scope/exp/revocación)
71
+ signDelegation (sub, scope, opts = {}) { return this._h('signDelegation', { sub, scope, ...opts }) }
72
+ revokeDelegation (nonce) { return this._h('revokeDelegation', { nonce }) }
73
+ /** Quitar el DISPOSITIVO: retira todos sus certificados vigentes, no solo uno. */
74
+ revokeDevice (sub) { return this._h('revokeDevice', { sub }) }
75
+ listDelegations () { return this._h('listDelegations') }
76
+ // Acta de perfil (qué llaves son del perfil y qué puede cada una; ver acta-de-perfil.md)
77
+ profileActa () { return this._h('profileActa') }
78
+ /** La cadena de selladores, para mandarla con una firma (ver `vault/acta.js`). */
79
+ sealerChain () { return this._h('sealerChain') }
80
+ profileMembers () { return this._h('profileMembers') }
81
+ /**
82
+ * Quien SELLA SOBRES (la bóveda) registra aquí cómo estrenar su llave de sellado: se
83
+ * llama al sellar cada acta, porque esa llave rota con el acta (§8.9 de
84
+ * `dotrino-vault/docs/secretos-sellados.md`). Va por aquí y no por el iframe porque
85
+ * este puente es en-proceso: al navegador no se le puede pasar una función, y tampoco
86
+ * la necesita — no sella secretos de nadie.
87
+ */
88
+ /** Abre un sobre sellado A ESTE aparato (envoltura + sobre). Ver `core.js`. */
89
+ openSealedValue (wrap, envelope) { return this._h('openSealedValue', { wrap, envelope }) }
90
+ /** Re-envuelve para otro miembro una llave que este aparato ya puede abrir. */
91
+ rewrapFor (wrap, encPub) { return this._h('rewrapFor', { wrap, encPub }) }
92
+ setSealKeyProvider (provider) { return this._h('setSealKeyProvider', { provider }) }
93
+ /** Estrena la llave de sellado en un acta nueva, sin tocar a ningún miembro. */
94
+ rotateSealKey () { return this._h('rotateSealKey') }
95
+ /** Con qué llave se comprueban los sobres que se firmaron con el acta `seq`. */
96
+ sealKeyAt (seq) { return this._h('sealKeyAt', { seq }) }
97
+ myMembership () { return this._h('myMembership') }
98
+ /**
99
+ * La CADENA de actas desde `sinceSeq` (para que un dispositivo que estuvo apagado
100
+ * compruebe el encadenamiento en vez de tragarse un salto a ciegas).
101
+ *
102
+ * Vivía en el núcleo pero NO estaba aquí, así que `identity.actaHistory` era
103
+ * `undefined` en el daemon: al pedir la lista de dispositivos con un `sinceSeq`
104
+ * —que es lo que manda SIEMPRE un navegador ya emparejado— reventaba con
105
+ * «is not a function», la bóveda respondía error, y el aparato NUNCA volvía a
106
+ * sincronizar su acta. Se quedaba con la copia del día que emparejó.
107
+ */
108
+ actaHistory (opts) { return this._h('actaHistory', opts || {}) }
109
+ isMaster () { return this._h('isMaster') }
110
+ admitMember (member) { return this._h('admitMember', member) }
111
+ /** Registra la llave de cifrado de un miembro ya admitido (evita re-enrolarlo). */
112
+ setMemberEncPub (args) { return this._h('setMemberEncPub', args) }
113
+ setCaps (pub, caps) { return this._h('setCaps', { pub, caps }) }
114
+ setCapsMany (changes) { return this._h('setCapsMany', { changes }) }
115
+ setLabel (pub, label) { return this._h('setLabel', { pub, label }) }
116
+ removeMember (pub) { return this._h('removeMember', { pub }) }
117
+ handoverMaster (to, member = null) { return this._h('handoverMaster', { to, member }) }
118
+ renounceCaps (caps) { return this._h('renounceCaps', { caps }) }
119
+ absorbRenounce (record) { return this._h('absorbRenounce', { record }) }
120
+ adoptActa (acta) { return this._h('adoptActa', { acta }) }
121
+ /** Une ESTE perfil a la cuenta de otro (solo si nació para eso: `createProfile(n, { forVault: true })`). */
122
+ joinProfile (acta) { return this._h('joinProfile', { acta }) }
123
+ /** Marca este perfil como nacido para ADOPTAR la cuenta de otro (camino A: lo usa la bóveda). */
124
+ prepareForAdoption () { return this._h('prepareForAdoption') }
125
+ /** MI tarjeta de perfil: lo mínimo que un contacto necesita para cifrarme a todos mis
126
+ * dispositivos (perfil, versión y llaves). Sin etiquetas ni permisos. */
127
+ profileCard () { return this._h('profileCard') }
128
+ /** Guarda la tarjeta de otra persona en su ficha de contacto (verificándola). */
129
+ adoptPeerCard (card) { return this._h('adoptPeerCard', { card }) }
130
+ /** La clave de contenido del perfil, abierta con la llave de cifrado de este dispositivo. */
131
+ contentKey () { return this._h('contentKey') }
132
+ /** Cifra con la clave de contenido del perfil (la privada de cifrado no sale del vault). */
133
+ sealContent (plaintext) { return this._h('sealContent', { plaintext }) }
134
+ /** Abre un sobre de contenido con el llavero del perfil. */
135
+ openContent (envelope) { return this._h('openContent', { envelope }) }
136
+ /** Rota la clave de contenido (corta el acceso al contenido FUTURO de quien ya no está). */
137
+ rotateContentKey () { return this._h('rotateContentKey') }
138
+ // Emparejar ESTE dispositivo con el vault del usuario (Fase 1)
139
+ enrollDevice (qr, { label = '', join = 'current' } = {}) { return this._h('vaultPair', { qr, label, join }) }
140
+ vaultStatus () { return this._h('vaultStatus') }
141
+ unpairDevice () { return this._h('vaultUnpair') }
142
+ vaultSign (payload) { return this._h('vaultSign', { payload }) }
143
+ vaultStore (method, args) { return this._h('vaultStore', { method, args }) }
144
+ listVaultDevices () { return this._h('listVaultDevices') }
145
+ getVaultCert () { return this._h('getVaultCert') }
146
+ vaultApprovals (op, args) { return this._h('vaultApprovals', { op, ...(args || {}) }) }
147
+ /** Los pedidos de TODAS las cuentas que aprueban, sin cambiar la activa. */
148
+ vaultApprovalsAll (args) { return this._h('vaultApprovalsAll', args || {}) }
149
+ onVault (handler) { return this.on('vault', handler) }
150
+ // Multi-perfil por dispositivo (crear/cambiar reinicializa con el nuevo perfil activo).
151
+ listProfiles () { return this._h('listProfiles') }
152
+ currentProfile () { return this._h('currentProfile') }
153
+ createProfile (name, { forVault = false } = {}) { return this._h('createProfile', { name, forVault }) }
154
+ switchProfile (id) { return this._h('switchProfile', { id }) }
155
+ renameProfile (id, name) { return this._h('renameProfile', { id, name }) }
156
+ deleteProfile (id) { return this._h('deleteProfile', { id }) }
157
+ mergeEndorsements (subject, endorsements, askerPubkey) {
158
+ return this._h('mergeEndorsements', { subject, endorsements, askerPubkey })
159
+ }
160
+ getRatingsForSubject (subject) { return this._h('getRatingsForSubject', { subject }) }
161
+ recordQuery (askerPubkey, subject) { return this._h('recordQuery', { askerPubkey, subject }) }
162
+ async setMyNickname (nickname) {
163
+ const result = await this._h('setMyNickname', { nickname })
164
+ return result
165
+ }
166
+ async updateMe (patch) { return this._h('updateMe', { patch }) }
167
+ getMe () { return this._h('getMe') }
168
+ publicMe () { return this._h('publicMe') }
169
+ /** Cómo fue el último empujón del perfil a la bóveda: `{ ok, at, error }`. */
170
+ profilePushState () { return this._h('profilePushState') }
171
+ getEncryptionPubkey () { return this._h('getEncryptionPubkey') }
172
+ encrypt (recipients, plaintext) { return this._h('encrypt', { recipients, plaintext }) }
173
+ decrypt (senderEncryptionPubkey, myToken, envelope) {
174
+ return this._h('decrypt', { senderEncryptionPubkey, myToken, envelope })
175
+ }
176
+ exportIdentity () { return this._h('exportIdentity') }
177
+ async importIdentity (blob) { return this._h('importIdentity', blob || {}) }
178
+
179
+ // Sync (Google Drive) no disponible headless — los handlers responden acorde.
180
+ syncConnect (clientId) { return this._h('syncConnect', { clientId }) }
181
+ syncDisconnect () { return this._h('syncDisconnect') }
182
+ syncUnlock (passphrase) { return this._h('syncUnlock', { passphrase }) }
183
+ syncLock () { return this._h('syncLock') }
184
+ syncStatus () { return this._h('syncStatus') }
185
+ syncNow () { return this._h('syncNow') }
186
+
187
+ onSync (handler) { return this.on('sync', handler) }
188
+ on (event, handler) {
189
+ if (!this._listeners.has(event)) this._listeners.set(event, new Set())
190
+ this._listeners.get(event).add(handler)
191
+ return () => this._listeners.get(event)?.delete(handler)
192
+ }
193
+ _emit (event, payload) {
194
+ const set = this._listeners.get(event)
195
+ if (!set) return
196
+ for (const h of set) { try { h(payload) } catch (e) { console.error(e) } }
197
+ }
198
+ }
199
+
200
+ export default IdentityFacade
package/vault/index.html CHANGED
@@ -155,7 +155,17 @@
155
155
  <script type="importmap">
156
156
  { "imports": {
157
157
  "@dotrino/proxy-client": "./vendor/proxy-client/index.js",
158
+ "@dotrino/proxy-client/sealing": "./vendor/proxy-client/sealing.js",
158
159
  "@dotrino/vault": "./vendor/vault/index.js",
160
+ "@dotrino/vault/browser": "./vendor/vault/browser.js",
161
+ "@dotrino/compat": "./vendor/compat/index.js",
162
+ "@dotrino/compat/ranges": "./vendor/compat/ranges.js",
163
+ "@dotrino/roadmap": "./vendor/roadmap/index.js",
164
+ "@dotrino/passmanager": "./vendor/passmanager/index.js",
165
+ "@dotrino/passmanager/sealed": "./vendor/passmanager/sealed/index.js",
166
+ "@dotrino/store/core": "./vendor/store/core.js",
167
+ "@dotrino/identity/facade": "./facade.js",
168
+ "@dotrino/identity/machine": "./machine.js",
159
169
  "@dotrino/vault/password-logins": "./vendor/vault/passwordLogins.js",
160
170
  "@dotrino/vault/login-client": "./vendor/vault/loginClient.js",
161
171
  "@dotrino/opaque": "./vendor/opaque/src/index.js",