@cuvo-health-us/api 0.1.0-next.0 → 0.1.0-next.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 +19 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/webhooks.d.ts +22 -1
- package/dist/webhooks.js +18 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -212,6 +212,20 @@ During the 24 hour grace after a secret rotation a delivery carries two signatur
|
|
|
212
212
|
`verifySignature` accepts a match against either, so you can move to the new secret whenever you
|
|
213
213
|
like inside the window.
|
|
214
214
|
|
|
215
|
+
Testing that handler needs a signature of your own, and so does `cuvo webhooks listen`, which
|
|
216
|
+
mirrors the event stream to a local URL and re-signs what it forwards. `signWebhook` is that half:
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
import { signWebhook } from "@cuvo-health-us/api";
|
|
220
|
+
|
|
221
|
+
const rawBody = JSON.stringify(event);
|
|
222
|
+
const signatureHeader = signWebhook({ rawBody, secret: process.env.CUVO_WEBHOOK_SECRET! });
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Sign the exact string you send, never the object, because the digest covers the bytes. Cuvo's own
|
|
226
|
+
deliveries are signed by the platform, so a header this produces proves only that whoever made it
|
|
227
|
+
held the secret.
|
|
228
|
+
|
|
215
229
|
`parseEvent` returns the event keyed by its `type`, so the branch above knows `event.data` is a
|
|
216
230
|
`Case` and `expandEvent` answers with one. An event read from `GET /v1/events` arrives as the
|
|
217
231
|
contract's single `Event` shape instead, because that is what one schema for the whole catalog
|
|
@@ -257,8 +271,10 @@ The package publishes from `dist`, under the `next` tag, at the prerelease versi
|
|
|
257
271
|
|
|
258
272
|
```sh
|
|
259
273
|
pnpm pack # inspect the tarball first
|
|
260
|
-
npm publish --tag next #
|
|
274
|
+
npm publish --tag next # needs an npm login in the cuvo-health-us organization
|
|
261
275
|
```
|
|
262
276
|
|
|
263
|
-
|
|
264
|
-
|
|
277
|
+
`next` is the tag every prerelease goes out under. The registry also pointed `latest` at the
|
|
278
|
+
first publish, because npm sets `latest` when a package has no tags at all, so `pnpm add
|
|
279
|
+
@cuvo-health-us/api` without a tag resolves to the prerelease today. `latest` moves to the first
|
|
280
|
+
stable release when the contract is stable and the founder says so.
|
package/dist/index.d.ts
CHANGED
|
@@ -9,4 +9,4 @@ export { CuvoApiError, type Problem, type ProblemIssue, unwrap } from "./errors.
|
|
|
9
9
|
export { type CuvoEvent, type CuvoEventOf, type CuvoEventResourceOf, type CuvoEventResourceType, type CuvoEventResourceTypeOf, type CuvoEventType, type CuvoTypedEvent, type ExpandedResource, expandEvent, typedEvent, } from "./events.js";
|
|
10
10
|
export type { components, operations, paths } from "./generated/v1.js";
|
|
11
11
|
export { type ListPage, paginate } from "./pagination.js";
|
|
12
|
-
export { DEFAULT_TOLERANCE_SECONDS, parseEvent, type VerifySignatureOptions, verifySignature, } from "./webhooks.js";
|
|
12
|
+
export { DEFAULT_TOLERANCE_SECONDS, parseEvent, type SignWebhookOptions, signWebhook, type VerifySignatureOptions, verifySignature, } from "./webhooks.js";
|
package/dist/index.js
CHANGED
|
@@ -8,4 +8,4 @@ export { createCuvoClient, DEFAULT_BASE_URL, DEFAULT_MAX_RETRIES, } from "./clie
|
|
|
8
8
|
export { CuvoApiError, unwrap } from "./errors.js";
|
|
9
9
|
export { expandEvent, typedEvent, } from "./events.js";
|
|
10
10
|
export { paginate } from "./pagination.js";
|
|
11
|
-
export { DEFAULT_TOLERANCE_SECONDS, parseEvent, verifySignature, } from "./webhooks.js";
|
|
11
|
+
export { DEFAULT_TOLERANCE_SECONDS, parseEvent, signWebhook, verifySignature, } from "./webhooks.js";
|
package/dist/webhooks.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type CuvoTypedEvent } from "./events.js";
|
|
2
2
|
/**
|
|
3
|
-
* Webhook
|
|
3
|
+
* Webhook signatures: the receiving half of the recipe in api/lib/webhooks/sign.ts, plus a
|
|
4
|
+
* signer for the code that stands in for the sender.
|
|
4
5
|
*
|
|
5
6
|
* Cuvo-Signature: t=<unix seconds>,v1=<hex hmac_sha256(secret, `${t}.${rawBody}`)>
|
|
6
7
|
*
|
|
@@ -32,6 +33,26 @@ export interface VerifySignatureOptions {
|
|
|
32
33
|
* when it is false, and never act on an unverified body.
|
|
33
34
|
*/
|
|
34
35
|
export declare function verifySignature(options: VerifySignatureOptions): boolean;
|
|
36
|
+
/** What `signWebhook` needs to produce a `Cuvo-Signature` header. */
|
|
37
|
+
export interface SignWebhookOptions {
|
|
38
|
+
/** The bytes that will be sent, exactly as they will be sent. */
|
|
39
|
+
rawBody: string;
|
|
40
|
+
/** The signing secret, `whsec_…`. */
|
|
41
|
+
secret: string;
|
|
42
|
+
/** Unix SECONDS. Defaults to now; pass it to make a test deterministic. */
|
|
43
|
+
timestamp?: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The sending half, for the code that stands in for Cuvo.
|
|
47
|
+
*
|
|
48
|
+
* Two callers, neither of them a delivery: a test that exercises its own handler against a body
|
|
49
|
+
* it made up, and `cuvo webhooks listen`, which re-signs the events it mirrors to a local handler
|
|
50
|
+
* with a session secret of its own. Cuvo's own deliveries are signed by the platform, never by
|
|
51
|
+
* this package, and a signature this makes is only ever as trustworthy as the secret behind it.
|
|
52
|
+
*
|
|
53
|
+
* One `v1=` value, because only a rotation puts two on the wire and nothing here rotates.
|
|
54
|
+
*/
|
|
55
|
+
export declare function signWebhook(options: SignWebhookOptions): string;
|
|
35
56
|
/**
|
|
36
57
|
* Parse a verified delivery body into an event keyed by its `type`, so a handler branching on
|
|
37
58
|
* `event.type` gets the resource that type carries without a cast.
|
package/dist/webhooks.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
2
2
|
import { typedEvent } from "./events.js";
|
|
3
3
|
/**
|
|
4
|
-
* Webhook
|
|
4
|
+
* Webhook signatures: the receiving half of the recipe in api/lib/webhooks/sign.ts, plus a
|
|
5
|
+
* signer for the code that stands in for the sender.
|
|
5
6
|
*
|
|
6
7
|
* Cuvo-Signature: t=<unix seconds>,v1=<hex hmac_sha256(secret, `${t}.${rawBody}`)>
|
|
7
8
|
*
|
|
@@ -58,6 +59,22 @@ export function verifySignature(options) {
|
|
|
58
59
|
// nothing to leak by the number of comparisons, only by their duration.
|
|
59
60
|
return candidates.some((candidate) => matches(candidate, expected));
|
|
60
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* The sending half, for the code that stands in for Cuvo.
|
|
64
|
+
*
|
|
65
|
+
* Two callers, neither of them a delivery: a test that exercises its own handler against a body
|
|
66
|
+
* it made up, and `cuvo webhooks listen`, which re-signs the events it mirrors to a local handler
|
|
67
|
+
* with a session secret of its own. Cuvo's own deliveries are signed by the platform, never by
|
|
68
|
+
* this package, and a signature this makes is only ever as trustworthy as the secret behind it.
|
|
69
|
+
*
|
|
70
|
+
* One `v1=` value, because only a rotation puts two on the wire and nothing here rotates.
|
|
71
|
+
*/
|
|
72
|
+
export function signWebhook(options) {
|
|
73
|
+
const { rawBody, secret, timestamp = Math.floor(Date.now() / 1000) } = options;
|
|
74
|
+
const seconds = Math.floor(timestamp);
|
|
75
|
+
const signature = createHmac("sha256", secret).update(`${seconds}.${rawBody}`).digest("hex");
|
|
76
|
+
return `t=${seconds},v1=${signature}`;
|
|
77
|
+
}
|
|
61
78
|
/**
|
|
62
79
|
* Parse a verified delivery body into an event keyed by its `type`, so a handler branching on
|
|
63
80
|
* `event.type` gets the resource that type carries without a cast.
|
package/package.json
CHANGED