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."; "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"; "alternativeBrowsers" = "Alternatively, you can also use the following compatible browsers";
"alternativeBrowserSafari" = "Alternatively, you can also use Safari."; "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"; "Download" = "Download";
"Language" = "Language"; "Language" = "Language";
"choose" = "Choose ..."; "choose" = "Choose ...";

View File

@ -448,6 +448,11 @@
"animation_LIMITED" = "Limited"; "animation_LIMITED" = "Limited";
"animation_NONE" = "None"; "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 */ /* External Sieve scripts */
"An external Sieve script is active" = "An external Sieve script is active"; "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."; "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"/> <input type="password" ng-model="app.creds.password" ng-required="true"/>
</md-input-container> </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 --> <!-- LANGUAGES SELECT -->
<div layout="row" layout-align="start end"> <div layout="row" layout-align="start end">
<md-icon>language</md-icon> <md-icon>language</md-icon>
@ -112,13 +102,13 @@
</md-button> </md-button>
<md-button class="md-fab md-accent md-hue-2" type="submit" <md-button class="md-fab md-accent md-hue-2" type="submit"
label:aria-label="Connect" 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-icon>arrow_forward</md-icon>
</md-button> </md-button>
</div> </div>
</form>
<sg-ripple class="md-default-theme md-accent md-bg" <sg-ripple class="md-default-theme md-accent md-bg"
ng-class="{ 'md-warn': app.loginState == 'error' }"><!-- ripple background --></sg-ripple> ng-class="{ 'md-warn': app.loginState == 'error' }"><!-- ripple background --></sg-ripple>
<sg-ripple-content class="md-flex ng-hide" <sg-ripple-content class="md-flex ng-hide"
@ -136,6 +126,36 @@
</div> </div>
</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 --> <!-- Logged in -->
<div layout="column" layout-align="center center" <div layout="column" layout-align="center center"
ng-switch-when="logged"> ng-switch-when="logged">
@ -152,10 +172,14 @@
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding"> <div class="md-default-theme md-accent md-hue-1 md-fg md-padding">
{{app.errorMessage}} {{app.errorMessage}}
</div> </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> </div>
</sg-ripple-content> </sg-ripple-content>
</form>
</div> </div>
</div> </div>
</md-content> </md-content>

View File

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

View File

@ -139,7 +139,8 @@ module.exports = function(grunt) {
'<%= src %>/angular-ui-router/release/angular-ui-router{,.min}.js{,.map}', '<%= src %>/angular-ui-router/release/angular-ui-router{,.min}.js{,.map}',
//'<%= src %>/ng-file-upload/ng-file-upload{,.min}.js{,map}', //'<%= src %>/ng-file-upload/ng-file-upload{,.min}.js{,map}',
'<%= src %>/ng-sortable/dist/ng-sortable.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++) { for (var j = 0; j < js.length; j++) {
var files = grunt.file.expand(grunt.template.process(js[j], {data: options})); 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)) if (/\blanguage=/.test($window.location.search))
this.creds.language = $window.language; this.creds.language = $window.language;
this.loginState = false; this.loginState = false;
this.showGoogleAuthenticatorCode = false;
// Code pattern for Google verification code
this.verificationCodePattern = '\\d{6}';
// Show login once everything is initialized // Show login once everything is initialized
this.showLogin = false; this.showLogin = false;
@ -35,9 +37,8 @@
Authentication.login(vm.creds) Authentication.login(vm.creds)
.then(function(data) { .then(function(data) {
if (typeof data.gamissingkey != 'undefined' && data.gamissingkey == 1) { if (data.gamissingkey) {
vm.showGoogleAuthenticatorCode = true; vm.loginState = 'googleauthenticatorcode';
vm.loginState = 'error';
} }
else { else {
vm.loginState = 'logged'; vm.loginState = 'logged';
@ -58,6 +59,11 @@
return false; return false;
}; };
this.restoreLogin = function() {
vm.loginState = false;
delete vm.creds.verificationCode;
};
this.showAbout = function($event) { this.showAbout = function($event) {
$mdDialog.show({ $mdDialog.show({
targetEvent: $event, targetEvent: $event,

View File

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