Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions packages/bots/telnyx/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,45 @@ describe('bot-telnyx webhook behavior', () => {
await handle.close();
}
});

it('falls back to the default signature tolerance for invalid config values', async () => {
const keys = generateKeyPairSync('ed25519');
const publicKey = Buffer.from(keys.publicKey.export({ format: 'der', type: 'spki' })).toString('base64');
const handler = vi.fn();
const handle = await bot.register(ctx(), [{ match: { type: 'message' }, handle: handler }], {
from: '+15551234567',
webhookPort: 0,
publicKey,
signatureToleranceSeconds: Number.POSITIVE_INFINITY,
}) as { close(): Promise<void>; port: number };

try {
const body = JSON.stringify({
data: {
event_type: 'message.received',
payload: {
from: { phone_number: '+15559876543' },
text: 'hello',
},
},
});
const staleTimestamp = String(Math.floor(Date.now() / 1000) - 600);
const response = await fetch(`http://127.0.0.1:${handle.port}/telnyx/messaging`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'telnyx-timestamp': staleTimestamp,
'telnyx-signature-ed25519': signature(keys.privateKey, staleTimestamp, body),
},
body,
});

expect(response.status).toBe(403);
expect(handler).not.toHaveBeenCalled();
} finally {
await handle.close();
}
});
});

function ctx() {
Expand Down
8 changes: 7 additions & 1 deletion packages/bots/telnyx/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ class TelnyxWebhookServer {
const signature = request.headers['telnyx-signature-ed25519'];
const timestamp = request.headers['telnyx-timestamp'];
if (typeof signature !== 'string' || typeof timestamp !== 'string') return false;
const tolerance = this.config.signatureToleranceSeconds ?? DEFAULT_SIGNATURE_TOLERANCE_SECONDS;
const tolerance = normalizeSignatureToleranceSeconds(this.config.signatureToleranceSeconds);
if (!validTimestamp(timestamp, tolerance)) return false;
return verifyTelnyxSignature(publicKey, timestamp, body, signature);
}
Expand Down Expand Up @@ -307,6 +307,12 @@ function validTimestamp(value: string, toleranceSeconds: number): boolean {
return Math.abs(Date.now() / 1000 - parsed) <= toleranceSeconds;
}

function normalizeSignatureToleranceSeconds(value: number | undefined): number {
if (value === undefined) return DEFAULT_SIGNATURE_TOLERANCE_SECONDS;
if (Number.isFinite(value) && value >= 0) return value;
return DEFAULT_SIGNATURE_TOLERANCE_SECONDS;
}

function parseEvent(body: string): TelnyxEvent {
const parsed = parseJson(body);
return typeof parsed === 'object' && parsed ? parsed as TelnyxEvent : {};
Expand Down
Loading