@dotrino/vaultd 0.7.6 → 0.7.8
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/bin/dotrino-vaultd.js +1 -1
- package/package.json +1 -1
- package/src/ctl.js +2 -2
- package/src/tui/app.js +40 -10
- package/src/tui/i18n.js +4 -2
- package/src/vaultControl.js +3 -3
package/bin/dotrino-vaultd.js
CHANGED
|
@@ -38,7 +38,7 @@ const mgr = await runDaemon()
|
|
|
38
38
|
if (process.argv.includes('--pair')) {
|
|
39
39
|
const { qr, expiresInMs } = mgr.current().startPairing({ label: 'cli' })
|
|
40
40
|
console.log(`\nEmparejá un dispositivo (válido ${expiresInMs / 60000} min):\n`)
|
|
41
|
-
console.log(qrToString(JSON.stringify(qr)))
|
|
41
|
+
console.log(qrToString(JSON.stringify(qr), 2))
|
|
42
42
|
console.log(JSON.stringify(qr))
|
|
43
43
|
}
|
|
44
44
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotrino/vaultd",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.8",
|
|
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/ctl.js
CHANGED
|
@@ -186,11 +186,11 @@ async function cmdPair (args = []) {
|
|
|
186
186
|
const acct = pair.profileName || pair.profile
|
|
187
187
|
if (acct) console.log('\nCuenta que se comparte: %s%s', acct, pair.profileName && pair.profile ? ` (${pair.profile})` : '')
|
|
188
188
|
console.log('\nEscanea este QR con el dispositivo que quieres conectar (válido %d min):\n', mins)
|
|
189
|
-
console.log(qrToString(
|
|
189
|
+
console.log(qrToString(payload, 2)) // QR con el JSON crudo (menos datos) y zona de silencio reducida
|
|
190
190
|
console.log(`${R}${B}⚠ Este código deja LEER tus datos y FIRMAR con tu identidad.${Z}`)
|
|
191
191
|
console.log(`${R} NO lo compartas con nadie, ni con "soporte". Solo escanéalo en TU dispositivo.${Z}`)
|
|
192
192
|
console.log('\nO abre esta dirección en el dispositivo:\n ' + url)
|
|
193
|
-
console.log('\nO pega este código en vault.dotrino.com/dispositivos :\n ' +
|
|
193
|
+
console.log('\nO pega este código en vault.dotrino.com/dispositivos :\n ' + b64)
|
|
194
194
|
|
|
195
195
|
// --save [archivo]: escribe la invitación (.dpair) para transferirla y abrirla en profile.
|
|
196
196
|
const saveIdx = args.indexOf('--save')
|
package/src/tui/app.js
CHANGED
|
@@ -118,6 +118,19 @@ function renderList (rows, selIdx, height, cols, t, scrollRef) {
|
|
|
118
118
|
return out
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Scroll simple para una lista de líneas planas (no seleccionables). Igual idea
|
|
123
|
+
* que `renderList`, pero sin índice de selección: solo desplaza la ventana.
|
|
124
|
+
*/
|
|
125
|
+
function scrollBody (lines, height, scrollRef) {
|
|
126
|
+
let top = scrollRef.value || 0
|
|
127
|
+
top = Math.max(0, Math.min(top, Math.max(0, lines.length - height)))
|
|
128
|
+
scrollRef.value = top
|
|
129
|
+
const out = []
|
|
130
|
+
for (let i = 0; i < height; i++) out.push(lines[top + i] ?? '')
|
|
131
|
+
return out
|
|
132
|
+
}
|
|
133
|
+
|
|
121
134
|
/**
|
|
122
135
|
* Barra de ayuda que SIEMPRE deja ver lo global (idioma y salir): si los segmentos
|
|
123
136
|
* no caben, recorta desde el MEDIO y marca el corte con «…». Sin esto, en 80
|
|
@@ -450,7 +463,7 @@ async function onKeyDevices (term, st, key) {
|
|
|
450
463
|
/** Abre el emparejamiento contra `profile` y salta a la pantalla del QR. */
|
|
451
464
|
async function beginPairing (term, st, profile) {
|
|
452
465
|
const r = await guard(term, st, L(st).startingPairing, () => vc.startPairing({ profile }))
|
|
453
|
-
if (r.ok) { st.pairing = r.v; st.pending = null; st.screen = 'pairing' }
|
|
466
|
+
if (r.ok) { st.pairing = r.v; st.pending = null; st.scroll.pairing = { value: 0 }; st.screen = 'pairing' }
|
|
454
467
|
return r.ok
|
|
455
468
|
}
|
|
456
469
|
|
|
@@ -514,6 +527,21 @@ async function onKeyPairing (term, st, key) {
|
|
|
514
527
|
if (pend) st.pending = pend
|
|
515
528
|
return true
|
|
516
529
|
}
|
|
530
|
+
// Scroll vertical para ver el QR completo cuando no cabe en la pantalla.
|
|
531
|
+
if (['up', 'down', 'pageup', 'pagedown', 'home', 'end'].includes(key.name)) {
|
|
532
|
+
const { rows } = term.size()
|
|
533
|
+
const contentH = Math.max(1, rows - 7)
|
|
534
|
+
const pb = pairingBody(st, term.t, term.size().cols, contentH)
|
|
535
|
+
const maxScroll = Math.max(0, pb.length - contentH)
|
|
536
|
+
const scroll = st.scroll.pairing || (st.scroll.pairing = { value: 0 })
|
|
537
|
+
if (key.name === 'up') scroll.value = Math.max(0, scroll.value - 1)
|
|
538
|
+
else if (key.name === 'down') scroll.value = Math.min(maxScroll, scroll.value + 1)
|
|
539
|
+
else if (key.name === 'pageup') scroll.value = Math.max(0, scroll.value - 5)
|
|
540
|
+
else if (key.name === 'pagedown') scroll.value = Math.min(maxScroll, scroll.value + 5)
|
|
541
|
+
else if (key.name === 'home') scroll.value = 0
|
|
542
|
+
else if (key.name === 'end') scroll.value = maxScroll
|
|
543
|
+
return true
|
|
544
|
+
}
|
|
517
545
|
if (ch === 'a' && st.pending) { promptApprove(term, st); return true }
|
|
518
546
|
if (ch === 'x' && st.pending) {
|
|
519
547
|
const r = await guard(term, st, i.rejecting, () => vc.rejectPending(st.pending.deviceId, activeId(st)))
|
|
@@ -522,7 +550,7 @@ async function onKeyPairing (term, st, key) {
|
|
|
522
550
|
}
|
|
523
551
|
if (ch === 'r') { // restart: reiniciar el emparejamiento
|
|
524
552
|
const r = await guard(term, st, i.restartingPairing, () => vc.startPairing({ profile: activeId(st) }))
|
|
525
|
-
if (r.ok) { st.pairing = r.v; st.pending = null }
|
|
553
|
+
if (r.ok) { st.pairing = r.v; st.pending = null; st.scroll.pairing = { value: 0 } }
|
|
526
554
|
return true
|
|
527
555
|
}
|
|
528
556
|
if (key.name === 'escape' || ch === 'b') { st.screen = 'devices'; st.pairing = null; await refreshDevices(term, st) }
|
|
@@ -670,20 +698,23 @@ function pairingBody (st, t, cols, height) {
|
|
|
670
698
|
const left = Math.max(0, Math.round((info.expiresAt - Date.now()) / 60000))
|
|
671
699
|
lines.push(t.muted(i.pairValid(left)))
|
|
672
700
|
lines.push('')
|
|
673
|
-
// QR
|
|
701
|
+
// QR: se dibuja siempre que quepa de ancho; si es más alto que la pantalla se
|
|
702
|
+
// puede hacer scroll hacia arriba/abajo para verlo completo.
|
|
674
703
|
let qr = ''
|
|
675
|
-
try { qr = qrToString(info.
|
|
704
|
+
try { qr = qrToString(info.payload, 2) } catch (_) {}
|
|
676
705
|
const qrLines = qr ? qr.replace(/\n$/, '').split('\n') : []
|
|
677
706
|
const qrWidth = qrLines.length ? Math.max(...qrLines.map((l) => l.replace(/\x1b\[[0-9;]*m/g, '').length)) : 0
|
|
678
|
-
|
|
679
|
-
if (qrLines.length && qrWidth <= cols && qrLines.length <= height - reserved) {
|
|
707
|
+
if (qrLines.length && qrWidth <= cols) {
|
|
680
708
|
for (const l of qrLines) lines.push(l)
|
|
681
709
|
lines.push('')
|
|
710
|
+
} else if (qrLines.length) {
|
|
711
|
+
lines.push(t.warn(i.pairQrTooNarrow(cols, qrWidth)))
|
|
712
|
+
lines.push('')
|
|
682
713
|
}
|
|
683
714
|
lines.push(t.bold(i.pairUrl) + info.url)
|
|
684
715
|
lines.push('')
|
|
685
716
|
lines.push(t.muted(i.pairPaste))
|
|
686
|
-
lines.push(info.payload)
|
|
717
|
+
lines.push(info.b64 || info.payload)
|
|
687
718
|
lines.push('')
|
|
688
719
|
lines.push(t.danger(i.pairWarning))
|
|
689
720
|
lines.push('')
|
|
@@ -731,8 +762,7 @@ function render (term, st) {
|
|
|
731
762
|
else if (st.screen === 'pairmode') body = renderList(pairModeRows(st, t), st.sel.pairmode, contentH, cols, t, scrollRef)
|
|
732
763
|
else if (st.screen === 'pairing') {
|
|
733
764
|
const pb = pairingBody(st, t, cols, contentH)
|
|
734
|
-
body = pb
|
|
735
|
-
while (body.length < contentH) body.push('')
|
|
765
|
+
body = scrollBody(pb, contentH, scrollRef)
|
|
736
766
|
}
|
|
737
767
|
for (let n = 0; n < contentH; n++) lines[top + n] = body[n] ?? ''
|
|
738
768
|
|
|
@@ -887,4 +917,4 @@ export async function runTui () {
|
|
|
887
917
|
}
|
|
888
918
|
|
|
889
919
|
// Solo para pruebas headless (render sin terminal real). No usar en runtime.
|
|
890
|
-
export const __test = { render, profileRows, deviceRows, secretRows, pairModeRows, pairingBody, fitHelp, toggleLang }
|
|
920
|
+
export const __test = { render, profileRows, deviceRows, secretRows, pairModeRows, pairingBody, scrollBody, fitHelp, toggleLang }
|
package/src/tui/i18n.js
CHANGED
|
@@ -141,6 +141,7 @@ const es = {
|
|
|
141
141
|
pairWarning: '⚠ Este código deja LEER tus datos y FIRMAR con tu identidad. No lo compartas.',
|
|
142
142
|
pairConnected: (id) => `⧗ Se conectó: ${id} — pulsa A y escribe el código que muestra.`,
|
|
143
143
|
pairWaiting: 'Esperando a que el dispositivo se conecte…',
|
|
144
|
+
pairQrTooNarrow: (cols, need) => `Agranda el terminal (el QR necesita ${need} cols; hay ${cols}).`,
|
|
144
145
|
|
|
145
146
|
// confirmación / entrada
|
|
146
147
|
confirmKeys: ' (s / N)',
|
|
@@ -154,7 +155,7 @@ const es = {
|
|
|
154
155
|
helpProfiles: ['↑↓', 'Enter entrar', 'p emparejar', 'n nueva', 'r renombrar', 'd borrar', 'c clave', 'x quitar-clave', 'u desbloq', 'k bloquear', 'l English', 'q salir'],
|
|
155
156
|
helpDevices: ['←→ pestaña', '↑↓', 'p emparejar', 'a aprobar', 'x rechazar', 'v revocar', 'r refrescar', 'Esc bóvedas', 'l English', 'q salir'],
|
|
156
157
|
helpSecrets: ['←→ pestaña', '↑↓', 'n nueva variable', 'x quitar (variable/scope)', 'r refrescar', 'Esc bóvedas', 'l English', 'q salir'],
|
|
157
|
-
helpPairing: ['a aprobar', 'x rechazar', 'r reiniciar', 'Esc atrás', 'l English'],
|
|
158
|
+
helpPairing: ['a aprobar', 'x rechazar', 'r reiniciar', '↑↓ scroll', 'Esc atrás', 'l English'],
|
|
158
159
|
helpPairMode: ['↑↓', 'Enter elegir', 'Esc atrás', 'l English', 'q salir'],
|
|
159
160
|
|
|
160
161
|
// pantalla "daemon caído"
|
|
@@ -303,6 +304,7 @@ const en = {
|
|
|
303
304
|
pairWarning: '⚠ This code lets someone READ your data and SIGN as you. Do not share it.',
|
|
304
305
|
pairConnected: (id) => `⧗ Connected: ${id} — press A and type the code it shows.`,
|
|
305
306
|
pairWaiting: 'Waiting for the device to connect…',
|
|
307
|
+
pairQrTooNarrow: (cols, need) => `Widen the terminal (the QR needs ${need} cols; you have ${cols}).`,
|
|
306
308
|
|
|
307
309
|
confirmKeys: ' (y / N)',
|
|
308
310
|
helpInput: 'Enter confirm · Esc cancel · Ctrl-U clear',
|
|
@@ -311,7 +313,7 @@ const en = {
|
|
|
311
313
|
helpProfiles: ['↑↓', 'Enter open', 'p pair', 'n new', 'r rename', 'd delete', 'c password', 'x drop-password', 'u unlock', 'k lock', 'l Español', 'q quit'],
|
|
312
314
|
helpDevices: ['←→ tab', '↑↓', 'p pair', 'a approve', 'x reject', 'v revoke', 'r refresh', 'Esc vaults', 'l Español', 'q quit'],
|
|
313
315
|
helpSecrets: ['←→ tab', '↑↓', 'n new variable', 'x remove (variable/scope)', 'r refresh', 'Esc vaults', 'l Español', 'q quit'],
|
|
314
|
-
helpPairing: ['a approve', 'x reject', 'r restart', 'Esc back', 'l Español'],
|
|
316
|
+
helpPairing: ['a approve', 'x reject', 'r restart', '↑↓ scroll', 'Esc back', 'l Español'],
|
|
315
317
|
helpPairMode: ['↑↓', 'Enter choose', 'Esc back', 'l Español', 'q quit'],
|
|
316
318
|
|
|
317
319
|
downTitle: 'The vault daemon is not running.',
|
package/src/vaultControl.js
CHANGED
|
@@ -262,7 +262,7 @@ export function pairUrl (qr) {
|
|
|
262
262
|
const payload = JSON.stringify(qr)
|
|
263
263
|
const b64 = Buffer.from(payload, 'utf8').toString('base64')
|
|
264
264
|
.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
|
|
265
|
-
return { url: PROFILE_URL + b64, payload }
|
|
265
|
+
return { url: PROFILE_URL + b64, payload, b64 }
|
|
266
266
|
}
|
|
267
267
|
|
|
268
268
|
/**
|
|
@@ -279,11 +279,11 @@ export async function startPairing ({ profile, service } = {}) {
|
|
|
279
279
|
await sleep(100)
|
|
280
280
|
const pr = read(F.pair, null)
|
|
281
281
|
if (pr?.expiresAt > Date.now()) {
|
|
282
|
-
const { url, payload } = pairUrl(pr.qr)
|
|
282
|
+
const { url, payload, b64 } = pairUrl(pr.qr)
|
|
283
283
|
// `profile`/`profileName`: DE QUÉ CUENTA del vault sale este QR. El vault
|
|
284
284
|
// puede tener varias y el emparejamiento mete al dispositivo en UNA; la TUI
|
|
285
285
|
// y la CLI lo muestran para que no se enrole en la equivocada.
|
|
286
|
-
return { qr: pr.qr, expiresAt: pr.expiresAt, url, payload, profile: pr.profile || null, profileName: pr.profileName || '' }
|
|
286
|
+
return { qr: pr.qr, expiresAt: pr.expiresAt, url, payload, b64, profile: pr.profile || null, profileName: pr.profileName || '' }
|
|
287
287
|
}
|
|
288
288
|
}
|
|
289
289
|
throw coded('el daemon no inició el emparejamiento', 'PAIR_FAILED')
|