feat(core(js)): improve Google Authenticator on login page, add QR code

Closes #2722
pull/274/head
Francis Lachapelle 2020-05-07 16:45:37 -04:00
parent f78300a12e
commit e8f0471bcf
8 changed files with 116 additions and 34 deletions

View File

@ -21,6 +21,12 @@
"browserNotCompatible" = "We've detected that your browser version is currently not supported on this site. Our recommendation is to use Firefox. Click on the link below to download the most current version of this browser.";
"alternativeBrowsers" = "Alternatively, you can also use the following compatible browsers";
"alternativeBrowserSafari" = "Alternatively, you can also use Safari.";
/* 2FA */
"Verification Code" = "Verification Code";
"Enter the 6-digit verification code from your Google Authenticator application." = "Enter the 6-digit verification code from your Google Authenticator application.";
"You provided an invalid Google Authenticator key." = "You provided an invalid Google Authenticator key.";
"Download" = "Download";
"Language" = "Language";
"choose" = "Choose ...";

View File

@ -448,6 +448,11 @@
"animation_LIMITED" = "Limited";
"animation_NONE" = "None";
/* 2FA */
"Enable two-factor authentication using Google Authenticator" = "Enable two-factor authentication using Google Authenticator";
"You must enter this key into your Google Authenticator application." = "You must enter this key into your Google Authenticator application.";
"If you do not and you log out you will not be able to login again." = "If you do not and you log out you will not be able to login again.";
/* External Sieve scripts */
"An external Sieve script is active" = "An external Sieve script is active";
"Sieve is a programming language that can be used for email filtering. If you let SOGo handle your filters, vacation and forward settings, your active script will be disabled." = "Sieve is a programming language that can be used for email filtering. If you let SOGo handle your filters, vacation and forward settings, your active script will be disabled.";

View File

@ -52,16 +52,6 @@
<input type="password" ng-model="app.creds.password" ng-required="true"/>
</md-input-container>
<var:if condition="isGoogleAuthenticatorEnabled">
<md-input-container class="md-block"
ng-show="app.showGoogleAuthenticatorCode">
<label><var:string label:value="Google Authenticator
Verification Code"/></label>
<md-icon>email</md-icon>
<input type="text" ng-model="app.creds.verificationCode" ng-required="false"/>
</md-input-container>
</var:if>
<!-- LANGUAGES SELECT -->
<div layout="row" layout-align="start end">
<md-icon>language</md-icon>
@ -112,13 +102,13 @@
</md-button>
<md-button class="md-fab md-accent md-hue-2" type="submit"
label:aria-label="Connect"
ng-disabled="loginForm.$invalid" sg-ripple-click="loginContent">
ng-if="!app.loginState"
ng-disabled="loginForm.$invalid"
sg-ripple-click="loginContent">
<md-icon>arrow_forward</md-icon>
</md-button>
</div>
</form>
<sg-ripple class="md-default-theme md-accent md-bg"
ng-class="{ 'md-warn': app.loginState == 'error' }"><!-- ripple background --></sg-ripple>
<sg-ripple-content class="md-flex ng-hide"
@ -136,6 +126,36 @@
</div>
</div>
<!-- Google Authenticator Code -->
<var:if condition="isGoogleAuthenticatorEnabled">
<div layout="row" layout-align="center center" layout-fill="layout-fill"
ng-switch-when="googleauthenticatorcode">
<div flex="80" flex-sm="50" flex-gt-sm="40">
<md-input-container class="md-block">
<label><var:string label:value="Verification Code"/></label>
<md-icon>lock</md-icon>
<input type="text" ng-pattern="app.verificationCodePattern" ng-model="app.creds.verificationCode" ng-required="app.loginState == 'googleauthenticatorcode'"/>
<div class="sg-hint"><var:string label:value="Enter the 6-digit verification code from your Google Authenticator application."/></div>
</md-input-container>
<div layout="row" layout-align="space-between center">
<md-button class="md-icon-button"
label:aria-label="Cancel"
ng-click="app.restoreLogin()"
sg-ripple-click="loginContent">
<md-icon>arrow_backward</md-icon>
</md-button>
<md-button class="md-fab md-accent md-hue-2" type="submit"
label:aria-label="Connect"
ng-if="app.loginState == 'googleauthenticatorcode'"
ng-disabled="loginForm.$invalid"
ng-click="app.login()">
<md-icon>arrow_forward</md-icon>
</md-button>
</div>
</div>
</div>
</var:if>
<!-- Logged in -->
<div layout="column" layout-align="center center"
ng-switch-when="logged">
@ -152,10 +172,14 @@
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding">
{{app.errorMessage}}
</div>
<md-button sg-ripple-click="loginContent"><var:string label:value="Retry"/></md-button>
<md-button
ng-click="app.restoreLogin()"
sg-ripple-click="loginContent"><var:string label:value="Retry"/></md-button>
</div>
</sg-ripple-content>
</form>
</div>
</div>
</md-content>

View File

@ -8,7 +8,7 @@
xmlns:label="OGo:label"
className="UIxPageFrame"
title="title"
const:jsFiles="vendor/ckeditor/ckeditor.js, vendor/ckeditor/ck.js, vendor/angular-file-upload.min.js, vendor/ng-sortable.min.js, Common.js, Preferences.js, Preferences.services.js, Mailer.services.js, Contacts.services.js">
const:jsFiles="vendor/ckeditor/ckeditor.js, vendor/ckeditor/ck.js, vendor/angular-file-upload.min.js, vendor/ng-sortable.min.js, vendor/qrcode.min.js, Common.js, Preferences.js, Preferences.services.js, Mailer.services.js, Contacts.services.js">
<main layout-fill="layout-fill" ui-view="preferences"
ng-controller="navController"><!-- preferences --> </main>
@ -232,24 +232,22 @@
</md-input-container>
<var:if condition="isGoogleAuthenticatorEnabled">
<md-checkbox flex="20"
ng-model="app.preferences.defaults.SOGoGoogleAuthenticatorEnabled"
<md-checkbox ng-model="app.preferences.defaults.SOGoGoogleAuthenticatorEnabled"
ng-true-value="1"
ng-false-value="0"
label:aria-label="Enable two-factor authentication using Google Authenticator">
<var:string label:value="Enable two-factor authentication using Google Authenticator"/>
</md-checkbox>
<input type="text"
ng-readonly="true"
ng-show="app.preferences.defaults.SOGoGoogleAuthenticatorEnabled == 1"
var:value="googleAuthenticatorKey"/>
<label
ng-show="app.preferences.defaults.SOGoGoogleAuthenticatorEnabled
== 1"><var:string label:value="You must enter
this key into your Google Authenticator
application. If you do not and you log out
you will not be able to access SOGo
again."/></label>
<div layout="row" layout-align="start center" layout-wrap="layout-wrap"
layout-padding="layout-padding" layout-margin="layout-margin"
ng-show="app.preferences.defaults.SOGoGoogleAuthenticatorEnabled">
<div>
<sg-qr-code var:text="googleAuthenticatorKey" />
</div>
<div flex="100" flex-sm="60" flex-gt-sm="50">
<var:string label:value="You must enter this key into your Google Authenticator application."/> <b><var:string label:value="If you do not and you log out you will not be able to login again."/></b>
</div>
</div>
</var:if>
</div>

View File

@ -139,7 +139,8 @@ module.exports = function(grunt) {
'<%= src %>/angular-ui-router/release/angular-ui-router{,.min}.js{,.map}',
//'<%= src %>/ng-file-upload/ng-file-upload{,.min}.js{,map}',
'<%= src %>/ng-sortable/dist/ng-sortable.min.js{,map}',
'<%= src %>/lodash/lodash{,.min}.js'
'<%= src %>/lodash/lodash{,.min}.js',
'<%= src %>/qrcodejs/qrcode{,.min}.js'
];
for (var j = 0; j < js.length; j++) {
var files = grunt.file.expand(grunt.template.process(js[j], {data: options}));

View File

@ -0,0 +1,41 @@
/* -*- Mode: javascript; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
(function() {
/* jshint validthis: true, newcap: false */
'use strict';
/**
* sgQrCode - Build a otpauth URI and generate a QR Code for the provided secret.
* @see {@link https://davidshimjs.github.io/qrcodejs/|QRCode.js}
* @memberof SOGo.Common
* @example:
<sg-qr-code text="secret"/>
*/
sgQrCode.$inject = ['sgSettings'];
function sgQrCode(Settings) {
return {
restrict: 'E',
scope: {
text: '@',
width: '@',
height: '@'
},
link: link
};
function link(scope, element, attrs) {
var width = parseInt(scope.width) || 256,
height = parseInt(scope.height) || width,
uri = 'otpauth://totp/SOGo:' + Settings.activeUser('email') + '?secret=' + scope.text.replace(/=+$/, '') + '&issuer=SOGo';
new QRCode(element[0], {
text: uri,
width: width,
height: height
});
}
}
angular
.module('SOGo.Common')
.directive('sgQrCode', sgQrCode);
})();

View File

@ -23,7 +23,9 @@
if (/\blanguage=/.test($window.location.search))
this.creds.language = $window.language;
this.loginState = false;
this.showGoogleAuthenticatorCode = false;
// Code pattern for Google verification code
this.verificationCodePattern = '\\d{6}';
// Show login once everything is initialized
this.showLogin = false;
@ -35,9 +37,8 @@
Authentication.login(vm.creds)
.then(function(data) {
if (typeof data.gamissingkey != 'undefined' && data.gamissingkey == 1) {
vm.showGoogleAuthenticatorCode = true;
vm.loginState = 'error';
if (data.gamissingkey) {
vm.loginState = 'googleauthenticatorcode';
}
else {
vm.loginState = 'logged';
@ -58,6 +59,11 @@
return false;
};
this.restoreLogin = function() {
vm.loginState = false;
delete vm.creds.verificationCode;
};
this.showAbout = function($event) {
$mdDialog.show({
targetEvent: $event,

View File

@ -30,7 +30,8 @@
"grunt-postcss": ">=0.6.0",
"grunt-sass": "^3.1.0",
"kss": "^3.0.1",
"node-sass": "^4.14.0",
"node-sass": "^4.14.1",
"qrcodejs": "^1.0.0",
"time-grunt": "latest"
},
"browserslist": [