@stage-labs/metro 0.1.0-beta.98 → 0.1.0-beta.99

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 (26) hide show
  1. package/package.json +1 -1
  2. package/runtime/marketplace/plugin/.claude-plugin/plugin.json +1 -1
  3. package/runtime/node_modules/@metro-labs/core/src/lines.ts +5 -0
  4. package/runtime/node_modules/@metro-labs/core/src/station-names.ts +1 -0
  5. package/runtime/node_modules/@metro-labs/daemon/package.json +1 -0
  6. package/runtime/node_modules/@metro-labs/daemon/src/agents/accounts-api.ts +2 -4
  7. package/runtime/node_modules/@metro-labs/daemon/src/mcp/accounts.ts +13 -2
  8. package/runtime/node_modules/@metro-labs/daemon/src/routes/body.ts +24 -0
  9. package/runtime/node_modules/@metro-labs/daemon/src/routes/http.ts +5 -23
  10. package/runtime/node_modules/@metro-labs/daemon/src/routes/threema-callback.ts +103 -0
  11. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach.ts +64 -0
  12. package/runtime/node_modules/@metro-labs/daemon/src/stations/materialize.ts +6 -1
  13. package/runtime/node_modules/@metro-labs/daemon/src/stations/registry.ts +2 -0
  14. package/runtime/node_modules/@metro-labs/daemon/src/stations/threema-callbacks.ts +32 -0
  15. package/runtime/node_modules/@metro-labs/threema/package.json +26 -0
  16. package/runtime/node_modules/@metro-labs/threema/src/accounts.ts +100 -0
  17. package/runtime/node_modules/@metro-labs/threema/src/actions.ts +219 -0
  18. package/runtime/node_modules/@metro-labs/threema/src/api.ts +130 -0
  19. package/runtime/node_modules/@metro-labs/threema/src/crypto.ts +132 -0
  20. package/runtime/node_modules/@metro-labs/threema/src/format.ts +123 -0
  21. package/runtime/node_modules/@metro-labs/threema/src/ids.ts +21 -0
  22. package/runtime/node_modules/@metro-labs/threema/src/index.ts +29 -0
  23. package/runtime/node_modules/@metro-labs/threema/src/station.ts +10 -0
  24. package/runtime/node_modules/@metro-labs/threema/src/verify.ts +117 -0
  25. package/runtime/runtime.json +1 -1
  26. package/runtime/stations.json +3 -0
@@ -0,0 +1,117 @@
1
+ import { createPrivateKey, createPublicKey } from 'node:crypto';
2
+ import {
3
+ GATEWAY_API,
4
+ isGatewayId,
5
+ normalizeThreemaId,
6
+ parsePrivateKey,
7
+ } from './ids.js';
8
+
9
+ export { parsePrivateKey } from './ids.js';
10
+
11
+ export class ThreemaGatewayError extends Error {}
12
+
13
+ export interface ThreemaCredentials {
14
+ gatewayId: string;
15
+ secret: string;
16
+ privateKey: string;
17
+ }
18
+
19
+ export interface ThreemaGatewayIdentity {
20
+ gatewayId: string;
21
+ publicKey: string;
22
+ credits: number;
23
+ }
24
+
25
+ const PKCS8_X25519_PREFIX = Buffer.from(
26
+ '302e020100300506032b656e04220420',
27
+ 'hex',
28
+ );
29
+ const TIMEOUT_MS = 10_000;
30
+ const PUBLIC_KEY_RE = /^[0-9a-f]{64}$/;
31
+
32
+ export function publicKeyOf(privateKeyHex: string): string {
33
+ const der = Buffer.concat([
34
+ PKCS8_X25519_PREFIX,
35
+ Buffer.from(privateKeyHex, 'hex'),
36
+ ]);
37
+ const key = createPrivateKey({ key: der, format: 'der', type: 'pkcs8' });
38
+ const jwk = createPublicKey(key).export({ format: 'jwk' });
39
+ return Buffer.from(String(jwk.x), 'base64url').toString('hex');
40
+ }
41
+
42
+ async function gatewayGet(
43
+ path: string,
44
+ gatewayId: string,
45
+ secret: string,
46
+ ): Promise<Response> {
47
+ const query = new URLSearchParams({ from: gatewayId, secret });
48
+ try {
49
+ return await fetch(`${GATEWAY_API}${path}?${query.toString()}`, {
50
+ signal: AbortSignal.timeout(TIMEOUT_MS),
51
+ });
52
+ } catch {
53
+ throw new ThreemaGatewayError(
54
+ 'could not reach the Threema Gateway to check those credentials',
55
+ );
56
+ }
57
+ }
58
+
59
+ function refused(res: Response, what: string): ThreemaGatewayError {
60
+ if (res.status === 401)
61
+ return new ThreemaGatewayError(
62
+ 'Threema rejected that Gateway ID or API secret',
63
+ );
64
+ if (res.status === 404)
65
+ return new ThreemaGatewayError(`Threema does not know ${what}`);
66
+ return new ThreemaGatewayError(
67
+ `the Threema Gateway answered ${res.status} while checking ${what}`,
68
+ );
69
+ }
70
+
71
+ async function fetchCredits(gatewayId: string, secret: string): Promise<number> {
72
+ const res = await gatewayGet('/credits', gatewayId, secret);
73
+ if (!res.ok) throw refused(res, 'the credit balance');
74
+ const credits = Number((await res.text()).trim());
75
+ if (!Number.isInteger(credits))
76
+ throw new ThreemaGatewayError(
77
+ 'the Threema Gateway gave an unexpected answer for the credit balance',
78
+ );
79
+ return credits;
80
+ }
81
+
82
+ async function fetchPublicKey(gatewayId: string, secret: string): Promise<string> {
83
+ const res = await gatewayGet(
84
+ `/pubkeys/${encodeURIComponent(gatewayId)}`,
85
+ gatewayId,
86
+ secret,
87
+ );
88
+ if (!res.ok) throw refused(res, `the public key of ${gatewayId}`);
89
+ const hex = (await res.text()).trim().toLowerCase();
90
+ if (!PUBLIC_KEY_RE.test(hex))
91
+ throw new ThreemaGatewayError(
92
+ 'the Threema Gateway gave an unexpected answer for the public key',
93
+ );
94
+ return hex;
95
+ }
96
+
97
+ export async function verifyThreemaGateway(
98
+ input: ThreemaCredentials,
99
+ ): Promise<ThreemaGatewayIdentity> {
100
+ const gatewayId = normalizeThreemaId(input.gatewayId);
101
+ if (!isGatewayId(gatewayId))
102
+ throw new ThreemaGatewayError(
103
+ 'a Threema Gateway ID is a * followed by 7 letters or digits, as in *ABCDEFG',
104
+ );
105
+ const privateKey = parsePrivateKey(input.privateKey);
106
+ if (privateKey === null)
107
+ throw new ThreemaGatewayError(
108
+ 'the private key is the 64 hex characters from the key file Threema gave you, with or without its private: prefix',
109
+ );
110
+ const credits = await fetchCredits(gatewayId, input.secret);
111
+ const registered = await fetchPublicKey(gatewayId, input.secret);
112
+ if (registered !== publicKeyOf(privateKey))
113
+ throw new ThreemaGatewayError(
114
+ `that private key does not belong to ${gatewayId}: the Gateway holds a different public key for it`,
115
+ );
116
+ return { gatewayId, publicKey: registered, credits };
117
+ }
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "0.1.0-beta.98"
2
+ "version": "0.1.0-beta.99"
3
3
  }
@@ -31,6 +31,9 @@
31
31
  "whatsapp": {
32
32
  "baileys": "7.0.0-rc14"
33
33
  },
34
+ "threema": {
35
+ "tweetnacl": "^1.0.3"
36
+ },
34
37
  "webhook": {}
35
38
  }
36
39
  }