Skip to content

Commit cc22506

Browse files
feat(api): manual updates
1 parent c165efa commit cc22506

File tree

6 files changed

+91
-3
lines changed

6 files changed

+91
-3
lines changed

.stats.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
configured_endpoints: 42
22
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-3d7da4b8ef2ed30aa32c4fb3e98e498e67402e91aaa5fd4c628fc080bfe82ea1.yml
33
openapi_spec_hash: aaa50fcbccec6f2cf1165f34bc6ac886
4-
config_hash: 84bf9f929b0248a6cdf2d526331ed8eb
4+
config_hash: 0f760028496146ece9431573b1ab0e46

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@
2626
"lint": "./scripts/lint",
2727
"fix": "./scripts/format"
2828
},
29-
"dependencies": {},
29+
"dependencies": {
30+
"standardwebhooks": "^1.0.0"
31+
},
3032
"devDependencies": {
3133
"@arethetypeswrong/cli": "^0.17.0",
3234
"@swc/core": "^1.3.102",

src/client.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,15 @@ export interface ClientOptions {
9999
*/
100100
password?: string | null | undefined;
101101

102+
/**
103+
* Your ImageKit webhook secret. This is used by the SDK to verify webhook signatures. It starts with a `whsec_` prefix.
104+
* You can view and manage your webhook secret in the [dashboard](https://imagekit.io/dashboard/developer/webhooks).
105+
* Treat the secret like a password, keep it private and do not expose it publicly.
106+
* Learn more about [webhook verification](https://imagekit.io/docs/webhooks#verify-webhook-signature).
107+
*
108+
*/
109+
webhookSecret?: string | null | undefined;
110+
102111
/**
103112
* Override the default base URL for the API, e.g., "https://api.example.com/v2/"
104113
*
@@ -174,6 +183,7 @@ export interface ClientOptions {
174183
export class ImageKit {
175184
privateAPIKey: string;
176185
password: string | null;
186+
webhookSecret: string | null;
177187

178188
baseURL: string;
179189
maxRetries: number;
@@ -192,6 +202,7 @@ export class ImageKit {
192202
*
193203
* @param {string | undefined} [opts.privateAPIKey=process.env['IMAGEKIT_PRIVATE_API_KEY'] ?? undefined]
194204
* @param {string | null | undefined} [opts.password=process.env['OPTIONAL_IMAGEKIT_IGNORES_THIS'] ?? do_not_set]
205+
* @param {string | null | undefined} [opts.webhookSecret=process.env['IMAGEKIT_WEBHOOK_SECRET'] ?? null]
195206
* @param {string} [opts.baseURL=process.env['IMAGE_KIT_BASE_URL'] ?? https://api.imagekit.io] - Override the default base URL for the API.
196207
* @param {number} [opts.timeout=1 minute] - The maximum amount of time (in milliseconds) the client will wait for a response before timing out.
197208
* @param {MergedRequestInit} [opts.fetchOptions] - Additional `RequestInit` options to be passed to `fetch` calls.
@@ -204,6 +215,7 @@ export class ImageKit {
204215
baseURL = readEnv('IMAGE_KIT_BASE_URL'),
205216
privateAPIKey = readEnv('IMAGEKIT_PRIVATE_API_KEY'),
206217
password = readEnv('OPTIONAL_IMAGEKIT_IGNORES_THIS') ?? 'do_not_set',
218+
webhookSecret = readEnv('IMAGEKIT_WEBHOOK_SECRET') ?? null,
207219
...opts
208220
}: ClientOptions = {}) {
209221
if (privateAPIKey === undefined) {
@@ -215,6 +227,7 @@ export class ImageKit {
215227
const options: ClientOptions = {
216228
privateAPIKey,
217229
password,
230+
webhookSecret,
218231
...opts,
219232
baseURL: baseURL || `https://api.imagekit.io`,
220233
};
@@ -238,6 +251,7 @@ export class ImageKit {
238251

239252
this.privateAPIKey = privateAPIKey;
240253
this.password = password;
254+
this.webhookSecret = webhookSecret;
241255
}
242256

243257
/**
@@ -255,6 +269,7 @@ export class ImageKit {
255269
fetchOptions: this.fetchOptions,
256270
privateAPIKey: this.privateAPIKey,
257271
password: this.password,
272+
webhookSecret: this.webhookSecret,
258273
...options,
259274
});
260275
return client;

src/resources/webhooks.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,23 @@
22

33
import { APIResource } from '../core/resource';
44
import * as FilesAPI from './files/files';
5+
import { Webhook } from 'standardwebhooks';
56

67
export class Webhooks extends APIResource {
78
unsafeUnwrap(body: string): UnsafeUnwrapWebhookEvent {
89
return JSON.parse(body) as UnsafeUnwrapWebhookEvent;
910
}
1011

11-
unwrap(body: string): UnwrapWebhookEvent {
12+
unwrap(
13+
body: string,
14+
{ headers, key }: { headers: Record<string, string>; key?: string },
15+
): UnwrapWebhookEvent {
16+
if (headers !== undefined) {
17+
const keyStr: string | null = key === undefined ? this._client.webhookSecret : key;
18+
if (keyStr === null) throw new Error('Webhook key must not be null in order to unwrap');
19+
const wh = new Webhook(keyStr);
20+
wh.verify(body, headers);
21+
}
1222
return JSON.parse(body) as UnwrapWebhookEvent;
1323
}
1424
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
2+
3+
import { Webhook } from 'standardwebhooks';
4+
5+
import ImageKit from '@imagekit/nodejs';
6+
7+
const client = new ImageKit({
8+
privateAPIKey: 'My Private API Key',
9+
password: 'My Password',
10+
baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010',
11+
});
12+
13+
describe('resource webhooks', () => {
14+
test.skip('unwrap', async () => {
15+
const key = 'whsec_c2VjcmV0Cg==';
16+
const payload =
17+
'{"id":"id","created_at":"2019-12-27T18:11:19.117Z","data":{"asset":{"url":"https://example.com"},"transformation":{"type":"video-transformation","options":{"audio_codec":"aac","auto_rotate":true,"format":"mp4","quality":0,"stream_protocol":"HLS","variants":["string"],"video_codec":"h264"}}},"request":{"url":"https://example.com","x_request_id":"x_request_id","user_agent":"user_agent"},"type":"video.transformation.accepted"}';
18+
const msgID = '1';
19+
const timestamp = new Date();
20+
const wh = new Webhook(key);
21+
const signature = wh.sign(msgID, timestamp, payload);
22+
const headers: Record<string, string> = {
23+
'webhook-signature': signature,
24+
'webhook-id': msgID,
25+
'webhook-timestamp': String(Math.floor(timestamp.getTime() / 1000)),
26+
};
27+
client.webhooks.unwrap(payload, { headers, key });
28+
expect(() => {
29+
const wrongKey = 'whsec_aaaaaaaaaa==';
30+
client.webhooks.unwrap(payload, { headers, key: wrongKey });
31+
}).toThrow('No matching signature found');
32+
expect(() => {
33+
const badSig = wh.sign(msgID, timestamp, 'some other payload');
34+
client.webhooks.unwrap(payload, { headers: { ...headers, 'webhook-signature': badSig }, key });
35+
}).toThrow('No matching signature found');
36+
expect(() => {
37+
client.webhooks.unwrap(payload, { headers: { ...headers, 'webhook-timestamp': '5' }, key });
38+
}).toThrow('Message timestamp too old');
39+
expect(() => {
40+
client.webhooks.unwrap(payload, { headers: { ...headers, 'webhook-id': 'wrong' }, key });
41+
}).toThrow('No matching signature found');
42+
});
43+
});

yarn.lock

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -743,6 +743,11 @@
743743
dependencies:
744744
"@sinonjs/commons" "^3.0.0"
745745

746+
"@stablelib/base64@^1.0.0":
747+
version "1.0.1"
748+
resolved "https://registry.yarnpkg.com/@stablelib/base64/-/base64-1.0.1.tgz#bdfc1c6d3a62d7a3b7bbc65b6cce1bb4561641be"
749+
integrity sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==
750+
746751
747752
version "1.4.16"
748753
resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.4.16.tgz#2cd45d709ce76d448d96bf8d0006849541436611"
@@ -1718,6 +1723,11 @@ fast-levenshtein@^2.0.6:
17181723
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
17191724
integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
17201725

1726+
fast-sha256@^1.3.0:
1727+
version "1.3.0"
1728+
resolved "https://registry.yarnpkg.com/fast-sha256/-/fast-sha256-1.3.0.tgz#7916ba2054eeb255982608cccd0f6660c79b7ae6"
1729+
integrity sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==
1730+
17211731
fastq@^1.6.0:
17221732
version "1.17.1"
17231733
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47"
@@ -3101,6 +3111,14 @@ stack-utils@^2.0.3:
31013111
dependencies:
31023112
escape-string-regexp "^2.0.0"
31033113

3114+
standardwebhooks@^1.0.0:
3115+
version "1.0.0"
3116+
resolved "https://registry.yarnpkg.com/standardwebhooks/-/standardwebhooks-1.0.0.tgz#5faa23ceacbf9accd344361101d9e3033b64324f"
3117+
integrity sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==
3118+
dependencies:
3119+
"@stablelib/base64" "^1.0.0"
3120+
fast-sha256 "^1.3.0"
3121+
31043122
string-length@^4.0.1:
31053123
version "4.0.2"
31063124
resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a"

0 commit comments

Comments
 (0)