@hostwebhook/node-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/code-runner.d.ts +20 -0
- package/dist/code-runner.js +138 -0
- package/dist/contratos.d.ts +121 -0
- package/dist/contratos.js +24 -0
- package/dist/dto/output-node.dto.d.ts +19 -0
- package/dist/dto/output-node.dto.js +96 -0
- package/dist/ensure-meta.d.ts +22 -0
- package/dist/ensure-meta.js +35 -0
- package/dist/execute-with-iteration.d.ts +18 -0
- package/dist/execute-with-iteration.js +66 -0
- package/dist/filter-utils.d.ts +22 -0
- package/dist/filter-utils.js +178 -0
- package/dist/handler-helpers.d.ts +21 -0
- package/dist/handler-helpers.js +53 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.js +73 -0
- package/dist/log-metadata.d.ts +191 -0
- package/dist/log-metadata.js +375 -0
- package/dist/node-dispatch.registry.d.ts +32 -0
- package/dist/node-dispatch.registry.js +45 -0
- package/dist/node-executors.d.ts +299 -0
- package/dist/node-executors.js +555 -0
- package/dist/node-lifecycle.d.ts +399 -0
- package/dist/node-lifecycle.js +782 -0
- package/dist/normalize-nodes.d.ts +18 -0
- package/dist/normalize-nodes.js +22 -0
- package/dist/output-node-ref.schema.d.ts +82 -0
- package/dist/output-node-ref.schema.js +90 -0
- package/dist/output-webhook-scope.d.ts +36 -0
- package/dist/output-webhook-scope.js +42 -0
- package/dist/payload-preview.d.ts +10 -0
- package/dist/payload-preview.js +39 -0
- package/dist/pipeline.constants.d.ts +29 -0
- package/dist/pipeline.constants.js +51 -0
- package/dist/pre-request-pool.d.ts +58 -0
- package/dist/pre-request-pool.js +308 -0
- package/dist/pre-request-runner-source.d.ts +28 -0
- package/dist/pre-request-runner-source.js +411 -0
- package/dist/regex-de-inquilino.d.ts +15 -0
- package/dist/regex-de-inquilino.js +98 -0
- package/dist/request-context.d.ts +18 -0
- package/dist/request-context.js +34 -0
- package/dist/retry-transient.d.ts +54 -0
- package/dist/retry-transient.js +67 -0
- package/dist/retry-utils.d.ts +17 -0
- package/dist/retry-utils.js +23 -0
- package/dist/schema-validator-utils.d.ts +9 -0
- package/dist/schema-validator-utils.js +140 -0
- package/dist/ssrf-guard.d.ts +202 -0
- package/dist/ssrf-guard.js +917 -0
- package/dist/swallow.d.ts +52 -0
- package/dist/swallow.js +55 -0
- package/dist/template-render.d.ts +33 -0
- package/dist/template-render.js +43 -0
- package/dist/try-parse.d.ts +41 -0
- package/dist/try-parse.js +69 -0
- package/dist/workspace-payloads.d.ts +66 -0
- package/dist/workspace-payloads.js +496 -0
- package/package.json +35 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Retry helper for pipeline nodes that make external HTTP requests.
|
|
4
|
+
* Retries on transient status codes (429, 502, 503, 504), waiting as long as
|
|
5
|
+
* the server asked when it said (`NodeResult.retryAfterMs`) and falling back
|
|
6
|
+
* to linear backoff when it didn't, plus jitter either way.
|
|
7
|
+
* Fire-and-forget safe — no state, no side effects.
|
|
8
|
+
*
|
|
9
|
+
* IMPORTANT: 500 is NOT retryable by default — a 500 from our own
|
|
10
|
+
* catch block (import error, parse failure, etc.) is NOT transient.
|
|
11
|
+
* Only external gateway errors (502/503/504) and rate limits (429) are.
|
|
12
|
+
*
|
|
13
|
+
* If fn() throws an exception (instead of returning a result),
|
|
14
|
+
* the error is caught and returned as statusCode 520 (internal error)
|
|
15
|
+
* WITHOUT retrying — thrown errors are bugs, not transient failures.
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.retryOnTransientError = retryOnTransientError;
|
|
19
|
+
const DEFAULT_RETRYABLE = new Set([429, 502, 503, 504]);
|
|
20
|
+
async function retryOnTransientError(fn, opts) {
|
|
21
|
+
const maxRetries = opts?.maxRetries ?? 2;
|
|
22
|
+
const retryable = opts?.retryableStatuses ?? DEFAULT_RETRYABLE;
|
|
23
|
+
const backoffMs = opts?.backoffMs ?? 3000;
|
|
24
|
+
const label = opts?.label ?? 'Node';
|
|
25
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
26
|
+
let result;
|
|
27
|
+
try {
|
|
28
|
+
result = await fn();
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
// Thrown errors are internal bugs — never retry
|
|
32
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
33
|
+
opts?.logger?.error?.(`${label} threw an exception (not retrying): ${msg}`);
|
|
34
|
+
return {
|
|
35
|
+
statusCode: 520,
|
|
36
|
+
responseBody: JSON.stringify({ error: msg, _internalError: true }),
|
|
37
|
+
latencyMs: 0,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
if (!retryable.has(result.statusCode) || attempt === maxRetries) {
|
|
41
|
+
if (attempt > 0 && result.statusCode < 400) {
|
|
42
|
+
opts?.logger?.log?.(`${label} succeeded on retry ${attempt}`);
|
|
43
|
+
}
|
|
44
|
+
// Mark that retries were attempted (for loop error policy decisions)
|
|
45
|
+
if (attempt > 0)
|
|
46
|
+
return { ...result, _retriedTransient: true };
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
// Prefer the server's own number over our linear guess, then spread the
|
|
50
|
+
// wake-ups. Without the spread this makes bursts worse, not better: a
|
|
51
|
+
// fan-out of N items hits the same rate limit at the same instant, reads
|
|
52
|
+
// the same `retry_after`, and every one of them wakes at that same instant
|
|
53
|
+
// to collide again. The jitter is what turns a wall into a queue.
|
|
54
|
+
const stated = result.retryAfterMs;
|
|
55
|
+
const base = stated && stated > 0 ? stated : backoffMs * (attempt + 1);
|
|
56
|
+
const capped = Math.min(base, opts?.maxBackoffMs ?? 15_000);
|
|
57
|
+
const wait = capped + Math.round(Math.random() * Math.max(250, capped * 0.25));
|
|
58
|
+
opts?.logger?.warn?.(`${label} got ${result.statusCode}, retrying in ${wait}ms ` +
|
|
59
|
+
`(${attempt + 1}/${maxRetries})${stated ? ' — server asked' : ''}...`);
|
|
60
|
+
await new Promise((r) => setTimeout(r, wait));
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
statusCode: 500,
|
|
64
|
+
responseBody: '{"error":"Max retries exceeded"}',
|
|
65
|
+
latencyMs: 0,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface RetryOptions {
|
|
2
|
+
/** Max number of attempts (default 3) */
|
|
3
|
+
maxAttempts?: number;
|
|
4
|
+
/** Initial delay in ms before first retry (default 1000) */
|
|
5
|
+
initialDelayMs?: number;
|
|
6
|
+
/** Max delay cap in ms (default 60000) */
|
|
7
|
+
maxDelayMs?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Retry a function that returns { statusCode } on 429 (rate limit) or 5xx errors.
|
|
11
|
+
* Uses exponential backoff with 20% jitter.
|
|
12
|
+
*
|
|
13
|
+
* Does NOT use try/catch — our executors return statusCode, they don't throw.
|
|
14
|
+
*/
|
|
15
|
+
export declare function retryOnRateLimit<T extends {
|
|
16
|
+
statusCode: number;
|
|
17
|
+
}>(fn: () => Promise<T>, opts?: RetryOptions): Promise<T>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.retryOnRateLimit = retryOnRateLimit;
|
|
4
|
+
/**
|
|
5
|
+
* Retry a function that returns { statusCode } on 429 (rate limit) or 5xx errors.
|
|
6
|
+
* Uses exponential backoff with 20% jitter.
|
|
7
|
+
*
|
|
8
|
+
* Does NOT use try/catch — our executors return statusCode, they don't throw.
|
|
9
|
+
*/
|
|
10
|
+
async function retryOnRateLimit(fn, opts) {
|
|
11
|
+
const { maxAttempts = 5, initialDelayMs = 2000, maxDelayMs = 60000, } = opts ?? {};
|
|
12
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
13
|
+
const result = await fn();
|
|
14
|
+
const shouldRetry = result.statusCode === 429 || result.statusCode >= 500;
|
|
15
|
+
if (!shouldRetry || attempt === maxAttempts - 1)
|
|
16
|
+
return result;
|
|
17
|
+
const delay = Math.min(initialDelayMs * Math.pow(2, attempt), maxDelayMs);
|
|
18
|
+
const jitter = Math.random() * 0.2 * delay;
|
|
19
|
+
await new Promise((r) => setTimeout(r, delay + jitter));
|
|
20
|
+
}
|
|
21
|
+
// Unreachable — loop always returns on last attempt
|
|
22
|
+
throw new Error('retryOnRateLimit: unexpected exit');
|
|
23
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SchemaField } from './contratos';
|
|
2
|
+
export interface SchemaValidationResult {
|
|
3
|
+
valid: boolean;
|
|
4
|
+
errors: {
|
|
5
|
+
path: string;
|
|
6
|
+
message: string;
|
|
7
|
+
}[];
|
|
8
|
+
}
|
|
9
|
+
export declare function validateSchema(fields: SchemaField[], payload: unknown, strictMode: boolean): SchemaValidationResult;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateSchema = validateSchema;
|
|
4
|
+
const regex_de_inquilino_1 = require("./regex-de-inquilino");
|
|
5
|
+
function getNestedValue(obj, path) {
|
|
6
|
+
// Split by . but also handle [N] bracket notation: "documents[0].size" → ["documents", "0", "size"]
|
|
7
|
+
const segments = path
|
|
8
|
+
.split(/\.|\[(\d+)\]/)
|
|
9
|
+
.filter((s) => s !== '' && s !== undefined);
|
|
10
|
+
return segments.reduce((curr, key) => {
|
|
11
|
+
if (curr === null || curr === undefined)
|
|
12
|
+
return undefined;
|
|
13
|
+
if (Array.isArray(curr) && /^\d+$/.test(key))
|
|
14
|
+
return curr[Number(key)];
|
|
15
|
+
if (typeof curr === 'object')
|
|
16
|
+
return curr[key];
|
|
17
|
+
return undefined;
|
|
18
|
+
}, obj);
|
|
19
|
+
}
|
|
20
|
+
function checkType(value, type) {
|
|
21
|
+
if (type === 'any')
|
|
22
|
+
return true;
|
|
23
|
+
switch (type) {
|
|
24
|
+
case 'string':
|
|
25
|
+
return typeof value === 'string';
|
|
26
|
+
case 'number':
|
|
27
|
+
return typeof value === 'number' && !isNaN(value);
|
|
28
|
+
case 'boolean':
|
|
29
|
+
return typeof value === 'boolean';
|
|
30
|
+
case 'array':
|
|
31
|
+
return Array.isArray(value);
|
|
32
|
+
case 'object':
|
|
33
|
+
return (value !== null && typeof value === 'object' && !Array.isArray(value));
|
|
34
|
+
default:
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
39
|
+
const URL_RE = /^https?:\/\/.+/;
|
|
40
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
41
|
+
const ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:?\d{2})?)?$/;
|
|
42
|
+
function checkFormat(value, format) {
|
|
43
|
+
switch (format) {
|
|
44
|
+
case 'email':
|
|
45
|
+
return EMAIL_RE.test(value);
|
|
46
|
+
case 'url':
|
|
47
|
+
return URL_RE.test(value);
|
|
48
|
+
case 'uuid':
|
|
49
|
+
return UUID_RE.test(value);
|
|
50
|
+
case 'iso-date':
|
|
51
|
+
return ISO_DATE_RE.test(value);
|
|
52
|
+
default:
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function checkConstraints(value, type, c) {
|
|
57
|
+
if (type === 'string' && typeof value === 'string') {
|
|
58
|
+
if (c.minLength !== undefined && value.length < c.minLength)
|
|
59
|
+
return `must be at least ${c.minLength} characters`;
|
|
60
|
+
if (c.maxLength !== undefined && value.length > c.maxLength)
|
|
61
|
+
return `must be at most ${c.maxLength} characters`;
|
|
62
|
+
if (c.pattern) {
|
|
63
|
+
/* Mismo motivo que en `filter-utils`: el patron es del inquilino y el
|
|
64
|
+
valor viene del payload publico. Ver `regex-de-inquilino.ts`. */
|
|
65
|
+
const re = (0, regex_de_inquilino_1.compilarRegexDeInquilino)(c.pattern, 'schema.pattern');
|
|
66
|
+
if (!re)
|
|
67
|
+
return `invalid regex pattern: ${c.pattern}`;
|
|
68
|
+
if (!re.test(value))
|
|
69
|
+
return `must match pattern /${c.pattern}/`;
|
|
70
|
+
}
|
|
71
|
+
if (c.format && !checkFormat(value, c.format))
|
|
72
|
+
return `must be a valid ${c.format}`;
|
|
73
|
+
if (c.enum && c.enum.length > 0 && !c.enum.includes(value))
|
|
74
|
+
return `must be one of: ${c.enum.join(', ')}`;
|
|
75
|
+
}
|
|
76
|
+
if (type === 'number' && typeof value === 'number') {
|
|
77
|
+
if (c.min !== undefined && value < c.min)
|
|
78
|
+
return `must be >= ${c.min}`;
|
|
79
|
+
if (c.max !== undefined && value > c.max)
|
|
80
|
+
return `must be <= ${c.max}`;
|
|
81
|
+
if (c.enum && c.enum.length > 0 && !c.enum.includes(String(value)))
|
|
82
|
+
return `must be one of: ${c.enum.join(', ')}`;
|
|
83
|
+
}
|
|
84
|
+
if (type === 'array' && Array.isArray(value)) {
|
|
85
|
+
if (c.minItems !== undefined && value.length < c.minItems)
|
|
86
|
+
return `must have at least ${c.minItems} items`;
|
|
87
|
+
if (c.maxItems !== undefined && value.length > c.maxItems)
|
|
88
|
+
return `must have at most ${c.maxItems} items`;
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
function validateSchema(fields, payload, strictMode) {
|
|
93
|
+
const errors = [];
|
|
94
|
+
if (!fields || fields.length === 0)
|
|
95
|
+
return { valid: true, errors: [] };
|
|
96
|
+
for (const field of fields) {
|
|
97
|
+
// Normalize path: strip {{payload.xxx}} or {{xxx}} template syntax → xxx
|
|
98
|
+
let path = field.path;
|
|
99
|
+
const tplMatch = path.match(/^\{\{\s*(?:payload\.)?(.+?)\s*\}\}$/);
|
|
100
|
+
if (tplMatch)
|
|
101
|
+
path = tplMatch[1];
|
|
102
|
+
const value = getNestedValue(payload, path);
|
|
103
|
+
// Check required
|
|
104
|
+
if (field.required && (value === undefined || value === null)) {
|
|
105
|
+
errors.push({ path, message: 'is required' });
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
// Skip further checks if value is missing and not required
|
|
109
|
+
if (value === undefined || value === null)
|
|
110
|
+
continue;
|
|
111
|
+
// Check type
|
|
112
|
+
if (field.type !== 'any' && !checkType(value, field.type)) {
|
|
113
|
+
errors.push({
|
|
114
|
+
path,
|
|
115
|
+
message: `expected ${field.type}, got ${Array.isArray(value) ? 'array' : typeof value}`,
|
|
116
|
+
});
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
// Check constraints
|
|
120
|
+
if (field.constraints) {
|
|
121
|
+
const constraintError = checkConstraints(value, field.type, field.constraints);
|
|
122
|
+
if (constraintError) {
|
|
123
|
+
errors.push({ path, message: constraintError });
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// Strict mode: reject unknown top-level keys
|
|
128
|
+
if (strictMode &&
|
|
129
|
+
payload &&
|
|
130
|
+
typeof payload === 'object' &&
|
|
131
|
+
!Array.isArray(payload)) {
|
|
132
|
+
const definedPaths = new Set(fields.map((f) => f.path.split('.')[0]));
|
|
133
|
+
for (const key of Object.keys(payload)) {
|
|
134
|
+
if (!definedPaths.has(key)) {
|
|
135
|
+
errors.push({ path: key, message: 'unknown field (strict mode)' });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return { valid: errors.length === 0, errors };
|
|
140
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import * as http from 'node:http';
|
|
2
|
+
import * as https from 'node:https';
|
|
3
|
+
import { Agent } from 'undici';
|
|
4
|
+
/**
|
|
5
|
+
* Throw if the URI's host(s) point at private / loopback / link-local
|
|
6
|
+
* addresses, OR if their DNS resolves there. mongodb+srv URIs are
|
|
7
|
+
* handled via SRV record lookup (the `<host>.mongodb.net` pattern
|
|
8
|
+
* Atlas uses doesn't have A/AAAA records — only the SRV at
|
|
9
|
+
* `_mongodb._tcp.<host>` does).
|
|
10
|
+
*/
|
|
11
|
+
export declare function assertSafeMongoUri(uri: string): Promise<void>;
|
|
12
|
+
/** Mask the credentials portion of a Mongo URI for logs / UI. */
|
|
13
|
+
export declare function maskMongoUri(uri: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Tunnel-aware variant: when `tunnelId` is set, the connection is going to
|
|
16
|
+
* route through a customer-installed agent (`hostwh expose-tcp`), so the
|
|
17
|
+
* URI's host is the customer's local network — `localhost` / `127.0.0.1` /
|
|
18
|
+
* `192.168.x.x` are all expected and safe (they're THEIR localhost, not
|
|
19
|
+
* ours). Skip the SSRF check entirely.
|
|
20
|
+
*
|
|
21
|
+
* Without a tunnel, behave exactly like assertSafeMongoUri.
|
|
22
|
+
*/
|
|
23
|
+
export declare function assertSafeMongoUriUnlessTunneled(uri: string, tunnelId: string | null | undefined): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Throw if the postgres URI's host points at private / loopback /
|
|
26
|
+
* link-local addresses, OR if its DNS resolves there. Mirrors
|
|
27
|
+
* assertSafeMongoUri but with the simpler postgres URI grammar.
|
|
28
|
+
*/
|
|
29
|
+
export declare function assertSafePostgresUri(uri: string): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Tunnel-aware variant for postgres — same semantics as the mongo one.
|
|
32
|
+
* When tunnelId is set, the URI describes the customer's own network
|
|
33
|
+
* reachable through their `hostwh expose --tcp` agent, so private IPs
|
|
34
|
+
* are intentional and we skip the SSRF check.
|
|
35
|
+
*/
|
|
36
|
+
export declare function assertSafePostgresUriUnlessTunneled(uri: string, tunnelId: string | null | undefined): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Which URI grammar each credential type's connection string uses, and so
|
|
39
|
+
* which guard applies to it.
|
|
40
|
+
*
|
|
41
|
+
* This exists because the mapping used to be inferred from the type slug by
|
|
42
|
+
* string transform, which silently produced nonsense for a snake_case type:
|
|
43
|
+
* `memory_mongodb` implied a helper named `assertSafeMemory_mongodbUri` that
|
|
44
|
+
* nobody would ever write, so the invariant test failed for a naming reason
|
|
45
|
+
* and the genuine gap behind it — memory_mongodb never being SSRF-checked at
|
|
46
|
+
* create time — sat unnoticed underneath the red test.
|
|
47
|
+
*
|
|
48
|
+
* Every credential type flagged `ssrfValidated` or `tunnelable` in
|
|
49
|
+
* CREDENTIAL_TYPES must appear here. `architecture.spec.ts` enforces that, so
|
|
50
|
+
* a new connection-string credential type cannot ship without someone
|
|
51
|
+
* deciding how its URI gets validated.
|
|
52
|
+
*/
|
|
53
|
+
export type UriGuardFamily = 'mongo' | 'postgres';
|
|
54
|
+
export declare const URI_GUARD_FAMILY: Readonly<Record<string, UriGuardFamily>>;
|
|
55
|
+
/** True when this credential type carries a connection string we must guard. */
|
|
56
|
+
export declare function isGuardedUriCredential(type: string): boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Los esquemas que puede llevar la URI de cada familia.
|
|
59
|
+
*
|
|
60
|
+
* Va aquí, junto a `URI_GUARD_FAMILY`, y no en un `if` suelto dentro del
|
|
61
|
+
* servicio: el tipo `Record<UriGuardFamily, …>` obliga a que una familia
|
|
62
|
+
* nueva declare los suyos o no compila, que es la misma disciplina que ya
|
|
63
|
+
* aplica el registro de guardias.
|
|
64
|
+
*/
|
|
65
|
+
export declare const URI_SCHEMES: Readonly<Record<UriGuardFamily, readonly string[]>>;
|
|
66
|
+
/**
|
|
67
|
+
* Que la cadena de conexión sea del producto que dice el tipo de credencial.
|
|
68
|
+
*
|
|
69
|
+
* Faltaba, y no se notaba porque los dos sitios que miraban la URI se
|
|
70
|
+
* rinden antes en el caso más común:
|
|
71
|
+
*
|
|
72
|
+
* - el guardia SSRF vuelve en cuanto la credencial lleva `tunnelId`, porque
|
|
73
|
+
* entonces la URI describe la red del propio cliente;
|
|
74
|
+
* - la comprobación de la base de Mongo vuelve si no se tecleó ninguna.
|
|
75
|
+
*
|
|
76
|
+
* Con las dos condiciones a la vez —una credencial con túnel y sin base, que
|
|
77
|
+
* es justo lo que sale del flujo de túneles— NADIE llegaba a mirar la cadena.
|
|
78
|
+
* Una `postgres://` se guardaba como MongoDB sin una palabra, y el error
|
|
79
|
+
* aparecía mucho después dentro de un nodo, en boca del driver:
|
|
80
|
+
* «Connection string is not a valid mongodb:// URI», que no nombra lo único
|
|
81
|
+
* que hacía falta saber: que eso era de Postgres.
|
|
82
|
+
*
|
|
83
|
+
* Por eso esto NO mira el túnel ni la base: corre siempre.
|
|
84
|
+
*/
|
|
85
|
+
export declare function assertUriSchemeMatchesType(type: string, uri: string): void;
|
|
86
|
+
/** Apply the right guard for a credential type. No-op for unguarded types. */
|
|
87
|
+
export declare function assertSafeCredentialUri(type: string, uri: string): Promise<void>;
|
|
88
|
+
/** As above, but a tunnelled credential describes the customer's own network. */
|
|
89
|
+
export declare function assertSafeCredentialUriUnlessTunneled(type: string, uri: string, tunnelId: string | null | undefined): Promise<void>;
|
|
90
|
+
/** Mask the password portion of a postgres URI for logs / UI. */
|
|
91
|
+
export declare function maskPostgresUri(uri: string): string;
|
|
92
|
+
/**
|
|
93
|
+
* Parse the first host:port pair out of a `mongodb://` or
|
|
94
|
+
* `mongodb+srv://` URI. Used by the TCP forwarder + URI rewriter to
|
|
95
|
+
* know what target to register and what host:port to substitute with
|
|
96
|
+
* the local forwarder address.
|
|
97
|
+
*
|
|
98
|
+
* Returns the bare host (no port) plus the explicit port — defaulting
|
|
99
|
+
* to 27017 for `mongodb://` and to a synthetic 27017 for `mongodb+srv://`
|
|
100
|
+
* (SRV resolution is bypassed entirely when tunnelled, the agent talks
|
|
101
|
+
* to its target directly).
|
|
102
|
+
*/
|
|
103
|
+
export declare function parseFirstMongoHost(uri: string): {
|
|
104
|
+
host: string;
|
|
105
|
+
port: number;
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Replace the host(s) section of a Mongo URI with a single host:port.
|
|
109
|
+
* Preserves user:pass, path (database), and querystring. Forces the
|
|
110
|
+
* scheme to `mongodb://` (drops `+srv`) because the rewritten URI
|
|
111
|
+
* points at our local forwarder, not an SRV-resolvable hostname.
|
|
112
|
+
*
|
|
113
|
+
* Also drops the `tls`, `ssl`, and `replicaSet` query params — they
|
|
114
|
+
* would cause the driver to negotiate TLS with our local plaintext
|
|
115
|
+
* forwarder. The agent talks to its real Atlas with TLS on its end.
|
|
116
|
+
*/
|
|
117
|
+
export declare function rewriteMongoUriHost(uri: string, host: string, port: number): string;
|
|
118
|
+
/**
|
|
119
|
+
* Parse the first host:port pair out of a `postgres://` /
|
|
120
|
+
* `postgresql://` URI. Same role as `parseFirstMongoHost` — the TCP
|
|
121
|
+
* forwarder needs to know what host:port to register against the
|
|
122
|
+
* tunnel, and the URI rewriter needs to know what to swap out.
|
|
123
|
+
*
|
|
124
|
+
* Postgres URIs only ever name one host (no comma list, no `+srv`
|
|
125
|
+
* sibling), but we still parse the userinfo correctly so passwords
|
|
126
|
+
* containing `@` (URL-encoded as `%40`) don't get treated as host
|
|
127
|
+
* delimiters.
|
|
128
|
+
*/
|
|
129
|
+
export declare function parseFirstPostgresHost(uri: string): {
|
|
130
|
+
host: string;
|
|
131
|
+
port: number;
|
|
132
|
+
};
|
|
133
|
+
/**
|
|
134
|
+
* Replace the host:port of a Postgres URI with the local forwarder
|
|
135
|
+
* address. Same idea as `rewriteMongoUriHost`: the local forwarder is
|
|
136
|
+
* plaintext (the agent restores TLS on its end of the wire), so any
|
|
137
|
+
* SSL-related query params would cause the driver to negotiate TLS
|
|
138
|
+
* against the wrong webhook and fail. Drop them.
|
|
139
|
+
*
|
|
140
|
+
* Preserves userinfo + path (the database name lives in the path) +
|
|
141
|
+
* remaining querystring. Also forces scheme to `postgres://` (drops
|
|
142
|
+
* `postgresql://` to one canonical form).
|
|
143
|
+
*/
|
|
144
|
+
/**
|
|
145
|
+
* Qué base declara una URI de Postgres en su ruta, o `null` si no declara
|
|
146
|
+
* ninguna —en ese caso el driver cae a la base con el nombre del usuario—.
|
|
147
|
+
*
|
|
148
|
+
* Se decodifica porque `rewritePostgresUriDatabase` codifica al escribir: sin
|
|
149
|
+
* esto, comparar «lo que había» con «lo que se pide» diría que `mi base` y
|
|
150
|
+
* `mi%20base` son distintas y registraría un cambio que no existe.
|
|
151
|
+
*/
|
|
152
|
+
export declare function baseDeLaUri(uri: string): string | null;
|
|
153
|
+
/**
|
|
154
|
+
* Cambiar la base de datos de una URI de Postgres — la que va en la ruta.
|
|
155
|
+
*
|
|
156
|
+
* En Mongo la base se elige con `client.db(nombre)` sobre una conexión ya
|
|
157
|
+
* abierta. En Postgres **no existe eso**: la base se decide al conectarse y
|
|
158
|
+
* viene en la URI, así que respetar el `databaseName` de un nodo obliga a
|
|
159
|
+
* reescribirla aquí. Ésa es la razón de que `databaseName` estuviera muerto en
|
|
160
|
+
* `postgres-actions.service.ts` — se calculaba y no se podía aplicar sin esto.
|
|
161
|
+
*
|
|
162
|
+
* Se conserva el esquema tal cual llegó (`postgres://` o `postgresql://`), el
|
|
163
|
+
* userinfo, el host y el querystring; sólo cambia la ruta. El nombre se
|
|
164
|
+
* codifica porque una base puede llevar caracteres que en una URI significan
|
|
165
|
+
* otra cosa, y sin codificar `mi base` o `a/b` producirían una URI distinta de
|
|
166
|
+
* la que se pidió.
|
|
167
|
+
*
|
|
168
|
+
* Con `database` vacío devuelve la URI intacta: quien llama decide si hay algo
|
|
169
|
+
* que aplicar.
|
|
170
|
+
*/
|
|
171
|
+
export declare function rewritePostgresUriDatabase(uri: string, database: string): string;
|
|
172
|
+
export declare function rewritePostgresUriHost(uri: string, host: string, port: number): string;
|
|
173
|
+
/** Igual que `isPrivateIp`, expuesto para quien tenga ya la IP resuelta. */
|
|
174
|
+
export declare function isPrivateAddress(ip: string): boolean;
|
|
175
|
+
/**
|
|
176
|
+
* Rechaza una URL de entrega que apunte adentro.
|
|
177
|
+
*
|
|
178
|
+
* Comprueba esquema, nombres bloqueados, IP literal y —cuando es un nombre— lo
|
|
179
|
+
* que resuelve AHORA. Esa resolución es la foto previa; lo que de verdad ata la
|
|
180
|
+
* dirección comprobada a la dirección marcada es el hook de `agentesDeEntrega`,
|
|
181
|
+
* porque entre esta consulta y el socket cabe un cambio de DNS.
|
|
182
|
+
*/
|
|
183
|
+
export declare function assertSafeHttpUrl(rawUrl: string): Promise<void>;
|
|
184
|
+
/** Los agentes de salida de la entrega. Se crean una vez: llevan pool de
|
|
185
|
+
* conexiones y uno por petición lo tiraría.
|
|
186
|
+
*
|
|
187
|
+
* Llevan las DOS capas: el `lookup` para los nombres y el conector para todo
|
|
188
|
+
* lo demás. Ninguna sobra — ver `conConectorQueRechazaPrivadas`. */
|
|
189
|
+
export declare function agentesDeEntrega(): {
|
|
190
|
+
httpAgent: http.Agent;
|
|
191
|
+
httpsAgent: https.Agent;
|
|
192
|
+
};
|
|
193
|
+
export declare function despachadorDeSalida(): Agent;
|
|
194
|
+
/**
|
|
195
|
+
* `fetch` con el guardia puesto: comprueba la URL antes de salir y resuelve
|
|
196
|
+
* por el despachador que rechaza direcciones privadas, así que un DNS que
|
|
197
|
+
* cambie entre la comprobación y el socket tampoco entra.
|
|
198
|
+
*
|
|
199
|
+
* Es el reemplazo de una palabra para cualquier `fetch` que salga a una URL
|
|
200
|
+
* que haya elegido un inquilino.
|
|
201
|
+
*/
|
|
202
|
+
export declare function fetchSeguro(url: string, init?: RequestInit): Promise<Response>;
|