@dotrino/identity 0.48.0 → 0.49.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 +1 -1
- package/src/node.js +5 -1
- package/vault/core.js +58 -39
package/package.json
CHANGED
package/src/node.js
CHANGED
|
@@ -122,7 +122,11 @@ export class Identity {
|
|
|
122
122
|
this._core = await createIdentityCore({
|
|
123
123
|
kv: fileKv(path.join(this._dir, 'identity.json'), this._atRest),
|
|
124
124
|
peers: filePeers(path.join(this._dir, 'peers.json')),
|
|
125
|
-
makeSync: null
|
|
125
|
+
makeSync: null,
|
|
126
|
+
// Aquí las cuentas las lleva quien hospeda (el daemon del vault tiene su propio
|
|
127
|
+
// registro de perfiles): que una expulsión borre una por debajo le dejaría el suyo
|
|
128
|
+
// apuntando a algo que ya no existe. Se borra el enlace y el acta, nada más.
|
|
129
|
+
removeAccountOnExpulsion: false
|
|
126
130
|
})
|
|
127
131
|
this._core.onSyncStatus((payload) => this._emit('sync', payload))
|
|
128
132
|
this._core.onVaultEvent((payload) => this._emit('vault', payload))
|
package/vault/core.js
CHANGED
|
@@ -158,7 +158,7 @@ function sanitizeProfilePatch (patch = {}) {
|
|
|
158
158
|
return out
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
export async function createIdentityCore ({ kv: rawKv, peers, makeSync = null, keyStore = null, sessionKv = null }) {
|
|
161
|
+
export async function createIdentityCore ({ kv: rawKv, peers, makeSync = null, keyStore = null, sessionKv = null, removeAccountOnExpulsion = true }) {
|
|
162
162
|
const {
|
|
163
163
|
initPeerStorage, loadPeers, savePeers, setPeersDirect, upsertPeer, onDirty
|
|
164
164
|
} = peers
|
|
@@ -350,18 +350,25 @@ export async function createIdentityCore ({ kv: rawKv, peers, makeSync = null, k
|
|
|
350
350
|
* de perfiles, con su nombre y su foto, sin poder hacer nada y sin que nadie supiera qué
|
|
351
351
|
* era ni cómo quitarlo.
|
|
352
352
|
*
|
|
353
|
-
* El aparato
|
|
354
|
-
*
|
|
355
|
-
*
|
|
356
|
-
*
|
|
353
|
+
* El aparato no se queda sin cuenta utilizable: al recargar, el arranque estrena una si
|
|
354
|
+
* no quedó ninguna, o entra en la primera que haya. Así al terminar hay exactamente lo
|
|
355
|
+
* que tiene que haber: un dispositivo con su cuenta, listo para usarse o para volver a
|
|
356
|
+
* conectarse a una bóveda.
|
|
357
357
|
*
|
|
358
358
|
* Los pasos van EN ESTE ORDEN a propósito:
|
|
359
359
|
* 1. fuera el enlace y el acta (deja de poder hablar con la bóveda y de enseñar el
|
|
360
360
|
* perfil del que lo echaron);
|
|
361
361
|
* 2. 'revoked' → `@dotrino/store` borra el store de ESE perfil (apunta al id que ya
|
|
362
362
|
* tenía fijado, así que da igual lo que hagamos después con el perfil activo);
|
|
363
|
-
* 3. se borra la cuenta
|
|
364
|
-
* 4. 'account-removed' → la app RECARGA (multi-perfil no es reactivo, por diseño)
|
|
363
|
+
* 3. se borra la cuenta;
|
|
364
|
+
* 4. 'account-removed' → la app RECARGA (multi-perfil no es reactivo, por diseño), y
|
|
365
|
+
* es el ARRANQUE quien deja puesta la que toque: si no queda ninguna estrena la
|
|
366
|
+
* primera, y si quedan cae a la primera de la lista. Esa decisión ya vivía ahí.
|
|
367
|
+
*
|
|
368
|
+
* El paso 3 es SOLO del navegador (`removeAccountOnExpulsion`). En Node las cuentas las
|
|
369
|
+
* lleva quien hospeda —el daemon del vault tiene su propio registro de perfiles, en
|
|
370
|
+
* `profiles.js`— y borrar una por debajo le dejaría el suyo apuntando a algo que ya no
|
|
371
|
+
* existe. Ahí se borra el enlace y el acta, y de la cuenta decide su dueño.
|
|
365
372
|
*/
|
|
366
373
|
const wipeVaultLink = () => {
|
|
367
374
|
try { kv.removeItem(VAULT_CERT_STORAGE); kv.removeItem(VAULT_DEVICE_STORAGE) } catch (_) {}
|
|
@@ -369,33 +376,54 @@ export async function createIdentityCore ({ kv: rawKv, peers, makeSync = null, k
|
|
|
369
376
|
emitVault({ phase: 'revoked' })
|
|
370
377
|
// Sin `await`: quien llama es un manejador de mensajes que no espera nada (y si algo
|
|
371
378
|
// aquí revienta, el enlace y el acta ya se fueron, que es lo que no puede quedar).
|
|
372
|
-
removeThisAccount().catch(() => {})
|
|
379
|
+
if (removeAccountOnExpulsion) removeThisAccount().catch(() => {})
|
|
373
380
|
}
|
|
374
381
|
|
|
375
|
-
/**
|
|
376
|
-
* Borra la cuenta de ESTE dispositivo y deja otra activa (existente o recién creada).
|
|
377
|
-
* Se usa al ser expulsado; ver `wipeVaultLink`.
|
|
378
|
-
*/
|
|
382
|
+
/** Borra la cuenta de ESTE dispositivo al ser expulsado; ver `wipeVaultLink`. */
|
|
379
383
|
let removingAccount = false
|
|
380
384
|
async function removeThisAccount () {
|
|
381
|
-
// UNA VEZ
|
|
382
|
-
// sin este cerrojo, la segunda borraría la cuenta RECIÉN CREADA (para entonces
|
|
383
|
-
// `currentPid` ya es la nueva) en vez de la que echaron.
|
|
385
|
+
// UNA VEZ: puede haber varias peticiones en vuelo y a todas les llega el mismo aviso.
|
|
384
386
|
if (removingAccount) return
|
|
385
387
|
removingAccount = true
|
|
386
388
|
const gone = currentPid
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
// activa; si había otras, es el propio `deleteProfile` quien pasa a la primera.
|
|
390
|
-
const created = loadProfiles().filter((p) => p.id !== gone).length === 0
|
|
391
|
-
try {
|
|
392
|
-
if (created) await handlers.createProfile({ name: '' })
|
|
393
|
-
await handlers.deleteProfile({ id: gone })
|
|
394
|
-
} catch (e) {
|
|
395
|
-
emitVault({ phase: 'account-removed', removed: gone, current: currentPid, error: e?.message || String(e) })
|
|
389
|
+
try { await purgeProfile(gone) } catch (e) {
|
|
390
|
+
emitVault({ phase: 'account-removed', removed: gone, error: e?.message || String(e) })
|
|
396
391
|
return
|
|
397
392
|
}
|
|
398
|
-
|
|
393
|
+
// Y ya está: qué cuenta queda puesta lo resuelve el ARRANQUE al recargar, que es donde
|
|
394
|
+
// esa decisión ya vivía —si no queda ninguna estrena la primera, y si quedan cae a la
|
|
395
|
+
// primera de la lista—. No hace falta decidirlo aquí también.
|
|
396
|
+
emitVault({ phase: 'account-removed', removed: gone, current: currentPid })
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Borra un perfil y todo lo suyo. Sin preguntas: el freno de «no te quedes sin ninguna»
|
|
401
|
+
* es de la interfaz y vive en `deleteProfile`.
|
|
402
|
+
*/
|
|
403
|
+
async function purgeProfile (id) {
|
|
404
|
+
const list = loadProfiles().filter((p) => p.id !== id)
|
|
405
|
+
saveProfiles(list)
|
|
406
|
+
for (const s of ['keypair', 'enc-keypair', 'me', 'nonces', 'delegations', 'revocations', 'vault.device', 'vault.cert', 'acta', 'renounced']) {
|
|
407
|
+
rawKv.removeItem(`dotrino.identity.p.${id}.${s}`)
|
|
408
|
+
}
|
|
409
|
+
// …y sus CryptoKeys no extractables del keyStore (IndexedDB).
|
|
410
|
+
if (keyStore) {
|
|
411
|
+
for (const s of ['keypair', 'enc-keypair']) {
|
|
412
|
+
try { await keyStore.remove(`dotrino.identity.p.${id}.${s}`) } catch (_) {}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
if (currentPid === id) {
|
|
416
|
+
if (list.length) { currentPid = list[0].id; rawKv.setItem(CURRENT_STORAGE, currentPid) }
|
|
417
|
+
else {
|
|
418
|
+
// No queda ninguna: el arranque estrenará la primera. Se borra el puntero, pero
|
|
419
|
+
// `currentPid` se deja como está a propósito — sin él, el kv deja de estar
|
|
420
|
+
// scopeado y cualquier escritura de aquí a la recarga caería en las claves SIN
|
|
421
|
+
// namespace, que son justo las que el arranque adopta como «Perfil 1». Apuntando
|
|
422
|
+
// a un perfil que ya no existe, lo que se escriba es inerte.
|
|
423
|
+
rawKv.removeItem(CURRENT_STORAGE)
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return { ok: true, current: currentPid }
|
|
399
427
|
}
|
|
400
428
|
|
|
401
429
|
/**
|
|
@@ -1255,22 +1283,13 @@ export async function createIdentityCore ({ kv: rawKv, peers, makeSync = null, k
|
|
|
1255
1283
|
return { id: e.id, name: e.name }
|
|
1256
1284
|
},
|
|
1257
1285
|
async deleteProfile ({ id } = {}) {
|
|
1258
|
-
|
|
1286
|
+
const list = loadProfiles()
|
|
1287
|
+
// El freno es de la INTERFAZ: el botón «Borrar» de la página de perfiles no puede
|
|
1288
|
+
// dejarte sin ninguna de un clic. La expulsión no pasa por aquí (ver `purgeProfile`):
|
|
1289
|
+
// ahí sí se va la última, porque no es un descuido sino que te echaron.
|
|
1259
1290
|
if (list.length <= 1) throw new Error('cannot delete the only profile')
|
|
1260
1291
|
if (!list.find((p) => p.id === id)) throw new Error('perfil no existe')
|
|
1261
|
-
|
|
1262
|
-
// Borrado directo del namespace del perfil (incluye su store del vault si lo tuviera).
|
|
1263
|
-
for (const s of ['keypair', 'enc-keypair', 'me', 'nonces', 'delegations', 'revocations', 'vault.device', 'vault.cert', 'acta', 'renounced']) {
|
|
1264
|
-
rawKv.removeItem(`dotrino.identity.p.${id}.${s}`)
|
|
1265
|
-
}
|
|
1266
|
-
// …y sus CryptoKeys no extractables del keyStore (IndexedDB).
|
|
1267
|
-
if (keyStore) {
|
|
1268
|
-
for (const s of ['keypair', 'enc-keypair']) {
|
|
1269
|
-
try { await keyStore.remove(`dotrino.identity.p.${id}.${s}`) } catch (_) {}
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
if (currentPid === id) { currentPid = list[0].id; rawKv.setItem(CURRENT_STORAGE, currentPid) }
|
|
1273
|
-
return { ok: true, current: currentPid }
|
|
1292
|
+
return purgeProfile(id)
|
|
1274
1293
|
},
|
|
1275
1294
|
|
|
1276
1295
|
// ----- ACTA DE PERFIL -----
|