@dotrino/identity 0.57.0 → 0.59.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.57.0",
3
+ "version": "0.59.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
@@ -404,6 +404,19 @@ export class Identity {
404
404
  return this._call('vaultAdmin', { op, ...(args || {}) }, 20000)
405
405
  }
406
406
 
407
+ /**
408
+ * PEDIDOS DE APROBACIÓN de la bóveda (cajones con `approval`): `op` = `approvals` ·
409
+ * `approve` · `deny` (estos dos con `{ id }`). Requiere `vault:approve` en el cert.
410
+ */
411
+ async vaultApprovals (op, args) {
412
+ return this._call('vaultApprovals', { op, ...(args || {}) }, 20000)
413
+ }
414
+
415
+ /** ¿El cert de este dispositivo le permite aprobar pedidos de la bóveda? */
416
+ async canApproveVault () {
417
+ return this._call('canApproveVault', {}, 20000)
418
+ }
419
+
407
420
  /** ¿El cert de este dispositivo le permite administrar el perfil a distancia? */
408
421
  async canAdminVault () {
409
422
  return this._call('canAdminVault', {}, 20000)
package/vault/acta.js CHANGED
@@ -53,10 +53,15 @@ const ACTA_LEIBLES = Object.freeze([1, 2])
53
53
  * el rol de master, que no se delega. Así un dispositivo con `admin` robado hace daño
54
54
  * acotado y **reversible** (se le revoca), en vez de poder dejarte fuera de tu cuenta.
55
55
  */
56
- export const CAPS = Object.freeze(['sign', 'store', 'read', 'secrets', 'admin'])
56
+ export const CAPS = Object.freeze(['sign', 'store', 'read', 'secrets', 'admin', 'approve'])
57
57
 
58
58
  /** Capacidades de un DISPOSITIVO (sin CN): acceso a todo lo del usuario. */
59
- export const DEVICE_CAPS = Object.freeze(['sign', 'store', 'read', 'admin'])
59
+ /**
60
+ * `approve` es el aparato que APRUEBA: cuando un cajón exige aprobación por uso, el
61
+ * vault le avisa y solo su firma libera los secretos (normalmente el teléfono). Como
62
+ * `admin`, no viaja en un QR: se concede a mano (`dotrino-vault caps <ID> +approve`).
63
+ */
64
+ export const DEVICE_CAPS = Object.freeze(['sign', 'store', 'read', 'admin', 'approve'])
60
65
 
61
66
  /**
62
67
  * Lo que recibe un dispositivo recién emparejado. `admin` **no está**: no se
@@ -87,12 +92,13 @@ export function capScope (cap, cn = null) {
87
92
  if (cap === 'store') return 'vault:store'
88
93
  if (cap === 'read') return 'vault:read'
89
94
  if (cap === 'admin') return 'vault:admin'
95
+ if (cap === 'approve') return 'vault:approve'
90
96
  if (cap === 'secrets') return isValidCn(cn) ? 'vault:secrets:' + cn : null
91
97
  return null
92
98
  }
93
99
 
94
100
  /** Compat: el mapa directo, para las capacidades de dispositivo. */
95
- export const CAP_SCOPE = Object.freeze({ sign: 'vault:sign', store: 'vault:store', read: 'vault:read', admin: 'vault:admin' })
101
+ export const CAP_SCOPE = Object.freeze({ sign: 'vault:sign', store: 'vault:store', read: 'vault:read', admin: 'vault:admin', approve: 'vault:approve' })
96
102
 
97
103
  const enc = (s) => new TextEncoder().encode(s)
98
104
  const hex = (buf) => [...new Uint8Array(buf)].map((b) => b.toString(16).padStart(2, '0')).join('')
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, requestRenounce as remoteRenounce, checkMembership as remoteCheck } from './remote.js'
25
+ import { enrollDevice as remoteEnroll, requestSign as remoteSign, requestStore as remoteStore, requestDevices as remoteDevices, requestRenew as remoteRenew, requestAdmin as remoteAdmin, requestApproval as remoteApproval, requestRenounce as remoteRenounce, checkMembership as remoteCheck } 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'
@@ -1956,6 +1956,26 @@ export async function createIdentityCore ({ kv: rawKv, peers, makeSync = null, k
1956
1956
  catch (e) { return handleVaultError(e) }
1957
1957
  },
1958
1958
 
1959
+ /**
1960
+ * PEDIDOS DE APROBACIÓN: lo que le toca al teléfono cuando un cajón de la bóveda exige
1961
+ * el visto bueno por uso. `op`: `approvals` (listar) · `approve` · `deny` (con `id`).
1962
+ * Requiere `vault:approve` en el cert, que se concede a mano (`caps <ID> +aprueba`).
1963
+ */
1964
+ async vaultApprovals ({ op, id } = {}) {
1965
+ const v = loadVaultCert(); const device = loadVaultDevice()
1966
+ if (!v?.cert || !device) throw new Error('this device is not paired with a vault')
1967
+ maybeRenewVaultCert()
1968
+ try { return await remoteApproval({ master: v.master, proxy: v.proxy, device, cert: v.cert, op, id, onRevoked: wipeVaultLink }) }
1969
+ catch (e) { return handleVaultError(e) }
1970
+ },
1971
+
1972
+ /** ¿Puede ESTE dispositivo aprobar pedidos? Mismo criterio que `canAdminVault`. */
1973
+ async canApproveVault () {
1974
+ if (certDesfasadoDelActa()) { try { await renovarCert() } catch (_) {} }
1975
+ const v = loadVaultCert()
1976
+ return !!v?.cert && (v.cert.scope || []).includes('vault:approve')
1977
+ },
1978
+
1959
1979
  /**
1960
1980
  * ¿Puede ESTE dispositivo administrar el perfil a distancia? Sale del scope del
1961
1981
  * cert que le dio la bóveda, no de una preferencia: la interfaz pregunta para
package/vault/remote.js CHANGED
@@ -395,6 +395,21 @@ export async function requestAdmin ({ master, proxy, device, cert, op, onRevoked
395
395
  return res.result
396
396
  }
397
397
 
398
+ /**
399
+ * PEDIDOS DE APROBACIÓN (cajones con `approval`): `approvals` (listar) · `approve` ·
400
+ * `deny`. Van por `vault.secrets`, firmados por un aparato cuyo cert lleve
401
+ * `vault:approve` — que, como `admin`, no se recibe al emparejar: se concede a mano.
402
+ * La bóveda contesta un cuerpo firmado por la maestra (`{ op, items | ok }`).
403
+ */
404
+ export async function requestApproval ({ master, proxy, device, cert, op, id, onRevoked } = {}) {
405
+ const res = await vaultRpc({
406
+ master, proxy, device, cert, onRevoked,
407
+ sendType: MSG.SECRETS, okType: MSG.SECRETS_RESULT,
408
+ data: id ? { op, id } : { op }
409
+ })
410
+ return res.body
411
+ }
412
+
398
413
  /**
399
414
  * ¿Es AUTÉNTICO este `vault.admin.event` (entró o salió alguien del perfil)? Solo si va
400
415
  * firmado por la maestra PINEADA. Un aviso sin firma no se muestra: si no, cualquiera
@@ -87,7 +87,8 @@ export const SCOPE = Object.freeze({
87
87
  // Consola remota (docs/consola-remota.md): admitir y expulsar miembros a distancia.
88
88
  // NO incluye cambiar permisos, traspasar el mando ni conceder `admin`: eso es el rol
89
89
  // de master y sigue siendo local. No se empareja — se concede desde el PC.
90
- ADMIN: 'vault:admin'
90
+ ADMIN: 'vault:admin',
91
+ APPROVE: 'vault:approve' // aprobar pedidos de secretos (cajones con `approval`); se concede a mano, como admin
91
92
  })
92
93
 
93
94
  /**