-
Notifications
You must be signed in to change notification settings - Fork 0
feat(auth): update password reset page UI and policy #149
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
ba52678
8e3299e
5eb77d2
65506e9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,7 +8,14 @@ import CardContent from "@material-ui/core/CardContent"; | |
| import Container from "@material-ui/core/Container"; | ||
| import CssBaseline from "@material-ui/core/CssBaseline"; | ||
| import Grid from "@material-ui/core/Grid"; | ||
| import IconButton from "@material-ui/core/IconButton"; | ||
| import InputAdornment from "@material-ui/core/InputAdornment"; | ||
| import InfoOutlinedIcon from "@material-ui/icons/InfoOutlined"; | ||
|
|
||
| import Visibility from "@material-ui/icons/Visibility"; | ||
| import VisibilityOff from "@material-ui/icons/VisibilityOff"; | ||
| import CheckCircle from "@material-ui/icons/CheckCircle"; | ||
| import Cancel from "@material-ui/icons/Cancel"; | ||
| import RadioButtonUnchecked from "@material-ui/icons/RadioButtonUnchecked"; | ||
| import PasswordStrengthBar from "react-password-strength-bar"; | ||
| import TextField from "@material-ui/core/TextField"; | ||
| import Typography from "@material-ui/core/Typography"; | ||
|
|
@@ -39,6 +46,7 @@ const ResetPasswordPage = ({ | |
| const formEl = useRef(null); | ||
| const captcha = useRef(null); | ||
| const [captchaConfirmation, setCaptchaConfirmation] = useState(null); | ||
| const [showPassword, setShowPassword] = useState(false); | ||
|
|
||
| useEffect(() => { | ||
| if (resetPasswordError) { | ||
|
|
@@ -76,6 +84,8 @@ const ResetPasswordPage = ({ | |
| } | ||
| }; | ||
|
|
||
| const passwordRequirementItems = passwordPolicy.shape_list.split(",").map(s => s.trim()); | ||
|
|
||
| return ( | ||
| <Container component="main" maxWidth="xs" className={styles.main_container}> | ||
| <CssBaseline /> | ||
|
|
@@ -94,8 +104,8 @@ const ResetPasswordPage = ({ | |
| > | ||
| <Card className={styles.reset_password_container} variant="outlined"> | ||
| <CardHeader | ||
| title="Reset your Password" | ||
| subheader="You can reset your password here." | ||
| title="Set a new password." | ||
| subheader="Resetting the password for the FNid below." | ||
| /> | ||
| <CardContent> | ||
| <Grid | ||
|
|
@@ -106,33 +116,49 @@ const ResetPasswordPage = ({ | |
| > | ||
| <Grid item> | ||
| <TextField | ||
| className={styles.email_field} | ||
| id="email" | ||
| name="email" | ||
| autoComplete="email" | ||
| variant="outlined" | ||
| variant="filled" | ||
| fullWidth | ||
| size="small" | ||
| label="Email Address" | ||
| hiddenLabel | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mulldug The email field lost its accessible name: this change replaces This is inconsistent with the accessibility work already in this PR — the visibility toggle has a state-aware Suggested fix: inputProps={{ 'aria-label': 'Email address' }}on the email |
||
| value={formik.values.email} | ||
| disabled={true} | ||
| InputProps={{ disableUnderline: true }} | ||
| /> | ||
| </Grid> | ||
| <Grid item xs={12}> | ||
| <TextField | ||
| id="password" | ||
| name="password" | ||
| type="password" | ||
| type={showPassword ? "text" : "password"} | ||
| variant="outlined" | ||
| fullWidth | ||
| size="small" | ||
| label="Password" | ||
| label="New password" | ||
| inputProps={{maxLength: passwordPolicy.max_length}} | ||
| value={formik.values.password} | ||
| onChange={formik.handleChange} | ||
| error={ | ||
| formik.touched.password && Boolean(formik.errors.password) | ||
| } | ||
| helperText={formik.touched.password && formik.errors.password} | ||
| InputProps={{ | ||
| endAdornment: ( | ||
| <InputAdornment position="end"> | ||
| <IconButton | ||
| onClick={() => setShowPassword(!showPassword)} | ||
| edge="end" | ||
| size="small" | ||
| aria-label={showPassword ? "Hide password" : "Show password"} | ||
| > | ||
| {showPassword ? <VisibilityOff /> : <Visibility />} | ||
| </IconButton> | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| </InputAdornment> | ||
| ) | ||
| }} | ||
| /> | ||
| {formik.values.password && ( | ||
| <PasswordStrengthBar | ||
|
|
@@ -149,7 +175,7 @@ const ResetPasswordPage = ({ | |
| variant="outlined" | ||
| fullWidth | ||
| size="small" | ||
| label="Confirm Password" | ||
| label="Confirm password" | ||
| inputProps={{maxLength: passwordPolicy.max_length}} | ||
| value={formik.values.password_confirmation} | ||
| onChange={formik.handleChange} | ||
|
|
@@ -161,14 +187,37 @@ const ResetPasswordPage = ({ | |
| formik.touched.password_confirmation && | ||
| formik.errors.password_confirmation | ||
| } | ||
| InputProps={{ | ||
| endAdornment: ( | ||
| <InputAdornment position="end"> | ||
| {!formik.values.password_confirmation | ||
| ? <RadioButtonUnchecked style={{ color: '#ccc' }} /> | ||
| : formik.values.password_confirmation === formik.values.password && formik.values.password | ||
| ? <CheckCircle style={{ color: '#2e7d32' }} /> | ||
| : <Cancel style={{ color: '#c62828', opacity: 0.6 }} /> | ||
| } | ||
| <span className={styles.sr_only} aria-live="polite"> | ||
| {!formik.values.password_confirmation | ||
| ? "" | ||
| : formik.values.password_confirmation === formik.values.password && formik.values.password | ||
| ? "Passwords match" | ||
| : "Passwords do not match" | ||
| } | ||
| </span> | ||
| </InputAdornment> | ||
| ) | ||
| }} | ||
| /> | ||
| </Grid> | ||
| <Grid item className={styles.password_hint}> | ||
| <InfoOutlinedIcon fontSize="small"/> | ||
| | ||
| <Typography variant="body2"> | ||
| <div dangerouslySetInnerHTML={{ __html: `The Password must be ${passwordPolicy.min_length}–${passwordPolicy.max_length} characters, and ${passwordPolicy.shape_warning}` }} /> | ||
| </Typography> | ||
| <p>Your password must include:</p> | ||
| <ul> | ||
| <li key="length">{passwordPolicy.min_length}–{passwordPolicy.max_length} characters</li> | ||
| {passwordRequirementItems.map((item, index) => ( | ||
| <li key={index}>{item}</li> | ||
| ))} | ||
| </ul> | ||
| <p className={styles.password_characters}>Allowed: <span>{passwordPolicy.allowed_special_characters_text}</span></p> | ||
| </Grid> | ||
| <Grid item container alignItems="center" justifyContent="center"> | ||
| <Grid container item justify='center'> | ||
|
|
@@ -195,6 +244,11 @@ const ResetPasswordPage = ({ | |
| disableElevation | ||
| fullWidth | ||
| type="submit" | ||
| disabled={ | ||
| !!formik.errors.password || | ||
| !formik.values.password_confirmation || | ||
| formik.values.password_confirmation !== formik.values.password | ||
| } | ||
| > | ||
| {submitButtonText} | ||
| </Button> | ||
|
|
@@ -207,6 +261,7 @@ const ResetPasswordPage = ({ | |
| <input type="hidden" value={initialValues.email} id="email" name="email"/> | ||
| <input type="hidden" name="token" value={token}/> | ||
| </form> | ||
| <p className={styles.help_link}>Need help? <a href="mailto:support@fntech.com">support@fntech.com</a></p> | ||
| </Container> | ||
| ); | ||
| }; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mulldug
.env.examplestill ships the pre-PR password pattern, overriding the new default this PR introduces.config/auth.php:107now defaults to^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*+\-])[A-Za-z0-9#?!@$%^&*+\-]+$(adds+, enforces the allowed charset), but this line still sets the old lookahead-only pattern without+. Any new environment copying the template gets a concrete mismatch: a password whose only special character is+(e.g.Abcdefgh1+) is rejected server-side, while the UI's "Allowed: #?!@$%^&*+-" hint (which falls back to the new config default, since the template doesn't setAUTH_PASSWORD_ALLOWED_SPECIAL_CHARACTERS_TEXT) tells the user+is fine. The old pattern also enforces no allowed-charset body at all. Deployed envs are unaffected (openstackid-docker/app/.env.productionandargocd-apps/openstackid/values-prod.yamldon't overrideAUTH_PASSWORD_SHAPE_PATTERN), so this only bites local dev and new environments — but it directly contradicts the PR's stated goal of adding+to the pattern.Suggested fix — update the template to match the new defaults (or drop both lines so the config defaults apply), and document the new floor while here: