@voyant-travel/utils 0.105.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/LICENSE +201 -0
- package/README.md +42 -0
- package/dist/cache.d.ts +123 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +182 -0
- package/dist/cache.js.map +1 -0
- package/dist/countries.d.ts +5 -0
- package/dist/countries.d.ts.map +1 -0
- package/dist/countries.js +251 -0
- package/dist/countries.js.map +1 -0
- package/dist/csv.d.ts +28 -0
- package/dist/csv.d.ts.map +1 -0
- package/dist/csv.js +44 -0
- package/dist/csv.js.map +1 -0
- package/dist/currencies/part-1.d.ts +408 -0
- package/dist/currencies/part-1.d.ts.map +1 -0
- package/dist/currencies/part-1.js +408 -0
- package/dist/currencies/part-1.js.map +1 -0
- package/dist/currencies/part-2.d.ts +408 -0
- package/dist/currencies/part-2.d.ts.map +1 -0
- package/dist/currencies/part-2.js +408 -0
- package/dist/currencies/part-2.js.map +1 -0
- package/dist/currencies/part-3.d.ts +201 -0
- package/dist/currencies/part-3.d.ts.map +1 -0
- package/dist/currencies/part-3.js +201 -0
- package/dist/currencies/part-3.js.map +1 -0
- package/dist/currencies.d.ts +1011 -0
- package/dist/currencies.d.ts.map +1 -0
- package/dist/currencies.js +9 -0
- package/dist/currencies.js.map +1 -0
- package/dist/geographic-regions.d.ts +66 -0
- package/dist/geographic-regions.d.ts.map +1 -0
- package/dist/geographic-regions.js +326 -0
- package/dist/geographic-regions.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/kms-aws.d.ts +28 -0
- package/dist/kms-aws.d.ts.map +1 -0
- package/dist/kms-aws.js +152 -0
- package/dist/kms-aws.js.map +1 -0
- package/dist/kms-env.d.ts +13 -0
- package/dist/kms-env.d.ts.map +1 -0
- package/dist/kms-env.js +13 -0
- package/dist/kms-env.js.map +1 -0
- package/dist/kms-gcp.d.ts +34 -0
- package/dist/kms-gcp.d.ts.map +1 -0
- package/dist/kms-gcp.js +135 -0
- package/dist/kms-gcp.js.map +1 -0
- package/dist/kms-local.d.ts +13 -0
- package/dist/kms-local.d.ts.map +1 -0
- package/dist/kms-local.js +13 -0
- package/dist/kms-local.js.map +1 -0
- package/dist/kms-symmetric.d.ts +14 -0
- package/dist/kms-symmetric.d.ts.map +1 -0
- package/dist/kms-symmetric.js +67 -0
- package/dist/kms-symmetric.js.map +1 -0
- package/dist/kms-voyant-cloud.d.ts +19 -0
- package/dist/kms-voyant-cloud.d.ts.map +1 -0
- package/dist/kms-voyant-cloud.js +67 -0
- package/dist/kms-voyant-cloud.js.map +1 -0
- package/dist/kms.d.ts +79 -0
- package/dist/kms.d.ts.map +1 -0
- package/dist/kms.js +194 -0
- package/dist/kms.js.map +1 -0
- package/dist/kv.d.ts +10 -0
- package/dist/kv.d.ts.map +1 -0
- package/dist/kv.js +18 -0
- package/dist/kv.js.map +1 -0
- package/dist/languages.d.ts +187 -0
- package/dist/languages.d.ts.map +1 -0
- package/dist/languages.js +187 -0
- package/dist/languages.js.map +1 -0
- package/dist/localized-countries-regions/en-part-1.d.ts +7 -0
- package/dist/localized-countries-regions/en-part-1.d.ts.map +1 -0
- package/dist/localized-countries-regions/en-part-1.js +118 -0
- package/dist/localized-countries-regions/en-part-1.js.map +1 -0
- package/dist/localized-countries-regions/en-part-2.d.ts +7 -0
- package/dist/localized-countries-regions/en-part-2.d.ts.map +1 -0
- package/dist/localized-countries-regions/en-part-2.js +138 -0
- package/dist/localized-countries-regions/en-part-2.js.map +1 -0
- package/dist/localized-countries-regions/en-part-3.d.ts +7 -0
- package/dist/localized-countries-regions/en-part-3.d.ts.map +1 -0
- package/dist/localized-countries-regions/en-part-3.js +101 -0
- package/dist/localized-countries-regions/en-part-3.js.map +1 -0
- package/dist/localized-countries-regions/ro-part-1.d.ts +7 -0
- package/dist/localized-countries-regions/ro-part-1.d.ts.map +1 -0
- package/dist/localized-countries-regions/ro-part-1.js +113 -0
- package/dist/localized-countries-regions/ro-part-1.js.map +1 -0
- package/dist/localized-countries-regions/ro-part-2.d.ts +7 -0
- package/dist/localized-countries-regions/ro-part-2.d.ts.map +1 -0
- package/dist/localized-countries-regions/ro-part-2.js +113 -0
- package/dist/localized-countries-regions/ro-part-2.js.map +1 -0
- package/dist/localized-countries-regions/ro-part-3.d.ts +7 -0
- package/dist/localized-countries-regions/ro-part-3.d.ts.map +1 -0
- package/dist/localized-countries-regions/ro-part-3.js +101 -0
- package/dist/localized-countries-regions/ro-part-3.js.map +1 -0
- package/dist/localized-countries-regions.d.ts +15 -0
- package/dist/localized-countries-regions.d.ts.map +1 -0
- package/dist/localized-countries-regions.js +19 -0
- package/dist/localized-countries-regions.js.map +1 -0
- package/dist/localized-regions.d.ts +33 -0
- package/dist/localized-regions.d.ts.map +1 -0
- package/dist/localized-regions.js +63 -0
- package/dist/localized-regions.js.map +1 -0
- package/dist/pdf-renderer.d.ts +9 -0
- package/dist/pdf-renderer.d.ts.map +1 -0
- package/dist/pdf-renderer.js +139 -0
- package/dist/pdf-renderer.js.map +1 -0
- package/dist/rate-limits.d.ts +39 -0
- package/dist/rate-limits.d.ts.map +1 -0
- package/dist/rate-limits.js +86 -0
- package/dist/rate-limits.js.map +1 -0
- package/dist/resilience.d.ts +81 -0
- package/dist/resilience.d.ts.map +1 -0
- package/dist/resilience.js +156 -0
- package/dist/resilience.js.map +1 -0
- package/dist/session-claims.d.ts +80 -0
- package/dist/session-claims.d.ts.map +1 -0
- package/dist/session-claims.js +205 -0
- package/dist/session-claims.js.map +1 -0
- package/dist/template-renderer.d.ts +2 -0
- package/dist/template-renderer.d.ts.map +1 -0
- package/dist/template-renderer.js +2 -0
- package/dist/template-renderer.js.map +1 -0
- package/dist/temporal.d.ts +2 -0
- package/dist/temporal.d.ts.map +1 -0
- package/dist/temporal.js +6 -0
- package/dist/temporal.js.map +1 -0
- package/dist/timezones/part-1.d.ts +9 -0
- package/dist/timezones/part-1.d.ts.map +1 -0
- package/dist/timezones/part-1.js +344 -0
- package/dist/timezones/part-1.js.map +1 -0
- package/dist/timezones/part-2.d.ts +9 -0
- package/dist/timezones/part-2.d.ts.map +1 -0
- package/dist/timezones/part-2.js +296 -0
- package/dist/timezones/part-2.js.map +1 -0
- package/dist/timezones/part-3.d.ts +9 -0
- package/dist/timezones/part-3.d.ts.map +1 -0
- package/dist/timezones/part-3.js +268 -0
- package/dist/timezones/part-3.js.map +1 -0
- package/dist/timezones/part-4.d.ts +9 -0
- package/dist/timezones/part-4.d.ts.map +1 -0
- package/dist/timezones/part-4.js +245 -0
- package/dist/timezones/part-4.js.map +1 -0
- package/dist/timezones/part-5.d.ts +9 -0
- package/dist/timezones/part-5.d.ts.map +1 -0
- package/dist/timezones/part-5.js +76 -0
- package/dist/timezones/part-5.js.map +1 -0
- package/dist/timezones.d.ts +9 -0
- package/dist/timezones.d.ts.map +1 -0
- package/dist/timezones.js +13 -0
- package/dist/timezones.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/with-timeout.d.ts +2 -0
- package/dist/with-timeout.d.ts.map +1 -0
- package/dist/with-timeout.js +15 -0
- package/dist/with-timeout.js.map +1 -0
- package/package.json +123 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate Limit Utilities
|
|
3
|
+
*
|
|
4
|
+
* Maps subscription plans to API rate limits
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Default rate limits by plan tier
|
|
8
|
+
*
|
|
9
|
+
* These limits apply to API key authentication
|
|
10
|
+
* Based on subscription tier or plan key
|
|
11
|
+
*/
|
|
12
|
+
export const RATE_LIMITS_BY_PLAN = {
|
|
13
|
+
// Free/Starter tier
|
|
14
|
+
free: {
|
|
15
|
+
rps: 5,
|
|
16
|
+
rpm: 300,
|
|
17
|
+
burst: 10,
|
|
18
|
+
},
|
|
19
|
+
starter: {
|
|
20
|
+
rps: 5,
|
|
21
|
+
rpm: 300,
|
|
22
|
+
burst: 10,
|
|
23
|
+
},
|
|
24
|
+
// Professional tier
|
|
25
|
+
professional: {
|
|
26
|
+
rps: 50,
|
|
27
|
+
rpm: 3000,
|
|
28
|
+
burst: 100,
|
|
29
|
+
},
|
|
30
|
+
pro: {
|
|
31
|
+
rps: 50,
|
|
32
|
+
rpm: 3000,
|
|
33
|
+
burst: 100,
|
|
34
|
+
},
|
|
35
|
+
// Business tier
|
|
36
|
+
business: {
|
|
37
|
+
rps: 100,
|
|
38
|
+
rpm: 6000,
|
|
39
|
+
burst: 200,
|
|
40
|
+
},
|
|
41
|
+
// Enterprise tier
|
|
42
|
+
enterprise: {
|
|
43
|
+
rps: 500,
|
|
44
|
+
rpm: 30000,
|
|
45
|
+
burst: 1000,
|
|
46
|
+
},
|
|
47
|
+
// Default fallback
|
|
48
|
+
default: {
|
|
49
|
+
rps: 5,
|
|
50
|
+
rpm: 300,
|
|
51
|
+
burst: 10,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Get rate limits for a given plan key
|
|
56
|
+
*
|
|
57
|
+
* @param planKey - The plan key (e.g., "starter", "professional", "enterprise")
|
|
58
|
+
* @returns Rate limit configuration
|
|
59
|
+
*/
|
|
60
|
+
export function getRateLimitsForPlan(planKey) {
|
|
61
|
+
if (!planKey)
|
|
62
|
+
return RATE_LIMITS_BY_PLAN.default;
|
|
63
|
+
const normalized = planKey.toLowerCase().trim();
|
|
64
|
+
return RATE_LIMITS_BY_PLAN[normalized] || RATE_LIMITS_BY_PLAN.default;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Check if a plan has enterprise-level access
|
|
68
|
+
*
|
|
69
|
+
* @param planKey - The plan key
|
|
70
|
+
* @returns True if enterprise plan
|
|
71
|
+
*/
|
|
72
|
+
export function isEnterprisePlan(planKey) {
|
|
73
|
+
if (!planKey)
|
|
74
|
+
return false;
|
|
75
|
+
return planKey.toLowerCase().includes("enterprise");
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Get human-readable rate limit description
|
|
79
|
+
*
|
|
80
|
+
* @param limits - Rate limit configuration
|
|
81
|
+
* @returns Formatted string
|
|
82
|
+
*/
|
|
83
|
+
export function describeRateLimits(limits) {
|
|
84
|
+
return `${limits.rpm} requests/minute, ${limits.rps} requests/second`;
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=rate-limits.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limits.js","sourceRoot":"","sources":["../src/rate-limits.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH;;;;;GAKG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAoC;IAClE,oBAAoB;IACpB,IAAI,EAAE;QACJ,GAAG,EAAE,CAAC;QACN,GAAG,EAAE,GAAG;QACR,KAAK,EAAE,EAAE;KACV;IACD,OAAO,EAAE;QACP,GAAG,EAAE,CAAC;QACN,GAAG,EAAE,GAAG;QACR,KAAK,EAAE,EAAE;KACV;IAED,oBAAoB;IACpB,YAAY,EAAE;QACZ,GAAG,EAAE,EAAE;QACP,GAAG,EAAE,IAAI;QACT,KAAK,EAAE,GAAG;KACX;IACD,GAAG,EAAE;QACH,GAAG,EAAE,EAAE;QACP,GAAG,EAAE,IAAI;QACT,KAAK,EAAE,GAAG;KACX;IAED,gBAAgB;IAChB,QAAQ,EAAE;QACR,GAAG,EAAE,GAAG;QACR,GAAG,EAAE,IAAI;QACT,KAAK,EAAE,GAAG;KACX;IAED,kBAAkB;IAClB,UAAU,EAAE;QACV,GAAG,EAAE,GAAG;QACR,GAAG,EAAE,KAAK;QACV,KAAK,EAAE,IAAI;KACZ;IAED,mBAAmB;IACnB,OAAO,EAAE;QACP,GAAG,EAAE,CAAC;QACN,GAAG,EAAE,GAAG;QACR,KAAK,EAAE,EAAE;KACV;CACF,CAAA;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAkC;IACrE,IAAI,CAAC,OAAO;QAAE,OAAO,mBAAmB,CAAC,OAAQ,CAAA;IAEjD,MAAM,UAAU,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAA;IAC/C,OAAO,mBAAmB,CAAC,UAAU,CAAC,IAAI,mBAAmB,CAAC,OAAQ,CAAA;AACxE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAAkC;IACjE,IAAI,CAAC,OAAO;QAAE,OAAO,KAAK,CAAA;IAC1B,OAAO,OAAO,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAA;AACrD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAuB;IACxD,OAAO,GAAG,MAAM,CAAC,GAAG,qBAAqB,MAAM,CAAC,GAAG,kBAAkB,CAAA;AACvE,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Outbound-HTTP resilience primitives (RFC voyant#1687 Phase 3.3).
|
|
3
|
+
*
|
|
4
|
+
* Every outbound call from a Worker burns request CPU/subrequest budget
|
|
5
|
+
* (now platform-enforced per plan tier), so a slow third-party must
|
|
6
|
+
* fail fast, a flaky one must retry with jitter instead of hammering,
|
|
7
|
+
* and a down one must trip a breaker instead of cascading. These are
|
|
8
|
+
* the defaults the plugin clients (e-invoicing, CMS sync, payments)
|
|
9
|
+
* and channel push wrap their fetches with — consumers stop
|
|
10
|
+
* hand-rolling them.
|
|
11
|
+
*/
|
|
12
|
+
export interface RetryOptions {
|
|
13
|
+
/** Max attempts INCLUDING the first. Default 3. */
|
|
14
|
+
attempts?: number;
|
|
15
|
+
/** Base backoff before the 2nd attempt; doubles per retry. Default 250ms. */
|
|
16
|
+
baseDelayMs?: number;
|
|
17
|
+
/** Backoff ceiling. Default 4s. */
|
|
18
|
+
maxDelayMs?: number;
|
|
19
|
+
/**
|
|
20
|
+
* Which failures retry. Defaults to network errors, timeouts, 429 and
|
|
21
|
+
* 5xx responses — for requests considered idempotent (see
|
|
22
|
+
* `retryNonIdempotent` on {@link ResilientFetchOptions}).
|
|
23
|
+
*/
|
|
24
|
+
retryOn?: (result: {
|
|
25
|
+
response?: Response;
|
|
26
|
+
error?: unknown;
|
|
27
|
+
}) => boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface CircuitBreakerOptions {
|
|
30
|
+
/** Consecutive failures before the circuit opens. Default 5. */
|
|
31
|
+
failureThreshold?: number;
|
|
32
|
+
/** How long an open circuit rejects immediately. Default 30s. */
|
|
33
|
+
openMs?: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Minimal consecutive-failure circuit breaker. Half-open after
|
|
37
|
+
* `openMs`: the next call probes; success closes, failure re-opens.
|
|
38
|
+
* State is per-isolate (Workers have many isolates — the breaker bounds
|
|
39
|
+
* each isolate's contribution to a hammering herd rather than providing
|
|
40
|
+
* a global cutoff, which is the right scope for edge runtimes).
|
|
41
|
+
*/
|
|
42
|
+
export interface CircuitBreaker {
|
|
43
|
+
/** Throws {@link CircuitOpenError} when open. */
|
|
44
|
+
assertClosed(): void;
|
|
45
|
+
recordSuccess(): void;
|
|
46
|
+
recordFailure(): void;
|
|
47
|
+
readonly state: "closed" | "open" | "half-open";
|
|
48
|
+
}
|
|
49
|
+
export declare class CircuitOpenError extends Error {
|
|
50
|
+
readonly retryAfterMs: number;
|
|
51
|
+
constructor(retryAfterMs: number);
|
|
52
|
+
}
|
|
53
|
+
export declare function createCircuitBreaker(options?: CircuitBreakerOptions): CircuitBreaker;
|
|
54
|
+
export interface ResilientFetchOptions {
|
|
55
|
+
/** Per-attempt timeout. Default 10s. */
|
|
56
|
+
timeoutMs?: number;
|
|
57
|
+
/** Retry policy. `false` disables retries. */
|
|
58
|
+
retry?: RetryOptions | false;
|
|
59
|
+
/**
|
|
60
|
+
* Retries default to idempotent methods only (GET/HEAD/PUT/DELETE).
|
|
61
|
+
* POSTs against APIs with their own idempotency keys (most payment /
|
|
62
|
+
* e-invoicing providers) can opt in.
|
|
63
|
+
*/
|
|
64
|
+
retryNonIdempotent?: boolean;
|
|
65
|
+
/** Optional breaker — share one per upstream service. */
|
|
66
|
+
breaker?: CircuitBreaker;
|
|
67
|
+
/** Injection point for tests. Defaults to global fetch. */
|
|
68
|
+
fetchImpl?: (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* `fetch` with a per-attempt timeout, capped exponential retries with
|
|
72
|
+
* full jitter, and an optional circuit breaker.
|
|
73
|
+
*
|
|
74
|
+
* Defaults: 10s timeout, 3 attempts on network errors/timeouts/429/5xx —
|
|
75
|
+
* but only for idempotent methods unless `retryNonIdempotent` is set.
|
|
76
|
+
* 4xx responses (other than 429) are returned, never retried. Breaker
|
|
77
|
+
* failures count once per attempt; a `CircuitOpenError` is thrown
|
|
78
|
+
* without touching the network.
|
|
79
|
+
*/
|
|
80
|
+
export declare function resilientFetch(input: string | URL | Request, init?: RequestInit, options?: ResilientFetchOptions): Promise<Response>;
|
|
81
|
+
//# sourceMappingURL=resilience.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resilience.d.ts","sourceRoot":"","sources":["../src/resilience.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,YAAY;IAC3B,mDAAmD;IACnD,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,mCAAmC;IACnC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,EAAE,QAAQ,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAA;CACxE;AAED,MAAM,WAAW,qBAAqB;IACpC,gEAAgE;IAChE,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,iDAAiD;IACjD,YAAY,IAAI,IAAI,CAAA;IACpB,aAAa,IAAI,IAAI,CAAA;IACrB,aAAa,IAAI,IAAI,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAA;CAChD;AAED,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;gBACjB,YAAY,EAAE,MAAM;CAKjC;AAED,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,qBAA0B,GAAG,cAAc,CA0CxF;AAED,MAAM,WAAW,qBAAqB;IACpC,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,8CAA8C;IAC9C,KAAK,CAAC,EAAE,YAAY,GAAG,KAAK,CAAA;IAC5B;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B,yDAAyD;IACzD,OAAO,CAAC,EAAE,cAAc,CAAA;IACxB,2DAA2D;IAC3D,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,OAAO,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;CACrF;AAwBD;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,MAAM,GAAG,GAAG,GAAG,OAAO,EAC7B,IAAI,GAAE,WAAgB,EACtB,OAAO,GAAE,qBAA0B,GAClC,OAAO,CAAC,QAAQ,CAAC,CAgEnB"}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Outbound-HTTP resilience primitives (RFC voyant#1687 Phase 3.3).
|
|
3
|
+
*
|
|
4
|
+
* Every outbound call from a Worker burns request CPU/subrequest budget
|
|
5
|
+
* (now platform-enforced per plan tier), so a slow third-party must
|
|
6
|
+
* fail fast, a flaky one must retry with jitter instead of hammering,
|
|
7
|
+
* and a down one must trip a breaker instead of cascading. These are
|
|
8
|
+
* the defaults the plugin clients (e-invoicing, CMS sync, payments)
|
|
9
|
+
* and channel push wrap their fetches with — consumers stop
|
|
10
|
+
* hand-rolling them.
|
|
11
|
+
*/
|
|
12
|
+
export class CircuitOpenError extends Error {
|
|
13
|
+
retryAfterMs;
|
|
14
|
+
constructor(retryAfterMs) {
|
|
15
|
+
super(`circuit open; retry in ~${Math.ceil(retryAfterMs / 1000)}s`);
|
|
16
|
+
this.name = "CircuitOpenError";
|
|
17
|
+
this.retryAfterMs = retryAfterMs;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export function createCircuitBreaker(options = {}) {
|
|
21
|
+
const failureThreshold = options.failureThreshold ?? 5;
|
|
22
|
+
const openMs = options.openMs ?? 30_000;
|
|
23
|
+
let consecutiveFailures = 0;
|
|
24
|
+
let openedAt = null;
|
|
25
|
+
let probing = false;
|
|
26
|
+
function currentState() {
|
|
27
|
+
if (openedAt === null)
|
|
28
|
+
return "closed";
|
|
29
|
+
if (Date.now() - openedAt >= openMs)
|
|
30
|
+
return "half-open";
|
|
31
|
+
return "open";
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
get state() {
|
|
35
|
+
return currentState();
|
|
36
|
+
},
|
|
37
|
+
assertClosed() {
|
|
38
|
+
const state = currentState();
|
|
39
|
+
if (state === "closed")
|
|
40
|
+
return;
|
|
41
|
+
if (state === "half-open" && !probing) {
|
|
42
|
+
// One probe through; everyone else keeps getting rejected until
|
|
43
|
+
// the probe settles.
|
|
44
|
+
probing = true;
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const elapsed = Date.now() - (openedAt ?? 0);
|
|
48
|
+
throw new CircuitOpenError(Math.max(0, openMs - elapsed));
|
|
49
|
+
},
|
|
50
|
+
recordSuccess() {
|
|
51
|
+
consecutiveFailures = 0;
|
|
52
|
+
openedAt = null;
|
|
53
|
+
probing = false;
|
|
54
|
+
},
|
|
55
|
+
recordFailure() {
|
|
56
|
+
consecutiveFailures += 1;
|
|
57
|
+
probing = false;
|
|
58
|
+
if (consecutiveFailures >= failureThreshold) {
|
|
59
|
+
openedAt = Date.now();
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
const IDEMPOTENT_METHODS = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
|
|
65
|
+
function defaultRetryOn(result) {
|
|
66
|
+
if (result.error)
|
|
67
|
+
return !(result.error instanceof CircuitOpenError);
|
|
68
|
+
if (!result.response)
|
|
69
|
+
return false;
|
|
70
|
+
return result.response.status === 429 || result.response.status >= 500;
|
|
71
|
+
}
|
|
72
|
+
function backoffDelay(attempt, base, cap) {
|
|
73
|
+
const exp = Math.min(base * 2 ** (attempt - 1), cap);
|
|
74
|
+
// Full jitter — desynchronizes retry herds across isolates.
|
|
75
|
+
return Math.round(exp * (0.5 + Math.random() * 0.5));
|
|
76
|
+
}
|
|
77
|
+
function sleep(ms) {
|
|
78
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
79
|
+
}
|
|
80
|
+
function defaultFetch(input, init) {
|
|
81
|
+
return fetch(input, init);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* `fetch` with a per-attempt timeout, capped exponential retries with
|
|
85
|
+
* full jitter, and an optional circuit breaker.
|
|
86
|
+
*
|
|
87
|
+
* Defaults: 10s timeout, 3 attempts on network errors/timeouts/429/5xx —
|
|
88
|
+
* but only for idempotent methods unless `retryNonIdempotent` is set.
|
|
89
|
+
* 4xx responses (other than 429) are returned, never retried. Breaker
|
|
90
|
+
* failures count once per attempt; a `CircuitOpenError` is thrown
|
|
91
|
+
* without touching the network.
|
|
92
|
+
*/
|
|
93
|
+
export async function resilientFetch(input, init = {}, options = {}) {
|
|
94
|
+
const fetchImpl = options.fetchImpl ?? defaultFetch;
|
|
95
|
+
const timeoutMs = options.timeoutMs ?? 10_000;
|
|
96
|
+
const method = (init.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase();
|
|
97
|
+
const retriesAllowed = options.retry !== false && (IDEMPOTENT_METHODS.has(method) || options.retryNonIdempotent);
|
|
98
|
+
const retry = options.retry === false ? undefined : options.retry;
|
|
99
|
+
const attempts = retriesAllowed ? (retry?.attempts ?? 3) : 1;
|
|
100
|
+
const retryOn = retry?.retryOn ?? defaultRetryOn;
|
|
101
|
+
const baseDelayMs = retry?.baseDelayMs ?? 250;
|
|
102
|
+
const maxDelayMs = retry?.maxDelayMs ?? 4_000;
|
|
103
|
+
let lastError;
|
|
104
|
+
for (let attempt = 1; attempt <= attempts; attempt++) {
|
|
105
|
+
options.breaker?.assertClosed();
|
|
106
|
+
const controller = new AbortController();
|
|
107
|
+
const timer = setTimeout(() => controller.abort(new Error(`request timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
108
|
+
// Compose with a caller-supplied signal when present.
|
|
109
|
+
if (init.signal) {
|
|
110
|
+
const upstream = init.signal;
|
|
111
|
+
if (upstream.aborted)
|
|
112
|
+
controller.abort(upstream.reason);
|
|
113
|
+
else
|
|
114
|
+
upstream.addEventListener("abort", () => controller.abort(upstream.reason));
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
// Cast: CF's lib types Request with its own generics; the runtime
|
|
118
|
+
// value is the same structural Request either way.
|
|
119
|
+
const requestInput = (input instanceof Request ? input.clone() : input);
|
|
120
|
+
const response = await fetchImpl(requestInput, { ...init, signal: controller.signal });
|
|
121
|
+
if (response.ok || !retryOn({ response })) {
|
|
122
|
+
if (response.ok)
|
|
123
|
+
options.breaker?.recordSuccess();
|
|
124
|
+
else if (response.status >= 500 || response.status === 429) {
|
|
125
|
+
options.breaker?.recordFailure();
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
// 4xx = upstream is alive and judging our request; not a
|
|
129
|
+
// service-health signal either way.
|
|
130
|
+
options.breaker?.recordSuccess();
|
|
131
|
+
}
|
|
132
|
+
return response;
|
|
133
|
+
}
|
|
134
|
+
options.breaker?.recordFailure();
|
|
135
|
+
lastError = new Error(`upstream responded ${response.status}`);
|
|
136
|
+
// Drain/cancel so the runtime doesn't hold the body across retries.
|
|
137
|
+
await response.body?.cancel().catch(() => { });
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
if (error instanceof CircuitOpenError)
|
|
141
|
+
throw error;
|
|
142
|
+
options.breaker?.recordFailure();
|
|
143
|
+
lastError = error;
|
|
144
|
+
if (!retryOn({ error }))
|
|
145
|
+
throw error;
|
|
146
|
+
}
|
|
147
|
+
finally {
|
|
148
|
+
clearTimeout(timer);
|
|
149
|
+
}
|
|
150
|
+
if (attempt < attempts) {
|
|
151
|
+
await sleep(backoffDelay(attempt, baseDelayMs, maxDelayMs));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=resilience.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resilience.js","sourceRoot":"","sources":["../src/resilience.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAuCH,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IAChC,YAAY,CAAQ;IAC7B,YAAY,YAAoB;QAC9B,KAAK,CAAC,2BAA2B,IAAI,CAAC,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,CAAA;QACnE,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAA;QAC9B,IAAI,CAAC,YAAY,GAAG,YAAY,CAAA;IAClC,CAAC;CACF;AAED,MAAM,UAAU,oBAAoB,CAAC,UAAiC,EAAE;IACtE,MAAM,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,IAAI,CAAC,CAAA;IACtD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,MAAM,CAAA;IACvC,IAAI,mBAAmB,GAAG,CAAC,CAAA;IAC3B,IAAI,QAAQ,GAAkB,IAAI,CAAA;IAClC,IAAI,OAAO,GAAG,KAAK,CAAA;IAEnB,SAAS,YAAY;QACnB,IAAI,QAAQ,KAAK,IAAI;YAAE,OAAO,QAAQ,CAAA;QACtC,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,IAAI,MAAM;YAAE,OAAO,WAAW,CAAA;QACvD,OAAO,MAAM,CAAA;IACf,CAAC;IAED,OAAO;QACL,IAAI,KAAK;YACP,OAAO,YAAY,EAAE,CAAA;QACvB,CAAC;QACD,YAAY;YACV,MAAM,KAAK,GAAG,YAAY,EAAE,CAAA;YAC5B,IAAI,KAAK,KAAK,QAAQ;gBAAE,OAAM;YAC9B,IAAI,KAAK,KAAK,WAAW,IAAI,CAAC,OAAO,EAAE,CAAC;gBACtC,gEAAgE;gBAChE,qBAAqB;gBACrB,OAAO,GAAG,IAAI,CAAA;gBACd,OAAM;YACR,CAAC;YACD,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,QAAQ,IAAI,CAAC,CAAC,CAAA;YAC5C,MAAM,IAAI,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAA;QAC3D,CAAC;QACD,aAAa;YACX,mBAAmB,GAAG,CAAC,CAAA;YACvB,QAAQ,GAAG,IAAI,CAAA;YACf,OAAO,GAAG,KAAK,CAAA;QACjB,CAAC;QACD,aAAa;YACX,mBAAmB,IAAI,CAAC,CAAA;YACxB,OAAO,GAAG,KAAK,CAAA;YACf,IAAI,mBAAmB,IAAI,gBAAgB,EAAE,CAAC;gBAC5C,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;YACvB,CAAC;QACH,CAAC;KACF,CAAA;AACH,CAAC;AAmBD,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC,CAAA;AAE/E,SAAS,cAAc,CAAC,MAAgD;IACtE,IAAI,MAAM,CAAC,KAAK;QAAE,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,YAAY,gBAAgB,CAAC,CAAA;IACpE,IAAI,CAAC,MAAM,CAAC,QAAQ;QAAE,OAAO,KAAK,CAAA;IAClC,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,IAAI,GAAG,CAAA;AACxE,CAAC;AAED,SAAS,YAAY,CAAC,OAAe,EAAE,IAAY,EAAE,GAAW;IAC9D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;IACpD,4DAA4D;IAC5D,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,CAAC,CAAC,CAAA;AACtD,CAAC;AAED,SAAS,KAAK,CAAC,EAAU;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAA;AAC1D,CAAC;AAED,SAAS,YAAY,CAAC,KAA6B,EAAE,IAAkB;IACrE,OAAO,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,CAAA;AAC3B,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,KAA6B,EAC7B,OAAoB,EAAE,EACtB,UAAiC,EAAE;IAEnC,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,YAAY,CAAA;IACnD,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,MAAM,CAAA;IAC7C,MAAM,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,KAAK,YAAY,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,WAAW,EAAE,CAAA;IAC/F,MAAM,cAAc,GAClB,OAAO,CAAC,KAAK,KAAK,KAAK,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC,kBAAkB,CAAC,CAAA;IAC3F,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAA;IACjE,MAAM,QAAQ,GAAG,cAAc,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,QAAQ,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IAC5D,MAAM,OAAO,GAAG,KAAK,EAAE,OAAO,IAAI,cAAc,CAAA;IAChD,MAAM,WAAW,GAAG,KAAK,EAAE,WAAW,IAAI,GAAG,CAAA;IAC7C,MAAM,UAAU,GAAG,KAAK,EAAE,UAAU,IAAI,KAAK,CAAA;IAE7C,IAAI,SAAkB,CAAA;IACtB,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,QAAQ,EAAE,OAAO,EAAE,EAAE,CAAC;QACrD,OAAO,CAAC,OAAO,EAAE,YAAY,EAAE,CAAA;QAE/B,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAA;QACxC,MAAM,KAAK,GAAG,UAAU,CACtB,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,2BAA2B,SAAS,IAAI,CAAC,CAAC,EAC3E,SAAS,CACV,CAAA;QACD,sDAAsD;QACtD,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAA;YAC5B,IAAI,QAAQ,CAAC,OAAO;gBAAE,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;;gBAClD,QAAQ,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAA;QAClF,CAAC;QAED,IAAI,CAAC;YACH,kEAAkE;YAClE,mDAAmD;YACnD,MAAM,YAAY,GAAG,CAAC,KAAK,YAAY,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAElE,CAAA;YACJ,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,YAAY,EAAE,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC,CAAA;YACtF,IAAI,QAAQ,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;gBAC1C,IAAI,QAAQ,CAAC,EAAE;oBAAE,OAAO,CAAC,OAAO,EAAE,aAAa,EAAE,CAAA;qBAC5C,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;oBAC3D,OAAO,CAAC,OAAO,EAAE,aAAa,EAAE,CAAA;gBAClC,CAAC;qBAAM,CAAC;oBACN,yDAAyD;oBACzD,oCAAoC;oBACpC,OAAO,CAAC,OAAO,EAAE,aAAa,EAAE,CAAA;gBAClC,CAAC;gBACD,OAAO,QAAQ,CAAA;YACjB,CAAC;YACD,OAAO,CAAC,OAAO,EAAE,aAAa,EAAE,CAAA;YAChC,SAAS,GAAG,IAAI,KAAK,CAAC,sBAAsB,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;YAC9D,oEAAoE;YACpE,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;QAC/C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,gBAAgB;gBAAE,MAAM,KAAK,CAAA;YAClD,OAAO,CAAC,OAAO,EAAE,aAAa,EAAE,CAAA;YAChC,SAAS,GAAG,KAAK,CAAA;YACjB,IAAI,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,CAAC;gBAAE,MAAM,KAAK,CAAA;QACtC,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,KAAK,CAAC,CAAA;QACrB,CAAC;QAED,IAAI,OAAO,GAAG,QAAQ,EAAE,CAAC;YACvB,MAAM,KAAK,CAAC,YAAY,CAAC,OAAO,EAAE,WAAW,EAAE,UAAU,CAAC,CAAC,CAAA;QAC7D,CAAC;IACH,CAAC;IACD,MAAM,SAAS,YAAY,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAA;AAC7E,CAAC"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Claims Cookie Utilities
|
|
3
|
+
*
|
|
4
|
+
* Implements signed session claims to reduce API calls in middleware.
|
|
5
|
+
* Claims contain minimal session info (userId, sessionId hash) that can be
|
|
6
|
+
* verified locally without database lookup.
|
|
7
|
+
*
|
|
8
|
+
* Security:
|
|
9
|
+
* - HMAC-SHA256 signing prevents tampering
|
|
10
|
+
* - 5-minute expiration ensures quick revocation
|
|
11
|
+
* - HttpOnly, Secure, SameSite cookies
|
|
12
|
+
* - Context-separated signing keys (HKDF-SHA256): the deployment secret
|
|
13
|
+
* (`SESSION_CLAIMS_SECRET`) is never used directly as an HMAC key.
|
|
14
|
+
* `signSessionClaims`/`verifySessionClaims` derive a dedicated key under
|
|
15
|
+
* the `voyant:session-claims:v1` context, so a leak of a token-signing
|
|
16
|
+
* key (or any other derived key) does not compromise sibling contexts
|
|
17
|
+
* (e.g. the cloud-admin state cookie) or the raw Better Auth secret.
|
|
18
|
+
*
|
|
19
|
+
* Compatible with environments that expose the standard Web Crypto API,
|
|
20
|
+
* including Node.js, browsers, and Cloudflare Workers.
|
|
21
|
+
*/
|
|
22
|
+
export interface SessionClaims {
|
|
23
|
+
userId: string;
|
|
24
|
+
sessionId: string;
|
|
25
|
+
iat: number;
|
|
26
|
+
exp: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* HKDF context label under which session-claims bearer tokens are signed.
|
|
30
|
+
* Bumping the version suffix invalidates all outstanding tokens.
|
|
31
|
+
*/
|
|
32
|
+
export declare const SESSION_CLAIMS_KEY_CONTEXT = "voyant:session-claims:v1";
|
|
33
|
+
/**
|
|
34
|
+
* HKDF context label for the Voyant Cloud admin-auth state-cookie HMAC key.
|
|
35
|
+
* Consumers (e.g. the operator auth handler) derive this at the boundary and
|
|
36
|
+
* pass the derived key down as `cookieSecret`.
|
|
37
|
+
*/
|
|
38
|
+
export declare const CLOUD_STATE_COOKIE_KEY_CONTEXT = "voyant:cloud-state-cookie:v1";
|
|
39
|
+
/**
|
|
40
|
+
* Derive a context-separated subkey from a root secret via HKDF-SHA256
|
|
41
|
+
* (Web Crypto, Workers-compatible).
|
|
42
|
+
*
|
|
43
|
+
* The same root secret yields independent keys per context label, so a
|
|
44
|
+
* compromise of one derived key cannot be replayed against another context
|
|
45
|
+
* or against anything still using the root secret (e.g. Better Auth).
|
|
46
|
+
*
|
|
47
|
+
* @param secret - Root secret (e.g. `SESSION_CLAIMS_SECRET`)
|
|
48
|
+
* @param context - Context label, e.g. `"voyant:session-claims:v1"`
|
|
49
|
+
* @returns base64url-encoded 256-bit key (43 chars — satisfies >=32-char
|
|
50
|
+
* secret checks downstream)
|
|
51
|
+
*/
|
|
52
|
+
export declare function deriveContextKey(secret: string, context: string): Promise<string>;
|
|
53
|
+
/**
|
|
54
|
+
* Sign session claims and return as JWT-like token
|
|
55
|
+
*
|
|
56
|
+
* Format: base64url(header).base64url(payload).base64url(signature)
|
|
57
|
+
*
|
|
58
|
+
* The HMAC key is NOT the raw `secret`: it is derived via
|
|
59
|
+
* `deriveContextKey(secret, SESSION_CLAIMS_KEY_CONTEXT)` so the root secret
|
|
60
|
+
* is never used directly as a token-signing key (context separation).
|
|
61
|
+
*
|
|
62
|
+
* @param userId - User ID from verified session
|
|
63
|
+
* @param sessionId - Full session ID (will be hashed)
|
|
64
|
+
* @param secret - Root secret; the signing key is derived from it
|
|
65
|
+
* @returns Signed token string
|
|
66
|
+
*/
|
|
67
|
+
export declare function signSessionClaims(userId: string, sessionId: string, secret: string): Promise<string>;
|
|
68
|
+
/**
|
|
69
|
+
* Verify and decode session claims token
|
|
70
|
+
*
|
|
71
|
+
* Verification uses the same context-derived key as `signSessionClaims`
|
|
72
|
+
* (`SESSION_CLAIMS_KEY_CONTEXT`), so tokens signed with the raw secret
|
|
73
|
+
* (pre context-separation) are rejected.
|
|
74
|
+
*
|
|
75
|
+
* @param token - Signed token from cookie
|
|
76
|
+
* @param secret - Root secret; the verification key is derived from it
|
|
77
|
+
* @returns Decoded claims if valid, null if invalid/expired
|
|
78
|
+
*/
|
|
79
|
+
export declare function verifySessionClaims(token: string, secret: string): Promise<SessionClaims | null>;
|
|
80
|
+
//# sourceMappingURL=session-claims.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-claims.d.ts","sourceRoot":"","sources":["../src/session-claims.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAUH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,MAAM,CAAA;IACjB,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;CACZ;AAID;;;GAGG;AACH,eAAO,MAAM,0BAA0B,6BAA6B,CAAA;AAEpE;;;;GAIG;AACH,eAAO,MAAM,8BAA8B,iCAAiC,CAAA;AAiB5E;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAYjF;AAmDD;;;;;;;;;;;;;GAaG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAwBjB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CA6C/B"}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Claims Cookie Utilities
|
|
3
|
+
*
|
|
4
|
+
* Implements signed session claims to reduce API calls in middleware.
|
|
5
|
+
* Claims contain minimal session info (userId, sessionId hash) that can be
|
|
6
|
+
* verified locally without database lookup.
|
|
7
|
+
*
|
|
8
|
+
* Security:
|
|
9
|
+
* - HMAC-SHA256 signing prevents tampering
|
|
10
|
+
* - 5-minute expiration ensures quick revocation
|
|
11
|
+
* - HttpOnly, Secure, SameSite cookies
|
|
12
|
+
* - Context-separated signing keys (HKDF-SHA256): the deployment secret
|
|
13
|
+
* (`SESSION_CLAIMS_SECRET`) is never used directly as an HMAC key.
|
|
14
|
+
* `signSessionClaims`/`verifySessionClaims` derive a dedicated key under
|
|
15
|
+
* the `voyant:session-claims:v1` context, so a leak of a token-signing
|
|
16
|
+
* key (or any other derived key) does not compromise sibling contexts
|
|
17
|
+
* (e.g. the cloud-admin state cookie) or the raw Better Auth secret.
|
|
18
|
+
*
|
|
19
|
+
* Compatible with environments that expose the standard Web Crypto API,
|
|
20
|
+
* including Node.js, browsers, and Cloudflare Workers.
|
|
21
|
+
*/
|
|
22
|
+
function getWebCrypto() {
|
|
23
|
+
if (typeof globalThis.crypto !== "undefined" && globalThis.crypto.subtle) {
|
|
24
|
+
return globalThis.crypto;
|
|
25
|
+
}
|
|
26
|
+
throw new Error("No crypto implementation available");
|
|
27
|
+
}
|
|
28
|
+
const CLAIMS_EXPIRY_SECONDS = 5 * 60; // 5 minutes
|
|
29
|
+
/**
|
|
30
|
+
* HKDF context label under which session-claims bearer tokens are signed.
|
|
31
|
+
* Bumping the version suffix invalidates all outstanding tokens.
|
|
32
|
+
*/
|
|
33
|
+
export const SESSION_CLAIMS_KEY_CONTEXT = "voyant:session-claims:v1";
|
|
34
|
+
/**
|
|
35
|
+
* HKDF context label for the Voyant Cloud admin-auth state-cookie HMAC key.
|
|
36
|
+
* Consumers (e.g. the operator auth handler) derive this at the boundary and
|
|
37
|
+
* pass the derived key down as `cookieSecret`.
|
|
38
|
+
*/
|
|
39
|
+
export const CLOUD_STATE_COOKIE_KEY_CONTEXT = "voyant:cloud-state-cookie:v1";
|
|
40
|
+
/**
|
|
41
|
+
* Fixed, public HKDF salt. Domain separation comes from the `info` (context)
|
|
42
|
+
* parameter; the salt only needs to be a stable application-wide constant.
|
|
43
|
+
*/
|
|
44
|
+
const HKDF_SALT = "voyant:hkdf:salt:v1";
|
|
45
|
+
const derivedKeyCache = new Map();
|
|
46
|
+
function base64UrlEncode(bytes) {
|
|
47
|
+
return btoa(String.fromCharCode(...bytes))
|
|
48
|
+
.replace(/\+/g, "-")
|
|
49
|
+
.replace(/\//g, "_")
|
|
50
|
+
.replace(/=/g, "");
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Derive a context-separated subkey from a root secret via HKDF-SHA256
|
|
54
|
+
* (Web Crypto, Workers-compatible).
|
|
55
|
+
*
|
|
56
|
+
* The same root secret yields independent keys per context label, so a
|
|
57
|
+
* compromise of one derived key cannot be replayed against another context
|
|
58
|
+
* or against anything still using the root secret (e.g. Better Auth).
|
|
59
|
+
*
|
|
60
|
+
* @param secret - Root secret (e.g. `SESSION_CLAIMS_SECRET`)
|
|
61
|
+
* @param context - Context label, e.g. `"voyant:session-claims:v1"`
|
|
62
|
+
* @returns base64url-encoded 256-bit key (43 chars — satisfies >=32-char
|
|
63
|
+
* secret checks downstream)
|
|
64
|
+
*/
|
|
65
|
+
export function deriveContextKey(secret, context) {
|
|
66
|
+
const cacheKey = `${context}\u0000${secret}`;
|
|
67
|
+
let cached = derivedKeyCache.get(cacheKey);
|
|
68
|
+
if (!cached) {
|
|
69
|
+
cached = deriveContextKeyUncached(secret, context).catch((error) => {
|
|
70
|
+
// Don't cache failures (e.g. transient crypto unavailability).
|
|
71
|
+
derivedKeyCache.delete(cacheKey);
|
|
72
|
+
throw error;
|
|
73
|
+
});
|
|
74
|
+
derivedKeyCache.set(cacheKey, cached);
|
|
75
|
+
}
|
|
76
|
+
return cached;
|
|
77
|
+
}
|
|
78
|
+
async function deriveContextKeyUncached(secret, context) {
|
|
79
|
+
const webCrypto = getWebCrypto();
|
|
80
|
+
const encoder = new TextEncoder();
|
|
81
|
+
const keyMaterial = await webCrypto.subtle.importKey("raw", encoder.encode(secret), "HKDF", false, ["deriveBits"]);
|
|
82
|
+
const bits = await webCrypto.subtle.deriveBits({
|
|
83
|
+
name: "HKDF",
|
|
84
|
+
hash: "SHA-256",
|
|
85
|
+
salt: encoder.encode(HKDF_SALT),
|
|
86
|
+
info: encoder.encode(context),
|
|
87
|
+
}, keyMaterial, 256);
|
|
88
|
+
return base64UrlEncode(new Uint8Array(bits));
|
|
89
|
+
}
|
|
90
|
+
async function hmacBase64Url(message, key) {
|
|
91
|
+
const webCrypto = getWebCrypto();
|
|
92
|
+
const encoder = new TextEncoder();
|
|
93
|
+
const cryptoKey = await webCrypto.subtle.importKey("raw", encoder.encode(key), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
94
|
+
const sigBuffer = await webCrypto.subtle.sign("HMAC", cryptoKey, encoder.encode(message));
|
|
95
|
+
return base64UrlEncode(new Uint8Array(sigBuffer));
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Create a short identifier from session ID for inclusion in claims
|
|
99
|
+
* Uses first 16 chars of base64url-encoded SHA-256 hash
|
|
100
|
+
*/
|
|
101
|
+
async function hashSessionId(sessionId) {
|
|
102
|
+
const webCrypto = getWebCrypto();
|
|
103
|
+
const encoder = new TextEncoder();
|
|
104
|
+
const data = encoder.encode(sessionId);
|
|
105
|
+
const hashBuffer = await webCrypto.subtle.digest("SHA-256", data);
|
|
106
|
+
return base64UrlEncode(new Uint8Array(hashBuffer)).slice(0, 16);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Sign session claims and return as JWT-like token
|
|
110
|
+
*
|
|
111
|
+
* Format: base64url(header).base64url(payload).base64url(signature)
|
|
112
|
+
*
|
|
113
|
+
* The HMAC key is NOT the raw `secret`: it is derived via
|
|
114
|
+
* `deriveContextKey(secret, SESSION_CLAIMS_KEY_CONTEXT)` so the root secret
|
|
115
|
+
* is never used directly as a token-signing key (context separation).
|
|
116
|
+
*
|
|
117
|
+
* @param userId - User ID from verified session
|
|
118
|
+
* @param sessionId - Full session ID (will be hashed)
|
|
119
|
+
* @param secret - Root secret; the signing key is derived from it
|
|
120
|
+
* @returns Signed token string
|
|
121
|
+
*/
|
|
122
|
+
export async function signSessionClaims(userId, sessionId, secret) {
|
|
123
|
+
const now = Math.floor(Date.now() / 1000);
|
|
124
|
+
const exp = now + CLAIMS_EXPIRY_SECONDS;
|
|
125
|
+
const sessionIdHash = await hashSessionId(sessionId);
|
|
126
|
+
const claims = {
|
|
127
|
+
userId,
|
|
128
|
+
sessionId: sessionIdHash,
|
|
129
|
+
iat: now,
|
|
130
|
+
exp,
|
|
131
|
+
};
|
|
132
|
+
// Encode payload (works in both environments)
|
|
133
|
+
const header = { alg: "HS256", typ: "JWT" };
|
|
134
|
+
const encoder = new TextEncoder();
|
|
135
|
+
const headerB64 = base64UrlEncode(encoder.encode(JSON.stringify(header)));
|
|
136
|
+
const payloadB64 = base64UrlEncode(encoder.encode(JSON.stringify(claims)));
|
|
137
|
+
// Create signature with the context-derived key
|
|
138
|
+
const message = `${headerB64}.${payloadB64}`;
|
|
139
|
+
const signingKey = await deriveContextKey(secret, SESSION_CLAIMS_KEY_CONTEXT);
|
|
140
|
+
const signature = await hmacBase64Url(message, signingKey);
|
|
141
|
+
return `${headerB64}.${payloadB64}.${signature}`;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Verify and decode session claims token
|
|
145
|
+
*
|
|
146
|
+
* Verification uses the same context-derived key as `signSessionClaims`
|
|
147
|
+
* (`SESSION_CLAIMS_KEY_CONTEXT`), so tokens signed with the raw secret
|
|
148
|
+
* (pre context-separation) are rejected.
|
|
149
|
+
*
|
|
150
|
+
* @param token - Signed token from cookie
|
|
151
|
+
* @param secret - Root secret; the verification key is derived from it
|
|
152
|
+
* @returns Decoded claims if valid, null if invalid/expired
|
|
153
|
+
*/
|
|
154
|
+
export async function verifySessionClaims(token, secret) {
|
|
155
|
+
try {
|
|
156
|
+
const parts = token.split(".");
|
|
157
|
+
if (parts.length !== 3) {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
const [headerB64, payloadB64, signature] = parts;
|
|
161
|
+
// Ensure all parts are defined
|
|
162
|
+
if (!headerB64 || !payloadB64 || !signature) {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
// Verify signature with the context-derived key
|
|
166
|
+
const message = `${headerB64}.${payloadB64}`;
|
|
167
|
+
const signingKey = await deriveContextKey(secret, SESSION_CLAIMS_KEY_CONTEXT);
|
|
168
|
+
const expectedSig = await hmacBase64Url(message, signingKey);
|
|
169
|
+
// Constant-time comparison
|
|
170
|
+
if (!constantTimeEqual(signature, expectedSig)) {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
// Decode payload (works in both environments)
|
|
174
|
+
// base64url decode: replace - with +, _ with /, then decode
|
|
175
|
+
const payloadJson = atob(payloadB64.replace(/-/g, "+").replace(/_/g, "/"));
|
|
176
|
+
const payload = JSON.parse(payloadJson);
|
|
177
|
+
// Check expiration
|
|
178
|
+
const now = Math.floor(Date.now() / 1000);
|
|
179
|
+
if (payload.exp < now) {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
// Validate structure
|
|
183
|
+
if (!payload.userId || !payload.sessionId || !payload.iat || !payload.exp) {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
return payload;
|
|
187
|
+
}
|
|
188
|
+
catch (_error) {
|
|
189
|
+
// Invalid token format or JSON parse error
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Constant-time string comparison to prevent timing attacks
|
|
195
|
+
*/
|
|
196
|
+
function constantTimeEqual(a, b) {
|
|
197
|
+
if (a.length !== b.length)
|
|
198
|
+
return false;
|
|
199
|
+
let result = 0;
|
|
200
|
+
for (let i = 0; i < a.length; i++) {
|
|
201
|
+
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
202
|
+
}
|
|
203
|
+
return result === 0;
|
|
204
|
+
}
|
|
205
|
+
//# sourceMappingURL=session-claims.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-claims.js","sourceRoot":"","sources":["../src/session-claims.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template-renderer.d.ts","sourceRoot":"","sources":["../src/template-renderer.ts"],"names":[],"mappings":"AAAA,cAAc,2BAA2B,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template-renderer.js","sourceRoot":"","sources":["../src/template-renderer.ts"],"names":[],"mappings":"AAAA,cAAc,2BAA2B,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"temporal.d.ts","sourceRoot":"","sources":["../src/temporal.ts"],"names":[],"mappings":"AACA,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAG/C"}
|