@backendfree/core 0.1.0 → 0.1.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.
@@ -1,207 +0,0 @@
1
- /**
2
- * Checking that a webhook really came from the platform.
3
- *
4
- * The other direction from the rest of this package: your server receives, and
5
- * this says whether to believe it. A webhook endpoint is a public URL, so
6
- * anybody can post to it, and `payment.succeeded` is a message that marks an
7
- * order paid. Without this, marking it paid is something a stranger can do.
8
- *
9
- * It lives in core rather than in the payments package because a delivery
10
- * carries whatever event it carries: a published page, a confirmed booking, a
11
- * settled payment. One verifier, whichever modules a project has switched on.
12
- *
13
- * Four things decide whether it works.
14
- *
15
- * **The raw body, exactly as it arrived.** The signature covers those bytes, so
16
- * a body that has been parsed and re-serialised computes a different digest and
17
- * every delivery fails. In Express that means `express.raw({type: '*\/*'})` on
18
- * this route and not `express.json()`. In a Next.js route handler it means
19
- * `await request.text()` before anything reads `request.json()`.
20
- *
21
- * **The timestamp is signed too.** It is inside the signed material, so it
22
- * cannot be edited, and it is checked for age separately. A signature on its own
23
- * proves the message was ours once, not that it is not a recording of one.
24
- *
25
- * **The digest is compared in constant time.** A comparison that stops at the
26
- * first wrong byte tells whoever is guessing how much of the prefix is right,
27
- * and a valid signature can be built one byte at a time from that.
28
- *
29
- * **The secret is the endpoint's own**, shown when the endpoint is created. It
30
- * is not an API key, it never goes in a header, and one per endpoint means
31
- * rotating one does not break the rest.
32
- */
33
- import { GeorithaminatorError } from './errors.js';
34
- export const SIGNATURE_HEADER = 'x-georithaminator-signature';
35
- export const TIMESTAMP_HEADER = 'x-georithaminator-timestamp';
36
- export const EVENT_HEADER = 'x-georithaminator-event';
37
- export const DELIVERY_HEADER = 'x-georithaminator-delivery';
38
- /**
39
- * How old a delivery may be, in seconds. Five minutes.
40
- *
41
- * Long enough for a queue backing up and for clocks that disagree by a little,
42
- * short enough that a captured request is not a spare key. Pass `0` to accept
43
- * any age, which is for replaying a stored delivery by hand and not for a live
44
- * endpoint.
45
- */
46
- export const DEFAULT_TOLERANCE = 300;
47
- /**
48
- * A delivery that could not be believed.
49
- *
50
- * Its own class because the answer is different from an API failure: there is
51
- * nothing to retry and nothing to fix at your end. Answer 400 and log it.
52
- * `code` says which check failed, and every one of them means the same thing to
53
- * a caller, which is do not act on this.
54
- */
55
- export class WebhookError extends GeorithaminatorError {
56
- constructor(code, message) {
57
- super('invalid_request', code, message);
58
- }
59
- }
60
- /**
61
- * Verify one delivery and hand back what it says.
62
- *
63
- * Throws `WebhookError` when anything is wrong: no signature, no timestamp, too
64
- * old, a digest that does not match, or a body that is not JSON. Nothing is
65
- * parsed until the signature has passed, so an unverified body is never
66
- * anything but bytes.
67
- *
68
- * import { verifyWebhook } from '@georithaminator/core';
69
- *
70
- * const event = await verifyWebhook({
71
- * body: await request.text(),
72
- * headers: request.headers,
73
- * secret: process.env.WEBHOOK_SECRET,
74
- * });
75
- * if (event.name === 'payment.succeeded') { ... }
76
- *
77
- * Answer 2xx quickly and do the work afterwards. The platform retries anything
78
- * that is not a success, so a slow handler becomes a duplicate delivery.
79
- * Duplicates happen anyway: a delivery is at least once, never exactly once, so
80
- * make the handler idempotent on `event.delivery` or on what the payload names.
81
- */
82
- export async function verifyWebhook(options) {
83
- const { secret, tolerance = DEFAULT_TOLERANCE } = options;
84
- if (!secret) {
85
- throw new WebhookError('secret_required', 'Pass the endpoint signing secret.');
86
- }
87
- const headers = readHeaders(options.headers);
88
- const signature = headers.get(SIGNATURE_HEADER);
89
- const stamp = headers.get(TIMESTAMP_HEADER);
90
- if (!signature) {
91
- throw new WebhookError('missing_signature', `This request carries no ${SIGNATURE_HEADER} header, so it did not come from the platform.`);
92
- }
93
- if (!stamp) {
94
- throw new WebhookError('missing_timestamp', `This request carries no ${TIMESTAMP_HEADER} header, and the timestamp is part of what is signed.`);
95
- }
96
- const timestamp = Number.parseInt(stamp, 10);
97
- if (!Number.isFinite(timestamp)) {
98
- throw new WebhookError('invalid_timestamp', `${TIMESTAMP_HEADER} is not a number of seconds.`);
99
- }
100
- // Before the digest, because an expired delivery is refused whatever it says
101
- // and there is no reason to spend a hash on it.
102
- const age = Math.floor((options.now ?? Date.now()) / 1000) - timestamp;
103
- if (tolerance > 0 && Math.abs(age) > tolerance) {
104
- throw new WebhookError('timestamp_out_of_tolerance', `This delivery is ${age}s old and at most ${tolerance}s is accepted. Either it is a ` +
105
- 'replay of an old one, or this machine and the platform disagree about the time.');
106
- }
107
- const body = asBytes(options.body);
108
- // `timestamp.body`, as bytes, never as a string that was decoded and encoded
109
- // again on the way. The digest is over what arrived.
110
- const signed = join(`${stamp}.`, body);
111
- const expected = await hmac(secret, signed);
112
- if (!constantTimeEqual(expected, parseSignature(signature))) {
113
- throw new WebhookError('invalid_signature', 'The signature does not match. Either the secret is the wrong one, or something ' +
114
- 'parsed the body before this saw it: the digest covers the exact bytes that arrived.');
115
- }
116
- return {
117
- name: headers.get(EVENT_HEADER) ?? '',
118
- delivery: headers.get(DELIVERY_HEADER) ?? '',
119
- timestamp,
120
- data: parse(body),
121
- };
122
- }
123
- // --- the parts ----------------------------------------------------------------
124
- /**
125
- * The digest out of the header value.
126
- *
127
- * `sha256=<hex>` is the form sent. The prefix is stripped rather than required,
128
- * so a second algorithm could be added without this refusing every delivery
129
- * from the day it ships.
130
- */
131
- function parseSignature(header) {
132
- const value = header.trim();
133
- const marker = value.indexOf('=');
134
- return (marker === -1 ? value : value.slice(marker + 1)).trim().toLowerCase();
135
- }
136
- async function hmac(secret, message) {
137
- const encoder = new TextEncoder();
138
- const key = await crypto.subtle.importKey('raw', encoder.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
139
- const signature = await crypto.subtle.sign('HMAC', key, message);
140
- return [...new Uint8Array(signature)].map((byte) => byte.toString(16).padStart(2, '0')).join('');
141
- }
142
- /**
143
- * Compare two hex digests without leaking where they differ.
144
- *
145
- * Every byte is looked at, whatever the first one says. A `===` on strings can
146
- * stop at the first difference, and that timing is enough to build a valid
147
- * signature a character at a time.
148
- *
149
- * Lengths are folded in rather than checked first, so a wrong length is not a
150
- * faster answer than a wrong digest.
151
- */
152
- function constantTimeEqual(a, b) {
153
- let difference = a.length ^ b.length;
154
- for (let index = 0; index < a.length; index += 1) {
155
- difference |= a.charCodeAt(index) ^ b.charCodeAt(index % (b.length || 1));
156
- }
157
- return difference === 0;
158
- }
159
- function asBytes(body) {
160
- return typeof body === 'string' ? new TextEncoder().encode(body) : body;
161
- }
162
- function join(prefix, body) {
163
- const head = new TextEncoder().encode(prefix);
164
- const out = new Uint8Array(head.length + body.length);
165
- out.set(head, 0);
166
- out.set(body, head.length);
167
- return out;
168
- }
169
- function parse(body) {
170
- try {
171
- return JSON.parse(new TextDecoder().decode(body));
172
- }
173
- catch (cause) {
174
- // Reached only after the signature passed, so this is the platform sending
175
- // something unreadable rather than anybody's attempt at anything.
176
- throw new WebhookError('malformed_body', `This delivery is signed but its body is not JSON: ${String(cause)}`);
177
- }
178
- }
179
- /**
180
- * One lookup that works for a Headers, a Map, or `req.headers`.
181
- *
182
- * A Headers is used as it is, since its own lookup already ignores case.
183
- * Anything else is copied into a lowercased map, because a plain object's keys
184
- * are whatever the server chose to call them: Node lowercases, other runtimes
185
- * do not, and a signature header missed on case reads as no signature at all.
186
- *
187
- * `append` is what tells a Headers from a Map. Both have `get`, but a Map's is
188
- * case-sensitive, so treating one as the other would silently miss every header
189
- * whose key was not already lower case.
190
- */
191
- function readHeaders(source) {
192
- const candidate = source;
193
- if (typeof candidate.get === 'function' && typeof candidate.append === 'function') {
194
- return { get: (name) => candidate.get(name) ?? undefined };
195
- }
196
- const entries = source instanceof Map ? source.entries() : Object.entries(source);
197
- const lowered = new Map();
198
- for (const [name, value] of entries) {
199
- if (value === undefined || value === null)
200
- continue;
201
- // Node hands back an array when a header arrived more than once. The first
202
- // is taken, because two signatures on one request is not a case to guess at.
203
- lowered.set(name.toLowerCase(), String(Array.isArray(value) ? value[0] : value));
204
- }
205
- return { get: (name) => lowered.get(name.toLowerCase()) };
206
- }
207
- //# sourceMappingURL=webhooks.js.map
@@ -1,115 +0,0 @@
1
- /**
2
- * Checking that a webhook really came from the platform.
3
- *
4
- * The other direction from the rest of this package: your server receives, and
5
- * this says whether to believe it. A webhook endpoint is a public URL, so
6
- * anybody can post to it, and `payment.succeeded` is a message that marks an
7
- * order paid. Without this, marking it paid is something a stranger can do.
8
- *
9
- * It lives in core rather than in the payments package because a delivery
10
- * carries whatever event it carries: a published page, a confirmed booking, a
11
- * settled payment. One verifier, whichever modules a project has switched on.
12
- *
13
- * Four things decide whether it works.
14
- *
15
- * **The raw body, exactly as it arrived.** The signature covers those bytes, so
16
- * a body that has been parsed and re-serialised computes a different digest and
17
- * every delivery fails. In Express that means `express.raw({type: '*\/*'})` on
18
- * this route and not `express.json()`. In a Next.js route handler it means
19
- * `await request.text()` before anything reads `request.json()`.
20
- *
21
- * **The timestamp is signed too.** It is inside the signed material, so it
22
- * cannot be edited, and it is checked for age separately. A signature on its own
23
- * proves the message was ours once, not that it is not a recording of one.
24
- *
25
- * **The digest is compared in constant time.** A comparison that stops at the
26
- * first wrong byte tells whoever is guessing how much of the prefix is right,
27
- * and a valid signature can be built one byte at a time from that.
28
- *
29
- * **The secret is the endpoint's own**, shown when the endpoint is created. It
30
- * is not an API key, it never goes in a header, and one per endpoint means
31
- * rotating one does not break the rest.
32
- */
33
- import { GeorithaminatorError } from './errors.js';
34
- export declare const SIGNATURE_HEADER = "x-georithaminator-signature";
35
- export declare const TIMESTAMP_HEADER = "x-georithaminator-timestamp";
36
- export declare const EVENT_HEADER = "x-georithaminator-event";
37
- export declare const DELIVERY_HEADER = "x-georithaminator-delivery";
38
- /**
39
- * How old a delivery may be, in seconds. Five minutes.
40
- *
41
- * Long enough for a queue backing up and for clocks that disagree by a little,
42
- * short enough that a captured request is not a spare key. Pass `0` to accept
43
- * any age, which is for replaying a stored delivery by hand and not for a live
44
- * endpoint.
45
- */
46
- export declare const DEFAULT_TOLERANCE = 300;
47
- /** A delivery that proved to be ours. */
48
- export interface WebhookEvent<T = Record<string, unknown>> {
49
- /** `payment.succeeded`, `booking.confirmed`, `page.published`. */
50
- name: string;
51
- /** This delivery's id. The same event resent is a new one, so log it. */
52
- delivery: string;
53
- /** When the platform signed it, in seconds since the epoch. */
54
- timestamp: number;
55
- /** The event's own payload, parsed. */
56
- data: T;
57
- }
58
- export interface VerifyOptions {
59
- /**
60
- * The body exactly as received, as text or as bytes.
61
- *
62
- * Not an object. By the time it is an object the bytes the signature covers
63
- * are gone, and no amount of re-serialising gets them back: key order and
64
- * spacing both change the digest.
65
- */
66
- body: string | Uint8Array;
67
- /**
68
- * The request's headers. A `Headers`, Node's `req.headers`, or any plain
69
- * object. Names are matched without case, since every server presents them
70
- * differently.
71
- */
72
- headers: HeaderSource;
73
- /** The endpoint's signing secret, from the dashboard when it was created. */
74
- secret: string;
75
- /** Seconds of age to allow. Defaults to five minutes; `0` allows any. */
76
- tolerance?: number;
77
- /** Milliseconds since the epoch, for tests. Defaults to now. */
78
- now?: number;
79
- }
80
- export type HeaderSource = Headers | Map<string, string> | Record<string, string | string[] | undefined>;
81
- /**
82
- * A delivery that could not be believed.
83
- *
84
- * Its own class because the answer is different from an API failure: there is
85
- * nothing to retry and nothing to fix at your end. Answer 400 and log it.
86
- * `code` says which check failed, and every one of them means the same thing to
87
- * a caller, which is do not act on this.
88
- */
89
- export declare class WebhookError extends GeorithaminatorError {
90
- constructor(code: string, message: string);
91
- }
92
- /**
93
- * Verify one delivery and hand back what it says.
94
- *
95
- * Throws `WebhookError` when anything is wrong: no signature, no timestamp, too
96
- * old, a digest that does not match, or a body that is not JSON. Nothing is
97
- * parsed until the signature has passed, so an unverified body is never
98
- * anything but bytes.
99
- *
100
- * import { verifyWebhook } from '@georithaminator/core';
101
- *
102
- * const event = await verifyWebhook({
103
- * body: await request.text(),
104
- * headers: request.headers,
105
- * secret: process.env.WEBHOOK_SECRET,
106
- * });
107
- * if (event.name === 'payment.succeeded') { ... }
108
- *
109
- * Answer 2xx quickly and do the work afterwards. The platform retries anything
110
- * that is not a success, so a slow handler becomes a duplicate delivery.
111
- * Duplicates happen anyway: a delivery is at least once, never exactly once, so
112
- * make the handler idempotent on `event.delivery` or on what the payload names.
113
- */
114
- export declare function verifyWebhook<T = Record<string, unknown>>(options: VerifyOptions): Promise<WebhookEvent<T>>;
115
- //# sourceMappingURL=webhooks.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"webhooks.d.ts","sourceRoot":"","sources":["../src/webhooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEnD,eAAO,MAAM,gBAAgB,gCAAgC,CAAC;AAC9D,eAAO,MAAM,gBAAgB,gCAAgC,CAAC;AAC9D,eAAO,MAAM,YAAY,4BAA4B,CAAC;AACtD,eAAO,MAAM,eAAe,+BAA+B,CAAC;AAE5D;;;;;;;GAOG;AACH,eAAO,MAAM,iBAAiB,MAAM,CAAC;AAErC,yCAAyC;AACzC,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACvD,kEAAkE;IAClE,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,QAAQ,EAAE,MAAM,CAAC;IACjB,+DAA+D;IAC/D,SAAS,EAAE,MAAM,CAAC;IAClB,uCAAuC;IACvC,IAAI,EAAE,CAAC,CAAC;CACT;AAED,MAAM,WAAW,aAAa;IAC5B;;;;;;OAMG;IACH,IAAI,EAAE,MAAM,GAAG,UAAU,CAAC;IAC1B;;;;OAIG;IACH,OAAO,EAAE,YAAY,CAAC;IACtB,6EAA6E;IAC7E,MAAM,EAAE,MAAM,CAAC;IACf,yEAAyE;IACzE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,YAAY,GACpB,OAAO,GACP,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GACnB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC;AAElD;;;;;;;GAOG;AACH,qBAAa,YAAa,SAAQ,oBAAoB;gBACxC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAG1C;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAsB,aAAa,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7D,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CA2D1B"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"webhooks.js","sourceRoot":"","sources":["../src/webhooks.ts"],"names":[],"mappings":"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"}