@dotrino/vaultd 0.75.1 → 0.76.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/lib/src/service.js +17 -4
- package/package.json +2 -2
- package/src/vault.js +16 -4
package/lib/src/service.js
CHANGED
|
@@ -421,7 +421,7 @@ export async function registerEncKey ({ dir, ns, proxyUrl, masterPubkey, device,
|
|
|
421
421
|
* Renueva el cert automáticamente si está por vencer (best-effort).
|
|
422
422
|
* @returns {Promise<Record<string,string>>} secretos KEY→valor
|
|
423
423
|
*/
|
|
424
|
-
export async function fetchSecrets ({ dir, ns, proxyUrl, masterPubkey, device, cert, enc, timeoutMs = 30000, onPending, approvalTimeoutMs = APPROVAL_TIMEOUT_MS } = {}) {
|
|
424
|
+
export async function fetchSecrets ({ dir, ns, proxyUrl, masterPubkey, device, cert, enc, timeoutMs = 30000, onPending, onCert, approvalTimeoutMs = APPROVAL_TIMEOUT_MS } = {}) {
|
|
425
425
|
let saved = null
|
|
426
426
|
if (dir) saved = readServiceIdentity(dir)
|
|
427
427
|
// Sin `dir`: la identidad viene entera por parámetros — es el caso de un agente
|
|
@@ -465,7 +465,15 @@ export async function fetchSecrets ({ dir, ns, proxyUrl, masterPubkey, device, c
|
|
|
465
465
|
recordar({ actaSeq: acta.seq })
|
|
466
466
|
}
|
|
467
467
|
|
|
468
|
-
|
|
468
|
+
// DOS MOTIVOS PARA PEDIR PAPEL NUEVO:
|
|
469
|
+
// · el nuestro es del MODELO VIEJO (lleva `exp` y no `seq`). Ese hay que cambiarlo sí
|
|
470
|
+
// o sí: se acepta por el repliegue de migración, pero muere en su fecha y con él el
|
|
471
|
+
// servicio. Sin esta rama la migración no terminaba nunca — el disparador comparaba
|
|
472
|
+
// `seq` contra `seq`, y un papel viejo no tiene ninguno, así que no saltaba jamás.
|
|
473
|
+
// · o el acta que hemos visto va por delante del papel: el dueño cambió permisos.
|
|
474
|
+
const legado = !!cert && typeof cert.seq !== 'number'
|
|
475
|
+
const atrasado = typeof saved?.actaSeq === 'number' && typeof cert?.seq === 'number' && saved.actaSeq > cert.seq
|
|
476
|
+
const renovar = legado || atrasado
|
|
469
477
|
|
|
470
478
|
// RENOVAR YA NO ES UNA TAREA DE CALENDARIO. El papel no vence: lo único que obliga a
|
|
471
479
|
// pedir otro es que la bóveda diga que el nuestro se quedó atrás respecto del acta, y
|
|
@@ -481,6 +489,11 @@ export async function fetchSecrets ({ dir, ns, proxyUrl, masterPubkey, device, c
|
|
|
481
489
|
if (res.type === MSG.RENEWED && res.cert?.sub === device.publickey) {
|
|
482
490
|
cert = res.cert
|
|
483
491
|
recordar({ cert })
|
|
492
|
+
// QUIEN NO PASA `dir` GUARDA ÉL. Un servicio que lleva su identidad en su propio
|
|
493
|
+
// archivo (`link.json`) y nos pasa el enlace a mano no tiene `saved`, así que
|
|
494
|
+
// `recordar` no escribe nada: sin esto renovaba en cada arranque y volvía a
|
|
495
|
+
// empezar, y su papel del modelo viejo no se cambiaba NUNCA.
|
|
496
|
+
try { onCert?.(cert) } catch (_) {}
|
|
484
497
|
}
|
|
485
498
|
} catch (_) { /* la renovación no bloquea el fetch */ }
|
|
486
499
|
}
|
|
@@ -978,11 +991,11 @@ function fingerprintOf (secrets) {
|
|
|
978
991
|
* servicio no opera hasta que esto resuelva — esa es la regla.
|
|
979
992
|
* @returns {Promise<Record<string,string>>}
|
|
980
993
|
*/
|
|
981
|
-
export async function waitForSecrets ({ dir, ns, proxyUrl, masterPubkey, device, cert, enc, retryMs = 5000, maxRetryMs = 60000, onRetry, onPending } = {}) {
|
|
994
|
+
export async function waitForSecrets ({ dir, ns, proxyUrl, masterPubkey, device, cert, enc, retryMs = 5000, maxRetryMs = 60000, onRetry, onPending, onCert } = {}) {
|
|
982
995
|
let delay = retryMs
|
|
983
996
|
for (;;) {
|
|
984
997
|
try {
|
|
985
|
-
return await fetchSecrets({ dir, ns, proxyUrl, masterPubkey, device, cert, enc, onPending })
|
|
998
|
+
return await fetchSecrets({ dir, ns, proxyUrl, masterPubkey, device, cert, enc, onPending, onCert })
|
|
986
999
|
} catch (e) {
|
|
987
1000
|
// Lo NO transitorio no se arregla reintentando: falta de enrolamiento,
|
|
988
1001
|
// cert revocado/vencido o scope equivocado exigen re-emparejar → se corta.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotrino/vaultd",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.76.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Certificador personal de Dotrino: daemon headless que custodia la clave maestra y delega capacidades a tus dispositivos por el proxy. Tu CA propia.",
|
|
6
6
|
"bin": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"node": ">=20"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@dotrino/identity": "0.74.
|
|
23
|
+
"@dotrino/identity": "0.74.1",
|
|
24
24
|
"@dotrino/passmanager": "^0.9.0",
|
|
25
25
|
"@dotrino/proxy-client": "^0.13.1",
|
|
26
26
|
"ws": "^8.18.0"
|
package/src/vault.js
CHANGED
|
@@ -688,7 +688,6 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
|
|
|
688
688
|
// cert fresco (misma sub-clave y scope) sin QR ni aprobación — sigue siendo el
|
|
689
689
|
// mismo dispositivo enrolado, solo extiende la ventana. Un cert vencido o
|
|
690
690
|
// revocado NO puede renovarse (ahí sí toca re-emparejar con aprobación).
|
|
691
|
-
const RENEW_TTL_MS = 30 * 24 * 60 * 60 * 1000
|
|
692
691
|
async function handleRenew (from, p) {
|
|
693
692
|
if (!isFresh(p.data)) { audit('rejected', { what: 'renew', reason: 'stale' }); return staleReply(from) }
|
|
694
693
|
const chk = await verifyChain({ data: p.data, signature: p.signature, cert: p.cert, ...(await contextoActa()), revoked: await revocationSet() })
|
|
@@ -714,9 +713,22 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
|
|
|
714
713
|
audit('rejected', { what: 'renew', device: await deviceIdOf(p.cert.sub), reason: 'not-a-member' })
|
|
715
714
|
return reply(from, { type: MSG.ERROR, error: 'unauthorized: the record no longer lists this device' })
|
|
716
715
|
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
716
|
+
// RENOVAR NO RETIRA EL PAPEL ANTERIOR (`supersede: false`), y esto costó dos servicios.
|
|
717
|
+
//
|
|
718
|
+
// Retirarlo al emitir parece limpio —un aparato, un papel— pero convierte cualquier
|
|
719
|
+
// renovación que falle DESPUÉS de emitirse en una expulsión permanente: la máquina se
|
|
720
|
+
// queda con el papel viejo, que acaba de quedar revocado, y ya no puede ni pedir otro.
|
|
721
|
+
// Pasó dos veces en la migración del VPS (el registro de selladores y el bot social):
|
|
722
|
+
// el papel nuevo salió, la respuesta no llegó a guardarse, y al reintentar la bóveda
|
|
723
|
+
// contestaba `unauthorized: revoked`. Re-emparejar a mano, con TTY, por una renovación.
|
|
724
|
+
//
|
|
725
|
+
// Que convivan dos papeles del mismo aparato ya no es el problema que era: ninguno
|
|
726
|
+
// vence, pero lo que pueden hacer lo decide el ACTA en cada mostrador, así que el
|
|
727
|
+
// viejo no autoriza nada que el nuevo no autorice. Y quitar el aparato de verdad sigue
|
|
728
|
+
// siendo `revokeDevice`, que los retira TODOS de una vez.
|
|
729
|
+
const { cert } = await identity.signDelegation(p.cert.sub, scope, { label: prev?.label || '', supersede: false })
|
|
730
|
+
audit('renew', { device: await deviceIdOf(p.cert.sub), label: prev?.label || '', scope, seq: cert.seq })
|
|
731
|
+
log(`[vault] cert renewed for ${await deviceIdOf(p.cert.sub)} · record #${cert.seq}`)
|
|
720
732
|
// El acta viaja con el papel: sin ella quien lo recibe no puede comprobar que lo firmó
|
|
721
733
|
// una selladora del perfil, que es lo que sustituyó a «lo firmó la maestra».
|
|
722
734
|
reply(from, { type: MSG.RENEWED, cert, acta: record })
|