From 683c4a70bfa6fd30a56b1008cd12be20765404b1 Mon Sep 17 00:00:00 2001 From: Amir Zarrinkafsh Date: Fri, 12 Feb 2021 16:59:42 +1100 Subject: [PATCH] fix(web): improve 2fa enrollment process (#1706) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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. --- docs/configuration/one-time-password.md | 6 +++--- docs/features/2fa/one-time-password.md | 13 ++++++++----- docs/features/2fa/push-notifications.md | 4 +--- docs/features/2fa/security-key.md | 7 +++++++ internal/suites/scenario_available_methods_test.go | 14 +++----------- internal/suites/suite_duo_push_test.go | 2 +- internal/suites/suite_standalone_test.go | 2 +- internal/templates/html_email.go | 6 +++--- internal/templates/plaintext_email.go | 2 +- .../DeviceRegistration/RegisterOneTimePassword.tsx | 2 +- .../LoginPortal/SecondFactor/MethodContainer.tsx | 4 +++- .../SecondFactor/MethodSelectionDialog.tsx | 4 ++-- .../SecondFactor/OneTimePasswordMethod.tsx | 1 + .../SecondFactor/PushNotificationMethod.tsx | 1 + .../LoginPortal/SecondFactor/SecurityKeyMethod.tsx | 1 + 15 files changed, 37 insertions(+), 32 deletions(-) diff --git a/docs/configuration/one-time-password.md b/docs/configuration/one-time-password.md index 6818faac..d1153690 100644 --- a/docs/configuration/one-time-password.md +++ b/docs/configuration/one-time-password.md @@ -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 diff --git a/docs/features/2fa/one-time-password.md b/docs/features/2fa/one-time-password.md index 558ca435..48cba8fa 100644 --- a/docs/features/2fa/one-time-password.md +++ b/docs/features/2fa/one-time-password.md @@ -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,12 +17,11 @@ 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/* -Once this validation step is completed, a QRCode gets displayed. +Once this validation step is completed, a QR Code gets displayed.

@@ -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 -[Google Authenticator]: https://google-authenticator.com/ \ No newline at end of file +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/ diff --git a/docs/features/2fa/push-notifications.md b/docs/features/2fa/push-notifications.md index 312d39ec..7b53439d 100644 --- a/docs/features/2fa/push-notifications.md +++ b/docs/features/2fa/push-notifications.md @@ -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/ \ No newline at end of file +[Duo]: https://duo.com/ diff --git a/docs/features/2fa/security-key.md b/docs/features/2fa/security-key.md index a547d12c..a56dda9e 100644 --- a/docs/features/2fa/security-key.md +++ b/docs/features/2fa/security-key.md @@ -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? diff --git a/internal/suites/scenario_available_methods_test.go b/internal/suites/scenario_available_methods_test.go index 328cac3d..07f9cb1a 100644 --- a/internal/suites/scenario_available_methods_test.go +++ b/internal/suites/scenario_available_methods_test.go @@ -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)) } } diff --git a/internal/suites/suite_duo_push_test.go b/internal/suites/suite_duo_push_test.go index 6ea2c825..cbe82365 100644 --- a/internal/suites/suite_duo_push_test.go +++ b/internal/suites/suite_duo_push_test.go @@ -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", })) } diff --git a/internal/suites/suite_standalone_test.go b/internal/suites/suite_standalone_test.go index af8dc0c9..edd4881a 100644 --- a/internal/suites/suite_standalone_test.go +++ b/internal/suites/suite_standalone_test.go @@ -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() { diff --git a/internal/templates/html_email.go b/internal/templates/html_email.go index a917324d..818af7b3 100644 --- a/internal/templates/html_email.go +++ b/internal/templates/html_email.go @@ -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 = ` - Please ignore this email if you did not initiate the process. + Please contact an administrator if you did not initiate the process. diff --git a/internal/templates/plaintext_email.go b/internal/templates/plaintext_email.go index 33196938..0e855a9d 100644 --- a/internal/templates/plaintext_email.go +++ b/internal/templates/plaintext_email.go @@ -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. ` diff --git a/web/src/views/DeviceRegistration/RegisterOneTimePassword.tsx b/web/src/views/DeviceRegistration/RegisterOneTimePassword.tsx index 9f126e13..99a029d0 100644 --- a/web/src/views/DeviceRegistration/RegisterOneTimePassword.tsx +++ b/web/src/views/DeviceRegistration/RegisterOneTimePassword.tsx @@ -74,7 +74,7 @@ const RegisterOneTimePassword = function () { const qrcodeFuzzyStyle = isLoading || hasErrored ? style.fuzzy : undefined; return ( - +

Need Google Authenticator? diff --git a/web/src/views/LoginPortal/SecondFactor/MethodContainer.tsx b/web/src/views/LoginPortal/SecondFactor/MethodContainer.tsx index d7390478..eb3db926 100644 --- a/web/src/views/LoginPortal/SecondFactor/MethodContainer.tsx +++ b/web/src/views/LoginPortal/SecondFactor/MethodContainer.tsx @@ -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) {
{props.onRegisterClick ? ( - Not registered yet? + {registerMessage} ) : null}
diff --git a/web/src/views/LoginPortal/SecondFactor/MethodSelectionDialog.tsx b/web/src/views/LoginPortal/SecondFactor/MethodSelectionDialog.tsx index 11247330..172a2d27 100644 --- a/web/src/views/LoginPortal/SecondFactor/MethodSelectionDialog.tsx +++ b/web/src/views/LoginPortal/SecondFactor/MethodSelectionDialog.tsx @@ -40,7 +40,7 @@ const MethodSelectionDialog = function (props: Props) { {props.methods.has(SecondFactorMethod.TOTP) ? ( props.onClick(SecondFactorMethod.TOTP)} /> @@ -48,7 +48,7 @@ const MethodSelectionDialog = function (props: Props) { {props.methods.has(SecondFactorMethod.U2F) && props.u2fSupported ? ( } onClick={() => props.onClick(SecondFactorMethod.U2F)} /> diff --git a/web/src/views/LoginPortal/SecondFactor/OneTimePasswordMethod.tsx b/web/src/views/LoginPortal/SecondFactor/OneTimePasswordMethod.tsx index 5e099b48..128d8800 100644 --- a/web/src/views/LoginPortal/SecondFactor/OneTimePasswordMethod.tsx +++ b/web/src/views/LoginPortal/SecondFactor/OneTimePasswordMethod.tsx @@ -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} > diff --git a/web/src/views/LoginPortal/SecondFactor/PushNotificationMethod.tsx b/web/src/views/LoginPortal/SecondFactor/PushNotificationMethod.tsx index 53acd729..14b7a487 100644 --- a/web/src/views/LoginPortal/SecondFactor/PushNotificationMethod.tsx +++ b/web/src/views/LoginPortal/SecondFactor/PushNotificationMethod.tsx @@ -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} >
{icon}
diff --git a/web/src/views/LoginPortal/SecondFactor/SecurityKeyMethod.tsx b/web/src/views/LoginPortal/SecondFactor/SecurityKeyMethod.tsx index b8c825e9..737abfea 100644 --- a/web/src/views/LoginPortal/SecondFactor/SecurityKeyMethod.tsx +++ b/web/src/views/LoginPortal/SecondFactor/SecurityKeyMethod.tsx @@ -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} >