@dotrino/identity 0.39.0 → 0.41.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/index.js +2 -0
- package/src/node.js +11 -0
- package/vault/core.js +44 -7
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -269,6 +269,8 @@ export class Identity {
|
|
|
269
269
|
|
|
270
270
|
/** Dónde estoy yo: { inProfile, profileId, seq, isMaster, caps, id }. */
|
|
271
271
|
async myMembership () { return this._call('myMembership') }
|
|
272
|
+
/** La cadena de actas desde `sinceSeq` (ver la nota en `node.js`). */
|
|
273
|
+
async actaHistory (opts) { return this._call('actaHistory', opts || {}) }
|
|
272
274
|
|
|
273
275
|
/** ¿Es ESTE dispositivo el master (el único que puede cambiar el acta)? */
|
|
274
276
|
async isMaster () { return this._call('isMaster') }
|
package/src/node.js
CHANGED
|
@@ -165,6 +165,17 @@ export class Identity {
|
|
|
165
165
|
profileActa () { return this._h('profileActa') }
|
|
166
166
|
profileMembers () { return this._h('profileMembers') }
|
|
167
167
|
myMembership () { return this._h('myMembership') }
|
|
168
|
+
/**
|
|
169
|
+
* La CADENA de actas desde `sinceSeq` (para que un dispositivo que estuvo apagado
|
|
170
|
+
* compruebe el encadenamiento en vez de tragarse un salto a ciegas).
|
|
171
|
+
*
|
|
172
|
+
* Vivía en el núcleo pero NO estaba aquí, así que `identity.actaHistory` era
|
|
173
|
+
* `undefined` en el daemon: al pedir la lista de dispositivos con un `sinceSeq`
|
|
174
|
+
* —que es lo que manda SIEMPRE un navegador ya emparejado— reventaba con
|
|
175
|
+
* «is not a function», la bóveda respondía error, y el aparato NUNCA volvía a
|
|
176
|
+
* sincronizar su acta. Se quedaba con la copia del día que emparejó.
|
|
177
|
+
*/
|
|
178
|
+
actaHistory (opts) { return this._h('actaHistory', opts || {}) }
|
|
168
179
|
isMaster () { return this._h('isMaster') }
|
|
169
180
|
admitMember (member) { return this._h('admitMember', member) }
|
|
170
181
|
setCaps (pub, caps) { return this._h('setCaps', { pub, caps }) }
|
package/vault/core.js
CHANGED
|
@@ -543,21 +543,49 @@ export async function createIdentityCore ({ kv: rawKv, peers, makeSync = null, k
|
|
|
543
543
|
const RENEW_WINDOW_MS = 15 * 24 * 60 * 60 * 1000
|
|
544
544
|
const RENEW_RETRY_MS = 60 * 60 * 1000 // si falla (vault apagado), no insistir >1 vez/hora
|
|
545
545
|
let renewLastTry = 0
|
|
546
|
+
/**
|
|
547
|
+
* ¿El cert se quedó atrás respecto del ACTA? El acta es la política (lo que el dueño
|
|
548
|
+
* decidió); el cert es su reflejo, y solo se refresca al renovar. Sin esta comprobación,
|
|
549
|
+
* un permiso concedido después de emparejar tardaba en llegar lo que tardara el cert en
|
|
550
|
+
* acercarse a su caducidad: hasta 30 días. En la práctica, dar «administra» y no ver
|
|
551
|
+
* NUNCA aparecer la consola remota.
|
|
552
|
+
*/
|
|
553
|
+
function certDesfasadoDelActa () {
|
|
554
|
+
try {
|
|
555
|
+
const v = loadVaultCert(); const acta = loadActa()
|
|
556
|
+
if (!v?.cert || !acta) return false
|
|
557
|
+
const debeTener = Acta.memberScopes(acta, publickeyJwkStr)
|
|
558
|
+
if (!debeTener.length) return false // ya no soy miembro: renovar no toca
|
|
559
|
+
const tiene = new Set(v.cert.scope || [])
|
|
560
|
+
return debeTener.length !== tiene.size || debeTener.some((s) => !tiene.has(s))
|
|
561
|
+
} catch (_) { return false }
|
|
562
|
+
}
|
|
563
|
+
|
|
546
564
|
function maybeRenewVaultCert () {
|
|
547
565
|
try {
|
|
548
566
|
const v = loadVaultCert(); const device = loadVaultDevice()
|
|
549
567
|
if (!v?.cert || !device) return
|
|
550
568
|
const now = Date.now()
|
|
551
|
-
|
|
569
|
+
// Se renueva por dos motivos: porque el cert se acerca a su fin, o porque el acta
|
|
570
|
+
// dice que este aparato puede algo distinto de lo que lleva escrito el cert.
|
|
571
|
+
const porCaducar = v.cert.exp > now && v.cert.exp - now <= RENEW_WINDOW_MS
|
|
572
|
+
if (v.cert.exp <= now || (!porCaducar && !certDesfasadoDelActa())) return
|
|
552
573
|
if (now - renewLastTry < RENEW_RETRY_MS) return
|
|
553
|
-
|
|
554
|
-
remoteRenew({ master: v.master, proxy: v.proxy, device, cert: v.cert, onRevoked: wipeVaultLink }).then(({ cert }) => {
|
|
555
|
-
kv.setItem(VAULT_CERT_STORAGE, JSON.stringify({ ...v, cert, renewedAt: Date.now() }))
|
|
556
|
-
emitVault({ phase: 'renewed', exp: cert.exp })
|
|
557
|
-
}).catch(() => {}) // best-effort: el cert vigente sigue sirviendo mientras tanto
|
|
574
|
+
renovarCert().catch(() => {}) // best-effort: el cert vigente sigue sirviendo mientras tanto
|
|
558
575
|
} catch (_) {}
|
|
559
576
|
}
|
|
560
577
|
|
|
578
|
+
/** Pide un cert fresco y lo guarda. Devuelve promesa para poder ESPERARLO cuando hace falta. */
|
|
579
|
+
async function renovarCert () {
|
|
580
|
+
const v = loadVaultCert(); const device = loadVaultDevice()
|
|
581
|
+
if (!v?.cert || !device) return null
|
|
582
|
+
renewLastTry = Date.now()
|
|
583
|
+
const { cert } = await remoteRenew({ master: v.master, proxy: v.proxy, device, cert: v.cert, onRevoked: wipeVaultLink })
|
|
584
|
+
kv.setItem(VAULT_CERT_STORAGE, JSON.stringify({ ...v, cert, renewedAt: Date.now() }))
|
|
585
|
+
emitVault({ phase: 'renewed', exp: cert.exp })
|
|
586
|
+
return cert
|
|
587
|
+
}
|
|
588
|
+
|
|
561
589
|
// ----- me (kv-backed) -----
|
|
562
590
|
|
|
563
591
|
function loadMe () {
|
|
@@ -1528,7 +1556,12 @@ export async function createIdentityCore ({ kv: rawKv, peers, makeSync = null, k
|
|
|
1528
1556
|
* cert que le dio la bóveda, no de una preferencia: la interfaz pregunta para
|
|
1529
1557
|
* saber qué pintar, pero quien decide es la bóveda al recibir la petición.
|
|
1530
1558
|
*/
|
|
1531
|
-
canAdminVault () {
|
|
1559
|
+
async canAdminVault () {
|
|
1560
|
+
// Si el acta ya dice que este aparato administra pero el cert todavía no, se ESPERA a
|
|
1561
|
+
// renovarlo aquí mismo. Disparar la renovación y contestar «no» dejaba la consola
|
|
1562
|
+
// escondida hasta la siguiente visita, y el dueño —que acababa de dar el permiso— no
|
|
1563
|
+
// tenía forma de saber que solo faltaba recargar.
|
|
1564
|
+
if (certDesfasadoDelActa()) { try { await renovarCert() } catch (_) {} }
|
|
1532
1565
|
const v = loadVaultCert()
|
|
1533
1566
|
return !!v?.cert && (v.cert.scope || []).includes('vault:admin')
|
|
1534
1567
|
},
|
|
@@ -1546,6 +1579,10 @@ export async function createIdentityCore ({ kv: rawKv, peers, makeSync = null, k
|
|
|
1546
1579
|
if (res.chain?.length && res.chain[0].profileId === loadActa()?.profileId) await adoptChain(res.chain)
|
|
1547
1580
|
else if (res.acta) await (res.acta.profileId === loadActa()?.profileId ? adoptActa(res.acta) : joinProfile(res.acta))
|
|
1548
1581
|
} catch (_) {}
|
|
1582
|
+
// El acta acaba de llegar: si trae permisos que el cert no lleva, se renueva YA. La
|
|
1583
|
+
// comprobación de arriba corrió ANTES de tenerla, así que sin esto haría falta una
|
|
1584
|
+
// segunda visita para estrenar un permiso recién concedido.
|
|
1585
|
+
maybeRenewVaultCert()
|
|
1549
1586
|
return res
|
|
1550
1587
|
} catch (e) { return handleVaultError(e) }
|
|
1551
1588
|
},
|