mirror of
https://github.com/0rangebananaspy/authelia.git
synced 2024-09-14 22:47:21 +07:00
[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 refactor
This commit is contained in:
parent
6128081e1f
commit
d82b46a3ec
|
@ -72,7 +72,7 @@ func (s *OneFactorSuite) TestShouldDenyAccessOnBadPassword() {
|
||||||
targetURL := fmt.Sprintf("%s/secret.html", AdminBaseURL)
|
targetURL := fmt.Sprintf("%s/secret.html", AdminBaseURL)
|
||||||
s.doLoginOneFactor(ctx, s.T(), "john", "bad-password", false, targetURL)
|
s.doLoginOneFactor(ctx, s.T(), "john", "bad-password", false, targetURL)
|
||||||
s.verifyIsFirstFactorPage(ctx, s.T())
|
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) {
|
func TestRunOneFactor(t *testing.T) {
|
||||||
|
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/suite"
|
"github.com/stretchr/testify/suite"
|
||||||
"github.com/tebeka/selenium"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type RegulationScenario struct {
|
type RegulationScenario struct {
|
||||||
|
@ -53,27 +52,26 @@ func (s *RegulationScenario) TestShouldBanUserAfterTooManyAttempt() {
|
||||||
|
|
||||||
s.doVisitLoginPage(ctx, s.T(), "")
|
s.doVisitLoginPage(ctx, s.T(), "")
|
||||||
s.doFillLoginPageAndClick(ctx, s.T(), "john", "bad-password", false)
|
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++ {
|
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()
|
s.WaitElementLocatedByID(ctx, s.T(), "sign-in-button").Click()
|
||||||
time.Sleep(1 * time.Second)
|
time.Sleep(1 * time.Second)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset password field
|
// Enter the correct password and test the regulation lock out
|
||||||
s.WaitElementLocatedByID(ctx, s.T(), "password-textfield").
|
|
||||||
SendKeys(selenium.ControlKey + "a" + selenium.BackspaceKey)
|
|
||||||
|
|
||||||
// And enter the correct password
|
|
||||||
s.WaitElementLocatedByID(ctx, s.T(), "password-textfield").SendKeys("password")
|
s.WaitElementLocatedByID(ctx, s.T(), "password-textfield").SendKeys("password")
|
||||||
s.WaitElementLocatedByID(ctx, s.T(), "sign-in-button").Click()
|
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)
|
time.Sleep(1 * time.Second)
|
||||||
s.verifyIsFirstFactorPage(ctx, s.T())
|
s.verifyIsFirstFactorPage(ctx, s.T())
|
||||||
|
|
||||||
time.Sleep(9 * time.Second)
|
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.WaitElementLocatedByID(ctx, s.T(), "sign-in-button").Click()
|
||||||
s.verifyIsSecondFactorPage(ctx, s.T())
|
s.verifyIsSecondFactorPage(ctx, s.T())
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,7 +56,7 @@ func (s *ResetPasswordScenario) TestShouldResetPassword() {
|
||||||
|
|
||||||
// Try to login with the old password
|
// Try to login with the old password
|
||||||
s.doLoginOneFactor(ctx, s.T(), "john", "password", false, "")
|
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
|
// Try to login with the new password
|
||||||
s.doLoginOneFactor(ctx, s.T(), "john", "abc", false, "")
|
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 classnames from "classnames";
|
||||||
import { makeStyles, Grid, Button, FormControlLabel, Checkbox, Link } from "@material-ui/core";
|
import { makeStyles, Grid, Button, FormControlLabel, Checkbox, Link } from "@material-ui/core";
|
||||||
import { useHistory } from "react-router";
|
import { useHistory } from "react-router";
|
||||||
|
@ -28,6 +28,13 @@ export default function (props: Props) {
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
const [passwordError, setPasswordError] = useState(false);
|
const [passwordError, setPasswordError] = useState(false);
|
||||||
const { createErrorNotification } = useNotifications();
|
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;
|
const disabled = props.disabled;
|
||||||
|
|
||||||
|
@ -54,8 +61,10 @@ export default function (props: Props) {
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
createErrorNotification(
|
createErrorNotification(
|
||||||
"There was a problem. Username or password might be incorrect.");
|
"Incorrect username or password.");
|
||||||
props.onAuthenticationFailure();
|
props.onAuthenticationFailure();
|
||||||
|
setPassword("");
|
||||||
|
passwordRef.current.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,6 +80,8 @@ export default function (props: Props) {
|
||||||
<Grid container spacing={2} className={style.root}>
|
<Grid container spacing={2} className={style.root}>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<FixedTextField
|
<FixedTextField
|
||||||
|
// TODO (PR: #806, Issue: #511) potentially refactor
|
||||||
|
inputRef={usernameRef}
|
||||||
id="username-textfield"
|
id="username-textfield"
|
||||||
label="Username"
|
label="Username"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
|
@ -80,10 +91,17 @@ export default function (props: Props) {
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
fullWidth
|
fullWidth
|
||||||
onChange={v => setUsername(v.target.value)}
|
onChange={v => setUsername(v.target.value)}
|
||||||
onFocus={() => setUsernameError(false)} />
|
onFocus={() => setUsernameError(false)}
|
||||||
|
onKeyPress={(ev) => {
|
||||||
|
if (ev.key === 'Enter') {
|
||||||
|
passwordRef.current.focus();
|
||||||
|
}
|
||||||
|
}} />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<FixedTextField
|
<FixedTextField
|
||||||
|
// TODO (PR: #806, Issue: #511) potentially refactor
|
||||||
|
inputRef={passwordRef}
|
||||||
id="password-textfield"
|
id="password-textfield"
|
||||||
label="Password"
|
label="Password"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
|
|
|
@ -20,6 +20,7 @@ export default function (props: Props) {
|
||||||
const dial = (
|
const dial = (
|
||||||
<span className={style.otpInput} id="otp-input">
|
<span className={style.otpInput} id="otp-input">
|
||||||
<OtpInput
|
<OtpInput
|
||||||
|
shouldAutoFocus
|
||||||
onChange={props.onChange}
|
onChange={props.onChange}
|
||||||
value={props.passcode}
|
value={props.passcode}
|
||||||
numInputs={6}
|
numInputs={6}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user