[FEATURE] Autofocus on authentication and OTP pages (#806)
* [FEATURE] Autofocus on authentication and OTP pages This change sets the input focus on the first factor authentication and OTP pages. The behaviour for the first factor authentication page has also been amended slightly, if an incorrect username or password is provided the password field will be cleared and set as the focus. One thing to note is that the OTP page does not focus on any re-rendering and this is because the component doesn't handle focusing. This means that the OTP input only is auto-focused when you first visit it, if you enter an incorrect OTP there will be no focus. Ideally we should be looking for a different library or writing a component for this ourselves in future. Closes #511. * Add TODO markers for potential refactorpull/810/head
parent
6128081e1f
commit
d82b46a3ec
|
@ -72,7 +72,7 @@ func (s *OneFactorSuite) TestShouldDenyAccessOnBadPassword() {
|
|||
targetURL := fmt.Sprintf("%s/secret.html", AdminBaseURL)
|
||||
s.doLoginOneFactor(ctx, s.T(), "john", "bad-password", false, targetURL)
|
||||
s.verifyIsFirstFactorPage(ctx, s.T())
|
||||
s.verifyNotificationDisplayed(ctx, s.T(), "There was a problem. Username or password might be incorrect.")
|
||||
s.verifyNotificationDisplayed(ctx, s.T(), "Incorrect username or password.")
|
||||
}
|
||||
|
||||
func TestRunOneFactor(t *testing.T) {
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/tebeka/selenium"
|
||||
)
|
||||
|
||||
type RegulationScenario struct {
|
||||
|
@ -53,27 +52,26 @@ func (s *RegulationScenario) TestShouldBanUserAfterTooManyAttempt() {
|
|||
|
||||
s.doVisitLoginPage(ctx, s.T(), "")
|
||||
s.doFillLoginPageAndClick(ctx, s.T(), "john", "bad-password", false)
|
||||
s.verifyNotificationDisplayed(ctx, s.T(), "There was a problem. Username or password might be incorrect.")
|
||||
s.verifyNotificationDisplayed(ctx, s.T(), "Incorrect username or password.")
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
s.WaitElementLocatedByID(ctx, s.T(), "password-textfield").SendKeys("bad-password")
|
||||
s.WaitElementLocatedByID(ctx, s.T(), "sign-in-button").Click()
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
|
||||
// Reset password field
|
||||
s.WaitElementLocatedByID(ctx, s.T(), "password-textfield").
|
||||
SendKeys(selenium.ControlKey + "a" + selenium.BackspaceKey)
|
||||
|
||||
// And enter the correct password
|
||||
// Enter the correct password and test the regulation lock out
|
||||
s.WaitElementLocatedByID(ctx, s.T(), "password-textfield").SendKeys("password")
|
||||
s.WaitElementLocatedByID(ctx, s.T(), "sign-in-button").Click()
|
||||
s.verifyNotificationDisplayed(ctx, s.T(), "There was a problem. Username or password might be incorrect.")
|
||||
s.verifyNotificationDisplayed(ctx, s.T(), "Incorrect username or password.")
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
s.verifyIsFirstFactorPage(ctx, s.T())
|
||||
|
||||
time.Sleep(9 * time.Second)
|
||||
|
||||
// Enter the correct password and test a successful login
|
||||
s.WaitElementLocatedByID(ctx, s.T(), "password-textfield").SendKeys("password")
|
||||
s.WaitElementLocatedByID(ctx, s.T(), "sign-in-button").Click()
|
||||
s.verifyIsSecondFactorPage(ctx, s.T())
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ func (s *ResetPasswordScenario) TestShouldResetPassword() {
|
|||
|
||||
// Try to login with the old password
|
||||
s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "")
|
||||
s.verifyNotificationDisplayed(ctx, s.T(), "There was a problem. Username or password might be incorrect.")
|
||||
s.verifyNotificationDisplayed(ctx, s.T(), "Incorrect username or password.")
|
||||
|
||||
// Try to login with the new password
|
||||
s.doLoginOneFactor(ctx, s.T(), "john", "abc", false, "")
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from "react";
|
||||
import React, { MutableRefObject, useEffect, useRef, useState } from "react";
|
||||
import classnames from "classnames";
|
||||
import { makeStyles, Grid, Button, FormControlLabel, Checkbox, Link } from "@material-ui/core";
|
||||
import { useHistory } from "react-router";
|
||||
|
@ -28,6 +28,13 @@ export default function (props: Props) {
|
|||
const [password, setPassword] = useState("");
|
||||
const [passwordError, setPasswordError] = useState(false);
|
||||
const { createErrorNotification } = useNotifications();
|
||||
// TODO (PR: #806, Issue: #511) potentially refactor
|
||||
const usernameRef = useRef() as MutableRefObject<HTMLInputElement>;
|
||||
const passwordRef = useRef() as MutableRefObject<HTMLInputElement>;
|
||||
useEffect(() => {
|
||||
const timeout = setTimeout(() => usernameRef.current.focus(), 10);
|
||||
return () => clearTimeout(timeout);
|
||||
}, [usernameRef]);
|
||||
|
||||
const disabled = props.disabled;
|
||||
|
||||
|
@ -54,8 +61,10 @@ export default function (props: Props) {
|
|||
} catch (err) {
|
||||
console.error(err);
|
||||
createErrorNotification(
|
||||
"There was a problem. Username or password might be incorrect.");
|
||||
"Incorrect username or password.");
|
||||
props.onAuthenticationFailure();
|
||||
setPassword("");
|
||||
passwordRef.current.focus();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -71,6 +80,8 @@ export default function (props: Props) {
|
|||
<Grid container spacing={2} className={style.root}>
|
||||
<Grid item xs={12}>
|
||||
<FixedTextField
|
||||
// TODO (PR: #806, Issue: #511) potentially refactor
|
||||
inputRef={usernameRef}
|
||||
id="username-textfield"
|
||||
label="Username"
|
||||
variant="outlined"
|
||||
|
@ -80,10 +91,17 @@ export default function (props: Props) {
|
|||
disabled={disabled}
|
||||
fullWidth
|
||||
onChange={v => setUsername(v.target.value)}
|
||||
onFocus={() => setUsernameError(false)} />
|
||||
onFocus={() => setUsernameError(false)}
|
||||
onKeyPress={(ev) => {
|
||||
if (ev.key === 'Enter') {
|
||||
passwordRef.current.focus();
|
||||
}
|
||||
}} />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<FixedTextField
|
||||
// TODO (PR: #806, Issue: #511) potentially refactor
|
||||
inputRef={passwordRef}
|
||||
id="password-textfield"
|
||||
label="Password"
|
||||
variant="outlined"
|
||||
|
|
|
@ -20,6 +20,7 @@ export default function (props: Props) {
|
|||
const dial = (
|
||||
<span className={style.otpInput} id="otp-input">
|
||||
<OtpInput
|
||||
shouldAutoFocus
|
||||
onChange={props.onChange}
|
||||
value={props.passcode}
|
||||
numInputs={6}
|
||||
|
|
Loading…
Reference in New Issue