diff --git a/airbyte-webapp/src/components/connectorBuilder/Builder/AuthenticationSection.tsx b/airbyte-webapp/src/components/connectorBuilder/Builder/AuthenticationSection.tsx
new file mode 100644
index 000000000000..89a06bcba7a6
--- /dev/null
+++ b/airbyte-webapp/src/components/connectorBuilder/Builder/AuthenticationSection.tsx
@@ -0,0 +1,179 @@
+import { BuilderCard } from "./BuilderCard";
+import { BuilderField } from "./BuilderField";
+import { BuilderOneOf } from "./BuilderOneOf";
+import { BuilderOptional } from "./BuilderOptional";
+import { KeyValueListField } from "./KeyValueListField";
+import { UserInputField } from "./UserInputField";
+
+export const AuthenticationSection: React.FC = () => {
+ return (
+
+
+
+
+ >
+ ),
+ },
+ {
+ label: "Bearer",
+ typeValue: "BearerAuthenticator",
+ default: {
+ api_token: "{{ config['api_key'] }}",
+ },
+ children: (
+
+ ),
+ },
+ {
+ label: "Basic HTTP",
+ typeValue: "BasicHttpAuthenticator",
+ default: {
+ username: "{{ config['username'] }}",
+ password: "{{ config['password'] }}",
+ },
+ children: (
+ <>
+
+
+ >
+ ),
+ },
+ {
+ label: "OAuth",
+ typeValue: "OAuthAuthenticator",
+ default: {
+ client_id: "{{ config['client_id'] }}",
+ client_secret: "{{ config['client_secret'] }}",
+ refresh_token: "{{ config['client_refresh_token'] }}",
+ refresh_request_body: [],
+ token_refresh_endpoint: "",
+ },
+ children: (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ ),
+ },
+ {
+ label: "Session token",
+ typeValue: "SessionTokenAuthenticator",
+ default: {
+ username: "{{ config['username'] }}",
+ password: "{{ config['password'] }}",
+ session_token: "{{ config['session_token'] }}",
+ },
+ children: (
+ <>
+
+
+
+
+
+
+
+ >
+ ),
+ },
+ ]}
+ />
+
+ );
+};
diff --git a/airbyte-webapp/src/components/connectorBuilder/Builder/BuilderOptional.module.scss b/airbyte-webapp/src/components/connectorBuilder/Builder/BuilderOptional.module.scss
new file mode 100644
index 000000000000..b5a7816f6ae1
--- /dev/null
+++ b/airbyte-webapp/src/components/connectorBuilder/Builder/BuilderOptional.module.scss
@@ -0,0 +1,34 @@
+@use "scss/variables";
+@use "scss/colors";
+@use "scss/mixins";
+
+.wrapper {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ border-top: variables.$border-thin solid colors.$grey-100;
+ gap: variables.$spacing-lg;
+}
+
+.container {
+ padding-left: variables.$spacing-xl;
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+ align-self: stretch;
+ gap: variables.$spacing-lg;
+}
+
+.label {
+ cursor: pointer;
+ background: none;
+ border: none;
+ display: flex;
+ gap: variables.$spacing-sm;
+ margin-top: variables.$spacing-lg;
+ align-items: center;
+
+ &.closed {
+ color: colors.$grey-400;
+ }
+}
diff --git a/airbyte-webapp/src/components/connectorBuilder/Builder/BuilderOptional.tsx b/airbyte-webapp/src/components/connectorBuilder/Builder/BuilderOptional.tsx
new file mode 100644
index 000000000000..639f5358726b
--- /dev/null
+++ b/airbyte-webapp/src/components/connectorBuilder/Builder/BuilderOptional.tsx
@@ -0,0 +1,25 @@
+import { faAngleDown, faAngleRight } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import classNames from "classnames";
+import React, { useState } from "react";
+import { FormattedMessage } from "react-intl";
+
+import styles from "./BuilderOptional.module.scss";
+
+export const BuilderOptional: React.FC> = ({ children }) => {
+ const [isOpen, setIsOpen] = useState(false);
+ return (
+
+
+ {isOpen &&
{children}
}
+
+ );
+};
diff --git a/airbyte-webapp/src/components/connectorBuilder/Builder/GlobalConfigView.tsx b/airbyte-webapp/src/components/connectorBuilder/Builder/GlobalConfigView.tsx
index 97f2e75fe0e5..bc8e29a532a5 100644
--- a/airbyte-webapp/src/components/connectorBuilder/Builder/GlobalConfigView.tsx
+++ b/airbyte-webapp/src/components/connectorBuilder/Builder/GlobalConfigView.tsx
@@ -1,12 +1,11 @@
import { useIntl } from "react-intl";
+import { AuthenticationSection } from "./AuthenticationSection";
import { BuilderCard } from "./BuilderCard";
import { BuilderConfigView } from "./BuilderConfigView";
import { BuilderField } from "./BuilderField";
-import { BuilderOneOf } from "./BuilderOneOf";
import { BuilderTitle } from "./BuilderTitle";
import styles from "./GlobalConfigView.module.scss";
-import { UserInputField } from "./UserInputField";
export const GlobalConfigView: React.FC = () => {
const { formatMessage } = useIntl();
@@ -18,70 +17,7 @@ export const GlobalConfigView: React.FC = () => {
-
-
-
-
- >
- ),
- },
- {
- label: "Bearer",
- typeValue: "BearerAuthenticator",
- default: {
- api_token: "{{ config['api_key'] }}",
- },
- children: (
-
- ),
- },
- {
- label: "Basic HTTP",
- typeValue: "BasicHttpAuthenticator",
- default: {
- username: "{{ config['username'] }}",
- password: "{{ config['password'] }}",
- },
- children: (
- <>
-
-
- >
- ),
- },
- ]}
- />
-
+
);
};
diff --git a/airbyte-webapp/src/components/connectorBuilder/types.ts b/airbyte-webapp/src/components/connectorBuilder/types.ts
index c570dada2934..a76d49dec56e 100644
--- a/airbyte-webapp/src/components/connectorBuilder/types.ts
+++ b/airbyte-webapp/src/components/connectorBuilder/types.ts
@@ -4,7 +4,16 @@ import * as yup from "yup";
import { SourceDefinitionSpecificationDraft } from "core/domain/connector";
import { PatchedConnectorManifest } from "core/domain/connectorBuilder/PatchedConnectorManifest";
import { AirbyteJSONSchema } from "core/jsonSchema/types";
-import { DeclarativeStream, HttpRequesterAllOfAuthenticator } from "core/request/ConnectorManifest";
+import {
+ ApiKeyAuthenticator,
+ BasicHttpAuthenticator,
+ BearerAuthenticator,
+ DeclarativeOauth2AuthenticatorAllOf,
+ DeclarativeStream,
+ HttpRequesterAllOfAuthenticator,
+ NoAuth,
+ SessionTokenAuthenticator,
+} from "core/request/ConnectorManifest";
export interface BuilderFormInput {
key: string;
@@ -12,11 +21,22 @@ export interface BuilderFormInput {
definition: AirbyteJSONSchema;
}
+type BuilderFormAuthenticator = (
+ | NoAuth
+ | (Omit & {
+ refresh_request_body: Array<[string, string]>;
+ })
+ | ApiKeyAuthenticator
+ | BearerAuthenticator
+ | BasicHttpAuthenticator
+ | SessionTokenAuthenticator
+) & { type: string };
+
export interface BuilderFormValues {
global: {
connectorName: string;
urlBase: string;
- authenticator: HttpRequesterAllOfAuthenticator;
+ authenticator: BuilderFormAuthenticator;
};
inputs: BuilderFormInput[];
inferredInputOverrides: Record>;
@@ -82,6 +102,68 @@ function getInferredInputList(values: BuilderFormValues): BuilderFormInput[] {
},
];
}
+ if (values.global.authenticator.type === "OAuthAuthenticator") {
+ return [
+ {
+ key: "client_id",
+ required: true,
+ definition: {
+ type: "string",
+ title: "Client ID",
+ airbyte_secret: true,
+ },
+ },
+ {
+ key: "client_secret",
+ required: true,
+ definition: {
+ type: "string",
+ title: "Client secret",
+ airbyte_secret: true,
+ },
+ },
+ {
+ key: "refresh_token",
+ required: true,
+ definition: {
+ type: "string",
+ title: "Refresh token",
+ airbyte_secret: true,
+ },
+ },
+ ];
+ }
+ if (values.global.authenticator.type === "SessionTokenAuthenticator") {
+ return [
+ {
+ key: "username",
+ required: false,
+ definition: {
+ type: "string",
+ title: "Username",
+ },
+ },
+ {
+ key: "password",
+ required: false,
+ definition: {
+ type: "string",
+ title: "Password",
+ airbyte_secret: true,
+ },
+ },
+ {
+ key: "session_token",
+ required: false,
+ definition: {
+ type: "string",
+ title: "Session token",
+ description: "Session token generated by user (if provided username and password are not required)",
+ airbyte_secret: true,
+ },
+ },
+ ];
+ }
return [];
}
@@ -103,7 +185,27 @@ export const builderFormValidationSchema = yup.object().shape({
urlBase: yup.string().required("form.empty.error"),
authenticator: yup.object({
header: yup.mixed().when("type", {
- is: "ApiKeyAuthenticator",
+ is: (type: string) => type === "ApiKeyAuthenticator" || type === "SessionTokenAuthenticator",
+ then: yup.string().required("form.empty.error"),
+ otherwise: (schema) => schema.strip(),
+ }),
+ token_refresh_endpoint: yup.mixed().when("type", {
+ is: "OAuthAuthenticator",
+ then: yup.string().required("form.empty.error"),
+ otherwise: (schema) => schema.strip(),
+ }),
+ session_token_response_key: yup.mixed().when("type", {
+ is: "SessionTokenAuthenticator",
+ then: yup.string().required("form.empty.error"),
+ otherwise: (schema) => schema.strip(),
+ }),
+ login_url: yup.mixed().when("type", {
+ is: "SessionTokenAuthenticator",
+ then: yup.string().required("form.empty.error"),
+ otherwise: (schema) => schema.strip(),
+ }),
+ validate_session_url: yup.mixed().when("type", {
+ is: "SessionTokenAuthenticator",
then: yup.string().required("form.empty.error"),
otherwise: (schema) => schema.strip(),
}),
@@ -124,6 +226,24 @@ export const builderFormValidationSchema = yup.object().shape({
),
});
+function builderFormAuthenticatorToAuthenticator(
+ globalSettings: BuilderFormValues["global"]
+): HttpRequesterAllOfAuthenticator {
+ if (globalSettings.authenticator.type === "OAuthAuthenticator") {
+ return {
+ ...globalSettings.authenticator,
+ refresh_request_body: Object.fromEntries(globalSettings.authenticator.refresh_request_body),
+ };
+ }
+ if (globalSettings.authenticator.type === "SessionTokenAuthenticator") {
+ return {
+ ...globalSettings.authenticator,
+ api_url: globalSettings.urlBase,
+ };
+ }
+ return globalSettings.authenticator as HttpRequesterAllOfAuthenticator;
+}
+
export const convertToManifest = (values: BuilderFormValues): PatchedConnectorManifest => {
const manifestStreams: DeclarativeStream[] = values.streams.map((stream) => {
return {
@@ -139,7 +259,7 @@ export const convertToManifest = (values: BuilderFormValues): PatchedConnectorMa
request_headers: Object.fromEntries(stream.requestOptions.requestHeaders),
request_body_data: Object.fromEntries(stream.requestOptions.requestBody),
},
- authenticator: values.global.authenticator,
+ authenticator: builderFormAuthenticatorToAuthenticator(values.global),
// TODO: remove these empty "config" values once they are no longer required in the connector manifest JSON schema
config: {},
},
diff --git a/airbyte-webapp/src/locales/en.json b/airbyte-webapp/src/locales/en.json
index d7a1bd39b008..c56f066425df 100644
--- a/airbyte-webapp/src/locales/en.json
+++ b/airbyte-webapp/src/locales/en.json
@@ -717,6 +717,7 @@
"connectorBuilder.inputsNoSpecUITooltip": "Add User Input fields to allow setting test inputs",
"connectorBuilder.inputsNoSpecYAMLTooltip": "Add a spec to your manifest to allow setting test inputs",
"connectorBuilder.setInUserInput": "This setting is configured as part of the user inputs in the testing panel",
+ "connectorBuilder.optionalFieldsLabel": "Optional fields",
"connectorBuilder.duplicateFieldID": "Make sure no field ID is used multiple times",
"jobs.noAttemptsFailure": "Failed to start job.",