@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 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 # requires an npm login with access to the @cuvo scope
274
+ npm publish --tag next # needs an npm login in the cuvo-health-us organization
261
275
  ```
262
276
 
263
- Nothing goes out under `latest` until the contract is stable and the founder says so: a stable tag
264
- is what a partner's `pnpm add @cuvo-health-us/api` resolves to by default.
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";
@@ -1,6 +1,7 @@
1
1
  import { type CuvoTypedEvent } from "./events.js";
2
2
  /**
3
- * Webhook verification, the receiving half of the recipe in api/lib/webhooks/sign.ts:
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 verification, the receiving half of the recipe in api/lib/webhooks/sign.ts:
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cuvo-health-us/api",
3
- "version": "0.1.0-next.0",
3
+ "version": "0.1.0-next.1",
4
4
  "type": "module",
5
5
  "description": "TypeScript client for the Cuvo Integrations API, generated from the v1 contract.",
6
6
  "types": "./dist/index.d.ts",