@dotrino/remote-agent 0.18.3 → 0.19.1
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 +5 -5
- package/src/link.js +40 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotrino/remote-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.1",
|
|
4
4
|
"description": "Middleware compartido del ecosistema Dotrino para apps de agente remoto cifrado punto a punto (handshake E2E, emparejamiento SAS con vault, revocación, descubrimiento). Lo consumen dotrino-terminal (PTY) y dotrino-ia (chat con IA).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.js",
|
|
@@ -46,13 +46,13 @@
|
|
|
46
46
|
"p2p"
|
|
47
47
|
],
|
|
48
48
|
"peerDependencies": {
|
|
49
|
-
"@dotrino/identity": ">=0.
|
|
49
|
+
"@dotrino/identity": ">=0.115.0",
|
|
50
50
|
"@dotrino/proxy-client": ">=0.30.0",
|
|
51
|
-
"@dotrino/vault": ">=0.
|
|
51
|
+
"@dotrino/vault": ">=0.85.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"@dotrino/identity": "0.
|
|
54
|
+
"@dotrino/identity": "0.116.0",
|
|
55
55
|
"@dotrino/proxy-client": "0.30.0",
|
|
56
|
-
"@dotrino/vault": "0.
|
|
56
|
+
"@dotrino/vault": "0.86.0"
|
|
57
57
|
}
|
|
58
58
|
}
|
package/src/link.js
CHANGED
|
@@ -20,6 +20,7 @@ import { signWithDevice, pubkeyId } from '@dotrino/identity/capabilities'
|
|
|
20
20
|
import { requestDevices, requestRenew } from '@dotrino/identity/vault/remote.js'
|
|
21
21
|
import { installNodeGlobals } from '../node-globals.js'
|
|
22
22
|
import { adoptRecord } from './record.js'
|
|
23
|
+
import { verifySealerChain } from '@dotrino/identity/acta'
|
|
23
24
|
|
|
24
25
|
export function dataDir (name = 'dotrino-remote-agent') {
|
|
25
26
|
if (process.env.DOTRINO_REMOTE_AGENT_DIR) return process.env.DOTRINO_REMOTE_AGENT_DIR
|
|
@@ -116,7 +117,12 @@ export function identityFromLink (link, { dir = dataDir() } = {}) {
|
|
|
116
117
|
return {
|
|
117
118
|
me: { publickey: device.publickey },
|
|
118
119
|
async signData (data) {
|
|
119
|
-
|
|
120
|
+
const firmado = await signWithDevice({ privateJwk: device.privateJwk, publickey: device.publickey, data })
|
|
121
|
+
// A NOMBRE DE QUIÉN VA, igual que en el navegador: el `profileId` y la cadena de
|
|
122
|
+
// selladores que lo prueba (`refreshChain`). Sin cadena la firma vale ante la bóveda
|
|
123
|
+
// (que conoce el papel) pero no ante un extraño que use `verifySignedFor`.
|
|
124
|
+
const chain = Array.isArray(link.sealerChain) && link.sealerChain.length ? link.sealerChain : null
|
|
125
|
+
return { ...firmado, profileId: chain ? chain[0].profileId : null, chain }
|
|
120
126
|
},
|
|
121
127
|
async getVaultCert () { return { cert: link.cert } },
|
|
122
128
|
async vaultStatus () {
|
|
@@ -192,9 +198,41 @@ export async function renewLink (link, { dir = dataDir(), force = false } = {})
|
|
|
192
198
|
}
|
|
193
199
|
}
|
|
194
200
|
|
|
201
|
+
/**
|
|
202
|
+
* TRAE LA CADENA DE SELLADORES de la cuenta y la guarda en el enlace: con ella `signData`
|
|
203
|
+
* firma a nombre de la cuenta de forma que `verifySignedFor` lo compruebe desde el génesis.
|
|
204
|
+
* Se comprueba ANTES de guardarla (`verifySealerChain`): una cadena que no encadena o que es
|
|
205
|
+
* de otra cuenta no entra. Un agente que firma de cara a terceros la pide al arrancar y la
|
|
206
|
+
* refresca de vez en cuando. No lanza: devuelve qué pasó.
|
|
207
|
+
*
|
|
208
|
+
* @returns {Promise<{ ok: boolean, seq?: number, reason?: string }>}
|
|
209
|
+
*/
|
|
210
|
+
export async function refreshChain (link, { dir = dataDir() } = {}) {
|
|
211
|
+
if (!link?.device?.privateJwk || !link?.cert || !link?.iss) return { ok: false, reason: 'invalid link' }
|
|
212
|
+
installNodeGlobals(dir)
|
|
213
|
+
try {
|
|
214
|
+
const res = await requestDevices({ master: link.iss, proxy: link.proxy || 'wss://proxy.dotrino.com', device: link.device, cert: link.cert })
|
|
215
|
+
const chain = res.sealerChain
|
|
216
|
+
if (!Array.isArray(chain) || !chain.length) return { ok: false, reason: 'the vault sent no sealer chain (vaultd < 0.154.0?)' }
|
|
217
|
+
const expected = link.sealerChain?.[0]?.profileId ?? link.acta?.profileId ?? null
|
|
218
|
+
const v = await verifySealerChain(chain, { expectedProfileId: expected })
|
|
219
|
+
if (!v.ok) return { ok: false, reason: 'bad sealer chain: ' + v.reason }
|
|
220
|
+
const head = chain[chain.length - 1]
|
|
221
|
+
if (typeof link.sealerChain?.[link.sealerChain.length - 1]?.seq === 'number' && head.seq < link.sealerChain[link.sealerChain.length - 1].seq) {
|
|
222
|
+
return { ok: false, reason: 'older than the chain already stored' }
|
|
223
|
+
}
|
|
224
|
+
link.sealerChain = chain
|
|
225
|
+
saveLink(dir, link)
|
|
226
|
+
return { ok: true, seq: head.seq }
|
|
227
|
+
} catch (e) {
|
|
228
|
+
// Un fallo de red llega a veces sin frase: se dice el código, o al menos que no se alcanzó.
|
|
229
|
+
return { ok: false, reason: e?.message || e?.code || 'unreachable' }
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
195
233
|
/** El enlace como lo esperan `ContentClient.connect({ link })` y `RemoteAgentClient`. */
|
|
196
234
|
export function clientLink (link, opts) {
|
|
197
235
|
return { id: identityFromLink(link, opts), cert: link.cert, iss: link.iss, proxy: link.proxy || 'wss://proxy.dotrino.com' }
|
|
198
236
|
}
|
|
199
237
|
|
|
200
|
-
export default { dataDir, loadLink, saveLink, parseQr, enroll, identityFromLink, clientLink, renewLink }
|
|
238
|
+
export default { dataDir, loadLink, saveLink, parseQr, enroll, identityFromLink, clientLink, renewLink, refreshChain }
|