@dotrino/identity 0.56.0 → 0.58.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 +22 -9
- package/vault/vendor/vault/protocol.js +2 -1
package/package.json
CHANGED
package/vault/acta.js
CHANGED
|
@@ -53,10 +53,15 @@ const ACTA_LEIBLES = Object.freeze([1, 2])
|
|
|
53
53
|
* el rol de master, que no se delega. Así un dispositivo con `admin` robado hace daño
|
|
54
54
|
* acotado y **reversible** (se le revoca), en vez de poder dejarte fuera de tu cuenta.
|
|
55
55
|
*/
|
|
56
|
-
export const CAPS = Object.freeze(['sign', 'store', 'read', 'secrets', 'admin'])
|
|
56
|
+
export const CAPS = Object.freeze(['sign', 'store', 'read', 'secrets', 'admin', 'approve'])
|
|
57
57
|
|
|
58
58
|
/** Capacidades de un DISPOSITIVO (sin CN): acceso a todo lo del usuario. */
|
|
59
|
-
|
|
59
|
+
/**
|
|
60
|
+
* `approve` es el aparato que APRUEBA: cuando un cajón exige aprobación por uso, el
|
|
61
|
+
* vault le avisa y solo su firma libera los secretos (normalmente el teléfono). Como
|
|
62
|
+
* `admin`, no viaja en un QR: se concede a mano (`dotrino-vault caps <ID> +approve`).
|
|
63
|
+
*/
|
|
64
|
+
export const DEVICE_CAPS = Object.freeze(['sign', 'store', 'read', 'admin', 'approve'])
|
|
60
65
|
|
|
61
66
|
/**
|
|
62
67
|
* Lo que recibe un dispositivo recién emparejado. `admin` **no está**: no se
|
|
@@ -68,6 +73,12 @@ export const PAIRED_CAPS = Object.freeze(['sign', 'store', 'read'])
|
|
|
68
73
|
/** Capacidades de un SERVICIO (con CN): solo su propio cajón de secretos. */
|
|
69
74
|
export const SERVICE_CAPS = Object.freeze(['secrets'])
|
|
70
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Qué capacidades puede llevar un miembro según tenga cajón (CN) o no. Con CN: su cajón
|
|
78
|
+
* y, si se le conceden, las de aparato. Sin CN: solo las de aparato (no hay cajón que abrir).
|
|
79
|
+
*/
|
|
80
|
+
export const allowedCaps = (cn) => (cn ? [...SERVICE_CAPS, ...DEVICE_CAPS] : [...DEVICE_CAPS])
|
|
81
|
+
|
|
71
82
|
/** Un CN válido: minúsculas, números y guiones (igual que el namespace de secretos). */
|
|
72
83
|
export const isValidCn = (cn) => typeof cn === 'string' && /^[a-z0-9-]{1,32}$/.test(cn)
|
|
73
84
|
|
|
@@ -81,12 +92,13 @@ export function capScope (cap, cn = null) {
|
|
|
81
92
|
if (cap === 'store') return 'vault:store'
|
|
82
93
|
if (cap === 'read') return 'vault:read'
|
|
83
94
|
if (cap === 'admin') return 'vault:admin'
|
|
95
|
+
if (cap === 'approve') return 'vault:approve'
|
|
84
96
|
if (cap === 'secrets') return isValidCn(cn) ? 'vault:secrets:' + cn : null
|
|
85
97
|
return null
|
|
86
98
|
}
|
|
87
99
|
|
|
88
100
|
/** Compat: el mapa directo, para las capacidades de dispositivo. */
|
|
89
|
-
export const CAP_SCOPE = Object.freeze({ sign: 'vault:sign', store: 'vault:store', read: 'vault:read', admin: 'vault:admin' })
|
|
101
|
+
export const CAP_SCOPE = Object.freeze({ sign: 'vault:sign', store: 'vault:store', read: 'vault:read', admin: 'vault:admin', approve: 'vault:approve' })
|
|
90
102
|
|
|
91
103
|
const enc = (s) => new TextEncoder().encode(s)
|
|
92
104
|
const hex = (buf) => [...new Uint8Array(buf)].map((b) => b.toString(16).padStart(2, '0')).join('')
|
|
@@ -178,7 +190,6 @@ export function checkShape (acta) {
|
|
|
178
190
|
// mezcle las dos cosas es lo que hace que el límite sea real y no una costumbre.
|
|
179
191
|
if (m.cn != null) {
|
|
180
192
|
if (!isValidCn(m.cn)) return 'cn-invalido'
|
|
181
|
-
if (m.caps.some((c) => !SERVICE_CAPS.includes(c))) return 'servicio-con-capacidades-de-dispositivo'
|
|
182
193
|
} else if (m.caps.includes('secrets')) {
|
|
183
194
|
return 'secretos-sin-cn'
|
|
184
195
|
}
|
|
@@ -306,8 +317,10 @@ export async function applyChanges (acta, changes, { by, now = Date.now(), sealP
|
|
|
306
317
|
encPub: m.encPub || null,
|
|
307
318
|
label: String(m.label || '').slice(0, 60),
|
|
308
319
|
cn,
|
|
309
|
-
//
|
|
310
|
-
|
|
320
|
+
// PERMISOS, no tipos (2026-08-22): el CN dice qué cajón abre; no recorta lo demás.
|
|
321
|
+
// Un miembro con CN puede llevar además las capacidades de aparato (un bot que
|
|
322
|
+
// firma como aparato del acta Y lee su cajón). Sin CN, `secrets` no significa nada.
|
|
323
|
+
caps: cleanCaps(m.caps).filter((c) => allowedCaps(cn).includes(c)),
|
|
311
324
|
addedAt: now,
|
|
312
325
|
cert: m.cert || null,
|
|
313
326
|
// Puente con la identidad que este miembro traía de antes (ver makeContinuity).
|
|
@@ -331,9 +344,9 @@ export async function applyChanges (acta, changes, { by, now = Date.now(), sealP
|
|
|
331
344
|
case 'caps': {
|
|
332
345
|
const m = find(ch.pub)
|
|
333
346
|
if (!m) throw new Error('caps: that member is not in the record')
|
|
334
|
-
//
|
|
335
|
-
//
|
|
336
|
-
m.caps = cleanCaps(ch.caps).filter((c) => (m.cn
|
|
347
|
+
// El CN no cambia por aquí (quitar el cajón es sacar al miembro y volver a admitirlo,
|
|
348
|
+
// que es un gesto visible); las capacidades sí, dentro de lo que su CN permite.
|
|
349
|
+
m.caps = cleanCaps(ch.caps).filter((c) => allowedCaps(m.cn).includes(c))
|
|
337
350
|
// CONCEDER LIMPIA LA RENUNCIA. `effectiveCaps` resta lo renunciado, así que sin
|
|
338
351
|
// esto una renuncia sellada era IRREVERSIBLE: el master le devolvía el permiso en
|
|
339
352
|
// `caps` y la resta seguía dejándolo fuera, para siempre. La interfaz promete
|
|
@@ -87,7 +87,8 @@ export const SCOPE = Object.freeze({
|
|
|
87
87
|
// Consola remota (docs/consola-remota.md): admitir y expulsar miembros a distancia.
|
|
88
88
|
// NO incluye cambiar permisos, traspasar el mando ni conceder `admin`: eso es el rol
|
|
89
89
|
// de master y sigue siendo local. No se empareja — se concede desde el PC.
|
|
90
|
-
ADMIN: 'vault:admin'
|
|
90
|
+
ADMIN: 'vault:admin',
|
|
91
|
+
APPROVE: 'vault:approve' // aprobar pedidos de secretos (cajones con `approval`); se concede a mano, como admin
|
|
91
92
|
})
|
|
92
93
|
|
|
93
94
|
/**
|