@ironfang/render 3.0.0 → 3.0.2
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 +14 -1
- package/dist/client.d.ts +1 -1
- package/dist/client.js +1 -1
- package/dist/types.d.ts +5 -2
- package/dist/webhook.d.ts +7 -1
- package/dist/webhook.js +19 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,6 +13,16 @@ 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
|
+
|
|
21
|
+
3.0.2 types a template's `vars` as `Record<string, string>` in `image()` and
|
|
22
|
+
`sign()`, as the API has always required: a number, boolean or object passed
|
|
23
|
+
the type check and then came back as a 400. Code that passed one no longer
|
|
24
|
+
compiles; pass it as text (`String(n)`).
|
|
25
|
+
|
|
16
26
|
```sh
|
|
17
27
|
npm install @ironfang/render
|
|
18
28
|
```
|
|
@@ -96,7 +106,10 @@ if (!('unmetered' in usage)) {
|
|
|
96
106
|
|
|
97
107
|
## Verifying a webhook
|
|
98
108
|
|
|
99
|
-
Run the check over the raw request body, before parsing it.
|
|
109
|
+
Run the check over the raw request body, before parsing it. Pass the signing
|
|
110
|
+
secret exactly as it was shown when the destination was created: a Render (or
|
|
111
|
+
Finance) secret is 64 hex characters, and the check decodes it to the 32-byte
|
|
112
|
+
key; an Ironfang Audit secret (`awsec_...`) is used as it is.
|
|
100
113
|
|
|
101
114
|
```ts
|
|
102
115
|
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.
|
|
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
package/dist/types.d.ts
CHANGED
|
@@ -85,7 +85,9 @@ export interface QrRequest {
|
|
|
85
85
|
logo_pad?: boolean;
|
|
86
86
|
}
|
|
87
87
|
export interface ImageRequest {
|
|
88
|
-
|
|
88
|
+
/** The template's placeholder values. Text only: the API refuses a number,
|
|
89
|
+
* boolean or object with 400, so pass `String(n)` for a number. */
|
|
90
|
+
vars?: Record<string, string>;
|
|
89
91
|
format?: 'png' | 'jpeg' | 'webp';
|
|
90
92
|
}
|
|
91
93
|
export interface ClipRequest {
|
|
@@ -168,7 +170,8 @@ export interface SignRequest {
|
|
|
168
170
|
kind: 'screenshot' | 'image';
|
|
169
171
|
url?: string;
|
|
170
172
|
template?: string;
|
|
171
|
-
|
|
173
|
+
/** Placeholder values for an `image` URL: text only, as for `image()`. */
|
|
174
|
+
vars?: Record<string, string>;
|
|
172
175
|
width?: number;
|
|
173
176
|
height?: number;
|
|
174
177
|
full_page?: boolean;
|
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',
|
|
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