@dotrino/vaultd 0.90.0 → 0.92.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/daemon.js +3 -3
- package/src/manager.js +33 -8
- package/src/passwords.js +1 -1
- package/src/transport.js +21 -7
- package/src/vault.js +4 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotrino/vaultd",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.92.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": {
|
package/src/daemon.js
CHANGED
|
@@ -225,7 +225,7 @@ export async function runDaemon () {
|
|
|
225
225
|
}, expiresInMs + 1000).unref?.()
|
|
226
226
|
console.log('[vault] pairing started (valid for %d min)', expiresInMs / 60000)
|
|
227
227
|
} catch (e) {
|
|
228
|
-
console.error('[vault]
|
|
228
|
+
console.error('[vault] could not start pairing:', e.message)
|
|
229
229
|
}
|
|
230
230
|
}
|
|
231
231
|
|
|
@@ -413,7 +413,7 @@ export async function runDaemon () {
|
|
|
413
413
|
} catch (e) { console.error('[vault] could not grant admin: %s', e.message) }
|
|
414
414
|
}
|
|
415
415
|
pendingAdmin = false
|
|
416
|
-
console.log('[vault]
|
|
416
|
+
console.log('[vault] approved %s', r.deviceId)
|
|
417
417
|
answer({ ok: true, deviceId: r.deviceId || null })
|
|
418
418
|
} catch (e) {
|
|
419
419
|
console.error('[vault] approval failed:', e.message)
|
|
@@ -440,7 +440,7 @@ export async function runDaemon () {
|
|
|
440
440
|
rm(path.join(dir, 'caps-request.json'))
|
|
441
441
|
try {
|
|
442
442
|
await targetOf(capsReq)?.setCaps(capsReq.pub, capsReq.caps)
|
|
443
|
-
console.log('[vault] permissions updated: %s', capsReq.caps.join(', ') || '(
|
|
443
|
+
console.log('[vault] permissions updated: %s', capsReq.caps.join(', ') || '(none)')
|
|
444
444
|
} catch (e) { console.error('[vault] could not change permissions:', e.message) }
|
|
445
445
|
}
|
|
446
446
|
// Quitar un dispositivo: se pide por `sub` (la llave del aparato). `nonce` sigue
|
package/src/manager.js
CHANGED
|
@@ -80,7 +80,7 @@ export async function startVaultManager ({ root = dataDir(), proxyUrl, log = con
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
const migrated = await profiles.migrate(mintKey)
|
|
83
|
-
if (migrated?.migrated) log('[vault]
|
|
83
|
+
if (migrated?.migrated) log('[vault] single-profile identity migrated to profile %s', migrated.id)
|
|
84
84
|
|
|
85
85
|
const running = new Map() // id -> instancia de startVault
|
|
86
86
|
|
|
@@ -121,7 +121,7 @@ export async function startVaultManager ({ root = dataDir(), proxyUrl, log = con
|
|
|
121
121
|
id = await profiles.ensureNamedByKey(p.id, mintKey)
|
|
122
122
|
if (id !== p.id) log('[vault] profile %s renamed to %s (the folder is named after its key)', p.id, id)
|
|
123
123
|
} catch (e) { log('[vault] could not name the folder of %s after its key: %s', p.id, e.message) }
|
|
124
|
-
try { await open(id) } catch (e) { log('[vault]
|
|
124
|
+
try { await open(id) } catch (e) { log('[vault] could not open profile %s: %s', id, e.message) }
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
const get = (id) => {
|
|
@@ -136,12 +136,37 @@ export async function startVaultManager ({ root = dataDir(), proxyUrl, log = con
|
|
|
136
136
|
* Va aquí y no en `profiles.js` porque el registro de perfiles no sabe de bóvedas
|
|
137
137
|
* corriendo: sabe de contraseñas. Quien tiene las dos cosas delante es esto.
|
|
138
138
|
*/
|
|
139
|
+
/**
|
|
140
|
+
* ABRIR DE VERDAD, NO SOLO QUITAR EL CANDADO.
|
|
141
|
+
*
|
|
142
|
+
* Esto decía `running.get(id)?.takeMasterKey?.()`, y con un `?.` de por medio un perfil
|
|
143
|
+
* que **no estaba corriendo** se marcaba desbloqueado sin abrir nada. Y hay un caso muy
|
|
144
|
+
* normal en el que no está corriendo: **su maestra está sellada bajo la contraseña**, así
|
|
145
|
+
* que al arrancar el servicio no hay frase con la que abrirlo y se queda fuera
|
|
146
|
+
* (`could not open profile …: vault locked`). Justo el perfil que más falta hace abrir
|
|
147
|
+
* era el único al que `unlock` no le hacía nada.
|
|
148
|
+
*
|
|
149
|
+
* Lo que se veía: `status` decía `🔓 desbloqueado` y **cada** petición contestaba
|
|
150
|
+
* `profile is not open`. La TUI se quedaba sin dispositivos, sin un error a la vista.
|
|
151
|
+
*
|
|
152
|
+
* Ahora se arranca si hace falta, y **si algo falla se vuelve a cerrar**: un candado que
|
|
153
|
+
* dice «abierto» sin estarlo es peor que uno cerrado, porque todo lo que venga detrás
|
|
154
|
+
* falla en otro sitio y por otro motivo.
|
|
155
|
+
*/
|
|
139
156
|
async function unlock (id, password) {
|
|
140
157
|
const r = await profiles.unlock(id, password)
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
158
|
+
try {
|
|
159
|
+
// El orden importa: `profiles.unlock` ya dejó la llave del perfil disponible
|
|
160
|
+
// (`openKey`), así que ahora `startVault` sí puede abrir su maestra.
|
|
161
|
+
const v = running.get(id) || await open(id)
|
|
162
|
+
// `takeMasterKey` además la deja SELLADA la primera vez: un perfil que ya tenía
|
|
163
|
+
// contraseña guardaba la maestra bajo la llave de máquina, y se migra aquí, que es el
|
|
164
|
+
// único momento en que la llave del perfil está en la mano.
|
|
165
|
+
await v.takeMasterKey()
|
|
166
|
+
} catch (e) {
|
|
167
|
+
try { profiles.lock(id) } catch (_) {}
|
|
168
|
+
throw new Error(`unlocked the profile but could not open it, so it stays closed: ${e.message}`)
|
|
169
|
+
}
|
|
145
170
|
return r
|
|
146
171
|
}
|
|
147
172
|
|
|
@@ -179,7 +204,7 @@ export async function startVaultManager ({ root = dataDir(), proxyUrl, log = con
|
|
|
179
204
|
async add (name, { adopt = false, kek = null } = {}) {
|
|
180
205
|
const p = await profiles.add(name, { adopt, kek, mintKey })
|
|
181
206
|
await open(p.id)
|
|
182
|
-
log('[vault]
|
|
207
|
+
log('[vault] profile created%s: %s (%s)', adopt ? ' (waiting to adopt an account)' : '', p.name || '(no name)', p.id)
|
|
183
208
|
return profiles.get(p.id)
|
|
184
209
|
},
|
|
185
210
|
|
|
@@ -209,7 +234,7 @@ export async function startVaultManager ({ root = dataDir(), proxyUrl, log = con
|
|
|
209
234
|
const res = profiles.remove(id) // valida: no es el único, no está bloqueado
|
|
210
235
|
try { running.get(id)?.close() } catch (_) {}
|
|
211
236
|
running.delete(id)
|
|
212
|
-
log('[vault]
|
|
237
|
+
log('[vault] profile deleted: %s (%s)', res.name || '(no name)', id)
|
|
213
238
|
return res
|
|
214
239
|
},
|
|
215
240
|
|
package/src/passwords.js
CHANGED
|
@@ -79,7 +79,7 @@ export function createPasswordDesk (opts = {}) {
|
|
|
79
79
|
// el criterio del vault es apuntar la operación, no el payload.
|
|
80
80
|
audit('passwords', { op: r.op, outcome: r.outcome, device: (r.from || '').slice(0, 24) })
|
|
81
81
|
if (r.outcome !== 'served') {
|
|
82
|
-
log(
|
|
82
|
+
log(`[vault] passwords: ${r.op} ${r.outcome}`)
|
|
83
83
|
}
|
|
84
84
|
},
|
|
85
85
|
})
|
package/src/transport.js
CHANGED
|
@@ -73,7 +73,7 @@ export async function createTransport ({ identity, dir, url = DEFAULT_PROXY, com
|
|
|
73
73
|
// quedaba en el log era el del repliegue. Así que cuando la consola decía «no
|
|
74
74
|
// contesta», no había forma de saber desde aquí si la bóveda estaba atada al proxio
|
|
75
75
|
// o no. Una línea por reconexión es barata; no tenerla cuesta una hora de sondeos.
|
|
76
|
-
log(
|
|
76
|
+
log(`[vault] identified on the proxy with the communication key · record #${record?.seq ?? '?'}`)
|
|
77
77
|
return
|
|
78
78
|
}
|
|
79
79
|
}
|
|
@@ -82,12 +82,26 @@ export async function createTransport ({ identity, dir, url = DEFAULT_PROXY, com
|
|
|
82
82
|
// comunicación en el acta se identifica con la maestra, como siempre. Exige el perfil
|
|
83
83
|
// ABIERTO —la maestra bajo llave no firma— y por eso se dice en voz alta: mientras se
|
|
84
84
|
// esté aquí, cerrar el perfil deja la bóveda sin voz.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
//
|
|
86
|
+
// QUEDARSE SIN VOZ NO ES NO ARRANCAR, y aquí eran lo mismo. Un perfil con la maestra
|
|
87
|
+
// sellada y sin llave de comunicación todavía llegaba hasta este `signData`, que tira
|
|
88
|
+
// `vault locked`; el error subía por `createTransport` hasta `startVault` y **el perfil
|
|
89
|
+
// entero se quedaba sin abrir**. Al dueño le pasó el 2026-09-02: su perfil principal no
|
|
90
|
+
// arrancaba, `unlock` lo marcaba abierto igual y cada petición contestaba «profile is
|
|
91
|
+
// not open», con la TUI vacía y sin un error a la vista.
|
|
92
|
+
//
|
|
93
|
+
// Se dice y se sigue. Sin red se puede vivir hasta que alguien teclee la frase; sin
|
|
94
|
+
// bóveda, no.
|
|
95
|
+
try {
|
|
96
|
+
const publickey = await masterPubkeyOf(identity)
|
|
97
|
+
if (!publickey) return
|
|
98
|
+
const data = { op: 'identify', publickey, token: client.token, ts: Date.now() }
|
|
99
|
+
const { signature } = await identity.signData(data)
|
|
100
|
+
log('[vault] identifying with the master key: this vault is not in its own record yet (open the profile once to fix it)')
|
|
101
|
+
await client.identify({ data, signature, acta: record })
|
|
102
|
+
} catch (e) {
|
|
103
|
+
log(`[vault] cannot identify on the proxy yet: ${e.message} — the profile still opens; unlock it once and it gets its own communication key`)
|
|
104
|
+
}
|
|
91
105
|
}
|
|
92
106
|
await identify()
|
|
93
107
|
// Re-identificar al reconectar (el token cambia).
|
package/src/vault.js
CHANGED
|
@@ -345,7 +345,8 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
|
|
|
345
345
|
// Escribir aparte y renombrar: a medio convertir, la bitácora sería ilegible entera.
|
|
346
346
|
fs.writeFileSync(activityFile + '.tmp', convertidas.join('\n'), { mode: 0o600 })
|
|
347
347
|
fs.renameSync(activityFile + '.tmp', activityFile)
|
|
348
|
-
|
|
348
|
+
// Sin `%d`: el `log` de la bóveda no formatea, así que el placeholder salía literal.
|
|
349
|
+
log(`[vault] activity log: ${convertidas.filter(Boolean).length} line(s) sealed at rest`)
|
|
349
350
|
}
|
|
350
351
|
// Y el modo, que hasta 0.89 se creaba 0664: legible por cualquier usuario.
|
|
351
352
|
if ((fs.statSync(activityFile).mode & 0o077) !== 0) fs.chmodSync(activityFile, 0o600)
|
|
@@ -1187,7 +1188,7 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
|
|
|
1187
1188
|
audit,
|
|
1188
1189
|
log,
|
|
1189
1190
|
}).start()
|
|
1190
|
-
log(
|
|
1191
|
+
log(`[vault] passwords: serving ${passwordDevices().length} device(s)`)
|
|
1191
1192
|
}
|
|
1192
1193
|
try {
|
|
1193
1194
|
// El acta ANTES de mirarla: es ella la que dice si hay a quién responder, y sin
|
|
@@ -1201,7 +1202,7 @@ export async function startVault ({ dir = dataDir(), proxyUrl, log = console.log
|
|
|
1201
1202
|
// Ni código de enlace ni pegar nada: un gestor entra como cualquier otro aparato.
|
|
1202
1203
|
else log('[vault] passwords: no device may ask yet · pair one with `dotrino-vault pair --scope contrasenas`')
|
|
1203
1204
|
} catch (e) {
|
|
1204
|
-
log(
|
|
1205
|
+
log(`[vault] passwords: could not start (${e.message}); everything else keeps going`)
|
|
1205
1206
|
}
|
|
1206
1207
|
|
|
1207
1208
|
log(`[vault] listo · id ${fp} · ${store.getTree().children.length} nodos`)
|