@dotrino/identity 0.41.0 → 0.42.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotrino/identity",
3
- "version": "0.41.0",
3
+ "version": "0.42.0",
4
4
  "description": "Identidad y rating de usuarios compartidos entre apps de Dotrino (vault iframe + postMessage)",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/vault/acta.js CHANGED
@@ -245,6 +245,15 @@ export async function applyChanges (acta, changes, { by, now = Date.now() } = {}
245
245
  // No se puede ascender un servicio a dispositivo cambiándole las capacidades: para
246
246
  // eso hay que sacarlo y volver a admitirlo, que es un gesto visible.
247
247
  m.caps = cleanCaps(ch.caps).filter((c) => (m.cn ? SERVICE_CAPS : DEVICE_CAPS).includes(c))
248
+ // CONCEDER LIMPIA LA RENUNCIA. `effectiveCaps` resta lo renunciado, así que sin
249
+ // esto una renuncia sellada era IRREVERSIBLE: el master le devolvía el permiso en
250
+ // `caps` y la resta seguía dejándolo fuera, para siempre. La interfaz promete
251
+ // «puedes devolvérselo desde el Master», así que tiene que ser verdad.
252
+ // Se tira el registro entero y no se le recortan capacidades: va FIRMADO por el
253
+ // miembro, y editarlo lo dejaría sin firma válida.
254
+ const renunciadas = new Set()
255
+ for (const r of next.renounced) if (r.member === m.pub) for (const c of (r.caps || [])) renunciadas.add(c)
256
+ if (m.caps.some((c) => renunciadas.has(c))) next.renounced = next.renounced.filter((r) => r.member !== m.pub)
248
257
  break
249
258
  }
250
259
  case 'label': {
package/vault/core.js CHANGED
@@ -22,7 +22,7 @@ import { signDelegationWith, MAX_DELEGATION_MS, DEFAULT_DELEGATION_MS } from './
22
22
  import * as Acta from './acta.js'
23
23
  import * as Content from './content.js'
24
24
  import { pubkeyId as pubkeyIdOf } from './capabilities.js'
25
- import { enrollDevice as remoteEnroll, requestSign as remoteSign, requestStore as remoteStore, requestDevices as remoteDevices, requestRenew as remoteRenew, requestAdmin as remoteAdmin } from './remote.js'
25
+ import { enrollDevice as remoteEnroll, requestSign as remoteSign, requestStore as remoteStore, requestDevices as remoteDevices, requestRenew as remoteRenew, requestAdmin as remoteAdmin, requestRenounce as remoteRenounce } from './remote.js'
26
26
 
27
27
  export const KEY_STORAGE = 'dotrino.identity.keypair'
28
28
  export const ENC_KEY_STORAGE = 'dotrino.identity.enc-keypair'
@@ -1270,6 +1270,16 @@ export async function createIdentityCore ({ kv: rawKv, peers, makeSync = null, k
1270
1270
  pend.push(record)
1271
1271
  saveRenounces(pend)
1272
1272
  emitVault({ phase: 'renounced', caps: record.caps })
1273
+ // Y se le MANDA a la bóveda para que la selle en el acta. Sin esto la renuncia solo
1274
+ // valía aquí dentro: la bóveda seguía teniendo escrito que este aparato puede firmar
1275
+ // y le seguía aceptando peticiones. Best-effort: si la bóveda está apagada, la
1276
+ // renuncia ya está en pie localmente (para eso no necesita a nadie) y se reintenta
1277
+ // la próxima vez que se renuncie.
1278
+ const v = loadVaultCert(); const dev = loadVaultDevice()
1279
+ if (v?.cert && dev) {
1280
+ remoteRenounce({ master: v.master, proxy: v.proxy, device: dev, cert: v.cert, record })
1281
+ .catch(() => {})
1282
+ }
1273
1283
  // Si además soy el master, la absorbo ya en el acta.
1274
1284
  if (amMaster()) { try { await sealChanges([{ op: 'renounce', record }]) } catch (_) {} }
1275
1285
  return { ok: true, record, caps: Acta.effectiveCaps(loadActa(), publickeyJwkStr, loadRenounces()) }
package/vault/remote.js CHANGED
@@ -32,6 +32,9 @@ const MSG = {
32
32
  ADMIN: 'vault.admin',
33
33
  ADMIN_RESULT: 'vault.admin.result',
34
34
  ADMIN_EVENT: 'vault.admin.event',
35
+ // Renuncia: el miembro se quita capacidades a sí mismo y la bóveda la sella en el acta.
36
+ RENOUNCE: 'vault.renounce',
37
+ RENOUNCE_RESULT: 'vault.renounce.result',
35
38
  ERROR: 'vault.error'
36
39
  }
37
40
  export { MSG as VAULT_MSG }
@@ -275,6 +278,26 @@ export async function requestRenew ({ master, proxy, device, cert, onRevoked } =
275
278
  return { cert: res.cert }
276
279
  }
277
280
 
281
+ /**
282
+ * Manda a la bóveda la RENUNCIA de este dispositivo para que la selle en el acta.
283
+ *
284
+ * Sin esto, renunciar solo valía en el propio aparato: la bóveda seguía teniendo escrito
285
+ * que podía firmar, le seguía aceptando peticiones, y cualquiera que mirara el acta lo
286
+ * seguía viendo como firmante. Una renuncia que no llega al acta no es oponible a nadie —
287
+ * y en el caso que la justifica (te robaron el aparato) su registro local no vale nada,
288
+ * porque lo borra quien lo tenga en la mano.
289
+ *
290
+ * El `record` va FIRMADO por el propio miembro y solo puede QUITAR, así que la bóveda no
291
+ * necesita comprobar ningún certificado para honrarlo: le basta la firma.
292
+ */
293
+ export async function requestRenounce ({ master, proxy, device, cert, record, onRevoked } = {}) {
294
+ const res = await vaultRpc({
295
+ master, proxy, device, cert, onRevoked,
296
+ sendType: MSG.RENOUNCE, okType: MSG.RENOUNCE_RESULT, data: { op: 'renounce', record }
297
+ })
298
+ return { ok: !!res.ok, seq: res.seq ?? null }
299
+ }
300
+
278
301
  /**
279
302
  * CONSOLA REMOTA (`dotrino-vault/docs/consola-remota.md`): administrar el perfil desde
280
303
  * este dispositivo, sin ir al PC. Requiere un cert con scope `vault:admin`, que **no se