@dotrino/vaultd 0.18.0 → 0.19.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/tui/app.js +94 -8
- package/src/tui/i18n.js +55 -10
- package/src/tui/term.js +10 -2
- package/src/vaultControl.js +33 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotrino/vaultd",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.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/tui/app.js
CHANGED
|
@@ -213,6 +213,37 @@ function pairModeRows (st, t) {
|
|
|
213
213
|
return rows
|
|
214
214
|
}
|
|
215
215
|
|
|
216
|
+
/**
|
|
217
|
+
* PERMISOS de un dispositivo. Los cuatro que existen, con lo que significan en cristiano y
|
|
218
|
+
* una marca de si los tiene. El de administrar va aparte y avisado: es el único que deja
|
|
219
|
+
* a ese aparato meter y sacar dispositivos sin venir aquí.
|
|
220
|
+
*/
|
|
221
|
+
const CAPS_ORDEN = ['sign', 'store', 'read', 'admin']
|
|
222
|
+
|
|
223
|
+
function capsRows (st, t) {
|
|
224
|
+
const i = L(st)
|
|
225
|
+
const objetivo = st.capsFor
|
|
226
|
+
if (!objetivo) return [{ text: t.muted(i.loading), sel: false }]
|
|
227
|
+
const miembro = (st.members || []).find((m) => m.pub === objetivo.pub)
|
|
228
|
+
if (!miembro) return [{ text: t.muted(i.capsNoMember), sel: false }]
|
|
229
|
+
|
|
230
|
+
const tiene = new Set(miembro.caps || [])
|
|
231
|
+
const rows = [
|
|
232
|
+
{ text: ' ' + t.bold(i.capsFor(objetivo.deviceId, miembro.label || '')), sel: false },
|
|
233
|
+
{ text: '', sel: false }
|
|
234
|
+
]
|
|
235
|
+
for (const cap of CAPS_ORDEN) {
|
|
236
|
+
const marca = tiene.has(cap) ? '[x]' : '[ ]'
|
|
237
|
+
const nombre = i.capName[cap]
|
|
238
|
+
const linea = ` ${marca} ${cap === 'admin' ? t.bold(nombre) : nombre}`
|
|
239
|
+
rows.push({ text: linea, sel: true, meta: { cap } })
|
|
240
|
+
rows.push({ text: t.muted(' ' + i.capHint[cap]), sel: false })
|
|
241
|
+
}
|
|
242
|
+
rows.push({ text: '', sel: false })
|
|
243
|
+
rows.push({ text: t.muted(' ' + i.capsApplyHint), sel: false })
|
|
244
|
+
return rows
|
|
245
|
+
}
|
|
246
|
+
|
|
216
247
|
function secretRows (st, t) {
|
|
217
248
|
const i = L(st)
|
|
218
249
|
const ns = st.secrets || {}
|
|
@@ -306,6 +337,10 @@ async function refreshSecrets (term, st) {
|
|
|
306
337
|
const r = await guard(term, st, L(st).loadingSecrets, () => vc.listSecrets(activeId(st)))
|
|
307
338
|
if (r.ok) st.secrets = r.v
|
|
308
339
|
}
|
|
340
|
+
async function refreshMembers (term, st) {
|
|
341
|
+
const r = await guard(term, st, L(st).loadingMembers, () => vc.listMembers(activeId(st)))
|
|
342
|
+
if (r.ok) st.members = r.v
|
|
343
|
+
}
|
|
309
344
|
async function refreshMe (term, st) {
|
|
310
345
|
const r = await guard(term, st, L(st).loadingProfile, () => vc.getMe(activeId(st)))
|
|
311
346
|
st.me = r.ok ? r.v : null
|
|
@@ -498,9 +533,10 @@ async function onKeyDevices (term, st, key) {
|
|
|
498
533
|
},
|
|
499
534
|
onNo: () => { st.confirm = null }
|
|
500
535
|
})
|
|
501
|
-
} else if (ch === '
|
|
536
|
+
} else if (ch === 'r' && cur) {
|
|
502
537
|
// Renombrar: el nombre lo trae el aparato al emparejarse (y si no le diste uno, entra
|
|
503
|
-
// con TU apodo de ese momento), así que a la semana ya no dice nada.
|
|
538
|
+
// con TU apodo de ese momento), así que a la semana ya no dice nada. `r` es renombrar
|
|
539
|
+
// también en Bóvedas: una tecla, un significado.
|
|
504
540
|
setInput(st, {
|
|
505
541
|
label: i.renameDeviceLabel(cur.deviceId),
|
|
506
542
|
hint: i.renameDeviceHint,
|
|
@@ -512,7 +548,12 @@ async function onKeyDevices (term, st, key) {
|
|
|
512
548
|
if (r.ok) { st.devices = r.v; flash(st, i.deviceRenamed(nombre)) }
|
|
513
549
|
}
|
|
514
550
|
})
|
|
515
|
-
} else if (ch === '
|
|
551
|
+
} else if (ch === 'c' && cur?.sub) {
|
|
552
|
+
st.capsFor = { pub: cur.sub, deviceId: cur.deviceId }
|
|
553
|
+
st.sel.caps = 0
|
|
554
|
+
await refreshMembers(term, st)
|
|
555
|
+
st.screen = 'caps'
|
|
556
|
+
} else if (key.name === 'f5') {
|
|
516
557
|
await refreshDevices(term, st)
|
|
517
558
|
}
|
|
518
559
|
return true
|
|
@@ -525,6 +566,47 @@ async function beginPairing (term, st, profile) {
|
|
|
525
566
|
return r.ok
|
|
526
567
|
}
|
|
527
568
|
|
|
569
|
+
/**
|
|
570
|
+
* Enter marca o desmarca un permiso y lo aplica en el acto. Sin botón de «guardar»: cada
|
|
571
|
+
* cambio se sella en el acta y se avisa a los demás aparatos, así que acumularlos en
|
|
572
|
+
* pantalla solo serviría para que el acta y lo que ves dijeran cosas distintas.
|
|
573
|
+
*/
|
|
574
|
+
async function onKeyCaps (term, st, key) {
|
|
575
|
+
const i = L(st)
|
|
576
|
+
const rows = capsRows(st, term.t)
|
|
577
|
+
const sels = rows.filter((r) => r.sel).map((r) => r.meta)
|
|
578
|
+
moveSel(st, key, 'caps', sels.length)
|
|
579
|
+
const cur = sels[Math.min(st.sel.caps || 0, sels.length - 1)]
|
|
580
|
+
const ch = key.name === 'char' ? key.ch.toLowerCase() : null
|
|
581
|
+
|
|
582
|
+
if (key.name === 'escape' || ch === 'b') { st.screen = 'devices'; st.capsFor = null; return true }
|
|
583
|
+
if (key.name === 'f5') { await refreshMembers(term, st); return true }
|
|
584
|
+
if ((key.name !== 'enter' && ch !== ' ') || !cur) return true
|
|
585
|
+
|
|
586
|
+
const miembro = (st.members || []).find((m) => m.pub === st.capsFor?.pub)
|
|
587
|
+
if (!miembro) return true
|
|
588
|
+
const caps = new Set(miembro.caps || [])
|
|
589
|
+
const dando = !caps.has(cur.cap)
|
|
590
|
+
if (dando) caps.add(cur.cap); else caps.delete(cur.cap)
|
|
591
|
+
|
|
592
|
+
const aplicar = async () => {
|
|
593
|
+
const r = await guard(term, st, i.applyingCaps, () => vc.setDeviceCaps(miembro.pub, [...caps], activeId(st)))
|
|
594
|
+
if (!r.ok) return
|
|
595
|
+
st.devices = r.v
|
|
596
|
+
await refreshMembers(term, st)
|
|
597
|
+
flash(st, dando ? i.capGiven(i.capName[cur.cap]) : i.capTaken(i.capName[cur.cap]))
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// Administrar se PREGUNTA: es el permiso que deja a ese aparato admitir y expulsar
|
|
601
|
+
// dispositivos sin pasar por aquí. Los otros tres se marcan y ya.
|
|
602
|
+
if (cur.cap === 'admin' && dando) {
|
|
603
|
+
setConfirm(st, { text: i.confirmAdmin(st.capsFor.deviceId), onYes: aplicar })
|
|
604
|
+
return true
|
|
605
|
+
}
|
|
606
|
+
await aplicar()
|
|
607
|
+
return true
|
|
608
|
+
}
|
|
609
|
+
|
|
528
610
|
async function onKeyPairMode (term, st, key) {
|
|
529
611
|
const i = L(st)
|
|
530
612
|
const rows = pairModeRows(st, term.t)
|
|
@@ -620,7 +702,7 @@ async function onKeyPairing (term, st, key) {
|
|
|
620
702
|
* que usas, no en la máquina donde vive la bóveda.
|
|
621
703
|
*/
|
|
622
704
|
async function onKeyMe (term, st, key) {
|
|
623
|
-
if (key.name === '
|
|
705
|
+
if (key.name === 'f5') await refreshMe(term, st)
|
|
624
706
|
return true
|
|
625
707
|
}
|
|
626
708
|
|
|
@@ -657,7 +739,7 @@ async function onKeySecrets (term, st, key) {
|
|
|
657
739
|
onNo: () => { st.confirm = null }
|
|
658
740
|
})
|
|
659
741
|
}
|
|
660
|
-
} else if (
|
|
742
|
+
} else if (key.name === 'f5') {
|
|
661
743
|
await refreshSecrets(term, st)
|
|
662
744
|
}
|
|
663
745
|
return true
|
|
@@ -741,7 +823,8 @@ const helpSegs = (i, screen, st = {}) => {
|
|
|
741
823
|
secrets: i.helpSecrets,
|
|
742
824
|
pairing: i.helpPairing,
|
|
743
825
|
pairmode: i.helpPairMode,
|
|
744
|
-
me: i.helpMe
|
|
826
|
+
me: i.helpMe,
|
|
827
|
+
caps: i.helpCaps
|
|
745
828
|
}[screen] || []
|
|
746
829
|
if (typeof segs !== 'function') return segs
|
|
747
830
|
return segs({
|
|
@@ -754,7 +837,8 @@ const helpSegs = (i, screen, st = {}) => {
|
|
|
754
837
|
const title = (i, screen) => ({
|
|
755
838
|
profiles: i.titleProfiles,
|
|
756
839
|
pairing: i.titlePairing,
|
|
757
|
-
pairmode: i.titlePairMode
|
|
840
|
+
pairmode: i.titlePairMode,
|
|
841
|
+
caps: i.titleCaps
|
|
758
842
|
})[screen] || ''
|
|
759
843
|
|
|
760
844
|
/** Barra de pestañas horizontal (Dispositivos | Scopes y variables) de la bóveda entrada. */
|
|
@@ -840,6 +924,7 @@ function render (term, st) {
|
|
|
840
924
|
else if (st.screen === 'devices') body = renderList(deviceRows(st, t), st.sel.devices, contentH, cols, t, scrollRef)
|
|
841
925
|
else if (st.screen === 'secrets') body = renderList(secretRows(st, t), st.sel.secrets, contentH, cols, t, scrollRef)
|
|
842
926
|
else if (st.screen === 'me') body = renderList(meRows(st, t), -1, contentH, cols, t, scrollRef)
|
|
927
|
+
else if (st.screen === 'caps') body = renderList(capsRows(st, t), st.sel.caps || 0, contentH, cols, t, scrollRef)
|
|
843
928
|
else if (st.screen === 'pairmode') body = renderList(pairModeRows(st, t), st.sel.pairmode, contentH, cols, t, scrollRef)
|
|
844
929
|
else if (st.screen === 'pairing') {
|
|
845
930
|
const pb = pairingBody(st, t, cols, contentH)
|
|
@@ -993,6 +1078,7 @@ export async function runTui () {
|
|
|
993
1078
|
else if (st.screen === 'devices') running = await onKeyDevices(term, st, key)
|
|
994
1079
|
else if (st.screen === 'secrets') running = await onKeySecrets(term, st, key)
|
|
995
1080
|
else if (st.screen === 'me') running = await onKeyMe(term, st, key)
|
|
1081
|
+
else if (st.screen === 'caps') running = await onKeyCaps(term, st, key)
|
|
996
1082
|
else if (st.screen === 'pairmode') running = await onKeyPairMode(term, st, key)
|
|
997
1083
|
else if (st.screen === 'pairing') running = await onKeyPairing(term, st, key)
|
|
998
1084
|
}
|
|
@@ -1002,4 +1088,4 @@ export async function runTui () {
|
|
|
1002
1088
|
}
|
|
1003
1089
|
|
|
1004
1090
|
// Solo para pruebas headless (render sin terminal real). No usar en runtime.
|
|
1005
|
-
export const __test = { render, profileRows, deviceRows, secretRows, meRows, pairModeRows, pairingBody, scrollBody, fitHelp, toggleLang }
|
|
1091
|
+
export const __test = { render, profileRows, deviceRows, secretRows, meRows, capsRows, pairModeRows, pairingBody, scrollBody, fitHelp, toggleLang }
|
package/src/tui/i18n.js
CHANGED
|
@@ -35,11 +35,34 @@ const es = {
|
|
|
35
35
|
tabDevices: 'Dispositivos',
|
|
36
36
|
tabSecrets: 'Scopes y variables',
|
|
37
37
|
tabMe: 'Perfil',
|
|
38
|
+
// PERMISOS de un dispositivo (§9.1: se dice el beneficio, no el scope del cert).
|
|
39
|
+
titleCaps: 'Permisos del dispositivo',
|
|
40
|
+
capsFor: (id, nombre) => `Permisos de ${id}${nombre ? ' · ' + nombre : ''}`,
|
|
41
|
+
capsNoMember: ' (este dispositivo ya no está en el acta)',
|
|
42
|
+
capsApplyHint: 'Enter marca o desmarca. Cada cambio se aplica y se avisa a tus otros aparatos.',
|
|
43
|
+
capName: {
|
|
44
|
+
sign: 'Firmar como tú',
|
|
45
|
+
store: 'Guardar tus datos',
|
|
46
|
+
read: 'Leer tus datos',
|
|
47
|
+
admin: 'Administrar el perfil'
|
|
48
|
+
},
|
|
49
|
+
capHint: {
|
|
50
|
+
sign: 'usar tu identidad en las apps del ecosistema',
|
|
51
|
+
store: 'escribir en tu bóveda (perfil, contenido, datos sensibles)',
|
|
52
|
+
read: 'ver lo que guardaste',
|
|
53
|
+
admin: 'conectar y quitar dispositivos desde ese aparato, sin venir aquí'
|
|
54
|
+
},
|
|
55
|
+
confirmAdmin: (id) => `¿Dejar que ${id} conecte y quite dispositivos sin venir aquí?`,
|
|
56
|
+
capGiven: (n) => `Concedido: ${n}`,
|
|
57
|
+
capTaken: (n) => `Quitado: ${n}`,
|
|
58
|
+
applyingCaps: 'Aplicando…',
|
|
59
|
+
loadingMembers: 'Cargando el acta…',
|
|
60
|
+
helpCaps: ['↑↓', 'Enter marcar', 'F5 refrescar', 'Esc dispositivos', 'l English', 'q salir'],
|
|
38
61
|
// Perfil del usuario (lo que sincronizan los dispositivos). Solo lectura: se edita en
|
|
39
62
|
// el aparato, no en la máquina donde vive la bóveda.
|
|
40
63
|
loadingProfile: 'Cargando el perfil…',
|
|
41
64
|
noProfile: ' (esta bóveda todavía no tiene perfil)',
|
|
42
|
-
noProfileHint: ' Edita tu nombre o tu foto en un dispositivo emparejado y pulsa
|
|
65
|
+
noProfileHint: ' Edita tu nombre o tu foto en un dispositivo emparejado y pulsa F5.',
|
|
43
66
|
profileUpdated: (cuando) => ` actualizado ${cuando}`,
|
|
44
67
|
fieldName: 'nombre',
|
|
45
68
|
fieldPhoto: 'foto',
|
|
@@ -53,7 +76,7 @@ const es = {
|
|
|
53
76
|
hidden: ' (oculto)',
|
|
54
77
|
noName: '(sin nombre)',
|
|
55
78
|
no: 'no',
|
|
56
|
-
helpMe: ['←→ pestaña', '
|
|
79
|
+
helpMe: ['←→ pestaña', 'F5 refrescar', 'Esc bóvedas', 'l English', 'q salir'],
|
|
57
80
|
tabsHint: ' (←→ cambiar)',
|
|
58
81
|
titleProfiles: 'Bóvedas',
|
|
59
82
|
titlePairing: 'Emparejar un dispositivo',
|
|
@@ -181,8 +204,8 @@ const es = {
|
|
|
181
204
|
helpDevices: ({ pendiente, hayAparatos } = {}) => [
|
|
182
205
|
'←→ pestaña', '↑↓', 'p emparejar',
|
|
183
206
|
...(pendiente ? ['a aprobar', 'x rechazar'] : []),
|
|
184
|
-
...(hayAparatos ? ['
|
|
185
|
-
'
|
|
207
|
+
...(hayAparatos ? ['r renombrar', 'c permisos', 'v revocar'] : []),
|
|
208
|
+
'F5 refrescar', 'Esc bóvedas', 'l English', 'q salir'
|
|
186
209
|
],
|
|
187
210
|
renameDeviceLabel: (id) => `¿Cómo quieres llamar a ${id}?`,
|
|
188
211
|
renameDeviceHint: 'el nombre con el que lo reconoces (Esc cancela)',
|
|
@@ -191,7 +214,7 @@ const es = {
|
|
|
191
214
|
helpSecrets: ({ haySecretos } = {}) => [
|
|
192
215
|
'←→ pestaña', '↑↓', 'n nueva variable',
|
|
193
216
|
...(haySecretos ? ['x quitar (variable/scope)'] : []),
|
|
194
|
-
'
|
|
217
|
+
'F5 refrescar', 'Esc bóvedas', 'l English', 'q salir'
|
|
195
218
|
],
|
|
196
219
|
helpPairing: ['a aprobar', 'x rechazar', 'r reiniciar', '↑↓ scroll', 'Esc atrás', 'l English'],
|
|
197
220
|
helpPairMode: ['↑↓', 'Enter elegir', 'Esc atrás', 'l English', 'q salir'],
|
|
@@ -240,9 +263,31 @@ const en = {
|
|
|
240
263
|
tabDevices: 'Devices',
|
|
241
264
|
tabSecrets: 'Scopes & variables',
|
|
242
265
|
tabMe: 'Profile',
|
|
266
|
+
titleCaps: 'Device permissions',
|
|
267
|
+
capsFor: (id, nombre) => `Permissions for ${id}${nombre ? ' · ' + nombre : ''}`,
|
|
268
|
+
capsNoMember: ' (this device is no longer in the record)',
|
|
269
|
+
capsApplyHint: 'Enter ticks or unticks. Each change applies and your other devices are told.',
|
|
270
|
+
capName: {
|
|
271
|
+
sign: 'Sign as you',
|
|
272
|
+
store: 'Save your data',
|
|
273
|
+
read: 'Read your data',
|
|
274
|
+
admin: 'Manage the profile'
|
|
275
|
+
},
|
|
276
|
+
capHint: {
|
|
277
|
+
sign: 'use your identity across the ecosystem apps',
|
|
278
|
+
store: 'write to your vault (profile, content, sensitive data)',
|
|
279
|
+
read: 'see what you saved',
|
|
280
|
+
admin: 'connect and remove devices from that device, without coming here'
|
|
281
|
+
},
|
|
282
|
+
confirmAdmin: (id) => `Let ${id} connect and remove devices without coming here?`,
|
|
283
|
+
capGiven: (n) => `Granted: ${n}`,
|
|
284
|
+
capTaken: (n) => `Removed: ${n}`,
|
|
285
|
+
applyingCaps: 'Applying…',
|
|
286
|
+
loadingMembers: 'Loading the record…',
|
|
287
|
+
helpCaps: ['↑↓', 'Enter tick', 'F5 refresh', 'Esc devices', 'l Español', 'q quit'],
|
|
243
288
|
loadingProfile: 'Loading profile…',
|
|
244
289
|
noProfile: ' (this vault has no profile yet)',
|
|
245
|
-
noProfileHint: ' Edit your name or photo on a paired device and press
|
|
290
|
+
noProfileHint: ' Edit your name or photo on a paired device and press F5.',
|
|
246
291
|
profileUpdated: (cuando) => ` updated ${cuando}`,
|
|
247
292
|
fieldName: 'name',
|
|
248
293
|
fieldPhoto: 'photo',
|
|
@@ -256,7 +301,7 @@ const en = {
|
|
|
256
301
|
hidden: ' (hidden)',
|
|
257
302
|
noName: '(no name)',
|
|
258
303
|
no: 'no',
|
|
259
|
-
helpMe: ['←→ tab', '
|
|
304
|
+
helpMe: ['←→ tab', 'F5 refresh', 'Esc vaults', 'l Español', 'q quit'],
|
|
260
305
|
tabsHint: ' (←→ switch)',
|
|
261
306
|
titleProfiles: 'Vaults',
|
|
262
307
|
titlePairing: 'Pair a device',
|
|
@@ -370,8 +415,8 @@ const en = {
|
|
|
370
415
|
helpDevices: ({ pendiente, hayAparatos } = {}) => [
|
|
371
416
|
'←→ tab', '↑↓', 'p pair',
|
|
372
417
|
...(pendiente ? ['a approve', 'x reject'] : []),
|
|
373
|
-
...(hayAparatos ? ['
|
|
374
|
-
'
|
|
418
|
+
...(hayAparatos ? ['r rename', 'c permissions', 'v revoke'] : []),
|
|
419
|
+
'F5 refresh', 'Esc vaults', 'l Español', 'q quit'
|
|
375
420
|
],
|
|
376
421
|
renameDeviceLabel: (id) => `What do you want to call ${id}?`,
|
|
377
422
|
renameDeviceHint: 'the name you recognise it by (Esc cancels)',
|
|
@@ -380,7 +425,7 @@ const en = {
|
|
|
380
425
|
helpSecrets: ({ haySecretos } = {}) => [
|
|
381
426
|
'←→ tab', '↑↓', 'n new variable',
|
|
382
427
|
...(haySecretos ? ['x remove (variable/scope)'] : []),
|
|
383
|
-
'
|
|
428
|
+
'F5 refresh', 'Esc vaults', 'l Español', 'q quit'
|
|
384
429
|
],
|
|
385
430
|
helpPairing: ['a approve', 'x reject', 'r restart', '↑↓ scroll', 'Esc back', 'l Español'],
|
|
386
431
|
helpPairMode: ['↑↓', 'Enter choose', 'Esc back', 'l Español', 'q quit'],
|
package/src/tui/term.js
CHANGED
|
@@ -124,7 +124,13 @@ export function makeTheme () {
|
|
|
124
124
|
|
|
125
125
|
// --------------------------------- teclado ----------------------------------
|
|
126
126
|
|
|
127
|
-
|
|
127
|
+
/**
|
|
128
|
+
* Decodifica un trozo de stdin en teclas. Exportado SOLO para poder probarlo: vivía
|
|
129
|
+
* encerrado en `createTerm`, así que las secuencias de escape (flechas, F5, supr) no las
|
|
130
|
+
* comprobaba nadie — y una tabla mal escrita ahí no falla, simplemente la tecla no hace
|
|
131
|
+
* nada, que es la avería más difícil de ver.
|
|
132
|
+
*/
|
|
133
|
+
export function parseChunk (s, push) {
|
|
128
134
|
let i = 0
|
|
129
135
|
const arrow = { A: 'up', B: 'down', C: 'right', D: 'left', H: 'home', F: 'end' }
|
|
130
136
|
while (i < s.length) {
|
|
@@ -138,7 +144,9 @@ function parseChunk (s, push) {
|
|
|
138
144
|
let j = i + 2; let num = ''
|
|
139
145
|
while (j < s.length && /[0-9;]/.test(s[j])) { num += s[j]; j++ }
|
|
140
146
|
const fin = s[j]
|
|
141
|
-
|
|
147
|
+
// F5 = refrescar. Es tecla de función y no consume un mnemónico: así `r` queda
|
|
148
|
+
// libre para renombrar, que es lo que significa en el resto de la TUI.
|
|
149
|
+
const seq = { 3: 'delete', 5: 'pageup', 6: 'pagedown', 1: 'home', 4: 'end', 15: 'f5' }
|
|
142
150
|
if (fin === '~' && seq[num]) { push({ name: seq[num] }); i = j + 1; continue }
|
|
143
151
|
i = (fin ? j + 1 : s.length); continue
|
|
144
152
|
}
|
package/src/vaultControl.js
CHANGED
|
@@ -33,12 +33,14 @@ const F = {
|
|
|
33
33
|
profilesList: 'profiles-list.json',
|
|
34
34
|
secretsList: 'secrets-list.json',
|
|
35
35
|
me: 'me.json',
|
|
36
|
+
acta: 'acta.json',
|
|
36
37
|
// peticiones (las escribe el control; el daemon las consume y borra)
|
|
37
38
|
pairReq: 'pair-request.json',
|
|
38
39
|
approveReq: 'approve-request.json',
|
|
39
40
|
rejectReq: 'reject-request.json',
|
|
40
41
|
revokeReq: 'revoke-request.json',
|
|
41
42
|
labelReq: 'label-request.json',
|
|
43
|
+
capsReq: 'caps-request.json',
|
|
42
44
|
secretReq: 'secret-request.json',
|
|
43
45
|
profileReq: 'profile-request.json',
|
|
44
46
|
meReq: 'me-request.json',
|
|
@@ -212,6 +214,37 @@ export async function setDeviceLabel (pub, label, profile) {
|
|
|
212
214
|
return listDevices(profile)
|
|
213
215
|
}
|
|
214
216
|
|
|
217
|
+
/**
|
|
218
|
+
* El ACTA del perfil: quién es miembro y qué puede hacer cada uno. Es lo que manda para los
|
|
219
|
+
* permisos — la lista de dispositivos enseña el SCOPE DEL CERT, que es su reflejo y puede
|
|
220
|
+
* ir por detrás hasta que el aparato renueve.
|
|
221
|
+
*/
|
|
222
|
+
export async function listMembers (profile) {
|
|
223
|
+
requireAlive()
|
|
224
|
+
rm(F.acta)
|
|
225
|
+
writeReq(F.dumpReq, {}, profile)
|
|
226
|
+
signalOrCleanup('SIGUSR2', [F.dumpReq])
|
|
227
|
+
const d = await waitFor(F.acta)
|
|
228
|
+
if (!d) throw coded('the daemon did not reply', 'NO_REPLY')
|
|
229
|
+
return d.members || []
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Cambia lo que PUEDE hacer un dispositivo. La lista es completa (no un delta): lo que no
|
|
234
|
+
* venga, se le quita.
|
|
235
|
+
*
|
|
236
|
+
* `admin` (administrar el perfil a distancia) se concede AQUÍ, en la máquina de la bóveda,
|
|
237
|
+
* y nunca al emparejar: así el QR que circula no puede otorgarla nunca, y darla es un
|
|
238
|
+
* gesto deliberado del dueño que queda escrito en el acta.
|
|
239
|
+
*/
|
|
240
|
+
export async function setDeviceCaps (pub, caps, profile) {
|
|
241
|
+
requireAlive()
|
|
242
|
+
writeReq(F.capsReq, { pub, caps }, profile)
|
|
243
|
+
signalOrCleanup('SIGUSR2', [F.capsReq])
|
|
244
|
+
await sleep(600)
|
|
245
|
+
return listDevices(profile)
|
|
246
|
+
}
|
|
247
|
+
|
|
215
248
|
/** Revoca un dispositivo por su `nonce` (le ordena autoborrarse) y revuelca. */
|
|
216
249
|
export async function revokeDevice (nonce, profile) {
|
|
217
250
|
requireAlive()
|