feat: add password strength configs (#19330)

* feat: add password strength configs

* Revert the changes to RadioGroup since they may cause issues in other usecases. Add a string to represent the empty string state for PASSWORD_REQUIRED_CHARACTERS property.

---------

Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
This commit is contained in:
Stojan Dimitrovski
2023-12-04 14:26:21 +01:00
committed by GitHub
parent 4e48e3c831
commit eed4e29db5
2 changed files with 246 additions and 85 deletions

View File

@@ -31,7 +31,13 @@ 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, useSelectedOrganization, useStore, useFlag } from 'hooks'
import { useCheckPermissions, useFlag, useSelectedOrganization, useStore } from 'hooks'
// Use a const string to represent no chars option. Represented as empty string on the backend side.
const NO_REQUIRED_CHARACTERS = 'NO_REQUIRED_CHARS'
const LETTERS_AND_DIGITS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789'
const LOWER_UPPER_DIGITS = 'abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789'
const LOWER_UPPER_DIGITS_SYMBOLS = LOWER_UPPER_DIGITS + ':!@#$%^&*()_+-=[]{};\'\\\\:"|<>?,./`~'
const schema = object({
DISABLE_SIGNUP: boolean().required(),
@@ -50,6 +56,8 @@ const schema = object({
SESSIONS_TIMEBOX: number().min(0, 'Must be a positive number'),
SESSIONS_INACTIVITY_TIMEOUT: number().min(0, 'Must be a positive number'),
SESSIONS_SINGLE_PER_USER: boolean(),
PASSWORD_MIN_LENGTH: number().min(6, 'Must be greater or equal to 6.'),
PASSWORD_REQUIRED_CHARACTERS: string(),
})
function HoursOrNeverText({ value }: { value: number }) {
@@ -85,6 +93,7 @@ const BasicAuthSettingsForm = observer(() => {
const isProPlanAndUp = isSuccessSubscription && subscription?.plan?.id !== 'free'
const singlePerUserReleased = useFlag('authSingleSessionPerUserReleased')
const passwordStrengthReleased = useFlag('authPasswordStrengthReleased')
const INITIAL_VALUES = {
DISABLE_SIGNUP: !authConfig?.DISABLE_SIGNUP,
@@ -100,11 +109,23 @@ const BasicAuthSettingsForm = observer(() => {
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,
}
: null),
}
const onSubmit = (values: any, { resetForm }: any) => {
const payload = { ...values }
payload.DISABLE_SIGNUP = !values.DISABLE_SIGNUP
// The backend uses empty string to represent no required characters in the password
if (payload.PASSWORD_REQUIRED_CHARACTERS === NO_REQUIRED_CHARACTERS) {
payload.PASSWORD_REQUIRED_CHARACTERS = ''
}
updateAuthConfig(
{ projectRef: projectRef!, config: payload },
@@ -184,6 +205,56 @@ const BasicAuthSettingsForm = observer(() => {
</FormSectionContent>
</FormSection>
<div className="border-t border-muted"></div>
{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}
/>
<>
<Radio.Group
id="PASSWORD_REQUIRED_CHARACTERS"
name="PASSWORD_REQUIRED_CHARACTERS"
label="Required characters"
descriptionText="Passwords that do not have at least one of each will be rejected as weak."
>
<Radio
label="No required characters"
value={NO_REQUIRED_CHARACTERS}
checked={values.PASSWORD_REQUIRED_CHARACTERS === NO_REQUIRED_CHARACTERS}
description="(default)"
/>
<Radio
label="Letters and digits"
value={LETTERS_AND_DIGITS}
checked={values.PASSWORD_REQUIRED_CHARACTERS === LETTERS_AND_DIGITS}
/>
<Radio
label="Lowercase, uppercase letters and digits"
value={LOWER_UPPER_DIGITS}
checked={values.PASSWORD_REQUIRED_CHARACTERS === LOWER_UPPER_DIGITS}
/>
<Radio
label="Lowercase, uppercase letters, digits and symbols"
value={LOWER_UPPER_DIGITS_SYMBOLS}
checked={
values.PASSWORD_REQUIRED_CHARACTERS === LOWER_UPPER_DIGITS_SYMBOLS
}
description="(recommended)"
/>
</Radio.Group>
</>
</FormSectionContent>
</FormSection>
<div className="border-t border-muted"></div>
</>
)}
<FormSection header={<FormSectionLabel>User Sessions</FormSectionLabel>}>
<FormSectionContent loading={isLoading}>
{isProPlanAndUp ? (