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: Session token and oauth authentication #20712

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
@@ -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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It isn't a huge deal, but when this is clicked it clears out any errors that are shown on the required fields.

To reproduce: select OAuth, click into Token refresh endpoint, click away (Required error is shown), click on Optional fields. Happens both when closing and when opening.

Ideally, the errors shown on the required fields would not go away just because optional fields are opened or closed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Formik is starting to annoy me... I played around a bit and noticed that if you type something into the field, then delete it again it will not remove the error on opening the collapsed section. So I just preset it with an empty string and now it works.

}}
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
@@ -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();
Expand All @@ -18,70 +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>
<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'] }}",
},
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"
/>
</>
),
},
]}
/>
</BuilderCard>
<AuthenticationSection />
</BuilderConfigView>
);
};
Loading