fix(web): improve 2fa enrollment process (#1706)

* refactor(web): improve 2fa enrollment process

This PR will change some of the wording and colours for the 2FA processes in order to provide more clarity and address some accessibility issues for end users.

The following is a summary of the changes:

* One-Time Password ⭢ Time-based One-Time Password
* Security Key ⭢ Security Key - U2F

![Screenshot_2021-02-02-09-36-17](https://user-images.githubusercontent.com/3339418/107138185-17656100-6967-11eb-8fac-9e75c7a82d09.png)


* QRCode ⭢ QR Code

![Screenshot_2021-02-07-05-07-25](https://user-images.githubusercontent.com/3339418/107138196-29df9a80-6967-11eb-811f-d77c9bb0159e.png)

* `Not registered yet?` text to display `Lost device?` if a user has already registered a device of said type

![Screenshot_2021-02-02-10-24-54](https://user-images.githubusercontent.com/3339418/107138205-395ee380-6967-11eb-8826-83e1438dd146.png)

* Change button and text colour in e-mails that Authelia generates
* Change Authelia email footer to be more security conscious

![Screenshot_2021-02-07-04-51-40](https://user-images.githubusercontent.com/3339418/107138211-4085f180-6967-11eb-890b-9d931bd1ce76.png)

The docs have also been updated to clarify the 2fa device enrollment limitation which only allows users to register one of each device type concurrently.

Closes #1560.
This commit is contained in:
Amir Zarrinkafsh 2021-02-12 16:59:42 +11:00 committed by GitHub
parent f188bfb1dc
commit 683c4a70bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 37 additions and 32 deletions

View File

@ -1,14 +1,14 @@
---
layout: default
title: One-Time Password
title: Time-based One-Time Password
parent: Configuration
nav_order: 4
---
# One-Time Password
# Time-based One-Time Password
Authelia uses time based one-time passwords as the OTP method. You have
the option to tune the settings of the TOTP generation and you can see a
the option to tune the settings of the TOTP generation, and you can see a
full example of TOTP configuration below, as well as sections describing them.
```yaml

View File

@ -1,6 +1,6 @@
---
layout: default
title: One-Time Password
title: Time-based One-Time Password
nav_order: 1
parent: Second Factor
grand_parent: Features
@ -17,8 +17,7 @@ grand_parent: Features
After having successfully completed the first factor, select **One-Time Password method**
option and click on **Not registered yet?** link. This will send you an e-mail to confirm
your identity.
option and click on **Not registered yet?** link. This will e-mail you to confirm your identity.
*NOTE: If you're testing **Authelia**, this e-mail has likely been sent to the mailbox available at https://mail.example.com:8080/*
@ -34,5 +33,9 @@ From now on, you get tokens generated every 30 seconds that
you can use to validate the second factor in **Authelia**.
## Limitations
Users currently can only enroll a single TOTP device in **Authelia**.
Multiple single type device enrollment will be available when [this issue](https://github.com/authelia/authelia/issues/275) has been resolved.
[Google Authenticator]: https://google-authenticator.com/

View File

@ -56,6 +56,4 @@ Users must be enrolled via the Duo Admin panel, they cannot enroll a device from
It's likely that you have not configured **Authelia** correctly. Please read this
documentation again and be sure you had a look at [config.template.yml](https://github.com/authelia/authelia/blob/master/config.template.yml).
[Duo]: https://duo.com/

View File

@ -44,6 +44,13 @@ by simply touching the token again when requested:
Easy, right?!
## Limitations
Users currently can only enroll a single U2F device in **Authelia**.
Multiple single type device enrollment will be available when [this issue](https://github.com/authelia/authelia/issues/275) has been resolved.
## FAQ
### Why don't I have access to the *Security Key* option?

View File

@ -6,6 +6,8 @@ import (
"time"
"github.com/tebeka/selenium"
"github.com/authelia/authelia/internal/utils"
)
type AvailableMethodsScenario struct {
@ -48,16 +50,6 @@ func (s *AvailableMethodsScenario) SetupTest() {
s.verifyIsHome(ctx, s.T())
}
func IsStringInList(str string, list []string) bool {
for _, v := range list {
if v == str {
return true
}
}
return false
}
func (s *AvailableMethodsScenario) TestShouldCheckAvailableMethods() {
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
@ -85,6 +77,6 @@ func (s *AvailableMethodsScenario) TestShouldCheckAvailableMethods() {
s.Assert().Len(optionsList, len(s.methods))
for _, m := range s.methods {
s.Assert().True(IsStringInList(m, optionsList))
s.Assert().True(utils.IsStringInSlice(m, optionsList))
}
}

View File

@ -135,7 +135,7 @@ func (s *DuoPushSuite) TestDuoPushRedirectionURLSuite() {
func (s *DuoPushSuite) TestAvailableMethodsScenario() {
suite.Run(s.T(), NewAvailableMethodsScenario([]string{
"ONE-TIME PASSWORD",
"TIME-BASED ONE-TIME PASSWORD",
"PUSH NOTIFICATION",
}))
}

View File

@ -180,7 +180,7 @@ func (s *StandaloneSuite) TestResetPasswordScenario() {
}
func (s *StandaloneSuite) TestAvailableMethodsScenario() {
suite.Run(s.T(), NewAvailableMethodsScenario([]string{"ONE-TIME PASSWORD"}))
suite.Run(s.T(), NewAvailableMethodsScenario([]string{"TIME-BASED ONE-TIME PASSWORD"}))
}
func (s *StandaloneSuite) TestRedirectionURLScenario() {

View File

@ -93,7 +93,7 @@ const emailHTMLContent = `
}
a {
color: #0a8cce;
color: #ffffff;
text-decoration: none;
text-decoration: none !important;
}
@ -105,7 +105,7 @@ const emailHTMLContent = `
.button {
padding: 15px 30px;
border-radius: 10px;
background: rgb(204, 204, 255);
background: rgb(25, 118, 210);
text-decoration: none;
}
@ -395,7 +395,7 @@ const emailHTMLContent = `
<td align="center" valign="middle"
style="font-family: Helvetica, arial, sans-serif; font-size: 14px;color: #666666"
st-content="postfooter">
Please ignore this email if you did not initiate the process.
Please contact an administrator if you did not initiate the process.
</td>
</tr>
<!-- Spacing -->

View File

@ -22,5 +22,5 @@ If you did not initiate the process your credentials might have been compromised
To setup your 2FA please visit the following URL: {{.url}}
Please ignore this email if you did not initiate the process.
Please contact an administrator if you did not initiate the process.
`

View File

@ -15,6 +15,7 @@ export enum State {
export interface Props {
id: string;
title: string;
registered: boolean;
explanation: string;
state: State;
children: ReactNode;
@ -24,6 +25,7 @@ export interface Props {
const DefaultMethodContainer = function (props: Props) {
const style = useStyles();
const registerMessage = props.registered ? "Lost your device?" : "Not registered yet?";
let container: ReactNode;
let stateClass: string = "";
@ -50,7 +52,7 @@ const DefaultMethodContainer = function (props: Props) {
</div>
{props.onRegisterClick ? (
<Link component="button" id="register-link" onClick={props.onRegisterClick}>
Not registered yet?
{registerMessage}
</Link>
) : null}
</div>

View File

@ -40,7 +40,7 @@ const MethodSelectionDialog = function (props: Props) {
{props.methods.has(SecondFactorMethod.TOTP) ? (
<MethodItem
id="one-time-password-option"
method="One-Time Password"
method="Time-based One-Time Password"
icon={pieChartIcon}
onClick={() => props.onClick(SecondFactorMethod.TOTP)}
/>
@ -48,7 +48,7 @@ const MethodSelectionDialog = function (props: Props) {
{props.methods.has(SecondFactorMethod.U2F) && props.u2fSupported ? (
<MethodItem
id="security-key-option"
method="Security Key"
method="Security Key - U2F"
icon={<FingerTouchIcon size={32} />}
onClick={() => props.onClick(SecondFactorMethod.U2F)}
/>

View File

@ -84,6 +84,7 @@ const OneTimePasswordMethod = function (props: Props) {
id={props.id}
title="One-Time Password"
explanation="Enter one-time password"
registered={props.registered}
state={methodState}
onRegisterClick={props.onRegisterClick}
>

View File

@ -98,6 +98,7 @@ const PushNotificationMethod = function (props: Props) {
id={props.id}
title="Push Notification"
explanation="A notification has been sent to your smartphone"
registered={true}
state={methodState}
>
<div className={style.icon}>{icon}</div>

View File

@ -107,6 +107,7 @@ const SecurityKeyMethod = function (props: Props) {
id={props.id}
title="Security Key"
explanation="Touch the token of your security key"
registered={props.registered}
state={methodState}
onRegisterClick={props.onRegisterClick}
>