Skip to content
Merged
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
20 changes: 20 additions & 0 deletions lib/scalekit.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,26 @@ export default class ScalekitClient {
* @return {boolean} Returns true if the payload is valid.
*/
verifyWebhookPayload(secret: string, headers: Record<string, string>, payload: string): boolean;
/**
* Verify interceptor payload
*
* @param {string} secret The secret
* @param {Record<string, string>} headers The headers
* @param {string} payload The payload
* @return {boolean} Returns true if the payload is valid.
*/
verifyInterceptorPayload(secret: string, headers: Record<string, string>, payload: string): boolean;
/**
* Common payload signature verification logic
*
* @param {string} secret The secret
* @param {string} id The webhook/interceptor id
* @param {string} timestamp The timestamp
* @param {string} signature The signature
* @param {string} payload The payload
* @return {boolean} Returns true if the payload signature is valid.
*/
private verifyPayloadSignature;
/**
* Validates a token and returns its payload if valid.
* Supports issuer, audience, and scope validation.
Expand Down
39 changes: 33 additions & 6 deletions lib/scalekit.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion lib/scalekit.js.map

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "2.1.4",
"version": "2.1.5",
"name": "@scalekit-sdk/node",
"description": "Official Scalekit Node SDK",
"main": "lib/index.js",
Expand Down
2 changes: 1 addition & 1 deletion src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export default class CoreClient {
public keys: JWK[] = [];
public accessToken: string | null = null;
public axios: Axios;
public sdkVersion = `Scalekit-Node/2.1.4`;
public sdkVersion = `Scalekit-Node/2.1.5`;
public apiVersion = "20250830";
public userAgent = `${this.sdkVersion} Node/${process.version} (${process.platform}; ${os.arch()})`;
constructor(
Expand Down
42 changes: 36 additions & 6 deletions src/scalekit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,37 @@ export default class ScalekitClient {
const webhookTimestamp = headers['webhook-timestamp'];
const webhookSignature = headers['webhook-signature'];

if (!webhookId || !webhookTimestamp || !webhookSignature) {
return this.verifyPayloadSignature(secret, webhookId, webhookTimestamp, webhookSignature, payload);
}

/**
* Verify interceptor payload
*
* @param {string} secret The secret
* @param {Record<string, string>} headers The headers
* @param {string} payload The payload
* @return {boolean} Returns true if the payload is valid.
*/
verifyInterceptorPayload(secret: string, headers: Record<string, string>, payload: string): boolean {
const interceptorId = headers['interceptor-id'];
const interceptorTimestamp = headers['interceptor-timestamp'];
const interceptorSignature = headers['interceptor-signature'];

return this.verifyPayloadSignature(secret, interceptorId, interceptorTimestamp, interceptorSignature, payload);
}

/**
* Common payload signature verification logic
*
* @param {string} secret The secret
* @param {string} id The webhook/interceptor id
* @param {string} timestamp The timestamp
* @param {string} signature The signature
* @param {string} payload The payload
* @return {boolean} Returns true if the payload signature is valid.
*/
private verifyPayloadSignature(secret: string, id: string, timestamp: string, signature: string, payload: string): boolean {
if (!id || !timestamp || !signature) {
throw new WebhookVerificationError("Missing required headers");
}

Expand All @@ -267,18 +297,18 @@ export default class ScalekitClient {
}

try {
const timestamp = this.verifyTimestamp(webhookTimestamp);
const data = `${webhookId}.${Math.floor(timestamp.getTime() / 1000)}.${payload}`;
const timestampDate = this.verifyTimestamp(timestamp);
const data = `${id}.${Math.floor(timestampDate.getTime() / 1000)}.${payload}`;
const secretBytes = Buffer.from(secretParts[1], 'base64');
const computedSignature = this.computeSignature(secretBytes, data);
const receivedSignatures = webhookSignature.split(" ");
const receivedSignatures = signature.split(" ");

for (const versionedSignature of receivedSignatures) {
const [version, signature] = versionedSignature.split(",");
const [version, receivedSignature] = versionedSignature.split(",");
if (version !== WEBHOOK_SIGNATURE_VERSION) {
continue;
}
if (crypto.timingSafeEqual(Buffer.from(signature, 'base64'), Buffer.from(computedSignature, 'base64'))) {
if (crypto.timingSafeEqual(Buffer.from(receivedSignature, 'base64'), Buffer.from(computedSignature, 'base64'))) {
return true;
}
}
Expand Down