diff --git a/.github/workflows/backend_checks.yml b/.github/workflows/backend_checks.yml
index 34a499a2bcf..cbc29e709d1 100644
--- a/.github/workflows/backend_checks.yml
+++ b/.github/workflows/backend_checks.yml
@@ -397,8 +397,7 @@ jobs:
DYNAMODB_ACCESS_KEY_ID: op://github-actions/dynamodb/DYNAMODB_ACCESS_KEY_ID
DYNAMODB_ACCESS_KEY: op://github-actions/dynamodb/DYNAMODB_ACCESS_KEY
DYNAMODB_REGION: op://github-actions/dynamodb/DYNAMODB_REGION
- OKTA_CLIENT_ID: op://github-actions/okta/OKTA_CLIENT_ID
- OKTA_PRIVATE_KEY: op://github-actions/okta/OKTA_PRIVATE_KEY
+ OKTA_CLIENT_TOKEN: op://github-actions/ctl/OKTA_CLIENT_TOKEN
REDSHIFT_FIDESCTL_PASSWORD: op://github-actions/ctl/REDSHIFT_FIDESCTL_PASSWORD
SNOWFLAKE_FIDESCTL_PASSWORD: op://github-actions/ctl/SNOWFLAKE_FIDESCTL_PASSWORD
@@ -470,9 +469,8 @@ jobs:
GOOGLE_CLOUD_SQL_POSTGRES_DB_IAM_USER: op://github-actions/gcp-postgres/GOOGLE_CLOUD_SQL_POSTGRES_DB_IAM_USER
GOOGLE_CLOUD_SQL_POSTGRES_INSTANCE_CONNECTION_NAME: op://github-actions/gcp-postgres/GOOGLE_CLOUD_SQL_POSTGRES_INSTANCE_CONNECTION_NAME
GOOGLE_CLOUD_SQL_POSTGRES_KEYFILE_CREDS: op://github-actions/gcp-postgres/GOOGLE_CLOUD_SQL_POSTGRES_KEYFILE_CREDS
- OKTA_CLIENT_ID: op://github-actions/okta/OKTA_CLIENT_ID
+ OKTA_API_TOKEN: op://github-actions/okta/OKTA_API_TOKEN
OKTA_ORG_URL: op://github-actions/okta/OKTA_ORG_URL
- OKTA_PRIVATE_KEY: op://github-actions/okta/OKTA_PRIVATE_KEY
RDS_MYSQL_AWS_ACCESS_KEY_ID: op://github-actions/rds-mysql/RDS_MYSQL_AWS_ACCESS_KEY_ID
RDS_MYSQL_AWS_SECRET_ACCESS_KEY: op://github-actions/rds-mysql/RDS_MYSQL_AWS_SECRET_ACCESS_KEY
RDS_MYSQL_DB_INSTANCE: op://github-actions/rds-mysql/RDS_MYSQL_DB_INSTANCE
diff --git a/clients/admin-ui/cypress/e2e/config-wizard.cy.ts b/clients/admin-ui/cypress/e2e/config-wizard.cy.ts
index 56955c372d6..7ed30c0fab3 100644
--- a/clients/admin-ui/cypress/e2e/config-wizard.cy.ts
+++ b/clients/admin-ui/cypress/e2e/config-wizard.cy.ts
@@ -108,12 +108,8 @@ describe("Config Wizard", () => {
cy.getByTestId("okta-btn").click();
// Fill form
cy.getByTestId("authenticate-okta-form");
- cy.getByTestId("input-orgUrl").type("https://dev-12345.okta.com");
- cy.getByTestId("input-clientId").type("0oa1abc2def3ghi4jkl5");
- cy.getByTestId("input-privateKey").type(
- '{"kty":"RSA","kid":"test","n":"test","e":"AQAB","d":"test"}',
- { parseSpecialCharSequences: false },
- );
+ cy.getByTestId("input-orgUrl").type("https://ethyca.com/");
+ cy.getByTestId("input-token").type("fakeToken");
});
it("Allows submitting the form and reviewing the results", () => {
diff --git a/clients/admin-ui/src/features/common/form/FormFieldFromSchema.tsx b/clients/admin-ui/src/features/common/form/FormFieldFromSchema.tsx
index 6b442fcb795..70f62b01c5d 100644
--- a/clients/admin-ui/src/features/common/form/FormFieldFromSchema.tsx
+++ b/clients/admin-ui/src/features/common/form/FormFieldFromSchema.tsx
@@ -7,7 +7,7 @@ import {
} from "~/features/connection-type/types";
import { ControlledSelect } from "./ControlledSelect";
-import { CustomTextArea, CustomTextInput } from "./inputs";
+import { CustomTextInput } from "./inputs";
export type FormFieldProps = {
name: string;
@@ -91,23 +91,6 @@ export const FormFieldFromSchema = ({
);
}
- if (fieldSchema.multiline) {
- return (
-
- );
- }
-
return (
{
- if (!value) {
- return true;
- }
- try {
- JSON.parse(value);
- return true;
- } catch {
- return false;
- }
- },
+ "is-valid-key",
+ "Private key must be in PEM format (starts with -----BEGIN RSA PRIVATE KEY-----)",
+ (value) => !value || value.includes("-----BEGIN"),
)
.label("Private Key"),
scopes: Yup.string()
.required()
.trim()
.label("Scopes")
- .default("okta.apps.read")
- .test(
- "valid-scopes",
- "Scopes must be a single scope or comma-separated list (e.g., 'okta.apps.read' or 'okta.apps.read, okta.users.read')",
- (value) => {
- if (!value) {
- return true;
- }
- // Split on comma and check each scope is non-empty and has no internal whitespace
- const scopes = value.split(",").map((s) => s.trim());
- return scopes.every((scope) => scope.length > 0 && !/\s/.test(scope));
- },
- ),
+ .default("okta.apps.read"),
+});
+
+const TokenValidationSchema = Yup.object().shape({
+ orgUrl: Yup.string().required().trim().url().label("Organization URL"),
+ token: Yup.string()
+ .required()
+ .trim()
+ .matches(/^[^\s]+$/, "Cannot contain spaces")
+ .label("Token"),
});
const AuthenticateOktaForm = () => {
const organizationKey = useAppSelector(selectOrganizationFidesKey);
const dispatch = useAppDispatch();
const { successAlert } = useAlert();
+ const { flags } = useFlags();
const [scannerError, setScannerError] = useState();
+ const useOAuth2 = flags.oktaMonitor;
+
const handleResults = (results: GenerateResponse["generate_results"]) => {
const systems: System[] = (results ?? []).filter(isSystem);
dispatch(setSystemsForReview(systems));
@@ -124,7 +126,7 @@ const AuthenticateOktaForm = () => {
const [generate, { isLoading }] = useGenerateMutation();
- const handleSubmit = async (values: FormValues) => {
+ const handleOAuth2Submit = async (values: OAuth2FormValues) => {
setScannerError(undefined);
const config: OktaOAuth2Config = {
@@ -148,11 +150,130 @@ const AuthenticateOktaForm = () => {
}
};
+ const handleTokenSubmit = async (values: TokenFormValues) => {
+ setScannerError(undefined);
+
+ const config: OktaTokenConfig = {
+ orgUrl: values.orgUrl,
+ token: values.token,
+ };
+
+ const result = await generate({
+ organization_key: organizationKey,
+ generate: {
+ config: config as OktaConfig,
+ target: ValidTargets.OKTA,
+ type: GenerateTypes.SYSTEMS,
+ },
+ });
+
+ if (isErrorResult(result)) {
+ handleError(result.error);
+ } else {
+ handleResults(result.data.generate_results);
+ }
+ };
+
+ if (useOAuth2) {
+ return (
+
+ {({ isValid, isSubmitting, dirty }) => (
+
+ )}
+
+ );
+ }
+
return (
{({ isValid, isSubmitting, dirty }) => (