@particle-academy/fancy-connector-core 0.11.0 → 0.13.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/CHANGELOG.md +74 -0
- package/dist/index.cjs +376 -136
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +316 -98
- package/dist/index.d.ts +316 -98
- package/dist/index.js +365 -136
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -1474,6 +1474,58 @@ declare function idempotencyKeyFor(ctx: unknown, nodeId: string, options?: Idemp
|
|
|
1474
1474
|
*/
|
|
1475
1475
|
declare const NO_IDEMPOTENCY_KEY_WARNING: string;
|
|
1476
1476
|
|
|
1477
|
+
/**
|
|
1478
|
+
* RS256 JWT verification against keys the HOST supplies.
|
|
1479
|
+
*
|
|
1480
|
+
* Gmail's Pub/Sub push carries an OIDC token, and a Gmail add-on's requests
|
|
1481
|
+
* carry a Google-signed one; both are RS256 JWS compact tokens. This verifies
|
|
1482
|
+
* one. It NEVER fetches a key set: nothing in this package contacts a URL of
|
|
1483
|
+
* its own, so the host fetches and caches the provider's JWKS and passes the
|
|
1484
|
+
* keys in. The PHP twin is `Jwt::verify`, and both runtimes read
|
|
1485
|
+
* `fixtures/jwt/cases.json`, led by RFC 7515 Appendix A.2.
|
|
1486
|
+
*
|
|
1487
|
+
* Every rule fails CLOSED, and each is a known way JWT verification goes wrong:
|
|
1488
|
+
* - RS256 only. `none` and `HS256` are refused BY NAME before anything else;
|
|
1489
|
+
* HS256 keyed with the public key is the classic confusion attack.
|
|
1490
|
+
* - A `kid` must name a supplied key. An unknown kid is refused, never
|
|
1491
|
+
* "try every key".
|
|
1492
|
+
* - `exp` is REQUIRED: a token that never expires is refused. `nbf` is honoured.
|
|
1493
|
+
* Both allow `leeway` seconds of clock skew (default 60).
|
|
1494
|
+
* - The audience is REQUIRED. A caller that deliberately checks none must pass
|
|
1495
|
+
* `audience: null`, so forgetting it cannot happen by omission.
|
|
1496
|
+
* - The issuer is matched when one is given.
|
|
1497
|
+
*/
|
|
1498
|
+
/** An RSA public key as a JWKS lists it. Extra members are ignored. */
|
|
1499
|
+
type Jwk = {
|
|
1500
|
+
kty: string;
|
|
1501
|
+
n?: string;
|
|
1502
|
+
e?: string;
|
|
1503
|
+
kid?: string;
|
|
1504
|
+
alg?: string;
|
|
1505
|
+
use?: string;
|
|
1506
|
+
};
|
|
1507
|
+
type JwtVerification = {
|
|
1508
|
+
ok: true;
|
|
1509
|
+
header: Record<string, unknown>;
|
|
1510
|
+
claims: Record<string, unknown>;
|
|
1511
|
+
} | {
|
|
1512
|
+
ok: false;
|
|
1513
|
+
reason: string;
|
|
1514
|
+
};
|
|
1515
|
+
type VerifyJwtOptions = {
|
|
1516
|
+
keys: readonly Jwk[];
|
|
1517
|
+
/** REQUIRED: the expected `aud` (any of a list), or `null` to check none ON PURPOSE. */
|
|
1518
|
+
audience: string | readonly string[] | null;
|
|
1519
|
+
issuer?: string | readonly string[];
|
|
1520
|
+
/** Seconds since the epoch. Injected so tests are not clock-dependent. */
|
|
1521
|
+
now?: number;
|
|
1522
|
+
/** Allowed clock skew for exp and nbf, in seconds. */
|
|
1523
|
+
leeway?: number;
|
|
1524
|
+
};
|
|
1525
|
+
/** The algorithms accepted. Closed, and shipped as data. */
|
|
1526
|
+
declare const JWT_ALGORITHMS: readonly ["RS256"];
|
|
1527
|
+
declare function verifyJwt(token: string, options: VerifyJwtOptions): Promise<JwtVerification>;
|
|
1528
|
+
|
|
1477
1529
|
/**
|
|
1478
1530
|
* A subscription that EXPIRES, and when the host must act on it.
|
|
1479
1531
|
*
|
|
@@ -2319,105 +2371,15 @@ declare function decodeFormFieldJson(raw: Uint8Array | string, field: string): u
|
|
|
2319
2371
|
* then calls `decodeDelivery` with the declaration, then starts the run.
|
|
2320
2372
|
* Shipped as data too, for a host mirroring the union across a JSON boundary.
|
|
2321
2373
|
*/
|
|
2322
|
-
declare const DELIVERY_DECODE_KINDS: readonly ["form-field-json"];
|
|
2374
|
+
declare const DELIVERY_DECODE_KINDS: readonly ["form-field-json", "pubsub-push"];
|
|
2323
2375
|
type DeliveryDecode = {
|
|
2324
|
-
kind:
|
|
2376
|
+
kind: "form-field-json";
|
|
2325
2377
|
field: string;
|
|
2378
|
+
} | {
|
|
2379
|
+
kind: "pubsub-push";
|
|
2326
2380
|
};
|
|
2327
2381
|
declare function decodeDelivery(decode: DeliveryDecode, raw: Uint8Array | string): unknown;
|
|
2328
2382
|
|
|
2329
|
-
/**
|
|
2330
|
-
* Dry-verify — proving the transport with a credential that cannot work.
|
|
2331
|
-
*
|
|
2332
|
-
* ## The discipline, and why it beats a fake server
|
|
2333
|
-
*
|
|
2334
|
-
* A connector written from documentation can be wrong in three ways that look
|
|
2335
|
-
* identical until a real credential arrives:
|
|
2336
|
-
*
|
|
2337
|
-
* 1. the URL is wrong,
|
|
2338
|
-
* 2. the request shape is wrong,
|
|
2339
|
-
* 3. the error handling never runs.
|
|
2340
|
-
*
|
|
2341
|
-
* **All three are invisible to a test with a fake server**, because a fake
|
|
2342
|
-
* server agrees with whatever the code does. That is the single most important
|
|
2343
|
-
* thing this module encodes: a green unit suite against a mock proves the code
|
|
2344
|
-
* is consistent with itself.
|
|
2345
|
-
*
|
|
2346
|
-
* So a probe calls the REAL API with a deliberately invalid credential and
|
|
2347
|
-
* requires an **auth-shaped refusal**. That proves the host resolved, the path
|
|
2348
|
-
* exists, the method was accepted, and a failure was recognised as a failure.
|
|
2349
|
-
*
|
|
2350
|
-
* ## What counts as a pass, precisely
|
|
2351
|
-
*
|
|
2352
|
-
* An auth-shaped refusal is `401`, `403`, or a `404` on a provider that answers
|
|
2353
|
-
* 404 for an unknown credential-in-the-path (a Discord webhook does exactly
|
|
2354
|
-
* this). The provider declares which, because guessing here would let a genuine
|
|
2355
|
-
* "this endpoint moved" 404 read as success — the one outcome a probe exists to
|
|
2356
|
-
* catch.
|
|
2357
|
-
*
|
|
2358
|
-
* ## Offline is SKIPPED, never failed
|
|
2359
|
-
*
|
|
2360
|
-
* A check that goes red on a train gets ignored, and then it is worth nothing
|
|
2361
|
-
* when it goes red for real. Unreachable is reported as skipped with the reason,
|
|
2362
|
-
* and the assertions that need no network still run.
|
|
2363
|
-
*
|
|
2364
|
-
* ## And nothing is ever sent
|
|
2365
|
-
*
|
|
2366
|
-
* Every probe is a READ. A probe that wrote something would be a send, and sends
|
|
2367
|
-
* are the host's to gate.
|
|
2368
|
-
*/
|
|
2369
|
-
|
|
2370
|
-
type ProbeOutcome = "pass" | "fail" | "skip";
|
|
2371
|
-
type ProbeResult = {
|
|
2372
|
-
connector: string;
|
|
2373
|
-
outcome: ProbeOutcome;
|
|
2374
|
-
/** What happened, in the provider's own words where possible. */
|
|
2375
|
-
detail: string;
|
|
2376
|
-
status?: number;
|
|
2377
|
-
kind?: FailureKind;
|
|
2378
|
-
};
|
|
2379
|
-
type ProbeSpec = {
|
|
2380
|
-
/** Which connector this probes. */
|
|
2381
|
-
connector: string;
|
|
2382
|
-
/**
|
|
2383
|
-
* The read-only request, fully formed, carrying a credential that cannot be
|
|
2384
|
-
* valid. Built by the connector so the probe exercises the connector's own URL
|
|
2385
|
-
* and auth placement rather than a second copy of them.
|
|
2386
|
-
*/
|
|
2387
|
-
request: () => Promise<{
|
|
2388
|
-
status: number;
|
|
2389
|
-
body: string;
|
|
2390
|
-
}>;
|
|
2391
|
-
/**
|
|
2392
|
-
* Statuses that count as an auth-shaped refusal for THIS provider.
|
|
2393
|
-
*
|
|
2394
|
-
* Declared rather than assumed. Discord answers `404` for an unknown webhook
|
|
2395
|
-
* id, and on a provider that does not, a 404 means the endpoint moved — the
|
|
2396
|
-
* exact drift a probe is for.
|
|
2397
|
-
*/
|
|
2398
|
-
authStatuses: number[];
|
|
2399
|
-
/** Why those statuses, for whoever reads a failure at 3am. */
|
|
2400
|
-
why: string;
|
|
2401
|
-
};
|
|
2402
|
-
/**
|
|
2403
|
-
* Run one probe.
|
|
2404
|
-
*
|
|
2405
|
-
* Never throws. A probe that threw would take the suite down with it, and the
|
|
2406
|
-
* suite's whole value is that it runs everywhere including on a laptop with no
|
|
2407
|
-
* network.
|
|
2408
|
-
*/
|
|
2409
|
-
declare function probe(spec: ProbeSpec): Promise<ProbeResult>;
|
|
2410
|
-
type ProbeReport = {
|
|
2411
|
-
results: ProbeResult[];
|
|
2412
|
-
passed: number;
|
|
2413
|
-
failed: number;
|
|
2414
|
-
skipped: number;
|
|
2415
|
-
/** True when nothing failed. Skips do not fail a report. */
|
|
2416
|
-
ok: boolean;
|
|
2417
|
-
};
|
|
2418
|
-
/** Run a set of probes and summarise. Sequential: probes hit real providers. */
|
|
2419
|
-
declare function runProbes(specs: ProbeSpec[]): Promise<ProbeReport>;
|
|
2420
|
-
|
|
2421
2383
|
/**
|
|
2422
2384
|
* Verifying an inbound provider webhook.
|
|
2423
2385
|
*
|
|
@@ -2468,7 +2430,7 @@ type HmacScheme = {
|
|
|
2468
2430
|
* `v0:${timestamp}:${body}`, GitHub signs the body alone, and Svix signs
|
|
2469
2431
|
* `${id}.${timestamp}.${body}` where `id` is the delivery's own id header.
|
|
2470
2432
|
*/
|
|
2471
|
-
payload: (raw: string, timestamp?: string, id?: string) => string;
|
|
2433
|
+
payload: (raw: string, timestamp?: string, id?: string, request?: SignedRequestLine) => string;
|
|
2472
2434
|
/** Seconds a delivery stays acceptable. */
|
|
2473
2435
|
tolerance?: number;
|
|
2474
2436
|
/** Encoding of the signature the provider sends. */
|
|
@@ -2483,7 +2445,37 @@ type HmacScheme = {
|
|
|
2483
2445
|
secretEncoding?: "utf8" | "base64";
|
|
2484
2446
|
/** A prefix to strip before decoding (`whsec_`). Its absence is a refusal, never a guess. */
|
|
2485
2447
|
secretPrefix?: string;
|
|
2448
|
+
/**
|
|
2449
|
+
* The unit the timestamp header is in. Seconds unless said otherwise:
|
|
2450
|
+
* HubSpot sends MILLISECONDS, and comparing those with a window in seconds
|
|
2451
|
+
* refuses every delivery ever made.
|
|
2452
|
+
*/
|
|
2453
|
+
timestampUnit?: "s" | "ms";
|
|
2454
|
+
/**
|
|
2455
|
+
* The provider signs the REQUEST LINE too (HubSpot v3:
|
|
2456
|
+
* method + uri + body + timestamp). A delivery the host passed without its
|
|
2457
|
+
* method or URL is refused by name, never signed with a gap.
|
|
2458
|
+
*/
|
|
2459
|
+
signsRequestLine?: boolean;
|
|
2460
|
+
/** How the signed URI is spelled from the URL the request was sent to. */
|
|
2461
|
+
uri?: SignedUriRule;
|
|
2462
|
+
};
|
|
2463
|
+
/** The URI rules a provider's signature can follow. Closed: each is a decision. */
|
|
2464
|
+
declare const SIGNED_URI_RULES: readonly ["hubspot-v3"];
|
|
2465
|
+
type SignedUriRule = (typeof SIGNED_URI_RULES)[number];
|
|
2466
|
+
/** The request line as signed: the method, and the URI after the scheme's rule. */
|
|
2467
|
+
type SignedRequestLine = {
|
|
2468
|
+
method: string;
|
|
2469
|
+
uri: string;
|
|
2486
2470
|
};
|
|
2471
|
+
/**
|
|
2472
|
+
* The URI a provider signs, from the URL the request arrived on.
|
|
2473
|
+
*
|
|
2474
|
+
* `hubspot-v3`: only the QUERY portion is percent-decoded, and only for twelve
|
|
2475
|
+
* escapes (case-insensitive); never its leading `?`; any `#fragment` dropped.
|
|
2476
|
+
* Everything else, `%26` and `%3D` included, stays as sent.
|
|
2477
|
+
*/
|
|
2478
|
+
declare function normaliseSignedUri(url: string, rule: SignedUriRule): string;
|
|
2487
2479
|
/** The HMAC key a scheme's secret spells, or the reason it cannot be one. */
|
|
2488
2480
|
declare function secretKeyBytes(secret: string, options?: {
|
|
2489
2481
|
secretEncoding?: "utf8" | "base64";
|
|
@@ -2516,6 +2508,11 @@ declare function verifyHmac(options: {
|
|
|
2516
2508
|
timestamp?: string;
|
|
2517
2509
|
/** The delivery's id, when the scheme signs one (Svix's `svix-id`). */
|
|
2518
2510
|
id?: string;
|
|
2511
|
+
/** The request line as RECEIVED, when the scheme signs it (HubSpot v3). */
|
|
2512
|
+
request?: {
|
|
2513
|
+
method: string;
|
|
2514
|
+
url: string;
|
|
2515
|
+
};
|
|
2519
2516
|
/** Seconds since the epoch. Injected so tests are not clock-dependent. */
|
|
2520
2517
|
now?: number;
|
|
2521
2518
|
}): Promise<WebhookVerification>;
|
|
@@ -2598,6 +2595,195 @@ declare function handshakeResponse(handshake: ChallengeHandshake | undefined, qu
|
|
|
2598
2595
|
*/
|
|
2599
2596
|
declare function header(headers: Record<string, string | string[] | undefined>, name: string): string | undefined;
|
|
2600
2597
|
|
|
2598
|
+
/**
|
|
2599
|
+
* A delivery authenticated by an OIDC BEARER TOKEN rather than a signature over
|
|
2600
|
+
* its body: Google Cloud Pub/Sub push, which carries Gmail's `users.watch`
|
|
2601
|
+
* notifications, sends `Authorization: Bearer <JWT>` signed by Google.
|
|
2602
|
+
*
|
|
2603
|
+
* The PHP twin is `OidcDelivery::verify`; both read
|
|
2604
|
+
* `fixtures/oidc-delivery/cases.json`. The token itself is checked by
|
|
2605
|
+
* `verifyJwt`, so every JWT rule there applies here unchanged.
|
|
2606
|
+
*
|
|
2607
|
+
* What this adds, and why each piece fails closed:
|
|
2608
|
+
*
|
|
2609
|
+
* - **The audience is REQUIRED.** Google signs a token for any push
|
|
2610
|
+
* subscription anyone creates; without the audience, every Google-signed
|
|
2611
|
+
* token in the world verifies.
|
|
2612
|
+
* - **The principal is REQUIRED, and it is the check people skip.** A
|
|
2613
|
+
* stranger can create a push subscription in their OWN project, aimed at
|
|
2614
|
+
* your public URL, with your URL as its audience: Google signs that token
|
|
2615
|
+
* correctly. Only the service account (`email`) says it was yours.
|
|
2616
|
+
* Its verified flag (`email_verified`) must be the literal `true`.
|
|
2617
|
+
* - **The keys are the host's.** Nothing here fetches Google's JWKS: the host
|
|
2618
|
+
* fetches and caches it and passes the keys in.
|
|
2619
|
+
*
|
|
2620
|
+
* A host's own configuration is checked before the delivery is read, so a
|
|
2621
|
+
* misconfigured trigger refuses every push rather than accepting any of them.
|
|
2622
|
+
*/
|
|
2623
|
+
|
|
2624
|
+
declare const OIDC_JWT_SCHEME_KIND: "oidc-jwt";
|
|
2625
|
+
type OidcJwtScheme = {
|
|
2626
|
+
kind: typeof OIDC_JWT_SCHEME_KIND;
|
|
2627
|
+
/** The header carrying `Bearer <token>`. */
|
|
2628
|
+
header: string;
|
|
2629
|
+
/** Accepted `iss` values. Google documents two spellings. */
|
|
2630
|
+
issuers: readonly string[];
|
|
2631
|
+
/** The claim naming WHO the token was minted for: Google push uses `email`. */
|
|
2632
|
+
principalClaim: string;
|
|
2633
|
+
/** A claim that must be the literal `true` beside it: `email_verified`. */
|
|
2634
|
+
verifiedClaim?: string;
|
|
2635
|
+
};
|
|
2636
|
+
/** What the HOST configured for this trigger. Never in the package. */
|
|
2637
|
+
type OidcExpectation = {
|
|
2638
|
+
keys: readonly Jwk[];
|
|
2639
|
+
/** The audience the subscription was created with. */
|
|
2640
|
+
audience: string | null;
|
|
2641
|
+
/** The service account the subscription pushes as. */
|
|
2642
|
+
principal: string | null;
|
|
2643
|
+
};
|
|
2644
|
+
declare function isOidcJwtScheme(scheme: unknown): scheme is OidcJwtScheme;
|
|
2645
|
+
declare function verifyOidcDelivery(options: {
|
|
2646
|
+
headers: Record<string, string | string[] | undefined>;
|
|
2647
|
+
scheme: OidcJwtScheme;
|
|
2648
|
+
expected: OidcExpectation | undefined;
|
|
2649
|
+
/** Seconds since the epoch. */
|
|
2650
|
+
now?: number;
|
|
2651
|
+
}): Promise<WebhookVerification>;
|
|
2652
|
+
|
|
2653
|
+
/**
|
|
2654
|
+
* Dry-verify — proving the transport with a credential that cannot work.
|
|
2655
|
+
*
|
|
2656
|
+
* ## The discipline, and why it beats a fake server
|
|
2657
|
+
*
|
|
2658
|
+
* A connector written from documentation can be wrong in three ways that look
|
|
2659
|
+
* identical until a real credential arrives:
|
|
2660
|
+
*
|
|
2661
|
+
* 1. the URL is wrong,
|
|
2662
|
+
* 2. the request shape is wrong,
|
|
2663
|
+
* 3. the error handling never runs.
|
|
2664
|
+
*
|
|
2665
|
+
* **All three are invisible to a test with a fake server**, because a fake
|
|
2666
|
+
* server agrees with whatever the code does. That is the single most important
|
|
2667
|
+
* thing this module encodes: a green unit suite against a mock proves the code
|
|
2668
|
+
* is consistent with itself.
|
|
2669
|
+
*
|
|
2670
|
+
* So a probe calls the REAL API with a deliberately invalid credential and
|
|
2671
|
+
* requires an **auth-shaped refusal**. That proves the host resolved, the path
|
|
2672
|
+
* exists, the method was accepted, and a failure was recognised as a failure.
|
|
2673
|
+
*
|
|
2674
|
+
* ## What counts as a pass, precisely
|
|
2675
|
+
*
|
|
2676
|
+
* An auth-shaped refusal is `401`, `403`, or a `404` on a provider that answers
|
|
2677
|
+
* 404 for an unknown credential-in-the-path (a Discord webhook does exactly
|
|
2678
|
+
* this). The provider declares which, because guessing here would let a genuine
|
|
2679
|
+
* "this endpoint moved" 404 read as success — the one outcome a probe exists to
|
|
2680
|
+
* catch.
|
|
2681
|
+
*
|
|
2682
|
+
* ## Offline is SKIPPED, never failed
|
|
2683
|
+
*
|
|
2684
|
+
* A check that goes red on a train gets ignored, and then it is worth nothing
|
|
2685
|
+
* when it goes red for real. Unreachable is reported as skipped with the reason,
|
|
2686
|
+
* and the assertions that need no network still run.
|
|
2687
|
+
*
|
|
2688
|
+
* ## And nothing is ever sent
|
|
2689
|
+
*
|
|
2690
|
+
* Every probe is a READ. A probe that wrote something would be a send, and sends
|
|
2691
|
+
* are the host's to gate.
|
|
2692
|
+
*/
|
|
2693
|
+
|
|
2694
|
+
type ProbeOutcome = "pass" | "fail" | "skip";
|
|
2695
|
+
type ProbeResult = {
|
|
2696
|
+
connector: string;
|
|
2697
|
+
outcome: ProbeOutcome;
|
|
2698
|
+
/** What happened, in the provider's own words where possible. */
|
|
2699
|
+
detail: string;
|
|
2700
|
+
status?: number;
|
|
2701
|
+
kind?: FailureKind;
|
|
2702
|
+
};
|
|
2703
|
+
type ProbeSpec = {
|
|
2704
|
+
/** Which connector this probes. */
|
|
2705
|
+
connector: string;
|
|
2706
|
+
/**
|
|
2707
|
+
* The read-only request, fully formed, carrying a credential that cannot be
|
|
2708
|
+
* valid. Built by the connector so the probe exercises the connector's own URL
|
|
2709
|
+
* and auth placement rather than a second copy of them.
|
|
2710
|
+
*/
|
|
2711
|
+
request: () => Promise<{
|
|
2712
|
+
status: number;
|
|
2713
|
+
body: string;
|
|
2714
|
+
}>;
|
|
2715
|
+
/**
|
|
2716
|
+
* Statuses that count as an auth-shaped refusal for THIS provider.
|
|
2717
|
+
*
|
|
2718
|
+
* Declared rather than assumed. Discord answers `404` for an unknown webhook
|
|
2719
|
+
* id, and on a provider that does not, a 404 means the endpoint moved — the
|
|
2720
|
+
* exact drift a probe is for.
|
|
2721
|
+
*/
|
|
2722
|
+
authStatuses: number[];
|
|
2723
|
+
/** Why those statuses, for whoever reads a failure at 3am. */
|
|
2724
|
+
why: string;
|
|
2725
|
+
};
|
|
2726
|
+
/**
|
|
2727
|
+
* Run one probe.
|
|
2728
|
+
*
|
|
2729
|
+
* Never throws. A probe that threw would take the suite down with it, and the
|
|
2730
|
+
* suite's whole value is that it runs everywhere including on a laptop with no
|
|
2731
|
+
* network.
|
|
2732
|
+
*/
|
|
2733
|
+
declare function probe(spec: ProbeSpec): Promise<ProbeResult>;
|
|
2734
|
+
type ProbeReport = {
|
|
2735
|
+
results: ProbeResult[];
|
|
2736
|
+
passed: number;
|
|
2737
|
+
failed: number;
|
|
2738
|
+
skipped: number;
|
|
2739
|
+
/** True when nothing failed. Skips do not fail a report. */
|
|
2740
|
+
ok: boolean;
|
|
2741
|
+
};
|
|
2742
|
+
/** Run a set of probes and summarise. Sequential: probes hit real providers. */
|
|
2743
|
+
declare function runProbes(specs: ProbeSpec[]): Promise<ProbeReport>;
|
|
2744
|
+
|
|
2745
|
+
/**
|
|
2746
|
+
* A Google Cloud Pub/Sub PUSH body, decoded to the message it carries: the
|
|
2747
|
+
* payload TRANSFORM Gmail's `users.watch` notifications need. Pub/Sub wraps
|
|
2748
|
+
* each message in an envelope and base64-encodes its `data`.
|
|
2749
|
+
*
|
|
2750
|
+
* ONE implementation in two runtimes (`php/src/PubSubPush.php` is the twin),
|
|
2751
|
+
* held to the authored corpus under `fixtures/pubsub-push/`, whose README
|
|
2752
|
+
* names the decision each case pins. It runs AFTER verification: a push is
|
|
2753
|
+
* authenticated by the OIDC token in its `Authorization` header, not by
|
|
2754
|
+
* anything in the body.
|
|
2755
|
+
*
|
|
2756
|
+
* The decisions:
|
|
2757
|
+
*
|
|
2758
|
+
* - `data` may be standard base64 or base64url, padded or not. The Gmail guide
|
|
2759
|
+
* says one, Pub/Sub's page the other. Anything outside both alphabets, and
|
|
2760
|
+
* bytes that are not UTF-8, are `bad-encoding`.
|
|
2761
|
+
* - `data` is parsed as JSON, and text that is not JSON is `data-not-json`,
|
|
2762
|
+
* never the raw string.
|
|
2763
|
+
* - `messageId` / `publishTime` are read camelCase first, snake_case second:
|
|
2764
|
+
* Google sends both spellings.
|
|
2765
|
+
* - Absent `attributes` is `{}` (Pub/Sub omits an empty map); absent
|
|
2766
|
+
* `deliveryAttempt` is `null` (it is only sent under a dead-letter policy,
|
|
2767
|
+
* and an attempt count nobody reported is not attempt one).
|
|
2768
|
+
*/
|
|
2769
|
+
declare const PUBSUB_PUSH_REASONS: readonly ["not-json", "missing-message", "missing-data", "bad-encoding", "data-not-json"];
|
|
2770
|
+
type PubSubPushReason = (typeof PUBSUB_PUSH_REASONS)[number];
|
|
2771
|
+
/** Refused BY NAME; `reason` is the same string PHP's exception carries. */
|
|
2772
|
+
declare class PubSubPushError extends Error {
|
|
2773
|
+
readonly reason: PubSubPushReason;
|
|
2774
|
+
constructor(reason: PubSubPushReason, message: string);
|
|
2775
|
+
}
|
|
2776
|
+
type PubSubPushMessage = {
|
|
2777
|
+
subscription: string | null;
|
|
2778
|
+
messageId: string | null;
|
|
2779
|
+
publishTime: string | null;
|
|
2780
|
+
attributes: Record<string, string>;
|
|
2781
|
+
deliveryAttempt: number | null;
|
|
2782
|
+
/** The message's `data`, base64-decoded and parsed as JSON. */
|
|
2783
|
+
data: unknown;
|
|
2784
|
+
};
|
|
2785
|
+
declare function decodePubSubPush(raw: Uint8Array | string): PubSubPushMessage;
|
|
2786
|
+
|
|
2601
2787
|
/**
|
|
2602
2788
|
* How a connector trigger learns that something happened.
|
|
2603
2789
|
*
|
|
@@ -2627,6 +2813,11 @@ declare function header(headers: Record<string, string | string[] | undefined>,
|
|
|
2627
2813
|
* SSE, CometD).
|
|
2628
2814
|
*/
|
|
2629
2815
|
type DeliveryMechanism = "webhook" | "subscription" | "poll" | "pubsub" | "stream";
|
|
2816
|
+
/** See `TriggerDescriptor.route`. `path` is dotted; `[]` means every element. */
|
|
2817
|
+
type DeliveryRoute = {
|
|
2818
|
+
by: string;
|
|
2819
|
+
path: string;
|
|
2820
|
+
};
|
|
2630
2821
|
type TriggerDescriptor = {
|
|
2631
2822
|
service: string;
|
|
2632
2823
|
operation: string;
|
|
@@ -2655,6 +2846,15 @@ type TriggerDescriptor = {
|
|
|
2655
2846
|
* it, over the same raw bytes the signature was checked against.
|
|
2656
2847
|
*/
|
|
2657
2848
|
decode?: DeliveryDecode;
|
|
2849
|
+
/**
|
|
2850
|
+
* For a delivery that reaches ONE endpoint on behalf of many tenants
|
|
2851
|
+
* (HubSpot app webhooks, Gmail's Pub/Sub topic): the ONLY key a host may
|
|
2852
|
+
* route by, and where it sits in the verified payload. The rule that goes
|
|
2853
|
+
* with it is fixed, not configurable: route only AFTER verification, only
|
|
2854
|
+
* to a connection or watch the host minted, and drop an unmapped key with a
|
|
2855
|
+
* 2xx and a log line, creating nothing. The package declares; the HOST routes.
|
|
2856
|
+
*/
|
|
2857
|
+
route?: DeliveryRoute;
|
|
2658
2858
|
/** For `poll`: the provider's smallest sane interval, in seconds. */
|
|
2659
2859
|
minPollSeconds?: number;
|
|
2660
2860
|
/** A faked sample event, so the node is runnable before any of the above. */
|
|
@@ -2697,12 +2897,28 @@ type SharedTokenVerificationSpec = {
|
|
|
2697
2897
|
/** A challenge the provider makes before it will deliver anything. */
|
|
2698
2898
|
handshake?: ChallengeHandshake;
|
|
2699
2899
|
};
|
|
2700
|
-
|
|
2900
|
+
/**
|
|
2901
|
+
* The provider sends a BEARER TOKEN it signed (Google Cloud Pub/Sub push). The
|
|
2902
|
+
* keys, audience and principal it is checked against are the HOST's, passed
|
|
2903
|
+
* to `verifyDelivery` as an `OidcExpectation`. See `verifyOidcDelivery`.
|
|
2904
|
+
*/
|
|
2905
|
+
type OidcJwtVerificationSpec = {
|
|
2906
|
+
scheme: OidcJwtScheme;
|
|
2907
|
+
};
|
|
2908
|
+
type WebhookVerificationSpec = HmacVerificationSpec | SharedTokenVerificationSpec | OidcJwtVerificationSpec;
|
|
2909
|
+
declare function isOidcJwtSpec(spec: WebhookVerificationSpec): spec is OidcJwtVerificationSpec;
|
|
2701
2910
|
declare function isSharedTokenSpec(spec: WebhookVerificationSpec): spec is SharedTokenVerificationSpec;
|
|
2702
2911
|
type InboundDelivery = {
|
|
2703
2912
|
/** The body EXACTLY as received. Not re-serialised. See `webhook.ts`. */
|
|
2704
2913
|
raw: string;
|
|
2705
2914
|
headers: Record<string, string | string[] | undefined>;
|
|
2915
|
+
/**
|
|
2916
|
+
* The request line as RECEIVED, for a provider that signs it (HubSpot v3).
|
|
2917
|
+
* Pass the full URL the provider called, scheme, host, path and query,
|
|
2918
|
+
* exactly as it arrived.
|
|
2919
|
+
*/
|
|
2920
|
+
method?: string;
|
|
2921
|
+
url?: string;
|
|
2706
2922
|
};
|
|
2707
2923
|
/**
|
|
2708
2924
|
* Verify an inbound delivery against a trigger's declared scheme.
|
|
@@ -2712,7 +2928,9 @@ type InboundDelivery = {
|
|
|
2712
2928
|
* for starting workflows in your account, and defaulting to "allow" would make
|
|
2713
2929
|
* every misconfiguration into an open door that looks shut.
|
|
2714
2930
|
*/
|
|
2715
|
-
declare function verifyDelivery(trigger: TriggerDescriptor, delivery: InboundDelivery, secret: string | undefined, now?: number
|
|
2931
|
+
declare function verifyDelivery(trigger: TriggerDescriptor, delivery: InboundDelivery, secret: string | undefined, now?: number,
|
|
2932
|
+
/** For an `oidc-jwt` scheme: the keys, audience and principal the host configured. `secret` is not read. */
|
|
2933
|
+
oidc?: OidcExpectation): Promise<WebhookVerification>;
|
|
2716
2934
|
/**
|
|
2717
2935
|
* The event a trigger node publishes when it runs.
|
|
2718
2936
|
*
|
|
@@ -2728,4 +2946,4 @@ declare function verifyDelivery(trigger: TriggerDescriptor, delivery: InboundDel
|
|
|
2728
2946
|
*/
|
|
2729
2947
|
declare function triggerEvent(trigger: TriggerDescriptor, connection: ResolvedConnection, injected: unknown, config: Record<string, unknown>): unknown;
|
|
2730
2948
|
|
|
2731
|
-
export { AMBIGUOUS_REFUSAL, type ApiContract, type Attempt, type Attendee, 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, DELIVERY_DECODE_KINDS, type DeliveryDeclaration, type DeliveryDecode, type DeliveryMechanism, type DeliveryOutcome, type DriftFinding, type DriftReport, EXPIRES_AT_UNITS, type ExpiresAtUnit, FAKE_EPOCH, FORM_FIELD_JSON_REASONS, type FailureKind, type FakeRequest, type FakeValues, FormFieldJsonError, type FormFieldJsonReason, type HmacScheme, type HmacVerificationSpec, type HostEnvironment, type IdempotencyOptions, type InboundDelivery, type Invite, type InviteMethod, type JoinTarget, type JoinTargetKind, 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 RecurrenceRule, 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, decodeDelivery, decodeFormFieldJson, deliver, fakeRequest, fakeValues, getConnectionHost, graphemes, handshakeResponse, header, hmac, httpFailure, idempotencyKeyFor, isEmptyPayload, isRemote, isSharedTokenScheme, isSharedTokenSpec, isUnconditionallyRetryable, joinTargets, leaseAction, leaseFromResponse, leaseRenewAt, leaseState, linkRanges, measure, nextOccurrence, parseInvite, 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 };
|
|
2949
|
+
export { AMBIGUOUS_REFUSAL, type ApiContract, type Attempt, type Attendee, 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, DELIVERY_DECODE_KINDS, type DeliveryDeclaration, type DeliveryDecode, type DeliveryMechanism, type DeliveryOutcome, type DeliveryRoute, type DriftFinding, type DriftReport, EXPIRES_AT_UNITS, type ExpiresAtUnit, FAKE_EPOCH, FORM_FIELD_JSON_REASONS, type FailureKind, type FakeRequest, type FakeValues, FormFieldJsonError, type FormFieldJsonReason, type HmacScheme, type HmacVerificationSpec, type HostEnvironment, type IdempotencyOptions, type InboundDelivery, type Invite, type InviteMethod, JWT_ALGORITHMS, type JoinTarget, type JoinTargetKind, type Jwk, type JwtVerification, 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, OIDC_JWT_SCHEME_KIND, type OidcExpectation, type OidcJwtScheme, type OidcJwtVerificationSpec, PROBLEM_SEVERITIES, PUBSUB_PUSH_REASONS, type PreparedRequest, type ProbeOutcome, type ProbeReport, type ProbeResult, type ProbeSpec, type Problem, type ProviderAdapter, PubSubPushError, type PubSubPushMessage, type PubSubPushReason, RENDERER_VERSION, REVIEW_STALE_DAYS, type RecordedShape, type RecurrenceRule, type RenderRules, type RenderedPayload, type RequestedMode, type ResolveOptions, type ResolvedConnection, type RetryPolicy, type RunIdentity, SANDBOX_KINDS, SIGNED_URI_RULES, SUPPORTED_CONNECTOR_API, type SandboxKind, type Segment, type ServiceDescriptor, type SetupStep, type ShapeMismatch, type SharedTokenScheme, type SharedTokenVerificationSpec, type SignedRequestLine, type SignedUriRule, type SpecSource, type SubscriptionLease, type TextUnit, type Transport, type TransportResponse, type TriggerDescriptor, type VerifyJwtOptions, type VerifyResult, type WebhookVerification, type WebhookVerificationSpec, assertConnectorApi, byteRangeOf, callConnector, capabilityProblems, checkAgainstOpenApi, checkAgainstRecordedShape, classifyError, classifyHttp, classifyStatus, classifyThrown, compareShape, connectionsFor, constantTimeEquals, decodeDelivery, decodeFormFieldJson, decodePubSubPush, deliver, fakeRequest, fakeValues, getConnectionHost, graphemes, handshakeResponse, header, hmac, httpFailure, idempotencyKeyFor, isEmptyPayload, isOidcJwtScheme, isOidcJwtSpec, isRemote, isSharedTokenScheme, isSharedTokenSpec, isUnconditionallyRetryable, joinTargets, leaseAction, leaseFromResponse, leaseRenewAt, leaseState, linkRanges, measure, nextOccurrence, normaliseSignedUri, parseInvite, 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, verifyJwt, verifyOidcDelivery, verifySharedToken, withResolvedLimit };
|