@dotrino/identity 0.74.1 → 0.76.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/vault/acta.js +45 -4
package/package.json
CHANGED
package/vault/acta.js
CHANGED
|
@@ -103,7 +103,7 @@ const conCampoSellador = (acta) => Number(acta?.v) < V_SIN_CAMPO_SELLADOR
|
|
|
103
103
|
* el rol de master, que no se delega. Así un dispositivo con `admin` robado hace daño
|
|
104
104
|
* acotado y **reversible** (se le revoca), en vez de poder dejarte fuera de tu cuenta.
|
|
105
105
|
*/
|
|
106
|
-
export const CAPS = Object.freeze(['sign', 'store', 'read', 'secrets', 'admin', 'approve', 'passwords', 'sealer'])
|
|
106
|
+
export const CAPS = Object.freeze(['sign', 'store', 'read', 'secrets', 'admin', 'approve', 'passwords', 'sealer', 'unattended'])
|
|
107
107
|
|
|
108
108
|
/** Capacidades de un DISPOSITIVO (sin CN): acceso a todo lo del usuario. */
|
|
109
109
|
/**
|
|
@@ -116,8 +116,22 @@ export const CAPS = Object.freeze(['sign', 'store', 'read', 'secrets', 'admin',
|
|
|
116
116
|
* dominio; nunca lista la bóveda. Va aquí y no en una lista aparte porque quién puede
|
|
117
117
|
* pedirle algo a la bóveda es exactamente lo que decide el acta — tener dos registros
|
|
118
118
|
* de lo mismo obliga a acordarse de los dos al quitar un aparato.
|
|
119
|
+
*
|
|
120
|
+
* `unattended` es RECIBIR CLAVES PRIVADAS SIN QUE NADIE APRUEBE. Sin él, la bóveda no
|
|
121
|
+
* entrega nada hasta que un aparato con `approve` lo firme — una vez por arranque del
|
|
122
|
+
* servicio, no por petición.
|
|
123
|
+
*
|
|
124
|
+
* El defecto es al revés que antes, y esa es la gracia (dueño, 2026-09-01): antes había
|
|
125
|
+
* que marcar a mano quién NECESITA aprobación, así que un aparato nuevo nacía pudiendo
|
|
126
|
+
* llevarse las claves y nadie se enteraba de que esa era la elección. Ahora hay que
|
|
127
|
+
* conceder a propósito quién puede llevárselas SOLO — y si el dato falta, se pide permiso,
|
|
128
|
+
* que es lo que hay que hacer cuando no se sabe.
|
|
129
|
+
*
|
|
130
|
+
* Y va EN EL ACTA, no en una marca local de una máquina: así lo respeta cualquier bóveda
|
|
131
|
+
* de la cuenta, se ve en la pantalla de permisos como los demás, y se quita quitándolo —
|
|
132
|
+
* sin acordarse de un segundo registro escondido.
|
|
119
133
|
*/
|
|
120
|
-
export const DEVICE_CAPS = Object.freeze(['sign', 'store', 'read', 'admin', 'approve', 'passwords', 'sealer'])
|
|
134
|
+
export const DEVICE_CAPS = Object.freeze(['sign', 'store', 'read', 'admin', 'approve', 'passwords', 'sealer', 'unattended'])
|
|
121
135
|
|
|
122
136
|
/**
|
|
123
137
|
* Lo que recibe un dispositivo recién emparejado. `admin` **no está**: no se
|
|
@@ -422,7 +436,31 @@ export function checkShape (acta) {
|
|
|
422
436
|
if (!Array.isArray(acta.members) || acta.members.length === 0) return 'members'
|
|
423
437
|
for (const m of acta.members) {
|
|
424
438
|
if (!isPub(m?.pub) || !Array.isArray(m?.caps)) return 'member'
|
|
425
|
-
|
|
439
|
+
// UN PERMISO DESCONOCIDO SE IGNORA, NO INVALIDA EL ACTA (dueño, 2026-09-01).
|
|
440
|
+
//
|
|
441
|
+
// Aquí ponía `if (m.caps.some((c) => !CAPS.includes(c))) return 'cap-desconocida'`, y
|
|
442
|
+
// eso convertía CADA permiso nuevo en una rotura de toda la flota: al añadir
|
|
443
|
+
// `unattended`, los aparatos con un pilar anterior empezaron a contestar «the record
|
|
444
|
+
// does not verify» y se quedaron sin configuración. El acta estaba perfectamente
|
|
445
|
+
// firmada; lo que fallaba era el lector, que la tiraba al suelo por ver una palabra
|
|
446
|
+
// que no conocía.
|
|
447
|
+
//
|
|
448
|
+
// Y el acta VIAJA — a otros aparatos, a otras bóvedas, al proxio —, así que un
|
|
449
|
+
// documento firmado tiene que poder leerlo alguien que no está al día. La firma cubre
|
|
450
|
+
// el documento tal cual, de modo que un permiso desconocido no la rompe: rechazarlo era
|
|
451
|
+
// una decisión del validador, y la equivocada.
|
|
452
|
+
//
|
|
453
|
+
// POR QUÉ IGNORARLO ES SEGURO, que es lo único que hace válido este cambio: todas las
|
|
454
|
+
// comprobaciones preguntan por un permiso CONCRETO (`memberCan(acta, pub, 'sign')`), así
|
|
455
|
+
// que una palabra que el lector no conoce no puede satisfacer ninguna — no concede nada.
|
|
456
|
+
// Ignorar cae siempre del lado estricto.
|
|
457
|
+
//
|
|
458
|
+
// ⚠️ La regla que hay que respetar al añadir permisos futuros: **un permiso nuevo
|
|
459
|
+
// CONCEDE, nunca restringe**. Si algún día se inventa uno cuya AUSENCIA sea lo
|
|
460
|
+
// permisivo, un lector viejo lo ignoraría y actuaría de más — y entonces sí habría que
|
|
461
|
+
// subir `ACTA_V` y negarse a leer las versiones que no se entienden. `unattended`
|
|
462
|
+
// cumple la regla: sin él se pide aprobación.
|
|
463
|
+
if (m.caps.some((c) => typeof c !== 'string' || !c)) return 'member'
|
|
426
464
|
// El CN es la frontera: un SERVICIO solo puede abrir su propio cajón, y un
|
|
427
465
|
// DISPOSITIVO no tiene cajón que abrir. Que no se pueda escribir un acta que
|
|
428
466
|
// mezcle las dos cosas es lo que hace que el límite sea real y no una costumbre.
|
|
@@ -895,11 +933,14 @@ export async function verifyRenounce (record) {
|
|
|
895
933
|
export function effectiveCaps (acta, pub, extraRenounces = []) {
|
|
896
934
|
const m = (acta?.members || []).find((x) => x.pub === pub)
|
|
897
935
|
if (!m) return []
|
|
936
|
+
// Lo que este lector no conoce no es una capacidad PARA ÉL: no la puede juzgar, así que
|
|
937
|
+
// no la enseña ni la cuenta. Sigue en el acta —la firma la cubre— y un lector al día sí
|
|
938
|
+
// la ve. Ver `checkShape` para por qué no se rechaza el acta entera.
|
|
898
939
|
const quitadas = new Set()
|
|
899
940
|
for (const r of [...(acta.renounced || []), ...extraRenounces]) {
|
|
900
941
|
if (r?.member === pub) for (const c of (r.caps || [])) quitadas.add(c)
|
|
901
942
|
}
|
|
902
|
-
return m.caps.filter((c) => !quitadas.has(c))
|
|
943
|
+
return m.caps.filter((c) => CAPS.includes(c) && !quitadas.has(c))
|
|
903
944
|
}
|
|
904
945
|
|
|
905
946
|
/**
|