@noidmejs/atomkit-http 0.2.0 → 0.4.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/README.md +13 -2
- package/dist/cache.d.ts +28 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +78 -0
- package/dist/cache.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/proxy.d.ts +14 -1
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +35 -14
- package/dist/proxy.js.map +1 -1
- package/dist/run.d.ts +12 -2
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +207 -92
- package/dist/run.js.map +1 -1
- package/dist/types.d.ts +49 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -17,8 +17,11 @@ const listUsers = {
|
|
|
17
17
|
url: 'https://api.example.com/users',
|
|
18
18
|
query: { q: '{{params.q}}' },
|
|
19
19
|
auth: { type: 'bearer', token: '{{secret.API_TOKEN}}' },
|
|
20
|
-
retry: { attempts: 3, backoffMs: 300 },
|
|
20
|
+
retry: { attempts: 3, backoffMs: 300 }, // GET/HEAD/OPTIONS only, unless you opt in
|
|
21
21
|
cache: { ttlMs: 30000 },
|
|
22
|
+
timeoutMs: 10000, // default; never unbounded
|
|
23
|
+
maxBytes: 5_000_000, // default response cap
|
|
24
|
+
expose: true, // REQUIRED to be reachable via handleRequest — default false
|
|
22
25
|
transform: 'data.items',
|
|
23
26
|
};
|
|
24
27
|
```
|
|
@@ -31,7 +34,8 @@ import { createProxy } from '@noidmejs/atomkit-http';
|
|
|
31
34
|
const proxy = createProxy({
|
|
32
35
|
sources: { listUsers },
|
|
33
36
|
secrets: { API_TOKEN: process.env.API_TOKEN! },
|
|
34
|
-
allowHosts: ['api.example.com'], // SSRF guard
|
|
37
|
+
allowHosts: ['api.example.com'], // SSRF guard: where a request may GO
|
|
38
|
+
authorize: async (req, sourceId, params) => isLoggedIn(req), // WHO may trigger it
|
|
35
39
|
});
|
|
36
40
|
|
|
37
41
|
// Next route handler / Cloudflare Worker:
|
|
@@ -42,6 +46,13 @@ The client POSTs `{ sourceId, params }`; the proxy injects secrets, enforces the
|
|
|
42
46
|
host allow-list, calls the API, and returns the (transformed) JSON. Tokens stay
|
|
43
47
|
on the server.
|
|
44
48
|
|
|
49
|
+
> **`handleRequest` authenticates nobody.** The allow-list governs where a request
|
|
50
|
+
> goes, never who may trigger it, and every call spends your server's
|
|
51
|
+
> secret-authenticated upstream quota with caller-chosen params. A source is
|
|
52
|
+
> therefore unreachable unless it sets `expose: true`, and any source returning
|
|
53
|
+
> personal data needs `authorize` (or host auth in front of the route) **plus rate
|
|
54
|
+
> limiting**, which this package does not provide.
|
|
55
|
+
|
|
45
56
|
## Client: run a public source directly
|
|
46
57
|
|
|
47
58
|
```ts
|
package/dist/cache.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ResponseCache } from './types.js';
|
|
2
|
+
/** Default cap on entries held by one cache instance. */
|
|
3
|
+
export declare const DEFAULT_MAX_ENTRIES = 500;
|
|
4
|
+
/**
|
|
5
|
+
* A bounded LRU with TTL eviction and single-flight.
|
|
6
|
+
*
|
|
7
|
+
* Replaces a module-global `Map` that was keyed by `source.id + params`, never
|
|
8
|
+
* evicted, and shared across every `createProxy()` in the process. That map had
|
|
9
|
+
* three defects this fixes:
|
|
10
|
+
*
|
|
11
|
+
* - **Cross-instance bleed.** Two proxies (say, per tenant) each declaring a
|
|
12
|
+
* source named `users` served each other's responses, because neither the URL,
|
|
13
|
+
* the auth headers, nor the owning instance were part of the key.
|
|
14
|
+
* - **Unbounded growth.** Entries were only ignored after their TTL, never
|
|
15
|
+
* deleted, and the key space included client-controlled params — so any caller
|
|
16
|
+
* of a public proxy could grow memory until OOM.
|
|
17
|
+
* - **Thundering herd.** No in-flight dedup, so every concurrent request for a
|
|
18
|
+
* hot key stampeded the upstream the moment its TTL lapsed.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createCache(maxEntries?: number): ResponseCache;
|
|
21
|
+
/** Process-wide fallback used when no per-instance cache is supplied. Bounded and
|
|
22
|
+
* evicting, but PREFER a per-instance cache (createProxy makes one for you). */
|
|
23
|
+
export declare const defaultCache: ResponseCache;
|
|
24
|
+
/** Stable, non-secret-bearing cache key: method + fully-resolved URL + a hash of
|
|
25
|
+
* the request headers. Two callers with different bearer tokens must never share
|
|
26
|
+
* an entry, but the token itself must not sit in a key we may log. */
|
|
27
|
+
export declare function cacheKeyFor(method: string, url: string, headers: Record<string, string>): string;
|
|
28
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,yDAAyD;AACzD,eAAO,MAAM,mBAAmB,MAAM,CAAC;AAEvC;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW,CAAC,UAAU,GAAE,MAA4B,GAAG,aAAa,CAuCnF;AAED;iFACiF;AACjF,eAAO,MAAM,YAAY,EAAE,aAA6B,CAAC;AAEzD;;uEAEuE;AACvE,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAShG"}
|
package/dist/cache.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/** Default cap on entries held by one cache instance. */
|
|
2
|
+
export const DEFAULT_MAX_ENTRIES = 500;
|
|
3
|
+
/**
|
|
4
|
+
* A bounded LRU with TTL eviction and single-flight.
|
|
5
|
+
*
|
|
6
|
+
* Replaces a module-global `Map` that was keyed by `source.id + params`, never
|
|
7
|
+
* evicted, and shared across every `createProxy()` in the process. That map had
|
|
8
|
+
* three defects this fixes:
|
|
9
|
+
*
|
|
10
|
+
* - **Cross-instance bleed.** Two proxies (say, per tenant) each declaring a
|
|
11
|
+
* source named `users` served each other's responses, because neither the URL,
|
|
12
|
+
* the auth headers, nor the owning instance were part of the key.
|
|
13
|
+
* - **Unbounded growth.** Entries were only ignored after their TTL, never
|
|
14
|
+
* deleted, and the key space included client-controlled params — so any caller
|
|
15
|
+
* of a public proxy could grow memory until OOM.
|
|
16
|
+
* - **Thundering herd.** No in-flight dedup, so every concurrent request for a
|
|
17
|
+
* hot key stampeded the upstream the moment its TTL lapsed.
|
|
18
|
+
*/
|
|
19
|
+
export function createCache(maxEntries = DEFAULT_MAX_ENTRIES) {
|
|
20
|
+
const entries = new Map();
|
|
21
|
+
const pending = new Map();
|
|
22
|
+
return {
|
|
23
|
+
get(key, ttlMs) {
|
|
24
|
+
const hit = entries.get(key);
|
|
25
|
+
if (!hit)
|
|
26
|
+
return undefined;
|
|
27
|
+
if (Date.now() - hit.at >= ttlMs) {
|
|
28
|
+
entries.delete(key); // evict on expired read — do not retain forever
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
entries.delete(key); // re-insert to mark most-recently-used
|
|
32
|
+
entries.set(key, hit);
|
|
33
|
+
return hit.data;
|
|
34
|
+
},
|
|
35
|
+
set(key, data) {
|
|
36
|
+
entries.delete(key);
|
|
37
|
+
entries.set(key, { at: Date.now(), data });
|
|
38
|
+
while (entries.size > maxEntries) {
|
|
39
|
+
const oldest = entries.keys().next();
|
|
40
|
+
if (oldest.done)
|
|
41
|
+
break;
|
|
42
|
+
entries.delete(oldest.value);
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
inflight(key, run) {
|
|
46
|
+
const existing = pending.get(key);
|
|
47
|
+
if (existing)
|
|
48
|
+
return existing;
|
|
49
|
+
const p = run().finally(() => pending.delete(key));
|
|
50
|
+
pending.set(key, p);
|
|
51
|
+
return p;
|
|
52
|
+
},
|
|
53
|
+
clear() {
|
|
54
|
+
entries.clear();
|
|
55
|
+
},
|
|
56
|
+
get size() {
|
|
57
|
+
return entries.size;
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/** Process-wide fallback used when no per-instance cache is supplied. Bounded and
|
|
62
|
+
* evicting, but PREFER a per-instance cache (createProxy makes one for you). */
|
|
63
|
+
export const defaultCache = createCache();
|
|
64
|
+
/** Stable, non-secret-bearing cache key: method + fully-resolved URL + a hash of
|
|
65
|
+
* the request headers. Two callers with different bearer tokens must never share
|
|
66
|
+
* an entry, but the token itself must not sit in a key we may log. */
|
|
67
|
+
export function cacheKeyFor(method, url, headers) {
|
|
68
|
+
const canonical = Object.keys(headers)
|
|
69
|
+
.sort()
|
|
70
|
+
.map((k) => `${k}:${headers[k]}`)
|
|
71
|
+
.join('\n');
|
|
72
|
+
// djb2 — not cryptographic; enough to separate credential scopes in-process.
|
|
73
|
+
let h = 5381;
|
|
74
|
+
for (let i = 0; i < canonical.length; i++)
|
|
75
|
+
h = ((h << 5) + h + canonical.charCodeAt(i)) | 0;
|
|
76
|
+
return `${method} ${url} #${(h >>> 0).toString(36)}`;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from './types.js';
|
|
2
2
|
export { interpolate, interpolateDeep, getPath } from './template.js';
|
|
3
|
-
export { runSource } from './run.js';
|
|
3
|
+
export { runSource, DEFAULT_TIMEOUT_MS, DEFAULT_MAX_BYTES } from './run.js';
|
|
4
4
|
export { createProxy, type ProxyConfig } from './proxy.js';
|
|
5
|
+
export { createCache, defaultCache, cacheKeyFor, DEFAULT_MAX_ENTRIES } from './cache.js';
|
|
5
6
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACtE,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACtE,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC5E,OAAO,EAAE,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,YAAY,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Call any API by config; keep secrets server-only behind an SSRF-guarded proxy.
|
|
3
3
|
export * from './types.js';
|
|
4
4
|
export { interpolate, interpolateDeep, getPath } from './template.js';
|
|
5
|
-
export { runSource } from './run.js';
|
|
5
|
+
export { runSource, DEFAULT_TIMEOUT_MS, DEFAULT_MAX_BYTES } from './run.js';
|
|
6
6
|
export { createProxy } from './proxy.js';
|
|
7
|
+
export { createCache, defaultCache, cacheKeyFor, DEFAULT_MAX_ENTRIES } from './cache.js';
|
|
7
8
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,iFAAiF;AACjF,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACtE,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,iFAAiF;AACjF,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACtE,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAC5E,OAAO,EAAE,WAAW,EAAoB,MAAM,YAAY,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/proxy.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DataSource, RunResult } from './types.js';
|
|
1
|
+
import type { DataSource, ResponseCache, RunResult } from './types.js';
|
|
2
2
|
export interface ProxyConfig {
|
|
3
3
|
sources: Record<string, DataSource>;
|
|
4
4
|
/** Server-only secret values referenced as {{secret.NAME}}. Never leaves the server. */
|
|
@@ -14,6 +14,18 @@ export interface ProxyConfig {
|
|
|
14
14
|
onError?: (sourceId: string, result: RunResult) => void;
|
|
15
15
|
/** Max redirect hops the SSRF guard follows before failing (default 5). */
|
|
16
16
|
maxRedirects?: number;
|
|
17
|
+
/**
|
|
18
|
+
* Caller authorization for `handleRequest`. Return false to reject with 403.
|
|
19
|
+
*
|
|
20
|
+
* The SSRF allow-list governs where a request GOES, never WHO may trigger it.
|
|
21
|
+
* `handleRequest` authenticates nobody, and every call spends the server's
|
|
22
|
+
* secret-authenticated upstream quota — so a source returning personal data
|
|
23
|
+
* needs this hook (or host-level auth in front of the route), plus rate limiting.
|
|
24
|
+
*/
|
|
25
|
+
authorize?: (req: Request, sourceId: string, params: Record<string, unknown>) => boolean | Promise<boolean>;
|
|
26
|
+
/** Response cache. Defaults to a fresh per-proxy instance — never shared across
|
|
27
|
+
* proxies, so two tenants cannot read each other's cached responses. */
|
|
28
|
+
cache?: ResponseCache;
|
|
17
29
|
}
|
|
18
30
|
/**
|
|
19
31
|
* A server-side proxy: resolves a named source with secrets injected, enforcing a
|
|
@@ -28,5 +40,6 @@ export interface ProxyConfig {
|
|
|
28
40
|
export declare function createProxy(config: ProxyConfig): {
|
|
29
41
|
resolve: (sourceId: string, params?: Record<string, unknown>) => Promise<RunResult>;
|
|
30
42
|
handleRequest: (req: Request) => Promise<Response>;
|
|
43
|
+
clearCache: () => void;
|
|
31
44
|
};
|
|
32
45
|
//# sourceMappingURL=proxy.d.ts.map
|
package/dist/proxy.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"proxy.d.ts","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAKvE,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACpC,wFAAwF;IACxF,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,yFAAyF;IACzF,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,KAAK,IAAI,CAAC;IACxD,2EAA2E;IAC3E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5G;6EACyE;IACzE,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB;AA2BD;;;;;;;;;GASG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW;wBAMZ,MAAM,WAAU,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAQ,OAAO,CAAC,SAAS,CAAC;yBA8BhE,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC;sBAmCtC,IAAI;EAK5B"}
|
package/dist/proxy.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { interpolate } from './template.js';
|
|
2
2
|
import { runSource } from './run.js';
|
|
3
|
+
import { createCache } from './cache.js';
|
|
3
4
|
/** Generic, non-revealing client message for a failed status. */
|
|
4
5
|
function genericError(status) {
|
|
5
6
|
if (status === 400)
|
|
@@ -41,12 +42,18 @@ function hostAllowed(rawUrl, allow) {
|
|
|
41
42
|
* multi-tenant input without adding IP-pinning + a security review.
|
|
42
43
|
*/
|
|
43
44
|
export function createProxy(config) {
|
|
45
|
+
// One cache per proxy. The old module-global map was keyed by `source.id`
|
|
46
|
+
// alone, so two proxies both declaring a source named "users" served each
|
|
47
|
+
// other's cached responses.
|
|
48
|
+
const cache = config.cache ?? createCache();
|
|
44
49
|
async function resolve(sourceId, params = {}) {
|
|
45
50
|
const source = config.sources[sourceId];
|
|
46
51
|
if (!source)
|
|
47
52
|
return { ok: false, status: 404, error: `unknown source "${sourceId}"` };
|
|
48
|
-
//
|
|
49
|
-
//
|
|
53
|
+
// Cheap pre-check on the secret-stripped URL: reject an off-list host before
|
|
54
|
+
// any secret is interpolated. NOT sufficient on its own — a secret sitting in
|
|
55
|
+
// the host position would pass this and then be fetched — so `runSource` also
|
|
56
|
+
// re-validates the fully-resolved URL immediately before fetch.
|
|
50
57
|
const withoutSecrets = source.url.replace(/\{\{\s*secret\.[\w.$-]+\s*\}\}/g, '');
|
|
51
58
|
const checkUrl = interpolate(withoutSecrets, { params, secret: {} });
|
|
52
59
|
if (!hostAllowed(checkUrl, config.allowHosts))
|
|
@@ -55,6 +62,9 @@ export function createProxy(config) {
|
|
|
55
62
|
params,
|
|
56
63
|
secrets: config.secrets,
|
|
57
64
|
fetchImpl: config.fetchImpl,
|
|
65
|
+
cache,
|
|
66
|
+
// Authoritative check against the URL actually fetched, secrets included.
|
|
67
|
+
allowRequestHost: (u) => hostAllowed(u, config.allowHosts),
|
|
58
68
|
// Re-validate every redirect hop against the SAME allow-list so a 30x can't
|
|
59
69
|
// escape it to a private IP / off-list host.
|
|
60
70
|
allowRedirectHost: (u) => hostAllowed(u, config.allowHosts),
|
|
@@ -62,8 +72,13 @@ export function createProxy(config) {
|
|
|
62
72
|
});
|
|
63
73
|
}
|
|
64
74
|
/** Web-standard handler: POST { sourceId, params } → JSON RunResult. Mount in a
|
|
65
|
-
* Next route handler, a Cloudflare Worker, an Express adapter, etc.
|
|
75
|
+
* Next route handler, a Cloudflare Worker, an Express adapter, etc.
|
|
76
|
+
*
|
|
77
|
+
* Only sources declaring `expose: true` are reachable, and `config.authorize`
|
|
78
|
+
* (if set) gates every call. This handler has no rate limiting of its own —
|
|
79
|
+
* put the host's in front of it. */
|
|
66
80
|
async function handleRequest(req) {
|
|
81
|
+
const json = (body, status) => new Response(JSON.stringify(body), { status, headers: { 'content-type': 'application/json', 'cache-control': 'no-store' } });
|
|
67
82
|
let payload = {};
|
|
68
83
|
try {
|
|
69
84
|
payload = (await req.json());
|
|
@@ -71,13 +86,18 @@ export function createProxy(config) {
|
|
|
71
86
|
catch {
|
|
72
87
|
/* invalid body → handled below */
|
|
73
88
|
}
|
|
74
|
-
if (!payload.sourceId)
|
|
75
|
-
return
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
89
|
+
if (!payload.sourceId)
|
|
90
|
+
return json({ ok: false, status: 400, error: 'missing sourceId' }, 400);
|
|
91
|
+
const params = payload.params ?? {};
|
|
92
|
+
// Same response for "no such source" and "not exposed": do not let a caller
|
|
93
|
+
// enumerate which sources exist.
|
|
94
|
+
const source = config.sources[payload.sourceId];
|
|
95
|
+
if (!source || source.expose !== true)
|
|
96
|
+
return json({ ok: false, status: 404, error: 'not found' }, 404);
|
|
97
|
+
if (config.authorize && !(await config.authorize(req, payload.sourceId, params))) {
|
|
98
|
+
return json({ ok: false, status: 403, error: 'forbidden' }, 403);
|
|
79
99
|
}
|
|
80
|
-
const result = await resolve(payload.sourceId,
|
|
100
|
+
const result = await resolve(payload.sourceId, params);
|
|
81
101
|
// Redact before crossing to the client: the raw `error` (fetch messages,
|
|
82
102
|
// resolved URLs, upstream bodies) can leak internal hosts or secret-bearing
|
|
83
103
|
// URLs. Log the detail server-side, return a generic message.
|
|
@@ -86,11 +106,12 @@ export function createProxy(config) {
|
|
|
86
106
|
config.onError?.(payload.sourceId, result);
|
|
87
107
|
client = { ok: false, status: result.status, error: genericError(result.status) };
|
|
88
108
|
}
|
|
89
|
-
return
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
109
|
+
return json(client, result.ok ? 200 : result.status || 500);
|
|
110
|
+
}
|
|
111
|
+
/** Drop every cached response held by this proxy (erasure of cached personal data). */
|
|
112
|
+
function clearCache() {
|
|
113
|
+
cache.clear();
|
|
93
114
|
}
|
|
94
|
-
return { resolve, handleRequest };
|
|
115
|
+
return { resolve, handleRequest, clearCache };
|
|
95
116
|
}
|
|
96
117
|
//# sourceMappingURL=proxy.js.map
|
package/dist/proxy.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"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"}
|
package/dist/run.d.ts
CHANGED
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
import type { DataSource, RunContext, RunResult } from './types.js';
|
|
2
|
+
/** Applied whenever `source.timeoutMs` is unset. An absent timeout let a hung or
|
|
3
|
+
* slow-loris upstream hold a request for the platform default (~300s), multiplied
|
|
4
|
+
* by every retry — behind a publicly mounted proxy that exhausts sockets. */
|
|
5
|
+
export declare const DEFAULT_TIMEOUT_MS = 10000;
|
|
6
|
+
/** Cap on a buffered upstream response body. The SSRF allow-list constrains WHERE
|
|
7
|
+
* a request goes, not how much it returns; without a cap one large (or endless)
|
|
8
|
+
* response is fully buffered per concurrent request. */
|
|
9
|
+
export declare const DEFAULT_MAX_BYTES: number;
|
|
2
10
|
/**
|
|
3
|
-
* Resolve a data source: build the request from config, fetch (with
|
|
4
|
-
*
|
|
11
|
+
* Resolve a data source: build the request from config, fetch (with timeout,
|
|
12
|
+
* jittered retry, SSRF re-validation), extract via `transform`, and cache
|
|
13
|
+
* successful GETs for `cache.ttlMs`.
|
|
14
|
+
*
|
|
5
15
|
* Works client- or server-side; pass `secrets` only on the server.
|
|
6
16
|
*/
|
|
7
17
|
export declare function runSource<T = unknown>(source: DataSource, ctx?: RunContext): Promise<RunResult<T>>;
|
package/dist/run.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAIpE;;8EAE8E;AAC9E,eAAO,MAAM,kBAAkB,QAAS,CAAC;AACzC;;yDAEyD;AACzD,eAAO,MAAM,iBAAiB,QAAkB,CAAC;AAkFjD;;;;;;GAMG;AACH,wBAAsB,SAAS,CAAC,CAAC,GAAG,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,GAAE,UAAe,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAgI5G"}
|
package/dist/run.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { interpolate, interpolateDeep, getPath } from './template.js';
|
|
2
|
-
|
|
2
|
+
import { defaultCache, cacheKeyFor } from './cache.js';
|
|
3
|
+
/** Applied whenever `source.timeoutMs` is unset. An absent timeout let a hung or
|
|
4
|
+
* slow-loris upstream hold a request for the platform default (~300s), multiplied
|
|
5
|
+
* by every retry — behind a publicly mounted proxy that exhausts sockets. */
|
|
6
|
+
export const DEFAULT_TIMEOUT_MS = 10_000;
|
|
7
|
+
/** Cap on a buffered upstream response body. The SSRF allow-list constrains WHERE
|
|
8
|
+
* a request goes, not how much it returns; without a cap one large (or endless)
|
|
9
|
+
* response is fully buffered per concurrent request. */
|
|
10
|
+
export const DEFAULT_MAX_BYTES = 5 * 1024 * 1024;
|
|
11
|
+
const IDEMPOTENT = new Set(['GET', 'HEAD', 'OPTIONS']);
|
|
3
12
|
function buildUrl(source, scope) {
|
|
4
13
|
let url = interpolate(source.url, scope);
|
|
5
14
|
const entries = [];
|
|
@@ -29,114 +38,220 @@ function buildHeaders(source, scope) {
|
|
|
29
38
|
h[a.header.toLowerCase()] = interpolate(a.key ?? '', scope);
|
|
30
39
|
return h;
|
|
31
40
|
}
|
|
41
|
+
/** Only a network error, a timeout, 408, 429 or a 5xx can succeed on a retry.
|
|
42
|
+
* Re-sending a 400/401/403/404 is guaranteed-futile load on a struggling upstream. */
|
|
43
|
+
function isRetryableStatus(status) {
|
|
44
|
+
return status === 408 || status === 429 || status >= 500;
|
|
45
|
+
}
|
|
46
|
+
/** `Retry-After: <seconds>` or an HTTP-date. Returns ms, or undefined. */
|
|
47
|
+
function retryAfterMs(res) {
|
|
48
|
+
const raw = res.headers.get('retry-after');
|
|
49
|
+
if (!raw)
|
|
50
|
+
return undefined;
|
|
51
|
+
const secs = Number(raw);
|
|
52
|
+
if (Number.isFinite(secs))
|
|
53
|
+
return Math.max(0, secs * 1000);
|
|
54
|
+
const when = Date.parse(raw);
|
|
55
|
+
if (Number.isFinite(when))
|
|
56
|
+
return Math.max(0, when - Date.now());
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
/** Full jitter (AWS): random in [0, base * 2^attempt]. A fixed linear backoff made
|
|
60
|
+
* a fleet of clients retry in lockstep against an upstream that was already down. */
|
|
61
|
+
function jitteredBackoff(base, attempt) {
|
|
62
|
+
const ceiling = Math.min(base * 2 ** attempt, 30_000);
|
|
63
|
+
return Math.random() * ceiling;
|
|
64
|
+
}
|
|
65
|
+
/** Read a response body with a hard byte cap, streaming where possible so an
|
|
66
|
+
* endless body is abandoned rather than buffered. */
|
|
67
|
+
async function readCapped(res, maxBytes) {
|
|
68
|
+
const declared = Number(res.headers.get('content-length'));
|
|
69
|
+
if (Number.isFinite(declared) && declared > maxBytes)
|
|
70
|
+
throw new Error('response too large');
|
|
71
|
+
if (!res.body) {
|
|
72
|
+
const text = await res.text();
|
|
73
|
+
if (text.length > maxBytes)
|
|
74
|
+
throw new Error('response too large');
|
|
75
|
+
return text;
|
|
76
|
+
}
|
|
77
|
+
const reader = res.body.getReader();
|
|
78
|
+
const chunks = [];
|
|
79
|
+
let total = 0;
|
|
80
|
+
for (;;) {
|
|
81
|
+
const { done, value } = await reader.read();
|
|
82
|
+
if (done)
|
|
83
|
+
break;
|
|
84
|
+
total += value.byteLength;
|
|
85
|
+
if (total > maxBytes) {
|
|
86
|
+
await reader.cancel();
|
|
87
|
+
throw new Error('response too large');
|
|
88
|
+
}
|
|
89
|
+
chunks.push(value);
|
|
90
|
+
}
|
|
91
|
+
const buf = new Uint8Array(total);
|
|
92
|
+
let off = 0;
|
|
93
|
+
for (const c of chunks) {
|
|
94
|
+
buf.set(c, off);
|
|
95
|
+
off += c.byteLength;
|
|
96
|
+
}
|
|
97
|
+
return new TextDecoder().decode(buf);
|
|
98
|
+
}
|
|
32
99
|
/**
|
|
33
|
-
* Resolve a data source: build the request from config, fetch (with
|
|
34
|
-
*
|
|
100
|
+
* Resolve a data source: build the request from config, fetch (with timeout,
|
|
101
|
+
* jittered retry, SSRF re-validation), extract via `transform`, and cache
|
|
102
|
+
* successful GETs for `cache.ttlMs`.
|
|
103
|
+
*
|
|
35
104
|
* Works client- or server-side; pass `secrets` only on the server.
|
|
36
105
|
*/
|
|
37
106
|
export async function runSource(source, ctx = {}) {
|
|
107
|
+
const started = Date.now();
|
|
38
108
|
const scope = { params: ctx.params ?? {}, secret: ctx.secrets ?? {} };
|
|
39
109
|
const method = source.method ?? 'GET';
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
110
|
+
const timeoutMs = source.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
111
|
+
const maxBytes = source.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
112
|
+
let url;
|
|
113
|
+
try {
|
|
114
|
+
url = buildUrl(source, scope);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return { ok: false, status: 0, error: 'invalid url', attempts: 0, durationMs: Date.now() - started };
|
|
45
118
|
}
|
|
46
|
-
const url = buildUrl(source, scope);
|
|
47
119
|
const headers = buildHeaders(source, scope);
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (typeof b !== 'string' && !headers['content-type'])
|
|
53
|
-
headers['content-type'] = 'application/json';
|
|
120
|
+
// SSRF: validate the URL we are ACTUALLY about to fetch — after secrets are
|
|
121
|
+
// interpolated — not a secret-stripped surrogate checked by the caller.
|
|
122
|
+
if (ctx.allowRequestHost && !ctx.allowRequestHost(url)) {
|
|
123
|
+
return { ok: false, status: 403, error: 'host not allowed', attempts: 0, durationMs: Date.now() - started };
|
|
54
124
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
125
|
+
// Key on the resolved request, so two sources sharing an `id` — or two tenants
|
|
126
|
+
// with different credentials — can never read each other's entries.
|
|
127
|
+
const cache = ctx.cache ?? defaultCache;
|
|
128
|
+
const ttlMs = source.cache?.ttlMs;
|
|
129
|
+
const useCache = !!ttlMs && method === 'GET';
|
|
130
|
+
const key = useCache ? cacheKeyFor(method, url, headers) : '';
|
|
131
|
+
if (useCache) {
|
|
132
|
+
const hit = cache.get(key, ttlMs);
|
|
133
|
+
if (hit !== undefined)
|
|
134
|
+
return { ok: true, status: 200, data: hit, cached: true, attempts: 0, durationMs: Date.now() - started };
|
|
135
|
+
}
|
|
136
|
+
const exec = async () => {
|
|
137
|
+
// Re-check after winning the single-flight race: the first caller may have
|
|
138
|
+
// already populated the entry while we queued behind it.
|
|
139
|
+
if (useCache) {
|
|
140
|
+
const hit = cache.get(key, ttlMs);
|
|
141
|
+
if (hit !== undefined)
|
|
142
|
+
return { ok: true, status: 200, data: hit, cached: true, attempts: 0, durationMs: Date.now() - started };
|
|
143
|
+
}
|
|
144
|
+
let body;
|
|
145
|
+
if (source.body !== undefined && method !== 'GET' && method !== 'HEAD') {
|
|
146
|
+
const b = interpolateDeep(source.body, scope);
|
|
147
|
+
body = typeof b === 'string' ? b : JSON.stringify(b);
|
|
148
|
+
if (typeof b !== 'string' && !headers['content-type'])
|
|
149
|
+
headers['content-type'] = 'application/json';
|
|
150
|
+
}
|
|
151
|
+
const f = ctx.fetchImpl ?? fetch;
|
|
152
|
+
const requested = Math.max(1, source.retry?.attempts ?? 1);
|
|
153
|
+
// A non-idempotent verb is not retried unless the caller asserts it is safe:
|
|
154
|
+
// a write that landed upstream but timed out here would otherwise run twice.
|
|
155
|
+
const canRetry = IDEMPOTENT.has(method) || source.retry?.retryNonIdempotent === true;
|
|
156
|
+
const attempts = canRetry ? requested : 1;
|
|
157
|
+
const backoff = source.retry?.backoffMs ?? 300;
|
|
158
|
+
const guardRedirects = typeof ctx.allowRedirectHost === 'function';
|
|
159
|
+
const maxRedirects = Math.max(0, ctx.maxRedirects ?? 5);
|
|
160
|
+
let last = { ok: false, status: 0, error: 'not run' };
|
|
161
|
+
let tried = 0;
|
|
162
|
+
for (let i = 0; i < attempts; i++) {
|
|
163
|
+
tried = i + 1;
|
|
164
|
+
// Always abort: an unset timeoutMs previously meant no AbortController at all.
|
|
165
|
+
const ctrl = new AbortController();
|
|
166
|
+
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
167
|
+
let waitMs;
|
|
168
|
+
try {
|
|
169
|
+
// Redirect handling. Default: let the platform follow (redirect:'follow').
|
|
170
|
+
// When an SSRF guard is supplied, follow MANUALLY and re-validate each hop —
|
|
171
|
+
// an allow-listed host can otherwise 30x-redirect to a private IP / off-list
|
|
172
|
+
// host and the platform would follow it, bypassing the allow-list.
|
|
173
|
+
const originHost = (() => { try {
|
|
174
|
+
return new URL(url).host;
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
return '';
|
|
178
|
+
} })();
|
|
179
|
+
let curUrl = url;
|
|
180
|
+
let curMethod = method;
|
|
181
|
+
const curHeaders = { ...headers };
|
|
182
|
+
let curBody = body;
|
|
183
|
+
let res = await f(curUrl, {
|
|
184
|
+
method: curMethod, headers: curHeaders, body: curBody,
|
|
185
|
+
signal: ctrl.signal, redirect: guardRedirects ? 'manual' : 'follow',
|
|
186
|
+
});
|
|
187
|
+
if (guardRedirects) {
|
|
188
|
+
let hops = 0;
|
|
189
|
+
while (res.status >= 300 && res.status < 400 && res.headers.get('location')) {
|
|
190
|
+
if (hops++ >= maxRedirects) {
|
|
88
191
|
clearTimeout(timer);
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (timer)
|
|
192
|
+
return { ok: false, status: 0, error: 'too many redirects', attempts: tried, durationMs: Date.now() - started };
|
|
193
|
+
}
|
|
194
|
+
let loc;
|
|
195
|
+
try {
|
|
196
|
+
loc = new URL(res.headers.get('location'), curUrl).toString();
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
97
199
|
clearTimeout(timer);
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (timer)
|
|
200
|
+
return { ok: false, status: 0, error: 'invalid redirect location', attempts: tried, durationMs: Date.now() - started };
|
|
201
|
+
}
|
|
202
|
+
if (!ctx.allowRedirectHost(loc)) {
|
|
102
203
|
clearTimeout(timer);
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
204
|
+
return { ok: false, status: 403, error: 'redirect host not allowed', attempts: tried, durationMs: Date.now() - started };
|
|
205
|
+
}
|
|
206
|
+
// Cross-host hop → drop credentials so bearer/cookie can't leak off-origin.
|
|
207
|
+
if (new URL(loc).host !== originHost) {
|
|
208
|
+
delete curHeaders['authorization'];
|
|
209
|
+
delete curHeaders['cookie'];
|
|
210
|
+
}
|
|
211
|
+
// 303 (and 301/302 on a non-idempotent method) → switch to GET, drop body;
|
|
212
|
+
// 307/308 preserve method + body.
|
|
213
|
+
if (res.status !== 307 && res.status !== 308 && curMethod !== 'GET' && curMethod !== 'HEAD') {
|
|
214
|
+
curMethod = 'GET';
|
|
215
|
+
curBody = undefined;
|
|
216
|
+
}
|
|
217
|
+
curUrl = loc;
|
|
218
|
+
res = await f(curUrl, { method: curMethod, headers: curHeaders, body: curBody, signal: ctrl.signal, redirect: 'manual' });
|
|
109
219
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
220
|
+
}
|
|
221
|
+
if (!res.ok) {
|
|
222
|
+
clearTimeout(timer);
|
|
223
|
+
last = { ok: false, status: res.status, error: `HTTP ${res.status}`, attempts: tried, durationMs: Date.now() - started };
|
|
224
|
+
if (!isRetryableStatus(res.status))
|
|
225
|
+
return last; // futile to re-send
|
|
226
|
+
waitMs = retryAfterMs(res); // honour the upstream's own pacing
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
const text = await readCapped(res, maxBytes);
|
|
230
|
+
clearTimeout(timer);
|
|
231
|
+
const ct = res.headers.get('content-type') ?? '';
|
|
232
|
+
const raw = ct.includes('json') ? (text ? JSON.parse(text) : undefined) : text;
|
|
233
|
+
const data = (source.transform ? getPath(raw, source.transform) : raw);
|
|
234
|
+
if (useCache)
|
|
235
|
+
cache.set(key, data);
|
|
236
|
+
return { ok: true, status: res.status, data, cached: false, attempts: tried, durationMs: Date.now() - started };
|
|
118
237
|
}
|
|
119
238
|
}
|
|
120
|
-
|
|
239
|
+
catch (e) {
|
|
121
240
|
clearTimeout(timer);
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
memo.set(cacheKey, { at: Date.now(), data });
|
|
129
|
-
return last;
|
|
241
|
+
const aborted = e instanceof Error && e.name === 'AbortError';
|
|
242
|
+
last = {
|
|
243
|
+
ok: false, status: 0,
|
|
244
|
+
error: aborted ? `timeout after ${timeoutMs}ms` : e instanceof Error ? e.message : 'fetch failed',
|
|
245
|
+
attempts: tried, durationMs: Date.now() - started,
|
|
246
|
+
};
|
|
130
247
|
}
|
|
248
|
+
if (i < attempts - 1)
|
|
249
|
+
await new Promise((r) => setTimeout(r, waitMs ?? jitteredBackoff(backoff, i)));
|
|
131
250
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if (i < attempts - 1)
|
|
138
|
-
await new Promise((r) => setTimeout(r, backoff * (i + 1)));
|
|
139
|
-
}
|
|
140
|
-
return last;
|
|
251
|
+
return { ...last, attempts: tried, durationMs: Date.now() - started };
|
|
252
|
+
};
|
|
253
|
+
// Share one in-flight upstream request per cache key: when a hot key expires,
|
|
254
|
+
// concurrent callers await the same promise instead of stampeding the upstream.
|
|
255
|
+
return useCache ? cache.inflight(key, exec) : exec();
|
|
141
256
|
}
|
|
142
257
|
//# sourceMappingURL=run.js.map
|
package/dist/run.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.js","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -14,10 +14,32 @@ export interface AuthConfig {
|
|
|
14
14
|
export interface RetryConfig {
|
|
15
15
|
attempts?: number;
|
|
16
16
|
backoffMs?: number;
|
|
17
|
+
/**
|
|
18
|
+
* Retry non-idempotent verbs (POST/PUT/PATCH/DELETE). Default false.
|
|
19
|
+
*
|
|
20
|
+
* A write that SUCCEEDED upstream but timed out client-side is indistinguishable
|
|
21
|
+
* from one that never landed, so retrying it silently duplicates the write. Only
|
|
22
|
+
* enable when the upstream is idempotent (e.g. you send an idempotency key).
|
|
23
|
+
*/
|
|
24
|
+
retryNonIdempotent?: boolean;
|
|
17
25
|
}
|
|
18
26
|
export interface CacheConfig {
|
|
19
27
|
ttlMs?: number;
|
|
20
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* A bounded, TTL-evicting response cache with single-flight. Create one PER
|
|
31
|
+
* proxy/tenant: entries are keyed by the fully-resolved request (method + URL +
|
|
32
|
+
* a hash of auth headers), never by `source.id` alone.
|
|
33
|
+
*/
|
|
34
|
+
export interface ResponseCache {
|
|
35
|
+
get(key: string, ttlMs: number): unknown | undefined;
|
|
36
|
+
set(key: string, data: unknown): void;
|
|
37
|
+
/** Share one in-flight request per key (stops a TTL-expiry stampede). */
|
|
38
|
+
inflight<T>(key: string, run: () => Promise<T>): Promise<T>;
|
|
39
|
+
/** Drop all entries — required for erasure of cached personal data. */
|
|
40
|
+
clear(): void;
|
|
41
|
+
readonly size: number;
|
|
42
|
+
}
|
|
21
43
|
/** A declarative data source. url/headers/query/body may contain {{params.x}}
|
|
22
44
|
* and (server-side only) {{secret.X}} template references. */
|
|
23
45
|
export interface DataSource {
|
|
@@ -28,9 +50,20 @@ export interface DataSource {
|
|
|
28
50
|
query?: Record<string, string>;
|
|
29
51
|
body?: unknown;
|
|
30
52
|
auth?: AuthConfig;
|
|
53
|
+
/** Per-request timeout. Defaults to `DEFAULT_TIMEOUT_MS` (10s) — never unbounded. */
|
|
31
54
|
timeoutMs?: number;
|
|
32
55
|
retry?: RetryConfig;
|
|
33
56
|
cache?: CacheConfig;
|
|
57
|
+
/** Cap on the upstream response body in bytes. Default `DEFAULT_MAX_BYTES` (5 MiB). */
|
|
58
|
+
maxBytes?: number;
|
|
59
|
+
/**
|
|
60
|
+
* Reachable through `createProxy().handleRequest`? Default FALSE.
|
|
61
|
+
*
|
|
62
|
+
* `handleRequest` performs no authentication of its own, and each call spends the
|
|
63
|
+
* server's secret-authenticated upstream quota. Opting a source in makes it
|
|
64
|
+
* invokable by any client that can reach the route, with attacker-chosen params.
|
|
65
|
+
*/
|
|
66
|
+
expose?: boolean;
|
|
34
67
|
/** Dot/bracket path into the JSON response to extract, e.g. "data.items". */
|
|
35
68
|
transform?: string;
|
|
36
69
|
}
|
|
@@ -50,11 +83,27 @@ export interface RunContext {
|
|
|
50
83
|
allowRedirectHost?: (url: string) => boolean;
|
|
51
84
|
/** Max redirect hops to follow when `allowRedirectHost` is set (default 5). */
|
|
52
85
|
maxRedirects?: number;
|
|
86
|
+
/**
|
|
87
|
+
* SSRF hardening for the INITIAL request. Checked against the fully-resolved URL
|
|
88
|
+
* — after `{{secret.X}}` interpolation — immediately before fetch. The proxy used
|
|
89
|
+
* to allow-list a secret-STRIPPED surrogate while `runSource` fetched the real
|
|
90
|
+
* one, so a secret in the host position (`api.example.com{{secret.SUFFIX}}`)
|
|
91
|
+
* passed the check and reached a different host.
|
|
92
|
+
*/
|
|
93
|
+
allowRequestHost?: (url: string) => boolean;
|
|
94
|
+
/** Per-instance response cache. Omit to use the process-wide default cache. */
|
|
95
|
+
cache?: ResponseCache;
|
|
53
96
|
}
|
|
54
97
|
export interface RunResult<T = unknown> {
|
|
55
98
|
ok: boolean;
|
|
56
99
|
status: number;
|
|
57
100
|
data?: T;
|
|
58
101
|
error?: string;
|
|
102
|
+
/** Observability: how many attempts were made (1 = no retry). */
|
|
103
|
+
attempts?: number;
|
|
104
|
+
/** Observability: served from cache without an upstream request. */
|
|
105
|
+
cached?: boolean;
|
|
106
|
+
/** Observability: wall-clock ms for the whole resolve. */
|
|
107
|
+
durationMs?: number;
|
|
59
108
|
}
|
|
60
109
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;AAExF,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;IACpC,kFAAkF;IAClF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kBAAkB;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,+EAA+E;IAC/E,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noidmejs/atomkit-http",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Declarative HTTP/backend connector: call any API by config (all verbs, headers, query, body, auth, retries, cache, transforms). Secrets stay server-only via a proxy with an SSRF host allow-list + per-redirect-hop re-validation.",
|
|
5
5
|
"keywords": ["atomkit", "http", "connector", "declarative", "fetch", "api", "proxy", "ssrf", "no-code"],
|
|
6
6
|
"license": "MIT",
|
|
@@ -14,12 +14,12 @@
|
|
|
14
14
|
"scripts": {
|
|
15
15
|
"build": "tsc -p tsconfig.json",
|
|
16
16
|
"test": "node test/http.test.mjs",
|
|
17
|
-
"prepublishOnly": "npm run build"
|
|
17
|
+
"prepublishOnly": "npm run build && npm test"
|
|
18
18
|
},
|
|
19
|
-
"devDependencies": { "typescript": "^
|
|
19
|
+
"devDependencies": { "typescript": "^7.0.0", "@types/node": "^22.0.0" },
|
|
20
20
|
"publishConfig": { "access": "public" },
|
|
21
21
|
"repository": { "type": "git", "url": "git+https://github.com/noidme-git/atomkit-http.git" },
|
|
22
22
|
"bugs": { "url": "https://github.com/noidme-git/atomkit-http/issues" },
|
|
23
23
|
"homepage": "https://github.com/noidme-git/atomkit-http#readme",
|
|
24
|
-
"engines": { "node": ">=
|
|
24
|
+
"engines": { "node": ">=22" }
|
|
25
25
|
}
|