@dotrino/identity 0.46.0 → 0.47.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.46.0",
3
+ "version": "0.47.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/src/index.js CHANGED
@@ -439,8 +439,18 @@ export class Identity {
439
439
  async selfVaultApprove (deviceId, code) { return this._call('selfVaultApprove', { deviceId, code }) }
440
440
  /** Rechaza una solicitud de emparejamiento pendiente. */
441
441
  async selfVaultReject (deviceId) { return this._call('selfVaultReject', { deviceId }) }
442
- /** Revoca una máquina/agente enrolado por nonce de delegación. */
443
- async selfVaultRevoke (nonce) { return this._call('selfVaultRevoke', { nonce }) }
442
+ /**
443
+ * QUITA una máquina/agente enrolado en esta bóveda. Se le pasa **el aparato**
444
+ * (`{ sub }`, su llave): sale del acta y se le retiran todos sus certificados.
445
+ *
446
+ * Un `nonce` suelto (string, o `{ nonce }`) retira UN certificado y deja al aparato
447
+ * dentro del acta: eso no es quitarlo, y quien queda así ya no recibe nunca el aviso
448
+ * de expulsión. Se acepta por compatibilidad, no como la forma normal.
449
+ */
450
+ async selfVaultRevoke (target) {
451
+ const p = typeof target === 'string' ? { nonce: target } : { sub: target?.sub, nonce: target?.nonce }
452
+ return this._call('selfVaultRevoke', p)
453
+ }
444
454
  /** Presencia online (ping/pong) de las máquinas enroladas. Devuelve { online: [pubkeys] }. */
445
455
  async selfVaultProbe (pubkeys) { return this._call('selfVaultProbe', { pubkeys }, 10000) }
446
456
  /** Suscribe a eventos del self-vault ('selfVault'): { running?, pending?, error? }. */
package/vault/remote.js CHANGED
@@ -223,6 +223,13 @@ export async function requestSign ({ master, proxy, device, cert, payload, onRev
223
223
  return { signature: res.signature, publickey: res.publickey }
224
224
  }
225
225
 
226
+ /**
227
+ * Cuánto se espera el aviso FIRMADO de expulsión después de que la bóveda conteste
228
+ * «revoked». Corto a propósito: es el tiempo de un mensaje que ya viene en camino, no una
229
+ * espera de verdad.
230
+ */
231
+ const REVOKE_GRACE_MS = 1500
232
+
226
233
  /**
227
234
  * Helper genérico: una RPC al vault firmada por D + cert, esperando `okType`.
228
235
  *
@@ -240,6 +247,7 @@ async function vaultRpc ({ master, proxy, device, cert, acta = null, sendType, o
240
247
  const signed = { ...data, publickey: device.publickey, ts: Date.now() }
241
248
  const { signature } = await signWithDevice({ privateJwk: device.privateJwk, privateKey: device.privateKey, publickey: device.publickey, data: signed })
242
249
  const pending = new Promise((resolve, reject) => {
250
+ let graceTimer = null
243
251
  const off = client.on('message', (_f, p) => {
244
252
  if (!p || typeof p !== 'object') return
245
253
  if (p.type === MSG.REVOKED) {
@@ -249,10 +257,21 @@ async function vaultRpc ({ master, proxy, device, cert, acta = null, sendType, o
249
257
  return
250
258
  }
251
259
  if (p.type === okType) { cleanup(); resolve(p) }
252
- else if (p.type === 'vault.error') { cleanup(); reject(new Error(p.error)) }
260
+ else if (p.type === 'vault.error') {
261
+ // «Te echaron» llega en DOS mensajes: este error (que no va firmado, así que no
262
+ // puede borrar nada — wipe-DoS) y el `vault.revoked` FIRMADO, que es el único que
263
+ // sí. Cerrar el socket al recibir el primero era llegar a cerrar la puerta justo
264
+ // antes que el segundo, y entonces el aparato se quedaba con la cuenta puesta
265
+ // hasta vaya a saber cuándo. Se le da un respiro corto para recogerlo.
266
+ if (/\brevoked\b/.test(p.error || '') && !graceTimer) {
267
+ graceTimer = setTimeout(() => { cleanup(); reject(new Error(p.error)) }, REVOKE_GRACE_MS)
268
+ return
269
+ }
270
+ cleanup(); reject(new Error(p.error))
271
+ }
253
272
  })
254
273
  const t = setTimeout(() => { cleanup(); reject(new Error('the vault did not reply (is it running?)')) }, timeoutMs)
255
- const cleanup = () => { off(); clearTimeout(t) }
274
+ const cleanup = () => { off(); clearTimeout(t); clearTimeout(graceTimer) }
256
275
  })
257
276
  client.sendByPubkey(master, { type: sendType, data: signed, signature, cert })
258
277
  return await pending
package/vault/vault.js CHANGED
@@ -239,7 +239,20 @@ import { pubkeyId } from './capabilities.js'
239
239
  daemon.reject(deviceId)
240
240
  return { ok: true }
241
241
  },
242
- selfVaultRevoke: async ({ nonce }) => {
242
+ /**
243
+ * QUITAR UN APARATO cuando la bóveda es ESTE navegador. Por `sub` (su llave): sale del
244
+ * acta y se le retiran todos los certificados, que son las dos caras del mismo acto.
245
+ *
246
+ * Con `nonce` a secas solo cae UN papel y el aparato sigue siendo miembro — un
247
+ * fantasma en la lista al que además ya nunca le llega el aviso de expulsión, porque
248
+ * mientras siga en el acta un papel retirado significa «renueva», no «estás fuera».
249
+ * Se conserva para quien de verdad quiera retirar un certificado suelto.
250
+ */
251
+ selfVaultRevoke: async ({ sub, nonce }) => {
252
+ if (sub) {
253
+ if (daemon?.revokeDevice) return daemon.revokeDevice(sub)
254
+ return handlers.revokeDevice({ sub })
255
+ }
243
256
  if (daemon) return daemon.revoke(nonce)
244
257
  return handlers.revokeDelegation({ nonce })
245
258
  },