@dotrino/identity 0.106.0 → 0.107.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/vault/content.js +59 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dotrino/identity",
3
- "version": "0.106.0",
3
+ "version": "0.107.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/content.js CHANGED
@@ -106,12 +106,68 @@ export async function myContentKey ({ keyring, myPub, myEncPrivateKey }) {
106
106
  return null
107
107
  }
108
108
 
109
- /** Cifra con la CEK. Devuelve un sobre `{ gen, iv, ct }` (el `gen` dice con cuál se cifró). */
109
+ /**
110
+ * La marca de un sobre de la llave de la cuenta. La lleva todo sobre de `encryptWithCek`
111
+ * para que quien guarda datos ajenos —la bóveda con el almacén de las apps— lo RECONOZCA
112
+ * dentro de un JSON cualquiera sin adivinar por la forma, y pueda volver a cerrarlo con la
113
+ * generación vigente cuando la llave rota (`resealStale`).
114
+ */
115
+ export const CEK_ENVELOPE = 'dotrino-cek'
116
+
117
+ /** ¿Es un sobre de la llave de la cuenta? */
118
+ export const isCekEnvelope = (v) => !!v && typeof v === 'object' && v.t === CEK_ENVELOPE &&
119
+ Number.isInteger(v.gen) && typeof v.iv === 'string' && typeof v.ct === 'string'
120
+
121
+ /** Cifra con la CEK. Devuelve un sobre `{ t, gen, iv, ct }` (el `gen` dice con cuál se cifró). */
110
122
  export async function encryptWithCek ({ cek, gen, plaintext }) {
111
123
  const k = await subtle.importKey('raw', fromB64(cek), { name: 'AES-GCM' }, false, ['encrypt'])
112
124
  const iv = globalThis.crypto.getRandomValues(new Uint8Array(12))
113
125
  const ct = await subtle.encrypt({ name: 'AES-GCM', iv }, k, new TextEncoder().encode(plaintext))
114
- return { gen, iv: b64(iv), ct: b64(ct) }
126
+ return { t: CEK_ENVELOPE, gen, iv: b64(iv), ct: b64(ct) }
127
+ }
128
+
129
+ /**
130
+ * VUELVE A CERRAR con la generación vigente todo sobre de una generación anterior que haya
131
+ * dentro de `value`, a cualquier profundidad. Devuelve una copia (no toca el original) y
132
+ * cuántos sobres cambió.
133
+ *
134
+ * Es lo que hace la bóveda al abrirse después de una rotación (dueño, 2026-09-30): lo que
135
+ * sirve va SIEMPRE con la llave vigente, así que un aparato que entra después —que solo
136
+ * recibe esa generación— abre todo, y la llave vieja deja de abrir nada porque ya no se
137
+ * le sirve nada cerrado con ella.
138
+ *
139
+ * `reseal(envelope)` abre y vuelve a cerrar un sobre; lo pone quien tiene las llaves. Si
140
+ * un sobre no se puede abrir, se PARA: no se deja a medias ni se salta en silencio.
141
+ *
142
+ * @param {any} value
143
+ * @param {{ gen: number, reseal: (env: object) => Promise<object> }} o
144
+ * @returns {Promise<{ value: any, changed: number }>}
145
+ */
146
+ export async function resealStale (value, { gen, reseal }) {
147
+ if (!Number.isInteger(gen)) throw new Error('resealStale: the current generation is required')
148
+ let changed = 0
149
+ const walk = async (v) => {
150
+ if (isCekEnvelope(v)) {
151
+ if (v.gen === gen) return v
152
+ const nuevo = await reseal(v)
153
+ if (!isCekEnvelope(nuevo) || nuevo.gen !== gen) throw new Error(`resealStale: reseal did not return an envelope of generation ${gen}`)
154
+ changed++
155
+ return nuevo
156
+ }
157
+ if (Array.isArray(v)) {
158
+ const out = []
159
+ for (const x of v) out.push(await walk(x))
160
+ return out
161
+ }
162
+ if (v && typeof v === 'object') {
163
+ const out = {}
164
+ for (const [k, x] of Object.entries(v)) out[k] = await walk(x)
165
+ return out
166
+ }
167
+ return v
168
+ }
169
+ const out = await walk(value)
170
+ return { value: out, changed }
115
171
  }
116
172
 
117
173
  /**
@@ -144,5 +200,5 @@ export async function decryptWithKeyring ({ envelope, keyring, myPub, myEncPriva
144
200
 
145
201
  export default {
146
202
  makeContentKey, wrapForMember, openWrap, makeGeneration, myContentKey,
147
- encryptWithCek, decryptWithCek, decryptWithKeyring
203
+ encryptWithCek, decryptWithCek, decryptWithKeyring, CEK_ENVELOPE, isCekEnvelope, resealStale
148
204
  }