diff --git a/src/api/tenants/index.ts b/src/api/tenants/index.ts index e967f62d..d464228f 100644 --- a/src/api/tenants/index.ts +++ b/src/api/tenants/index.ts @@ -34,28 +34,6 @@ export const useTenantCreateService = () => { } | undefined > => { - // TODO: Temporary mock data - // await new Promise((resolve) => setTimeout(resolve, 1000)); - - // const status = localStorage.getItem("create-tenant-status"); - // if (status === "MULTITENANCY_NOT_ENABLED_IN_CORE_ERROR") { - // return { - // status: "MULTITENANCY_NOT_ENABLED_IN_CORE_ERROR", - // }; - // } - - // if (status === "INVALID_TENANT_ID_ERROR") { - // return { - // status: "INVALID_TENANT_ID_ERROR", - // message: "tenant id is invalid", - // }; - // } - - // return { - // status: "OK", - // createdNew: true, - // }; - const response = await fetchData({ url: getApiUrl("/api/tenant"), method: "PUT", @@ -92,79 +70,6 @@ export const useTenantGetService = () => { } | undefined > => { - // TODO: Temporary mock data - // await new Promise((resolve) => setTimeout(resolve, 10)); - - // const status = localStorage.getItem("get-tenant-status"); - // if (status === "UNKNOWN_TENANT_ERROR") { - // return { - // status: "UNKNOWN_TENANT_ERROR", - // }; - // } - - // return { - // status: "OK", - // tenant: { - // tenantId, - // thirdParty: { - // providers: ["apple", "google"], - // }, - // firstFactors: ["thirdparty"], - // requiredSecondaryFactors: [], - // userCount: 12, - // coreConfig: [ - // { - // key: "password_reset_token_lifetime", - // valueType: "number", - // value: 3600000, - // description: "The time in milliseconds for which the password reset token is valid.", - // isSaaSProtected: false, - // isDifferentAcrossTenants: true, - // isModifyableOnlyViaConfigYaml: false, - // defaultValue: 3600000, - // isNullable: false, - // isPluginProperty: false, - // }, - // { - // key: "access_token_blacklisting", - // valueType: "boolean", - // value: false, - // description: "Whether to blacklist access tokens or not.", - // isSaaSProtected: false, - // isDifferentAcrossTenants: true, - // isModifyableOnlyViaConfigYaml: false, - // defaultValue: false, - // isNullable: false, - // isPluginProperty: false, - // }, - // { - // key: "ip_allow_regex", - // valueType: "string", - // value: null, - // description: "The regex to match the IP address of the user.", - // isSaaSProtected: false, - // isDifferentAcrossTenants: true, - // isModifyableOnlyViaConfigYaml: false, - // defaultValue: null, - // isNullable: true, - // isPluginProperty: false, - // }, - // { - // key: "postgresql_emailpassword_users_table_name", - // valueType: "string", - // value: null, - // description: "The name of the table where the emailpassword users are stored.", - // isSaaSProtected: false, - // isDifferentAcrossTenants: true, - // isModifyableOnlyViaConfigYaml: false, - // defaultValue: 3600000, - // isNullable: true, - // isPluginProperty: true, - // }, - // ], - // }, - // }; - const response = await fetchData({ url: getApiUrl("/api/tenant", tenantId), method: "GET", @@ -190,13 +95,6 @@ export const useTenantDeleteService = () => { method: "DELETE", }); - // TODO: Temporary mock data - // await new Promise((resolve) => setTimeout(resolve, 1000)); - - // return { - // status: "OK", - // }; - if (response.ok) { return await response.json(); } @@ -219,27 +117,6 @@ export const useUpdateFirstFactorsService = () => { | { status: "RECIPE_NOT_CONFIGURED_ON_BACKEND_SDK_ERROR"; message: string } | { status: "UNKNOWN_TENANT_ERROR" } > => { - // TODO: Temporary mock data - // await new Promise((resolve) => setTimeout(resolve, 1000)); - - // const status = localStorage.getItem("update-first-factors-status"); - // if (status === "RECIPE_NOT_CONFIGURED_ON_BACKEND_SDK_ERROR") { - // return { - // status: "RECIPE_NOT_CONFIGURED_ON_BACKEND_SDK_ERROR", - // message: "Recipe not configured", - // }; - // } - - // if (status === "UNKNOWN_TENANT_ERROR") { - // return { - // status: "UNKNOWN_TENANT_ERROR", - // }; - // } - - // return { - // status: "OK", - // }; - const response = await fetchData({ url: getApiUrl("/api/tenant/first-factor", tenantId), method: "PUT", @@ -274,37 +151,6 @@ export const useUpdateSecondaryFactorsService = () => { | { status: "MFA_NOT_INITIALIZED_ERROR" } | { status: "UNKNOWN_TENANT_ERROR" } > => { - // TODO: Temporary mock data - // await new Promise((resolve) => setTimeout(resolve, 1000)); - - // const status = localStorage.getItem("update-secondary-factors-status"); - // if (status === "RECIPE_NOT_CONFIGURED_ON_BACKEND_SDK_ERROR") { - // return { - // status: "RECIPE_NOT_CONFIGURED_ON_BACKEND_SDK_ERROR", - // message: "Recipe not configured", - // }; - // } - - // if (status === "MFA_NOT_INITIALIZED_ERROR") { - // return { - // status: "MFA_NOT_INITIALIZED_ERROR", - // }; - // } - - // if (status === "UNKNOWN_TENANT_ERROR") { - // return { - // status: "UNKNOWN_TENANT_ERROR", - // }; - // } - - // const isMFARequirementsForAuthOverridden = - // localStorage.getItem("isMFARequirementsForAuthOverridden") === "true"; - - // return { - // status: "OK", - // isMFARequirementsForAuthOverridden: isMFARequirementsForAuthOverridden, - // }; - const response = await fetchData({ url: getApiUrl("/api/tenant/secondary-factor", tenantId), method: "PUT", @@ -336,14 +182,6 @@ export const useUpdateCoreConfigService = () => { ): Promise< { status: "OK" } | { status: "UNKNOWN_TENANT_ERROR" } | { status: "INVALID_CONFIG_ERROR"; message: string } > => { - // TODO: Temporary mock data - // await new Promise((resolve) => setTimeout(resolve, 1000)); - - // return { - // status: "INVALID_CONFIG_ERROR", - // message: "Invalid config", - // }; - const response = await fetchData({ url: getApiUrl("/api/tenant/core-config", tenantId), method: "PUT", @@ -381,30 +219,6 @@ export const useGetThirdPartyProviderInfo = () => { status: "UNKNOWN_TENANT_ERROR"; } > => { - // TODO: Temporary mock data - // await new Promise((resolve) => setTimeout(resolve, 1000)); - - // return { - // status: "OK", - // providerConfig: { - // oidcDiscoveryEndpoint: "https://oidc.discovery.endpoint", - // thirdPartyId: providerId, - // name: "Provider Name", - // clients: [ - // { - // clientId: "", - // clientSecret: "", - // scope: [""], - // forcePKCE: false, - // additionalConfig, - // }, - // ], - // isGetAuthorisationRedirectUrlOverridden: false, - // isExchangeAuthCodeForOAuthTokensOverridden: false, - // isGetUserInfoOverridden: false, - // }, - // }; - const additionalConfigQueryParams = new URLSearchParams(additionalConfig).toString(); const response = await fetchData({ @@ -434,13 +248,6 @@ export const useCreateOrUpdateThirdPartyProvider = () => { tenantId: string, providerConfig: ProviderConfig ): Promise<{ status: "OK" } | { status: "UNKNOWN_TENANT_ERROR" }> => { - // TODO: Temporary mock data - // await new Promise((resolve) => setTimeout(resolve, 1000)); - - // return { - // status: "OK", - // }; - const response = await fetchData({ url: getApiUrl("/api/thirdparty/config", tenantId), method: "PUT", @@ -469,13 +276,6 @@ export const useDeleteThirdPartyProvider = () => { tenantId: string, providerId: string ): Promise<{ status: "OK" } | { status: "UNKNOWN_TENANT_ERROR" }> => { - // TODO: Temporary mock data - // await new Promise((resolve) => setTimeout(resolve, 1000)); - - // return { - // status: "OK", - // }; - const response = await fetchData({ url: getApiUrl(`/api/thirdparty/config?thirdPartyId=${providerId}`, tenantId), method: "DELETE", diff --git a/src/constants.ts b/src/constants.ts index 168b5744..9619c7da 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -112,37 +112,31 @@ export const FIRST_FACTOR_IDS = [ label: "Email Password", description: "Sign in/up using email and password (Requires the EmailPassword recipe to be initialized)", id: "emailpassword", - loginMethod: "emailpassword", }, { label: "OTP - Email", description: "Sign in/up using OTP sent to email (Requires the Passwordless recipe to be initialized)", id: "otp-email", - loginMethod: "passwordless", }, { label: "OTP - Phone", description: "Sign in/up using OTP sent to phone (Requires the Passwordless recipe to be initialized)", id: "otp-phone", - loginMethod: "passwordless", }, { label: "Link - Email", description: "Sign in/up using link sent to email (Requires the Passwordless recipe to be initialized)", id: "link-email", - loginMethod: "passwordless", }, { label: "Link - Phone", description: "Sign in/up using link sent to phone (Requires the Passwordless recipe to be initialized)", id: "link-phone", - loginMethod: "passwordless", }, { label: "Third Party", description: "Sign in/up using third party providers (Requires the ThirdParty recipe to be initialized)", id: "thirdparty", - loginMethod: "thirdparty", }, ]; @@ -158,14 +152,12 @@ export const SECONDARY_FACTOR_IDS = [ description: "Require OTP sent to email as a secondary factor for successful authentication (Requires the Passwordless recipe to be initialized)", id: "otp-email", - loginMethod: "passwordless", }, { label: "OTP - Phone", description: "Require OTP sent to phone as a secondary factor for successful authentication (Requires the Passwordless recipe to be initialized)", id: "otp-phone", - loginMethod: "passwordless", }, ]; export const FactorIds = { diff --git a/src/ui/components/tenants/tenantDetail/CoreConfigSection.tsx b/src/ui/components/tenants/tenantDetail/CoreConfigSection.tsx index 071d08c4..f6a90fcc 100644 --- a/src/ui/components/tenants/tenantDetail/CoreConfigSection.tsx +++ b/src/ui/components/tenants/tenantDetail/CoreConfigSection.tsx @@ -18,7 +18,7 @@ import { ReactComponent as PencilIcon } from "../../../../assets/edit.svg"; import { ReactComponent as InfoIcon } from "../../../../assets/info-icon.svg"; import { ReactComponent as QuestionMarkIcon } from "../../../../assets/question-mark.svg"; import { PUBLIC_TENANT_ID } from "../../../../constants"; -import { getImageUrl } from "../../../../utils"; +import { getConnectionUri, getImageUrl } from "../../../../utils"; import { PopupContentContext } from "../../../contexts/PopupContentContext"; import Button from "../../button"; import { Checkbox } from "../../checkbox/Checkbox"; @@ -148,7 +148,7 @@ type CoreConfigTableRowProps = { }; const isUsingSaaS = localStorage.getItem("isUsingSaaS") === "true"; -const isUsingNonPublicApp = localStorage.getItem("isUsingNonPublicApp") === "true"; +const isUsingNonPublicApp = /appid-.*$/.test(getConnectionUri()); const CoreConfigTableRow = ({ name, @@ -278,7 +278,7 @@ const CoreConfigTableRow = ({ if ((isPublicTenant && !isUsingNonPublicApp) || isModifyableOnlyViaConfigYaml) { return isUsingSaaS - ? "You can modify this property via the SaaS dashboard." + ? "To modify this property, please visit the dashboard on supertokens.com and click on the edit configuration button." : "This property is modifyable only via the config.yaml file or via Docker env variables."; } diff --git a/src/ui/components/tenants/tenantDetail/LoginMethodsSection.tsx b/src/ui/components/tenants/tenantDetail/LoginMethodsSection.tsx index e80639af..be2c7522 100644 --- a/src/ui/components/tenants/tenantDetail/LoginMethodsSection.tsx +++ b/src/ui/components/tenants/tenantDetail/LoginMethodsSection.tsx @@ -27,9 +27,9 @@ import { PanelHeader, PanelHeaderTitleWithTooltip, PanelRoot } from "./tenantDet export const LoginMethodsSection = () => { const { tenantInfo } = useTenantDetailContext(); - const [secondaryFactorsError, setSecondaryFactorsError] = useState< - null | "MFA_NOT_INITIALIZED" | "MFA_REQUIREMENTS_FOR_AUTH_OVERRIDDEN" - >(null); + const [mfaError, setMfaError] = useState( + null + ); const doesTenantHasEmailPasswordAndPasswordlessEnabled = tenantInfo.firstFactors?.includes(FactorIds.EMAILPASSWORD) && @@ -68,7 +68,7 @@ export const LoginMethodsSection = () => { label={method.label} description={method.description} checked={tenantInfo?.firstFactors.includes(method.id)} - setSecondaryFactorsError={setSecondaryFactorsError} + setMfaError={setMfaError} /> ))} @@ -81,7 +81,7 @@ export const LoginMethodsSection = () => { Secondary Factors - {secondaryFactorsError === "MFA_NOT_INITIALIZED" && ( + {mfaError === "MFA_NOT_INITIALIZED" && ( You need to initialize the MFA recipe to use secondary factors.{" "} { to see MFA docs for more info. )} - {secondaryFactorsError === "MFA_REQUIREMENTS_FOR_AUTH_OVERRIDDEN" && ( + {mfaError === "MFA_REQUIREMENTS_FOR_AUTH_OVERRIDDEN" && ( Setting secondary factors might not take effect as getMFARequirementsForAuth has been overridden in the SDK. To be able to modify the secondary factors from the UI you would need to @@ -111,7 +111,7 @@ export const LoginMethodsSection = () => { fixedGap description={method.description} checked={tenantInfo.requiredSecondaryFactors?.includes(method.id) ?? false} - setSecondaryFactorsError={setSecondaryFactorsError} + setMfaError={setMfaError} /> ))} @@ -128,7 +128,7 @@ const LoginFactor = ({ checked, fixedGap, type, - setSecondaryFactorsError, + setMfaError, }: { id: string; label: string; @@ -136,7 +136,7 @@ const LoginFactor = ({ checked: boolean; fixedGap?: boolean; type: "first-factor" | "secondary-factor"; - setSecondaryFactorsError: (error: null | "MFA_NOT_INITIALIZED" | "MFA_REQUIREMENTS_FOR_AUTH_OVERRIDDEN") => void; + setMfaError: (error: null | "MFA_NOT_INITIALIZED" | "MFA_REQUIREMENTS_FOR_AUTH_OVERRIDDEN") => void; }) => { const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); @@ -166,14 +166,14 @@ const LoginFactor = ({ if (res.status === "RECIPE_NOT_CONFIGURED_ON_BACKEND_SDK_ERROR") { setError(res.message); } else if (res.status === "MFA_NOT_INITIALIZED_ERROR") { - setSecondaryFactorsError("MFA_NOT_INITIALIZED"); + setMfaError("MFA_NOT_INITIALIZED"); } else { throw new Error(res.status); } } if (res.status === "OK" && res.isMFARequirementsForAuthOverridden) { - setSecondaryFactorsError("MFA_REQUIREMENTS_FOR_AUTH_OVERRIDDEN"); + setMfaError("MFA_REQUIREMENTS_FOR_AUTH_OVERRIDDEN"); } // If this is not a MFA related error then clear the error @@ -181,7 +181,7 @@ const LoginFactor = ({ (res.status === "OK" && !res.isMFARequirementsForAuthOverridden) || res.status === "RECIPE_NOT_CONFIGURED_ON_BACKEND_SDK_ERROR" ) { - setSecondaryFactorsError(null); + setMfaError(null); } } } catch (error) { diff --git a/src/ui/components/tenants/tenantDetail/providerListDialog/thirdPartyProvidersList.scss b/src/ui/components/tenants/tenantDetail/providerListDialog/thirdPartyProvidersList.scss index 7c5e2fab..e67c687f 100644 --- a/src/ui/components/tenants/tenantDetail/providerListDialog/thirdPartyProvidersList.scss +++ b/src/ui/components/tenants/tenantDetail/providerListDialog/thirdPartyProvidersList.scss @@ -15,8 +15,9 @@ .provider-dialog-container { max-width: 872px; - height: auto; + height: fit-content; overflow-y: auto; + padding: 24px; } .provider-list-header { @@ -59,7 +60,7 @@ &__providers-grid { display: grid; width: 100%; - gap: 10px; + gap: 24px; grid-template-columns: 1fr 1fr 1fr; } } @@ -77,7 +78,7 @@ height: 1px; background-color: var(--color-border-command); border: none; - margin: 24px 0px; + margin: 16px 0px; } .providers-list-footer { diff --git a/src/ui/components/tenants/tenantDetail/thirdPartyPage/ThirdPartyPage.tsx b/src/ui/components/tenants/tenantDetail/thirdPartyPage/ThirdPartyPage.tsx index 67a94905..498970db 100644 --- a/src/ui/components/tenants/tenantDetail/thirdPartyPage/ThirdPartyPage.tsx +++ b/src/ui/components/tenants/tenantDetail/thirdPartyPage/ThirdPartyPage.tsx @@ -402,7 +402,8 @@ const GoogleWorkspacesForm = ({ handleContinue, handleGoBack }: AdditionalConfig

- Enter “ * “ if you want to allow logins for any google workspace domain. + For example: use "example.com" if you want to allow logins only from that domain. Enter “ * “ if you + want to allow logins for any google workspace domain.


diff --git a/src/ui/components/tenants/tenantDetail/thirdPartyProviderButton/ThirdPartyProviderButton.tsx b/src/ui/components/tenants/tenantDetail/thirdPartyProviderButton/ThirdPartyProviderButton.tsx index f4af0e54..3c6483ab 100644 --- a/src/ui/components/tenants/tenantDetail/thirdPartyProviderButton/ThirdPartyProviderButton.tsx +++ b/src/ui/components/tenants/tenantDetail/thirdPartyProviderButton/ThirdPartyProviderButton.tsx @@ -37,13 +37,13 @@ export type ThirdPartyProviderButtonProps = export const ThirdPartyProviderButton = (props: ThirdPartyProviderButtonProps) => { return ( - )} - - - ); - - if (isSAMLProvider && isAddingNewProvider && !hasAddedBoxyURLForNewSAMLProvider) { - return ( - - {panelHeader} -
-

- You will need to setup BoxyHQ to add the SAML client. -

- - -
Email us to receive your Boxy URL and continue setup - of your SAML client. - - - - Follow the steps in the{" "} - - BoxyHQ docs - {" "} - to get your Boxy URL and continue setup of your SAML client. - - -

Add the Boxy URL below

- -
- { - setProviderConfigState((prev) => ({ - ...prev, - clients: Array.isArray(prev.clients) - ? [ - { - ...prev.clients[0], - additionalConfig: { - ...prev.clients?.[0].additionalConfig, - boxyURL: e.target.value, - }, - }, - ] - : [], - })); - }} - /> -
- -
-
-
-
- - -
-
-
- - ); - } - - return ( - <> - {isSAMLProvider && ( -
-
Info
-

- Follow the steps in the{" "} - - documentation - {" "} - to get the Client Id and Secret for your SAML provider. -

-
- )} - - {panelHeader} -
- {isAddingNewProvider ? ( - - ) : ( - null} - /> - )} - - {providerConfigState.clients?.map((client, index) => ( - { - setProviderConfigState((prev) => ({ - ...prev, - clients: prev.clients?.map((c, i) => (i === index ? client : c)), - })); - }} - additionalConfigFields={customFields?.additionalConfigFields} - handleDeleteClient={() => { - setProviderConfigState((prev) => ({ - ...prev, - clients: prev.clients?.filter((_, i) => i !== index), - })); - }} - /> - ))} -
-
-
- -
- - -
-
- {isDeleteProviderDialogOpen && ( - setIsDeleteProviderDialogOpen(false)} - thirdPartyId={providerId} - goBack={() => handleGoBack(true)} - /> - )} -
- - ); -}; - -const SAMLInfoBox = ({ title, children }: { title: string; children: React.ReactNode }) => { - return ( -
-
-

{title}

-
-
{children}
-
- ); -}; - -const getBuiltInProviderInfo = (providerId: string, providerConfig?: ProviderConfig) => { - const customFieldProviderKey = Object.keys(IN_BUILT_PROVIDERS_CUSTOM_FIELDS).find((id) => - providerId.startsWith(id) - ); - const customFields = customFieldProviderKey ? IN_BUILT_PROVIDERS_CUSTOM_FIELDS[customFieldProviderKey] : undefined; - - if (providerConfig) { - return { - ...providerConfig, - clients: providerConfig.clients?.map((client) => ({ - ...client, - scope: client.scope ?? customFields?.defaultScopes, - })), - }; - } - - const baseProviderConfig = { - thirdPartyId: providerId, - clients: [{ clientId: "", clientSecret: "", clientType: "", scope: [""], additionalConfig: {} }], - }; - - if (customFields === undefined) { - return baseProviderConfig; - } - - const additionalConfigFields = customFields.additionalConfigFields ?? []; - - if (additionalConfigFields.length > 0) { - const additionalConfig = additionalConfigFields.reduce((acc, field) => { - acc[field.id] = ""; - return acc; - }, {} as Record); - - baseProviderConfig.clients[0].additionalConfig = additionalConfig; - } - - if (customFields.defaultScopes) { - baseProviderConfig.clients[0].scope = customFields.defaultScopes; - } - - return baseProviderConfig; -}; - -const IN_BUILT_PROVIDERS_CUSTOM_FIELDS: BuiltInProvidersCustomFields = { - apple: { - additionalConfigFields: [ - { - label: "Key Id", - id: "keyId", - tooltip: "The key Id for Apple.", - type: "text", - required: true, - }, - { - label: "Team Id", - id: "teamId", - tooltip: "The team Id for Apple.", - type: "text", - required: true, - }, - { - label: "Private Key", - id: "privateKey", - tooltip: "The private key for Apple.", - type: "text", - required: true, - }, - ], - defaultScopes: ["openid", "email"], - }, - "google-workspaces": { - additionalConfigFields: [ - { - label: "Hosted Domain", - id: "hd", - tooltip: "The hosted domain for Google Workspaces.", - type: "text", - required: true, - }, - ], - }, - gitlab: { - additionalConfigFields: [ - { - label: "Gitlab Base URL", - id: "gitlabBaseUrl", - tooltip: - "The base URL for Gitlab, The OIDC discovery endpoint for Gitlab, if you are using a self-hosted Gitlab instance.", - type: "text", - required: false, - }, - ], - defaultScopes: ["openid", "email"], - }, - - "active-directory": { - additionalConfigFields: [ - { - label: "Directory Id", - id: "directoryId", - tooltip: - "The id of the Microsoft Entra tenant, this is required if OIDC discovery endpoint is not provided.", - type: "text", - required: true, - }, - ], - defaultScopes: ["openid", "email"], - }, - bitbucket: { - defaultScopes: ["account", "email"], - }, - discord: { - defaultScopes: ["identify", "email"], - }, - facebook: { - defaultScopes: ["email"], - }, - github: { - defaultScopes: ["read:user", "user:email"], - }, - google: { - defaultScopes: ["openid", "email"], - }, - linkedin: { - defaultScopes: ["openid", "profile", "email"], - }, - okta: { - additionalConfigFields: [ - { - label: "Okta Domain", - id: "oktaDomain", - tooltip: - "The domain of your Okta account, this is required if OIDC discovery endpoint is not provided.", - type: "text", - required: true, - }, - ], - defaultScopes: ["openid", "email"], - }, - twitter: { - defaultScopes: ["users.read", "tweet.read"], - }, - "boxy-saml": { - additionalConfigFields: [ - { - label: "Boxy URL", - id: "boxyURL", - tooltip: "The URL of the Boxy instance.", - type: "text", - required: true, - }, - ], - }, -}; diff --git a/src/ui/components/tenants/tenantDetail/thirdPartyProviderConfig/ProviderInfoForm.tsx b/src/ui/components/tenants/tenantDetail/thirdPartyProviderConfig/ProviderInfoForm.tsx index 2636b55f..8f32d81f 100644 --- a/src/ui/components/tenants/tenantDetail/thirdPartyProviderConfig/ProviderInfoForm.tsx +++ b/src/ui/components/tenants/tenantDetail/thirdPartyProviderConfig/ProviderInfoForm.tsx @@ -69,29 +69,43 @@ export const ProviderInfoForm = ({ providerId?.startsWith(id) ); const customFields = customFieldProviderKey ? IN_BUILT_PROVIDERS_CUSTOM_FIELDS[customFieldProviderKey] : undefined; + const formHasError = Object.values(errorState).some((error) => error !== ""); const handleAddNewClient = () => { - let additionalConfigFields: Array<[string, string]> = [["", ""]]; + setProviderConfigState((prev) => { + const commonFields: Pick = { + scope: [""], + additionalConfig: [["", ""]], + forcePKCE: false, + }; + // Copy over the scope, additionalConfig and forcePKCE from the first client + if (prev.clients[0]) { + const { scope, additionalConfig, forcePKCE } = prev.clients[0]; + commonFields.scope = Array.isArray(scope) ? [...scope] : [""]; + commonFields.additionalConfig = Array.isArray(additionalConfig) ? [...additionalConfig] : [["", ""]]; + commonFields.forcePKCE = forcePKCE; + } - if (customFields) { - additionalConfigFields = customFields.map((field) => [field.id, ""]); - } + // If there are any custom additional config like in case of apple + // we don't copy those fields instead we set them to empty string + if (customFields) { + commonFields.additionalConfig = customFields.map((field) => [field.id, ""]); + } - setProviderConfigState((prev) => ({ - ...prev, - clients: [ - ...(prev?.clients ?? []), - { - clientId: "", - clientSecret: "", - clientType: "", - scope: [""], - additionalConfig: additionalConfigFields, - forcePKCE: false, - key: crypto.randomUUID(), - }, - ], - })); + return { + ...prev, + clients: [ + ...(prev?.clients ?? []), + { + clientId: "", + clientSecret: "", + clientType: "", + ...commonFields, + key: crypto.randomUUID(), + }, + ], + }; + }); }; const handleFieldChange = (e: ChangeEvent) => { @@ -357,8 +371,8 @@ export const ProviderInfoForm = ({ return ( -
-
+
+
{isAddingNewProvider ? "Configure new provider" : "Provider Configuration"} @@ -577,18 +591,15 @@ export const ProviderInfoForm = ({ />
- {(emailSelectValue === "sometimes" || emailSelectValue === "never") && ( + {emailSelectValue === "never" && (
- Note:{" "} - {emailSelectValue === "never" - ? "We will generate a fake email for the end users automatically using their user id. If you want override how the fake email is generated by the SDK you can do so by overriding the generateFakeEmail method in the provider config" - : "Add a custom override for the getUserInfo method for this provider to handle the case when provider doesn't return email. You can also choose to generate a fake email using the option below, when the provider doesn't return email (to override how the fake email is generated you can override generateFakeEmail in the provider config)."} + Note: We will generate a fake email for the end users automatically using their user id.
)} {emailSelectValue === "sometimes" && (
- +

-
+
+ {formHasError && ( +
+ Error in field +

+ Please ensure all fields are correctly filled out before saving. +

+
+ )} +