Skip to content

Commit

Permalink
Add maxFailedAttemptCount to recovery form and restructure the form.
Browse files Browse the repository at this point in the history
  • Loading branch information
RushanNanayakkara committed Apr 23, 2024
1 parent 6e1d095 commit 8191574
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 28 deletions.
4 changes: 3 additions & 1 deletion apps/console/src/extensions/i18n/models/extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3039,7 +3039,8 @@ export interface Extensions {
enableEmailBasedRecovery: FormAttributes;
expiryTime: FormAttributes;
notifySuccess: FormAttributes;
otpMaxResendCount: FormAttributes;
maxResendCount: FormAttributes;
maxFailedAttemptCount: FormAttributes;
smsOtpExpiryTime: FormAttributes;
passwordRecoveryOtpUseUppercase: FormAttributes;
passwordRecoveryOtpUseLowercase: FormAttributes;
Expand All @@ -3051,6 +3052,7 @@ export interface Extensions {
recoveryOptionSubHeadingSMS: string;
recoveryOptionHeading: string;
otpConfigHeading: string;
otherConfigHeading: string;
connectorDescription: string;
heading: string;
notification: {
Expand Down
20 changes: 17 additions & 3 deletions apps/console/src/extensions/i18n/resources/en-US/extensions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3578,9 +3578,9 @@ export const extensions: Extensions = {
"recovery is successful.",
label: "Notify on successful recovery"
},
otpMaxResendCount: {
hint: "Password recovery OTP maximum resend count.",
label: "Max resend count",
maxResendCount: {
hint: "Password recovery maximum resend count.",
label: "Max resend attempts count",
placeholder: "Enter max resend count",
validations: {
invalid: "Password recovery OTP resend count should be an integer.",
Expand All @@ -3591,6 +3591,19 @@ export const extensions: Extensions = {
"Password recovery OTP resend count should be a number with 1 digits."
}
},
maxFailedAttemptCount: {
hint: "Password recovery maximum failed attempt count.",
label: "Max failed attempts count",
placeholder: "Enter max failed attempts",
validations: {
invalid: "Password recovery max failed attempts count should be an integer.",
empty: "Password recovery max failed attempts count cannot be empty.",
range:
"Password recovery max failed attempts count should be between 1 & 10.",
maxLengthReached:
"Password recovery max failed attempts count should be a number with less than 3 digits."
}
},
smsOtpExpiryTime: {
hint: "Password recovery OTP expiry time in minutes.",
label: "Password recovery OTP expiry time",
Expand Down Expand Up @@ -3637,6 +3650,7 @@ export const extensions: Extensions = {
recoveryOptionSubHeadingSMS: "SMS OTP",
recoveryOptionHeading: "Recovery Option Selection",
otpConfigHeading: "OTP Code Configuration",
otherConfigHeading: "Other Configuration",
connectorDescription: "Enable self-service password recovery for users " + "on the login page.",
heading: "Password Recovery",
notification: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ export class GovernanceConnectorConstants {
EXPIRY_TIME_MAX_VALUE: number;
EXPIRY_TIME_MIN_LENGTH: number;
EXPIRY_TIME_MIN_VALUE: number;
MAX_FAILED_ATTEMPT_COUNT_MIN_LENGTH: number;
MAX_FAILED_ATTEMPT_COUNT_MAX_LENGTH: number;
MAX_FAILED_ATTEMPT_COUNT_MIN_VALUE: number;
MAX_FAILED_ATTEMPT_COUNT_MAX_VALUE: number;
MAX_RESEND_COUNT_MIN_LENGTH: number;
MAX_RESEND_COUNT_MAX_LENGTH: number;
MAX_RESEND_COUNT_MIN_VALUE: number;
Expand All @@ -76,6 +80,10 @@ export class GovernanceConnectorConstants {
EXPIRY_TIME_MAX_VALUE: 10080,
EXPIRY_TIME_MIN_LENGTH: 1,
EXPIRY_TIME_MIN_VALUE: 1,
MAX_FAILED_ATTEMPT_COUNT_MAX_LENGTH: 2,
MAX_FAILED_ATTEMPT_COUNT_MAX_VALUE: 10,
MAX_FAILED_ATTEMPT_COUNT_MIN_LENGTH: 1,
MAX_FAILED_ATTEMPT_COUNT_MIN_VALUE: 1,
MAX_RESEND_COUNT_MAX_LENGTH: 1,
MAX_RESEND_COUNT_MAX_VALUE: 5,
MAX_RESEND_COUNT_MIN_LENGTH: 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,9 @@ export class ServerConfigurationsConstants {

public static readonly RECOVERY_OTP_USE_NUMERIC: string = "Recovery.Notification.Password.OTP.UseNumbersInOTP";
public static readonly RECOVERY_OTP_LENGTH: string = "Recovery.Notification.Password.OTP.OTPLength";
public static readonly RECOVERY_OTP_MAX_RESEND_COUNT: string = "Recovery.Notification.Password.MaxResendAttempts";
public static readonly RECOVERY_MAX_RESEND_COUNT: string = "Recovery.Notification.Password.MaxResendAttempts";
public static readonly RECOVERY_MAX_FAILED_ATTEMPTS_COUNT: string =
"Recovery.Notification.Password.MaxFailedAttempts";

/**
* Login policies - account locking API Keyword constants.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,13 @@ interface PasswordRecoveryFormInitialValuesInterface {
*/
smsOtpLength: string;
/**
* The maximum amount of times otp is resent.
* The maximum amount of times recovery code/link is resent.
*/
otpMaxResendCount: string;
maxResendCount: string;
/**
* The maximum allowed failed attempts for a recovery flow.
*/
maxFailedAttemptCount: string;
}

/**
Expand All @@ -122,9 +126,13 @@ export interface PasswordRecoveryFormErrorValidationsInterface {
*/
smsOtpLength: string;
/**
* OTP max resend count field.
* max resend count field.
*/
maxResendCount: string;
/**
* Max allowed failed attempts count field.
*/
otpMaxResendCount: string;
maxFailedAttemptCount: string;
}

const allowedConnectorFields: string[] = [
Expand All @@ -137,7 +145,8 @@ const allowedConnectorFields: string[] = [
ServerConfigurationsConstants.RECOVERY_OTP_USE_LOWERCASE,
ServerConfigurationsConstants.RECOVERY_OTP_USE_NUMERIC,
ServerConfigurationsConstants.RECOVERY_OTP_LENGTH,
ServerConfigurationsConstants.RECOVERY_OTP_MAX_RESEND_COUNT
ServerConfigurationsConstants.RECOVERY_MAX_RESEND_COUNT,
ServerConfigurationsConstants.RECOVERY_MAX_FAILED_ATTEMPTS_COUNT
];

const FORM_ID: string = "governance-connectors-password-recovery-form";
Expand Down Expand Up @@ -228,10 +237,15 @@ export const PasswordRecoveryConfigurationForm: FunctionComponent<PasswordRecove
...resolvedInitialValues,
smsOtpLength: property.value
};
} else if (property.name === ServerConfigurationsConstants.RECOVERY_OTP_MAX_RESEND_COUNT) {
} else if (property.name === ServerConfigurationsConstants.RECOVERY_MAX_RESEND_COUNT) {
resolvedInitialValues = {
...resolvedInitialValues,
maxResendCount : property.value
};
} else if (property.name === ServerConfigurationsConstants.RECOVERY_MAX_FAILED_ATTEMPTS_COUNT) {
resolvedInitialValues = {
...resolvedInitialValues,
otpMaxResendCount : property.value
maxFailedAttemptCount : property.value
};
}
}
Expand All @@ -254,7 +268,8 @@ export const PasswordRecoveryConfigurationForm: FunctionComponent<PasswordRecove
PasswordRecoveryFormErrorValidationsInterface => {
const errors: PasswordRecoveryFormErrorValidationsInterface = {
expiryTime: undefined,
otpMaxResendCount: undefined,
maxFailedAttemptCount: undefined,
maxResendCount: undefined,
smsOtpExpiryTime: undefined,
smsOtpLength: undefined
};
Expand Down Expand Up @@ -312,17 +327,28 @@ export const PasswordRecoveryConfigurationForm: FunctionComponent<PasswordRecove
// Check for invalid input length.
errors.smsOtpLength = t("extensions:manage.serverConfigurations.accountRecovery." +
"passwordRecovery.form.fields.smsOtpLength.validations.maxLengthReached");
} else if (!values.otpMaxResendCount) {
} else if (!values.maxResendCount) {
// Check for required error
errors.smsOtpLength = t("extensions:manage.serverConfigurations.accountRecovery." +
"passwordRecovery.form.fields.otpMaxResendCount.validations.empty");
} else if (parseInt(values.otpMaxResendCount, 10) < GovernanceConnectorConstants
"passwordRecovery.form.fields.maxResendCount.validations.empty");
} else if (parseInt(values.maxResendCount, 10) < GovernanceConnectorConstants
.PASSWORD_RECOVERY_FORM_FIELD_CONSTRAINTS.MAX_RESEND_COUNT_MIN_VALUE ||
parseInt(values.otpMaxResendCount, 10) > GovernanceConnectorConstants
parseInt(values.maxResendCount, 10) > GovernanceConnectorConstants
.PASSWORD_RECOVERY_FORM_FIELD_CONSTRAINTS.MAX_RESEND_COUNT_MAX_VALUE) {
// Check for invalid input length.
errors.otpMaxResendCount = t("extensions:manage.serverConfigurations.accountRecovery." +
"passwordRecovery.form.fields.otpMaxResendCount.validations.maxLengthReached");
// Check for invalid range.
errors.maxResendCount = t("extensions:manage.serverConfigurations.accountRecovery." +
"passwordRecovery.form.fields.maxResendCount.validations.range");
} else if (!values.maxFailedAttemptCount) {
// Check for required error
errors.maxFailedAttemptCount = t("extensions:manage.serverConfigurations.accountRecovery." +
"passwordRecovery.form.fields.maxFailedAttemptCount.validations.empty");
} else if (parseInt(values.maxFailedAttemptCount, 10) < GovernanceConnectorConstants
.PASSWORD_RECOVERY_FORM_FIELD_CONSTRAINTS.MAX_FAILED_ATTEMPT_COUNT_MIN_VALUE ||
parseInt(values.maxFailedAttemptCount, 10) > GovernanceConnectorConstants
.PASSWORD_RECOVERY_FORM_FIELD_CONSTRAINTS.MAX_FAILED_ATTEMPT_COUNT_MAX_VALUE) {
// Check for invalid range.
errors.maxFailedAttemptCount = t("extensions:manage.serverConfigurations.accountRecovery." +
"passwordRecovery.form.fields.maxFailedAttemptCount.validations.range");
}

return errors;
Expand All @@ -339,6 +365,7 @@ export const PasswordRecoveryConfigurationForm: FunctionComponent<PasswordRecove
"Recovery.ExpiryTime": any;
"Recovery.Notification.Password.emailLink.Enable": boolean;
"Recovery.Notification.Password.ExpiryTime.smsOtp": number;
"Recovery.Notification.Password.MaxFailedAttempts": number;
"Recovery.Notification.Password.MaxResendAttempts": number;
"Recovery.Notification.Password.smsOtp.Enable": boolean;
"Recovery.Notification.Password.OTP.UseUppercaseCharactersInOTP": number;
Expand All @@ -353,9 +380,12 @@ export const PasswordRecoveryConfigurationForm: FunctionComponent<PasswordRecove
"Recovery.Notification.Password.ExpiryTime.smsOtp": values.smsOtpExpiryTime !== undefined
? values.smsOtpExpiryTime
: initialConnectorValues?.smsOtpExpiryTime,
"Recovery.Notification.Password.MaxResendAttempts": values.otpMaxResendCount !== undefined
? values.otpMaxResendCount
: initialConnectorValues?.otpMaxResendCount,
"Recovery.Notification.Password.MaxFailedAttempts": values.maxFailedAttemptCount !== undefined
? values.maxFailedAttemptCount
: initialConnectorValues?.maxFailedAttemptCount,
"Recovery.Notification.Password.MaxResendAttempts": values.maxResendCount !== undefined
? values.maxResendCount
: initialConnectorValues?.maxResendCount,
"Recovery.Notification.Password.OTP.OTPLength": values.smsOtpLength !== undefined
? values.smsOtpLength
: initialConnectorValues?.smsOtpLength,
Expand Down Expand Up @@ -639,10 +669,55 @@ export const PasswordRecoveryConfigurationForm: FunctionComponent<PasswordRecove
{ t("extensions:manage.serverConfigurations.accountRecovery." +
"passwordRecovery.form.fields.passwordRecoveryOtpLength.hint") as ReactNode }
</Hint>
<Divider/>
<Heading as="h4" className={ "mt-4 mb-4" }>
{ t("extensions:manage.serverConfigurations.accountRecovery." +
"passwordRecovery.otherConfigHeading") as ReactNode }
</Heading>
<Field.Input
ariaLabel="maxFailedAttemptCount"
inputType="number"
name="maxFailedAttemptCount"
min={
GovernanceConnectorConstants.PASSWORD_RECOVERY_FORM_FIELD_CONSTRAINTS
.MAX_FAILED_ATTEMPT_COUNT_MIN_VALUE
}
max={
GovernanceConnectorConstants.PASSWORD_RECOVERY_FORM_FIELD_CONSTRAINTS
.MAX_FAILED_ATTEMPT_COUNT_MAX_VALUE
}
label={ t("extensions:manage.serverConfigurations.accountRecovery." +
"passwordRecovery.form.fields.maxFailedAttemptCount.label") }
placeholder={ t("extensions:manage.serverConfigurations.accountRecovery." +
"passwordRecovery.form.fields.maxFailedAttemptCount.placeholder") }
required={ false }
maxLength={
GovernanceConnectorConstants
.PASSWORD_RECOVERY_FORM_FIELD_CONSTRAINTS.MAX_FAILED_ATTEMPT_COUNT_MAX_LENGTH
}
minLength={
GovernanceConnectorConstants
.PASSWORD_RECOVERY_FORM_FIELD_CONSTRAINTS.MAX_FAILED_ATTEMPT_COUNT_MIN_LENGTH
}
readOnly={ readOnly }
width={ 10 }
labelPosition="right"
disabled={ !isConnectorEnabled }
data-testid={ `${ testId }-max-fail-attempt-count` }
>
<input/>
<Label
content={ "attempts" }
/>
</Field.Input>
<Hint className={ "mb-5 " }>
{ t("extensions:manage.serverConfigurations.accountRecovery." +
"passwordRecovery.form.fields.maxFailedAttemptCount.hint") as ReactNode }
</Hint>
<Field.Input
ariaLabel="otpMaxResendCount"
ariaLabel="maxResendCount"
inputType="number"
name="otpMaxResendCount"
name="maxResendCount"
min={
GovernanceConnectorConstants.PASSWORD_RECOVERY_FORM_FIELD_CONSTRAINTS
.MAX_RESEND_COUNT_MIN_VALUE
Expand All @@ -652,9 +727,9 @@ export const PasswordRecoveryConfigurationForm: FunctionComponent<PasswordRecove
.MAX_RESEND_COUNT_MAX_VALUE
}
label={ t("extensions:manage.serverConfigurations.accountRecovery." +
"passwordRecovery.form.fields.otpMaxResendCount.label") }
"passwordRecovery.form.fields.maxResendCount.label") }
placeholder={ t("extensions:manage.serverConfigurations.accountRecovery." +
"passwordRecovery.form.fields.otpMaxResendCount.placeholder") }
"passwordRecovery.form.fields.maxResendCount.placeholder") }
required={ false }
maxLength={
GovernanceConnectorConstants
Expand All @@ -677,7 +752,7 @@ export const PasswordRecoveryConfigurationForm: FunctionComponent<PasswordRecove
</Field.Input>
<Hint className={ "mb-5 " }>
{ t("extensions:manage.serverConfigurations.accountRecovery." +
"passwordRecovery.form.fields.otpMaxResendCount.hint") as ReactNode }
"passwordRecovery.form.fields.maxResendCount.hint") as ReactNode }
</Hint>
<Field.Button
className= { "mt-4" }
Expand Down

0 comments on commit 8191574

Please sign in to comment.