@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.
- package/package.json +4 -1
- package/src/node.js +4 -180
- package/vault/content.js +28 -1
- package/vault/core.js +66 -7
- package/vault/facade.js +200 -0
- package/vault/index.html +10 -0
- package/vault/remote.js +45 -5
- 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 +136 -11
- package/vault/vendor/proxy-client/index.js +1 -0
- package/vault/vendor/proxy-client/parts.js +127 -0
- package/vault/vendor/proxy-client/webrtc.js +23 -0
- package/vault/vendor/roadmap/VERSION.txt +3 -0
- package/vault/vendor/roadmap/index.js +108 -0
- package/vault/vendor/roadmap/manifests.js +802 -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.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.
|
|
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
|
|
3101
|
-
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 })
|
|
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
|
|
3118
|
-
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
|
+
}
|
|
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
|
-
|
|
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
|
}
|
package/vault/facade.js
ADDED
|
@@ -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",
|