Skip to content

Commit

Permalink
Add Salesforce SAML & OIDC Providers (#203)
Browse files Browse the repository at this point in the history
* Add Salesforce SAML & OIDC Providers

* Cleanup OIDC Flow

* Fix Alt Text Numbering

---------

Co-authored-by: Garth <[email protected]>
  • Loading branch information
cdgco and xgp authored Jan 14, 2025
1 parent 7122fdf commit 6c41d53
Show file tree
Hide file tree
Showing 36 changed files with 1,067 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import React from "react";
import SalesforceOidcStep0Image from "@app/images/salesforce/OIDC/salesforce_oidc_0.png";
import SalesforceOidcStep1Image from "@app/images/salesforce/OIDC/salesforce_oidc_1.png";
import {
InstructionProps,
Step,
ClipboardCopyComponent,
StepImage,
} from "@wizardComponents";

type Props = {
loginRedirectURL: string;
};

export const SalesforceStepTwo: React.FC<Props> = ({ loginRedirectURL }) => {
const instructions: InstructionProps[] = [
{
text: (
<div>
Under the <b>API (Enable OAuth Settings)</b> section, check the <b>Enable OAuth Settings</b>{" "}
checkbox and paste the URL from below into the <b>Callback URL</b> field.
</div>
),
component: <StepImage src={SalesforceOidcStep0Image} alt="Step 2.1" />,
},
{
component: (
<ClipboardCopyComponent
label="Copy this login Callback URL"
initialValue={loginRedirectURL}
/>
),
},
{
text: (
<div>
Select the <b>Access the identity URL service (id, profile, email, address, phone)</b> and{" "}
<b>Access unique user identifiers (openid)</b> OAuth Scopes, then click the <b>Add</b> button.{" "}
<br/><br/>
Uncheck the <b>Require Proof Key for Code Exchange (PKCE) Extension for Supported Authorization Flows</b> checkbox.
</div>
),
component: <StepImage src={SalesforceOidcStep1Image} alt="Step 2.2" />,
},
{
component: (
<div>
Click the <b>Save</b> button at the bottom of the page to save your changes.
</div>
),
}
];

return (
<Step
title="Step 2: Configure OAuth Settings"
instructionList={instructions}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Card, CardBody } from "@patternfly/react-core";
import React, { FC } from "react";
import SalesforceOidcStep2Image from "@app/images/salesforce/oidc/salesforce_oidc_2.png";
import SalesforceOidcStep3Image from "@app/images/salesforce/oidc/salesforce_oidc_3.png";
import SalesforceOidcStep4Image from "@app/images/salesforce/oidc/salesforce_oidc_4.png";
import { InstructionProps, Step, StepImage } from "@wizardComponents";
import { API_RETURN_PROMISE } from "@app/configurations/api-status";
import { ClientCredentials } from "../forms";

interface Props {
onFormSubmission: ({
domain,
clientId,
clientSecret,
}: {
domain: string;
clientId: string;
clientSecret: string;
}) => API_RETURN_PROMISE;
values: {
domain: string;
clientId: string;
clientSecret: string;
};
}

export const SalesforceStepThree: FC<Props> = ({ onFormSubmission, values }) => {
const instructions: InstructionProps[] = [
{
text: (
<div>
On the next page, press the <b>Manage Consumer Details</b> button under the <b>API (Enable OAuth Settings)</b> section to view your app's credentials.
</div>
),
component: <StepImage src={SalesforceOidcStep2Image} alt="Step 3.1" />,
},
{
text: (
<div>
Copy the <b>Consumer Key</b> and <b>Consumer Secret</b> and paste them into the fields below.
</div>
),
component: <StepImage src={SalesforceOidcStep3Image} alt="Step 3.2" />,
},
{
text: (
<div>
In the sidebar menu, click <b>My Domain</b> under <b>Company Settings</b> to view your domain.{" "}
Copy the domain listed under <b>Current My Domain URL</b> and paste it into the domain field below.
</div>
),
component: <StepImage src={SalesforceOidcStep4Image} alt="Step 3.3" />,
},
{
component: (
<Card className="card-shadow">
<CardBody>
<ClientCredentials
credentials={values}
handleFormSubmit={onFormSubmission}
/>
</CardBody>
</Card>
),
},
];

return (
<Step
title="Step 3: Provide Credentials and Domain"
instructionList={instructions}
/>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./2";
export * from "./3";
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import React, { FC, useState } from "react";
import { useFormik } from "formik";
import {
ActionGroup,
Alert,
Button,
Form,
FormAlert,
FormGroup,
TextInput,
} from "@patternfly/react-core";
import * as Yup from "yup";
import {
API_RETURN,
API_RETURN_PROMISE,
API_STATUS,
} from "@app/configurations/api-status";

export type ClientCreds = {
domain: string;
clientId: string;
clientSecret: string;
};

const ClientCredentialsSchema = Yup.object().shape({
clientId: Yup.string().required("Consumer Key is a required field."),
clientSecret: Yup.string().required("Consumer Secret is a required field."),
domain: Yup.string().required("Domain is a required field."),
});

type Props = {
handleFormSubmit: ({
domain,
clientId,
clientSecret,
}: ClientCreds) => API_RETURN_PROMISE;
formActive?: boolean;
credentials: ClientCreds;
};

export const ClientCredentials: FC<Props> = ({
handleFormSubmit,
formActive = true,
credentials,
}) => {
const [submissionResp, setSubmissionResp] = useState<API_RETURN | null>();
const {
handleSubmit,
handleChange,
values,
errors,
touched,
isSubmitting,
setSubmitting,
} = useFormik({
initialValues: {
domain: credentials.domain,
clientId: credentials.clientId,
clientSecret: credentials.clientSecret,
},
onSubmit: async (values) => {
const resp = await handleFormSubmit(values);
setSubmissionResp(resp);
setSubmitting(false);
},
validationSchema: ClientCredentialsSchema,
});

const hasError = (key: string) =>
errors[key] && touched[key] ? "error" : "default";

return (
<Form onSubmit={handleSubmit}>
{submissionResp && (
<FormAlert>
<Alert
variant={
submissionResp.status === API_STATUS.SUCCESS
? "success"
: "danger"
}
title={submissionResp.message}
aria-live="polite"
isInline
/>
</FormAlert>
)}
<FormGroup
label="Consumer Key"
isRequired
fieldId="clientId"
validated={hasError("clientId")}
helperTextInvalid={errors.clientId}
>
<TextInput
isRequired
id="clientId"
name="clientId"
value={values.clientId}
onChange={(val, e) => handleChange(e)}
validated={hasError("clientId")}
isDisabled={!formActive}
/>
</FormGroup>
<FormGroup
label="Consumer Secret"
isRequired
fieldId="clientSecret"
validated={hasError("clientSecret")}
helperTextInvalid={errors.clientSecret}
>
<TextInput
isRequired
id="clientSecret"
name="clientSecret"
value={values.clientSecret}
onChange={(val, e) => handleChange(e)}
validated={hasError("clientSecret")}
isDisabled={!formActive}
type="password"
/>
</FormGroup>
<FormGroup
label="Domain"
isRequired
fieldId="domain"
validated={hasError("domain")}
helperTextInvalid={errors.domain}
>
<TextInput
isRequired
id="domain"
name="domain"
value={values.domain}
onChange={(val, e) => handleChange(e)}
validated={hasError("domain")}
isDisabled={!formActive}
placeholder="mydomain.my.salesforce.com"
/>
</FormGroup>
<ActionGroup style={{ marginTop: 0 }}>
<Button
type="submit"
isDisabled={isSubmitting || !formActive}
isLoading={isSubmitting}
>
Validate Credentials
</Button>
</ActionGroup>
</Form>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./client-credentials";
Loading

0 comments on commit 6c41d53

Please sign in to comment.