@soyeht/soyeht 0.2.1 → 0.2.2
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/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/src/index.ts +2 -0
- package/src/pairing.ts +55 -0
- package/src/service.ts +13 -48
- package/src/version.ts +1 -1
package/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
import {
|
|
24
24
|
handleSecurityIdentity,
|
|
25
25
|
handleSecurityPair,
|
|
26
|
+
handleSecurityPairingInfo,
|
|
26
27
|
handleSecurityPairingStart,
|
|
27
28
|
} from "./pairing.js";
|
|
28
29
|
import { PLUGIN_VERSION } from "./version.js";
|
|
@@ -89,6 +90,7 @@ const soyehtPlugin: OpenClawPluginDefinition = {
|
|
|
89
90
|
// Security RPC
|
|
90
91
|
api.registerGatewayMethod("soyeht.security.identity", handleSecurityIdentity(api, v2deps));
|
|
91
92
|
api.registerGatewayMethod("soyeht.security.pairing.start", handleSecurityPairingStart(api, v2deps));
|
|
93
|
+
api.registerGatewayMethod("soyeht.security.pairing.info", handleSecurityPairingInfo(api, v2deps));
|
|
92
94
|
api.registerGatewayMethod("soyeht.security.pair", handleSecurityPair(api, v2deps));
|
|
93
95
|
api.registerGatewayMethod("soyeht.security.handshake.init", handleSecurityHandshake(api, v2deps));
|
|
94
96
|
api.registerGatewayMethod("soyeht.security.handshake.finish", handleSecurityHandshakeFinish(api, v2deps));
|
package/src/pairing.ts
CHANGED
|
@@ -136,6 +136,61 @@ function resolveGatewayUrl(api: OpenClawPluginApi, configGatewayUrl: string): st
|
|
|
136
136
|
return "";
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
// soyeht.security.pairing.info — return session data for a given pairing token
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
|
|
143
|
+
export function handleSecurityPairingInfo(
|
|
144
|
+
_api: OpenClawPluginApi,
|
|
145
|
+
v2deps: SecurityV2Deps,
|
|
146
|
+
): GatewayRequestHandler {
|
|
147
|
+
return async ({ params, respond }) => {
|
|
148
|
+
if (!v2deps.ready) {
|
|
149
|
+
respond(false, undefined, { code: "NOT_READY", message: "Service not ready" });
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (!v2deps.identity) {
|
|
153
|
+
respond(false, undefined, { code: "NO_IDENTITY", message: "Identity not initialized" });
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const pairingToken = params["pairingToken"] as string | undefined;
|
|
158
|
+
if (!pairingToken) {
|
|
159
|
+
respond(false, undefined, {
|
|
160
|
+
code: "INVALID_PARAMS",
|
|
161
|
+
message: "Missing required param: pairingToken",
|
|
162
|
+
});
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const session = v2deps.pairingSessions.get(pairingToken);
|
|
167
|
+
if (!session) {
|
|
168
|
+
respond(false, undefined, {
|
|
169
|
+
code: "PAIRING_REQUIRED",
|
|
170
|
+
message: "No active pairing session. Scan a fresh QR code first.",
|
|
171
|
+
});
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (session.expiresAt <= Date.now()) {
|
|
176
|
+
respond(false, undefined, {
|
|
177
|
+
code: "PAIRING_EXPIRED",
|
|
178
|
+
message: "Pairing QR expired. Scan a fresh QR code first.",
|
|
179
|
+
});
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
respond(true, {
|
|
184
|
+
accountId: session.accountId,
|
|
185
|
+
expiresAt: session.expiresAt,
|
|
186
|
+
allowOverwrite: session.allowOverwrite,
|
|
187
|
+
pluginIdentityKey: v2deps.identity.signKey.publicKeyB64,
|
|
188
|
+
pluginDhKey: v2deps.identity.dhKey.publicKeyB64,
|
|
189
|
+
fingerprint: computeFingerprint(v2deps.identity),
|
|
190
|
+
});
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
139
194
|
// ---------------------------------------------------------------------------
|
|
140
195
|
// soyeht.security.identity — expose plugin public keys
|
|
141
196
|
// ---------------------------------------------------------------------------
|
package/src/service.ts
CHANGED
|
@@ -10,13 +10,11 @@ import {
|
|
|
10
10
|
} from "./security.js";
|
|
11
11
|
import { loadOrGenerateIdentity, loadPeers, loadSessions, saveSession } from "./identity.js";
|
|
12
12
|
import { zeroBuffer } from "./ratchet.js";
|
|
13
|
-
import { base64UrlEncode, computeFingerprint,
|
|
13
|
+
import { base64UrlEncode, computeFingerprint, type X25519KeyPair } from "./crypto.js";
|
|
14
14
|
import type { IdentityBundle, PeerIdentity } from "./identity.js";
|
|
15
15
|
import type { RatchetState } from "./ratchet.js";
|
|
16
16
|
import { createOutboundQueue, type OutboundQueue } from "./outbound-queue.js";
|
|
17
17
|
import { renderQrTerminal } from "./qr.js";
|
|
18
|
-
import { resolveSoyehtAccount } from "./config.js";
|
|
19
|
-
import { buildPairingQrTranscript, buildPairingQrTranscriptV2 } from "./pairing.js";
|
|
20
18
|
|
|
21
19
|
const HEARTBEAT_INTERVAL_MS = 60_000; // 60s
|
|
22
20
|
|
|
@@ -93,49 +91,12 @@ async function showPairingQr(api: OpenClawPluginApi, v2deps: SecurityV2Deps): Pr
|
|
|
93
91
|
const fingerprint = computeFingerprint(identity);
|
|
94
92
|
|
|
95
93
|
// Resolve gatewayUrl from config or runtime
|
|
96
|
-
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
gatewayUrl = runtime["gatewayUrl"];
|
|
103
|
-
} else if (typeof runtime["baseUrl"] === "string" && runtime["baseUrl"]) {
|
|
104
|
-
gatewayUrl = runtime["baseUrl"];
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const basePayload = {
|
|
109
|
-
accountId,
|
|
110
|
-
pairingToken,
|
|
111
|
-
expiresAt,
|
|
112
|
-
allowOverwrite: false,
|
|
113
|
-
pluginIdentityKey: identity.signKey.publicKeyB64,
|
|
114
|
-
pluginDhKey: identity.dhKey.publicKeyB64,
|
|
115
|
-
fingerprint,
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
let qrPayload: Record<string, unknown>;
|
|
119
|
-
|
|
120
|
-
if (gatewayUrl) {
|
|
121
|
-
const transcript = buildPairingQrTranscriptV2({ gatewayUrl, ...basePayload });
|
|
122
|
-
const signature = base64UrlEncode(ed25519Sign(identity.signKey.privateKey, transcript));
|
|
123
|
-
qrPayload = {
|
|
124
|
-
version: 2,
|
|
125
|
-
type: "soyeht_pairing_qr",
|
|
126
|
-
gatewayUrl,
|
|
127
|
-
...basePayload,
|
|
128
|
-
signature,
|
|
129
|
-
};
|
|
130
|
-
} else {
|
|
131
|
-
const transcript = buildPairingQrTranscript(basePayload);
|
|
132
|
-
const signature = base64UrlEncode(ed25519Sign(identity.signKey.privateKey, transcript));
|
|
133
|
-
qrPayload = {
|
|
134
|
-
version: 1,
|
|
135
|
-
type: "soyeht_pairing_qr",
|
|
136
|
-
...basePayload,
|
|
137
|
-
signature,
|
|
138
|
-
};
|
|
94
|
+
let gatewayUrl = "";
|
|
95
|
+
const runtime = api.runtime as Record<string, unknown>;
|
|
96
|
+
if (typeof runtime["gatewayUrl"] === "string" && runtime["gatewayUrl"]) {
|
|
97
|
+
gatewayUrl = runtime["gatewayUrl"];
|
|
98
|
+
} else if (typeof runtime["baseUrl"] === "string" && runtime["baseUrl"]) {
|
|
99
|
+
gatewayUrl = runtime["baseUrl"];
|
|
139
100
|
}
|
|
140
101
|
|
|
141
102
|
v2deps.pairingSessions.set(pairingToken, {
|
|
@@ -145,11 +106,15 @@ async function showPairingQr(api: OpenClawPluginApi, v2deps: SecurityV2Deps): Pr
|
|
|
145
106
|
allowOverwrite: false,
|
|
146
107
|
});
|
|
147
108
|
|
|
148
|
-
|
|
109
|
+
// Compact QR: soyeht://pair?g=<gatewayUrl>&t=<token>&fp=<fingerprint>
|
|
110
|
+
// App fetches full key material via RPC soyeht.security.pairing.info
|
|
111
|
+
const qrText = `soyeht://pair?g=${encodeURIComponent(gatewayUrl)}&t=${pairingToken}&fp=${fingerprint}`;
|
|
149
112
|
const rendered = renderQrTerminal(qrText);
|
|
150
113
|
|
|
151
114
|
if (rendered) {
|
|
152
|
-
|
|
115
|
+
// Write QR directly to stdout to avoid logger prefixes breaking ANSI escape codes
|
|
116
|
+
process.stdout.write("\n" + rendered + "\n\n");
|
|
117
|
+
api.logger.info(`[soyeht] Scan the QR code above with the Soyeht app to pair`);
|
|
153
118
|
api.logger.info(`[soyeht] Fingerprint: ${fingerprint}`);
|
|
154
119
|
api.logger.info(`[soyeht] QR expires in ${AUTO_PAIRING_TTL_MS / 1000}s — restart plugin to generate a new one`);
|
|
155
120
|
} else {
|
package/src/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const PLUGIN_VERSION = "0.2.
|
|
1
|
+
export const PLUGIN_VERSION = "0.2.2";
|