@dotrino/remote-agent 0.1.0 → 0.2.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 +11 -3
- package/src/link.js +26 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotrino/remote-agent",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.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",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"test": "node -e \"import('@dotrino/remote-agent').then(()=>console.log('ok'))\""
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@dotrino/identity": "0.
|
|
25
|
+
"@dotrino/identity": "0.23.0",
|
|
26
26
|
"@dotrino/proxy-client": "0.6.4",
|
|
27
27
|
"ws": "8.21.0"
|
|
28
28
|
},
|
|
@@ -35,5 +35,13 @@
|
|
|
35
35
|
"url": "git+ssh://git@github.com/imdotrino/dotrino-remote-agent.git"
|
|
36
36
|
},
|
|
37
37
|
"homepage": "https://github.com/imdotrino/dotrino-remote-agent",
|
|
38
|
-
"keywords": [
|
|
38
|
+
"keywords": [
|
|
39
|
+
"dotrino",
|
|
40
|
+
"remote",
|
|
41
|
+
"agent",
|
|
42
|
+
"e2e",
|
|
43
|
+
"vault",
|
|
44
|
+
"proxy",
|
|
45
|
+
"p2p"
|
|
46
|
+
]
|
|
39
47
|
}
|
package/src/link.js
CHANGED
|
@@ -17,7 +17,7 @@ import fs from 'node:fs'
|
|
|
17
17
|
import path from 'node:path'
|
|
18
18
|
import os from 'node:os'
|
|
19
19
|
import {
|
|
20
|
-
makeDeviceKey, signWithDevice, verifyDelegation, makePairingCode, pubkeyId
|
|
20
|
+
makeDeviceKey, signWithDevice, verifyDelegation, makePairingCode, commitCode, pubkeyId
|
|
21
21
|
} from '@dotrino/identity/capabilities'
|
|
22
22
|
import { installNodeGlobals } from '../node-globals.js'
|
|
23
23
|
|
|
@@ -47,11 +47,31 @@ function saveLink (dir, link) {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
export function parseQr (text) {
|
|
50
|
-
const qr =
|
|
50
|
+
const qr = normalizeQr(text)
|
|
51
51
|
if (!qr?.iss || !qr?.proxy || !qr?.token || !qr?.sn) throw new Error('QR inválido (v2): faltan iss/proxy/token/sn')
|
|
52
52
|
return qr
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
// Acepta el código de emparejamiento en 3 formas: JSON crudo, base64url del JSON, o
|
|
56
|
+
// URL "profile.dotrino.com/#vault=<base64url>". El base64url es el formato canónico
|
|
57
|
+
// del ecosistema (no expone la estructura en texto plano).
|
|
58
|
+
export function b64urlDecode (s) {
|
|
59
|
+
const b64 = String(s).replace(/-/g, '+').replace(/_/g, '/')
|
|
60
|
+
return Buffer.from(b64, 'base64').toString('utf8')
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function normalizeQr (text) {
|
|
64
|
+
let s = String(text ?? '').trim()
|
|
65
|
+
const i = s.indexOf('#vault=')
|
|
66
|
+
if (i >= 0) s = s.slice(i + 7).trim() // profile.dotrino.com/#vault=…
|
|
67
|
+
if (!s.startsWith('{')) { // no es JSON → probar base64url
|
|
68
|
+
const json = b64urlDecode(s)
|
|
69
|
+
if (json && json.trim().startsWith('{')) s = json
|
|
70
|
+
else throw new Error('código de emparejamiento inválido (se espera JSON o base64url)')
|
|
71
|
+
}
|
|
72
|
+
return JSON.parse(s)
|
|
73
|
+
}
|
|
74
|
+
|
|
55
75
|
async function freshClient (proxyUrl, dir) {
|
|
56
76
|
installNodeGlobals(dir)
|
|
57
77
|
const { WebSocketProxyClient } = await import('@dotrino/proxy-client')
|
|
@@ -81,7 +101,10 @@ export async function enroll ({ qr, label = 'remote-agent', dir = dataDir(), onC
|
|
|
81
101
|
// lo mostramos y NO lo enviamos. La bóveda lo aprende solo cuando un humano lo tipea
|
|
82
102
|
// y nos lo ECHA de vuelta → así aprobar exige TENER esta máquina (de aquí sale el código).
|
|
83
103
|
const code = makePairingCode()
|
|
84
|
-
|
|
104
|
+
// El COMPROMISO del código (nunca el código): la bóveda lo recompone con lo que
|
|
105
|
+
// tipeas y solo entonces firma el cert → aprobar exige haber leído esta pantalla.
|
|
106
|
+
const commit = await commitCode({ code, dpub: device.publickey, sn: qr.sn })
|
|
107
|
+
const data = { op: 'enroll', dpub: device.publickey, token: qr.token, sn: qr.sn, commit, label, ts: Date.now() }
|
|
85
108
|
const { signature } = await signWithDevice({ privateJwk: device.privateJwk, data })
|
|
86
109
|
|
|
87
110
|
const done = new Promise((resolve, reject) => {
|