@particle-academy/fancy-connector-core 0.8.0 → 0.9.0

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/dist/index.d.cts CHANGED
@@ -2333,18 +2333,47 @@ type WebhookVerification = {
2333
2333
  reason: string;
2334
2334
  };
2335
2335
  type HmacScheme = {
2336
+ /**
2337
+ * Optional and always `"hmac"`: every scheme written before 0.8.0 omits it
2338
+ * and still means HMAC. It exists so `verifyDelivery` dispatches on a NAMED
2339
+ * kind — `"shared-token"`, `"hmac"` or absent — and refuses any other value
2340
+ * by name, rather than treating "has no kind" as HMAC forever. (Fancy's
2341
+ * alignment review of 0.8.0: a third scheme that forgot its kind would
2342
+ * otherwise be verified as HMAC, silently.)
2343
+ */
2344
+ kind?: "hmac";
2336
2345
  /** Hash to use. */
2337
2346
  algorithm: "SHA-256" | "SHA-1" | "SHA-512";
2338
2347
  /**
2339
2348
  * Build the exact string that gets signed. Providers differ here more than
2340
2349
  * anywhere else — Stripe signs `${timestamp}.${body}`, Slack signs
2341
- * `v0:${timestamp}:${body}`, GitHub signs the body alone.
2350
+ * `v0:${timestamp}:${body}`, GitHub signs the body alone, and Svix signs
2351
+ * `${id}.${timestamp}.${body}` where `id` is the delivery's own id header.
2342
2352
  */
2343
- payload: (raw: string, timestamp?: string) => string;
2353
+ payload: (raw: string, timestamp?: string, id?: string) => string;
2344
2354
  /** Seconds a delivery stays acceptable. */
2345
2355
  tolerance?: number;
2346
2356
  /** Encoding of the signature the provider sends. */
2347
2357
  encoding?: "hex" | "base64";
2358
+ /**
2359
+ * How the SECRET is spelled. `utf8` (the default, and every provider before
2360
+ * Svix): the key is the text's bytes. `base64`: the key is the DECODED
2361
+ * bytes — Svix hands out `whsec_<base64>`, and half of the decoded bytes are
2362
+ * not valid UTF-8, so encoding the text would sign with the wrong key and
2363
+ * fail exactly like a wrong secret.
2364
+ */
2365
+ secretEncoding?: "utf8" | "base64";
2366
+ /** A prefix to strip before decoding (`whsec_`). Its absence is a refusal, never a guess. */
2367
+ secretPrefix?: string;
2368
+ };
2369
+ /** The HMAC key a scheme's secret spells, or the reason it cannot be one. */
2370
+ declare function secretKeyBytes(secret: string, options?: {
2371
+ secretEncoding?: "utf8" | "base64";
2372
+ secretPrefix?: string;
2373
+ }): {
2374
+ bytes: Uint8Array;
2375
+ } | {
2376
+ reason: string;
2348
2377
  };
2349
2378
  /**
2350
2379
  * Verify an HMAC-signed delivery.
@@ -2355,15 +2384,33 @@ type HmacScheme = {
2355
2384
  */
2356
2385
  declare function verifyHmac(options: {
2357
2386
  raw: string;
2358
- signature: string | undefined;
2387
+ /**
2388
+ * The signature(s) the delivery carried. A LIST when the provider sends
2389
+ * several — Stripe signs once per active secret while a secret is rolled
2390
+ * and Svix's header "could be any number of signatures" — and the delivery
2391
+ * is accepted when ANY matches. A first-only rule fails every delivery
2392
+ * whose first signature came from the new secret, for the whole roll, and
2393
+ * it reads exactly like a wrong secret.
2394
+ */
2395
+ signature: string | string[] | undefined;
2359
2396
  secret: string | undefined;
2360
2397
  scheme: HmacScheme;
2361
2398
  timestamp?: string;
2399
+ /** The delivery's id, when the scheme signs one (Svix's `svix-id`). */
2400
+ id?: string;
2362
2401
  /** Seconds since the epoch. Injected so tests are not clock-dependent. */
2363
2402
  now?: number;
2364
2403
  }): Promise<WebhookVerification>;
2365
- /** HMAC of `payload` under `secret`, hex or base64 encoded. */
2366
- declare function hmac(secret: string, payload: string, algorithm: HmacScheme["algorithm"], encoding?: "hex" | "base64"): Promise<string>;
2404
+ /**
2405
+ * HMAC of `payload` under `secret`, hex or base64 encoded. `options` says how
2406
+ * the secret is spelled (see `HmacScheme.secretEncoding`); a secret that
2407
+ * cannot be a key THROWS here — signing has no result to carry a reason in —
2408
+ * while `verifyHmac` names it in its result before ever calling this.
2409
+ */
2410
+ declare function hmac(secret: string, payload: string, algorithm: HmacScheme["algorithm"], encoding?: "hex" | "base64", options?: {
2411
+ secretEncoding?: "utf8" | "base64";
2412
+ secretPrefix?: string;
2413
+ }): Promise<string>;
2367
2414
  /**
2368
2415
  * Compare two strings without leaking where they diverge.
2369
2416
  *
@@ -2494,13 +2541,23 @@ type HmacVerificationSpec = {
2494
2541
  signatureHeader: string;
2495
2542
  /** Header carrying the timestamp, when the scheme signs one. */
2496
2543
  timestampHeader?: string;
2544
+ /**
2545
+ * Header carrying the delivery's ID, when the scheme signs one — Svix
2546
+ * signs `${svix-id}.${svix-timestamp}.${body}`. A declared id header the
2547
+ * delivery does not carry is a refusal, never a payload with a hole in it.
2548
+ */
2549
+ idHeader?: string;
2497
2550
  scheme: HmacScheme;
2498
2551
  /**
2499
2552
  * Some schemes pack the timestamp INTO the signature header
2500
2553
  * (Stripe: `t=…,v1=…`). Given the raw header value, return the parts.
2554
+ * `signatures` carries EVERY signature the header held (a provider rolling
2555
+ * a secret sends one per active secret); `signature` alone is the 0.8.x
2556
+ * shape and still means one.
2501
2557
  */
2502
2558
  parse?: (raw: string) => {
2503
2559
  signature?: string;
2560
+ signatures?: string[];
2504
2561
  timestamp?: string;
2505
2562
  };
2506
2563
  /** A challenge the provider makes before it will deliver anything. */
@@ -2546,4 +2603,4 @@ declare function verifyDelivery(trigger: TriggerDescriptor, delivery: InboundDel
2546
2603
  */
2547
2604
  declare function triggerEvent(trigger: TriggerDescriptor, connection: ResolvedConnection, injected: unknown, config: Record<string, unknown>): unknown;
2548
2605
 
2549
- export { AMBIGUOUS_REFUSAL, type ApiContract, type Attempt, type ByteRange, CANONICAL_METRICS, CONNECTOR_API_VERSION, CONNECTOR_MODES, CREDENTIAL_SCOPES, type CallOptions, type CallResult, type Capabilities, type Catalogue, type ChainLinks, type ChainOutcome, type ChainRef, type ChallengeHandshake, type Citation, type Classified, type ConnectionCredentials, type ConnectionHost, type ConnectionSpec, type Connector, ConnectorAmbiguous, ConnectorApiMismatch, ConnectorAuthError, ConnectorConfigError, ConnectorError, type ConnectorErrorContext, type ConnectorFaker, ConnectorIdempotencyExpiredError, type ConnectorMode, ConnectorModeError, ConnectorRateLimited, type ConnectorRequest, ConnectorRequestError, type ConnectorResult, ConnectorTransient, ConnectorUnreachable, type ContractOperation, type CredentialField, type CredentialScope, DEFAULT_IDEMPOTENCY_WINDOW_SECONDS, DEFAULT_RETRY, type DeliveryDeclaration, type DeliveryMechanism, type DeliveryOutcome, type DriftFinding, type DriftReport, EXPIRES_AT_UNITS, type ExpiresAtUnit, FAKE_EPOCH, type FailureKind, type FakeRequest, type FakeValues, type HmacScheme, type HmacVerificationSpec, type HostEnvironment, type IdempotencyOptions, type InboundDelivery, type LeaseAction, type LeaseDeclaration, type LeaseState, MAX_IDEMPOTENCY_KEY_LENGTH, type MetricDescriptor, type MetricSample, type MimeAttachment, type MimeHeader, type MimeMessage, type MimePart, type ModeInputs, NO_IDEMPOTENCY_KEY_WARNING, PROBLEM_SEVERITIES, type PreparedRequest, type ProbeOutcome, type ProbeReport, type ProbeResult, type ProbeSpec, type Problem, type ProviderAdapter, RENDERER_VERSION, REVIEW_STALE_DAYS, type RecordedShape, type RenderRules, type RenderedPayload, type RequestedMode, type ResolveOptions, type ResolvedConnection, type RetryPolicy, type RunIdentity, SANDBOX_KINDS, SUPPORTED_CONNECTOR_API, type SandboxKind, type Segment, type ServiceDescriptor, type SetupStep, type ShapeMismatch, type SharedTokenScheme, type SharedTokenVerificationSpec, type SpecSource, type SubscriptionLease, type TextUnit, type Transport, type TransportResponse, type TriggerDescriptor, type VerifyResult, type WebhookVerification, type WebhookVerificationSpec, assertConnectorApi, byteRangeOf, callConnector, capabilityProblems, checkAgainstOpenApi, checkAgainstRecordedShape, classifyError, classifyHttp, classifyStatus, classifyThrown, compareShape, connectionsFor, constantTimeEquals, deliver, fakeRequest, fakeValues, getConnectionHost, graphemes, handshakeResponse, header, hmac, httpFailure, idempotencyKeyFor, isEmptyPayload, isRemote, isSharedTokenScheme, isSharedTokenSpec, isUnconditionallyRetryable, leaseAction, leaseFromResponse, leaseRenewAt, leaseState, linkRanges, measure, parseMime, payloadHash, postChain, probe, providerProblems, providerSandboxIsSelectable, registerConnectionHost, registerTransport, render, reported, resetRateState, resolveConnection, resolveConnectorMode, respectRate, runIdentityFrom, runKeyFrom, runProbes, sandboxIsSelectable, sandboxRefusal, satisfiesMinimum, seedFrom, shapeOf, shouldRetry, sliceByteRange, splitToFit, subscriptionLease, triggerEvent, unchecked, utf8Length, verifyDelivery, verifyHmac, verifySharedToken, withResolvedLimit };
2606
+ export { AMBIGUOUS_REFUSAL, type ApiContract, type Attempt, type ByteRange, CANONICAL_METRICS, CONNECTOR_API_VERSION, CONNECTOR_MODES, CREDENTIAL_SCOPES, type CallOptions, type CallResult, type Capabilities, type Catalogue, type ChainLinks, type ChainOutcome, type ChainRef, type ChallengeHandshake, type Citation, type Classified, type ConnectionCredentials, type ConnectionHost, type ConnectionSpec, type Connector, ConnectorAmbiguous, ConnectorApiMismatch, ConnectorAuthError, ConnectorConfigError, ConnectorError, type ConnectorErrorContext, type ConnectorFaker, ConnectorIdempotencyExpiredError, type ConnectorMode, ConnectorModeError, ConnectorRateLimited, type ConnectorRequest, ConnectorRequestError, type ConnectorResult, ConnectorTransient, ConnectorUnreachable, type ContractOperation, type CredentialField, type CredentialScope, DEFAULT_IDEMPOTENCY_WINDOW_SECONDS, DEFAULT_RETRY, type DeliveryDeclaration, type DeliveryMechanism, type DeliveryOutcome, type DriftFinding, type DriftReport, EXPIRES_AT_UNITS, type ExpiresAtUnit, FAKE_EPOCH, type FailureKind, type FakeRequest, type FakeValues, type HmacScheme, type HmacVerificationSpec, type HostEnvironment, type IdempotencyOptions, type InboundDelivery, type LeaseAction, type LeaseDeclaration, type LeaseState, MAX_IDEMPOTENCY_KEY_LENGTH, type MetricDescriptor, type MetricSample, type MimeAttachment, type MimeHeader, type MimeMessage, type MimePart, type ModeInputs, NO_IDEMPOTENCY_KEY_WARNING, PROBLEM_SEVERITIES, type PreparedRequest, type ProbeOutcome, type ProbeReport, type ProbeResult, type ProbeSpec, type Problem, type ProviderAdapter, RENDERER_VERSION, REVIEW_STALE_DAYS, type RecordedShape, type RenderRules, type RenderedPayload, type RequestedMode, type ResolveOptions, type ResolvedConnection, type RetryPolicy, type RunIdentity, SANDBOX_KINDS, SUPPORTED_CONNECTOR_API, type SandboxKind, type Segment, type ServiceDescriptor, type SetupStep, type ShapeMismatch, type SharedTokenScheme, type SharedTokenVerificationSpec, type SpecSource, type SubscriptionLease, type TextUnit, type Transport, type TransportResponse, type TriggerDescriptor, type VerifyResult, type WebhookVerification, type WebhookVerificationSpec, assertConnectorApi, byteRangeOf, callConnector, capabilityProblems, checkAgainstOpenApi, checkAgainstRecordedShape, classifyError, classifyHttp, classifyStatus, classifyThrown, compareShape, connectionsFor, constantTimeEquals, deliver, fakeRequest, fakeValues, getConnectionHost, graphemes, handshakeResponse, header, hmac, httpFailure, idempotencyKeyFor, isEmptyPayload, isRemote, isSharedTokenScheme, isSharedTokenSpec, isUnconditionallyRetryable, leaseAction, leaseFromResponse, leaseRenewAt, leaseState, linkRanges, measure, parseMime, payloadHash, postChain, probe, providerProblems, providerSandboxIsSelectable, registerConnectionHost, registerTransport, render, reported, resetRateState, resolveConnection, resolveConnectorMode, respectRate, runIdentityFrom, runKeyFrom, runProbes, sandboxIsSelectable, sandboxRefusal, satisfiesMinimum, secretKeyBytes, seedFrom, shapeOf, shouldRetry, sliceByteRange, splitToFit, subscriptionLease, triggerEvent, unchecked, utf8Length, verifyDelivery, verifyHmac, verifySharedToken, withResolvedLimit };
package/dist/index.d.ts CHANGED
@@ -2333,18 +2333,47 @@ type WebhookVerification = {
2333
2333
  reason: string;
2334
2334
  };
2335
2335
  type HmacScheme = {
2336
+ /**
2337
+ * Optional and always `"hmac"`: every scheme written before 0.8.0 omits it
2338
+ * and still means HMAC. It exists so `verifyDelivery` dispatches on a NAMED
2339
+ * kind — `"shared-token"`, `"hmac"` or absent — and refuses any other value
2340
+ * by name, rather than treating "has no kind" as HMAC forever. (Fancy's
2341
+ * alignment review of 0.8.0: a third scheme that forgot its kind would
2342
+ * otherwise be verified as HMAC, silently.)
2343
+ */
2344
+ kind?: "hmac";
2336
2345
  /** Hash to use. */
2337
2346
  algorithm: "SHA-256" | "SHA-1" | "SHA-512";
2338
2347
  /**
2339
2348
  * Build the exact string that gets signed. Providers differ here more than
2340
2349
  * anywhere else — Stripe signs `${timestamp}.${body}`, Slack signs
2341
- * `v0:${timestamp}:${body}`, GitHub signs the body alone.
2350
+ * `v0:${timestamp}:${body}`, GitHub signs the body alone, and Svix signs
2351
+ * `${id}.${timestamp}.${body}` where `id` is the delivery's own id header.
2342
2352
  */
2343
- payload: (raw: string, timestamp?: string) => string;
2353
+ payload: (raw: string, timestamp?: string, id?: string) => string;
2344
2354
  /** Seconds a delivery stays acceptable. */
2345
2355
  tolerance?: number;
2346
2356
  /** Encoding of the signature the provider sends. */
2347
2357
  encoding?: "hex" | "base64";
2358
+ /**
2359
+ * How the SECRET is spelled. `utf8` (the default, and every provider before
2360
+ * Svix): the key is the text's bytes. `base64`: the key is the DECODED
2361
+ * bytes — Svix hands out `whsec_<base64>`, and half of the decoded bytes are
2362
+ * not valid UTF-8, so encoding the text would sign with the wrong key and
2363
+ * fail exactly like a wrong secret.
2364
+ */
2365
+ secretEncoding?: "utf8" | "base64";
2366
+ /** A prefix to strip before decoding (`whsec_`). Its absence is a refusal, never a guess. */
2367
+ secretPrefix?: string;
2368
+ };
2369
+ /** The HMAC key a scheme's secret spells, or the reason it cannot be one. */
2370
+ declare function secretKeyBytes(secret: string, options?: {
2371
+ secretEncoding?: "utf8" | "base64";
2372
+ secretPrefix?: string;
2373
+ }): {
2374
+ bytes: Uint8Array;
2375
+ } | {
2376
+ reason: string;
2348
2377
  };
2349
2378
  /**
2350
2379
  * Verify an HMAC-signed delivery.
@@ -2355,15 +2384,33 @@ type HmacScheme = {
2355
2384
  */
2356
2385
  declare function verifyHmac(options: {
2357
2386
  raw: string;
2358
- signature: string | undefined;
2387
+ /**
2388
+ * The signature(s) the delivery carried. A LIST when the provider sends
2389
+ * several — Stripe signs once per active secret while a secret is rolled
2390
+ * and Svix's header "could be any number of signatures" — and the delivery
2391
+ * is accepted when ANY matches. A first-only rule fails every delivery
2392
+ * whose first signature came from the new secret, for the whole roll, and
2393
+ * it reads exactly like a wrong secret.
2394
+ */
2395
+ signature: string | string[] | undefined;
2359
2396
  secret: string | undefined;
2360
2397
  scheme: HmacScheme;
2361
2398
  timestamp?: string;
2399
+ /** The delivery's id, when the scheme signs one (Svix's `svix-id`). */
2400
+ id?: string;
2362
2401
  /** Seconds since the epoch. Injected so tests are not clock-dependent. */
2363
2402
  now?: number;
2364
2403
  }): Promise<WebhookVerification>;
2365
- /** HMAC of `payload` under `secret`, hex or base64 encoded. */
2366
- declare function hmac(secret: string, payload: string, algorithm: HmacScheme["algorithm"], encoding?: "hex" | "base64"): Promise<string>;
2404
+ /**
2405
+ * HMAC of `payload` under `secret`, hex or base64 encoded. `options` says how
2406
+ * the secret is spelled (see `HmacScheme.secretEncoding`); a secret that
2407
+ * cannot be a key THROWS here — signing has no result to carry a reason in —
2408
+ * while `verifyHmac` names it in its result before ever calling this.
2409
+ */
2410
+ declare function hmac(secret: string, payload: string, algorithm: HmacScheme["algorithm"], encoding?: "hex" | "base64", options?: {
2411
+ secretEncoding?: "utf8" | "base64";
2412
+ secretPrefix?: string;
2413
+ }): Promise<string>;
2367
2414
  /**
2368
2415
  * Compare two strings without leaking where they diverge.
2369
2416
  *
@@ -2494,13 +2541,23 @@ type HmacVerificationSpec = {
2494
2541
  signatureHeader: string;
2495
2542
  /** Header carrying the timestamp, when the scheme signs one. */
2496
2543
  timestampHeader?: string;
2544
+ /**
2545
+ * Header carrying the delivery's ID, when the scheme signs one — Svix
2546
+ * signs `${svix-id}.${svix-timestamp}.${body}`. A declared id header the
2547
+ * delivery does not carry is a refusal, never a payload with a hole in it.
2548
+ */
2549
+ idHeader?: string;
2497
2550
  scheme: HmacScheme;
2498
2551
  /**
2499
2552
  * Some schemes pack the timestamp INTO the signature header
2500
2553
  * (Stripe: `t=…,v1=…`). Given the raw header value, return the parts.
2554
+ * `signatures` carries EVERY signature the header held (a provider rolling
2555
+ * a secret sends one per active secret); `signature` alone is the 0.8.x
2556
+ * shape and still means one.
2501
2557
  */
2502
2558
  parse?: (raw: string) => {
2503
2559
  signature?: string;
2560
+ signatures?: string[];
2504
2561
  timestamp?: string;
2505
2562
  };
2506
2563
  /** A challenge the provider makes before it will deliver anything. */
@@ -2546,4 +2603,4 @@ declare function verifyDelivery(trigger: TriggerDescriptor, delivery: InboundDel
2546
2603
  */
2547
2604
  declare function triggerEvent(trigger: TriggerDescriptor, connection: ResolvedConnection, injected: unknown, config: Record<string, unknown>): unknown;
2548
2605
 
2549
- export { AMBIGUOUS_REFUSAL, type ApiContract, type Attempt, type ByteRange, CANONICAL_METRICS, CONNECTOR_API_VERSION, CONNECTOR_MODES, CREDENTIAL_SCOPES, type CallOptions, type CallResult, type Capabilities, type Catalogue, type ChainLinks, type ChainOutcome, type ChainRef, type ChallengeHandshake, type Citation, type Classified, type ConnectionCredentials, type ConnectionHost, type ConnectionSpec, type Connector, ConnectorAmbiguous, ConnectorApiMismatch, ConnectorAuthError, ConnectorConfigError, ConnectorError, type ConnectorErrorContext, type ConnectorFaker, ConnectorIdempotencyExpiredError, type ConnectorMode, ConnectorModeError, ConnectorRateLimited, type ConnectorRequest, ConnectorRequestError, type ConnectorResult, ConnectorTransient, ConnectorUnreachable, type ContractOperation, type CredentialField, type CredentialScope, DEFAULT_IDEMPOTENCY_WINDOW_SECONDS, DEFAULT_RETRY, type DeliveryDeclaration, type DeliveryMechanism, type DeliveryOutcome, type DriftFinding, type DriftReport, EXPIRES_AT_UNITS, type ExpiresAtUnit, FAKE_EPOCH, type FailureKind, type FakeRequest, type FakeValues, type HmacScheme, type HmacVerificationSpec, type HostEnvironment, type IdempotencyOptions, type InboundDelivery, type LeaseAction, type LeaseDeclaration, type LeaseState, MAX_IDEMPOTENCY_KEY_LENGTH, type MetricDescriptor, type MetricSample, type MimeAttachment, type MimeHeader, type MimeMessage, type MimePart, type ModeInputs, NO_IDEMPOTENCY_KEY_WARNING, PROBLEM_SEVERITIES, type PreparedRequest, type ProbeOutcome, type ProbeReport, type ProbeResult, type ProbeSpec, type Problem, type ProviderAdapter, RENDERER_VERSION, REVIEW_STALE_DAYS, type RecordedShape, type RenderRules, type RenderedPayload, type RequestedMode, type ResolveOptions, type ResolvedConnection, type RetryPolicy, type RunIdentity, SANDBOX_KINDS, SUPPORTED_CONNECTOR_API, type SandboxKind, type Segment, type ServiceDescriptor, type SetupStep, type ShapeMismatch, type SharedTokenScheme, type SharedTokenVerificationSpec, type SpecSource, type SubscriptionLease, type TextUnit, type Transport, type TransportResponse, type TriggerDescriptor, type VerifyResult, type WebhookVerification, type WebhookVerificationSpec, assertConnectorApi, byteRangeOf, callConnector, capabilityProblems, checkAgainstOpenApi, checkAgainstRecordedShape, classifyError, classifyHttp, classifyStatus, classifyThrown, compareShape, connectionsFor, constantTimeEquals, deliver, fakeRequest, fakeValues, getConnectionHost, graphemes, handshakeResponse, header, hmac, httpFailure, idempotencyKeyFor, isEmptyPayload, isRemote, isSharedTokenScheme, isSharedTokenSpec, isUnconditionallyRetryable, leaseAction, leaseFromResponse, leaseRenewAt, leaseState, linkRanges, measure, parseMime, payloadHash, postChain, probe, providerProblems, providerSandboxIsSelectable, registerConnectionHost, registerTransport, render, reported, resetRateState, resolveConnection, resolveConnectorMode, respectRate, runIdentityFrom, runKeyFrom, runProbes, sandboxIsSelectable, sandboxRefusal, satisfiesMinimum, seedFrom, shapeOf, shouldRetry, sliceByteRange, splitToFit, subscriptionLease, triggerEvent, unchecked, utf8Length, verifyDelivery, verifyHmac, verifySharedToken, withResolvedLimit };
2606
+ export { AMBIGUOUS_REFUSAL, type ApiContract, type Attempt, type ByteRange, CANONICAL_METRICS, CONNECTOR_API_VERSION, CONNECTOR_MODES, CREDENTIAL_SCOPES, type CallOptions, type CallResult, type Capabilities, type Catalogue, type ChainLinks, type ChainOutcome, type ChainRef, type ChallengeHandshake, type Citation, type Classified, type ConnectionCredentials, type ConnectionHost, type ConnectionSpec, type Connector, ConnectorAmbiguous, ConnectorApiMismatch, ConnectorAuthError, ConnectorConfigError, ConnectorError, type ConnectorErrorContext, type ConnectorFaker, ConnectorIdempotencyExpiredError, type ConnectorMode, ConnectorModeError, ConnectorRateLimited, type ConnectorRequest, ConnectorRequestError, type ConnectorResult, ConnectorTransient, ConnectorUnreachable, type ContractOperation, type CredentialField, type CredentialScope, DEFAULT_IDEMPOTENCY_WINDOW_SECONDS, DEFAULT_RETRY, type DeliveryDeclaration, type DeliveryMechanism, type DeliveryOutcome, type DriftFinding, type DriftReport, EXPIRES_AT_UNITS, type ExpiresAtUnit, FAKE_EPOCH, type FailureKind, type FakeRequest, type FakeValues, type HmacScheme, type HmacVerificationSpec, type HostEnvironment, type IdempotencyOptions, type InboundDelivery, type LeaseAction, type LeaseDeclaration, type LeaseState, MAX_IDEMPOTENCY_KEY_LENGTH, type MetricDescriptor, type MetricSample, type MimeAttachment, type MimeHeader, type MimeMessage, type MimePart, type ModeInputs, NO_IDEMPOTENCY_KEY_WARNING, PROBLEM_SEVERITIES, type PreparedRequest, type ProbeOutcome, type ProbeReport, type ProbeResult, type ProbeSpec, type Problem, type ProviderAdapter, RENDERER_VERSION, REVIEW_STALE_DAYS, type RecordedShape, type RenderRules, type RenderedPayload, type RequestedMode, type ResolveOptions, type ResolvedConnection, type RetryPolicy, type RunIdentity, SANDBOX_KINDS, SUPPORTED_CONNECTOR_API, type SandboxKind, type Segment, type ServiceDescriptor, type SetupStep, type ShapeMismatch, type SharedTokenScheme, type SharedTokenVerificationSpec, type SpecSource, type SubscriptionLease, type TextUnit, type Transport, type TransportResponse, type TriggerDescriptor, type VerifyResult, type WebhookVerification, type WebhookVerificationSpec, assertConnectorApi, byteRangeOf, callConnector, capabilityProblems, checkAgainstOpenApi, checkAgainstRecordedShape, classifyError, classifyHttp, classifyStatus, classifyThrown, compareShape, connectionsFor, constantTimeEquals, deliver, fakeRequest, fakeValues, getConnectionHost, graphemes, handshakeResponse, header, hmac, httpFailure, idempotencyKeyFor, isEmptyPayload, isRemote, isSharedTokenScheme, isSharedTokenSpec, isUnconditionallyRetryable, leaseAction, leaseFromResponse, leaseRenewAt, leaseState, linkRanges, measure, parseMime, payloadHash, postChain, probe, providerProblems, providerSandboxIsSelectable, registerConnectionHost, registerTransport, render, reported, resetRateState, resolveConnection, resolveConnectorMode, respectRate, runIdentityFrom, runKeyFrom, runProbes, sandboxIsSelectable, sandboxRefusal, satisfiesMinimum, secretKeyBytes, seedFrom, shapeOf, shouldRetry, sliceByteRange, splitToFit, subscriptionLease, triggerEvent, unchecked, utf8Length, verifyDelivery, verifyHmac, verifySharedToken, withResolvedLimit };
package/dist/index.js CHANGED
@@ -1557,12 +1557,35 @@ var PROBLEM_SEVERITIES = ["block", "warn"];
1557
1557
  var CANONICAL_METRICS = ["like", "share", "reply", "quote", "view"];
1558
1558
 
1559
1559
  // src/webhook.ts
1560
+ function secretKeyBytes(secret, options = {}) {
1561
+ let material = secret;
1562
+ if (options.secretPrefix !== void 0) {
1563
+ if (!material.startsWith(options.secretPrefix)) {
1564
+ return { reason: `signing secret does not start with ${JSON.stringify(options.secretPrefix)}` };
1565
+ }
1566
+ material = material.slice(options.secretPrefix.length);
1567
+ }
1568
+ if ((options.secretEncoding ?? "utf8") === "base64") {
1569
+ if (!/^[A-Za-z0-9+/]*={0,2}$/.test(material) || material.length % 4 !== 0) {
1570
+ return { reason: "signing secret is not valid base64" };
1571
+ }
1572
+ try {
1573
+ return { bytes: Uint8Array.from(atob(material), (c) => c.charCodeAt(0)) };
1574
+ } catch {
1575
+ return { reason: "signing secret is not valid base64" };
1576
+ }
1577
+ }
1578
+ return { bytes: new TextEncoder().encode(material) };
1579
+ }
1560
1580
  async function verifyHmac(options) {
1561
- const { raw, signature, secret, scheme, timestamp } = options;
1581
+ const { raw, secret, scheme, timestamp, id } = options;
1582
+ const signatures = (Array.isArray(options.signature) ? options.signature : [options.signature]).filter(
1583
+ (s) => typeof s === "string" && s !== ""
1584
+ );
1562
1585
  if (!secret) {
1563
1586
  return { ok: false, reason: "no signing secret configured for this trigger" };
1564
1587
  }
1565
- if (!signature) return { ok: false, reason: "delivery carried no signature header" };
1588
+ if (signatures.length === 0) return { ok: false, reason: "delivery carried no signature header" };
1566
1589
  if (scheme.tolerance !== void 0) {
1567
1590
  if (!timestamp) return { ok: false, reason: "delivery carried no timestamp header" };
1568
1591
  const sent = Number(timestamp);
@@ -1572,20 +1595,24 @@ async function verifyHmac(options) {
1572
1595
  return { ok: false, reason: `delivery is outside the ${scheme.tolerance}s replay window` };
1573
1596
  }
1574
1597
  }
1575
- const expected = await hmac(secret, scheme.payload(raw, timestamp), scheme.algorithm, scheme.encoding ?? "hex");
1576
- return constantTimeEquals(expected, signature) ? { ok: true } : { ok: false, reason: "signature did not match" };
1598
+ const key = secretKeyBytes(secret, scheme);
1599
+ if ("reason" in key) return { ok: false, reason: key.reason };
1600
+ const expected = await hmac(secret, scheme.payload(raw, timestamp, id), scheme.algorithm, scheme.encoding ?? "hex", scheme);
1601
+ return signatures.some((candidate) => constantTimeEquals(expected, candidate)) ? { ok: true } : { ok: false, reason: "signature did not match" };
1577
1602
  }
1578
- async function hmac(secret, payload, algorithm, encoding = "hex") {
1603
+ async function hmac(secret, payload, algorithm, encoding = "hex", options = {}) {
1579
1604
  const subtle = globalThis.crypto?.subtle;
1580
1605
  if (!subtle) {
1581
1606
  throw new Error(
1582
1607
  "WebCrypto is not available in this runtime, so webhook signatures cannot be verified. Refusing to accept deliveries unverified."
1583
1608
  );
1584
1609
  }
1610
+ const material = secretKeyBytes(secret, options);
1611
+ if ("reason" in material) throw new Error(material.reason);
1585
1612
  const encoder2 = new TextEncoder();
1586
1613
  const key = await subtle.importKey(
1587
1614
  "raw",
1588
- encoder2.encode(secret),
1615
+ material.bytes,
1589
1616
  { name: "HMAC", hash: algorithm },
1590
1617
  false,
1591
1618
  ["sign"]
@@ -1676,15 +1703,25 @@ async function verifyDelivery(trigger, delivery, secret, now) {
1676
1703
  if (isSharedTokenSpec(spec)) {
1677
1704
  return verifySharedToken({ raw: delivery.raw, headers: delivery.headers, secret, scheme: spec.scheme });
1678
1705
  }
1706
+ const kind = spec.scheme.kind ?? "hmac";
1707
+ if (kind !== "hmac") {
1708
+ return {
1709
+ ok: false,
1710
+ reason: `${trigger.service}.${trigger.operation} declares a verification scheme "${kind}" this runtime does not know \u2014 refusing rather than guessing.`
1711
+ };
1712
+ }
1679
1713
  const rawHeader = header2(delivery.headers, spec.signatureHeader);
1680
1714
  const parsed = spec.parse && rawHeader ? spec.parse(rawHeader) : { signature: rawHeader };
1681
1715
  const timestamp = parsed.timestamp ?? (spec.timestampHeader ? header2(delivery.headers, spec.timestampHeader) : void 0);
1716
+ const id = spec.idHeader ? header2(delivery.headers, spec.idHeader) : void 0;
1717
+ if (spec.idHeader && !id) return { ok: false, reason: "delivery carried no id header" };
1682
1718
  return verifyHmac({
1683
1719
  raw: delivery.raw,
1684
- signature: parsed.signature,
1720
+ signature: parsed.signatures ?? parsed.signature,
1685
1721
  secret,
1686
1722
  scheme: spec.scheme,
1687
1723
  timestamp,
1724
+ id,
1688
1725
  ...now === void 0 ? {} : { now }
1689
1726
  });
1690
1727
  }
@@ -1788,6 +1825,7 @@ export {
1788
1825
  sandboxIsSelectable,
1789
1826
  sandboxRefusal,
1790
1827
  satisfiesMinimum,
1828
+ secretKeyBytes,
1791
1829
  seedFrom,
1792
1830
  shapeOf,
1793
1831
  shouldRetry,