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 6818faacb..d11536901 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 558ca4357..48cba8faf 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 312d39ec3..7b53439d9 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 a547d12cb..a56dda9ee 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 328cac3d8..07f9cb1a1 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 6ea2c825a..cbe823657 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 af8dc0c97..edd4881a3 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 a917324df..818af7b3e 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 33196938f..0e855a9dc 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 9f126e139..99a029d0e 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 d73904788..eb3db9264 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 112473309..172a2d276 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 5e099b48a..128d8800b 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 53acd7290..14b7a4874 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 b8c825e94..737abfea7 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} >