@dotrino/identity 0.75.0 → 0.77.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/index.js +8 -1
- package/vault/acta.js +29 -2
- package/vault/core.js +18 -3
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -489,7 +489,14 @@ export class Identity {
|
|
|
489
489
|
// Podés tener varios perfiles (identidades) en el mismo navegador, cada uno conectado o no
|
|
490
490
|
// a su propio vault. Crear/cambiar setea el perfil activo; la app RECARGA la página y toma
|
|
491
491
|
// el nuevo (no reactivo: las apps abiertas conservan el perfil con el que cargaron).
|
|
492
|
-
/**
|
|
492
|
+
/**
|
|
493
|
+
* Lista de perfiles: `[{ id, name, pubkey, avatar, current, pendingJoin, vault, approve }]`.
|
|
494
|
+
*
|
|
495
|
+
* `vault` = ese perfil está conectado a una bóveda · `approve` = además su certificado
|
|
496
|
+
* le deja aprobar pedidos. Los dos salen de leer el cert guardado de cada perfil, sin
|
|
497
|
+
* cambiar el activo ni firmar nada — es lo que necesita la pantalla de Pedidos para
|
|
498
|
+
* saber a QUÉ perfil pertenece lo que timbró el teléfono.
|
|
499
|
+
*/
|
|
493
500
|
async listProfiles () { return this._call('listProfiles') }
|
|
494
501
|
/** El perfil activo: { id, name, pubkey }. */
|
|
495
502
|
async currentProfile () { return this._call('currentProfile') }
|
package/vault/acta.js
CHANGED
|
@@ -436,7 +436,31 @@ export function checkShape (acta) {
|
|
|
436
436
|
if (!Array.isArray(acta.members) || acta.members.length === 0) return 'members'
|
|
437
437
|
for (const m of acta.members) {
|
|
438
438
|
if (!isPub(m?.pub) || !Array.isArray(m?.caps)) return 'member'
|
|
439
|
-
|
|
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'
|
|
440
464
|
// El CN es la frontera: un SERVICIO solo puede abrir su propio cajón, y un
|
|
441
465
|
// DISPOSITIVO no tiene cajón que abrir. Que no se pueda escribir un acta que
|
|
442
466
|
// mezcle las dos cosas es lo que hace que el límite sea real y no una costumbre.
|
|
@@ -909,11 +933,14 @@ export async function verifyRenounce (record) {
|
|
|
909
933
|
export function effectiveCaps (acta, pub, extraRenounces = []) {
|
|
910
934
|
const m = (acta?.members || []).find((x) => x.pub === pub)
|
|
911
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.
|
|
912
939
|
const quitadas = new Set()
|
|
913
940
|
for (const r of [...(acta.renounced || []), ...extraRenounces]) {
|
|
914
941
|
if (r?.member === pub) for (const c of (r.caps || [])) quitadas.add(c)
|
|
915
942
|
}
|
|
916
|
-
return m.caps.filter((c) => !quitadas.has(c))
|
|
943
|
+
return m.caps.filter((c) => CAPS.includes(c) && !quitadas.has(c))
|
|
917
944
|
}
|
|
918
945
|
|
|
919
946
|
/**
|
package/vault/core.js
CHANGED
|
@@ -1524,9 +1524,24 @@ export async function createIdentityCore ({ kv: rawKv, peers, makeSync = null, k
|
|
|
1524
1524
|
const m = raw ? JSON.parse(raw) : null
|
|
1525
1525
|
if (m && typeof m.avatar === 'string') avatar = m.avatar
|
|
1526
1526
|
} catch (_) {}
|
|
1527
|
-
//
|
|
1528
|
-
//
|
|
1529
|
-
|
|
1527
|
+
// QUÉ PERFIL ESTÁ CONECTADO A UNA BÓVEDA, Y CUÁL APRUEBA.
|
|
1528
|
+
//
|
|
1529
|
+
// Con varios perfiles en el mismo aparato, el pedido que timbra el teléfono es de
|
|
1530
|
+
// UNO de ellos, y puede no ser el activo. Sin esto la pantalla de Pedidos abría con
|
|
1531
|
+
// el que hubiera y decía «este aparato no aprueba pedidos» — falso: el que aprueba
|
|
1532
|
+
// era otro perfil, y no había forma de saber cuál.
|
|
1533
|
+
//
|
|
1534
|
+
// Se lee el CERT de cada perfil, que está en claro en el kv (no es un secreto: dice
|
|
1535
|
+
// qué puede hacer este aparato, no cómo). Es solo lectura y no hace falta la llave
|
|
1536
|
+
// del otro perfil, así que no se cambia el activo ni se firma nada.
|
|
1537
|
+
let vault = false; let approve = false
|
|
1538
|
+
try {
|
|
1539
|
+
const raw = p.id === currentPid ? kv.getItem(VAULT_CERT_STORAGE) : rawKv.getItem(`dotrino.identity.p.${p.id}.vault.cert`)
|
|
1540
|
+
const v = raw ? JSON.parse(raw) : null
|
|
1541
|
+
vault = !!v?.cert
|
|
1542
|
+
approve = vault && (v.cert.scope || []).includes('vault:approve')
|
|
1543
|
+
} catch (_) {}
|
|
1544
|
+
return { id: p.id, name: p.name || '', pubkey: p.pubkey || null, avatar, current: p.id === currentPid, pendingJoin: !!p.pendingJoin, vault, approve }
|
|
1530
1545
|
})
|
|
1531
1546
|
},
|
|
1532
1547
|
async currentProfile () {
|