@dotrino/vaultd 0.19.0 → 0.22.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/protocol.js +2 -0
- package/package.json +2 -2
- package/src/ctl.js +1 -1
- package/src/tui/i18n.js +2 -2
- package/src/vault.js +39 -3
package/lib/src/protocol.js
CHANGED
|
@@ -42,6 +42,8 @@ export const MSG = Object.freeze({
|
|
|
42
42
|
DEVICES: 'vault.devices', // dispositivo → vault: { data:{publickey,ts}, signature, cert }
|
|
43
43
|
DEVICES_RESULT: 'vault.devices.result', // vault → dispositivo: { devices, revoked }
|
|
44
44
|
RENEW: 'vault.renew', // dispositivo → vault: { data:{op,publickey,ts}, signature, cert }
|
|
45
|
+
RENOUNCE: 'vault.renounce', // dispositivo → vault: { record } (RENUNCIA firmada por el propio miembro)
|
|
46
|
+
RENOUNCE_RESULT: 'vault.renounce.result', // vault → dispositivo: { ok, seq }
|
|
45
47
|
RENEWED: 'vault.renewed', // vault → dispositivo: { cert } (cert fresco, misma sub-clave/scope)
|
|
46
48
|
SECRETS: 'vault.secrets', // servicio → vault: { data:{op,ns,ek,publickey,ts}, signature, cert }
|
|
47
49
|
SECRETS_RESULT: 'vault.secrets.result', // vault → servicio: { body:{op,ns,enc,ts}, signature } (enc SELLADO a ek; body firmado por la maestra)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotrino/vaultd",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.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": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"node": ">=20"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@dotrino/identity": "^0.
|
|
22
|
+
"@dotrino/identity": "^0.42.0",
|
|
23
23
|
"@dotrino/proxy-client": "^0.10.0",
|
|
24
24
|
"ws": "^8.18.0"
|
|
25
25
|
},
|
package/src/ctl.js
CHANGED
|
@@ -329,7 +329,7 @@ async function cmdMembers () {
|
|
|
329
329
|
for (const m of acta.members) {
|
|
330
330
|
const quien = m.label || m.id
|
|
331
331
|
const marcas = [
|
|
332
|
-
m.isMaster ? `${B}
|
|
332
|
+
m.isMaster ? `${B}Master${Z}` : null,
|
|
333
333
|
m.isMe ? 'este dispositivo' : null,
|
|
334
334
|
m.cn ? `servicio «${m.cn}»` : null
|
|
335
335
|
].filter(Boolean)
|
package/src/tui/i18n.js
CHANGED
|
@@ -242,7 +242,7 @@ const es = {
|
|
|
242
242
|
errNotApplied: 'El daemon no aplicó el cambio (revisa los logs del servicio).',
|
|
243
243
|
errNotDeleted: 'El daemon no borró la variable (revisa los logs del servicio).',
|
|
244
244
|
errPairFailed: 'El daemon no inició el emparejamiento.',
|
|
245
|
-
errMasterWithMembers: 'Esta
|
|
245
|
+
errMasterWithMembers: 'Esta bóveda es el Master de esta cuenta y hay otros dispositivos: pásale primero el Master a uno que esté conectado. Si la borras así, se quedan con su llave y sin nadie que pueda volver a firmar el acta.'
|
|
246
246
|
}
|
|
247
247
|
|
|
248
248
|
// ---------------------------------- inglés ----------------------------------
|
|
@@ -451,7 +451,7 @@ const en = {
|
|
|
451
451
|
errNotApplied: 'The daemon did not apply the change (check the service logs).',
|
|
452
452
|
errNotDeleted: 'The daemon did not delete the variable (check the service logs).',
|
|
453
453
|
errPairFailed: 'The daemon did not start the pairing.',
|
|
454
|
-
errMasterWithMembers: 'This vault is
|
|
454
|
+
errMasterWithMembers: 'This vault is the Master of this account and it has other devices: hand the Master over to one that is online first. If you delete it like this, they keep their key with nobody able to sign the record again.'
|
|
455
455
|
}
|
|
456
456
|
|
|
457
457
|
// --------------------------- selección y persistencia -----------------------
|
package/src/vault.js
CHANGED
|
@@ -229,9 +229,16 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
|
|
|
229
229
|
// que falta (ventana de retención, §1.3) para que compruebe el encadenamiento en vez
|
|
230
230
|
// de tragarse un salto a ciegas. Si se salió de la ventana, llega vacía y toca
|
|
231
231
|
// re-emparejar — que es justo lo que debe pasar.
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
232
|
+
// El `.catch()` de antes NO protegía de nada: si `actaHistory` no existe, el
|
|
233
|
+
// TypeError es SÍNCRONO y salta antes de que haya promesa que encadenar. Pasó: el
|
|
234
|
+
// método vivía en el núcleo pero no en el envoltorio de Node, así que esta línea
|
|
235
|
+
// tiraba toda la respuesta y ningún navegador ya emparejado —que manda `sinceSeq`
|
|
236
|
+
// siempre— volvía a sincronizar su acta. Un try/catch de verdad, y opcional.
|
|
237
|
+
let chain = null
|
|
238
|
+
if (typeof p.data?.sinceSeq === 'number') {
|
|
239
|
+
try { chain = (await identity.actaHistory?.({ sinceSeq: p.data.sinceSeq }))?.chain || null }
|
|
240
|
+
catch (e) { log('[vault] could not build the record chain:', e.message) }
|
|
241
|
+
}
|
|
235
242
|
// `sub` (pubkey completa) va incluida: es la DIRECCIÓN de cada dispositivo en el
|
|
236
243
|
// proxy → permite a las apps AUTODESCUBRIR tus máquinas (p. ej. la terminal
|
|
237
244
|
// lista tus agentes sin pegar nada). Solo la ve quien presenta un cert tuyo válido.
|
|
@@ -324,6 +331,7 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
|
|
|
324
331
|
if (payload.type === MSG.RENEW) return await handleRenew(from, payload)
|
|
325
332
|
if (payload.type === MSG.SECRETS) return await handleSecrets(from, payload)
|
|
326
333
|
if (payload.type === MSG.ADMIN) return await handleAdmin(from, payload)
|
|
334
|
+
if (payload.type === MSG.RENOUNCE) return await handleRenounce(from, payload)
|
|
327
335
|
} catch (e) {
|
|
328
336
|
reply(from, { type: MSG.ERROR, error: e.message })
|
|
329
337
|
}
|
|
@@ -449,6 +457,34 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
|
|
|
449
457
|
}
|
|
450
458
|
})
|
|
451
459
|
|
|
460
|
+
/**
|
|
461
|
+
* RENUNCIA: un miembro se quita capacidades a sí mismo y la bóveda la SELLA en el acta.
|
|
462
|
+
*
|
|
463
|
+
* NO se pide certificado, y es a propósito: el registro va firmado por el propio miembro
|
|
464
|
+
* y solo puede QUITAR, así que honrarlo no puede hacer daño — y exigir un cert válido
|
|
465
|
+
* dejaría fuera justo el caso que la justifica (un aparato que ya no es de fiar, o cuyo
|
|
466
|
+
* cert caducó). Sin esto la renuncia se quedaba en el dispositivo: la bóveda seguía
|
|
467
|
+
* teniendo escrito que podía firmar y le seguía aceptando peticiones.
|
|
468
|
+
*/
|
|
469
|
+
async function handleRenounce (from, p) {
|
|
470
|
+
const record = p?.data?.record || p?.record
|
|
471
|
+
if (!record || typeof record !== 'object') return reply(from, { type: MSG.ERROR, error: 'renounce: record required' })
|
|
472
|
+
if (!(await Acta.verifyRenounce(record).catch(() => false))) {
|
|
473
|
+
audit('rejected', { what: 'renounce', reason: 'signature' })
|
|
474
|
+
return reply(from, { type: MSG.ERROR, error: 'renounce: the signature is not the member own' })
|
|
475
|
+
}
|
|
476
|
+
try {
|
|
477
|
+
const r = await identity.absorbRenounce(record)
|
|
478
|
+
const device = await deviceIdOf(record.member).catch(() => null)
|
|
479
|
+
audit('renounce', { device, caps: record.caps })
|
|
480
|
+
log(`[vault] ${device} renounced: ${(record.caps || []).join(', ')}`)
|
|
481
|
+
await notifyMembers('renounce', { deviceId: device, caps: record.caps })
|
|
482
|
+
reply(from, { type: MSG.RENOUNCE_RESULT, ok: true, seq: r?.seq ?? null })
|
|
483
|
+
} catch (e) {
|
|
484
|
+
reply(from, { type: MSG.ERROR, error: 'renounce: ' + e.message })
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
452
488
|
async function handleAdmin (from, p) {
|
|
453
489
|
// La frescura se comprueba aquí (es del transporte, igual que en el resto de
|
|
454
490
|
// handlers); el resto de la regla vive en el módulo puro.
|