@ironfang/render 3.0.0 → 3.0.1

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/README.md CHANGED
@@ -13,6 +13,11 @@ reports `usage` instead of `credits`, `usage()` returns the month per meter,
13
13
  request history records `meter` and `quantity`, and billing refusals throw
14
14
  `IronfangBillingError`. Renders accept `idempotencyKey`.
15
15
 
16
+ 3.0.1 fixes `verifyWebhook` for Render: it used the text of the signing secret as
17
+ the HMAC key, while a Render secret is the hex of the 32-byte key deliveries
18
+ are signed with, so it rejected every genuine delivery. Upgrade if you
19
+ verify Render webhooks with it.
20
+
16
21
  ```sh
17
22
  npm install @ironfang/render
18
23
  ```
@@ -96,7 +101,10 @@ if (!('unmetered' in usage)) {
96
101
 
97
102
  ## Verifying a webhook
98
103
 
99
- Run the check over the raw request body, before parsing it.
104
+ Run the check over the raw request body, before parsing it. Pass the signing
105
+ secret exactly as it was shown when the destination was created: a Render (or
106
+ Finance) secret is 64 hex characters, and the check decodes it to the 32-byte
107
+ key; an Ironfang Audit secret (`awsec_...`) is used as it is.
100
108
 
101
109
  ```ts
102
110
  import { verifyWebhook } from '@ironfang/render';
package/dist/client.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { ClipRequest, Destination, DestinationDraft, ImageRequest, Job, JobSubmission, PdfRequest, QrRequest, RequestFilter, RequestRecord, ScreenshotRequest, SignRequest, SignedUrl, SitePreviewRequest, UnmeteredUsage, Usage } from './types.js';
2
- export declare const VERSION = "3.0.0";
2
+ export declare const VERSION = "3.0.1";
3
3
  export interface ClientOptions {
4
4
  /** Your `rw_live_...` key. Never ship this to a browser. */
5
5
  apiKey: string;
package/dist/client.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { BILLING_CODES, IronfangBillingError, IronfangRenderError } from './errors.js';
2
- export const VERSION = '3.0.0';
2
+ export const VERSION = '3.0.1';
3
3
  export class IronfangRender {
4
4
  apiKey;
5
5
  baseUrl;
package/dist/webhook.d.ts CHANGED
@@ -6,9 +6,15 @@
6
6
  * result as `Ironfang-Signature: v1=<hex>` with `Ironfang-Timestamp` beside
7
7
  * it. The check must run over the raw bytes as received: re-serialising parsed
8
8
  * JSON changes them and the signature will not match.
9
+ *
10
+ * Pass the signing secret exactly as Ironfang returned it. A Render or
11
+ * Finance secret is 64 hex characters, the hex form of a 32-byte key, and the
12
+ * key is those 32 bytes; an Audit secret (`awsec_...`) is used as it is. This
13
+ * module decodes accordingly, so the same call verifies a delivery from any of
14
+ * the three.
9
15
  */
10
16
  export interface WebhookInput {
11
- /** The endpoint's signing secret, shown once when the destination was created. */
17
+ /** The endpoint's signing secret, exactly as it was shown once when the destination was created. */
12
18
  secret: string;
13
19
  /** The `Ironfang-Timestamp` header. */
14
20
  timestamp: string;
package/dist/webhook.js CHANGED
@@ -13,7 +13,7 @@ export async function verifyWebhook(input) {
13
13
  }
14
14
  /** Compute the signature Ironfang Render would send. Exposed for tests and for tooling that replays deliveries. */
15
15
  export async function signWebhook(secret, timestamp, payload) {
16
- const key = await crypto.subtle.importKey('raw', encoder.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
16
+ const key = await crypto.subtle.importKey('raw', signingKey(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
17
17
  const body = typeof payload === 'string' ? encoder.encode(payload) : payload;
18
18
  const signed = new Uint8Array(encoder.encode(timestamp + '.').length + body.length);
19
19
  signed.set(encoder.encode(timestamp + '.'), 0);
@@ -21,6 +21,24 @@ export async function signWebhook(secret, timestamp, payload) {
21
21
  const mac = new Uint8Array(await crypto.subtle.sign('HMAC', key, signed));
22
22
  return 'v1=' + Array.from(mac, (b) => b.toString(16).padStart(2, '0')).join('');
23
23
  }
24
+ const HEX_SECRET = /^[0-9a-f]{64}$/i;
25
+ /**
26
+ * The HMAC key a secret stands for. Render and Finance mint 32 random bytes
27
+ * and show them as 64 hex characters, and sign with the bytes; Audit shows an
28
+ * `awsec_` string and signs with that string. Until 3.0.1 this used the text
29
+ * of every secret, so no Render delivery verified.
30
+ */
31
+ function signingKey(secret) {
32
+ const trimmed = secret.trim();
33
+ if (!HEX_SECRET.test(trimmed)) {
34
+ return encoder.encode(secret);
35
+ }
36
+ const key = new Uint8Array(32);
37
+ for (let i = 0; i < 32; i++) {
38
+ key[i] = parseInt(trimmed.slice(2 * i, 2 * i + 2), 16);
39
+ }
40
+ return key;
41
+ }
24
42
  function constantTimeEqual(a, b) {
25
43
  const x = encoder.encode(a);
26
44
  const y = encoder.encode(b);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ironfang/render",
3
- "version": "3.0.0",
3
+ "version": "3.0.1",
4
4
  "description": "Ironfang Render API client: screenshots, PDFs, QR codes, template images, clips and site previews from a URL or HTML.",
5
5
  "license": "MIT",
6
6
  "type": "module",