Clean up auth related feature flags

This commit is contained in:
Joshen Lim committed 2024-02-14 14:07:05 +08:00
1 parent 6e25cd693a
commit 310fac3f4d
5 files changed
+105 -195

No files matched your search

@@ -1,7 +1,5 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useParams } from 'common'
import { observer } from 'mobx-react-lite'
import { useEffect, useState } from 'react'
import {
AlertDescription_Shadcn_,
@@ -18,6 +16,7 @@ import {
} from 'ui'
import { boolean, number, object, string } from 'yup'
import { Markdown } from 'components/interfaces/Markdown'
import {
FormActions,
FormHeader,
@@ -30,8 +29,7 @@ import UpgradeToPro from 'components/ui/UpgradeToPro'
import { useAuthConfigQuery } from 'data/auth/auth-config-query'
import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useCheckPermissions, useFlag, useSelectedOrganization, useStore } from 'hooks'
import { Markdown } from 'components/interfaces/Markdown'
import { useCheckPermissions, useSelectedOrganization, useStore } from 'hooks'
import FormField from '../AuthProvidersForm/FormField'
// Use a const string to represent no chars option. Represented as empty string on the backend side.
@@ -75,7 +73,7 @@ function HoursOrNeverText({ value }: { value: number }) {
const formId = 'auth-config-basic-settings'
const BasicAuthSettingsForm = observer(() => {
const BasicAuthSettingsForm = () => {
const { ui } = useStore()
const { ref: projectRef } = useParams()
const {
@@ -96,9 +94,6 @@ const BasicAuthSettingsForm = observer(() => {
})
const isProPlanAndUp = isSuccessSubscription && subscription?.plan?.id !== 'free'
const singlePerUserReleased = useFlag('authSingleSessionPerUserReleased')
const passwordStrengthReleased = useFlag('authPasswordStrengthReleased')
const hibpReleased = useFlag('authHIBPReleased')
const INITIAL_VALUES = {
DISABLE_SIGNUP: !authConfig?.DISABLE_SIGNUP,
@@ -109,25 +104,11 @@ const BasicAuthSettingsForm = observer(() => {
SECURITY_CAPTCHA_PROVIDER: authConfig?.SECURITY_CAPTCHA_PROVIDER || 'hcaptcha',
SESSIONS_TIMEBOX: authConfig?.SESSIONS_TIMEBOX || 0,
SESSIONS_INACTIVITY_TIMEOUT: authConfig?.SESSIONS_INACTIVITY_TIMEOUT || 0,
...(singlePerUserReleased
? {
SESSIONS_SINGLE_PER_USER: authConfig?.SESSIONS_SINGLE_PER_USER || false,
}
: null),
...(passwordStrengthReleased
? {
PASSWORD_MIN_LENGTH: authConfig?.PASSWORD_MIN_LENGTH || 6,
PASSWORD_REQUIRED_CHARACTERS:
authConfig?.PASSWORD_REQUIRED_CHARACTERS || NO_REQUIRED_CHARACTERS,
...(hibpReleased
? {
PASSWORD_HIBP_ENABLED: authConfig?.PASSWORD_HIBP_ENABLED || false,
}
: null),
}
: null),
SESSIONS_SINGLE_PER_USER: authConfig?.SESSIONS_SINGLE_PER_USER || false,
PASSWORD_MIN_LENGTH: authConfig?.PASSWORD_MIN_LENGTH || 6,
PASSWORD_REQUIRED_CHARACTERS:
authConfig?.PASSWORD_REQUIRED_CHARACTERS || NO_REQUIRED_CHARACTERS,
PASSWORD_HIBP_ENABLED: authConfig?.PASSWORD_HIBP_ENABLED || false,
}
const onSubmit = (values: any, { resetForm }: any) => {
@@ -173,6 +154,7 @@ const BasicAuthSettingsForm = observer(() => {
{({ handleReset, resetForm, values, initialValues }: any) => {
const hasChanges = JSON.stringify(values) !== JSON.stringify(initialValues)
// Form is reset once remote data is loaded in store
// eslint-disable-next-line react-hooks/rules-of-hooks
useEffect(() => {
if (isSuccess) resetForm({ values: INITIAL_VALUES, initialValues: INITIAL_VALUES })
}, [isSuccess])
@@ -228,70 +210,65 @@ const BasicAuthSettingsForm = observer(() => {
/>
</FormSectionContent>
</FormSection>
{passwordStrengthReleased && (
<>
<FormSection header={<FormSectionLabel>Passwords</FormSectionLabel>}>
<FormSectionContent loading={isLoading}>
<InputNumber
id="PASSWORD_MIN_LENGTH"
size="small"
label="Minimum password length"
descriptionText="Passwords shorter than this value will be rejected as weak. Minimum 6, recommended 8 or more."
actions={<span className="mr-3 text-foreground-lighter">characters</span>}
disabled={!canUpdateConfig}
/>
<FormField
name="PASSWORD_REQUIRED_CHARACTERS"
properties={{
type: 'select',
title: 'Password Requirements',
description:
'Passwords that do not have at least one of each will be rejected as weak.',
enum: [
{
label: 'No required characters (default)',
value: NO_REQUIRED_CHARACTERS,
},
{
label: 'Letters and digits',
value: LETTERS_AND_DIGITS,
},
{
label: 'Lowercase, uppercase letters and digits',
value: LOWER_UPPER_DIGITS,
},
{
label:
'Lowercase, uppercase letters, digits and symbols (recommended)',
value: LOWER_UPPER_DIGITS_SYMBOLS,
},
],
}}
formValues={values}
/>
{isProPlanAndUp ? (
<></>
) : (
<UpgradeToPro
primaryText="Upgrade to Pro"
secondaryText="Leaked password protection available on Pro plans and up."
projectRef={projectRef!}
organizationSlug={organization!.slug}
/>
)}
<Toggle
id="PASSWORD_HIBP_ENABLED"
size="small"
label="Prevent use of leaked passwords"
afterLabel=" (recommended)"
layout="flex"
descriptionText="Rejects the use of known or easy to guess passwords on sign up or password change. Powered by the HaveIBeenPwned.org Pwned Passwords API."
disabled={!canUpdateConfig || !isProPlanAndUp}
/>
</FormSectionContent>
</FormSection>
</>
)}
<FormSection header={<FormSectionLabel>Passwords</FormSectionLabel>}>
<FormSectionContent loading={isLoading}>
<InputNumber
id="PASSWORD_MIN_LENGTH"
size="small"
label="Minimum password length"
descriptionText="Passwords shorter than this value will be rejected as weak. Minimum 6, recommended 8 or more."
actions={<span className="mr-3 text-foreground-lighter">characters</span>}
disabled={!canUpdateConfig}
/>
<FormField
name="PASSWORD_REQUIRED_CHARACTERS"
properties={{
type: 'select',
title: 'Password Requirements',
description:
'Passwords that do not have at least one of each will be rejected as weak.',
enum: [
{
label: 'No required characters (default)',
value: NO_REQUIRED_CHARACTERS,
},
{
label: 'Letters and digits',
value: LETTERS_AND_DIGITS,
},
{
label: 'Lowercase, uppercase letters and digits',
value: LOWER_UPPER_DIGITS,
},
{
label: 'Lowercase, uppercase letters, digits and symbols (recommended)',
value: LOWER_UPPER_DIGITS_SYMBOLS,
},
],
}}
formValues={values}
/>
{isProPlanAndUp ? (
<></>
) : (
<UpgradeToPro
primaryText="Upgrade to Pro"
secondaryText="Leaked password protection available on Pro plans and up."
projectRef={projectRef!}
organizationSlug={organization!.slug}
/>
)}
<Toggle
id="PASSWORD_HIBP_ENABLED"
size="small"
label="Prevent use of leaked passwords"
afterLabel=" (recommended)"
layout="flex"
descriptionText="Rejects the use of known or easy to guess passwords on sign up or password change. Powered by the HaveIBeenPwned.org Pwned Passwords API."
disabled={!canUpdateConfig || !isProPlanAndUp}
/>
</FormSectionContent>
</FormSection>
<FormSection header={<FormSectionLabel>User Sessions</FormSectionLabel>}>
<FormSectionContent loading={isLoading}>
{isProPlanAndUp ? (
@@ -304,16 +281,14 @@ const BasicAuthSettingsForm = observer(() => {
organizationSlug={organization!.slug}
/>
)}
{singlePerUserReleased && (
<Toggle
id="SESSIONS_SINGLE_PER_USER"
size="small"
label="Enforce single session per user"
layout="flex"
descriptionText="If enabled, all but a user's most recently active session will be terminated."
disabled={!canUpdateConfig || !isProPlanAndUp}
/>
)}
<Toggle
id="SESSIONS_SINGLE_PER_USER"
size="small"
label="Enforce single session per user"
layout="flex"
descriptionText="If enabled, all but a user's most recently active session will be terminated."
disabled={!canUpdateConfig || !isProPlanAndUp}
/>
<InputNumber
id="SESSIONS_TIMEBOX"
size="small"
@@ -398,6 +373,6 @@ const BasicAuthSettingsForm = observer(() => {
}}
</Form>
)
})
}
export default BasicAuthSettingsForm
@@ -21,8 +21,7 @@ import {
} from 'components/ui/Forms'
import { useAuthConfigQuery } from 'data/auth/auth-config-query'
import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation'
import { useCheckPermissions, useFlag, useStore } from 'hooks'
import { useCheckPermissions, useStore } from 'hooks'
import SchemaFunctionSelector from './SchemaFunctionSelector'
const schema = object({
@@ -43,19 +42,12 @@ const BasicHooksConfig = () => {
isSuccess,
} = useAuthConfigQuery({ projectRef })
const { mutate: updateAuthConfig, isLoading: isUpdatingConfig } = useAuthConfigUpdateMutation()
const customizeAccessTokenReleased = useFlag('authHooksCustomizeAccessToken')
const canUpdateConfig = useCheckPermissions(PermissionAction.UPDATE, 'custom_config_gotrue')
// TODO: Remove as any once these properties are defined in Auth Config types
const INITIAL_VALUES = {
...(customizeAccessTokenReleased
? {
HOOK_CUSTOM_ACCESS_TOKEN_ENABLED: authConfig?.HOOK_CUSTOM_ACCESS_TOKEN_ENABLED || false,
HOOK_CUSTOM_ACCESS_TOKEN_URI: authConfig?.HOOK_CUSTOM_ACCESS_TOKEN_URI || '',
}
: null),
HOOK_CUSTOM_ACCESS_TOKEN_ENABLED: authConfig?.HOOK_CUSTOM_ACCESS_TOKEN_ENABLED || false,
HOOK_CUSTOM_ACCESS_TOKEN_URI: authConfig?.HOOK_CUSTOM_ACCESS_TOKEN_URI || '',
}
const onSubmit = (values: any, { resetForm }: any) => {
@@ -142,23 +134,15 @@ const BasicHooksConfig = () => {
descriptionText="Select the function to be called by Supabase Auth each time a new JWT is created. It should return the claims you wish to be present in the JWT."
values={values}
setFieldValue={setFieldValue}
disabled={!canUpdateConfig || !customizeAccessTokenReleased}
disabled={!canUpdateConfig}
/>
{!customizeAccessTokenReleased && (
<Alert_Shadcn_ variant="default">
<AlertTitle_Shadcn_>Coming soon!</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
This hook is not available yet on your project.
</AlertDescription_Shadcn_>
</Alert_Shadcn_>
)}
{values.HOOK_CUSTOM_ACCESS_TOKEN_URI && (
<Toggle
id="HOOK_CUSTOM_ACCESS_TOKEN_ENABLED"
size="medium"
label="Enable hook"
layout="flex"
disabled={!canUpdateConfig || !customizeAccessTokenReleased}
disabled={!canUpdateConfig}
/>
)}
</FormSectionContent>
@@ -23,8 +23,7 @@ import UpgradeToPro from 'components/ui/UpgradeToPro'
import { useAuthConfigQuery } from 'data/auth/auth-config-query'
import { useAuthConfigUpdateMutation } from 'data/auth/auth-config-update-mutation'
import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query'
import { useCheckPermissions, useFlag, useSelectedOrganization, useStore } from 'hooks'
import { useCheckPermissions, useSelectedOrganization, useStore } from 'hooks'
import SchemaFunctionSelector from './SchemaFunctionSelector'
const schema = object({
@@ -47,10 +46,7 @@ const EnterpriseHooksConfig = () => {
isSuccess,
} = useAuthConfigQuery({ projectRef })
const { mutate: updateAuthConfig, isLoading: isUpdatingConfig } = useAuthConfigUpdateMutation()
const canUpdateConfig = useCheckPermissions(PermissionAction.UPDATE, 'custom_config_gotrue')
const mfaVerificationAttemptReleased = useFlag('authHookMFAVerification')
const passwordVerificationAttemptReleased = useFlag('authHookPasswordVerification')
const organization = useSelectedOrganization()
const { data: subscription, isSuccess: isSuccessSubscription } = useOrgSubscriptionQuery({
@@ -61,22 +57,14 @@ const EnterpriseHooksConfig = () => {
isSuccessSubscription && subscription?.plan?.id !== 'free' && subscription?.plan?.id !== 'pro'
const INITIAL_VALUES = {
...(mfaVerificationAttemptReleased
? {
HOOK_MFA_VERIFICATION_ATTEMPT_ENABLED:
authConfig?.HOOK_MFA_VERIFICATION_ATTEMPT_ENABLED || false,
HOOK_MFA_VERIFICATION_ATTEMPT_URI: authConfig?.HOOK_MFA_VERIFICATION_ATTEMPT_URI || '',
}
: null),
...(passwordVerificationAttemptReleased
? {
// remove as any when the types are merged in
HOOK_PASSWORD_VERIFICATION_ATTEMPT_ENABLED:
(authConfig as any)?.HOOK_PASSWORD_VERIFICATION_ATTEMPT_ENABLED || false,
HOOK_PASSWORD_VERIFICATION_ATTEMPT_URI:
(authConfig as any)?.HOOK_PASSWORD_VERIFICATION_ATTEMPT_URI || '',
}
: null),
HOOK_MFA_VERIFICATION_ATTEMPT_ENABLED:
authConfig?.HOOK_MFA_VERIFICATION_ATTEMPT_ENABLED || false,
HOOK_MFA_VERIFICATION_ATTEMPT_URI: authConfig?.HOOK_MFA_VERIFICATION_ATTEMPT_URI || '',
// remove as any when the types are merged in
HOOK_PASSWORD_VERIFICATION_ATTEMPT_ENABLED:
(authConfig as any)?.HOOK_PASSWORD_VERIFICATION_ATTEMPT_ENABLED || false,
HOOK_PASSWORD_VERIFICATION_ATTEMPT_URI:
(authConfig as any)?.HOOK_PASSWORD_VERIFICATION_ATTEMPT_URI || '',
}
const onSubmit = (values: any, { resetForm }: any) => {
@@ -172,31 +160,15 @@ const EnterpriseHooksConfig = () => {
descriptionText="Select the function to be called by Supabase Auth each time a user tries to verify an MFA factor. Return a decision on whether to reject the attempt and future ones, or to allow the user to keep trying."
values={values}
setFieldValue={setFieldValue}
disabled={
!canUpdateConfig ||
!isTeamsEnterprisePlan ||
!mfaVerificationAttemptReleased
}
disabled={!canUpdateConfig || !isTeamsEnterprisePlan}
/>
{!mfaVerificationAttemptReleased && (
<Alert_Shadcn_ variant="default">
<AlertTitle_Shadcn_>Coming soon!</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
This hook is not available yet on your project.
</AlertDescription_Shadcn_>
</Alert_Shadcn_>
)}
{values.HOOK_MFA_VERIFICATION_ATTEMPT_URI && (
<Toggle
id="HOOK_MFA_VERIFICATION_ATTEMPT_ENABLED"
size="small"
label="Enable hook"
layout="flex"
disabled={
!canUpdateConfig ||
!isTeamsEnterprisePlan ||
!mfaVerificationAttemptReleased
}
disabled={!canUpdateConfig || !isTeamsEnterprisePlan}
/>
)}
</FormSectionContent>
@@ -212,31 +184,15 @@ const EnterpriseHooksConfig = () => {
descriptionText="Select the function to be called by Supabase Auth each time a user tries to sign in with a password. Return a decision whether to allow the user to reject the attempt, or to allow the user to keep trying."
values={values}
setFieldValue={setFieldValue}
disabled={
!canUpdateConfig ||
!isTeamsEnterprisePlan ||
!passwordVerificationAttemptReleased
}
disabled={!canUpdateConfig || !isTeamsEnterprisePlan}
/>
{!passwordVerificationAttemptReleased && (
<Alert_Shadcn_ variant="default">
<AlertTitle_Shadcn_>Coming soon!</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
This hook is not available yet on your project.
</AlertDescription_Shadcn_>
</Alert_Shadcn_>
)}
{values.HOOK_PASSWORD_VERIFICATION_ATTEMPT_URI && (
<Toggle
id="HOOK_PASSWORD_VERIFICATION_ATTEMPT_ENABLED"
size="small"
label="Enable hook"
layout="flex"
disabled={
!canUpdateConfig ||
!isTeamsEnterprisePlan ||
!passwordVerificationAttemptReleased
}
disabled={!canUpdateConfig || !isTeamsEnterprisePlan}
/>
)}
</FormSectionContent>
@@ -5,7 +5,7 @@ import { PropsWithChildren } from 'react'
import { useIsColumnLevelPrivilegesEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import ProductMenu from 'components/ui/ProductMenu'
import { useAuthConfigPrefetch } from 'data/auth/auth-config-query'
import { useFlag, withAuth } from 'hooks'
import { withAuth } from 'hooks'
import ProjectLayout from '../'
import { generateAuthMenu } from './AuthLayout.utils'
@@ -15,7 +15,6 @@ export interface AuthLayoutProps {
const AuthLayout = ({ title, children }: PropsWithChildren<AuthLayoutProps>) => {
const { ref: projectRef = 'default' } = useParams()
const hooksReleased = useFlag('authHooksReleased')
const columnLevelPrivileges = useIsColumnLevelPrivilegesEnabled()
useAuthConfigPrefetch({ projectRef })
@@ -30,7 +29,7 @@ const AuthLayout = ({ title, children }: PropsWithChildren<AuthLayoutProps>) =>
productMenu={
<ProductMenu
page={page}
menu={generateAuthMenu(projectRef ?? 'default', { hooksReleased, columnLevelPrivileges })}
menu={generateAuthMenu(projectRef ?? 'default', { columnLevelPrivileges })}
/>
}
isBlocking={false}
@@ -3,7 +3,7 @@ import { IS_PLATFORM } from 'lib/constants'
export const generateAuthMenu = (
ref: string,
options: { hooksReleased?: boolean; columnLevelPrivileges?: boolean } = {}
options: { columnLevelPrivileges?: boolean } = {}
): ProductMenuGroup[] => {
return [
{
@@ -56,17 +56,13 @@ export const generateAuthMenu = (
url: `/project/${ref}/auth/url-configuration`,
items: [],
},
...(options?.hooksReleased
? [
{
name: 'Hooks',
key: 'hooks',
url: `/project/${ref}/auth/hooks`,
items: [],
label: 'BETA',
},
]
: []),
{
name: 'Hooks',
key: 'hooks',
url: `/project/${ref}/auth/hooks`,
items: [],
label: 'BETA',
},
]
: []),
],