latchkey 2.6.0 → 2.7.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 +63 -7
- package/dist/scripts/recordBrowserSession.js +3 -3
- package/dist/scripts/recordBrowserSession.js.map +1 -1
- package/dist/src/{apiCredentials.d.ts → apiCredentials/base.d.ts} +6 -6
- package/dist/src/apiCredentials/base.d.ts.map +1 -0
- package/dist/src/{apiCredentials.js → apiCredentials/base.js} +5 -5
- package/dist/src/apiCredentials/base.js.map +1 -0
- package/dist/src/{apiCredentialsSerialization.d.ts → apiCredentials/serialization.d.ts} +5 -5
- package/dist/src/apiCredentials/serialization.d.ts.map +1 -0
- package/dist/src/{apiCredentialsSerialization.js → apiCredentials/serialization.js} +9 -9
- package/dist/src/apiCredentials/serialization.js.map +1 -0
- package/dist/src/{apiCredentialStore.d.ts → apiCredentials/store.d.ts} +3 -3
- package/dist/src/apiCredentials/store.d.ts.map +1 -0
- package/dist/src/{apiCredentialStore.js → apiCredentials/store.js} +2 -2
- package/dist/src/apiCredentials/store.js.map +1 -0
- package/dist/src/apiCredentials/utils.d.ts +13 -0
- package/dist/src/apiCredentials/utils.d.ts.map +1 -0
- package/dist/src/apiCredentials/utils.js +27 -0
- package/dist/src/apiCredentials/utils.js.map +1 -0
- package/dist/src/cli.js +42 -39
- package/dist/src/cli.js.map +1 -1
- package/dist/src/cliCommands.d.ts +6 -3
- package/dist/src/cliCommands.d.ts.map +1 -1
- package/dist/src/cliCommands.js +243 -190
- package/dist/src/cliCommands.js.map +1 -1
- package/dist/src/config.d.ts +36 -2
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/config.js +112 -17
- package/dist/src/config.js.map +1 -1
- package/dist/src/configDataStore.d.ts +44 -0
- package/dist/src/configDataStore.d.ts.map +1 -1
- package/dist/src/configDataStore.js +27 -0
- package/dist/src/configDataStore.js.map +1 -1
- package/dist/src/curl.d.ts +41 -8
- package/dist/src/curl.d.ts.map +1 -1
- package/dist/src/curl.js +80 -75
- package/dist/src/curl.js.map +1 -1
- package/dist/src/curlInjection.d.ts +46 -0
- package/dist/src/curlInjection.d.ts.map +1 -0
- package/dist/src/curlInjection.js +99 -0
- package/dist/src/curlInjection.js.map +1 -0
- package/dist/src/errorMessages.d.ts +14 -0
- package/dist/src/errorMessages.d.ts.map +1 -0
- package/dist/src/errorMessages.js +22 -0
- package/dist/src/errorMessages.js.map +1 -0
- package/dist/src/gateway/client.d.ts +32 -0
- package/dist/src/gateway/client.d.ts.map +1 -0
- package/dist/src/gateway/client.js +89 -0
- package/dist/src/gateway/client.js.map +1 -0
- package/dist/src/gateway/gatewayEndpoint.d.ts +43 -0
- package/dist/src/gateway/gatewayEndpoint.d.ts.map +1 -0
- package/dist/src/gateway/gatewayEndpoint.js +297 -0
- package/dist/src/gateway/gatewayEndpoint.js.map +1 -0
- package/dist/src/gateway/latchkeyEndpoint.d.ts +105 -0
- package/dist/src/gateway/latchkeyEndpoint.d.ts.map +1 -0
- package/dist/src/gateway/latchkeyEndpoint.js +144 -0
- package/dist/src/gateway/latchkeyEndpoint.js.map +1 -0
- package/dist/src/gateway/server.d.ts +20 -0
- package/dist/src/gateway/server.d.ts.map +1 -0
- package/dist/src/gateway/server.js +90 -0
- package/dist/src/gateway/server.js.map +1 -0
- package/dist/src/index.d.ts +4 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +5 -5
- package/dist/src/index.js.map +1 -1
- package/dist/src/permissions.d.ts +2 -1
- package/dist/src/permissions.d.ts.map +1 -1
- package/dist/src/permissions.js +8 -4
- package/dist/src/permissions.js.map +1 -1
- package/dist/src/{registry.d.ts → serviceRegistry.d.ts} +4 -4
- package/dist/src/serviceRegistry.d.ts.map +1 -0
- package/dist/src/{registry.js → serviceRegistry.js} +4 -4
- package/dist/src/serviceRegistry.js.map +1 -0
- package/dist/src/services/aws.d.ts +2 -2
- package/dist/src/services/aws.d.ts.map +1 -1
- package/dist/src/services/aws.js +17 -10
- package/dist/src/services/aws.js.map +1 -1
- package/dist/src/services/core/base.d.ts +2 -2
- package/dist/src/services/core/base.d.ts.map +1 -1
- package/dist/src/services/core/base.js +3 -3
- package/dist/src/services/core/base.js.map +1 -1
- package/dist/src/services/core/registered.d.ts +2 -2
- package/dist/src/services/core/registered.d.ts.map +1 -1
- package/dist/src/services/core/registered.js +2 -2
- package/dist/src/services/core/registered.js.map +1 -1
- package/dist/src/services/discord.d.ts +1 -1
- package/dist/src/services/discord.d.ts.map +1 -1
- package/dist/src/services/discord.js +1 -1
- package/dist/src/services/discord.js.map +1 -1
- package/dist/src/services/dropbox.d.ts +1 -1
- package/dist/src/services/dropbox.d.ts.map +1 -1
- package/dist/src/services/dropbox.js +1 -1
- package/dist/src/services/dropbox.js.map +1 -1
- package/dist/src/services/github.d.ts +1 -1
- package/dist/src/services/github.d.ts.map +1 -1
- package/dist/src/services/github.js +1 -1
- package/dist/src/services/github.js.map +1 -1
- package/dist/src/services/google/base.d.ts +2 -2
- package/dist/src/services/google/base.d.ts.map +1 -1
- package/dist/src/services/google/base.js +3 -3
- package/dist/src/services/google/base.js.map +1 -1
- package/dist/src/services/google/directions.d.ts +1 -1
- package/dist/src/services/google/directions.d.ts.map +1 -1
- package/dist/src/services/linear.d.ts +1 -1
- package/dist/src/services/linear.d.ts.map +1 -1
- package/dist/src/services/linear.js +1 -1
- package/dist/src/services/linear.js.map +1 -1
- package/dist/src/services/notion.d.ts +1 -1
- package/dist/src/services/notion.d.ts.map +1 -1
- package/dist/src/services/notion.js +1 -1
- package/dist/src/services/notion.js.map +1 -1
- package/dist/src/services/sentry.d.ts +2 -2
- package/dist/src/services/sentry.d.ts.map +1 -1
- package/dist/src/services/sentry.js +6 -3
- package/dist/src/services/sentry.js.map +1 -1
- package/dist/src/services/slack.d.ts +3 -3
- package/dist/src/services/slack.d.ts.map +1 -1
- package/dist/src/services/slack.js +5 -5
- package/dist/src/services/slack.js.map +1 -1
- package/dist/src/services/telegram.d.ts +2 -2
- package/dist/src/services/telegram.d.ts.map +1 -1
- package/dist/src/services/telegram.js +2 -2
- package/dist/src/services/telegram.js.map +1 -1
- package/dist/src/sharedOperations.d.ts +44 -0
- package/dist/src/sharedOperations.d.ts.map +1 -0
- package/dist/src/sharedOperations.js +131 -0
- package/dist/src/sharedOperations.js.map +1 -0
- package/dist/src/version.d.ts +2 -0
- package/dist/src/version.d.ts.map +1 -0
- package/dist/src/version.js +4 -0
- package/dist/src/version.js.map +1 -0
- package/dist/tests/apiCredentialStore.test.js +2 -2
- package/dist/tests/apiCredentialStore.test.js.map +1 -1
- package/dist/tests/apiCredentials.test.js +37 -36
- package/dist/tests/apiCredentials.test.js.map +1 -1
- package/dist/tests/cli.test.js +241 -55
- package/dist/tests/cli.test.js.map +1 -1
- package/dist/tests/config.test.d.ts +2 -0
- package/dist/tests/config.test.d.ts.map +1 -0
- package/dist/tests/config.test.js +150 -0
- package/dist/tests/config.test.js.map +1 -0
- package/dist/tests/gateway.test.d.ts +2 -0
- package/dist/tests/gateway.test.d.ts.map +1 -0
- package/dist/tests/gateway.test.js +566 -0
- package/dist/tests/gateway.test.js.map +1 -0
- package/dist/tests/gatewayClient.test.d.ts +2 -0
- package/dist/tests/gatewayClient.test.d.ts.map +1 -0
- package/dist/tests/gatewayClient.test.js +85 -0
- package/dist/tests/gatewayClient.test.js.map +1 -0
- package/dist/tests/latchkeyEndpoint.test.d.ts +2 -0
- package/dist/tests/latchkeyEndpoint.test.d.ts.map +1 -0
- package/dist/tests/latchkeyEndpoint.test.js +385 -0
- package/dist/tests/latchkeyEndpoint.test.js.map +1 -0
- package/dist/tests/permissions.test.js +18 -3
- package/dist/tests/permissions.test.js.map +1 -1
- package/dist/tests/serviceRegistry.test.d.ts +2 -0
- package/dist/tests/serviceRegistry.test.d.ts.map +1 -0
- package/dist/tests/{registry.test.js → serviceRegistry.test.js} +17 -17
- package/dist/tests/serviceRegistry.test.js.map +1 -0
- package/dist/tests/servicesAgainstRecordings.test.js +3 -3
- package/dist/tests/servicesAgainstRecordings.test.js.map +1 -1
- package/dist/tests/sharedOperations.test.d.ts +2 -0
- package/dist/tests/sharedOperations.test.d.ts.map +1 -0
- package/dist/tests/sharedOperations.test.js +264 -0
- package/dist/tests/sharedOperations.test.js.map +1 -0
- package/package.json +8 -2
- package/dist/src/apiCredentialStore.d.ts.map +0 -1
- package/dist/src/apiCredentialStore.js.map +0 -1
- package/dist/src/apiCredentials.d.ts.map +0 -1
- package/dist/src/apiCredentials.js.map +0 -1
- package/dist/src/apiCredentialsSerialization.d.ts.map +0 -1
- package/dist/src/apiCredentialsSerialization.js.map +0 -1
- package/dist/src/registry.d.ts.map +0 -1
- package/dist/src/registry.js.map +0 -1
- package/dist/tests/registry.test.d.ts +0 -2
- package/dist/tests/registry.test.d.ts.map +0 -1
- package/dist/tests/registry.test.js.map +0 -1
package/dist/src/curl.js
CHANGED
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
* Curl subprocess utilities.
|
|
3
3
|
*/
|
|
4
4
|
import { spawn, spawnSync } from 'node:child_process';
|
|
5
|
+
import { CurlParseError, parseCurlArgs } from '@imbue-ai/detent';
|
|
5
6
|
import { CONFIG } from './config.js';
|
|
7
|
+
// Re-export detent's curl-parsing primitives so the rest of the codebase can
|
|
8
|
+
// treat `./curl.js` as the single entry point for curl parsing and doesn't
|
|
9
|
+
// have to depend on detent directly.
|
|
10
|
+
export { CurlParseError, parseCurlArgs };
|
|
6
11
|
function defaultSubprocessRunner(args) {
|
|
7
12
|
const result = spawnSync(CONFIG.curlCommand, args, {
|
|
8
13
|
stdio: ['inherit', 'inherit', 'inherit'],
|
|
@@ -33,7 +38,38 @@ function defaultDetachedSubprocessRunner(args) {
|
|
|
33
38
|
}
|
|
34
39
|
let subprocessRunner = defaultSubprocessRunner;
|
|
35
40
|
let capturingSubprocessRunner = defaultCapturingSubprocessRunner;
|
|
41
|
+
function defaultAsyncSubprocessRunner(args, options) {
|
|
42
|
+
return new Promise((resolve, reject) => {
|
|
43
|
+
const child = spawn(CONFIG.curlCommand, args, {
|
|
44
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
45
|
+
});
|
|
46
|
+
const stdoutChunks = [];
|
|
47
|
+
const stderrChunks = [];
|
|
48
|
+
child.stdout.on('data', (chunk) => {
|
|
49
|
+
stdoutChunks.push(chunk);
|
|
50
|
+
});
|
|
51
|
+
child.stderr.on('data', (chunk) => {
|
|
52
|
+
stderrChunks.push(chunk.toString());
|
|
53
|
+
});
|
|
54
|
+
child.on('error', reject);
|
|
55
|
+
child.on('close', (code) => {
|
|
56
|
+
resolve({
|
|
57
|
+
returncode: code ?? 1,
|
|
58
|
+
stdout: Buffer.concat(stdoutChunks),
|
|
59
|
+
stderr: stderrChunks.join(''),
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
if (options?.stdin !== undefined) {
|
|
63
|
+
child.stdin.write(options.stdin);
|
|
64
|
+
child.stdin.end();
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
child.stdin.end();
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
36
71
|
let detachedSubprocessRunner = defaultDetachedSubprocessRunner;
|
|
72
|
+
let asyncSubprocessRunner = defaultAsyncSubprocessRunner;
|
|
37
73
|
export function setSubprocessRunner(runner) {
|
|
38
74
|
subprocessRunner = runner;
|
|
39
75
|
}
|
|
@@ -52,6 +88,12 @@ export function setDetachedSubprocessRunner(runner) {
|
|
|
52
88
|
export function resetDetachedSubprocessRunner() {
|
|
53
89
|
detachedSubprocessRunner = defaultDetachedSubprocessRunner;
|
|
54
90
|
}
|
|
91
|
+
export function setAsyncSubprocessRunner(runner) {
|
|
92
|
+
asyncSubprocessRunner = runner;
|
|
93
|
+
}
|
|
94
|
+
export function resetAsyncSubprocessRunner() {
|
|
95
|
+
asyncSubprocessRunner = defaultAsyncSubprocessRunner;
|
|
96
|
+
}
|
|
55
97
|
/**
|
|
56
98
|
* Run curl without capturing output (for interactive CLI use).
|
|
57
99
|
*/
|
|
@@ -71,89 +113,52 @@ export function runCaptured(args, timeout = 10) {
|
|
|
71
113
|
export function runDetached(args) {
|
|
72
114
|
detachedSubprocessRunner(args);
|
|
73
115
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
116
|
+
/**
|
|
117
|
+
* Run curl asynchronously with output capture (for gateway proxy use).
|
|
118
|
+
*/
|
|
119
|
+
export function runAsync(args, options) {
|
|
120
|
+
return asyncSubprocessRunner(args, options);
|
|
78
121
|
}
|
|
79
122
|
/**
|
|
80
|
-
*
|
|
81
|
-
*
|
|
123
|
+
* Extract the target URL argument from a curl invocation.
|
|
124
|
+
*
|
|
125
|
+
* Delegates curl flag parsing to `parseCurlArgs` from detent (which knows the
|
|
126
|
+
* full curl flag vocabulary) to determine the canonical request URL, and then
|
|
127
|
+
* returns the original, unnormalized argument string so callers can use it for
|
|
128
|
+
* positional substitution (gateway rewriting, Telegram URL rewriting, ...).
|
|
129
|
+
*
|
|
130
|
+
* Schemeless URLs (e.g. `www.example.com`) are supported: curl defaults them
|
|
131
|
+
* to `http://`, and we do the same when normalizing for the comparison.
|
|
132
|
+
* Only http(s) URLs are recognized; other schemes (ftp, file, ...) return
|
|
133
|
+
* null.
|
|
134
|
+
*
|
|
135
|
+
* Throws `CurlParseError` (from detent) when the arguments don't form a valid
|
|
136
|
+
* curl invocation (missing URL, malformed header, ...). The error message
|
|
137
|
+
* carries useful detail for the user, so callers should surface it rather
|
|
138
|
+
* than silently swallow it.
|
|
82
139
|
*/
|
|
83
|
-
export function
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
const argument = curlArguments[i];
|
|
88
|
-
if (argument === '-X' || argument === '--request') {
|
|
89
|
-
method = curlArguments[i + 1]?.toUpperCase();
|
|
90
|
-
i++;
|
|
91
|
-
}
|
|
92
|
-
else if (argument === '-d' ||
|
|
93
|
-
argument === '--data' ||
|
|
94
|
-
argument === '--data-raw' ||
|
|
95
|
-
argument === '--data-binary' ||
|
|
96
|
-
argument === '--data-urlencode') {
|
|
97
|
-
hasData = true;
|
|
98
|
-
i++;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
if (method !== undefined) {
|
|
102
|
-
return method;
|
|
140
|
+
export function extractUrlFromCurlArguments(args) {
|
|
141
|
+
const parsedUrl = parseCurlArgs(args).url;
|
|
142
|
+
if (!parsedUrl.startsWith('http://') && !parsedUrl.startsWith('https://')) {
|
|
143
|
+
return null;
|
|
103
144
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
for (
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
argument === '--data' ||
|
|
112
|
-
argument === '--data-raw' ||
|
|
113
|
-
argument === '--data-binary') {
|
|
114
|
-
return curlArguments[i + 1] ?? '';
|
|
145
|
+
// Find the original input argument that produced this URL, so callers can
|
|
146
|
+
// substitute it in-place. Each candidate arg is normalized the same way
|
|
147
|
+
// curl / parseCurlArgs would (defaulting the scheme to http://) before the
|
|
148
|
+
// comparison.
|
|
149
|
+
for (const arg of args) {
|
|
150
|
+
if (arg === parsedUrl) {
|
|
151
|
+
return arg;
|
|
115
152
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
export function extractHeadersFromCurlArguments(curlArguments) {
|
|
121
|
-
const headers = {};
|
|
122
|
-
for (let i = 0; i < curlArguments.length; i++) {
|
|
123
|
-
const argument = curlArguments[i];
|
|
124
|
-
if (argument === '-H' || argument === '--header') {
|
|
125
|
-
const headerValue = curlArguments[i + 1];
|
|
126
|
-
if (headerValue !== undefined) {
|
|
127
|
-
const colonIndex = headerValue.indexOf(':');
|
|
128
|
-
if (colonIndex > 0) {
|
|
129
|
-
const name = headerValue.slice(0, colonIndex).trim().toLowerCase();
|
|
130
|
-
const value = headerValue.slice(colonIndex + 1).trim();
|
|
131
|
-
headers[name] = value;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
i++;
|
|
153
|
+
const withScheme = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(arg) ? arg : `http://${arg}`;
|
|
154
|
+
let normalized;
|
|
155
|
+
try {
|
|
156
|
+
normalized = new URL(withScheme).href;
|
|
135
157
|
}
|
|
136
|
-
|
|
137
|
-
return headers;
|
|
138
|
-
}
|
|
139
|
-
export function extractUrlFromCurlArguments(args) {
|
|
140
|
-
const filteredArgs = filterPassthroughFlags(args);
|
|
141
|
-
// Simple URL extraction: look for arguments that look like URLs
|
|
142
|
-
// or parse known curl argument patterns
|
|
143
|
-
for (let i = 0; i < filteredArgs.length; i++) {
|
|
144
|
-
const arg = filteredArgs[i];
|
|
145
|
-
if (arg === undefined)
|
|
146
|
-
continue;
|
|
147
|
-
// Skip flags and their values
|
|
148
|
-
if (arg.startsWith('-')) {
|
|
149
|
-
// Skip flags that take a value
|
|
150
|
-
if (['-H', '-d', '-X', '-o', '-w', '-u', '-A', '-e', '-b', '-c', '-F', '-T'].includes(arg)) {
|
|
151
|
-
i++; // Skip the next argument which is the value
|
|
152
|
-
}
|
|
158
|
+
catch {
|
|
153
159
|
continue;
|
|
154
160
|
}
|
|
155
|
-
|
|
156
|
-
if (arg.startsWith('http://') || arg.startsWith('https://')) {
|
|
161
|
+
if (normalized === parsedUrl) {
|
|
157
162
|
return arg;
|
|
158
163
|
}
|
|
159
164
|
}
|
package/dist/src/curl.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"curl.js","sourceRoot":"","sources":["../../src/curl.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,KAAK,EAAE,SAAS,EAAoB,MAAM,oBAAoB,CAAC;AACxE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"curl.js","sourceRoot":"","sources":["../../src/curl.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared credential-injection pipeline used by both `latchkey curl` and the
|
|
3
|
+
* gateway's `/gateway/<url>` proxy.
|
|
4
|
+
*
|
|
5
|
+
* Given the raw curl arguments of an outgoing request, this function performs
|
|
6
|
+
* the permission check, URL extraction, service lookup, credential load and
|
|
7
|
+
* expiry/refresh steps, and returns the final argument list to pass to curl.
|
|
8
|
+
* Problems are reported as dedicated error subclasses. Actually invoking curl
|
|
9
|
+
* is left to the caller, so the CLI can inherit stdio while the gateway can
|
|
10
|
+
* stream request bodies and capture response headers.
|
|
11
|
+
*/
|
|
12
|
+
import type { ApiCredentialStore } from './apiCredentials/store.js';
|
|
13
|
+
import type { ServiceRegistry } from './serviceRegistry.js';
|
|
14
|
+
export declare class RequestNotPermittedError extends Error {
|
|
15
|
+
constructor();
|
|
16
|
+
}
|
|
17
|
+
export declare class UrlExtractionFailedError extends Error {
|
|
18
|
+
constructor(detail?: string);
|
|
19
|
+
}
|
|
20
|
+
export declare class NoServiceForUrlError extends Error {
|
|
21
|
+
readonly url: string;
|
|
22
|
+
constructor(url: string);
|
|
23
|
+
}
|
|
24
|
+
export declare class NoCredentialsForServiceError extends Error {
|
|
25
|
+
readonly serviceName: string;
|
|
26
|
+
constructor(serviceName: string);
|
|
27
|
+
}
|
|
28
|
+
export declare class CredentialsExpiredError extends Error {
|
|
29
|
+
readonly serviceName: string;
|
|
30
|
+
constructor(serviceName: string);
|
|
31
|
+
}
|
|
32
|
+
export interface CurlInjectionDependencies {
|
|
33
|
+
readonly registry: ServiceRegistry;
|
|
34
|
+
readonly checkPermission: (curlArguments: readonly string[], configPath: string, doNotUseBuiltinSchemas: boolean) => Promise<boolean>;
|
|
35
|
+
readonly permissionsConfigPath: string;
|
|
36
|
+
readonly permissionsDoNotUseBuiltinSchemas: boolean;
|
|
37
|
+
readonly passthroughUnknown: boolean;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Run the credential-injection pipeline for a curl invocation and return the
|
|
41
|
+
* final argument list to pass to curl. On problems, throws one of the error
|
|
42
|
+
* classes exported from this module (or a `PermissionCheckError` from the
|
|
43
|
+
* underlying permission check).
|
|
44
|
+
*/
|
|
45
|
+
export declare function prepareCurlInvocation(curlArguments: readonly string[], apiCredentialStore: ApiCredentialStore, dependencies: CurlInjectionDependencies): Promise<readonly string[]>;
|
|
46
|
+
//# sourceMappingURL=curlInjection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"curlInjection.d.ts","sourceRoot":"","sources":["../../src/curlInjection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAIpE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAE5D,qBAAa,wBAAyB,SAAQ,KAAK;;CAKlD;AAED,qBAAa,wBAAyB,SAAQ,KAAK;gBACrC,MAAM,CAAC,EAAE,MAAM;CAQ5B;AAED,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;gBAET,GAAG,EAAE,MAAM;CAKxB;AAED,qBAAa,4BAA6B,SAAQ,KAAK;IACrD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;gBAEjB,WAAW,EAAE,MAAM;CAKhC;AAED,qBAAa,uBAAwB,SAAQ,KAAK;IAChD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;gBAEjB,WAAW,EAAE,MAAM;CAKhC;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,QAAQ,CAAC,eAAe,EAAE,CACxB,aAAa,EAAE,SAAS,MAAM,EAAE,EAChC,UAAU,EAAE,MAAM,EAClB,sBAAsB,EAAE,OAAO,KAC5B,OAAO,CAAC,OAAO,CAAC,CAAC;IACtB,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;IACvC,QAAQ,CAAC,iCAAiC,EAAE,OAAO,CAAC;IACpD,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAC;CACtC;AAED;;;;;GAKG;AACH,wBAAsB,qBAAqB,CACzC,aAAa,EAAE,SAAS,MAAM,EAAE,EAChC,kBAAkB,EAAE,kBAAkB,EACtC,YAAY,EAAE,yBAAyB,GACtC,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,CA+C5B"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared credential-injection pipeline used by both `latchkey curl` and the
|
|
3
|
+
* gateway's `/gateway/<url>` proxy.
|
|
4
|
+
*
|
|
5
|
+
* Given the raw curl arguments of an outgoing request, this function performs
|
|
6
|
+
* the permission check, URL extraction, service lookup, credential load and
|
|
7
|
+
* expiry/refresh steps, and returns the final argument list to pass to curl.
|
|
8
|
+
* Problems are reported as dedicated error subclasses. Actually invoking curl
|
|
9
|
+
* is left to the caller, so the CLI can inherit stdio while the gateway can
|
|
10
|
+
* stream request bodies and capture response headers.
|
|
11
|
+
*/
|
|
12
|
+
import { maybeRefreshCredentials } from './apiCredentials/utils.js';
|
|
13
|
+
import { CurlParseError, extractUrlFromCurlArguments } from './curl.js';
|
|
14
|
+
import { ErrorMessages } from './errorMessages.js';
|
|
15
|
+
export class RequestNotPermittedError extends Error {
|
|
16
|
+
constructor() {
|
|
17
|
+
super(ErrorMessages.requestNotPermitted);
|
|
18
|
+
this.name = 'RequestNotPermittedError';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export class UrlExtractionFailedError extends Error {
|
|
22
|
+
constructor(detail) {
|
|
23
|
+
super(detail === undefined
|
|
24
|
+
? ErrorMessages.couldNotExtractUrl
|
|
25
|
+
: `${ErrorMessages.couldNotExtractUrlBrief} ${detail}`);
|
|
26
|
+
this.name = 'UrlExtractionFailedError';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export class NoServiceForUrlError extends Error {
|
|
30
|
+
url;
|
|
31
|
+
constructor(url) {
|
|
32
|
+
super(ErrorMessages.noServiceMatchesUrl(url));
|
|
33
|
+
this.name = 'NoServiceForUrlError';
|
|
34
|
+
this.url = url;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export class NoCredentialsForServiceError extends Error {
|
|
38
|
+
serviceName;
|
|
39
|
+
constructor(serviceName) {
|
|
40
|
+
super(ErrorMessages.noCredentialsFound(serviceName));
|
|
41
|
+
this.name = 'NoCredentialsForServiceError';
|
|
42
|
+
this.serviceName = serviceName;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export class CredentialsExpiredError extends Error {
|
|
46
|
+
serviceName;
|
|
47
|
+
constructor(serviceName) {
|
|
48
|
+
super(ErrorMessages.credentialsExpired(serviceName));
|
|
49
|
+
this.name = 'CredentialsExpiredError';
|
|
50
|
+
this.serviceName = serviceName;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Run the credential-injection pipeline for a curl invocation and return the
|
|
55
|
+
* final argument list to pass to curl. On problems, throws one of the error
|
|
56
|
+
* classes exported from this module (or a `PermissionCheckError` from the
|
|
57
|
+
* underlying permission check).
|
|
58
|
+
*/
|
|
59
|
+
export async function prepareCurlInvocation(curlArguments, apiCredentialStore, dependencies) {
|
|
60
|
+
const allowed = await dependencies.checkPermission(curlArguments, dependencies.permissionsConfigPath, dependencies.permissionsDoNotUseBuiltinSchemas);
|
|
61
|
+
if (!allowed) {
|
|
62
|
+
throw new RequestNotPermittedError();
|
|
63
|
+
}
|
|
64
|
+
let url;
|
|
65
|
+
try {
|
|
66
|
+
url = extractUrlFromCurlArguments(curlArguments);
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
if (error instanceof CurlParseError) {
|
|
70
|
+
throw new UrlExtractionFailedError(error.message);
|
|
71
|
+
}
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
if (url === null) {
|
|
75
|
+
throw new UrlExtractionFailedError();
|
|
76
|
+
}
|
|
77
|
+
const service = dependencies.registry.getByUrl(url);
|
|
78
|
+
if (service === null) {
|
|
79
|
+
if (dependencies.passthroughUnknown) {
|
|
80
|
+
return [...curlArguments];
|
|
81
|
+
}
|
|
82
|
+
throw new NoServiceForUrlError(url);
|
|
83
|
+
}
|
|
84
|
+
let apiCredentials = apiCredentialStore.get(service.name);
|
|
85
|
+
if (apiCredentials === null) {
|
|
86
|
+
if (dependencies.passthroughUnknown) {
|
|
87
|
+
return [...curlArguments];
|
|
88
|
+
}
|
|
89
|
+
throw new NoCredentialsForServiceError(service.name);
|
|
90
|
+
}
|
|
91
|
+
if (apiCredentials.isExpired() === true) {
|
|
92
|
+
apiCredentials = await maybeRefreshCredentials(service, apiCredentials, apiCredentialStore);
|
|
93
|
+
if (apiCredentials.isExpired() === true) {
|
|
94
|
+
throw new CredentialsExpiredError(service.name);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return await apiCredentials.injectIntoCurlCall(curlArguments);
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=curlInjection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"curlInjection.js","sourceRoot":"","sources":["../../src/curlInjection.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized error messages used by both the CLI and the gateway.
|
|
3
|
+
*/
|
|
4
|
+
export declare const ErrorMessages: {
|
|
5
|
+
readonly requestNotPermitted: "Error: Request not permitted by the user.";
|
|
6
|
+
readonly couldNotExtractUrl: "Error: Could not extract URL from curl arguments. Only http(s) requests are supported.";
|
|
7
|
+
readonly couldNotExtractUrlBrief: "Error: Could not extract URL from curl arguments.";
|
|
8
|
+
readonly upstreamRequestFailed: "Error: Upstream request failed.";
|
|
9
|
+
readonly requestBodyTooLarge: "Error: Request body too large.";
|
|
10
|
+
readonly noServiceMatchesUrl: (url: string) => string;
|
|
11
|
+
readonly noCredentialsFound: (serviceName: string) => string;
|
|
12
|
+
readonly credentialsExpired: (serviceName: string) => string;
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=errorMessages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errorMessages.d.ts","sourceRoot":"","sources":["../../src/errorMessages.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,eAAO,MAAM,aAAa;;;;;;wCAQC,MAAM,KAAG,MAAM;+CAIR,MAAM,KAAG,MAAM;+CAOf,MAAM,KAAG,MAAM;CAMvC,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized error messages used by both the CLI and the gateway.
|
|
3
|
+
*/
|
|
4
|
+
export const ErrorMessages = {
|
|
5
|
+
requestNotPermitted: 'Error: Request not permitted by the user.',
|
|
6
|
+
couldNotExtractUrl: 'Error: Could not extract URL from curl arguments. Only http(s) requests are supported.',
|
|
7
|
+
couldNotExtractUrlBrief: 'Error: Could not extract URL from curl arguments.',
|
|
8
|
+
upstreamRequestFailed: 'Error: Upstream request failed.',
|
|
9
|
+
requestBodyTooLarge: 'Error: Request body too large.',
|
|
10
|
+
noServiceMatchesUrl(url) {
|
|
11
|
+
return `Error: No service matches URL: ${url}`;
|
|
12
|
+
},
|
|
13
|
+
noCredentialsFound(serviceName) {
|
|
14
|
+
return (`Error: No credentials found for ${serviceName}.\n` +
|
|
15
|
+
`Run 'latchkey auth browser ${serviceName}' or 'latchkey auth set ${serviceName}' first.`);
|
|
16
|
+
},
|
|
17
|
+
credentialsExpired(serviceName) {
|
|
18
|
+
return (`Error: Credentials for ${serviceName} are expired.\n` +
|
|
19
|
+
`Run 'latchkey auth browser ${serviceName}' or 'latchkey auth set ${serviceName}' to refresh them.`);
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=errorMessages.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errorMessages.js","sourceRoot":"","sources":["../../src/errorMessages.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,mBAAmB,EAAE,2CAA2C;IAChE,kBAAkB,EAChB,wFAAwF;IAC1F,uBAAuB,EAAE,mDAAmD;IAC5E,qBAAqB,EAAE,iCAAiC;IACxD,mBAAmB,EAAE,gCAAgC;IAErD,mBAAmB,CAAC,GAAW;QAC7B,OAAO,kCAAkC,GAAG,EAAE,CAAC;IACjD,CAAC;IAED,kBAAkB,CAAC,WAAmB;QACpC,OAAO,CACL,mCAAmC,WAAW,KAAK;YACnD,8BAA8B,WAAW,2BAA2B,WAAW,UAAU,CAC1F,CAAC;IACJ,CAAC;IAED,kBAAkB,CAAC,WAAmB;QACpC,OAAO,CACL,0BAA0B,WAAW,iBAAiB;YACtD,8BAA8B,WAAW,2BAA2B,WAAW,oBAAoB,CACpG,CAAC;IACJ,CAAC;CACO,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client used by the CLI when `LATCHKEY_GATEWAY` is set.
|
|
3
|
+
*
|
|
4
|
+
* Commands are forwarded to the gateway's `/latchkey/` RPC endpoint, while
|
|
5
|
+
* `latchkey curl` has its target URL rewritten to route through `/gateway/`.
|
|
6
|
+
*/
|
|
7
|
+
import type { LatchkeyRequest } from './latchkeyEndpoint.js';
|
|
8
|
+
export declare class GatewayRequestError extends Error {
|
|
9
|
+
readonly statusCode: number;
|
|
10
|
+
constructor(message: string, statusCode: number);
|
|
11
|
+
}
|
|
12
|
+
export declare class GatewayCommandNotSupportedError extends Error {
|
|
13
|
+
constructor(commandName: string);
|
|
14
|
+
}
|
|
15
|
+
export declare class GatewayCurlRewriteError extends Error {
|
|
16
|
+
constructor(message: string);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* POST a request to the gateway's `/latchkey/` endpoint and return its `result`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function callLatchkeyEndpoint(gatewayUrl: string, request: LatchkeyRequest): Promise<unknown>;
|
|
22
|
+
/**
|
|
23
|
+
* Build the URL used to proxy a `latchkey curl` invocation through the
|
|
24
|
+
* gateway's `/gateway/` endpoint.
|
|
25
|
+
*/
|
|
26
|
+
export declare function buildGatewayProxyUrl(gatewayUrl: string, targetUrl: string): string;
|
|
27
|
+
/**
|
|
28
|
+
* Rewrite a curl argument list so the target URL points at the gateway's
|
|
29
|
+
* `/gateway/<target>` endpoint. Returns a new array; the original is unchanged.
|
|
30
|
+
*/
|
|
31
|
+
export declare function rewriteCurlArgumentsForGateway(curlArguments: readonly string[], targetUrl: string, gatewayUrl: string): readonly string[];
|
|
32
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/gateway/client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;gBAEhB,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM;CAKhD;AAED,qBAAa,+BAAgC,SAAQ,KAAK;gBAC5C,WAAW,EAAE,MAAM;CAOhC;AAED,qBAAa,uBAAwB,SAAQ,KAAK;gBACpC,OAAO,EAAE,MAAM;CAI5B;AAOD;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,eAAe,GACvB,OAAO,CAAC,OAAO,CAAC,CAoClB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAElF;AAED;;;GAGG;AACH,wBAAgB,8BAA8B,CAC5C,aAAa,EAAE,SAAS,MAAM,EAAE,EAChC,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,SAAS,MAAM,EAAE,CAkBnB"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client used by the CLI when `LATCHKEY_GATEWAY` is set.
|
|
3
|
+
*
|
|
4
|
+
* Commands are forwarded to the gateway's `/latchkey/` RPC endpoint, while
|
|
5
|
+
* `latchkey curl` has its target URL rewritten to route through `/gateway/`.
|
|
6
|
+
*/
|
|
7
|
+
export class GatewayRequestError extends Error {
|
|
8
|
+
statusCode;
|
|
9
|
+
constructor(message, statusCode) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = 'GatewayRequestError';
|
|
12
|
+
this.statusCode = statusCode;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export class GatewayCommandNotSupportedError extends Error {
|
|
16
|
+
constructor(commandName) {
|
|
17
|
+
super(`'${commandName}' cannot be invoked when LATCHKEY_GATEWAY is set. ` +
|
|
18
|
+
`Unset the environment variable to run it locally.`);
|
|
19
|
+
this.name = 'GatewayCommandNotSupportedError';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export class GatewayCurlRewriteError extends Error {
|
|
23
|
+
constructor(message) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = 'GatewayCurlRewriteError';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function buildEndpointUrl(gatewayUrl, path) {
|
|
29
|
+
const base = gatewayUrl.replace(/\/+$/, '');
|
|
30
|
+
return `${base}${path}`;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* POST a request to the gateway's `/latchkey/` endpoint and return its `result`.
|
|
34
|
+
*/
|
|
35
|
+
export async function callLatchkeyEndpoint(gatewayUrl, request) {
|
|
36
|
+
const endpoint = buildEndpointUrl(gatewayUrl, '/latchkey');
|
|
37
|
+
let response;
|
|
38
|
+
try {
|
|
39
|
+
response = await fetch(endpoint, {
|
|
40
|
+
method: 'POST',
|
|
41
|
+
headers: { 'Content-Type': 'application/json' },
|
|
42
|
+
body: JSON.stringify(request),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
47
|
+
throw new GatewayRequestError(`Failed to reach latchkey gateway at ${endpoint}: ${message}`, 0);
|
|
48
|
+
}
|
|
49
|
+
const bodyText = await response.text();
|
|
50
|
+
let parsedBody;
|
|
51
|
+
try {
|
|
52
|
+
parsedBody =
|
|
53
|
+
bodyText === '' ? {} : JSON.parse(bodyText);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
throw new GatewayRequestError(`Latchkey gateway returned invalid JSON (status ${response.status.toString()}): ${bodyText}`, response.status);
|
|
57
|
+
}
|
|
58
|
+
if (!response.ok) {
|
|
59
|
+
const message = typeof parsedBody.error === 'string'
|
|
60
|
+
? parsedBody.error
|
|
61
|
+
: `Latchkey gateway returned status ${response.status.toString()}`;
|
|
62
|
+
throw new GatewayRequestError(message, response.status);
|
|
63
|
+
}
|
|
64
|
+
return parsedBody.result ?? null;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Build the URL used to proxy a `latchkey curl` invocation through the
|
|
68
|
+
* gateway's `/gateway/` endpoint.
|
|
69
|
+
*/
|
|
70
|
+
export function buildGatewayProxyUrl(gatewayUrl, targetUrl) {
|
|
71
|
+
return `${buildEndpointUrl(gatewayUrl, '/gateway/')}${targetUrl}`;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Rewrite a curl argument list so the target URL points at the gateway's
|
|
75
|
+
* `/gateway/<target>` endpoint. Returns a new array; the original is unchanged.
|
|
76
|
+
*/
|
|
77
|
+
export function rewriteCurlArgumentsForGateway(curlArguments, targetUrl, gatewayUrl) {
|
|
78
|
+
const occurrences = curlArguments.reduce((count, argument) => (argument === targetUrl ? count + 1 : count), 0);
|
|
79
|
+
if (occurrences === 0) {
|
|
80
|
+
throw new GatewayCurlRewriteError(`Target URL '${targetUrl}' not found in curl arguments; refusing to rewrite.`);
|
|
81
|
+
}
|
|
82
|
+
if (occurrences > 1) {
|
|
83
|
+
throw new GatewayCurlRewriteError(`Target URL '${targetUrl}' appears ${occurrences.toString()} times in curl arguments; ` +
|
|
84
|
+
`refusing to rewrite to avoid ambiguous substitution.`);
|
|
85
|
+
}
|
|
86
|
+
const proxyUrl = buildGatewayProxyUrl(gatewayUrl, targetUrl);
|
|
87
|
+
return curlArguments.map((argument) => (argument === targetUrl ? proxyUrl : argument));
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/gateway/client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,MAAM,OAAO,mBAAoB,SAAQ,KAAK;IACnC,UAAU,CAAS;IAE5B,YAAY,OAAe,EAAE,UAAkB;QAC7C,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;QAClC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;CACF;AAED,MAAM,OAAO,+BAAgC,SAAQ,KAAK;IACxD,YAAY,WAAmB;QAC7B,KAAK,CACH,IAAI,WAAW,oDAAoD;YACjE,mDAAmD,CACtD,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,iCAAiC,CAAC;IAChD,CAAC;CACF;AAED,MAAM,OAAO,uBAAwB,SAAQ,KAAK;IAChD,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;IACxC,CAAC;CACF;AAED,SAAS,gBAAgB,CAAC,UAAkB,EAAE,IAAY;IACxD,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAC5C,OAAO,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC;AAC1B,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,UAAkB,EAClB,OAAwB;IAExB,MAAM,QAAQ,GAAG,gBAAgB,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;IAE3D,IAAI,QAAkB,CAAC;IACvB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,KAAK,CAAC,QAAQ,EAAE;YAC/B,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;SAC9B,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,MAAM,IAAI,mBAAmB,CAAC,uCAAuC,QAAQ,KAAK,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;IAClG,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IACvC,IAAI,UAAgD,CAAC;IACrD,IAAI,CAAC;QACH,UAAU;YACR,QAAQ,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,QAAQ,CAA0C,CAAC;IAC1F,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,mBAAmB,CAC3B,kDAAkD,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,QAAQ,EAAE,EAC5F,QAAQ,CAAC,MAAM,CAChB,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,OAAO,GACX,OAAO,UAAU,CAAC,KAAK,KAAK,QAAQ;YAClC,CAAC,CAAC,UAAU,CAAC,KAAK;YAClB,CAAC,CAAC,oCAAoC,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC;QACvE,MAAM,IAAI,mBAAmB,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC1D,CAAC;IAED,OAAO,UAAU,CAAC,MAAM,IAAI,IAAI,CAAC;AACnC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAC,UAAkB,EAAE,SAAiB;IACxE,OAAO,GAAG,gBAAgB,CAAC,UAAU,EAAE,WAAW,CAAC,GAAG,SAAS,EAAE,CAAC;AACpE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,8BAA8B,CAC5C,aAAgC,EAChC,SAAiB,EACjB,UAAkB;IAElB,MAAM,WAAW,GAAG,aAAa,CAAC,MAAM,CACtC,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,EACjE,CAAC,CACF,CAAC;IACF,IAAI,WAAW,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,uBAAuB,CAC/B,eAAe,SAAS,qDAAqD,CAC9E,CAAC;IACJ,CAAC;IACD,IAAI,WAAW,GAAG,CAAC,EAAE,CAAC;QACpB,MAAM,IAAI,uBAAuB,CAC/B,eAAe,SAAS,aAAa,WAAW,CAAC,QAAQ,EAAE,4BAA4B;YACrF,sDAAsD,CACzD,CAAC;IACJ,CAAC;IACD,MAAM,QAAQ,GAAG,oBAAoB,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;IAC7D,OAAO,aAAa,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;AACzF,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP handler for the `/gateway/<target-url>` proxy endpoint.
|
|
3
|
+
*
|
|
4
|
+
* Forwards the incoming HTTP request through latchkey's credential injection
|
|
5
|
+
* pipeline (via `curl`) to the target URL and streams the upstream response
|
|
6
|
+
* back to the client.
|
|
7
|
+
*/
|
|
8
|
+
import * as http from 'node:http';
|
|
9
|
+
import type { ApiCredentialStore } from '../apiCredentials/store.js';
|
|
10
|
+
import type { CliDependencies } from '../cliCommands.js';
|
|
11
|
+
export declare const GATEWAY_PATH_PREFIX = "/gateway/";
|
|
12
|
+
export declare class BodyTooLargeError extends Error {
|
|
13
|
+
constructor();
|
|
14
|
+
}
|
|
15
|
+
export interface GatewayOptions {
|
|
16
|
+
readonly port: number;
|
|
17
|
+
readonly host: string;
|
|
18
|
+
readonly maxBodySize: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Extract the target URL from a raw gateway request URL.
|
|
22
|
+
* Strips the `/gateway/` prefix and returns the target URL.
|
|
23
|
+
* Returns null if the path doesn't start with `/gateway/` or the target URL is invalid.
|
|
24
|
+
*/
|
|
25
|
+
export declare function extractTargetUrl(rawUrl: string): string | null;
|
|
26
|
+
/**
|
|
27
|
+
* Build curl arguments from an HTTP request's components.
|
|
28
|
+
* Strips hop-by-hop headers and constructs a curl-compatible argument array.
|
|
29
|
+
*/
|
|
30
|
+
export declare function buildCurlArguments(method: string, headers: ReadonlyMap<string, string>, targetUrl: string, hasBody: boolean): readonly string[];
|
|
31
|
+
/**
|
|
32
|
+
* Parse response headers from curl's -D output.
|
|
33
|
+
* Returns the status code from the last status line and all response headers.
|
|
34
|
+
*/
|
|
35
|
+
export declare function parseResponseHeaders(headerDump: string): {
|
|
36
|
+
statusCode: number;
|
|
37
|
+
headers: ReadonlyMap<string, readonly string[]>;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Execute a proxied request through the credential injection pipeline.
|
|
41
|
+
*/
|
|
42
|
+
export declare function handleGatewayRequest(request: http.IncomingMessage, response: http.ServerResponse, targetUrl: string, deps: CliDependencies, apiCredentialStore: ApiCredentialStore, options: GatewayOptions): Promise<void>;
|
|
43
|
+
//# sourceMappingURL=gatewayEndpoint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gatewayEndpoint.d.ts","sourceRoot":"","sources":["../../../src/gateway/gatewayEndpoint.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAIlC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAErE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AA2BzD,eAAO,MAAM,mBAAmB,cAAc,CAAC;AAE/C,qBAAa,iBAAkB,SAAQ,KAAK;;CAK3C;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAwBD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAU9D;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EACpC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,OAAO,GACf,SAAS,MAAM,EAAE,CAqBnB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG;IACxD,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC;CACjD,CAiCA;AAkED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,IAAI,CAAC,eAAe,EAC7B,QAAQ,EAAE,IAAI,CAAC,cAAc,EAC7B,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,eAAe,EACrB,kBAAkB,EAAE,kBAAkB,EACtC,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,IAAI,CAAC,CA6Hf"}
|