remove char and digit rangs and other requested changes/improvments

This commit is contained in:
Maidul Islam
2025-03-11 12:28:48 -04:00
parent 16d3bbb67a
commit 6fa41a609b
4 changed files with 168 additions and 275 deletions

View File

@@ -1,20 +1,14 @@
import { z } from "zod"; import { z } from "zod";
export type PasswordRequirements = { export type PasswordRequirements = {
minLength: number; length: number;
maxLength: number;
required: { required: {
lowercase: number; lowercase: number;
uppercase: number; uppercase: number;
digits: number; digits: number;
symbols: number; symbols: number;
}; };
allowedCharacters?: { allowedSymbols?: string;
lowercase?: string;
uppercase?: string;
digits?: string;
symbols?: string;
};
}; };
export enum SqlProviders { export enum SqlProviders {
@@ -119,23 +113,22 @@ export const DynamicSecretSqlDBSchema = z.object({
password: z.string().trim(), password: z.string().trim(),
passwordRequirements: z passwordRequirements: z
.object({ .object({
minLength: z.number().min(1).max(100), length: z.number().min(1).max(250),
maxLength: z.number().min(1).max(100),
required: z.object({ required: z.object({
lowercase: z.number().min(0), lowercase: z.number().min(0),
uppercase: z.number().min(0), uppercase: z.number().min(0),
digits: z.number().min(0), digits: z.number().min(0),
symbols: z.number().min(0) symbols: z.number().min(0)
}), }).refine((data) => {
allowedCharacters: z const total = Object.values(data).reduce((sum, count) => sum + count, 0);
.object({ return total <= 250;
lowercase: z.string().optional(), }, "Sum of required characters cannot exceed 250"),
uppercase: z.string().optional(), allowedSymbols: z.string().optional()
digits: z.string().optional(),
symbols: z.string().optional()
})
.optional()
}) })
.refine((data) => {
const total = Object.values(data.required).reduce((sum, count) => sum + count, 0);
return total <= data.length;
}, "Sum of required characters cannot exceed the total length")
.optional() .optional()
.describe('Password generation requirements'), .describe('Password generation requirements'),
creationStatement: z.string().trim(), creationStatement: z.string().trim(),

View File

@@ -2,6 +2,7 @@ import handlebars from "handlebars";
import knex from "knex"; import knex from "knex";
import { customAlphabet } from "nanoid"; import { customAlphabet } from "nanoid";
import { z } from "zod"; import { z } from "zod";
import { randomInt } from 'crypto';
import { withGatewayProxy } from "@app/lib/gateway"; import { withGatewayProxy } from "@app/lib/gateway";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
@@ -13,26 +14,19 @@ import { DynamicSecretSqlDBSchema, PasswordRequirements, SqlProviders, TDynamicP
const EXTERNAL_REQUEST_TIMEOUT = 10 * 1000; const EXTERNAL_REQUEST_TIMEOUT = 10 * 1000;
const DEFAULT_PASSWORD_REQUIREMENTS = { const DEFAULT_PASSWORD_REQUIREMENTS = {
minLength: 48, length: 48,
maxLength: 48,
required: { required: {
lowercase: 1, lowercase: 1,
uppercase: 1, uppercase: 1,
digits: 1, digits: 1,
symbols: 0 symbols: 0
}, },
allowedCharacters: { allowedSymbols: '-_.~!*'
lowercase: 'abcdefghijklmnopqrstuvwxyz',
uppercase: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
digits: '0123456789',
symbols: '-_.~!*'
}
}; };
const ORACLE_PASSWORD_REQUIREMENTS = { const ORACLE_PASSWORD_REQUIREMENTS = {
...DEFAULT_PASSWORD_REQUIREMENTS, ...DEFAULT_PASSWORD_REQUIREMENTS,
minLength: 30, length: 30
maxLength: 30
}; };
const generatePassword = (provider: SqlProviders, requirements?: PasswordRequirements) => { const generatePassword = (provider: SqlProviders, requirements?: PasswordRequirements) => {
@@ -40,52 +34,56 @@ const generatePassword = (provider: SqlProviders, requirements?: PasswordRequire
const finalReqs = requirements || defaultReqs; const finalReqs = requirements || defaultReqs;
try { try {
const { minLength, maxLength, required, allowedCharacters = {} } = finalReqs; const { length, required, allowedSymbols } = finalReqs;
const chars = { const chars = {
lowercase: allowedCharacters.lowercase || 'abcdefghijklmnopqrstuvwxyz', lowercase: 'abcdefghijklmnopqrstuvwxyz',
uppercase: allowedCharacters.uppercase || 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', uppercase: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
digits: allowedCharacters.digits || '0123456789', digits: '0123456789',
symbols: allowedCharacters.symbols || '-_.~!*' symbols: allowedSymbols || '-_.~!*'
}; };
const parts: string[] = []; const parts: string[] = [];
if (required.lowercase > 0) { if (required.lowercase > 0) {
parts.push(...Array(required.lowercase).fill(0).map(() => parts.push(...Array(required.lowercase).fill(0).map(() =>
chars.lowercase[Math.floor(Math.random() * chars.lowercase.length)] chars.lowercase[randomInt(chars.lowercase.length)]
)); ));
} }
if (required.uppercase > 0) { if (required.uppercase > 0) {
parts.push(...Array(required.uppercase).fill(0).map(() => parts.push(...Array(required.uppercase).fill(0).map(() =>
chars.uppercase[Math.floor(Math.random() * chars.uppercase.length)] chars.uppercase[randomInt(chars.uppercase.length)]
)); ));
} }
if (required.digits > 0) { if (required.digits > 0) {
parts.push(...Array(required.digits).fill(0).map(() => parts.push(...Array(required.digits).fill(0).map(() =>
chars.digits[Math.floor(Math.random() * chars.digits.length)] chars.digits[randomInt(chars.digits.length)]
)); ));
} }
if (required.symbols > 0) { if (required.symbols > 0) {
parts.push(...Array(required.symbols).fill(0).map(() => parts.push(...Array(required.symbols).fill(0).map(() =>
chars.symbols[Math.floor(Math.random() * chars.symbols.length)] chars.symbols[randomInt(chars.symbols.length)]
)); ));
} }
const requiredTotal = Object.values(required).reduce<number>((a, b) => a + b, 0); const requiredTotal = Object.values(required).reduce<number>((a, b) => a + b, 0);
const remainingLength = Math.max(minLength - requiredTotal, 0); const remainingLength = Math.max(length - requiredTotal, 0);
const allowedChars = Object.entries(chars)
.filter(([key]) => required[key as keyof typeof required] > 0)
.map(([, value]) => value)
.join('');
const allChars = Object.values(chars).join('');
parts.push(...Array(remainingLength).fill(0).map(() => parts.push(...Array(remainingLength).fill(0).map(() =>
allChars[Math.floor(Math.random() * allChars.length)] allowedChars[randomInt(allowedChars.length)]
)); ));
// shuffle the array to mix up the characters // shuffle the array to mix up the characters
for (let i = parts.length - 1; i > 0; i--) { for (let i = parts.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1)); const j = randomInt(i + 1);
[parts[i], parts[j]] = [parts[j], parts[i]]; [parts[i], parts[j]] = [parts[j], parts[i]];
} }

View File

@@ -24,23 +24,21 @@ import { gatewaysQueryKeys, useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders, SqlProviders } from "@app/hooks/api/dynamicSecret/types"; import { DynamicSecretProviders, SqlProviders } from "@app/hooks/api/dynamicSecret/types";
const passwordRequirementsSchema = z.object({ const passwordRequirementsSchema = z.object({
minLength: z.number().min(1).max(100), length: z.number().min(1).max(250),
maxLength: z.number().min(1).max(100),
required: z.object({ required: z.object({
lowercase: z.number().min(0), lowercase: z.number().min(0),
uppercase: z.number().min(0), uppercase: z.number().min(0),
digits: z.number().min(0), digits: z.number().min(0),
symbols: z.number().min(0) symbols: z.number().min(0)
}), }).refine((data) => {
allowedCharacters: z const total = Object.values(data).reduce((sum, count) => sum + count, 0);
.object({ return total <= 250;
lowercase: z.string().optional(), }, "Sum of required characters cannot exceed 250"),
uppercase: z.string().optional(), allowedSymbols: z.string().optional()
digits: z.string().optional(), }).refine((data) => {
symbols: z.string().optional() const total = Object.values(data.required).reduce((sum, count) => sum + count, 0);
}) return total <= data.length;
.optional() }, "Sum of required characters cannot exceed the total length");
});
const formSchema = z.object({ const formSchema = z.object({
provider: z.object({ provider: z.object({
@@ -153,21 +151,22 @@ export const SqlDatabaseInputForm = ({
control, control,
setValue, setValue,
formState: { isSubmitting }, formState: { isSubmitting },
handleSubmit handleSubmit,
watch
} = useForm<TForm>({ } = useForm<TForm>({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
defaultValues: { defaultValues: {
provider: { provider: {
...getSqlStatements(SqlProviders.Postgres), ...getSqlStatements(SqlProviders.Postgres),
passwordRequirements: { passwordRequirements: {
minLength: 48, length: 48,
maxLength: 48,
required: { required: {
lowercase: 1, lowercase: 1,
uppercase: 1, uppercase: 1,
digits: 1, digits: 1,
symbols: 0 symbols: 0
} },
allowedSymbols: '-_.~!*'
} }
} }
} }
@@ -208,9 +207,8 @@ export const SqlDatabaseInputForm = ({
setValue("provider.port", getDefaultPort(type)); setValue("provider.port", getDefaultPort(type));
// Update password requirements based on provider // Update password requirements based on provider
const minMaxLength = type === SqlProviders.Oracle ? 30 : 48; const length = type === SqlProviders.Oracle ? 30 : 48;
setValue("provider.passwordRequirements.minLength", minMaxLength); setValue("provider.passwordRequirements.length", length);
setValue("provider.passwordRequirements.maxLength", minMaxLength);
}; };
return ( return (
@@ -499,41 +497,21 @@ export const SqlDatabaseInputForm = ({
Set constraints on the generated database password Set constraints on the generated database password
</div> </div>
<div className="space-y-4"> <div className="space-y-4">
<div className="grid grid-cols-2 gap-4"> <div>
<Controller <Controller
control={control} control={control}
name="provider.passwordRequirements.minLength" name="provider.passwordRequirements.length"
defaultValue={48} defaultValue={48}
render={({ field, fieldState: { error } }) => ( render={({ field, fieldState: { error } }) => (
<FormControl <FormControl
label="Minimum Length" label="Password Length"
isError={Boolean(error)} isError={Boolean(error)}
errorText={error?.message} errorText={error?.message}
> >
<Input <Input
type="number" type="number"
min={1} min={1}
max={100} max={250}
{...field}
onChange={(e) => field.onChange(Number(e.target.value))}
/>
</FormControl>
)}
/>
<Controller
control={control}
name="provider.passwordRequirements.maxLength"
defaultValue={48}
render={({ field, fieldState: { error } }) => (
<FormControl
label="Maximum Length"
isError={Boolean(error)}
errorText={error?.message}
>
<Input
type="number"
min={1}
max={100}
{...field} {...field}
onChange={(e) => field.onChange(Number(e.target.value))} onChange={(e) => field.onChange(Number(e.target.value))}
/> />
@@ -543,7 +521,19 @@ export const SqlDatabaseInputForm = ({
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<h4 className="text-sm font-medium">Required Characters</h4> <h4 className="text-sm font-medium">Minimum Required Character Counts</h4>
<div className="text-sm text-gray-500">
{(() => {
const total = Object.values(watch("provider.passwordRequirements.required") || {}).reduce((sum, count) => sum + Number(count || 0), 0);
const length = watch("provider.passwordRequirements.length") || 0;
const isError = total > length;
return (
<span className={isError ? "text-red-500" : ""}>
Total required characters: {total} {isError ? `(exceeds length of ${length})` : ""}
</span>
);
})()}
</div>
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<Controller <Controller
control={control} control={control}
@@ -551,9 +541,10 @@ export const SqlDatabaseInputForm = ({
defaultValue={1} defaultValue={1}
render={({ field, fieldState: { error } }) => ( render={({ field, fieldState: { error } }) => (
<FormControl <FormControl
label="Lowercase" label="Lowercase Count"
isError={Boolean(error)} isError={Boolean(error)}
errorText={error?.message} errorText={error?.message}
helperText="Minimum number of lowercase letters"
> >
<Input <Input
type="number" type="number"
@@ -570,9 +561,10 @@ export const SqlDatabaseInputForm = ({
defaultValue={1} defaultValue={1}
render={({ field, fieldState: { error } }) => ( render={({ field, fieldState: { error } }) => (
<FormControl <FormControl
label="Uppercase" label="Uppercase Count"
isError={Boolean(error)} isError={Boolean(error)}
errorText={error?.message} errorText={error?.message}
helperText="Minimum number of uppercase letters"
> >
<Input <Input
type="number" type="number"
@@ -589,9 +581,10 @@ export const SqlDatabaseInputForm = ({
defaultValue={1} defaultValue={1}
render={({ field, fieldState: { error } }) => ( render={({ field, fieldState: { error } }) => (
<FormControl <FormControl
label="Digits" label="Digit Count"
isError={Boolean(error)} isError={Boolean(error)}
errorText={error?.message} errorText={error?.message}
helperText="Minimum number of digits"
> >
<Input <Input
type="number" type="number"
@@ -608,9 +601,10 @@ export const SqlDatabaseInputForm = ({
defaultValue={0} defaultValue={0}
render={({ field, fieldState: { error } }) => ( render={({ field, fieldState: { error } }) => (
<FormControl <FormControl
label="Symbols" label="Symbol Count"
isError={Boolean(error)} isError={Boolean(error)}
errorText={error?.message} errorText={error?.message}
helperText="Minimum number of symbols"
> >
<Input <Input
type="number" type="number"
@@ -625,65 +619,22 @@ export const SqlDatabaseInputForm = ({
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<h4 className="text-sm font-medium">Allowed Characters (Optional)</h4> <h4 className="text-sm font-medium">Allowed Symbols</h4>
<div className="grid grid-cols-2 gap-4"> <Controller
<Controller control={control}
control={control} name="provider.passwordRequirements.allowedSymbols"
name="provider.passwordRequirements.allowedCharacters.lowercase" defaultValue="-_.~!*"
render={({ field, fieldState: { error } }) => ( render={({ field, fieldState: { error } }) => (
<FormControl <FormControl
label="Lowercase Characters" label="Symbols to use in password"
isError={Boolean(error)} isError={Boolean(error)}
errorText={error?.message} errorText={error?.message}
helperText="Default: a-z" helperText="Default: -_.~!*"
> >
<Input {...field} placeholder="abcdefghijklmnopqrstuvwxyz" /> <Input {...field} placeholder="-_.~!*" />
</FormControl> </FormControl>
)} )}
/> />
<Controller
control={control}
name="provider.passwordRequirements.allowedCharacters.uppercase"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Uppercase Characters"
isError={Boolean(error)}
errorText={error?.message}
helperText="Default: A-Z"
>
<Input {...field} placeholder="ABCDEFGHIJKLMNOPQRSTUVWXYZ" />
</FormControl>
)}
/>
<Controller
control={control}
name="provider.passwordRequirements.allowedCharacters.digits"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Digit Characters"
isError={Boolean(error)}
errorText={error?.message}
helperText="Default: 0-9"
>
<Input {...field} placeholder="0123456789" />
</FormControl>
)}
/>
<Controller
control={control}
name="provider.passwordRequirements.allowedCharacters.symbols"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Symbol Characters"
isError={Boolean(error)}
errorText={error?.message}
helperText="Default: -_.~!*"
>
<Input {...field} placeholder="-_.~!*" />
</FormControl>
)}
/>
</div>
</div> </div>
</div> </div>
</AccordionContent> </AccordionContent>

View File

@@ -24,23 +24,21 @@ import { gatewaysQueryKeys, useUpdateDynamicSecret } from "@app/hooks/api";
import { SqlProviders, TDynamicSecret } from "@app/hooks/api/dynamicSecret/types"; import { SqlProviders, TDynamicSecret } from "@app/hooks/api/dynamicSecret/types";
const passwordRequirementsSchema = z.object({ const passwordRequirementsSchema = z.object({
minLength: z.number().min(1).max(100), length: z.number().min(1).max(250),
maxLength: z.number().min(1).max(100),
required: z.object({ required: z.object({
lowercase: z.number().min(0), lowercase: z.number().min(0),
uppercase: z.number().min(0), uppercase: z.number().min(0),
digits: z.number().min(0), digits: z.number().min(0),
symbols: z.number().min(0) symbols: z.number().min(0)
}), }).refine((data) => {
allowedCharacters: z const total = Object.values(data).reduce((sum, count) => sum + count, 0);
.object({ return total <= 250; // Sanity check for individual validation
lowercase: z.string().optional(), }, "Sum of required characters cannot exceed 250"),
uppercase: z.string().optional(), allowedSymbols: z.string().optional()
digits: z.string().optional(), }).refine((data) => {
symbols: z.string().optional() const total = Object.values(data.required).reduce((sum, count) => sum + count, 0);
}) return total <= data.length;
.optional() }, "Sum of required characters cannot exceed the total length");
});
const formSchema = z.object({ const formSchema = z.object({
inputs: z inputs: z
@@ -103,14 +101,14 @@ export const EditDynamicSecretSqlProviderForm = ({
projectSlug projectSlug
}: Props) => { }: Props) => {
const getDefaultPasswordRequirements = (provider: SqlProviders) => ({ const getDefaultPasswordRequirements = (provider: SqlProviders) => ({
minLength: provider === SqlProviders.Oracle ? 30 : 48, length: provider === SqlProviders.Oracle ? 30 : 48,
maxLength: provider === SqlProviders.Oracle ? 30 : 48,
required: { required: {
lowercase: 1, lowercase: 1,
uppercase: 1, uppercase: 1,
digits: 1, digits: 1,
symbols: 0 symbols: 0
} },
allowedSymbols: '-_.~!*'
}); });
const { const {
@@ -463,41 +461,21 @@ export const EditDynamicSecretSqlProviderForm = ({
Set constraints on the generated database password Set constraints on the generated database password
</div> </div>
<div className="space-y-4"> <div className="space-y-4">
<div className="grid grid-cols-2 gap-4"> <div>
<Controller <Controller
control={control} control={control}
name="inputs.passwordRequirements.minLength" name="inputs.passwordRequirements.length"
defaultValue={48} defaultValue={48}
render={({ field, fieldState: { error } }) => ( render={({ field, fieldState: { error } }) => (
<FormControl <FormControl
label="Minimum Length" label="Password Length"
isError={Boolean(error)} isError={Boolean(error)}
errorText={error?.message} errorText={error?.message}
> >
<Input <Input
type="number" type="number"
min={1} min={1}
max={100} max={250}
{...field}
onChange={(e) => field.onChange(Number(e.target.value))}
/>
</FormControl>
)}
/>
<Controller
control={control}
name="inputs.passwordRequirements.maxLength"
defaultValue={48}
render={({ field, fieldState: { error } }) => (
<FormControl
label="Maximum Length"
isError={Boolean(error)}
errorText={error?.message}
>
<Input
type="number"
min={1}
max={100}
{...field} {...field}
onChange={(e) => field.onChange(Number(e.target.value))} onChange={(e) => field.onChange(Number(e.target.value))}
/> />
@@ -507,7 +485,19 @@ export const EditDynamicSecretSqlProviderForm = ({
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<h4 className="text-sm font-medium">Required Characters</h4> <h4 className="text-sm font-medium">Minimum Required Character Counts</h4>
<div className="text-sm text-gray-500">
{(() => {
const total = Object.values(watch("inputs.passwordRequirements.required") || {}).reduce((sum, count) => sum + Number(count || 0), 0);
const length = watch("inputs.passwordRequirements.length") || 0;
const isError = total > length;
return (
<span className={isError ? "text-red-500" : ""}>
Total required characters: {total} {isError ? `(exceeds length of ${length})` : ""}
</span>
);
})()}
</div>
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<Controller <Controller
control={control} control={control}
@@ -515,16 +505,17 @@ export const EditDynamicSecretSqlProviderForm = ({
defaultValue={1} defaultValue={1}
render={({ field, fieldState: { error } }) => ( render={({ field, fieldState: { error } }) => (
<FormControl <FormControl
label="Lowercase" label="Lowercase Count"
isError={Boolean(error)} isError={Boolean(error)}
errorText={error?.message} errorText={error?.message}
helperText="Minimum number of lowercase letters"
> >
<Input <Input
type="number" type="number"
min={0} min={0}
{...field} {...field}
onChange={(e) => field.onChange(Number(e.target.value))} onChange={(e) => field.onChange(Number(e.target.value))}
/> />
</FormControl> </FormControl>
)} )}
/> />
@@ -534,16 +525,17 @@ export const EditDynamicSecretSqlProviderForm = ({
defaultValue={1} defaultValue={1}
render={({ field, fieldState: { error } }) => ( render={({ field, fieldState: { error } }) => (
<FormControl <FormControl
label="Uppercase" label="Uppercase Count"
isError={Boolean(error)} isError={Boolean(error)}
errorText={error?.message} errorText={error?.message}
helperText="Minimum number of uppercase letters"
> >
<Input <Input
type="number" type="number"
min={0} min={0}
{...field} {...field}
onChange={(e) => field.onChange(Number(e.target.value))} onChange={(e) => field.onChange(Number(e.target.value))}
/> />
</FormControl> </FormControl>
)} )}
/> />
@@ -553,16 +545,17 @@ export const EditDynamicSecretSqlProviderForm = ({
defaultValue={1} defaultValue={1}
render={({ field, fieldState: { error } }) => ( render={({ field, fieldState: { error } }) => (
<FormControl <FormControl
label="Digits" label="Digit Count"
isError={Boolean(error)} isError={Boolean(error)}
errorText={error?.message} errorText={error?.message}
helperText="Minimum number of digits"
> >
<Input <Input
type="number" type="number"
min={0} min={0}
{...field} {...field}
onChange={(e) => field.onChange(Number(e.target.value))} onChange={(e) => field.onChange(Number(e.target.value))}
/> />
</FormControl> </FormControl>
)} )}
/> />
@@ -572,16 +565,17 @@ export const EditDynamicSecretSqlProviderForm = ({
defaultValue={0} defaultValue={0}
render={({ field, fieldState: { error } }) => ( render={({ field, fieldState: { error } }) => (
<FormControl <FormControl
label="Symbols" label="Symbol Count"
isError={Boolean(error)} isError={Boolean(error)}
errorText={error?.message} errorText={error?.message}
helperText="Minimum number of symbols"
> >
<Input <Input
type="number" type="number"
min={0} min={0}
{...field} {...field}
onChange={(e) => field.onChange(Number(e.target.value))} onChange={(e) => field.onChange(Number(e.target.value))}
/> />
</FormControl> </FormControl>
)} )}
/> />
@@ -589,65 +583,22 @@ export const EditDynamicSecretSqlProviderForm = ({
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<h4 className="text-sm font-medium">Allowed Characters (Optional)</h4> <h4 className="text-sm font-medium">Allowed Symbols</h4>
<div className="grid grid-cols-2 gap-4"> <Controller
<Controller control={control}
control={control} name="inputs.passwordRequirements.allowedSymbols"
name="inputs.passwordRequirements.allowedCharacters.lowercase" defaultValue="-_.~!*"
render={({ field, fieldState: { error } }) => ( render={({ field, fieldState: { error } }) => (
<FormControl <FormControl
label="Lowercase Characters" label="Symbols to use in password"
isError={Boolean(error)} isError={Boolean(error)}
errorText={error?.message} errorText={error?.message}
helperText="Default: a-z" helperText="Default: -_.~!*"
> >
<Input {...field} placeholder="abcdefghijklmnopqrstuvwxyz" /> <Input {...field} placeholder="-_.~!*" />
</FormControl> </FormControl>
)} )}
/> />
<Controller
control={control}
name="inputs.passwordRequirements.allowedCharacters.uppercase"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Uppercase Characters"
isError={Boolean(error)}
errorText={error?.message}
helperText="Default: A-Z"
>
<Input {...field} placeholder="ABCDEFGHIJKLMNOPQRSTUVWXYZ" />
</FormControl>
)}
/>
<Controller
control={control}
name="inputs.passwordRequirements.allowedCharacters.digits"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Digit Characters"
isError={Boolean(error)}
errorText={error?.message}
helperText="Default: 0-9"
>
<Input {...field} placeholder="0123456789" />
</FormControl>
)}
/>
<Controller
control={control}
name="inputs.passwordRequirements.allowedCharacters.symbols"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Symbol Characters"
isError={Boolean(error)}
errorText={error?.message}
helperText="Default: -_.~!*"
>
<Input {...field} placeholder="-_.~!*" />
</FormControl>
)}
/>
</div>
</div> </div>
</div> </div>
</AccordionContent> </AccordionContent>