Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🪟🎉 Connector builder authentication #20645

Merged
Original file line number Diff line number Diff line change
@@ -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 (
<BuilderCard>
<BuilderOneOf
path="global.authenticator"
label="Authentication"
tooltip="Authentication method to use for requests sent to the API"
options={[
{ label: "No Auth", typeValue: "NoAuth" },
{
label: "API Key",
typeValue: "ApiKeyAuthenticator",
default: {
api_token: "{{ config['api_key'] }}",
header: "",
},
children: (
<>
<BuilderField
type="string"
path="global.authenticator.header"
label="Header"
tooltip="HTTP header which should be set to the API Key"
/>
<UserInputField
label="API Key"
tooltip="The API key issued by the service. Fill it in in the user inputs"
/>
</>
),
},
{
label: "Bearer",
typeValue: "BearerAuthenticator",
default: {
api_token: "{{ config['api_key'] }}",
},
children: (
<UserInputField
label="API Key"
tooltip="The API key issued by the service. Fill it in in the user inputs"
/>
),
},
{
label: "Basic HTTP",
typeValue: "BasicHttpAuthenticator",
default: {
username: "{{ config['username'] }}",
password: "{{ config['password'] }}",
},
children: (
<>
<UserInputField label="Username" tooltip="The username for the login. Fill it in in the user inputs" />
<UserInputField label="Password" tooltip="The password for the login. Fill it in in the user inputs" />
</>
),
},
{
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: (
<>
<BuilderField
type="string"
path="global.authenticator.token_refresh_endpoint"
label="Token refresh endpoint"
tooltip="The URL to call to obtain a new access token"
/>
<UserInputField label="Client ID" tooltip="The OAuth client ID" />
<UserInputField label="Client secret" tooltip="The OAuth client secret" />
<UserInputField label="Refresh token" tooltip="The OAuth refresh token" />
<BuilderOptional>
<BuilderField
type="array"
path="global.authenticator.scopes"
optional
label="Scopes"
tooltip="Scopes to request"
/>
<BuilderField
type="array"
path="global.authenticator.token_expiry_date_format"
optional
label="Token expiry date format"
tooltip="The format of the expiry date of the access token as obtained from the refresh endpoint"
/>
<BuilderField
type="string"
path="global.authenticator.expires_in_name"
optional
label="Token expiry property name"
tooltip="The name of the property which contains the token exipiry date in the response from the token refresh endpoint"
/>
<BuilderField
type="string"
path="global.authenticator.access_token_name"
optional
label="Access token property name"
tooltip="The name of the property which contains the access token in the response from the token refresh endpoint"
/>
<BuilderField
type="string"
path="global.authenticator.grant_type"
optional
label="Grant type"
tooltip="The grant type to request for access_token"
/>
<KeyValueListField
path="global.authenticator.refresh_request_body"
label="Request Parameters"
tooltip="The request body to send in the refresh request"
/>
</BuilderOptional>
</>
),
},
{
label: "Session token",
typeValue: "SessionTokenAuthenticator",
default: {
username: "{{ config['username'] }}",
password: "{{ config['password'] }}",
session_token: "{{ config['session_token'] }}",
},
children: (
<>
<BuilderField
type="string"
path="global.authenticator.header"
label="Header"
tooltip="Specific HTTP header of source API for providing session token"
/>
<BuilderField
type="string"
path="global.authenticator.session_token_response_key"
label="Session token response key"
tooltip="Key for retrieving session token from api response"
/>
<BuilderField
type="string"
path="global.authenticator.login_url"
label="Login url"
tooltip="Url for getting a specific session token"
/>
<BuilderField
type="string"
path="global.authenticator.validate_session_url"
label="Validate session url"
tooltip="Url to validate passed session token"
/>
<UserInputField label="Username" tooltip="The username" />
<UserInputField label="Password" tooltip="The password" />
<UserInputField
label="Session token"
tooltip="Session token generated by user (if provided username and password are not required)"
/>
</>
),
},
]}
/>
</BuilderCard>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useEffect } from "react";

import { BuilderView, useConnectorBuilderState } from "services/connectorBuilder/ConnectorBuilderStateService";

import { BuilderFormValues } from "../types";
import { builderFormValidationSchema, BuilderFormValues } from "../types";
import styles from "./Builder.module.scss";
import { BuilderSidebar } from "./BuilderSidebar";
import { GlobalConfigView } from "./GlobalConfigView";
Expand All @@ -29,7 +29,7 @@ function getView(selectedView: BuilderView) {
export const Builder: React.FC<BuilderProps> = ({ values, toggleYamlEditor }) => {
const { setBuilderFormValues, selectedView } = useConnectorBuilderState();
useEffect(() => {
setBuilderFormValues(values);
setBuilderFormValues(values, builderFormValidationSchema.isValidSync(values));
}, [values, setBuilderFormValues]);

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { useField } from "formik";
import React from "react";

import GroupControls from "components/GroupControls";
import { ControlLabels } from "components/LabeledControl";
import { DropDown } from "components/ui/DropDown";

interface Option {
label: string;
value: string;
default?: object;
}

interface OneOfOption {
label: string; // label shown in the dropdown menu
typeValue: string; // value to set on the `type` field for this component - should match the oneOf type definition
default?: object; // default values for the path
children?: React.ReactNode;
}

interface BuilderOneOfProps {
options: OneOfOption[];
path: string; // path to the oneOf component in the json schema
label: string;
tooltip: string;
}

export const BuilderOneOf: React.FC<BuilderOneOfProps> = ({ options, path, label, tooltip }) => {
const [, , oneOfPathHelpers] = useField(path);
const typePath = `${path}.type`;
const [typePathField] = useField(typePath);
const value = typePathField.value;

const selectedOption = options.find((option) => option.typeValue === value);

return (
<GroupControls
label={<ControlLabels label={label} infoTooltipContent={tooltip} />}
dropdown={
<DropDown
{...typePathField}
options={options.map((option) => {
return { label: option.label, value: option.typeValue, default: option.default };
})}
value={value ?? options[0].typeValue}
onChange={(selectedOption: Option) => {
if (selectedOption.value === value) {
return;
}
// clear all values for this oneOf and set selected option and default values
oneOfPathHelpers.setValue({
type: selectedOption.value,
...selectedOption.default,
});
}}
/>
}
>
{selectedOption?.children}
</GroupControls>
);
};
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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<React.PropsWithChildren<unknown>> = ({ children }) => {
const [isOpen, setIsOpen] = useState(false);
return (
<div className={styles.wrapper}>
<button
onClick={() => {
setIsOpen(!isOpen);
}}
className={classNames(styles.label, { [styles.closed]: !isOpen })}
>
{isOpen ? <FontAwesomeIcon icon={faAngleDown} /> : <FontAwesomeIcon icon={faAngleRight} />}
<FormattedMessage id="connectorBuilder.optionalFieldsLabel" />
</button>
{isOpen && <div className={styles.container}>{children}</div>}
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
} from "services/connectorBuilder/ConnectorBuilderStateService";

import { DownloadYamlButton } from "../DownloadYamlButton";
import { BuilderFormValues } from "../types";
import { BuilderFormValues, getInferredInputs } from "../types";
import { useBuilderErrors } from "../useBuilderErrors";
import { AddStreamButton } from "./AddStreamButton";
import styles from "./BuilderSidebar.module.scss";
Expand Down Expand Up @@ -115,7 +115,10 @@ export const BuilderSidebar: React.FC<BuilderSidebarProps> = ({ className, toggl
onClick={() => handleViewSelect("inputs")}
>
<FontAwesomeIcon icon={faUser} />
<FormattedMessage id="connectorBuilder.userInputs" values={{ number: values.inputs.length }} />
<FormattedMessage
id="connectorBuilder.userInputs"
values={{ number: values.inputs.length + getInferredInputs(values).length }}
/>
</ViewSelectButton>

<div className={styles.streamsHeader}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useIntl } from "react-intl";

import { AuthenticationSection } from "./AuthenticationSection";
import { BuilderCard } from "./BuilderCard";
import { BuilderConfigView } from "./BuilderConfigView";
import { BuilderField } from "./BuilderField";
Expand All @@ -16,6 +17,7 @@ export const GlobalConfigView: React.FC = () => {
<BuilderCard className={styles.content}>
<BuilderField type="string" path="global.urlBase" label="API URL" tooltip="Base URL of the source API" />
</BuilderCard>
<AuthenticationSection />
</BuilderConfigView>
);
};
Loading