@dotrino/identity 0.40.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 +1 -1
- package/src/index.js +2 -0
- package/src/node.js +11 -0
- package/vault/acta.js +9 -0
- package/vault/core.js +11 -1
- package/vault/remote.js +23 -0
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -269,6 +269,8 @@ export class Identity {
|
|
|
269
269
|
|
|
270
270
|
/** Dónde estoy yo: { inProfile, profileId, seq, isMaster, caps, id }. */
|
|
271
271
|
async myMembership () { return this._call('myMembership') }
|
|
272
|
+
/** La cadena de actas desde `sinceSeq` (ver la nota en `node.js`). */
|
|
273
|
+
async actaHistory (opts) { return this._call('actaHistory', opts || {}) }
|
|
272
274
|
|
|
273
275
|
/** ¿Es ESTE dispositivo el master (el único que puede cambiar el acta)? */
|
|
274
276
|
async isMaster () { return this._call('isMaster') }
|
package/src/node.js
CHANGED
|
@@ -165,6 +165,17 @@ export class Identity {
|
|
|
165
165
|
profileActa () { return this._h('profileActa') }
|
|
166
166
|
profileMembers () { return this._h('profileMembers') }
|
|
167
167
|
myMembership () { return this._h('myMembership') }
|
|
168
|
+
/**
|
|
169
|
+
* La CADENA de actas desde `sinceSeq` (para que un dispositivo que estuvo apagado
|
|
170
|
+
* compruebe el encadenamiento en vez de tragarse un salto a ciegas).
|
|
171
|
+
*
|
|
172
|
+
* Vivía en el núcleo pero NO estaba aquí, así que `identity.actaHistory` era
|
|
173
|
+
* `undefined` en el daemon: al pedir la lista de dispositivos con un `sinceSeq`
|
|
174
|
+
* —que es lo que manda SIEMPRE un navegador ya emparejado— reventaba con
|
|
175
|
+
* «is not a function», la bóveda respondía error, y el aparato NUNCA volvía a
|
|
176
|
+
* sincronizar su acta. Se quedaba con la copia del día que emparejó.
|
|
177
|
+
*/
|
|
178
|
+
actaHistory (opts) { return this._h('actaHistory', opts || {}) }
|
|
168
179
|
isMaster () { return this._h('isMaster') }
|
|
169
180
|
admitMember (member) { return this._h('admitMember', member) }
|
|
170
181
|
setCaps (pub, caps) { return this._h('setCaps', { pub, caps }) }
|
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
|