@dotrino/identity 0.24.0 → 0.25.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/vault/acta.js +28 -1
- package/vault/core.js +7 -1
- package/vault/remote.js +4 -2
- package/vault/vendor/vault/VERSION.txt +1 -1
- package/vault/vendor/vault/enroll.js +8 -1
package/package.json
CHANGED
package/vault/acta.js
CHANGED
|
@@ -168,7 +168,9 @@ export async function applyChanges (acta, changes, { by, now = Date.now() } = {}
|
|
|
168
168
|
label: String(m.label || '').slice(0, 60),
|
|
169
169
|
caps: cleanCaps(m.caps),
|
|
170
170
|
addedAt: now,
|
|
171
|
-
cert: m.cert || null
|
|
171
|
+
cert: m.cert || null,
|
|
172
|
+
// Puente con la identidad que este miembro traía de antes (ver makeContinuity).
|
|
173
|
+
...(m.continuity ? { continuity: m.continuity } : {})
|
|
172
174
|
})
|
|
173
175
|
break
|
|
174
176
|
}
|
|
@@ -263,6 +265,30 @@ export function memberCan (acta, pub, cap, extraRenounces = []) {
|
|
|
263
265
|
return effectiveCaps(acta, pub, extraRenounces).includes(cap)
|
|
264
266
|
}
|
|
265
267
|
|
|
268
|
+
// ----- continuidad: unir una identidad que ya existía -----
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* CERTIFICADO DE CONTINUIDAD. Cuando una identidad que ya existía entra en otro perfil,
|
|
272
|
+
* firma —con su propia llave— que a partir de ahora es miembro de él. Sirve de puente:
|
|
273
|
+
* lo que hizo antes (su reputación, lo que firmó, quien la tenía de contacto) se puede
|
|
274
|
+
* seguir atribuyendo a la misma persona en vez de quedar huérfano.
|
|
275
|
+
*
|
|
276
|
+
* No otorga nada por sí solo: es una declaración del que se une, y solo tiene efecto
|
|
277
|
+
* dentro del acta donde el master la mete.
|
|
278
|
+
*/
|
|
279
|
+
export async function makeContinuity ({ member, from, privateKey, privateJwk, now = Date.now() }) {
|
|
280
|
+
const body = { op: 'continuity', member, from: from || member, ts: now }
|
|
281
|
+
const { signature } = await signWithDevice({ privateKey, privateJwk, publickey: member, data: body })
|
|
282
|
+
return { ...body, sig: signature }
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** ¿La firmó de verdad la identidad que dice venir? (única comprobación posible). */
|
|
286
|
+
export async function verifyContinuity (record) {
|
|
287
|
+
if (!record || record.op !== 'continuity' || !isPub(record.member) || typeof record.sig !== 'string') return false
|
|
288
|
+
const { sig, ...body } = record
|
|
289
|
+
return verifyDeviceSig({ publickey: record.member, data: body, signature: sig })
|
|
290
|
+
}
|
|
291
|
+
|
|
266
292
|
// ----- adopción y empates (§2.4.1) -----
|
|
267
293
|
|
|
268
294
|
/**
|
|
@@ -311,5 +337,6 @@ export async function canAdopt ({ candidate, current }) {
|
|
|
311
337
|
export default {
|
|
312
338
|
ACTA_V, CAPS, CAP_SCOPE, genesisActa, actaBody, actaHash, memberId, checkShape, isHandover,
|
|
313
339
|
sealActa, verifyActa, applyChanges, makeRenounce, verifyRenounce,
|
|
340
|
+
makeContinuity, verifyContinuity,
|
|
314
341
|
effectiveCaps, memberCan, canAdopt
|
|
315
342
|
}
|
package/vault/core.js
CHANGED
|
@@ -1096,7 +1096,13 @@ export async function createIdentityCore ({ kv: rawKv, peers, makeSync = null, k
|
|
|
1096
1096
|
// La privada es la CryptoKey del perfil (no extractable): se pasa como `privateKey`
|
|
1097
1097
|
// y NO se persiste ningún JWK del dispositivo (marcador useIdentityKey).
|
|
1098
1098
|
const device = { publickey: publickeyJwkStr, privateKey: keypair.privateKey }
|
|
1099
|
-
|
|
1099
|
+
// Si esta identidad ya existía por su cuenta, se lleva un certificado de continuidad
|
|
1100
|
+
// firmado por ella misma: es el puente para que su reputación previa siga contando.
|
|
1101
|
+
const mio = loadActa()
|
|
1102
|
+
const continuity = (mio && mio.members.length === 1)
|
|
1103
|
+
? await Acta.makeContinuity({ member: publickeyJwkStr, from: mio.profileId, privateKey: keypair.privateKey })
|
|
1104
|
+
: null
|
|
1105
|
+
const res = await remoteEnroll({ qr, device, continuity, onChallenge: (c) => emitVault({ phase: 'challenge', deviceId: c.deviceId, code: c.code }) })
|
|
1100
1106
|
kv.setItem(VAULT_DEVICE_STORAGE, JSON.stringify({ useIdentityKey: true, publickey: publickeyJwkStr }))
|
|
1101
1107
|
kv.setItem(VAULT_CERT_STORAGE, JSON.stringify({ cert: res.cert, master: res.master, proxy: res.proxy, deviceId: res.deviceId, pairedAt: Date.now() }))
|
|
1102
1108
|
// Conectarse a una bóveda es ENTRAR A SU PERFIL: el acta viene con el cert.
|
package/vault/remote.js
CHANGED
|
@@ -56,7 +56,7 @@ async function identifyAsDevice (client, device) {
|
|
|
56
56
|
* @param {number} [opts.approveTimeoutMs] Espera de la aprobación humana (def 3 min).
|
|
57
57
|
* @returns {Promise<{device, cert, master:string, proxy:string, deviceId:string}>}
|
|
58
58
|
*/
|
|
59
|
-
export async function enrollDevice ({ qr, device, onChallenge, label = '', approveTimeoutMs = 180000 } = {}) {
|
|
59
|
+
export async function enrollDevice ({ qr, device, onChallenge, label = '', continuity = null, approveTimeoutMs = 180000 } = {}) {
|
|
60
60
|
if (!qr?.iss || !qr?.proxy || !qr?.token || !qr?.sn) throw new Error('qr inválido (v2): faltan iss/proxy/token/sn')
|
|
61
61
|
const { WebSocketProxyClient } = await import('@dotrino/proxy-client')
|
|
62
62
|
const client = new WebSocketProxyClient({ url: qr.proxy, enableWebRTC: false, autoReconnect: false })
|
|
@@ -75,7 +75,9 @@ export async function enrollDevice ({ qr, device, onChallenge, label = '', appro
|
|
|
75
75
|
// haber leído el código de ESTA pantalla. Y al ECHARLO de vuelta, el dispositivo confía:
|
|
76
76
|
// una bóveda falsa no conoce el código y no puede enrolarlo.
|
|
77
77
|
const commit = await commitCode({ code, dpub: dev.publickey, sn: qr.sn })
|
|
78
|
-
|
|
78
|
+
// `continuity`: si esta identidad ya existía, va firmada por ella misma para que lo
|
|
79
|
+
// que hizo antes se pueda seguir atribuyendo a la misma persona (ver acta.js).
|
|
80
|
+
const data = { op: 'enroll', dpub: dev.publickey, token: qr.token, sn: qr.sn, commit, label, ts: Date.now(), ...(continuity ? { continuity } : {}) }
|
|
79
81
|
const { signature } = await signWithDevice({ privateJwk: dev.privateJwk, privateKey: dev.privateKey, publickey: dev.publickey, data })
|
|
80
82
|
|
|
81
83
|
const enrolled = new Promise((resolve, reject) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Copia vendorizada de @dotrino/vault@0.
|
|
1
|
+
Copia vendorizada de @dotrino/vault@0.6.0 (lib/src/{index,enroll}.js, sin dependencias).
|
|
2
2
|
El iframe de identity se sirve estatico (vanilla, sin build); asi startDeviceVault
|
|
3
3
|
resuelve en el navegador sin bundler. index.js importa ./enroll.js (relativo, se
|
|
4
4
|
vendoriza tambien) y @dotrino/identity/capabilities (=../../capabilities.js) y
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
* `docs/pairing-protocol.md`).
|
|
31
31
|
*/
|
|
32
32
|
import { verifyDeviceSig, pubkeyId, commitCode } from '@dotrino/identity/capabilities'
|
|
33
|
+
import { verifyContinuity } from '@dotrino/identity/acta'
|
|
33
34
|
|
|
34
35
|
/** Un token de emparejamiento vale 5 min. */
|
|
35
36
|
export const PAIRING_TTL_MS = 5 * 60 * 1000
|
|
@@ -157,6 +158,12 @@ export function createEnrollDesk ({
|
|
|
157
158
|
pend.dpub = d.dpub
|
|
158
159
|
pend.deviceId = deviceId
|
|
159
160
|
pend.commit = d.commit
|
|
161
|
+
// Certificado de continuidad (opcional): lo firma la identidad que se une, con su
|
|
162
|
+
// propia llave. Se comprueba aquí y se guarda con el miembro al aprobar.
|
|
163
|
+
if (d.continuity) {
|
|
164
|
+
const okC = await verifyContinuity(d.continuity)
|
|
165
|
+
pend.continuity = (okC && d.continuity.member === d.dpub) ? d.continuity : null
|
|
166
|
+
}
|
|
160
167
|
pend.from = from // la bóveda NO conoce el código: lo aprende cuando lo tipeas
|
|
161
168
|
if (d.label) pend.label = String(d.label).slice(0, 60)
|
|
162
169
|
|
|
@@ -206,7 +213,7 @@ export function createEnrollDesk ({
|
|
|
206
213
|
try {
|
|
207
214
|
if (typeof identity.admitMember === 'function') {
|
|
208
215
|
const caps = scopeToCaps(pend.scope)
|
|
209
|
-
if (caps.length) await identity.admitMember({ pub: pend.dpub, label: pend.label || '', caps, cert })
|
|
216
|
+
if (caps.length) await identity.admitMember({ pub: pend.dpub, label: pend.label || '', caps, cert, continuity: pend.continuity || null })
|
|
210
217
|
}
|
|
211
218
|
acta = (await identity.profileActa?.())?.acta || null
|
|
212
219
|
} catch (e) { log('[vault] no se pudo admitir en el acta:', e.message) }
|