@dotrino/identity 0.114.1 → 0.116.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/package.json +7 -1
- package/src/index.js +9 -1
- package/src/node.js +4 -180
- package/vault/acta.js +5 -1
- package/vault/core.js +95 -15
- package/vault/facade.js +200 -0
- package/vault/index.html +10 -0
- package/vault/machine.js +95 -0
- package/vault/remote.js +54 -9
- package/vault/vault.js +29 -58
- package/vault/vendor/compat/VERSION.txt +3 -0
- package/vault/vendor/compat/advisory.js +141 -0
- package/vault/vendor/compat/index.js +188 -0
- package/vault/vendor/compat/ranges.js +93 -0
- package/vault/vendor/opaque/VERSION.txt +1 -1
- package/vault/vendor/passmanager/VERSION.txt +3 -0
- package/vault/vendor/passmanager/crypto.js +167 -0
- package/vault/vendor/passmanager/fields.js +183 -0
- package/vault/vendor/passmanager/generate.js +74 -0
- package/vault/vendor/passmanager/import.js +156 -0
- package/vault/vendor/passmanager/index.js +27 -0
- package/vault/vendor/passmanager/match.js +137 -0
- package/vault/vendor/passmanager/model.js +332 -0
- package/vault/vendor/passmanager/pubkey.js +33 -0
- package/vault/vendor/passmanager/sealed/convert.js +72 -0
- package/vault/vendor/passmanager/sealed/device.js +211 -0
- package/vault/vendor/passmanager/sealed/entry.js +209 -0
- package/vault/vendor/passmanager/sealed/index.js +20 -0
- package/vault/vendor/passmanager/sealed/keys.js +120 -0
- package/vault/vendor/passmanager/sealed/recovery.js +136 -0
- package/vault/vendor/passmanager/sealed/store.js +508 -0
- package/vault/vendor/passmanager/session-cache.js +60 -0
- package/vault/vendor/passmanager/totp.js +111 -0
- package/vault/vendor/passmanager/transport/protocol.js +83 -0
- package/vault/vendor/passmanager/transport/proxy.js +112 -0
- package/vault/vendor/passmanager/transport/sealed.js +74 -0
- package/vault/vendor/passmanager/vault/approval.js +63 -0
- package/vault/vendor/passmanager/vault/errors.js +34 -0
- package/vault/vendor/passmanager/vault/guard.js +114 -0
- package/vault/vendor/passmanager/vault/interface.js +34 -0
- package/vault/vendor/passmanager/vault/local.js +213 -0
- package/vault/vendor/passmanager/vault/remote.js +85 -0
- package/vault/vendor/passmanager/vault/responder.js +192 -0
- package/vault/vendor/passmanager/vault/sealed-local.js +199 -0
- package/vault/vendor/passmanager/vault/sealed-responder.js +159 -0
- package/vault/vendor/passmanager/vault/sealed.js +362 -0
- package/vault/vendor/passmanager/webauthn.js +221 -0
- package/vault/vendor/proxy-client/VERSION.txt +1 -1
- package/vault/vendor/proxy-client/client.js +36 -0
- package/vault/vendor/roadmap/VERSION.txt +3 -0
- package/vault/vendor/roadmap/index.js +108 -0
- package/vault/vendor/roadmap/manifests.js +797 -0
- package/vault/vendor/store/VERSION.txt +3 -0
- package/vault/vendor/store/core.js +309 -0
- package/vault/vendor/vault/VERSION.txt +4 -1
- package/vault/vendor/vault/admin.js +437 -0
- package/vault/vendor/vault/browser.js +167 -0
- package/vault/vendor/vault/core/approvals.js +204 -0
- package/vault/vendor/vault/core/blocked.js +68 -0
- package/vault/vendor/vault/core/browserDisk.js +109 -0
- package/vault/vendor/vault/core/commKey.js +68 -0
- package/vault/vendor/vault/core/disk.js +65 -0
- package/vault/vendor/vault/core/libVersion.js +8 -0
- package/vault/vendor/vault/core/machines.js +66 -0
- package/vault/vendor/vault/core/notices.js +71 -0
- package/vault/vendor/vault/core/notifiable.js +73 -0
- package/vault/vendor/vault/core/passwords.js +125 -0
- package/vault/vendor/vault/core/sealKey.js +77 -0
- package/vault/vendor/vault/core/sealer.js +172 -0
- package/vault/vendor/vault/core/sealers.js +159 -0
- package/vault/vendor/vault/core/secretsStore.js +1326 -0
- package/vault/vendor/vault/core/sha256.js +69 -0
- package/vault/vendor/vault/core/store.js +68 -0
- package/vault/vendor/vault/core/subacta.js +101 -0
- package/vault/vendor/vault/core/threadStore.js +273 -0
- package/vault/vendor/vault/core/transport.js +270 -0
- package/vault/vendor/vault/core/vault.js +4414 -0
- package/vault/vendor/vault/enroll.js +8 -4
- package/vault/vendor/vault/procContext.js +97 -0
- package/vault/vendor/vault/revocation.js +67 -0
- 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.
|
|
3
|
+
"version": "0.116.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,8 +20,14 @@
|
|
|
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",
|
|
28
|
+
"./machine": {
|
|
29
|
+
"import": "./vault/machine.js"
|
|
30
|
+
},
|
|
25
31
|
"./acta": {
|
|
26
32
|
"import": "./vault/acta.js"
|
|
27
33
|
},
|
package/src/index.js
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
* use this library share identity even across different origins.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import { browserMachine } from '../vault/machine.js'
|
|
11
|
+
|
|
10
12
|
const DEFAULT_VAULT_URL = 'https://id.dotrino.com/'
|
|
11
13
|
|
|
12
14
|
let singleton = null
|
|
@@ -125,6 +127,9 @@ export class Identity {
|
|
|
125
127
|
this._me = msg.me || null
|
|
126
128
|
this._locked = !!msg.locked
|
|
127
129
|
this._readyResolve(this)
|
|
130
|
+
// Dónde corre esta página (`vault/machine.js`), para los pedidos a la bóveda. Un
|
|
131
|
+
// iframe viejo no conoce el método: no pasa nada, solo no se manda.
|
|
132
|
+
this._call('setMachine', { machine: browserMachine() }).catch(() => {})
|
|
128
133
|
return
|
|
129
134
|
}
|
|
130
135
|
|
|
@@ -427,7 +432,9 @@ export class Identity {
|
|
|
427
432
|
* @returns {Promise<{ ok:boolean, deviceId:string, master:string, exp:number, scope:string[] }>}
|
|
428
433
|
*/
|
|
429
434
|
async enrollDevice (qr, { label = '', join = 'current' } = {}) {
|
|
430
|
-
|
|
435
|
+
// DÓNDE CORRE se mira AQUÍ, en la página, y no dentro del iframe: el iframe no ve si la
|
|
436
|
+
// página es una extensión o la app nativa (`vault/machine.js`).
|
|
437
|
+
return this._call('vaultPair', { qr, label, join, machine: browserMachine() }, 200000)
|
|
431
438
|
}
|
|
432
439
|
|
|
433
440
|
/** Estado de emparejamiento: { paired, deviceId?, master?, scope?, exp?, pairedAt? }. */
|
|
@@ -853,4 +860,5 @@ export { makeDeviceKey, makeDeviceEncKey, importDeviceEncKey, signWithDevice, ve
|
|
|
853
860
|
// PARA QUIÉN vale una firma, y hasta cuándo. Verificar NO necesita el iframe ni la clave
|
|
854
861
|
// de nadie, así que un servicio puede importarlo suelto (`@dotrino/identity/assertion`, que
|
|
855
862
|
// no arrastra el cliente del vault); aquí se reexporta para quien ya tiene esto cargado.
|
|
863
|
+
export { cleanMachine, machineLine, browserMachine, MACHINE_KINDS } from '../vault/machine.js'
|
|
856
864
|
export { verifyAssertion, verifySignedFor, newAssertionNonce, cleanScopes, claimsAllowed, assertionBody, SCOPES, SCOPE_CLAIMS, ASSERTION_MAX_TTL_MS, ASSERTION_DEFAULT_TTL_MS, ASSERTION_MAX_SKEW_MS } from '../vault/assertion.js'
|
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.
|
|
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/acta.js
CHANGED
|
@@ -36,6 +36,8 @@
|
|
|
36
36
|
*/
|
|
37
37
|
import { canonicalStringify } from './core.js'
|
|
38
38
|
import { signWithDevice, verifyDeviceSig, verifyDelegation, pubkeyId } from './capabilities.js'
|
|
39
|
+
import { cleanMachine } from './machine.js'
|
|
40
|
+
export { cleanMachine }
|
|
39
41
|
|
|
40
42
|
export const ACTA_V = 5
|
|
41
43
|
|
|
@@ -816,7 +818,9 @@ export async function applyChanges (acta, changes, { by, now = Date.now(), sealP
|
|
|
816
818
|
addedAt: now,
|
|
817
819
|
cert: m.cert || null,
|
|
818
820
|
// Puente con la identidad que este miembro traía de antes (ver makeContinuity).
|
|
819
|
-
...(m.continuity ? { continuity: m.continuity } : {})
|
|
821
|
+
...(m.continuity ? { continuity: m.continuity } : {}),
|
|
822
|
+
// DÓNDE CORRE (`machine.js`): lo que dijo al emparejar, para reconocerlo en la lista.
|
|
823
|
+
...(cleanMachine(m.machine) ? { machine: cleanMachine(m.machine) } : {})
|
|
820
824
|
})
|
|
821
825
|
break
|
|
822
826
|
}
|
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,7 +791,43 @@ 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
|
+
}
|
|
821
|
+
// DÓNDE CORRE ESTE APARATO (`machine.js`). Lo dice la PÁGINA al conectar (`setMachine`),
|
|
822
|
+
// porque el iframe no ve si está dentro de una extensión o de la app nativa, y viaja con
|
|
823
|
+
// cada pedido firmado a la bóveda para que su lista lo enseñe al día. Sin página que lo
|
|
824
|
+
// diga (Node, un cliente viejo) no se manda nada: mejor ningún dato que uno equivocado.
|
|
825
|
+
let pageMachine = null
|
|
794
826
|
const loadVaultDevice = () => {
|
|
827
|
+
const d = loadVaultDeviceKey()
|
|
828
|
+
return d && pageMachine ? { ...d, machine: pageMachine } : d
|
|
829
|
+
}
|
|
830
|
+
const loadVaultDeviceKey = () => {
|
|
795
831
|
try {
|
|
796
832
|
const d = JSON.parse(kv.getItem(VAULT_DEVICE_STORAGE) || 'null')
|
|
797
833
|
if (!d) return null
|
|
@@ -1293,7 +1329,7 @@ export async function createIdentityCore ({ kv: hostKv, peers, makeSync = null,
|
|
|
1293
1329
|
* cert contra la bóveda y entra a su cuenta. Aparte para que la decisión de CUÁL cuenta y
|
|
1294
1330
|
* el deshacerla si esto falla vivan juntos arriba, y aquí solo quede el trámite.
|
|
1295
1331
|
*/
|
|
1296
|
-
async function pairWithVault ({ qr, label = '' }) {
|
|
1332
|
+
async function pairWithVault ({ qr, label = '', machine = undefined }) {
|
|
1297
1333
|
// Usa la PROPIA llave de identidad de este navegador como dispositivo: el cert delega
|
|
1298
1334
|
// TU identidad (P) desde la maestra M → una sola identidad (signData/identify/cert = P).
|
|
1299
1335
|
// La privada es la CryptoKey del perfil (no extractable): se pasa como `privateKey`
|
|
@@ -1307,7 +1343,7 @@ export async function createIdentityCore ({ kv: hostKv, peers, makeSync = null,
|
|
|
1307
1343
|
const continuity = (mine && mine.members.length === 1 && !isPendingJoin())
|
|
1308
1344
|
? await Acta.makeContinuity({ member: publickeyJwkStr, from: mine.profileId, privateKey: masterKey() })
|
|
1309
1345
|
: null
|
|
1310
|
-
const res = await remoteEnroll({ qr, device, continuity, encPub: encPublickeyJwkStr, label: label || me?.nickname || '', onChallenge: (c) => emitVault({ phase: 'challenge', deviceId: c.deviceId, code: c.code }) })
|
|
1346
|
+
const res = await remoteEnroll({ qr, device, continuity, encPub: encPublickeyJwkStr, label: label || me?.nickname || '', machine: machine === undefined && pageMachine ? pageMachine : machine, onChallenge: (c) => emitVault({ phase: 'challenge', deviceId: c.deviceId, code: c.code }) })
|
|
1311
1347
|
kv.setItem(VAULT_DEVICE_STORAGE, JSON.stringify({ useIdentityKey: true, publickey: publickeyJwkStr }))
|
|
1312
1348
|
kv.setItem(VAULT_CERT_STORAGE, JSON.stringify({ cert: res.cert, master: res.master, proxy: res.proxy, deviceId: res.deviceId, pairedAt: Date.now() }))
|
|
1313
1349
|
// Conectarse a una bóveda es ENTRAR A SU CUENTA: el acta viene con el cert.
|
|
@@ -2560,6 +2596,8 @@ export async function createIdentityCore ({ kv: hostKv, peers, makeSync = null,
|
|
|
2560
2596
|
isService: !!m.cn,
|
|
2561
2597
|
caps: Acta.effectiveCaps(acta, m.pub, pend),
|
|
2562
2598
|
addedAt: m.addedAt || null,
|
|
2599
|
+
// Dónde corre, según dijo al emparejarse (`machine.js`). `null` si entró antes.
|
|
2600
|
+
machine: m.machine || null,
|
|
2563
2601
|
isMe: m.pub === publickeyJwkStr,
|
|
2564
2602
|
isMaster: Acta.canSeal(acta, m.pub),
|
|
2565
2603
|
// La llave de CIFRADO del miembro y, derivado, si se le puede envolver algo.
|
|
@@ -2605,7 +2643,7 @@ export async function createIdentityCore ({ kv: hostKv, peers, makeSync = null,
|
|
|
2605
2643
|
* Admite un miembro (solo el master). El cert lo emite quien llama, antes o después.
|
|
2606
2644
|
* `continuity`: si esa identidad ya existía por su cuenta, su puente firmado (F3).
|
|
2607
2645
|
*/
|
|
2608
|
-
async admitMember ({ pub, encPub = null, label = '', cn = null, caps = null, cert = null, continuity = null } = {}) {
|
|
2646
|
+
async admitMember ({ pub, encPub = null, label = '', cn = null, caps = null, cert = null, continuity = null, machine = null } = {}) {
|
|
2609
2647
|
// `cn`: si viene, este miembro es un SERVICIO: abre el cajón de secretos de ESE nombre
|
|
2610
2648
|
// por llevarlo, sin permiso aparte, y no ve nada más del usuario. Sin `cn` es un dispositivo.
|
|
2611
2649
|
const finales = caps || (cn ? [] : ['store', 'read'])
|
|
@@ -2622,7 +2660,7 @@ export async function createIdentityCore ({ kv: hostKv, peers, makeSync = null,
|
|
|
2622
2660
|
const mine = encPub ? await myCek() : null
|
|
2623
2661
|
if (mine) wrap = { op: 'wrap', gen: mine.gen, pub, wrap: await Content.wrapForMember({ cek: mine.cek, memberEncPub: encPub }) }
|
|
2624
2662
|
} catch (_) { wrap = null }
|
|
2625
|
-
const acta = await sealChanges([{ op: 'admit', member: { pub, encPub, label, cn, caps: finales, cert, continuity } }, ...(wrap ? [wrap] : [])])
|
|
2663
|
+
const acta = await sealChanges([{ op: 'admit', member: { pub, encPub, label, cn, caps: finales, cert, continuity, machine } }, ...(wrap ? [wrap] : [])])
|
|
2626
2664
|
return { ok: true, seq: acta.seq, wrapped: !!wrap }
|
|
2627
2665
|
},
|
|
2628
2666
|
|
|
@@ -2898,8 +2936,8 @@ export async function createIdentityCore ({ kv: hostKv, peers, makeSync = null,
|
|
|
2898
2936
|
* master: si esta cuenta ya la manda otra bóveda, no hay nada que
|
|
2899
2937
|
* regalar y falla en voz alta.
|
|
2900
2938
|
*/
|
|
2901
|
-
async vaultPair ({ qr, label = '', join = 'current' }) {
|
|
2902
|
-
if (join === 'adopt') return handlers.vaultAdopt({ qr, label })
|
|
2939
|
+
async vaultPair ({ qr, label = '', join = 'current', machine = undefined }) {
|
|
2940
|
+
if (join === 'adopt') return handlers.vaultAdopt({ qr, label, machine })
|
|
2903
2941
|
// La cuenta abierta AQUÍ y la que se cree para este intento. `born` es la que hay que
|
|
2904
2942
|
// tirar si el emparejamiento no llega a término: nació para él y no tiene nada dentro.
|
|
2905
2943
|
const from = currentPid
|
|
@@ -2929,7 +2967,7 @@ export async function createIdentityCore ({ kv: hostKv, peers, makeSync = null,
|
|
|
2929
2967
|
}
|
|
2930
2968
|
}
|
|
2931
2969
|
try {
|
|
2932
|
-
return await pairWithVault({ qr, label })
|
|
2970
|
+
return await pairWithVault({ qr, label, machine })
|
|
2933
2971
|
} catch (e) {
|
|
2934
2972
|
// El intento falló (código vencido, la bóveda dijo que no, se agotó la espera): la
|
|
2935
2973
|
// cuenta que nació para él se va con él. Si no, cada reintento dejaba una cuenta
|
|
@@ -2953,7 +2991,7 @@ export async function createIdentityCore ({ kv: hostKv, peers, makeSync = null,
|
|
|
2953
2991
|
* bóveda, este aparato no puede regalar lo que no tiene; el traspaso se hace desde la
|
|
2954
2992
|
* que manda hoy.
|
|
2955
2993
|
*/
|
|
2956
|
-
async vaultAdopt ({ qr, label = '' } = {}) {
|
|
2994
|
+
async vaultAdopt ({ qr, label = '', machine = undefined } = {}) {
|
|
2957
2995
|
const mine = loadActa()
|
|
2958
2996
|
if (!mine) throw new Error('this device has no account to hand over yet')
|
|
2959
2997
|
if (!amMaster()) throw new Error('not-the-master: another device or vault is in charge of this account; the handover is done from there')
|
|
@@ -2963,6 +3001,7 @@ export async function createIdentityCore ({ kv: hostKv, peers, makeSync = null,
|
|
|
2963
3001
|
qr,
|
|
2964
3002
|
device,
|
|
2965
3003
|
intent: 'adopt',
|
|
3004
|
+
machine,
|
|
2966
3005
|
profileId: mine.profileId,
|
|
2967
3006
|
encPub: encPublickeyJwkStr,
|
|
2968
3007
|
label: label || me?.nickname || '',
|
|
@@ -3009,6 +3048,15 @@ export async function createIdentityCore ({ kv: hostKv, peers, makeSync = null,
|
|
|
3009
3048
|
return { ok: true, pending: true }
|
|
3010
3049
|
},
|
|
3011
3050
|
|
|
3051
|
+
/**
|
|
3052
|
+
* La página dice dónde corre (`machine.js`); se manda con los pedidos a la bóveda.
|
|
3053
|
+
* @param {any} [args]
|
|
3054
|
+
*/
|
|
3055
|
+
async setMachine ({ machine } = {}) {
|
|
3056
|
+
pageMachine = Acta.cleanMachine(machine)
|
|
3057
|
+
return { ok: true }
|
|
3058
|
+
},
|
|
3059
|
+
|
|
3012
3060
|
async vaultStatus () {
|
|
3013
3061
|
const v = loadVaultCert()
|
|
3014
3062
|
if (!v?.cert) return { paired: false }
|
|
@@ -3076,8 +3124,10 @@ export async function createIdentityCore ({ kv: hostKv, peers, makeSync = null,
|
|
|
3076
3124
|
* master, en su máquina.
|
|
3077
3125
|
*/
|
|
3078
3126
|
async vaultAdmin ({ op, ...rest } = {}) {
|
|
3079
|
-
const
|
|
3080
|
-
if (!
|
|
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 })
|
|
3081
3131
|
maybeRenewVaultCert()
|
|
3082
3132
|
try { return await remoteAdmin({ master: v.master, proxy: v.proxy, device, cert: v.cert, op, ...rest, onRevoked: wipeVaultLink }) }
|
|
3083
3133
|
catch (e) { return handleVaultError(e) }
|
|
@@ -3093,8 +3143,14 @@ export async function createIdentityCore ({ kv: hostKv, peers, makeSync = null,
|
|
|
3093
3143
|
*/
|
|
3094
3144
|
async vaultApprovals ({ op, id, profile = null, notify } = {}) {
|
|
3095
3145
|
if (profile && profile !== currentPid) return approvalsDeOtroPerfil(profile, { op, id })
|
|
3096
|
-
const
|
|
3097
|
-
if (!
|
|
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
|
+
}
|
|
3098
3154
|
maybeRenewVaultCert()
|
|
3099
3155
|
const ask = (cert) => remoteApproval({ master: v.master, proxy: v.proxy, device, cert, op, id, notify, onRevoked: wipeVaultLink })
|
|
3100
3156
|
try {
|
|
@@ -3143,12 +3199,14 @@ export async function createIdentityCore ({ kv: hostKv, peers, makeSync = null,
|
|
|
3143
3199
|
try { maybeRenewVaultCert() } catch (_) {}
|
|
3144
3200
|
const perfiles = loadProfiles()
|
|
3145
3201
|
const salida = await Promise.all(perfiles.map(async (p) => {
|
|
3146
|
-
|
|
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)
|
|
3147
3205
|
if (!v?.cert) return null // sin bóveda: no es asunto suyo
|
|
3148
3206
|
if (!(v.cert.scope || []).includes('vault:approve')) return null // no aprueba: tampoco
|
|
3149
3207
|
const base = { profile: p.id, name: p.name || '', current: p.id === currentPid }
|
|
3150
3208
|
let device = null
|
|
3151
|
-
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) {
|
|
3152
3210
|
return { ...base, items: [], error: e?.code || 'no-key' }
|
|
3153
3211
|
}
|
|
3154
3212
|
if (!device) return { ...base, items: [], error: 'no-key' }
|
|
@@ -3189,6 +3247,7 @@ export async function createIdentityCore ({ kv: hostKv, peers, makeSync = null,
|
|
|
3189
3247
|
|
|
3190
3248
|
/** ¿Puede ESTE dispositivo aprobar pedidos? Mismo criterio que `canAdminVault`. */
|
|
3191
3249
|
async canApproveVault () {
|
|
3250
|
+
if (!loadVaultCert() && selfLink) return (adminLink()?.v.cert.scope || []).includes('vault:approve')
|
|
3192
3251
|
if (certDesfasadoDelActa()) { try { await renovarCert() } catch (_) {} }
|
|
3193
3252
|
const v = loadVaultCert()
|
|
3194
3253
|
return !!v?.cert && (v.cert.scope || []).includes('vault:approve')
|
|
@@ -3204,6 +3263,7 @@ export async function createIdentityCore ({ kv: hostKv, peers, makeSync = null,
|
|
|
3204
3263
|
// renovarlo aquí mismo. Disparar la renovación y contestar «no» dejaba la consola
|
|
3205
3264
|
// escondida hasta la siguiente visita, y el dueño —que acababa de dar el permiso— no
|
|
3206
3265
|
// tenía forma de saber que solo faltaba recargar.
|
|
3266
|
+
if (!loadVaultCert() && selfLink) return (adminLink()?.v.cert.scope || []).includes('vault:admin')
|
|
3207
3267
|
if (certDesfasadoDelActa()) { try { await renovarCert() } catch (_) {} }
|
|
3208
3268
|
const v = loadVaultCert()
|
|
3209
3269
|
return !!v?.cert && (v.cert.scope || []).includes('vault:admin')
|
|
@@ -3212,6 +3272,13 @@ export async function createIdentityCore ({ kv: hostKv, peers, makeSync = null,
|
|
|
3212
3272
|
// Lista (solo lectura) de dispositivos enrolados en tu vault.
|
|
3213
3273
|
async listVaultDevices () {
|
|
3214
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
|
+
}
|
|
3215
3282
|
if (!v?.cert || !device) {
|
|
3216
3283
|
// SOLO SE AVISA SI ES RARO. Un aparato que manda en su propia cuenta —o que aún no
|
|
3217
3284
|
// entró en ninguna bóveda— no está paired y no pasa absolutamente nada: avisar ahí
|
|
@@ -3619,6 +3686,19 @@ export async function createIdentityCore ({ kv: hostKv, peers, makeSync = null,
|
|
|
3619
3686
|
* `handlers`) porque ninguna aplicación tiene por qué poder instalar una identidad.
|
|
3620
3687
|
*/
|
|
3621
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
|
+
},
|
|
3622
3702
|
onSyncStatus (fn) { if (sync) sync.onStatus(fn) },
|
|
3623
3703
|
onVaultEvent (fn) { vaultListeners.add(fn); return () => vaultListeners.delete(fn) }
|
|
3624
3704
|
}
|