@dotrino/vaultd 0.58.0 → 0.59.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/enroll.js CHANGED
@@ -277,7 +277,12 @@ export function createEnrollDesk ({
277
277
  pend.continuity = (okC && d.continuity.member === d.dpub) ? d.continuity : null
278
278
  }
279
279
  pend.from = from // la bóveda NO conoce el código: lo aprende cuando lo tipeas
280
- if (d.label) pend.label = String(d.label).slice(0, 60)
280
+ // EL NOMBRE QUE PUSISTE AQUÍ MANDA. El aparato manda el suyo al enrolarse, y hasta
281
+ // ahora pisaba siempre al de la bóveda — como el aparato usa por defecto el apodo del
282
+ // PERFIL, acababas con varios dispositivos llamados igual que tú y sin forma de saber
283
+ // cuál era cuál. Si en `pair` le diste un nombre, ese es el nombre; el del aparato
284
+ // sigue valiendo como propuesta cuando no dijiste nada.
285
+ if (d.label && !pend.label) pend.label = String(d.label).slice(0, 60)
281
286
  // Camino A: de qué cuenta estamos hablando. Se guarda para poder comprobar, cuando
282
287
  // llegue el acta sellada, que es la que este dispositivo dijo que iba a entregar.
283
288
  if (intent === 'adopt' && typeof d.profileId === 'string') pend.profileId = d.profileId
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotrino/vaultd",
3
- "version": "0.58.0",
3
+ "version": "0.59.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/ctl.js CHANGED
@@ -205,6 +205,17 @@ async function cmdPair (args = []) {
205
205
  // --approval: el aparato que entre pedirá tu aprobación (teléfono) en cada petición de
206
206
  // claves privadas. Por defecto NO pide; se cambia después con `caps <ID> +permiso`.
207
207
  const approval = args.includes('--approval')
208
+ // --name <n>: CÓMO SE VA A LLAMAR el aparato que entre, decidido aquí y antes de nada.
209
+ // Sin esto el nombre lo ponía el propio aparato, que por defecto usa el apodo del
210
+ // PERFIL: acababas con varios dispositivos llamados igual que tú, y para distinguirlos
211
+ // había que renombrarlos después —cuando ya no sabías cuál era cuál—.
212
+ const nIdx = args.indexOf('--name')
213
+ let label = null
214
+ if (nIdx >= 0) {
215
+ const v = args[nIdx + 1]
216
+ if (!v || v.startsWith('-')) { console.error('uso: dotrino-vault pair --name "teléfono de casa"'); process.exit(2) }
217
+ label = v.slice(0, 60)
218
+ }
208
219
  // --admin: el aparato que entre por esta invitación podrá ADMINISTRAR (la consola).
209
220
  // El QR no lleva nada: es una nota local de la bóveda y el permiso se aplica al aprobar
210
221
  // con el código que tecleas aquí. Ahorra el `caps <ID> +administra` de después, que en un
@@ -258,9 +269,11 @@ async function cmdPair (args = []) {
258
269
  // meter el dispositivo en una cuenta que ya vive aquí, se ESTRENA una (vacía) y
259
270
  // entra a ella; las demás no se tocan. En la TUI esto es una pregunta con sus
260
271
  // opciones; en la CLI es una bandera, para que siga sirviendo en un script.
272
+ // `--name` lleva su valor detrás: no puede confundirse con el nombre de la cuenta.
273
+ const consumido = (i) => nIdx >= 0 && i === nIdx + 1
261
274
  const naIdx = args.findIndex((a) => a === '--new-account')
262
275
  if (naIdx >= 0) {
263
- const next = args[naIdx + 1]
276
+ const next = consumido(naIdx + 1) ? null : args[naIdx + 1]
264
277
  const name = (next && !next.startsWith('-')) ? next : `cuenta ${new Date().toISOString().slice(0, 10)}`
265
278
  const d = await profileRequest('add', { name, ...(pairKek ? { kek: pairKek } : {}) })
266
279
  if (d.error) { console.error('%s', d.error); process.exit(1) }
@@ -275,7 +288,7 @@ async function cmdPair (args = []) {
275
288
  const adIdx = args.findIndex((a) => a === '--adopt')
276
289
  const adopt = adIdx >= 0
277
290
  if (adopt) {
278
- const next = args[adIdx + 1]
291
+ const next = consumido(adIdx + 1) ? null : args[adIdx + 1]
279
292
  const name = (next && !next.startsWith('-')) ? next : `cuenta del dispositivo`
280
293
  const d = await profileRequest('add', { name, adopt: true, ...(pairKek ? { kek: pairKek } : {}) })
281
294
  if (d.error) { console.error('%s', d.error); process.exit(1) }
@@ -298,7 +311,7 @@ async function cmdPair (args = []) {
298
311
 
299
312
  // La petición se escribe SIEMPRE (aunque no haya --service): lleva a qué perfil
300
313
  // se empareja el dispositivo.
301
- writeReq('pair-request.json', { ...(service ? { service } : {}), ...(scope ? { scope } : {}), ...(adopt ? { mode: 'adopt' } : {}), ...(approval ? { approval: true } : {}), ...(admin ? { admin: true } : {}) })
314
+ writeReq('pair-request.json', { ...(service ? { service } : {}), ...(scope ? { scope } : {}), ...(adopt ? { mode: 'adopt' } : {}), ...(approval ? { approval: true } : {}), ...(admin ? { admin: true } : {}), ...(label ? { label } : {}) })
302
315
  sendSignal(s.pid, 'SIGUSR1')
303
316
 
304
317
  let pair = null
@@ -1469,6 +1482,9 @@ function help () {
1469
1482
  estrena una cuenta VACÍA en este vault y mete ahí al dispositivo
1470
1483
  (sin la bandera entra a la cuenta activa, o a la de --profile)
1471
1484
  pair --service <ns> empareja un SERVICIO (proxy, geo…) con acceso SOLO a sus secretos
1485
+ pair --name <nombre> cómo se llamará el aparato que entre. Sin esto el nombre lo pone
1486
+ ÉL, y por defecto usa el apodo del perfil: acabas con varios
1487
+ dispositivos llamados igual que tú
1472
1488
  pair --approval el aparato que entre pedirá tu aprobación (teléfono) al recibir claves
1473
1489
  pair --admin el aparato que entre podrá ADMINISTRAR (es lo que es una consola).
1474
1490
  El QR no lleva nada: el permiso se aplica al aprobar el código aquí
package/src/tui/app.js CHANGED
@@ -918,12 +918,41 @@ async function descartarCuenta (term, st, id, volverA) {
918
918
  if (r.ok) flash(st, i.accountDiscarded)
919
919
  }
920
920
 
921
+ /**
922
+ * PRIMERO EL NOMBRE, y luego el QR.
923
+ *
924
+ * Sin preguntarlo, el nombre lo pone el propio aparato al enrolarse, y por defecto usa el
925
+ * apodo del PERFIL: te quedaban tres dispositivos llamados igual que tú y ninguna forma de
926
+ * saber cuál era cuál sin renombrarlos después —cuando ya no te acordabas—. Se pregunta
927
+ * ANTES porque después ya estás mirando un QR con el teléfono en la mano.
928
+ *
929
+ * Enter en blanco sigue valiendo: deja que lo diga el aparato, que es el comportamiento de
930
+ * siempre para quien no quiera decidirlo.
931
+ */
932
+ function beginPairingNamed (term, st, profile, service = null, after = null) {
933
+ const i = L(st)
934
+ setInput(st, {
935
+ label: i.nameDeviceLabel,
936
+ hint: i.nameDeviceHint,
937
+ onSubmit: async (nombre) => {
938
+ st.input = null
939
+ const ok = await beginPairing(term, st, profile, service, (nombre || '').trim().slice(0, 60) || null)
940
+ // `after` es para quien tenga que limpiar si el emparejamiento no llega a abrirse
941
+ // (la cuenta que nació para él). Va aquí porque con el nombre de por medio ya no se
942
+ // puede mirar el valor de retorno desde fuera.
943
+ if (after) await after(ok)
944
+ },
945
+ onCancel: () => { st.input = null }
946
+ })
947
+ return true
948
+ }
949
+
921
950
  /**
922
951
  * Abre el emparejamiento contra `profile` y salta a la pantalla del QR. Con `service`,
923
952
  * el QR es el de un SERVICIO de ese namespace (cert limitado a sus variables).
924
953
  */
925
- async function beginPairing (term, st, profile, service = null) {
926
- const r = await guard(term, st, L(st).startingPairing, () => vc.startPairing({ profile, ...(service ? { service } : {}) }))
954
+ async function beginPairing (term, st, profile, service = null, label = null) {
955
+ const r = await guard(term, st, L(st).startingPairing, () => vc.startPairing({ profile, ...(service ? { service } : {}), ...(label ? { label } : {}) }))
927
956
  // `service` se pega al estado porque el daemon no lo devuelve: la pantalla del QR
928
957
  // tiene que poder decir qué se está entregando, que no es lo mismo un aparato tuyo
929
958
  // que una máquina que solo va a leer la configuración del proxy.
@@ -983,7 +1012,7 @@ async function onKeyPairMode (term, st, key) {
983
1012
  if (key.name === 'escape' || ch === 'b') { st.screen = 'devices'; return true }
984
1013
  if (key.name !== 'enter' || !cur) return true
985
1014
 
986
- if (cur.mode === 'here') { await beginPairing(term, st, activeId(st)); return true }
1015
+ if (cur.mode === 'here') { return beginPairingNamed(term, st, activeId(st)) }
987
1016
 
988
1017
  // SERVICIO: entra a la cuenta activa, pero con un certificado que solo sirve para
989
1018
  // pedir las variables de SU namespace. El nombre del ns es el que luego pide el
@@ -1025,8 +1054,10 @@ async function onKeyPairMode (term, st, key) {
1025
1054
  // Si el emparejamiento no llega a abrirse, la cuenta que se creó PARA él se va con
1026
1055
  // él: si no, cada intento fallido dejaba una cuenta vacía —y encima activa— que
1027
1056
  // luego había que ir a borrar a mano adivinando cuál era.
1028
- if (!await beginPairing(term, st, created)) await descartarCuenta(term, st, created, previa)
1029
- else st.pairing.born = { id: created, from: previa }
1057
+ beginPairingNamed(term, st, created, null, async (ok) => {
1058
+ if (!ok) await descartarCuenta(term, st, created, previa)
1059
+ else st.pairing.born = { id: created, from: previa }
1060
+ })
1030
1061
  },
1031
1062
  onCancel: () => { st.input = null }
1032
1063
  })
package/src/tui/i18n.js CHANGED
@@ -140,6 +140,8 @@ const es = {
140
140
  cantRemoveMaster: 'Esta bóveda es la que manda: no se quita a sí misma.',
141
141
  revokedCount: (n) => ` Revocados: ${n}`,
142
142
  startingPairing: 'Iniciando emparejamiento…',
143
+ nameDeviceLabel: '¿Cómo se va a llamar este aparato?',
144
+ nameDeviceHint: 'Enter para dejar que lo diga él (usará tu apodo, y así no se distinguen)',
143
145
  noPending: 'No hay ningún dispositivo pendiente',
144
146
  noPendingToReject: 'No hay ningún dispositivo pendiente para rechazar',
145
147
  rejecting: 'Rechazando…',
@@ -435,6 +437,8 @@ const en = {
435
437
  cantRemoveMaster: 'This vault is the Master: it does not remove itself.',
436
438
  revokedCount: (n) => ` Revoked: ${n}`,
437
439
  startingPairing: 'Starting pairing…',
440
+ nameDeviceLabel: 'What will this device be called?',
441
+ nameDeviceHint: 'Enter to let it say (it will use your nickname, and then they all look alike)',
438
442
  noPending: 'No device is waiting',
439
443
  noPendingToReject: 'No device is waiting to be rejected',
440
444
  rejecting: 'Rejecting…',
@@ -604,10 +604,10 @@ export function pairUrl (qr) {
604
604
  return { url: inviteUrl(qr), code, payload: JSON.stringify(qr), b64: code }
605
605
  }
606
606
 
607
- export async function startPairing ({ profile, service } = {}) {
607
+ export async function startPairing ({ profile, service, label } = {}) {
608
608
  requireAlive()
609
609
  rm(F.pair); rm(F.pending)
610
- await writeReq(F.pairReq, service ? { service } : {}, profile)
610
+ await writeReq(F.pairReq, { ...(service ? { service } : {}), ...(label ? { label } : {}) }, profile)
611
611
  signalOrCleanup('SIGUSR1', [F.pairReq])
612
612
  for (let i = 0; i < 50; i++) {
613
613
  await sleep(100)