@vellumai/vellum-gateway 0.4.6 → 0.4.7
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/ARCHITECTURE.md
CHANGED
|
@@ -88,6 +88,9 @@ Guardian verification endpoints are exposed directly by the gateway and forwarde
|
|
|
88
88
|
| POST | `/v1/integrations/guardian/outbound/start` |
|
|
89
89
|
| POST | `/v1/integrations/guardian/outbound/resend` |
|
|
90
90
|
| POST | `/v1/integrations/guardian/outbound/cancel` |
|
|
91
|
+
| POST | `/v1/integrations/guardian/vellum/refresh` |
|
|
92
|
+
|
|
93
|
+
The `/vellum/refresh` endpoint is the only public ingress for rotating actor + refresh token credentials. Clients must call this through the gateway; the runtime endpoint is not directly exposed. The gateway validates the caller's bearer token and forwards to the runtime, which handles refresh token validation, rotation, and replay detection (see [`assistant/ARCHITECTURE.md`](../assistant/ARCHITECTURE.md) for refresh token lifecycle details).
|
|
91
94
|
|
|
92
95
|
**Authentication boundary:**
|
|
93
96
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vellumai/vellum-gateway",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.7",
|
|
4
4
|
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
"./twilio/verify": "./src/twilio/verify.ts",
|
|
7
|
+
"./package.json": "./package.json"
|
|
8
|
+
},
|
|
5
9
|
"scripts": {
|
|
6
10
|
"dev": "bun run --watch src/index.ts",
|
|
7
11
|
"dev:proxy": "GATEWAY_RUNTIME_PROXY_ENABLED=true GATEWAY_RUNTIME_PROXY_REQUIRE_AUTH=false bun run --watch src/index.ts",
|
|
@@ -107,5 +107,13 @@ export function createGuardianControlPlaneProxyHandler(config: GatewayConfig) {
|
|
|
107
107
|
async handleCancelGuardianOutbound(req: Request): Promise<Response> {
|
|
108
108
|
return proxyToRuntime(req, "/v1/integrations/guardian/outbound/cancel", "");
|
|
109
109
|
},
|
|
110
|
+
|
|
111
|
+
async handleGuardianVellumBootstrap(req: Request): Promise<Response> {
|
|
112
|
+
return proxyToRuntime(req, "/v1/integrations/guardian/vellum/bootstrap", "");
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
async handleGuardianRefresh(req: Request): Promise<Response> {
|
|
116
|
+
return proxyToRuntime(req, "/v1/integrations/guardian/vellum/refresh", "");
|
|
117
|
+
},
|
|
110
118
|
};
|
|
111
119
|
}
|
package/src/index.ts
CHANGED
|
@@ -522,6 +522,13 @@ function main() {
|
|
|
522
522
|
}
|
|
523
523
|
}
|
|
524
524
|
|
|
525
|
+
// ── Guardian vellum bootstrap (actor token) ──
|
|
526
|
+
if (url.pathname === "/v1/integrations/guardian/vellum/bootstrap" && req.method === "POST") {
|
|
527
|
+
const authError = requireRuntimeBearerAuth();
|
|
528
|
+
if (authError) return authError;
|
|
529
|
+
return guardianControlPlaneProxy.handleGuardianVellumBootstrap(tracedReq);
|
|
530
|
+
}
|
|
531
|
+
|
|
525
532
|
// ── Guardian verification control-plane proxy ──
|
|
526
533
|
if (
|
|
527
534
|
(url.pathname === "/v1/integrations/guardian/challenge" && req.method === "POST")
|
|
@@ -548,6 +555,13 @@ function main() {
|
|
|
548
555
|
return guardianControlPlaneProxy.handleCancelGuardianOutbound(tracedReq);
|
|
549
556
|
}
|
|
550
557
|
|
|
558
|
+
// ── Guardian vellum refresh proxy ──
|
|
559
|
+
if (url.pathname === "/v1/integrations/guardian/vellum/refresh" && req.method === "POST") {
|
|
560
|
+
const authError = requireRuntimeBearerAuth();
|
|
561
|
+
if (authError) return authError;
|
|
562
|
+
return guardianControlPlaneProxy.handleGuardianRefresh(tracedReq);
|
|
563
|
+
}
|
|
564
|
+
|
|
551
565
|
// ── Twilio integration control-plane proxy ──
|
|
552
566
|
if (
|
|
553
567
|
(url.pathname === "/v1/integrations/twilio/config" && req.method === "GET")
|
package/src/twilio/verify.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* Compute a Twilio-compatible HMAC-SHA1 signature.
|
|
5
5
|
*
|
|
6
6
|
* Algorithm (from Twilio docs):
|
|
7
7
|
* 1. Take the full URL of the request.
|
|
@@ -9,25 +9,35 @@ import { createHmac, timingSafeEqual } from "node:crypto";
|
|
|
9
9
|
* 3. Concatenate the URL with each key-value pair (key + value, no delimiters).
|
|
10
10
|
* 4. HMAC-SHA1 the result using the auth token as the key.
|
|
11
11
|
* 5. Base64-encode the hash.
|
|
12
|
-
* 6. Compare to the X-Twilio-Signature header value.
|
|
13
12
|
*/
|
|
14
|
-
export function
|
|
13
|
+
export function computeTwilioSignature(
|
|
15
14
|
url: string,
|
|
16
15
|
params: Record<string, string>,
|
|
17
|
-
signature: string,
|
|
18
16
|
authToken: string,
|
|
19
|
-
):
|
|
17
|
+
): string {
|
|
20
18
|
const sortedKeys = Object.keys(params).sort();
|
|
21
19
|
let data = url;
|
|
22
20
|
for (const key of sortedKeys) {
|
|
23
21
|
data += key + params[key];
|
|
24
22
|
}
|
|
25
23
|
|
|
26
|
-
|
|
24
|
+
return createHmac("sha1", authToken)
|
|
27
25
|
.update(data)
|
|
28
26
|
.digest("base64");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Verify a Twilio X-Twilio-Signature header using HMAC-SHA1 with
|
|
31
|
+
* constant-time comparison to prevent timing attacks.
|
|
32
|
+
*/
|
|
33
|
+
export function verifyTwilioSignature(
|
|
34
|
+
url: string,
|
|
35
|
+
params: Record<string, string>,
|
|
36
|
+
signature: string,
|
|
37
|
+
authToken: string,
|
|
38
|
+
): boolean {
|
|
39
|
+
const computed = computeTwilioSignature(url, params, authToken);
|
|
29
40
|
|
|
30
|
-
// Constant-time comparison to prevent timing attacks
|
|
31
41
|
const a = Buffer.from(computed);
|
|
32
42
|
const b = Buffer.from(signature);
|
|
33
43
|
if (a.length !== b.length) return false;
|