@dotrino/identity 0.45.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 +1 -1
- package/src/index.js +12 -2
- package/vault/remote.js +30 -4
- package/vault/vault.js +14 -1
package/package.json
CHANGED
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
|
-
/**
|
|
443
|
-
|
|
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
|
@@ -45,10 +45,17 @@ export { MSG as VAULT_MSG }
|
|
|
45
45
|
* un `vault.error` con la palabra «revocado» no borra nada (cierra el wipe-DoS, ver
|
|
46
46
|
* `dotrino-vault/docs/pairing-protocol.md §2.3`).
|
|
47
47
|
*/
|
|
48
|
-
export async function isAuthenticRevoke ({ body, signature, master, devicePubkey }) {
|
|
48
|
+
export async function isAuthenticRevoke ({ body, signature, master, devicePubkey, currentNonce = null }) {
|
|
49
49
|
if (!body || body.op !== 'revoke' || typeof signature !== 'string') return false
|
|
50
50
|
if (body.sub !== devicePubkey) return false
|
|
51
51
|
if (typeof body.exp === 'number' && Date.now() > body.exp) return false
|
|
52
|
+
// Y tiene que hablar del certificado que este aparato usa AHORA. Un certificado se
|
|
53
|
+
// retira también cuando NO pasa nada malo: renovar retira el anterior, y cambiar
|
|
54
|
+
// permisos obliga a renovar. Sin esta comprobación, el aviso de «tu papel viejo ya no
|
|
55
|
+
// vale» borraba el enlace con la bóveda entero: dabas «administra» a un aparato y el
|
|
56
|
+
// aparato desaparecía solo, como si lo hubieran echado. El proxy encola 24 h, así que
|
|
57
|
+
// el aviso puede llegar mucho después de haber renovado.
|
|
58
|
+
if (currentNonce && typeof body.nonce === 'string' && body.nonce !== currentNonce) return false
|
|
52
59
|
return verifyDeviceSig({ publickey: master, data: body, signature })
|
|
53
60
|
}
|
|
54
61
|
|
|
@@ -216,6 +223,13 @@ export async function requestSign ({ master, proxy, device, cert, payload, onRev
|
|
|
216
223
|
return { signature: res.signature, publickey: res.publickey }
|
|
217
224
|
}
|
|
218
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
|
+
|
|
219
233
|
/**
|
|
220
234
|
* Helper genérico: una RPC al vault firmada por D + cert, esperando `okType`.
|
|
221
235
|
*
|
|
@@ -233,19 +247,31 @@ async function vaultRpc ({ master, proxy, device, cert, acta = null, sendType, o
|
|
|
233
247
|
const signed = { ...data, publickey: device.publickey, ts: Date.now() }
|
|
234
248
|
const { signature } = await signWithDevice({ privateJwk: device.privateJwk, privateKey: device.privateKey, publickey: device.publickey, data: signed })
|
|
235
249
|
const pending = new Promise((resolve, reject) => {
|
|
250
|
+
let graceTimer = null
|
|
236
251
|
const off = client.on('message', (_f, p) => {
|
|
237
252
|
if (!p || typeof p !== 'object') return
|
|
238
253
|
if (p.type === MSG.REVOKED) {
|
|
239
|
-
isAuthenticRevoke({ body: p.body, signature: p.signature, master, devicePubkey: device.publickey })
|
|
254
|
+
isAuthenticRevoke({ body: p.body, signature: p.signature, master, devicePubkey: device.publickey, currentNonce: cert?.nonce || null })
|
|
240
255
|
.then((ok) => { if (ok) { try { onRevoked?.() } catch (_) {} } })
|
|
241
256
|
.catch(() => {})
|
|
242
257
|
return
|
|
243
258
|
}
|
|
244
259
|
if (p.type === okType) { cleanup(); resolve(p) }
|
|
245
|
-
else if (p.type === 'vault.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
|
+
}
|
|
246
272
|
})
|
|
247
273
|
const t = setTimeout(() => { cleanup(); reject(new Error('the vault did not reply (is it running?)')) }, timeoutMs)
|
|
248
|
-
const cleanup = () => { off(); clearTimeout(t) }
|
|
274
|
+
const cleanup = () => { off(); clearTimeout(t); clearTimeout(graceTimer) }
|
|
249
275
|
})
|
|
250
276
|
client.sendByPubkey(master, { type: sendType, data: signed, signature, cert })
|
|
251
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
|
-
|
|
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
|
},
|