diff --git a/packages/client/src/components/form-inputs/PasswordForm.tsx b/packages/client/src/components/form-inputs/PasswordForm.tsx index 526efbd4..6bb53bc1 100644 --- a/packages/client/src/components/form-inputs/PasswordForm.tsx +++ b/packages/client/src/components/form-inputs/PasswordForm.tsx @@ -61,6 +61,10 @@ const formValidator = { confirmPassword: passwordValidator, }; +export interface PasswordFormProps extends InputProps { + passwordInHistory: boolean; +} + /** * A password form * @@ -107,10 +111,19 @@ export default class PasswordForm extends React.Component< Please enter and confirm a password.
+
+ New update effective January 1st, 2024: All passwords must be at least 11 characters + in length. To read more about this change, please visit{' '} + + our user guide + + . +
+
Passwords must meet the following requirements: * A special character is a space character or one of the following symbols: -
^ ! @ # $ % ^ & * ( ) {'{'} {'}'} _ + - = {'<'} {'>'} , . ? / [ ] \ | ; ' - " +
^ ! @ # $ % ^ & * ( ) {'{'} {'}'} _ + - = < > , . ? / [ ] \ | ; + ' "
Passwords must also match @@ -213,7 +226,7 @@ export default class PasswordForm extends React.Component< hasError = true; } - if (fields.password.length >= 8) { + if (fields.password.length >= 11) { update.showLengthError = ShowLevel.SHOWGOOD; } else { hasError = true; diff --git a/packages/client/src/pages/account/CreateProspectiveMember.tsx b/packages/client/src/pages/account/CreateProspectiveMember.tsx index 27a6314f..6845bffc 100644 --- a/packages/client/src/pages/account/CreateProspectiveMember.tsx +++ b/packages/client/src/pages/account/CreateProspectiveMember.tsx @@ -359,7 +359,10 @@ export default class CreateProspectiveMember extends Page, Fin public componentDidMount(): void { this.props.deleteReduxState(); } - + public render = (): JSX.Element => ( disableOnInvalid={true} @@ -116,7 +116,7 @@ export default class FinishSignup extends Page, Fin const fetchResult = await fetchApi.member.account.finishAccountSetup( {}, - { token, username, password }, + { token, username: username.trim(), password }, ); if (Either.isLeft(fetchResult)) { diff --git a/packages/client/src/pages/account/RequestPasswordReset.tsx b/packages/client/src/pages/account/RequestPasswordReset.tsx index daa1d5f4..a81eee5b 100644 --- a/packages/client/src/pages/account/RequestPasswordReset.tsx +++ b/packages/client/src/pages/account/RequestPasswordReset.tsx @@ -56,7 +56,7 @@ export default class RequestPasswordResetForm extends Page ( values={this.state.form} @@ -68,7 +68,7 @@ export default class RequestPasswordResetForm extends Page val?.length > 0, }} submitInfo={{ - disabled: this.state.tryingSubmit, + disabled: this.state.tryingSubmit || this.state.success, text: 'Request password reset', }} > @@ -80,14 +80,18 @@ export default class RequestPasswordResetForm extends Page -

- Password reset request succesful. Please check your inbox for a password - reset link. -

-

- If the email is not received in your inbox, please check your Spam or Junk - folder for the message -

+
+

+ Password reset request succesful. Please check your inbox for a password + reset link. The email may take up to 5 minutes to appear in your inbox. + If nothing shows up after 5 minutes, please double check the username + provided +

+

+ If the email is not received in your inbox, please check your Spam or + Junk folder for the message +

+
) : null} @@ -136,7 +140,7 @@ export default class RequestPasswordResetForm extends Page ( values={this.state.form} @@ -68,7 +68,7 @@ export default class RequestUsernameForm extends Page val !== null && val >= 100000, }} submitInfo={{ - disabled: this.state.tryingSubmit, + disabled: this.state.tryingSubmit || this.state.success, text: 'Request login', }} > @@ -80,14 +80,17 @@ export default class RequestUsernameForm extends Page -

- Login request successful. Please check the email associated with the CAP ID - entered for an email with your CAP account. -

-

- If the email is not received in your inbox, please check your Spam or Junk - folder for the message -

+
+

+ Login request successful. Please check the email associated with the CAP + ID entered for an email with your CAP account. This email may take up to + 5 minutes to appear in your inbox. +

+

+ If the email is not received in your inbox, please check your Spam or + Junk folder for the message +

+
) : null} diff --git a/packages/client/src/pages/account/Signin.tsx b/packages/client/src/pages/account/Signin.tsx index 1379b777..8da02818 100644 --- a/packages/client/src/pages/account/Signin.tsx +++ b/packages/client/src/pages/account/Signin.tsx @@ -156,7 +156,7 @@ export const RegularSignin: React.FC = ({ state, authorizeUser, .signin( {}, { - username: state.signinFormValues.username, + username: state.signinFormValues.username.trim(), password: state.signinFormValues.password, token: { type: SigninTokenType.RECAPTCHA, diff --git a/packages/client/src/pages/account/Signup.tsx b/packages/client/src/pages/account/Signup.tsx index 44077e61..56abdac5 100644 --- a/packages/client/src/pages/account/Signup.tsx +++ b/packages/client/src/pages/account/Signup.tsx @@ -58,7 +58,7 @@ export default class Signup extends Page { public componentDidMount(): void { this.props.deleteReduxState(); } - + public constructor(props: PageProps) { super(props); @@ -99,8 +99,10 @@ export default class Signup extends Page { ) : null} {this.state.success ? ( - Account request successful. Check your inbox and spam folder for a link to - finish creating your account +
+ Account request successful. Check your inbox and spam folder for a link to + finish creating your account +
) : null} diff --git a/packages/common-lib/src/lib/passwordComplexity.ts b/packages/common-lib/src/lib/passwordComplexity.ts index 9ea29f67..da96208a 100644 --- a/packages/common-lib/src/lib/passwordComplexity.ts +++ b/packages/common-lib/src/lib/passwordComplexity.ts @@ -24,7 +24,7 @@ */ export const passwordMeetsRequirements = (password: string): boolean => password.length > 32 || - (password.length >= 8 && + (password.length >= 11 && // lowercase letter !!password.match(/[a-z]/g) && // uppercase letter diff --git a/packages/server-common/src/member/pam/Password.ts b/packages/server-common/src/member/pam/Password.ts index 0b780318..d379e466 100644 --- a/packages/server-common/src/member/pam/Password.ts +++ b/packages/server-common/src/member/pam/Password.ts @@ -263,7 +263,10 @@ export const checkIfPasswordValid = async ( const passwordExpireDate = userInfo.passwordHistory[0].created + PASSWORD_MAX_AGE; if (passwordExpireDate < Date.now()) { - return PasswordResult.VALID_EXPIRED; + if (passwordExpireDate < 1706763600000 + PASSWORD_MAX_AGE) { + return PasswordResult.VALID_EXPIRED; + } + return PasswordResult.VALID; } return PasswordResult.VALID; diff --git a/packages/server/src/api/member/account/finishpasswordreset.ts b/packages/server/src/api/member/account/finishpasswordreset.ts index 1c27367c..a14e7cda 100644 --- a/packages/server/src/api/member/account/finishpasswordreset.ts +++ b/packages/server/src/api/member/account/finishpasswordreset.ts @@ -17,7 +17,7 @@ * along with EvMPlus.org. If not, see . */ -import { api, get, Maybe } from 'common-lib'; +import { always, api, get, Maybe } from 'common-lib'; import { Backends, getCombinedPAMBackend, PAM, withBackends } from 'server-common'; import { Endpoint } from '../../..'; @@ -27,7 +27,9 @@ export const func: Endpoint< > = backend => req => backend .validatePasswordResetToken(req.body.token) - .tap(username => backend.addPasswordForUser([username, req.body.newPassword])) + .flatMap(username => + backend.addPasswordForUser([username, req.body.newPassword]).map(always(username)), + ) .tap(() => backend.removePasswordValidationToken(req.body.token)) .flatMap(backend.getUserInformationForUser) .filter(Maybe.isSome, {