@executor-js/plugin-openapi 0.0.2 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AddOpenApiSource-EV3NJHLA.js +19 -0
- package/dist/AddOpenApiSource-EV3NJHLA.js.map +1 -0
- package/dist/EditOpenApiSource-J7NXCVT5.js +665 -0
- package/dist/EditOpenApiSource-J7NXCVT5.js.map +1 -0
- package/dist/OpenApiSourceSummary-K5WZD5NR.js +122 -0
- package/dist/OpenApiSourceSummary-K5WZD5NR.js.map +1 -0
- package/dist/api/group.d.ts +79 -10
- package/dist/api/index.d.ts +384 -0
- package/dist/chunk-2BXVRXGL.js +1345 -0
- package/dist/chunk-2BXVRXGL.js.map +1 -0
- package/dist/chunk-E4QUTDQ2.js +1120 -0
- package/dist/chunk-E4QUTDQ2.js.map +1 -0
- package/dist/chunk-LBTK5F65.js +216 -0
- package/dist/chunk-LBTK5F65.js.map +1 -0
- package/dist/chunk-TRD7KSKA.js +1444 -0
- package/dist/chunk-TRD7KSKA.js.map +1 -0
- package/dist/client.js +165 -0
- package/dist/client.js.map +1 -0
- package/dist/core.js +8 -10
- package/dist/index.js +2 -1
- package/dist/react/AddOpenApiSource.d.ts +1 -1
- package/dist/react/OpenApiSourceDetailsFields.d.ts +18 -0
- package/dist/react/atoms.d.ts +158 -13
- package/dist/react/client.d.ts +85 -349
- package/dist/react/plugin-client.d.ts +2 -0
- package/dist/react/source-plugin.d.ts +1 -1
- package/dist/sdk/index.d.ts +1 -1
- package/dist/sdk/openapi-utils.d.ts +4 -3
- package/dist/sdk/parse.d.ts +1 -1
- package/dist/sdk/parse.test.d.ts +1 -0
- package/dist/sdk/plugin.d.ts +154 -34
- package/dist/sdk/store.d.ts +132 -47
- package/dist/sdk/types.d.ts +19 -44
- package/dist/sdk/upstream-failures.test.d.ts +1 -0
- package/dist/sdk/usage-scope-isolation.test.d.ts +1 -0
- package/dist/testing/index.d.ts +34 -0
- package/dist/testing.js +56 -0
- package/dist/testing.js.map +1 -0
- package/package.json +16 -4
- package/dist/chunk-ZZ7TQ4JC.js +0 -2602
- package/dist/chunk-ZZ7TQ4JC.js.map +0 -1
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AddOpenApiSource,
|
|
3
|
+
OPENAPI_OAUTH_CALLBACK_PATH,
|
|
4
|
+
OPENAPI_OAUTH_POPUP_NAME,
|
|
5
|
+
inferOAuthIssuerUrl,
|
|
6
|
+
openApiOAuthConnectionId,
|
|
7
|
+
resolveOAuthUrl
|
|
8
|
+
} from "./chunk-E4QUTDQ2.js";
|
|
9
|
+
import "./chunk-LBTK5F65.js";
|
|
10
|
+
import "./chunk-2BXVRXGL.js";
|
|
11
|
+
export {
|
|
12
|
+
OPENAPI_OAUTH_CALLBACK_PATH,
|
|
13
|
+
OPENAPI_OAUTH_POPUP_NAME,
|
|
14
|
+
AddOpenApiSource as default,
|
|
15
|
+
inferOAuthIssuerUrl,
|
|
16
|
+
openApiOAuthConnectionId,
|
|
17
|
+
resolveOAuthUrl
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=AddOpenApiSource-EV3NJHLA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,665 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OPENAPI_OAUTH_CALLBACK_PATH,
|
|
3
|
+
OPENAPI_OAUTH_POPUP_NAME,
|
|
4
|
+
OpenApiSourceDetailsFields,
|
|
5
|
+
inferOAuthIssuerUrl,
|
|
6
|
+
resolveOAuthUrl
|
|
7
|
+
} from "./chunk-E4QUTDQ2.js";
|
|
8
|
+
import {
|
|
9
|
+
openApiSourceAtom,
|
|
10
|
+
openApiSourceBindingsAtom,
|
|
11
|
+
removeOpenApiSourceBinding,
|
|
12
|
+
setOpenApiSourceBinding,
|
|
13
|
+
updateOpenApiSource
|
|
14
|
+
} from "./chunk-LBTK5F65.js";
|
|
15
|
+
import {
|
|
16
|
+
OAuth2SourceConfig,
|
|
17
|
+
OpenApiSourceBindingInput,
|
|
18
|
+
oauth2ClientSecretSlot
|
|
19
|
+
} from "./chunk-2BXVRXGL.js";
|
|
20
|
+
|
|
21
|
+
// src/react/EditOpenApiSource.tsx
|
|
22
|
+
import { useEffect, useMemo, useRef, useState } from "react";
|
|
23
|
+
import { useAtomSet, useAtomValue } from "@effect/atom-react";
|
|
24
|
+
import * as Exit from "effect/Exit";
|
|
25
|
+
import * as Option from "effect/Option";
|
|
26
|
+
import * as Schema from "effect/Schema";
|
|
27
|
+
import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
|
|
28
|
+
import { connectionsAtom, sourceAtom, startOAuth } from "@executor-js/react/api/atoms";
|
|
29
|
+
import { useScope, useScopeStack, useUserScope } from "@executor-js/react/api/scope-context";
|
|
30
|
+
import { connectionWriteKeys, sourceWriteKeys } from "@executor-js/react/api/reactivity-keys";
|
|
31
|
+
import { Button } from "@executor-js/react/components/button";
|
|
32
|
+
import { CopyButton } from "@executor-js/react/components/copy-button";
|
|
33
|
+
import {
|
|
34
|
+
CardStack,
|
|
35
|
+
CardStackContent,
|
|
36
|
+
CardStackEntry,
|
|
37
|
+
CardStackEntryContent,
|
|
38
|
+
CardStackEntryDescription,
|
|
39
|
+
CardStackEntryField,
|
|
40
|
+
CardStackEntryTitle
|
|
41
|
+
} from "@executor-js/react/components/card-stack";
|
|
42
|
+
import { FilterTabs } from "@executor-js/react/components/filter-tabs";
|
|
43
|
+
import { Input } from "@executor-js/react/components/input";
|
|
44
|
+
import { sourceWriteKeys as openApiWriteKeys } from "@executor-js/react/api/reactivity-keys";
|
|
45
|
+
import { ConnectionId, ScopeId, SecretId } from "@executor-js/sdk/core";
|
|
46
|
+
import { useSecretPickerSecrets } from "@executor-js/react/plugins/use-secret-picker-secrets";
|
|
47
|
+
import {
|
|
48
|
+
oauthCallbackUrl,
|
|
49
|
+
useOAuthPopupFlow
|
|
50
|
+
} from "@executor-js/react/plugins/oauth-sign-in";
|
|
51
|
+
import {
|
|
52
|
+
effectiveCredentialBindingForScope,
|
|
53
|
+
exactCredentialBindingForScope,
|
|
54
|
+
isConnectionCredentialBindingValue,
|
|
55
|
+
isSecretCredentialBindingValue
|
|
56
|
+
} from "@executor-js/react/plugins/credential-bindings";
|
|
57
|
+
import { SecretCredentialSlotBindings } from "@executor-js/react/plugins/credential-slot-bindings";
|
|
58
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
59
|
+
var ErrorMessage = Schema.Struct({ message: Schema.String });
|
|
60
|
+
var decodeErrorMessage = Schema.decodeUnknownOption(ErrorMessage);
|
|
61
|
+
var errorMessageFromExit = (exit, fallback) => Option.match(Option.flatMap(Exit.findErrorOption(exit), decodeErrorMessage), {
|
|
62
|
+
onNone: () => fallback,
|
|
63
|
+
onSome: ({ message }) => message
|
|
64
|
+
});
|
|
65
|
+
var slugify = (value) => value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "default";
|
|
66
|
+
var shortHash = (value) => {
|
|
67
|
+
let hash = 2166136261;
|
|
68
|
+
for (let i = 0; i < value.length; i++) {
|
|
69
|
+
hash ^= value.charCodeAt(i);
|
|
70
|
+
hash = Math.imul(hash, 16777619);
|
|
71
|
+
}
|
|
72
|
+
return (hash >>> 0).toString(36).slice(0, 6);
|
|
73
|
+
};
|
|
74
|
+
var openApiOAuthConnectionId = (sourceId, securitySchemeName, targetScope) => ConnectionId.make(
|
|
75
|
+
`openapi-oauth-${slugify(sourceId)}-${slugify(securitySchemeName)}-${shortHash(targetScope)}`
|
|
76
|
+
);
|
|
77
|
+
var effectiveClientSecretSlot = (oauth2) => oauth2.clientSecretSlot ?? oauth2ClientSecretSlot(oauth2.securitySchemeName);
|
|
78
|
+
function EditOpenApiSource(props) {
|
|
79
|
+
const displayScope = useScope();
|
|
80
|
+
const scopeStack = useScopeStack();
|
|
81
|
+
const userScope = useUserScope();
|
|
82
|
+
const sourceSummaryResult = useAtomValue(sourceAtom(props.sourceId, displayScope));
|
|
83
|
+
const sourceSummary = AsyncResult.isSuccess(sourceSummaryResult) && sourceSummaryResult.value ? sourceSummaryResult.value : null;
|
|
84
|
+
const sourceScopeId = sourceSummary?.scopeId ?? displayScope;
|
|
85
|
+
const sourceScope = ScopeId.make(sourceScopeId);
|
|
86
|
+
const scopeRanks = useMemo(
|
|
87
|
+
() => new Map(scopeStack.map((scope, index) => [scope.id, index])),
|
|
88
|
+
[scopeStack]
|
|
89
|
+
);
|
|
90
|
+
const sourceResult = useAtomValue(openApiSourceAtom(sourceScope, props.sourceId));
|
|
91
|
+
const bindingsResult = useAtomValue(
|
|
92
|
+
openApiSourceBindingsAtom(displayScope, props.sourceId, sourceScope)
|
|
93
|
+
);
|
|
94
|
+
const connectionsResult = useAtomValue(connectionsAtom(displayScope));
|
|
95
|
+
const secretList = useSecretPickerSecrets();
|
|
96
|
+
const doUpdate = useAtomSet(updateOpenApiSource, { mode: "promiseExit" });
|
|
97
|
+
const doSetBinding = useAtomSet(setOpenApiSourceBinding, {
|
|
98
|
+
mode: "promiseExit"
|
|
99
|
+
});
|
|
100
|
+
const doRemoveBinding = useAtomSet(removeOpenApiSourceBinding, {
|
|
101
|
+
mode: "promiseExit"
|
|
102
|
+
});
|
|
103
|
+
const doStartOAuth = useAtomSet(startOAuth, { mode: "promiseExit" });
|
|
104
|
+
const oauth = useOAuthPopupFlow({
|
|
105
|
+
popupName: OPENAPI_OAUTH_POPUP_NAME,
|
|
106
|
+
popupBlockedMessage: "OAuth popup was blocked by the browser",
|
|
107
|
+
startErrorMessage: "Failed to connect OAuth"
|
|
108
|
+
});
|
|
109
|
+
const source = AsyncResult.isSuccess(sourceResult) && sourceResult.value ? sourceResult.value : null;
|
|
110
|
+
const bindingRows = AsyncResult.isSuccess(bindingsResult) ? bindingsResult.value : [];
|
|
111
|
+
const connections = AsyncResult.isSuccess(connectionsResult) ? connectionsResult.value : [];
|
|
112
|
+
const oauth2RedirectUrl = oauthCallbackUrl(OPENAPI_OAUTH_CALLBACK_PATH);
|
|
113
|
+
const [name, setName] = useState(source?.name ?? "");
|
|
114
|
+
const [baseUrl, setBaseUrl] = useState(source?.config.baseUrl ?? "");
|
|
115
|
+
const [sourceSaveState, setSourceSaveState] = useState("idle");
|
|
116
|
+
const [error, setError] = useState(null);
|
|
117
|
+
const [busyKey, setBusyKey] = useState(null);
|
|
118
|
+
const [pendingOAuthConnection, setPendingOAuthConnection] = useState(null);
|
|
119
|
+
const [loadedSourceKey, setLoadedSourceKey] = useState(null);
|
|
120
|
+
const [selectedOAuthTokenScope, setSelectedOAuthTokenScope] = useState(
|
|
121
|
+
userScope !== sourceScopeId ? userScope : sourceScopeId
|
|
122
|
+
);
|
|
123
|
+
const [oauth2AuthorizationUrl, setOAuth2AuthorizationUrl] = useState(
|
|
124
|
+
source?.config.oauth2?.authorizationUrl ?? ""
|
|
125
|
+
);
|
|
126
|
+
const [oauth2TokenUrl, setOAuth2TokenUrl] = useState(source?.config.oauth2?.tokenUrl ?? "");
|
|
127
|
+
const [oauth2EndpointsSaveState, setOAuth2EndpointsSaveState] = useState("idle");
|
|
128
|
+
const editIdentity = useMemo(
|
|
129
|
+
() => ({
|
|
130
|
+
name,
|
|
131
|
+
namespace: props.sourceId,
|
|
132
|
+
setName,
|
|
133
|
+
setNamespace: () => {
|
|
134
|
+
},
|
|
135
|
+
reset: () => {
|
|
136
|
+
}
|
|
137
|
+
}),
|
|
138
|
+
[name, props.sourceId]
|
|
139
|
+
);
|
|
140
|
+
const sourceSaveSeq = useRef(0);
|
|
141
|
+
const oauth2EndpointsSaveSeq = useRef(0);
|
|
142
|
+
useEffect(() => {
|
|
143
|
+
setSelectedOAuthTokenScope(userScope !== sourceScopeId ? userScope : sourceScopeId);
|
|
144
|
+
}, [sourceScopeId, userScope]);
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
if (!source) return;
|
|
147
|
+
const sourceKey = `${sourceScopeId}:${source.namespace}`;
|
|
148
|
+
if (loadedSourceKey === sourceKey) return;
|
|
149
|
+
setName(source.name);
|
|
150
|
+
setBaseUrl(source.config.baseUrl ?? "");
|
|
151
|
+
setOAuth2AuthorizationUrl(source.config.oauth2?.authorizationUrl ?? "");
|
|
152
|
+
setOAuth2TokenUrl(source.config.oauth2?.tokenUrl ?? "");
|
|
153
|
+
setOAuth2EndpointsSaveState("idle");
|
|
154
|
+
setSourceSaveState("idle");
|
|
155
|
+
setLoadedSourceKey(sourceKey);
|
|
156
|
+
}, [loadedSourceKey, source, sourceScopeId]);
|
|
157
|
+
useEffect(() => {
|
|
158
|
+
if (!source) return;
|
|
159
|
+
const sourceKey = `${sourceScopeId}:${source.namespace}`;
|
|
160
|
+
if (loadedSourceKey !== sourceKey) return;
|
|
161
|
+
const nextName = name.trim();
|
|
162
|
+
const nextBaseUrl = baseUrl.trim();
|
|
163
|
+
const currentName = source.name;
|
|
164
|
+
const currentBaseUrl = source.config.baseUrl ?? "";
|
|
165
|
+
if ((nextName || currentName) === currentName && nextBaseUrl === currentBaseUrl) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const timeout = window.setTimeout(() => {
|
|
169
|
+
const seq = ++sourceSaveSeq.current;
|
|
170
|
+
setSourceSaveState("saving");
|
|
171
|
+
setError(null);
|
|
172
|
+
void (async () => {
|
|
173
|
+
const exit = await doUpdate({
|
|
174
|
+
params: { scopeId: displayScope, namespace: props.sourceId },
|
|
175
|
+
payload: {
|
|
176
|
+
sourceScope,
|
|
177
|
+
name: nextName || void 0,
|
|
178
|
+
baseUrl: nextBaseUrl || void 0
|
|
179
|
+
},
|
|
180
|
+
reactivityKeys: openApiWriteKeys
|
|
181
|
+
});
|
|
182
|
+
if (sourceSaveSeq.current !== seq) return;
|
|
183
|
+
if (Exit.isFailure(exit)) {
|
|
184
|
+
setSourceSaveState("idle");
|
|
185
|
+
setError(errorMessageFromExit(exit, "Failed to save source details"));
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
setSourceSaveState("saved");
|
|
189
|
+
window.setTimeout(() => {
|
|
190
|
+
if (sourceSaveSeq.current === seq) setSourceSaveState("idle");
|
|
191
|
+
}, 1600);
|
|
192
|
+
})();
|
|
193
|
+
}, 600);
|
|
194
|
+
return () => window.clearTimeout(timeout);
|
|
195
|
+
}, [
|
|
196
|
+
baseUrl,
|
|
197
|
+
displayScope,
|
|
198
|
+
doUpdate,
|
|
199
|
+
loadedSourceKey,
|
|
200
|
+
name,
|
|
201
|
+
props.sourceId,
|
|
202
|
+
source,
|
|
203
|
+
sourceScope,
|
|
204
|
+
sourceScopeId
|
|
205
|
+
]);
|
|
206
|
+
const secretSlots = useMemo(() => {
|
|
207
|
+
if (!source) return [];
|
|
208
|
+
const slots = [];
|
|
209
|
+
for (const [headerName, value] of Object.entries(source.config.headers ?? {})) {
|
|
210
|
+
if (typeof value === "string") continue;
|
|
211
|
+
slots.push({
|
|
212
|
+
kind: "secret",
|
|
213
|
+
slot: value.slot,
|
|
214
|
+
label: headerName,
|
|
215
|
+
hint: value.prefix ? `Prefix: ${value.prefix}` : void 0
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
if (source.config.oauth2) {
|
|
219
|
+
const clientSecretSlot = effectiveClientSecretSlot(source.config.oauth2);
|
|
220
|
+
slots.push({
|
|
221
|
+
kind: "secret",
|
|
222
|
+
slot: source.config.oauth2.clientIdSlot,
|
|
223
|
+
label: "Client ID"
|
|
224
|
+
});
|
|
225
|
+
slots.push({
|
|
226
|
+
kind: "secret",
|
|
227
|
+
slot: clientSecretSlot,
|
|
228
|
+
label: "Client Secret",
|
|
229
|
+
hint: source.config.oauth2.flow === "authorizationCode" ? "Optional for public PKCE clients" : void 0
|
|
230
|
+
});
|
|
231
|
+
slots.push({
|
|
232
|
+
kind: "oauth2",
|
|
233
|
+
slot: source.config.oauth2.connectionSlot,
|
|
234
|
+
label: source.config.oauth2.flow === "clientCredentials" ? "OAuth Client Credentials" : "OAuth Authorization Code"
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
return slots;
|
|
238
|
+
}, [source]);
|
|
239
|
+
const credentialScopes = useMemo(() => {
|
|
240
|
+
const entries = [{ scopeId: ScopeId.make(sourceScopeId), label: "Organization" }];
|
|
241
|
+
if (userScope !== sourceScopeId) {
|
|
242
|
+
entries.unshift({ scopeId: ScopeId.make(userScope), label: "Personal" });
|
|
243
|
+
} else {
|
|
244
|
+
entries[0] = {
|
|
245
|
+
scopeId: ScopeId.make(sourceScopeId),
|
|
246
|
+
label: "Credentials"
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
return entries;
|
|
250
|
+
}, [sourceScopeId, userScope]);
|
|
251
|
+
const credentialScopeOptions = useMemo(
|
|
252
|
+
() => credentialScopes.map((entry) => ({
|
|
253
|
+
scopeId: entry.scopeId,
|
|
254
|
+
label: entry.label,
|
|
255
|
+
description: entry.label === "Personal" ? "Saved only for your account." : "Shared with everyone who can use this source."
|
|
256
|
+
})),
|
|
257
|
+
[credentialScopes]
|
|
258
|
+
);
|
|
259
|
+
const organizationCredentialScope = credentialScopes.find((entry) => entry.label === "Organization") ?? credentialScopes[0];
|
|
260
|
+
const personalCredentialScope = credentialScopes.find((entry) => entry.label === "Personal") ?? null;
|
|
261
|
+
const secretBindingScopes = personalCredentialScope && personalCredentialScope.scopeId !== organizationCredentialScope.scopeId ? [organizationCredentialScope, personalCredentialScope] : [organizationCredentialScope];
|
|
262
|
+
const activeOAuthTokenScope = credentialScopes.find((entry) => entry.scopeId === selectedOAuthTokenScope) ?? credentialScopes[0];
|
|
263
|
+
const activeOAuthTokenScopeId = activeOAuthTokenScope.scopeId;
|
|
264
|
+
const activeOAuthTokenScopeLabel = activeOAuthTokenScope.label;
|
|
265
|
+
if (!source) {
|
|
266
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
267
|
+
/* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold text-foreground", children: "Edit OpenAPI Source" }),
|
|
268
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Loading configuration\u2026" })
|
|
269
|
+
] });
|
|
270
|
+
}
|
|
271
|
+
const setSecretBinding = async (targetScope, slot, secretId, secretScope) => {
|
|
272
|
+
const inputKey = `${targetScope}:${slot}`;
|
|
273
|
+
const trimmed = secretId.trim();
|
|
274
|
+
if (!trimmed) return;
|
|
275
|
+
setBusyKey(inputKey);
|
|
276
|
+
setError(null);
|
|
277
|
+
const exit = await doSetBinding({
|
|
278
|
+
params: { scopeId: displayScope },
|
|
279
|
+
payload: new OpenApiSourceBindingInput({
|
|
280
|
+
sourceId: props.sourceId,
|
|
281
|
+
sourceScope,
|
|
282
|
+
scope: targetScope,
|
|
283
|
+
slot,
|
|
284
|
+
value: {
|
|
285
|
+
kind: "secret",
|
|
286
|
+
secretId: SecretId.make(trimmed),
|
|
287
|
+
secretScopeId: secretScope
|
|
288
|
+
}
|
|
289
|
+
}),
|
|
290
|
+
reactivityKeys: sourceWriteKeys
|
|
291
|
+
});
|
|
292
|
+
if (Exit.isFailure(exit)) {
|
|
293
|
+
setError(errorMessageFromExit(exit, "Failed to save credential binding"));
|
|
294
|
+
}
|
|
295
|
+
setBusyKey(null);
|
|
296
|
+
};
|
|
297
|
+
const clearBinding = async (targetScope, slot) => {
|
|
298
|
+
setBusyKey(`${targetScope}:${slot}:clear`);
|
|
299
|
+
setError(null);
|
|
300
|
+
const exit = await doRemoveBinding({
|
|
301
|
+
params: { scopeId: displayScope },
|
|
302
|
+
payload: {
|
|
303
|
+
sourceId: props.sourceId,
|
|
304
|
+
sourceScope,
|
|
305
|
+
slot,
|
|
306
|
+
scope: targetScope
|
|
307
|
+
},
|
|
308
|
+
reactivityKeys: sourceWriteKeys
|
|
309
|
+
});
|
|
310
|
+
if (Exit.isFailure(exit)) {
|
|
311
|
+
setError(errorMessageFromExit(exit, "Failed to clear credential binding"));
|
|
312
|
+
}
|
|
313
|
+
setBusyKey(null);
|
|
314
|
+
};
|
|
315
|
+
const connectOAuth = async (targetScope) => {
|
|
316
|
+
const oauth2 = source.config.oauth2;
|
|
317
|
+
if (!oauth2) return;
|
|
318
|
+
const clientIdBinding = effectiveCredentialBindingForScope(
|
|
319
|
+
bindingRows,
|
|
320
|
+
oauth2.clientIdSlot,
|
|
321
|
+
targetScope,
|
|
322
|
+
scopeRanks
|
|
323
|
+
);
|
|
324
|
+
const clientSecretSlot = effectiveClientSecretSlot(oauth2);
|
|
325
|
+
const clientSecretBinding = effectiveCredentialBindingForScope(
|
|
326
|
+
bindingRows,
|
|
327
|
+
clientSecretSlot,
|
|
328
|
+
targetScope,
|
|
329
|
+
scopeRanks
|
|
330
|
+
);
|
|
331
|
+
if (!clientIdBinding || !isSecretCredentialBindingValue(clientIdBinding.value)) {
|
|
332
|
+
setError("Client ID must be bound before connecting");
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
const clientIdSecretId = clientIdBinding.value.secretId;
|
|
336
|
+
if (oauth2.flow === "clientCredentials" && (!clientSecretBinding || !isSecretCredentialBindingValue(clientSecretBinding.value))) {
|
|
337
|
+
setError("Client secret must be bound before connecting");
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
const clientSecretValue = oauth2.flow === "clientCredentials" && clientSecretBinding && isSecretCredentialBindingValue(clientSecretBinding.value) ? clientSecretBinding.value : null;
|
|
341
|
+
const existingConnection = exactCredentialBindingForScope(
|
|
342
|
+
bindingRows,
|
|
343
|
+
oauth2.connectionSlot,
|
|
344
|
+
targetScope
|
|
345
|
+
);
|
|
346
|
+
const connectionId = existingConnection && isConnectionCredentialBindingValue(existingConnection.value) ? existingConnection.value.connectionId : openApiOAuthConnectionId(props.sourceId, oauth2.securitySchemeName, targetScope);
|
|
347
|
+
setBusyKey(`${targetScope}:${oauth2.connectionSlot}:connect`);
|
|
348
|
+
setPendingOAuthConnection({
|
|
349
|
+
scopeId: targetScope,
|
|
350
|
+
slot: oauth2.connectionSlot,
|
|
351
|
+
connectionId
|
|
352
|
+
});
|
|
353
|
+
setError(null);
|
|
354
|
+
const failConnect = (message) => {
|
|
355
|
+
setError(message);
|
|
356
|
+
setPendingOAuthConnection(null);
|
|
357
|
+
setBusyKey(null);
|
|
358
|
+
};
|
|
359
|
+
const displayName = source.name;
|
|
360
|
+
const tokenUrl = resolveOAuthUrl(oauth2.tokenUrl, source.config.baseUrl ?? "");
|
|
361
|
+
if (oauth2.flow === "clientCredentials") {
|
|
362
|
+
const startOAuthExit2 = await doStartOAuth({
|
|
363
|
+
params: { scopeId: targetScope },
|
|
364
|
+
payload: {
|
|
365
|
+
endpoint: tokenUrl,
|
|
366
|
+
redirectUrl: tokenUrl,
|
|
367
|
+
connectionId,
|
|
368
|
+
tokenScope: targetScope,
|
|
369
|
+
strategy: {
|
|
370
|
+
kind: "client-credentials",
|
|
371
|
+
tokenEndpoint: tokenUrl,
|
|
372
|
+
clientIdSecretId,
|
|
373
|
+
clientSecretSecretId: clientSecretValue.secretId,
|
|
374
|
+
scopes: [...oauth2.scopes]
|
|
375
|
+
},
|
|
376
|
+
pluginId: "openapi",
|
|
377
|
+
identityLabel: `${displayName} OAuth`
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
if (Exit.isFailure(startOAuthExit2)) {
|
|
381
|
+
failConnect(errorMessageFromExit(startOAuthExit2, "Failed to connect OAuth"));
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
const response2 = startOAuthExit2.value;
|
|
385
|
+
if (!response2.completedConnection) {
|
|
386
|
+
failConnect("Unexpected OAuth response");
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
const setBindingExit = await doSetBinding({
|
|
390
|
+
params: { scopeId: displayScope },
|
|
391
|
+
payload: new OpenApiSourceBindingInput({
|
|
392
|
+
sourceId: props.sourceId,
|
|
393
|
+
sourceScope,
|
|
394
|
+
scope: targetScope,
|
|
395
|
+
slot: oauth2.connectionSlot,
|
|
396
|
+
value: {
|
|
397
|
+
kind: "connection",
|
|
398
|
+
connectionId: ConnectionId.make(response2.completedConnection.connectionId)
|
|
399
|
+
}
|
|
400
|
+
}),
|
|
401
|
+
reactivityKeys: [...sourceWriteKeys, ...connectionWriteKeys]
|
|
402
|
+
});
|
|
403
|
+
if (Exit.isFailure(setBindingExit)) {
|
|
404
|
+
failConnect(errorMessageFromExit(setBindingExit, "Failed to connect OAuth"));
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
setPendingOAuthConnection(null);
|
|
408
|
+
setBusyKey(null);
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
const authorizationUrl = resolveOAuthUrl(
|
|
412
|
+
oauth2.authorizationUrl ?? "",
|
|
413
|
+
source.config.baseUrl ?? ""
|
|
414
|
+
);
|
|
415
|
+
const issuerUrl = oauth2.issuerUrl ?? inferOAuthIssuerUrl(authorizationUrl);
|
|
416
|
+
const startOAuthExit = await doStartOAuth({
|
|
417
|
+
params: { scopeId: targetScope },
|
|
418
|
+
payload: {
|
|
419
|
+
endpoint: authorizationUrl,
|
|
420
|
+
connectionId,
|
|
421
|
+
tokenScope: targetScope,
|
|
422
|
+
redirectUrl: oauth2RedirectUrl,
|
|
423
|
+
strategy: {
|
|
424
|
+
kind: "authorization-code",
|
|
425
|
+
authorizationEndpoint: authorizationUrl,
|
|
426
|
+
tokenEndpoint: tokenUrl,
|
|
427
|
+
issuerUrl,
|
|
428
|
+
clientIdSecretId,
|
|
429
|
+
clientSecretSecretId: clientSecretBinding && isSecretCredentialBindingValue(clientSecretBinding.value) ? clientSecretBinding.value.secretId : null,
|
|
430
|
+
scopes: [...oauth2.scopes]
|
|
431
|
+
},
|
|
432
|
+
pluginId: "openapi",
|
|
433
|
+
identityLabel: `${displayName} OAuth`
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
if (Exit.isFailure(startOAuthExit)) {
|
|
437
|
+
failConnect(errorMessageFromExit(startOAuthExit, "Failed to connect OAuth"));
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
const response = startOAuthExit.value;
|
|
441
|
+
if (response.authorizationUrl === null) {
|
|
442
|
+
failConnect("Unexpected OAuth response");
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
await oauth.openAuthorization({
|
|
446
|
+
tokenScope: targetScope,
|
|
447
|
+
run: async () => ({
|
|
448
|
+
sessionId: response.sessionId,
|
|
449
|
+
authorizationUrl: response.authorizationUrl
|
|
450
|
+
}),
|
|
451
|
+
onSuccess: async (result) => {
|
|
452
|
+
const setBindingExit = await doSetBinding({
|
|
453
|
+
params: { scopeId: displayScope },
|
|
454
|
+
payload: new OpenApiSourceBindingInput({
|
|
455
|
+
sourceId: props.sourceId,
|
|
456
|
+
sourceScope,
|
|
457
|
+
scope: targetScope,
|
|
458
|
+
slot: oauth2.connectionSlot,
|
|
459
|
+
value: {
|
|
460
|
+
kind: "connection",
|
|
461
|
+
connectionId: ConnectionId.make(result.connectionId)
|
|
462
|
+
}
|
|
463
|
+
}),
|
|
464
|
+
reactivityKeys: [...sourceWriteKeys, ...connectionWriteKeys]
|
|
465
|
+
});
|
|
466
|
+
if (Exit.isFailure(setBindingExit)) {
|
|
467
|
+
failConnect(errorMessageFromExit(setBindingExit, "Failed to connect OAuth"));
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
setPendingOAuthConnection(null);
|
|
471
|
+
setBusyKey(null);
|
|
472
|
+
},
|
|
473
|
+
onError: (message) => {
|
|
474
|
+
setError(message);
|
|
475
|
+
setPendingOAuthConnection(null);
|
|
476
|
+
setBusyKey(null);
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
};
|
|
480
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
|
|
481
|
+
/* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold text-foreground", children: "OpenAPI Source" }) }),
|
|
482
|
+
/* @__PURE__ */ jsx(
|
|
483
|
+
OpenApiSourceDetailsFields,
|
|
484
|
+
{
|
|
485
|
+
title: "Source Details",
|
|
486
|
+
description: "Name and base URL save automatically.",
|
|
487
|
+
identity: editIdentity,
|
|
488
|
+
baseUrl,
|
|
489
|
+
onBaseUrlChange: setBaseUrl,
|
|
490
|
+
specUrl: source.config.sourceUrl ?? "",
|
|
491
|
+
onSpecUrlChange: () => {
|
|
492
|
+
},
|
|
493
|
+
specUrlDisabled: true,
|
|
494
|
+
namespaceReadOnly: true,
|
|
495
|
+
saveState: sourceSaveState,
|
|
496
|
+
footer: source.config.oauth2 ? `Authentication Template: OAuth2 ${source.config.oauth2.flow}` : Object.keys(source.config.headers ?? {}).length > 0 ? `Authentication Template: ${Object.keys(source.config.headers ?? {}).length} header binding${Object.keys(source.config.headers ?? {}).length === 1 ? "" : "s"}` : "Authentication Template: None"
|
|
497
|
+
}
|
|
498
|
+
),
|
|
499
|
+
/* @__PURE__ */ jsx(CardStack, { children: /* @__PURE__ */ jsxs(CardStackContent, { className: "border-t-0", children: [
|
|
500
|
+
/* @__PURE__ */ jsx(CardStackEntry, { children: /* @__PURE__ */ jsx(CardStackEntryContent, { children: /* @__PURE__ */ jsx(CardStackEntryTitle, { children: "Secrets" }) }) }),
|
|
501
|
+
/* @__PURE__ */ jsx(
|
|
502
|
+
SecretCredentialSlotBindings,
|
|
503
|
+
{
|
|
504
|
+
slots: secretSlots.filter((slot) => slot.kind === "secret"),
|
|
505
|
+
bindingScopes: secretBindingScopes,
|
|
506
|
+
bindingRows,
|
|
507
|
+
scopeRanks,
|
|
508
|
+
secrets: secretList,
|
|
509
|
+
sourceId: props.sourceId,
|
|
510
|
+
sourceName: source.name,
|
|
511
|
+
credentialScopeOptions,
|
|
512
|
+
busyKey,
|
|
513
|
+
onSetSecretBinding: setSecretBinding,
|
|
514
|
+
onClearBinding: clearBinding
|
|
515
|
+
}
|
|
516
|
+
),
|
|
517
|
+
source.config.oauth2 && (() => {
|
|
518
|
+
const oauth2 = source.config.oauth2;
|
|
519
|
+
const trimmedAuthUrl = oauth2AuthorizationUrl.trim();
|
|
520
|
+
const trimmedTokenUrl = oauth2TokenUrl.trim();
|
|
521
|
+
const savedAuthUrl = oauth2.authorizationUrl ?? "";
|
|
522
|
+
const isAuthCode = oauth2.flow === "authorizationCode";
|
|
523
|
+
const endpointsDirty = isAuthCode && trimmedAuthUrl !== savedAuthUrl || trimmedTokenUrl !== oauth2.tokenUrl;
|
|
524
|
+
const saving = oauth2EndpointsSaveState === "saving";
|
|
525
|
+
const tokenUrlMissing = trimmedTokenUrl.length === 0;
|
|
526
|
+
const authUrlMissing = isAuthCode && trimmedAuthUrl.length === 0;
|
|
527
|
+
const canSave = endpointsDirty && !saving && !tokenUrlMissing && !authUrlMissing;
|
|
528
|
+
const saveOAuth2Endpoints = async () => {
|
|
529
|
+
const seq = ++oauth2EndpointsSaveSeq.current;
|
|
530
|
+
setOAuth2EndpointsSaveState("saving");
|
|
531
|
+
setError(null);
|
|
532
|
+
const exit = await doUpdate({
|
|
533
|
+
params: { scopeId: displayScope, namespace: props.sourceId },
|
|
534
|
+
payload: {
|
|
535
|
+
sourceScope,
|
|
536
|
+
oauth2: new OAuth2SourceConfig({
|
|
537
|
+
kind: "oauth2",
|
|
538
|
+
securitySchemeName: oauth2.securitySchemeName,
|
|
539
|
+
flow: oauth2.flow,
|
|
540
|
+
tokenUrl: trimmedTokenUrl,
|
|
541
|
+
authorizationUrl: isAuthCode ? trimmedAuthUrl || null : null,
|
|
542
|
+
issuerUrl: oauth2.issuerUrl ?? null,
|
|
543
|
+
clientIdSlot: oauth2.clientIdSlot,
|
|
544
|
+
clientSecretSlot: oauth2.clientSecretSlot,
|
|
545
|
+
connectionSlot: oauth2.connectionSlot,
|
|
546
|
+
scopes: [...oauth2.scopes]
|
|
547
|
+
})
|
|
548
|
+
},
|
|
549
|
+
reactivityKeys: openApiWriteKeys
|
|
550
|
+
});
|
|
551
|
+
if (oauth2EndpointsSaveSeq.current !== seq) return;
|
|
552
|
+
if (Exit.isFailure(exit)) {
|
|
553
|
+
setOAuth2EndpointsSaveState("idle");
|
|
554
|
+
setError(errorMessageFromExit(exit, "Failed to save OAuth endpoints"));
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
setOAuth2EndpointsSaveState("saved");
|
|
558
|
+
window.setTimeout(() => {
|
|
559
|
+
if (oauth2EndpointsSaveSeq.current === seq) {
|
|
560
|
+
setOAuth2EndpointsSaveState("idle");
|
|
561
|
+
}
|
|
562
|
+
}, 1600);
|
|
563
|
+
};
|
|
564
|
+
const exact = exactCredentialBindingForScope(
|
|
565
|
+
bindingRows,
|
|
566
|
+
oauth2.connectionSlot,
|
|
567
|
+
activeOAuthTokenScopeId
|
|
568
|
+
);
|
|
569
|
+
const binding = exact ?? effectiveCredentialBindingForScope(
|
|
570
|
+
bindingRows,
|
|
571
|
+
oauth2.connectionSlot,
|
|
572
|
+
activeOAuthTokenScopeId,
|
|
573
|
+
scopeRanks
|
|
574
|
+
);
|
|
575
|
+
const connectionBinding = binding && isConnectionCredentialBindingValue(binding.value) ? binding.value : null;
|
|
576
|
+
const connection = connectionBinding ? connections.find((entry) => entry.id === connectionBinding.connectionId) : null;
|
|
577
|
+
const bindingScopeId = connectionBinding && binding ? binding.scopeId : null;
|
|
578
|
+
const isConnecting = busyKey === `${activeOAuthTokenScopeId}:${oauth2.connectionSlot}:connect`;
|
|
579
|
+
const isPendingOAuthConnection = pendingOAuthConnection?.scopeId === activeOAuthTokenScopeId && pendingOAuthConnection !== null && pendingOAuthConnection.slot === oauth2.connectionSlot;
|
|
580
|
+
const isConnected = connection !== null && connection !== void 0;
|
|
581
|
+
const statusText = isConnecting || isPendingOAuthConnection ? "Saving OAuth connection..." : connectionBinding && bindingScopeId ? connection ? bindingScopeId === activeOAuthTokenScopeId ? `Connected in ${activeOAuthTokenScopeLabel.toLowerCase()} as ${connection.identityLabel ?? connection.id}` : `Using organization connection ${connection.identityLabel ?? connection.id}` : bindingScopeId === activeOAuthTokenScopeId ? `Saved connection is missing in ${activeOAuthTokenScopeLabel.toLowerCase()}` : "Organization connection is missing" : `No ${activeOAuthTokenScopeLabel.toLowerCase()} connection`;
|
|
582
|
+
const connectDisabled = isConnecting || endpointsDirty || saving;
|
|
583
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
584
|
+
/* @__PURE__ */ jsxs(CardStackEntry, { children: [
|
|
585
|
+
/* @__PURE__ */ jsxs(CardStackEntryContent, { children: [
|
|
586
|
+
/* @__PURE__ */ jsx(CardStackEntryTitle, { children: "OAuth Endpoints" }),
|
|
587
|
+
/* @__PURE__ */ jsx(CardStackEntryDescription, { children: "Override the URLs from the OpenAPI spec when a provider publishes the wrong values." })
|
|
588
|
+
] }),
|
|
589
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
590
|
+
oauth2EndpointsSaveState !== "idle" && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: saving ? "Saving\u2026" : "Saved" }),
|
|
591
|
+
/* @__PURE__ */ jsx(
|
|
592
|
+
Button,
|
|
593
|
+
{
|
|
594
|
+
size: "sm",
|
|
595
|
+
onClick: () => void saveOAuth2Endpoints(),
|
|
596
|
+
disabled: !canSave,
|
|
597
|
+
children: "Save"
|
|
598
|
+
}
|
|
599
|
+
)
|
|
600
|
+
] })
|
|
601
|
+
] }),
|
|
602
|
+
isAuthCode && /* @__PURE__ */ jsx(CardStackEntryField, { label: "Authorization URL", children: /* @__PURE__ */ jsx(
|
|
603
|
+
Input,
|
|
604
|
+
{
|
|
605
|
+
value: oauth2AuthorizationUrl,
|
|
606
|
+
onChange: (e) => setOAuth2AuthorizationUrl(e.target.value),
|
|
607
|
+
className: "font-mono text-sm"
|
|
608
|
+
}
|
|
609
|
+
) }),
|
|
610
|
+
/* @__PURE__ */ jsx(CardStackEntryField, { label: "Token URL", children: /* @__PURE__ */ jsx(
|
|
611
|
+
Input,
|
|
612
|
+
{
|
|
613
|
+
value: oauth2TokenUrl,
|
|
614
|
+
onChange: (e) => setOAuth2TokenUrl(e.target.value),
|
|
615
|
+
className: "font-mono text-sm"
|
|
616
|
+
}
|
|
617
|
+
) }),
|
|
618
|
+
/* @__PURE__ */ jsx(CardStackEntryField, { label: "Redirect URL", children: /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
619
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 rounded-md border border-border bg-background/50 px-2.5 py-1.5 font-mono text-[11px]", children: [
|
|
620
|
+
/* @__PURE__ */ jsx("span", { className: "truncate flex-1 text-foreground", children: oauth2RedirectUrl }),
|
|
621
|
+
/* @__PURE__ */ jsx(CopyButton, { value: oauth2RedirectUrl })
|
|
622
|
+
] }),
|
|
623
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Add this to your OAuth app's allowed redirects." })
|
|
624
|
+
] }) }),
|
|
625
|
+
credentialScopes.length > 1 && /* @__PURE__ */ jsxs(CardStackEntry, { children: [
|
|
626
|
+
/* @__PURE__ */ jsxs(CardStackEntryContent, { children: [
|
|
627
|
+
/* @__PURE__ */ jsx(CardStackEntryTitle, { children: "OAuth token" }),
|
|
628
|
+
/* @__PURE__ */ jsx(CardStackEntryDescription, { children: "Choose where the signed-in OAuth token is saved." })
|
|
629
|
+
] }),
|
|
630
|
+
/* @__PURE__ */ jsx(
|
|
631
|
+
FilterTabs,
|
|
632
|
+
{
|
|
633
|
+
tabs: credentialScopes.map((entry) => ({
|
|
634
|
+
value: entry.scopeId,
|
|
635
|
+
label: entry.label
|
|
636
|
+
})),
|
|
637
|
+
value: activeOAuthTokenScopeId,
|
|
638
|
+
onChange: setSelectedOAuthTokenScope
|
|
639
|
+
}
|
|
640
|
+
)
|
|
641
|
+
] }),
|
|
642
|
+
/* @__PURE__ */ jsx(CardStackEntryField, { label: "OAuth Connection", children: /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
643
|
+
/* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: statusText }),
|
|
644
|
+
/* @__PURE__ */ jsx(
|
|
645
|
+
Button,
|
|
646
|
+
{
|
|
647
|
+
size: "sm",
|
|
648
|
+
onClick: () => void connectOAuth(activeOAuthTokenScopeId),
|
|
649
|
+
disabled: connectDisabled,
|
|
650
|
+
children: isConnecting ? "Connecting\u2026" : isConnected ? "Reconnect" : "Connect"
|
|
651
|
+
}
|
|
652
|
+
),
|
|
653
|
+
endpointsDirty && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Save endpoint changes before reconnecting." })
|
|
654
|
+
] }) })
|
|
655
|
+
] });
|
|
656
|
+
})()
|
|
657
|
+
] }) }),
|
|
658
|
+
error && /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: error }) }),
|
|
659
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center justify-start border-t border-border pt-4", children: /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: props.onSave, children: "Back" }) })
|
|
660
|
+
] });
|
|
661
|
+
}
|
|
662
|
+
export {
|
|
663
|
+
EditOpenApiSource as default
|
|
664
|
+
};
|
|
665
|
+
//# sourceMappingURL=EditOpenApiSource-J7NXCVT5.js.map
|