@vellumai/vellum-gateway 0.4.46 → 0.4.48
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 +4 -4
- package/package.json +1 -1
- package/src/__tests__/credential-cache.test.ts +3 -2
- package/src/__tests__/credential-reader.test.ts +24 -23
- package/src/__tests__/credential-watcher.test.ts +2 -2
- package/src/__tests__/load-guards.test.ts +2 -1
- package/src/__tests__/slack-deliver-ratelimit.test.ts +2 -1
- package/src/__tests__/slack-deliver.test.ts +3 -2
- package/src/__tests__/telegram-api-redaction.test.ts +2 -1
- package/src/__tests__/telegram-deliver-auth.test.ts +3 -1
- package/src/__tests__/telegram-send-attachments.test.ts +2 -1
- package/src/__tests__/telegram-webhook-handler.test.ts +5 -2
- package/src/__tests__/telegram-webhook-manager.test.ts +3 -2
- package/src/__tests__/twilio-webhooks.test.ts +2 -1
- package/src/__tests__/whatsapp-download.test.ts +4 -2
- package/src/credential-key.ts +17 -0
- package/src/credential-reader.ts +23 -10
- package/src/http/routes/slack-deliver.ts +5 -2
- package/src/http/routes/telegram-webhook.test.ts +3 -1
- package/src/http/routes/telegram-webhook.ts +5 -2
- package/src/http/routes/whatsapp-webhook.test.ts +15 -12
- package/src/http/routes/whatsapp-webhook.ts +5 -4
- package/src/index.ts +3 -2
- package/src/telegram/api.ts +7 -2
- package/src/telegram/download.ts +2 -1
- package/src/telegram/send.test.ts +2 -1
- package/src/telegram/webhook-manager.ts +5 -2
- package/src/twilio/validate-webhook.ts +4 -3
- package/src/whatsapp/api.ts +3 -2
package/ARCHITECTURE.md
CHANGED
|
@@ -584,11 +584,11 @@ Entry points:
|
|
|
584
584
|
|
|
585
585
|
Both paths converge at:
|
|
586
586
|
→ Daemon handler validates token via Telegram getMe API
|
|
587
|
-
→ setSecureKey("credential
|
|
587
|
+
→ setSecureKey("credential/telegram/bot_token", token)
|
|
588
588
|
→ upsertCredentialMetadata("telegram", "bot_token", {})
|
|
589
589
|
→ Stores bot username in config at telegram.botUsername
|
|
590
590
|
→ Auto-generates webhook secret if missing
|
|
591
|
-
→ setSecureKey("credential
|
|
591
|
+
→ setSecureKey("credential/telegram/webhook_secret", secret)
|
|
592
592
|
→ upsertCredentialMetadata("telegram", "webhook_secret", {})
|
|
593
593
|
→ On storage failure: rolls back bot_token + metadata, returns error
|
|
594
594
|
→ If webhook secret already exists: upserts metadata anyway (self-heal)
|
|
@@ -660,7 +660,7 @@ The Slack channel requires two tokens:
|
|
|
660
660
|
| App token | `xapp-...` | Used for `apps.connections.open` to establish the Socket Mode WebSocket connection |
|
|
661
661
|
| Bot token | `xoxb-...` | Used for `chat.postMessage` to send outbound messages and for `auth.test` validation |
|
|
662
662
|
|
|
663
|
-
Both tokens are stored in secure storage (`credential
|
|
663
|
+
Both tokens are stored in secure storage (`credential/slack_channel/app_token`, `credential/slack_channel/bot_token`) via the assistant's Slack channel config endpoints (see `assistant/ARCHITECTURE.md`). The gateway reads them via its `credential-reader` module using the same broker-first fallback strategy as Telegram credentials.
|
|
664
664
|
|
|
665
665
|
**Auto-reconnect behavior:**
|
|
666
666
|
|
|
@@ -1057,7 +1057,7 @@ The resolution is performed by `resolveCallerIdentity()` in `call-domain.ts`:
|
|
|
1057
1057
|
|
|
1058
1058
|
1. **Per-call override** — If `callerIdentityMode` is provided in the call input and `calls.callerIdentity.allowPerCallOverride` is enabled, the requested mode is used (source: `per_call_override`).
|
|
1059
1059
|
2. **Implicit default** — Otherwise, `assistant_number` is always used (source: `implicit_default`). There is no configurable default mode — this is a strict policy.
|
|
1060
|
-
3. **User number lookup** — For `user_number` mode (explicit only), the number is resolved from (in priority order): `calls.callerIdentity.userNumber` config (source: `user_config`), `TWILIO_USER_PHONE_NUMBER` environment variable (source: `env_var`), or the `credential
|
|
1060
|
+
3. **User number lookup** — For `user_number` mode (explicit only), the number is resolved from (in priority order): `calls.callerIdentity.userNumber` config (source: `user_config`), `TWILIO_USER_PHONE_NUMBER` environment variable (source: `env_var`), or the `credential/twilio/user_phone_number` secure key (source: `secure_key`).
|
|
1061
1061
|
4. **Eligibility check** — User numbers are verified against the Twilio API to confirm they can be used as an outbound caller ID.
|
|
1062
1062
|
|
|
1063
1063
|
Both the resolved mode and source are logged at info level on success, and rejections are logged at warn level.
|
package/package.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { describe, test, expect, mock, beforeEach } from "bun:test";
|
|
2
|
+
import { credentialKey } from "../credential-key.js";
|
|
2
3
|
|
|
3
4
|
// ---------------------------------------------------------------------------
|
|
4
5
|
// Mock readCredential so tests don't touch the real credential store
|
|
@@ -38,8 +39,8 @@ beforeEach(() => {
|
|
|
38
39
|
describe("CredentialCache", () => {
|
|
39
40
|
test("returns the value from readCredential", async () => {
|
|
40
41
|
const cache = new CredentialCache();
|
|
41
|
-
const result = await cache.get("
|
|
42
|
-
expect(result).toBe(
|
|
42
|
+
const result = await cache.get(credentialKey("test", "key"));
|
|
43
|
+
expect(result).toBe(`value-for-${credentialKey("test", "key")}`);
|
|
43
44
|
expect(callCount).toBe(1);
|
|
44
45
|
});
|
|
45
46
|
|
|
@@ -4,6 +4,7 @@ import { createServer, type Server } from "node:net";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { hostname, tmpdir, userInfo } from "node:os";
|
|
6
6
|
import { createCipheriv, pbkdf2Sync, randomBytes } from "node:crypto";
|
|
7
|
+
import { credentialKey } from "../credential-key.js";
|
|
7
8
|
|
|
8
9
|
// ---------------------------------------------------------------------------
|
|
9
10
|
// Logger mock — captures all log calls so the secret-leak test can inspect them
|
|
@@ -261,8 +262,8 @@ describe("readTelegramCredentials", () => {
|
|
|
261
262
|
]);
|
|
262
263
|
|
|
263
264
|
writeEncryptedStore({
|
|
264
|
-
"
|
|
265
|
-
"
|
|
265
|
+
[credentialKey("telegram", "bot_token")]: "enc-bot-token",
|
|
266
|
+
[credentialKey("telegram", "webhook_secret")]: "enc-webhook-secret",
|
|
266
267
|
});
|
|
267
268
|
|
|
268
269
|
const result = await readTelegramCredentials();
|
|
@@ -280,7 +281,7 @@ describe("readTelegramCredentials", () => {
|
|
|
280
281
|
describe("readCredential broker integration", () => {
|
|
281
282
|
test("returns undefined when broker env var is unset", async () => {
|
|
282
283
|
delete process.env.VELLUM_KEYCHAIN_BROKER_SOCKET;
|
|
283
|
-
const result = await readCredential("
|
|
284
|
+
const result = await readCredential(credentialKey("test", "key"));
|
|
284
285
|
expect(result).toBeUndefined();
|
|
285
286
|
});
|
|
286
287
|
|
|
@@ -289,10 +290,10 @@ describe("readCredential broker integration", () => {
|
|
|
289
290
|
delete process.env.VELLUM_KEYCHAIN_BROKER_SOCKET;
|
|
290
291
|
|
|
291
292
|
writeEncryptedStore({
|
|
292
|
-
"
|
|
293
|
+
[credentialKey("test", "key")]: "encrypted-value",
|
|
293
294
|
});
|
|
294
295
|
|
|
295
|
-
const result = await readCredential("
|
|
296
|
+
const result = await readCredential(credentialKey("test", "key"));
|
|
296
297
|
expect(result).toBe("encrypted-value");
|
|
297
298
|
});
|
|
298
299
|
|
|
@@ -301,10 +302,10 @@ describe("readCredential broker integration", () => {
|
|
|
301
302
|
writeBrokerToken(TEST_TOKEN);
|
|
302
303
|
|
|
303
304
|
writeEncryptedStore({
|
|
304
|
-
"
|
|
305
|
+
[credentialKey("test", "key")]: "encrypted-value",
|
|
305
306
|
});
|
|
306
307
|
|
|
307
|
-
const result = await readCredential("
|
|
308
|
+
const result = await readCredential(credentialKey("test", "key"));
|
|
308
309
|
expect(result).toBe("encrypted-value");
|
|
309
310
|
});
|
|
310
311
|
|
|
@@ -313,22 +314,22 @@ describe("readCredential broker integration", () => {
|
|
|
313
314
|
// Don't write a token file
|
|
314
315
|
|
|
315
316
|
writeEncryptedStore({
|
|
316
|
-
"
|
|
317
|
+
[credentialKey("test", "key")]: "encrypted-value",
|
|
317
318
|
});
|
|
318
319
|
|
|
319
|
-
const result = await readCredential("
|
|
320
|
+
const result = await readCredential(credentialKey("test", "key"));
|
|
320
321
|
expect(result).toBe("encrypted-value");
|
|
321
322
|
});
|
|
322
323
|
|
|
323
324
|
test("reads credential from broker when available", async () => {
|
|
324
325
|
const broker = createMockBroker({
|
|
325
|
-
"
|
|
326
|
+
[credentialKey("test", "key")]: "broker-secret-value",
|
|
326
327
|
});
|
|
327
328
|
try {
|
|
328
329
|
process.env.VELLUM_KEYCHAIN_BROKER_SOCKET = broker.socketPath;
|
|
329
330
|
writeBrokerToken(TEST_TOKEN);
|
|
330
331
|
|
|
331
|
-
const result = await readCredential("
|
|
332
|
+
const result = await readCredential(credentialKey("test", "key"));
|
|
332
333
|
expect(result).toBe("broker-secret-value");
|
|
333
334
|
} finally {
|
|
334
335
|
broker.close();
|
|
@@ -337,17 +338,17 @@ describe("readCredential broker integration", () => {
|
|
|
337
338
|
|
|
338
339
|
test("broker result takes priority over encrypted store", async () => {
|
|
339
340
|
const broker = createMockBroker({
|
|
340
|
-
"
|
|
341
|
+
[credentialKey("test", "key")]: "broker-value",
|
|
341
342
|
});
|
|
342
343
|
try {
|
|
343
344
|
process.env.VELLUM_KEYCHAIN_BROKER_SOCKET = broker.socketPath;
|
|
344
345
|
writeBrokerToken(TEST_TOKEN);
|
|
345
346
|
|
|
346
347
|
writeEncryptedStore({
|
|
347
|
-
"
|
|
348
|
+
[credentialKey("test", "key")]: "encrypted-value",
|
|
348
349
|
});
|
|
349
350
|
|
|
350
|
-
const result = await readCredential("
|
|
351
|
+
const result = await readCredential(credentialKey("test", "key"));
|
|
351
352
|
expect(result).toBe("broker-value");
|
|
352
353
|
} finally {
|
|
353
354
|
broker.close();
|
|
@@ -355,17 +356,17 @@ describe("readCredential broker integration", () => {
|
|
|
355
356
|
});
|
|
356
357
|
|
|
357
358
|
test("falls back to encrypted store when broker returns not found", async () => {
|
|
358
|
-
// Broker has no entry for
|
|
359
|
+
// Broker has no entry for the test credential key
|
|
359
360
|
const broker = createMockBroker({});
|
|
360
361
|
try {
|
|
361
362
|
process.env.VELLUM_KEYCHAIN_BROKER_SOCKET = broker.socketPath;
|
|
362
363
|
writeBrokerToken(TEST_TOKEN);
|
|
363
364
|
|
|
364
365
|
writeEncryptedStore({
|
|
365
|
-
"
|
|
366
|
+
[credentialKey("test", "key")]: "encrypted-value",
|
|
366
367
|
});
|
|
367
368
|
|
|
368
|
-
const result = await readCredential("
|
|
369
|
+
const result = await readCredential(credentialKey("test", "key"));
|
|
369
370
|
expect(result).toBe("encrypted-value");
|
|
370
371
|
} finally {
|
|
371
372
|
broker.close();
|
|
@@ -385,13 +386,13 @@ describe("secret leak prevention", () => {
|
|
|
385
386
|
test("broker read does not leak secret values into logs", async () => {
|
|
386
387
|
const secretValue = "super-secret-broker-credential-value";
|
|
387
388
|
const broker = createMockBroker({
|
|
388
|
-
"
|
|
389
|
+
[credentialKey("leak-test", "key")]: secretValue,
|
|
389
390
|
});
|
|
390
391
|
try {
|
|
391
392
|
process.env.VELLUM_KEYCHAIN_BROKER_SOCKET = broker.socketPath;
|
|
392
393
|
writeBrokerToken(TEST_TOKEN);
|
|
393
394
|
|
|
394
|
-
const result = await readCredential("
|
|
395
|
+
const result = await readCredential(credentialKey("leak-test", "key"));
|
|
395
396
|
expect(result).toBe(secretValue);
|
|
396
397
|
|
|
397
398
|
const serialized = allLogStrings();
|
|
@@ -408,10 +409,10 @@ describe("secret leak prevention", () => {
|
|
|
408
409
|
delete process.env.VELLUM_KEYCHAIN_BROKER_SOCKET;
|
|
409
410
|
|
|
410
411
|
writeEncryptedStore({
|
|
411
|
-
"
|
|
412
|
+
[credentialKey("leak-test", "key")]: secretValue,
|
|
412
413
|
});
|
|
413
414
|
|
|
414
|
-
const result = await readCredential("
|
|
415
|
+
const result = await readCredential(credentialKey("leak-test", "key"));
|
|
415
416
|
expect(result).toBe(secretValue);
|
|
416
417
|
|
|
417
418
|
const serialized = allLogStrings();
|
|
@@ -427,8 +428,8 @@ describe("secret leak prevention", () => {
|
|
|
427
428
|
{ service: "telegram", field: "webhook_secret" },
|
|
428
429
|
]);
|
|
429
430
|
writeEncryptedStore({
|
|
430
|
-
"
|
|
431
|
-
"
|
|
431
|
+
[credentialKey("telegram", "bot_token")]: secretValue,
|
|
432
|
+
[credentialKey("telegram", "webhook_secret")]: "webhook-secret-value",
|
|
432
433
|
});
|
|
433
434
|
|
|
434
435
|
const result = await readTelegramCredentials();
|
|
@@ -106,8 +106,8 @@ function writeEncryptedStore(botToken: string, webhookSecret: string): void {
|
|
|
106
106
|
version: 1,
|
|
107
107
|
salt: salt.toString("hex"),
|
|
108
108
|
entries: {
|
|
109
|
-
"credential
|
|
110
|
-
"credential
|
|
109
|
+
"credential/telegram/bot_token": encrypt(botToken, key),
|
|
110
|
+
"credential/telegram/webhook_secret": encrypt(webhookSecret, key),
|
|
111
111
|
},
|
|
112
112
|
};
|
|
113
113
|
|
|
@@ -2,11 +2,12 @@ import { describe, test, expect } from "bun:test";
|
|
|
2
2
|
import { createTelegramWebhookHandler } from "../http/routes/telegram-webhook.js";
|
|
3
3
|
import type { GatewayConfig } from "../config.js";
|
|
4
4
|
import type { CredentialCache } from "../credential-cache.js";
|
|
5
|
+
import { credentialKey } from "../credential-key.js";
|
|
5
6
|
|
|
6
7
|
function makeCaches() {
|
|
7
8
|
const credentials = {
|
|
8
9
|
get: async (key: string) => {
|
|
9
|
-
if (key === "
|
|
10
|
+
if (key === credentialKey("telegram", "webhook_secret")) return "wh-sec";
|
|
10
11
|
return undefined;
|
|
11
12
|
},
|
|
12
13
|
invalidate: () => {},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, test, expect, mock, beforeEach } from "bun:test";
|
|
2
2
|
import type { GatewayConfig } from "../config.js";
|
|
3
3
|
import type { CredentialCache } from "../credential-cache.js";
|
|
4
|
+
import { credentialKey } from "../credential-key.js";
|
|
4
5
|
import { initSigningKey, mintToken } from "../auth/token-service.js";
|
|
5
6
|
import { CURRENT_POLICY_EPOCH } from "../auth/policy.js";
|
|
6
7
|
|
|
@@ -69,7 +70,7 @@ function makeRequest(body: unknown): Request {
|
|
|
69
70
|
function makeCaches() {
|
|
70
71
|
const credentials = {
|
|
71
72
|
get: async (key: string) => {
|
|
72
|
-
if (key === "
|
|
73
|
+
if (key === credentialKey("slack_channel", "bot_token"))
|
|
73
74
|
return "xoxb-test-bot-token";
|
|
74
75
|
return undefined;
|
|
75
76
|
},
|
|
@@ -2,6 +2,7 @@ import { describe, test, expect, mock, beforeEach, afterEach } from "bun:test";
|
|
|
2
2
|
import type { GatewayConfig } from "../config.js";
|
|
3
3
|
import type { ConfigFileCache } from "../config-file-cache.js";
|
|
4
4
|
import type { CredentialCache } from "../credential-cache.js";
|
|
5
|
+
import { credentialKey } from "../credential-key.js";
|
|
5
6
|
|
|
6
7
|
type FetchFn = (
|
|
7
8
|
input: string | URL | Request,
|
|
@@ -75,7 +76,7 @@ function makeCaches(...args: [] | [string | undefined]) {
|
|
|
75
76
|
const botToken = args.length === 0 ? "xoxb-test-bot-token" : args[0];
|
|
76
77
|
const credentials = {
|
|
77
78
|
get: async (key: string) => {
|
|
78
|
-
if (key === "
|
|
79
|
+
if (key === credentialKey("slack_channel", "bot_token")) return botToken;
|
|
79
80
|
return undefined;
|
|
80
81
|
},
|
|
81
82
|
invalidate: () => {},
|
|
@@ -329,7 +330,7 @@ describe("slack-deliver endpoint", () => {
|
|
|
329
330
|
let callCount = 0;
|
|
330
331
|
const credentials = {
|
|
331
332
|
get: async (key: string, opts?: { force?: boolean }) => {
|
|
332
|
-
if (key === "
|
|
333
|
+
if (key === credentialKey("slack_channel", "bot_token")) {
|
|
333
334
|
callCount++;
|
|
334
335
|
// First call returns undefined; second call with force returns the token
|
|
335
336
|
if (callCount === 1 && !opts?.force) return undefined;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { afterEach, describe, expect, mock, test } from "bun:test";
|
|
2
2
|
import type { ConfigFileCache } from "../config-file-cache.js";
|
|
3
3
|
import type { CredentialCache } from "../credential-cache.js";
|
|
4
|
+
import { credentialKey } from "../credential-key.js";
|
|
4
5
|
|
|
5
6
|
type FetchFn = (
|
|
6
7
|
input: string | URL | Request,
|
|
@@ -35,7 +36,7 @@ function makeConfigFile(): ConfigFileCache {
|
|
|
35
36
|
function makeCredentials(botToken: string): CredentialCache {
|
|
36
37
|
return {
|
|
37
38
|
get: async (key: string) => {
|
|
38
|
-
if (key === "
|
|
39
|
+
if (key === credentialKey("telegram", "bot_token")) return botToken;
|
|
39
40
|
return undefined;
|
|
40
41
|
},
|
|
41
42
|
invalidate: () => {},
|
|
@@ -2,6 +2,7 @@ import { describe, test, expect, mock, afterEach } from "bun:test";
|
|
|
2
2
|
import type { GatewayConfig } from "../config.js";
|
|
3
3
|
import type { ConfigFileCache } from "../config-file-cache.js";
|
|
4
4
|
import type { CredentialCache } from "../credential-cache.js";
|
|
5
|
+
import { credentialKey } from "../credential-key.js";
|
|
5
6
|
import { initSigningKey, mintToken } from "../auth/token-service.js";
|
|
6
7
|
import { CURRENT_POLICY_EPOCH } from "../auth/policy.js";
|
|
7
8
|
|
|
@@ -95,7 +96,8 @@ afterEach(() => {
|
|
|
95
96
|
function makeCaches(configFile?: ConfigFileCache) {
|
|
96
97
|
const credentials = {
|
|
97
98
|
get: async (key: string) => {
|
|
98
|
-
if (key === "
|
|
99
|
+
if (key === credentialKey("telegram", "bot_token"))
|
|
100
|
+
return "test-bot-token";
|
|
99
101
|
return undefined;
|
|
100
102
|
},
|
|
101
103
|
invalidate: () => {},
|
|
@@ -3,6 +3,7 @@ import type { RuntimeAttachmentMeta } from "../runtime/client.js";
|
|
|
3
3
|
import type { GatewayConfig } from "../config.js";
|
|
4
4
|
import type { CredentialCache } from "../credential-cache.js";
|
|
5
5
|
import type { ConfigFileCache } from "../config-file-cache.js";
|
|
6
|
+
import { credentialKey } from "../credential-key.js";
|
|
6
7
|
import { initSigningKey } from "../auth/token-service.js";
|
|
7
8
|
|
|
8
9
|
const TEST_SIGNING_KEY = Buffer.from("test-signing-key-at-least-32-bytes-long");
|
|
@@ -49,7 +50,7 @@ function makeConfig(overrides: Partial<GatewayConfig> = {}): GatewayConfig {
|
|
|
49
50
|
/** Mock credential cache that provides a test bot token. */
|
|
50
51
|
const testCreds: CredentialCache = {
|
|
51
52
|
get: async (key: string) => {
|
|
52
|
-
if (key === "
|
|
53
|
+
if (key === credentialKey("telegram", "bot_token")) return "test-bot-token";
|
|
53
54
|
return undefined;
|
|
54
55
|
},
|
|
55
56
|
invalidate: () => {},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, test, expect, mock, afterEach, beforeEach } from "bun:test";
|
|
2
2
|
import type { GatewayConfig } from "../config.js";
|
|
3
3
|
import type { CredentialCache } from "../credential-cache.js";
|
|
4
|
+
import { credentialKey } from "../credential-key.js";
|
|
4
5
|
import { initSigningKey } from "../auth/token-service.js";
|
|
5
6
|
|
|
6
7
|
const TEST_SIGNING_KEY = Buffer.from("test-signing-key-at-least-32-bytes-long");
|
|
@@ -80,8 +81,10 @@ function makeWebhookRequest(
|
|
|
80
81
|
function makeCaches(webhookSecret: string | undefined = "test-webhook-secret") {
|
|
81
82
|
const credentials = {
|
|
82
83
|
get: async (key: string) => {
|
|
83
|
-
if (key === "
|
|
84
|
-
|
|
84
|
+
if (key === credentialKey("telegram", "webhook_secret"))
|
|
85
|
+
return webhookSecret;
|
|
86
|
+
if (key === credentialKey("telegram", "bot_token"))
|
|
87
|
+
return "test-bot-token";
|
|
85
88
|
return undefined;
|
|
86
89
|
},
|
|
87
90
|
invalidate: () => {},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, test, expect, mock, afterEach } from "bun:test";
|
|
2
2
|
import type { CredentialCache } from "../credential-cache.js";
|
|
3
3
|
import type { ConfigFileCache } from "../config-file-cache.js";
|
|
4
|
+
import { credentialKey } from "../credential-key.js";
|
|
4
5
|
|
|
5
6
|
type FetchFn = (
|
|
6
7
|
input: string | URL | Request,
|
|
@@ -48,8 +49,8 @@ function makeCaches(
|
|
|
48
49
|
? (opts.ingressUrl ?? undefined)
|
|
49
50
|
: "https://example.ngrok.io";
|
|
50
51
|
const credentialMap: Record<string, string | undefined> = {
|
|
51
|
-
"
|
|
52
|
-
"
|
|
52
|
+
[credentialKey("telegram", "bot_token")]: botToken,
|
|
53
|
+
[credentialKey("telegram", "webhook_secret")]: webhookSecret,
|
|
53
54
|
};
|
|
54
55
|
const credentials = {
|
|
55
56
|
get: async (key: string) => credentialMap[key],
|
|
@@ -3,6 +3,7 @@ import { createHmac } from "node:crypto";
|
|
|
3
3
|
import type { GatewayConfig } from "../config.js";
|
|
4
4
|
import type { CredentialCache } from "../credential-cache.js";
|
|
5
5
|
import type { ConfigFileCache } from "../config-file-cache.js";
|
|
6
|
+
import { credentialKey } from "../credential-key.js";
|
|
6
7
|
import { initSigningKey } from "../auth/token-service.js";
|
|
7
8
|
|
|
8
9
|
const TEST_SIGNING_KEY = Buffer.from("test-signing-key-at-least-32-bytes-long");
|
|
@@ -147,7 +148,7 @@ function makeCaches(
|
|
|
147
148
|
const { authToken = AUTH_TOKEN, ingressUrl } = opts;
|
|
148
149
|
const credentials = {
|
|
149
150
|
get: async (key: string, _opts?: { force?: boolean }) => {
|
|
150
|
-
if (key === "
|
|
151
|
+
if (key === credentialKey("twilio", "auth_token")) return authToken;
|
|
151
152
|
return undefined;
|
|
152
153
|
},
|
|
153
154
|
invalidate: () => {},
|
|
@@ -2,6 +2,7 @@ import { describe, test, expect, mock, afterEach } from "bun:test";
|
|
|
2
2
|
import type { GatewayConfig } from "../config.js";
|
|
3
3
|
import type { CredentialCache } from "../credential-cache.js";
|
|
4
4
|
import type { ConfigFileCache } from "../config-file-cache.js";
|
|
5
|
+
import { credentialKey } from "../credential-key.js";
|
|
5
6
|
|
|
6
7
|
type FetchFn = (
|
|
7
8
|
input: string | URL | Request,
|
|
@@ -60,9 +61,10 @@ function makeConfigFile(overrides?: { maxRetries?: number }): ConfigFileCache {
|
|
|
60
61
|
function makeCaches(opts?: { maxRetries?: number }) {
|
|
61
62
|
const credentials = {
|
|
62
63
|
get: async (key: string) => {
|
|
63
|
-
if (key === "
|
|
64
|
+
if (key === credentialKey("whatsapp", "access_token"))
|
|
64
65
|
return "test-access-token";
|
|
65
|
-
if (key === "
|
|
66
|
+
if (key === credentialKey("whatsapp", "phone_number_id"))
|
|
67
|
+
return "test-phone-id";
|
|
66
68
|
return undefined;
|
|
67
69
|
},
|
|
68
70
|
invalidate: () => {},
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for credential key format in the secure store.
|
|
3
|
+
*
|
|
4
|
+
* Keys follow the pattern: credential/{service}/{field}
|
|
5
|
+
*
|
|
6
|
+
* This mirrors the assistant's credential-key.ts helper to ensure both
|
|
7
|
+
* packages use the same key format when reading/writing credentials.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Build a credential key for the secure store.
|
|
12
|
+
*
|
|
13
|
+
* @returns A key of the form `credential/{service}/{field}`
|
|
14
|
+
*/
|
|
15
|
+
export function credentialKey(service: string, field: string): string {
|
|
16
|
+
return `credential/${service}/${field}`;
|
|
17
|
+
}
|
package/src/credential-reader.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
10
10
|
import { createConnection } from "node:net";
|
|
11
11
|
import { hostname, userInfo } from "node:os";
|
|
12
12
|
import { join } from "node:path";
|
|
13
|
+
import { credentialKey } from "./credential-key.js";
|
|
13
14
|
import { getLogger } from "./logger.js";
|
|
14
15
|
|
|
15
16
|
const log = getLogger("credential-reader");
|
|
@@ -319,9 +320,11 @@ export async function readTelegramCredentials(): Promise<TelegramCredentials | n
|
|
|
319
320
|
|
|
320
321
|
if (!hasBotToken || !hasWebhookSecret) return null;
|
|
321
322
|
|
|
322
|
-
const botToken = await readCredential(
|
|
323
|
+
const botToken = await readCredential(
|
|
324
|
+
credentialKey("telegram", "bot_token"),
|
|
325
|
+
);
|
|
323
326
|
const webhookSecret = await readCredential(
|
|
324
|
-
"
|
|
327
|
+
credentialKey("telegram", "webhook_secret"),
|
|
325
328
|
);
|
|
326
329
|
|
|
327
330
|
if (!botToken || !webhookSecret) {
|
|
@@ -367,8 +370,12 @@ export async function readTwilioCredentials(): Promise<TwilioCredentials | null>
|
|
|
367
370
|
|
|
368
371
|
if (!hasAccountSid || !hasAuthToken) return null;
|
|
369
372
|
|
|
370
|
-
const accountSid = await readCredential(
|
|
371
|
-
|
|
373
|
+
const accountSid = await readCredential(
|
|
374
|
+
credentialKey("twilio", "account_sid"),
|
|
375
|
+
);
|
|
376
|
+
const authToken = await readCredential(
|
|
377
|
+
credentialKey("twilio", "auth_token"),
|
|
378
|
+
);
|
|
372
379
|
|
|
373
380
|
if (!accountSid || !authToken) {
|
|
374
381
|
log.warn(
|
|
@@ -420,8 +427,12 @@ export async function readSlackChannelCredentials(): Promise<SlackChannelCredent
|
|
|
420
427
|
|
|
421
428
|
if (!hasBotToken || !hasAppToken) return null;
|
|
422
429
|
|
|
423
|
-
const botToken = await readCredential(
|
|
424
|
-
|
|
430
|
+
const botToken = await readCredential(
|
|
431
|
+
credentialKey("slack_channel", "bot_token"),
|
|
432
|
+
);
|
|
433
|
+
const appToken = await readCredential(
|
|
434
|
+
credentialKey("slack_channel", "app_token"),
|
|
435
|
+
);
|
|
425
436
|
|
|
426
437
|
if (!botToken || !appToken) {
|
|
427
438
|
log.warn(
|
|
@@ -492,14 +503,16 @@ export async function readWhatsAppCredentials(): Promise<WhatsAppCredentials | n
|
|
|
492
503
|
return null;
|
|
493
504
|
|
|
494
505
|
const phoneNumberId = await readCredential(
|
|
495
|
-
"
|
|
506
|
+
credentialKey("whatsapp", "phone_number_id"),
|
|
496
507
|
);
|
|
497
508
|
const accessToken = await readCredential(
|
|
498
|
-
"
|
|
509
|
+
credentialKey("whatsapp", "access_token"),
|
|
510
|
+
);
|
|
511
|
+
const appSecret = await readCredential(
|
|
512
|
+
credentialKey("whatsapp", "app_secret"),
|
|
499
513
|
);
|
|
500
|
-
const appSecret = await readCredential("credential:whatsapp:app_secret");
|
|
501
514
|
const webhookVerifyToken = await readCredential(
|
|
502
|
-
"
|
|
515
|
+
credentialKey("whatsapp", "webhook_verify_token"),
|
|
503
516
|
);
|
|
504
517
|
|
|
505
518
|
if (!phoneNumberId || !accessToken || !appSecret || !webhookVerifyToken) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { GatewayConfig } from "../../config.js";
|
|
2
2
|
import type { ConfigFileCache } from "../../config-file-cache.js";
|
|
3
3
|
import type { CredentialCache } from "../../credential-cache.js";
|
|
4
|
+
import { credentialKey } from "../../credential-key.js";
|
|
4
5
|
import { fetchImpl } from "../../fetch.js";
|
|
5
6
|
import { getLogger } from "../../logger.js";
|
|
6
7
|
import { checkDeliverAuth } from "../middleware/deliver-auth.js";
|
|
@@ -328,14 +329,16 @@ export function createSlackDeliverHandler(
|
|
|
328
329
|
|
|
329
330
|
// Resolve bot token from cache
|
|
330
331
|
let botToken = caches?.credentials
|
|
331
|
-
? await caches.credentials.get(
|
|
332
|
+
? await caches.credentials.get(
|
|
333
|
+
credentialKey("slack_channel", "bot_token"),
|
|
334
|
+
)
|
|
332
335
|
: undefined;
|
|
333
336
|
|
|
334
337
|
// One-shot force retry: if token is missing and caches are available,
|
|
335
338
|
// force-refresh and retry once.
|
|
336
339
|
if (!botToken && caches?.credentials) {
|
|
337
340
|
botToken = await caches.credentials.get(
|
|
338
|
-
"
|
|
341
|
+
credentialKey("slack_channel", "bot_token"),
|
|
339
342
|
{ force: true },
|
|
340
343
|
);
|
|
341
344
|
if (botToken) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect, mock, beforeEach } from "bun:test";
|
|
2
2
|
import type { GatewayConfig } from "../../config.js";
|
|
3
3
|
import type { CredentialCache } from "../../credential-cache.js";
|
|
4
|
+
import { credentialKey } from "../../credential-key.js";
|
|
4
5
|
|
|
5
6
|
// --- Mocks ----------------------------------------------------------------
|
|
6
7
|
|
|
@@ -104,7 +105,8 @@ function postRequest(body: string): Request {
|
|
|
104
105
|
function makeCaches() {
|
|
105
106
|
const credentials = {
|
|
106
107
|
get: async (key: string) => {
|
|
107
|
-
if (key === "
|
|
108
|
+
if (key === credentialKey("telegram", "webhook_secret"))
|
|
109
|
+
return "test-secret";
|
|
108
110
|
return undefined;
|
|
109
111
|
},
|
|
110
112
|
invalidate: () => {},
|
|
@@ -2,6 +2,7 @@ import { buildTelegramTransportMetadata } from "../../channels/transport-hints.j
|
|
|
2
2
|
import type { ConfigFileCache } from "../../config-file-cache.js";
|
|
3
3
|
import type { GatewayConfig } from "../../config.js";
|
|
4
4
|
import type { CredentialCache } from "../../credential-cache.js";
|
|
5
|
+
import { credentialKey } from "../../credential-key.js";
|
|
5
6
|
import { DedupCache } from "../../dedup-cache.js";
|
|
6
7
|
import { handleInbound } from "../../handlers/handle-inbound.js";
|
|
7
8
|
import { getLogger } from "../../logger.js";
|
|
@@ -72,7 +73,9 @@ export function createTelegramWebhookHandler(
|
|
|
72
73
|
|
|
73
74
|
// Verify webhook secret from cache
|
|
74
75
|
const webhookSecret = caches?.credentials
|
|
75
|
-
? await caches.credentials.get(
|
|
76
|
+
? await caches.credentials.get(
|
|
77
|
+
credentialKey("telegram", "webhook_secret"),
|
|
78
|
+
)
|
|
76
79
|
: undefined;
|
|
77
80
|
|
|
78
81
|
let secretVerified =
|
|
@@ -82,7 +85,7 @@ export function createTelegramWebhookHandler(
|
|
|
82
85
|
// force-refresh the webhook secret and retry once.
|
|
83
86
|
if (!secretVerified && caches?.credentials) {
|
|
84
87
|
const freshSecret = await caches.credentials.get(
|
|
85
|
-
"
|
|
88
|
+
credentialKey("telegram", "webhook_secret"),
|
|
86
89
|
{ force: true },
|
|
87
90
|
);
|
|
88
91
|
if (freshSecret) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it, mock } from "bun:test";
|
|
2
2
|
import type { GatewayConfig } from "../../config.js";
|
|
3
3
|
import type { CredentialCache } from "../../credential-cache.js";
|
|
4
|
+
import { credentialKey } from "../../credential-key.js";
|
|
4
5
|
|
|
5
6
|
const handleInboundMock = mock(() =>
|
|
6
7
|
Promise.resolve({ forwarded: true, rejected: false }),
|
|
@@ -99,11 +100,13 @@ const baseConfig: GatewayConfig = {
|
|
|
99
100
|
function makeCaches() {
|
|
100
101
|
const credentials = {
|
|
101
102
|
get: async (key: string) => {
|
|
102
|
-
if (key === "
|
|
103
|
-
|
|
103
|
+
if (key === credentialKey("whatsapp", "app_secret"))
|
|
104
|
+
return "test-app-secret";
|
|
105
|
+
if (key === credentialKey("whatsapp", "webhook_verify_token"))
|
|
104
106
|
return "verify-token";
|
|
105
|
-
if (key === "
|
|
106
|
-
|
|
107
|
+
if (key === credentialKey("whatsapp", "phone_number_id"))
|
|
108
|
+
return "test-phone-id";
|
|
109
|
+
if (key === credentialKey("whatsapp", "access_token"))
|
|
107
110
|
return "test-access-token";
|
|
108
111
|
return undefined;
|
|
109
112
|
},
|
|
@@ -189,17 +192,17 @@ describe("whatsapp-webhook", () => {
|
|
|
189
192
|
const caches = {
|
|
190
193
|
credentials: {
|
|
191
194
|
get: async (key: string, opts?: { force?: boolean }) => {
|
|
192
|
-
if (key === "
|
|
195
|
+
if (key === credentialKey("whatsapp", "app_secret")) {
|
|
193
196
|
callCount++;
|
|
194
197
|
// First call (non-forced) returns undefined; second call (forced) returns a valid secret
|
|
195
198
|
if (callCount === 1 && !opts?.force) return undefined;
|
|
196
199
|
return "refreshed-app-secret";
|
|
197
200
|
}
|
|
198
|
-
if (key === "
|
|
201
|
+
if (key === credentialKey("whatsapp", "webhook_verify_token"))
|
|
199
202
|
return "verify-token";
|
|
200
|
-
if (key === "
|
|
203
|
+
if (key === credentialKey("whatsapp", "phone_number_id"))
|
|
201
204
|
return "test-phone-id";
|
|
202
|
-
if (key === "
|
|
205
|
+
if (key === credentialKey("whatsapp", "access_token"))
|
|
203
206
|
return "test-access-token";
|
|
204
207
|
return undefined;
|
|
205
208
|
},
|
|
@@ -227,12 +230,12 @@ describe("whatsapp-webhook", () => {
|
|
|
227
230
|
credentials: {
|
|
228
231
|
get: async (key: string) => {
|
|
229
232
|
// Always return undefined for app_secret — both normal and forced reads
|
|
230
|
-
if (key === "
|
|
231
|
-
if (key === "
|
|
233
|
+
if (key === credentialKey("whatsapp", "app_secret")) return undefined;
|
|
234
|
+
if (key === credentialKey("whatsapp", "webhook_verify_token"))
|
|
232
235
|
return "verify-token";
|
|
233
|
-
if (key === "
|
|
236
|
+
if (key === credentialKey("whatsapp", "phone_number_id"))
|
|
234
237
|
return "test-phone-id";
|
|
235
|
-
if (key === "
|
|
238
|
+
if (key === credentialKey("whatsapp", "access_token"))
|
|
236
239
|
return "test-access-token";
|
|
237
240
|
return undefined;
|
|
238
241
|
},
|
|
@@ -3,6 +3,7 @@ import { buildWhatsAppTransportMetadata } from "../../channels/transport-hints.j
|
|
|
3
3
|
import type { GatewayConfig } from "../../config.js";
|
|
4
4
|
import type { ConfigFileCache } from "../../config-file-cache.js";
|
|
5
5
|
import type { CredentialCache } from "../../credential-cache.js";
|
|
6
|
+
import { credentialKey } from "../../credential-key.js";
|
|
6
7
|
import { StringDedupCache } from "../../dedup-cache.js";
|
|
7
8
|
import { handleInbound } from "../../handlers/handle-inbound.js";
|
|
8
9
|
import { getLogger } from "../../logger.js";
|
|
@@ -62,7 +63,7 @@ export function createWhatsAppWebhookHandler(
|
|
|
62
63
|
// Resolve the verify token from cache
|
|
63
64
|
const verifyToken = caches?.credentials
|
|
64
65
|
? await caches.credentials.get(
|
|
65
|
-
"
|
|
66
|
+
credentialKey("whatsapp", "webhook_verify_token"),
|
|
66
67
|
)
|
|
67
68
|
: undefined;
|
|
68
69
|
|
|
@@ -112,7 +113,7 @@ export function createWhatsAppWebhookHandler(
|
|
|
112
113
|
|
|
113
114
|
// Resolve app secret from cache
|
|
114
115
|
const appSecret = caches?.credentials
|
|
115
|
-
? await caches.credentials.get("
|
|
116
|
+
? await caches.credentials.get(credentialKey("whatsapp", "app_secret"))
|
|
116
117
|
: undefined;
|
|
117
118
|
|
|
118
119
|
// If the initial cache read returned undefined but a credential cache is available,
|
|
@@ -121,7 +122,7 @@ export function createWhatsAppWebhookHandler(
|
|
|
121
122
|
let effectiveAppSecret = appSecret;
|
|
122
123
|
if (!effectiveAppSecret && caches?.credentials) {
|
|
123
124
|
effectiveAppSecret = await caches.credentials.get(
|
|
124
|
-
"
|
|
125
|
+
credentialKey("whatsapp", "app_secret"),
|
|
125
126
|
{ force: true },
|
|
126
127
|
);
|
|
127
128
|
if (effectiveAppSecret) {
|
|
@@ -151,7 +152,7 @@ export function createWhatsAppWebhookHandler(
|
|
|
151
152
|
// force-refresh the app secret and retry once.
|
|
152
153
|
if (!signatureValid && caches?.credentials) {
|
|
153
154
|
const freshAppSecret = await caches.credentials.get(
|
|
154
|
-
"
|
|
155
|
+
credentialKey("whatsapp", "app_secret"),
|
|
155
156
|
{ force: true },
|
|
156
157
|
);
|
|
157
158
|
if (freshAppSecret) {
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ import { ConfigFileCache } from "./config-file-cache.js";
|
|
|
14
14
|
import { ConfigFileWatcher } from "./config-file-watcher.js";
|
|
15
15
|
import { loadConfig } from "./config.js";
|
|
16
16
|
import { CredentialCache } from "./credential-cache.js";
|
|
17
|
+
import { credentialKey } from "./credential-key.js";
|
|
17
18
|
import {
|
|
18
19
|
CredentialWatcher,
|
|
19
20
|
type CredentialChangeEvent,
|
|
@@ -843,10 +844,10 @@ async function main() {
|
|
|
843
844
|
}
|
|
844
845
|
|
|
845
846
|
const botToken = await credentialCache.get(
|
|
846
|
-
"
|
|
847
|
+
credentialKey("slack_channel", "bot_token"),
|
|
847
848
|
);
|
|
848
849
|
const appToken = await credentialCache.get(
|
|
849
|
-
"
|
|
850
|
+
credentialKey("slack_channel", "app_token"),
|
|
850
851
|
);
|
|
851
852
|
if (!botToken || !appToken) return;
|
|
852
853
|
|
package/src/telegram/api.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CredentialCache } from "../credential-cache.js";
|
|
2
2
|
import type { ConfigFileCache } from "../config-file-cache.js";
|
|
3
|
+
import { credentialKey } from "../credential-key.js";
|
|
3
4
|
import { fetchImpl } from "../fetch.js";
|
|
4
5
|
import { getLogger } from "../logger.js";
|
|
5
6
|
|
|
@@ -173,7 +174,9 @@ export async function callTelegramApi<T>(
|
|
|
173
174
|
): Promise<T> {
|
|
174
175
|
let botToken: string | undefined;
|
|
175
176
|
if (opts?.credentials) {
|
|
176
|
-
botToken = await opts.credentials.get(
|
|
177
|
+
botToken = await opts.credentials.get(
|
|
178
|
+
credentialKey("telegram", "bot_token"),
|
|
179
|
+
);
|
|
177
180
|
}
|
|
178
181
|
|
|
179
182
|
if (!botToken) {
|
|
@@ -205,7 +208,9 @@ export async function callTelegramApiMultipart<T>(
|
|
|
205
208
|
): Promise<T> {
|
|
206
209
|
let botToken: string | undefined;
|
|
207
210
|
if (opts?.credentials) {
|
|
208
|
-
botToken = await opts.credentials.get(
|
|
211
|
+
botToken = await opts.credentials.get(
|
|
212
|
+
credentialKey("telegram", "bot_token"),
|
|
213
|
+
);
|
|
209
214
|
}
|
|
210
215
|
|
|
211
216
|
if (!botToken) {
|
package/src/telegram/download.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { fileTypeFromBuffer } from "file-type";
|
|
2
2
|
import type { ConfigFileCache } from "../config-file-cache.js";
|
|
3
3
|
import type { CredentialCache } from "../credential-cache.js";
|
|
4
|
+
import { credentialKey } from "../credential-key.js";
|
|
4
5
|
import { fetchImpl } from "../fetch.js";
|
|
5
6
|
import { callTelegramApi } from "./api.js";
|
|
6
7
|
|
|
@@ -39,7 +40,7 @@ export async function downloadTelegramFile(
|
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
const botToken = opts?.credentials
|
|
42
|
-
? await opts.credentials.get("
|
|
43
|
+
? await opts.credentials.get(credentialKey("telegram", "bot_token"))
|
|
43
44
|
: undefined;
|
|
44
45
|
|
|
45
46
|
const apiBaseUrl =
|
|
@@ -3,6 +3,7 @@ import type { ApprovalPayload } from "../http/routes/telegram-deliver.js";
|
|
|
3
3
|
import type { GatewayConfig } from "../config.js";
|
|
4
4
|
import type { CredentialCache } from "../credential-cache.js";
|
|
5
5
|
import type { ConfigFileCache } from "../config-file-cache.js";
|
|
6
|
+
import { credentialKey } from "../credential-key.js";
|
|
6
7
|
|
|
7
8
|
// Mock fetch at the transport level (same pattern as all other test files)
|
|
8
9
|
// instead of mocking ./api.js — mock.module for api.js leaks across test
|
|
@@ -54,7 +55,7 @@ const sampleApproval: ApprovalPayload = {
|
|
|
54
55
|
/** Mock credential cache providing test bot token. */
|
|
55
56
|
const testCreds: CredentialCache = {
|
|
56
57
|
get: async (key: string) => {
|
|
57
|
-
if (key === "
|
|
58
|
+
if (key === credentialKey("telegram", "bot_token")) return "test-bot-token";
|
|
58
59
|
return undefined;
|
|
59
60
|
},
|
|
60
61
|
invalidate: () => {},
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CredentialCache } from "../credential-cache.js";
|
|
2
2
|
import type { ConfigFileCache } from "../config-file-cache.js";
|
|
3
|
+
import { credentialKey } from "../credential-key.js";
|
|
3
4
|
import { callTelegramApi } from "./api.js";
|
|
4
5
|
import { getLogger } from "../logger.js";
|
|
5
6
|
|
|
@@ -36,9 +37,11 @@ export async function reconcileTelegramWebhook(
|
|
|
36
37
|
let botToken: string | undefined;
|
|
37
38
|
let webhookSecret: string | undefined;
|
|
38
39
|
if (caches?.credentials) {
|
|
39
|
-
botToken = await caches.credentials.get(
|
|
40
|
+
botToken = await caches.credentials.get(
|
|
41
|
+
credentialKey("telegram", "bot_token"),
|
|
42
|
+
);
|
|
40
43
|
webhookSecret = await caches.credentials.get(
|
|
41
|
-
"
|
|
44
|
+
credentialKey("telegram", "webhook_secret"),
|
|
42
45
|
);
|
|
43
46
|
}
|
|
44
47
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { CredentialCache } from "../credential-cache.js";
|
|
2
2
|
import type { ConfigFileCache } from "../config-file-cache.js";
|
|
3
3
|
import type { GatewayConfig } from "../config.js";
|
|
4
|
+
import { credentialKey } from "../credential-key.js";
|
|
4
5
|
import { getLogger } from "../logger.js";
|
|
5
6
|
import { verifyTwilioSignature } from "./verify.js";
|
|
6
7
|
|
|
@@ -202,7 +203,7 @@ export async function validateTwilioWebhookRequest(
|
|
|
202
203
|
|
|
203
204
|
// Resolve the auth token from cache
|
|
204
205
|
let authToken = caches?.credentials
|
|
205
|
-
? await caches.credentials.get("
|
|
206
|
+
? await caches.credentials.get(credentialKey("twilio", "auth_token"))
|
|
206
207
|
: undefined;
|
|
207
208
|
|
|
208
209
|
// Resolve ingress URL from cache
|
|
@@ -220,7 +221,7 @@ export async function validateTwilioWebhookRequest(
|
|
|
220
221
|
// One-shot force retry: if missing and caches available, try force refresh
|
|
221
222
|
if (!authToken && caches?.credentials) {
|
|
222
223
|
const freshAuthToken = await caches.credentials.get(
|
|
223
|
-
"
|
|
224
|
+
credentialKey("twilio", "auth_token"),
|
|
224
225
|
{ force: true },
|
|
225
226
|
);
|
|
226
227
|
if (freshAuthToken) {
|
|
@@ -293,7 +294,7 @@ export async function validateTwilioWebhookRequest(
|
|
|
293
294
|
// force-refresh the auth token and ingress URL then retry once.
|
|
294
295
|
if (validatingIndex === -1 && caches?.credentials) {
|
|
295
296
|
const freshAuthToken = await caches.credentials.get(
|
|
296
|
-
"
|
|
297
|
+
credentialKey("twilio", "auth_token"),
|
|
297
298
|
{ force: true },
|
|
298
299
|
);
|
|
299
300
|
let freshIngressUrl: string | undefined;
|
package/src/whatsapp/api.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { CredentialCache } from "../credential-cache.js";
|
|
2
2
|
import type { ConfigFileCache } from "../config-file-cache.js";
|
|
3
|
+
import { credentialKey } from "../credential-key.js";
|
|
3
4
|
import { fetchImpl } from "../fetch.js";
|
|
4
5
|
import { getLogger } from "../logger.js";
|
|
5
6
|
|
|
@@ -158,10 +159,10 @@ async function resolveWhatsAppCredentials(caches?: WhatsAppApiCaches): Promise<{
|
|
|
158
159
|
let accessToken: string | undefined;
|
|
159
160
|
if (caches?.credentials) {
|
|
160
161
|
phoneNumberId = await caches.credentials.get(
|
|
161
|
-
"
|
|
162
|
+
credentialKey("whatsapp", "phone_number_id"),
|
|
162
163
|
);
|
|
163
164
|
accessToken = await caches.credentials.get(
|
|
164
|
-
"
|
|
165
|
+
credentialKey("whatsapp", "access_token"),
|
|
165
166
|
);
|
|
166
167
|
}
|
|
167
168
|
return { phoneNumberId, accessToken };
|