diff --git a/packages/ui/src/components/Listbox/Listbox2.tsx b/packages/ui/src/components/Listbox/Listbox2.tsx index 288c3e4c0d3..9ed1d8258d1 100644 --- a/packages/ui/src/components/Listbox/Listbox2.tsx +++ b/packages/ui/src/components/Listbox/Listbox2.tsx @@ -21,7 +21,7 @@ function classNames(...classes: any) { export interface Props extends Omit, 'size'> { className?: string children: React.ReactNode - descriptionText?: string + descriptionText?: string | React.ReactNode error?: string icon?: any id?: string diff --git a/studio/components/interfaces/Auth/AuthProvidersForm/AuthProvidersForm.tsx b/studio/components/interfaces/Auth/AuthProvidersForm/AuthProvidersForm.tsx index 138d1711c28..f9ab8236697 100644 --- a/studio/components/interfaces/Auth/AuthProvidersForm/AuthProvidersForm.tsx +++ b/studio/components/interfaces/Auth/AuthProvidersForm/AuthProvidersForm.tsx @@ -1,6 +1,6 @@ import { observer } from 'mobx-react-lite' -import { useStore } from 'hooks' -import { PROVIDERS_SCHEMAS } from 'stores/authConfig/schema' +import { useStore, useFlag } from 'hooks' +import { PROVIDERS_SCHEMAS, PROVIDER_SAML } from 'stores/authConfig/schema' import { FormHeader } from 'components/ui/Forms' import { HorizontalShimmerWithIcon } from 'components/ui/Shimmers' import ProviderForm from './ProviderForm' @@ -8,7 +8,10 @@ import { ProviderCollapsibleClasses } from './AuthProvidersForm.constants' const AuthProvidersForm = () => { const { authConfig } = useStore() - const providers = PROVIDERS_SCHEMAS + const isSamlEnabled = useFlag('isSamlEnabled') + const providers = isSamlEnabled + ? PROVIDERS_SCHEMAS + : PROVIDERS_SCHEMAS.filter((provider) => provider !== PROVIDER_SAML) return (
diff --git a/studio/components/interfaces/Auth/AuthProvidersForm/FormField.tsx b/studio/components/interfaces/Auth/AuthProvidersForm/FormField.tsx index 49e793ec9eb..f9437860cc7 100644 --- a/studio/components/interfaces/Auth/AuthProvidersForm/FormField.tsx +++ b/studio/components/interfaces/Auth/AuthProvidersForm/FormField.tsx @@ -103,7 +103,17 @@ const FormField: FC = ({ name, properties, formValues, disabled = false } name={name} disabled={disabled} label={properties.title} - descriptionText={properties.description} + descriptionText={ + properties.description ? ( + + {properties.description} + + ) : null + } /> ) @@ -114,7 +124,17 @@ const FormField: FC = ({ name, properties, formValues, disabled = false } name={name} disabled={disabled} label={properties.title} - descriptionText={properties.description} + descriptionText={ + properties.description ? ( + + {properties.description} + + ) : null + } defaultValue={properties.enum[0]} > {properties.enum.map((option: Enum) => { diff --git a/studio/components/interfaces/Auth/AuthProvidersForm/ProviderForm.tsx b/studio/components/interfaces/Auth/AuthProvidersForm/ProviderForm.tsx index 74c1ee5a4f6..7e70bcebd4d 100644 --- a/studio/components/interfaces/Auth/AuthProvidersForm/ProviderForm.tsx +++ b/studio/components/interfaces/Auth/AuthProvidersForm/ProviderForm.tsx @@ -26,12 +26,23 @@ const ProviderForm: FC = ({ provider }) => { Object.keys(provider.properties).forEach((key) => { // When the key is a 'double negative' key, we must reverse the boolean before adding it to the form const isDoubleNegative = doubleNegativeKeys.includes(key) - initialValues[key] = isDoubleNegative ? !authConfig.config[key] : authConfig.config[key] ?? '' + + if (provider.title === 'SAML 2.0') { + initialValues[key] = authConfig.config[key] ?? false + } else { + initialValues[key] = isDoubleNegative + ? !authConfig.config[key] + : authConfig.config[key] ?? '' + } }) return initialValues } - const isActive = authConfig.config[`EXTERNAL_${provider?.title?.toUpperCase()}_ENABLED`] + // [Joshen] Doing this check as SAML doesn't follow the same naming structure as the other provider options + const isActive = + provider.title === 'SAML 2.0' + ? authConfig.config['SAML_ENABLED'] || false + : authConfig.config[`EXTERNAL_${provider?.title?.toUpperCase()}_ENABLED`] const INITIAL_VALUES = generateInitialValues() const onSubmit = async (values: any, { setSubmitting, resetForm }: any) => { diff --git a/studio/public/img/icons/saml-icon.svg b/studio/public/img/icons/saml-icon.svg new file mode 100644 index 00000000000..7348e7b2e96 --- /dev/null +++ b/studio/public/img/icons/saml-icon.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/studio/stores/authConfig/schema/AuthProviders/AuthProvidersFormValidation.tsx b/studio/stores/authConfig/schema/AuthProviders/AuthProvidersFormValidation.tsx index c5ffa57f386..084c21c0fcf 100644 --- a/studio/stores/authConfig/schema/AuthProviders/AuthProvidersFormValidation.tsx +++ b/studio/stores/authConfig/schema/AuthProviders/AuthProvidersFormValidation.tsx @@ -998,534 +998,30 @@ const EXTERNAL_PROVIDER_ZOOM = { }, } -export const OLD = { +export const PROVIDER_SAML = { + $schema: JSON_SCHEMA_VERSION, type: 'object', - required: [ - 'SITE_URL', - 'DISABLE_SIGNUP', - 'JWT_EXP', - 'JWT_AUD', - 'JWT_DEFAULT_GROUP_NAME', - 'SECURITY_UPDATE_PASSWORD_REQUIRE_REAUTHENTICATION', - 'EXTERNAL_EMAIL_ENABLED', - 'EXTERNAL_PHONE_ENABLED', - 'EXTERNAL_APPLE_ENABLED', - 'EXTERNAL_AZURE_ENABLED', - 'EXTERNAL_BITBUCKET_ENABLED', - 'EXTERNAL_DISCORD_ENABLED', - 'EXTERNAL_FACEBOOK_ENABLED', - 'EXTERNAL_GITHUB_ENABLED', - 'EXTERNAL_GITLAB_ENABLED', - 'EXTERNAL_GOOGLE_ENABLED', - 'EXTERNAL_KEYCLOAK_ENABLED', - 'EXTERNAL_LINKEDIN_ENABLED', - 'EXTERNAL_NOTION_ENABLED', - 'EXTERNAL_TWITCH_ENABLED', - 'EXTERNAL_TWITTER_ENABLED', - 'EXTERNAL_SLACK_ENABLED', - 'EXTERNAL_SPOTIFY_ENABLED', - 'EXTERNAL_WORKOS_ENABLED', - 'EXTERNAL_ZOOM_ENABLED', - 'SMS_AUTOCONFIRM', - 'SMS_MAX_FREQUENCY', - 'SMS_OTP_EXP', - 'SMS_OTP_LENGTH', - 'SMS_PROVIDER', - 'SMS_TEMPLATE', - 'MAILER_AUTOCONFIRM', - 'MAILER_URLPATHS_INVITE', - 'MAILER_URLPATHS_CONFIRMATION', - 'MAILER_URLPATHS_RECOVERY', - 'MAILER_URLPATHS_EMAIL_CHANGE', - 'MAILER_SUBJECTS_INVITE', - 'MAILER_SUBJECTS_CONFIRMATION', - 'MAILER_SUBJECTS_RECOVERY', - 'MAILER_SUBJECTS_MAGIC_LINK', - 'MAILER_SUBJECTS_EMAIL_CHANGE', - 'MAILER_TEMPLATES_INVITE_CONTENT', - 'MAILER_TEMPLATES_CONFIRMATION_CONTENT', - 'MAILER_TEMPLATES_RECOVERY_CONTENT', - 'MAILER_TEMPLATES_MAGIC_LINK_CONTENT', - 'MAILER_TEMPLATES_EMAIL_CHANGE_CONTENT', - ], + title: 'SAML 2.0', properties: { - SITE_URL: { - title: 'Site URL', - type: 'string', - help: 'The base URL of your website. Used as an allow-list for redirects and for constructing URLs used in emails.', - }, - URI_ALLOW_LIST: { - title: 'Additional redirect URLs', - type: 'string', - help: 'A comma separated list of *exact* URLs that auth providers are permitted to redirect to post authentication.', - }, - DISABLE_SIGNUP: { - title: 'Disable signup', - type: 'boolean', - help: 'Allow/disallow new user signups to your project.', - }, - EXTERNAL_EMAIL_ENABLED: { - title: 'Enable email signup', - type: 'boolean', - help: 'Allow/disallow new user signups via email to your project.', - }, - EXTERNAL_PHONE_ENABLED: { - title: 'Enable phone signup', - type: 'boolean', - help: 'Allow/disallow new user signups via phone to your project.', - }, - JWT_EXP: { - title: 'JWT expiry', - type: 'integer', - help: 'How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 seconds (one week).', - minimum: 1, - maximum: 604800, - multipleof: 1, - }, - JWT_AUD: { - title: 'JWT audience', - type: 'string', - }, - JWT_DEFAULT_GROUP_NAME: { - title: 'Default user group', - type: 'string', - }, - EXTERNAL_APPLE_ENABLED: { - title: 'Apple enabled', + SAML_ENABLED: { + title: 'Enable SAML 2.0 Single Sign-on', + description: + 'You will need to use the [Supabase CLI](https://supabase.com/docs/guides/auth/sso/auth-sso-saml#managing-saml-20-connections) to set up SAML after enabling it', type: 'boolean', }, - EXTERNAL_APPLE_CLIENT_ID: { - title: 'Apple client ID', - type: 'string', - }, - EXTERNAL_APPLE_SECRET: { - title: 'Apple secret', - type: 'string', - }, - EXTERNAL_AZURE_ENABLED: { - title: 'Azure enabled', - type: 'boolean', - }, - EXTERNAL_AZURE_CLIENT_ID: { - title: 'Azure client ID', - type: 'string', - }, - EXTERNAL_AZURE_SECRET: { - title: 'Azure secret', - type: 'string', - }, - EXTERNAL_AZURE_URL: { - title: 'Azure Tenant URL', - type: 'string', - }, - EXTERNAL_BITBUCKET_ENABLED: { - title: 'Bitbucket enabled', - type: 'boolean', - }, - EXTERNAL_BITBUCKET_CLIENT_ID: { - title: 'Bitbucket client ID', - type: 'string', - }, - EXTERNAL_BITBUCKET_SECRET: { - title: 'Bitbucket secret', - type: 'string', - }, - EXTERNAL_DISCORD_ENABLED: { - title: 'Discord enabled', - type: 'boolean', - }, - EXTERNAL_DISCORD_CLIENT_ID: { - title: 'Discord client ID', - type: 'string', - }, - EXTERNAL_DISCORD_SECRET: { - title: 'Discord secret', - type: 'string', - }, - EXTERNAL_FACEBOOK_ENABLED: { - title: 'Facebook enabled', - type: 'boolean', - }, - EXTERNAL_FACEBOOK_CLIENT_ID: { - title: 'Facebook client ID', - type: 'string', - }, - EXTERNAL_FACEBOOK_SECRET: { - title: 'Facebook secret', - type: 'string', - }, - EXTERNAL_GITHUB_ENABLED: { - title: 'GitHub enabled', - type: 'boolean', - }, - EXTERNAL_GITHUB_CLIENT_ID: { - title: 'GitHub client ID', - type: 'string', - }, - EXTERNAL_GITHUB_SECRET: { - title: 'GitHub secret', - type: 'string', - }, - EXTERNAL_GITLAB_ENABLED: { - title: 'GitLab enabled', - type: 'boolean', - }, - EXTERNAL_GITLAB_CLIENT_ID: { - title: 'GitLab client ID', - type: 'string', - }, - EXTERNAL_GITLAB_SECRET: { - title: 'Gitlab secret', - type: 'string', - }, - EXTERNAL_GITLAB_URL: { - title: - 'The base URL used for constructing the URLs to request authorization and access tokens.', - type: 'string', - }, - EXTERNAL_GOOGLE_ENABLED: { - title: 'Google enabled', - type: 'boolean', - }, - EXTERNAL_GOOGLE_CLIENT_ID: { - title: 'Google client ID', - type: 'string', - }, - EXTERNAL_GOOGLE_SECRET: { - title: 'Google secret', - type: 'string', - }, - EXTERNAL_KEYCLOAK_ENABLED: { - title: 'Keycloak enabled', - type: 'boolean', - }, - EXTERNAL_KEYCLOAK_CLIENT_ID: { - title: 'Keycloak client ID', - type: 'string', - }, - EXTERNAL_KEYCLOAK_SECRET: { - title: 'Keycloak secret', - type: 'string', - }, - EXTERNAL_KEYCLOAK_URL: { - title: 'Keycloak URL', - type: 'string', - }, - EXTERNAL_LINKEDIN_ENABLED: { - title: 'Linkedin enabled', - type: 'boolean', - }, - EXTERNAL_LINKEDIN_CLIENT_ID: { - title: 'Linkedin client ID', - type: 'string', - }, - EXTERNAL_LINKEDIN_SECRET: { - title: 'Linkedin secret', - type: 'string', - }, - EXTERNAL_NOTION_ENABLED: { - title: 'Notion enabled', - type: 'boolean', - }, - EXTERNAL_NOTION_CLIENT_ID: { - title: 'Notion client ID', - type: 'string', - }, - EXTERNAL_NOTION_SECRET: { - title: 'Notion secret', - type: 'string', - }, - EXTERNAL_TWITCH_ENABLED: { - title: 'Twitch enabled', - type: 'boolean', - }, - EXTERNAL_TWITCH_CLIENT_ID: { - title: 'Twitch client ID', - type: 'string', - }, - EXTERNAL_TWITCH_SECRET: { - title: 'Twitch secret', - type: 'string', - }, - EXTERNAL_TWITTER_ENABLED: { - title: 'Twitter enabled', - type: 'boolean', - }, - EXTERNAL_TWITTER_CLIENT_ID: { - title: 'Twitter client ID', - type: 'string', - }, - EXTERNAL_TWITTER_SECRET: { - title: 'Twitter secret', - type: 'string', - }, - EXTERNAL_SLACK_ENABLED: { - title: 'Slack enabled', - type: 'boolean', - }, - EXTERNAL_SLACK_CLIENT_ID: { - title: 'Slack client ID', - type: 'string', - }, - EXTERNAL_SLACK_SECRET: { - title: 'Slack secret', - type: 'string', - }, - EXTERNAL_SPOTIFY_ENABLED: { - title: 'Spotify enabled', - type: 'boolean', - }, - EXTERNAL_SPOTIFY_CLIENT_ID: { - title: 'Spotify client ID', - type: 'string', - }, - EXTERNAL_SPOTIFY_SECRET: { - title: 'Spotify secret', - type: 'string', - }, - EXTERNAL_WORKOS_ENABLED: { - title: 'WorkOS enabled', - type: 'boolean', - }, - EXTERNAL_WORKOS_CLIENT_ID: { - title: 'WorkOS client ID', - type: 'string', - }, - EXTERNAL_WORKOS_SECRET: { - title: 'WorkOS secret', - type: 'string', - }, - EXTERNAL_WORKOS_URL: { - title: 'WorkOS URL', - type: 'string', - }, - EXTERNAL_ZOOM_ENABLED: { - title: 'Zoom enabled', - type: 'boolean', - }, - EXTERNAL_ZOOM_CLIENT_ID: { - title: 'Zoom client ID', - type: 'string', - }, - EXTERNAL_ZOOM_SECRET: { - title: 'Zoom secret', - type: 'string', - }, - SMTP_ADMIN_EMAIL: { - title: 'SMTP admin email', - type: 'string', - }, - SMTP_HOST: { - title: 'SMTP host', - type: 'string', - }, - SMTP_PORT: { - title: 'SMTP port', - type: 'string', - }, - SMTP_USER: { - title: 'SMTP user', - type: 'string', - }, - SMTP_PASS: { - title: 'SMTP password', - type: 'string', - }, - SMTP_PASS_ENCRYPTED: { - title: 'SMTP password', - type: 'string', - }, - SMTP_SENDER_NAME: { - title: 'SMTP sender name', - type: 'string', - }, - RATE_LIMIT_EMAIL_SENT: { - title: 'Rate limit', - type: 'number', - help: 'Maximum number of emails sent per hour (Default: 30, Max: 32,767)', - minimum: 1, - maximum: 32767, - multipleof: 1, - }, - RATE_LIMIT_SMS_SENT: { - title: 'Rate limit', - type: 'number', - help: 'Maximum number of SMS-es sent per hour (Default: 30, Max: 32,767)', - minimum: 1, - maximum: 32767, - multipleof: 1, - }, - MAILER_SECURE_EMAIL_CHANGE_ENABLED: { - title: 'Double confirm email changes', - type: 'boolean', - help: 'If enabled, a user will be required to confirm any email change on both the old, and new email addresses. If disabled, only the new email is required to confirm', - }, - MAILER_AUTOCONFIRM: { - title: 'Enable email confirmations', - type: 'boolean', - help: 'If enabled, users need to confirm their email address before signing in.', - }, - MAILER_URLPATHS_INVITE: { - title: 'Confirmation URL', - type: 'string', - }, - MAILER_URLPATHS_CONFIRMATION: { - title: 'Path', - type: 'string', - }, - MAILER_URLPATHS_RECOVERY: { - title: 'Confirmation URL', - type: 'string', - }, - MAILER_URLPATHS_EMAIL_CHANGE: { - title: 'Confirmation URL', - type: 'string', - }, - MAILER_SUBJECTS_INVITE: { - title: 'Subject', - type: 'string', - }, - MAILER_SUBJECTS_CONFIRMATION: { - title: 'Subject', - type: 'string', - }, - MAILER_SUBJECTS_MAGIC_LINK: { - title: 'Subject', - type: 'string', - }, - MAILER_SUBJECTS_RECOVERY: { - title: 'Subject', - type: 'string', - }, - MAILER_SUBJECTS_EMAIL_CHANGE: { - title: 'Subject', - type: 'string', - }, - MAILER_TEMPLATES_INVITE: { - type: 'string', - }, - MAILER_TEMPLATES_INVITE_CONTENT: { - title: 'Body', - type: 'string', - }, - MAILER_TEMPLATES_CONFIRMATION: { - type: 'string', - }, - MAILER_TEMPLATES_CONFIRMATION_CONTENT: { - title: 'Body', - type: 'string', - }, - MAILER_TEMPLATES_RECOVERY: { - type: 'string', - }, - MAILER_TEMPLATES_MAGIC_LINK: { - type: 'string', - }, - MAILER_TEMPLATES_RECOVERY_CONTENT: { - title: 'Body', - type: 'string', - }, - MAILER_TEMPLATES_MAGIC_LINK_CONTENT: { - title: 'Body', - type: 'string', - }, - MAILER_TEMPLATES_EMAIL_CHANGE: { - type: 'string', - }, - MAILER_TEMPLATES_EMAIL_CHANGE_CONTENT: { - title: 'Body', - type: 'string', - }, - PASSWORD_MIN_LENGTH: { - title: 'Minimum password length', - type: 'integer', - }, - SECURITY_UPDATE_PASSWORD_REQUIRE_REAUTHENTICATION: { - title: 'Enable strict password updates', - type: 'boolean', - help: 'If enabled, users need to reauthenticate via email or phone before updating their passwords.', - }, - SMS_AUTOCONFIRM: { - title: 'Enable phone confirmations', - type: 'boolean', - help: 'If enabled, users need to confirm their phone number before signing in.', - }, - SMS_PROVIDER: { - title: 'Sms provider', - type: 'string', - options: [ - { - value: 'twilio', - label: 'Twilio', - }, - { - value: 'messagebird', - label: 'Messagebird', - }, - { - value: 'vonage', - label: 'Vonage', - }, - { - value: 'textlocal', - label: 'Textlocal', - }, - ], - }, - SMS_TEXTLOCAL_API_KEY: { - title: 'Textlocal API key', - type: 'string', - }, - SMS_TEXTLOCAL_SENDER: { - title: 'Textlocal sender', - type: 'string', - help: 'Textlocal sender phone number', - }, - SMS_TWILIO_ACCOUNT_SID: { - title: 'Twilio account SID', - type: 'string', - }, - SMS_TWILIO_AUTH_TOKEN: { - title: 'Twilio auth token', - type: 'string', - }, - SMS_TWILIO_MESSAGE_SERVICE_SID: { - title: 'Twilio message service SID', - type: 'string', - help: 'Twilio message service SID or twilio phone number', - }, - SMS_MESSAGEBIRD_ACCESS_KEY: { - title: 'Messagebird access key', - type: 'string', - }, - SMS_MESSAGEBIRD_ORIGINATOR: { - title: 'Messagebird originator', - type: 'string', - help: 'Messagebird sender name or phone number', - }, - SMS_VONAGE_API_KEY: { - title: 'Vonage API key', - type: 'string', - }, - SMS_VONAGE_API_SECRET: { - title: 'Vonage API secret', - type: 'string', - }, - SMS_VONAGE_FROM: { - title: 'Vonage sender', - type: 'string', - help: 'Vonage sender phone number', - }, - SMS_TEMPLATE: { - title: 'Body', - type: 'string', - }, + }, + validationSchema: object().shape({ + SAML_ENABLED: boolean().required(), + }), + misc: { + iconKey: 'saml-icon', }, } export const PROVIDERS_SCHEMAS = [ PROVIDER_EMAIL, PROVIDER_PHONE, + PROVIDER_SAML, EXTERNAL_PROVIDER_APPLE, EXTERNAL_PROVIDER_AZURE, EXTERNAL_PROVIDER_BITBUCKET, diff --git a/studio/stores/authConfig/schema/index.ts b/studio/stores/authConfig/schema/index.ts index 05aa9d8f6c5..43266b2cd49 100644 --- a/studio/stores/authConfig/schema/index.ts +++ b/studio/stores/authConfig/schema/index.ts @@ -1,2 +1,2 @@ -export { PROVIDERS_SCHEMAS } from './AuthProviders/AuthProvidersFormValidation' +export { PROVIDERS_SCHEMAS, PROVIDER_SAML } from './AuthProviders/AuthProvidersFormValidation' export { TEMPLATES_SCHEMAS } from './AuthProviders/AuthTemplatesValidation' diff --git a/studio/styles/ui.scss b/studio/styles/ui.scss index b9191559f62..12e09fa2017 100644 --- a/studio/styles/ui.scss +++ b/studio/styles/ui.scss @@ -414,6 +414,12 @@ } } +.form-field-markdown { + a { + @apply text-brand-900 transition opacity-75 hover:opacity-100 underline; + } +} + .view-icon { rect { @apply fill-blue-500 stroke-blue-500;