2-Step Verification (Google Authenticator)

Small fixes
This commit is contained in:
RainLoop Team 2014-04-05 23:48:22 +04:00
parent cae0cc2f77
commit 419b47a81e
51 changed files with 1747 additions and 419 deletions

View file

@ -7,6 +7,7 @@ function AdminSecurity()
{
this.csrfProtection = ko.observable(!!RL.settingsGet('UseTokenProtection'));
this.openPGP = ko.observable(!!RL.settingsGet('OpenPGP'));
this.allowTwoFactorAuth = ko.observable(!!RL.settingsGet('AllowTwoFactorAuth'));
this.adminLogin = ko.observable(RL.settingsGet('AdminLogin'));
this.adminPassword = ko.observable('');
@ -72,6 +73,12 @@ AdminSecurity.prototype.onBuild = function ()
'OpenPGP': bValue ? '1' : '0'
});
});
this.allowTwoFactorAuth.subscribe(function (bValue) {
RL.remote().saveAdminConfig(Utils.emptyFunction, {
'AllowTwoFactorAuth': bValue ? '1' : '0'
});
});
};
AdminSecurity.prototype.onHide = function ()

View file

@ -937,6 +937,11 @@ RainLoopApp.prototype.bootstart = function ()
Utils.removeSettingsViewModel(SettingsOpenPGP);
}
if (!RL.settingsGet('AllowTwoFactorAuth'))
{
Utils.removeSettingsViewModel(SettingsSecurity);
}
if (!bGoogle && !bFacebook && !bTwitter)
{
Utils.removeSettingsViewModel(SettingsSocialScreen);

View file

@ -317,6 +317,9 @@ Enums.Notification = {
'DomainNotAllowed': 109,
'AccountNotAllowed': 110,
'AccountTwoFactorAuthRequired': 120,
'AccountTwoFactorAuthError': 121,
'CantGetMessageList': 201,
'CantGetMessage': 202,
'CantDeleteMessage': 203,

View file

@ -179,7 +179,7 @@ NewHtmlEditorWrapper.prototype.init = function ()
{
self.editor.on('instanceReady', function () {
self.editor.setKeystroke(window.CKEDITOR.CTRL + 65/* A */, 'selectAll');
self.editor.setKeystroke(window.CKEDITOR.CTRL + 65 /* A */, 'selectAll');
self.fOnReady();
self.__resizable = true;

View file

@ -568,6 +568,9 @@ Utils.initNotificationLanguage = function ()
NotificationI18N[Enums.Notification.DomainNotAllowed] = Utils.i18n('NOTIFICATIONS/DOMAIN_NOT_ALLOWED');
NotificationI18N[Enums.Notification.AccountNotAllowed] = Utils.i18n('NOTIFICATIONS/ACCOUNT_NOT_ALLOWED');
NotificationI18N[Enums.Notification.AccountTwoFactorAuthRequired] = Utils.i18n('NOTIFICATIONS/ACCOUNT_TWO_FACTOR_AUTH_REQUIRED');
NotificationI18N[Enums.Notification.AccountTwoFactorAuthError] = Utils.i18n('NOTIFICATIONS/ACCOUNT_TWO_FACTOR_AUTH_ERROR');
NotificationI18N[Enums.Notification.CantGetMessageList] = Utils.i18n('NOTIFICATIONS/CANT_GET_MESSAGE_LIST');
NotificationI18N[Enums.Notification.CantGetMessage] = Utils.i18n('NOTIFICATIONS/CANT_GET_MESSAGE');
NotificationI18N[Enums.Notification.CantDeleteMessage] = Utils.i18n('NOTIFICATIONS/CANT_DELETE_MESSAGE');
@ -672,14 +675,14 @@ Utils.delegateRun = function (oObject, sMethodName, aParameters, nDelay)
Utils.killCtrlAandS = function (oEvent)
{
oEvent = oEvent || window.event;
if (oEvent)
if (oEvent && oEvent.ctrlKey && !oEvent.shiftKey && !oEvent.altKey)
{
var
oSender = oEvent.target || oEvent.srcElement,
iKey = oEvent.keyCode || oEvent.which
;
if (oEvent.ctrlKey && iKey === Enums.EventKeyCode.S)
if (iKey === Enums.EventKeyCode.S)
{
oEvent.preventDefault();
return;
@ -690,7 +693,7 @@ Utils.killCtrlAandS = function (oEvent)
return;
}
if (oEvent.ctrlKey && iKey === Enums.EventKeyCode.A)
if (iKey === Enums.EventKeyCode.A)
{
if (window.getSelection)
{
@ -1716,48 +1719,3 @@ Utils.selectElement = function (element)
}
/* jshint onevar: true */
};
Utils.openPgpImportPublicKeys = function (sPublicKeysArmored)
{
if (window.openpgp && RL)
{
var
oOpenpgpKeyring = RL.data().openpgpKeyring,
oImported = window.openpgp.key.readArmored(sPublicKeysArmored)
;
if (oOpenpgpKeyring && oImported && !oImported.err && Utils.isArray(oImported.keys) &&
0 < oImported.keys.length)
{
_.each(oImported.keys, function (oPrivKey) {
if (oPrivKey)
{
window.console.log(oPrivKey);
// var oKey = oOpenpgpKeyring.getKeysForKeyId(oPrivKey.primaryKey.getFingerprint());
// if (oKey && oKey[0])
// {
// if (oKey[0].isPublic())
// {
// oPrivKey.update(oKey[0]);
// oOpenpgpKeyring.publicKeys.removeForId(oPrivKey.primaryKey.getFingerprint());
// oOpenpgpKeyring.privateKeys.push(oPrivKey);
// }
// else
// {
// oKey[0].update(oPrivKey);
// }
// }
// else
// {
// oOpenpgpKeyring.importKey(oPrivKey.armored);
// }
}
});
oOpenpgpKeyring.store();
return true;
}
}
return false;
};

View file

@ -151,15 +151,14 @@ FolderModel.prototype.initComputed = function ()
{
return '' + iCount;
}
else if (0 < iUnread && Enums.FolderType.Trash !== iType && Enums.FolderType.SentItems !== iType)
else if (0 < iUnread && Enums.FolderType.Trash !== iType && Enums.FolderType.Archive !== iType && Enums.FolderType.SentItems !== iType)
{
return '' + iUnread;
}
}
return '';
// return 0 < iUnread && (Enums.FolderType.Inbox === iType || Enums.FolderType.Spam === iType) ? '' + iUnread :
// (0 < iCount && Enums.FolderType.Draft === iType ? '' + iCount : '');
}, this);
this.canBeDeleted = ko.computed(function () {

120
dev/Settings/Security.js Normal file
View file

@ -0,0 +1,120 @@
/* RainLoop Webmail (c) RainLoop Team | Licensed under CC BY-NC-SA 3.0 */
/**
* @constructor
*/
function SettingsSecurity()
{
this.processing = ko.observable(false);
this.clearing = ko.observable(false);
this.viewUser = ko.observable('');
this.viewEnable = ko.observable(false);
this.viewEnable.subs = true;
this.twoFactorStatus = ko.observable(false);
this.viewSecret = ko.observable('');
this.viewBackupCodes = ko.observable('');
this.viewUrl = ko.observable('');
this.bFirst = true;
this.viewTwoFactorStatus = ko.computed(function () {
Globals.langChangeTrigger();
return Utils.i18n(
this.twoFactorStatus() ?
'SETTINGS_SECURITY/TWO_FACTOR_SECRET_CONFIGURED_DESC' :
'SETTINGS_SECURITY/TWO_FACTOR_SECRET_NOT_CONFIGURED_DESC'
);
}, this);
this.onResult = _.bind(this.onResult, this);
}
Utils.addSettingsViewModel(SettingsSecurity, 'SettingsSecurity', 'SETTINGS_LABELS/LABEL_SECURITY_NAME', 'security');
SettingsSecurity.prototype.createTwoFactor = function ()
{
this.processing(true);
RL.remote().createTwoFactor(this.onResult);
};
SettingsSecurity.prototype.enableTwoFactor = function ()
{
this.processing(true);
RL.remote().enableTwoFactor(this.onResult, this.viewEnable());
};
SettingsSecurity.prototype.testTwoFactor = function ()
{
kn.showScreenPopup(PopupsTwoFactorTestViewModel);
};
SettingsSecurity.prototype.clearTwoFactor = function ()
{
this.viewSecret('');
this.viewBackupCodes('');
this.viewUrl('');
this.clearing(true);
RL.remote().clearTwoFactor(this.onResult);
};
SettingsSecurity.prototype.onShow = function ()
{
this.viewSecret('');
this.viewBackupCodes('');
this.viewUrl('');
};
SettingsSecurity.prototype.onResult = function (sResult, oData)
{
this.processing(false);
this.clearing(false);
if (Enums.StorageResultType.Success === sResult && oData && oData.Result)
{
this.viewUser(Utils.pString(oData.Result.User));
this.viewEnable(!!oData.Result.Enable);
this.twoFactorStatus(!!oData.Result.IsSet);
this.viewSecret(Utils.pString(oData.Result.Secret));
this.viewBackupCodes(Utils.pString(oData.Result.BackupCodes).replace(/[\s]+/g, ' '));
this.viewUrl(Utils.pString(oData.Result.Url));
}
else
{
this.viewUser('');
this.viewEnable(false);
this.twoFactorStatus(false);
this.viewSecret('');
this.viewBackupCodes('');
this.viewUrl('');
}
if (this.bFirst)
{
this.bFirst = false;
var self = this;
this.viewEnable.subscribe(function (bValue) {
if (this.viewEnable.subs)
{
RL.remote().enableTwoFactor(function (sResult, oData) {
if (Enums.StorageResultType.Success !== sResult || !oData || !oData.Result)
{
self.viewEnable.subs = false;
self.viewEnable(false);
self.viewEnable.subs = true;
}
}, bValue);
}
}, this);
}
};
SettingsSecurity.prototype.onBuild = function ()
{
this.processing(true);
RL.remote().getTwoFactor(this.onResult);
};

View file

@ -34,18 +34,74 @@ WebMailAjaxRemoteStorage.prototype.folders = function (fCallback)
* @param {string} sPassword
* @param {boolean} bSignMe
* @param {string=} sLanguage
* @param {string=} sAdditionalCode
*/
WebMailAjaxRemoteStorage.prototype.login = function (fCallback, sEmail, sLogin, sPassword, bSignMe, sLanguage)
WebMailAjaxRemoteStorage.prototype.login = function (fCallback, sEmail, sLogin, sPassword, bSignMe, sLanguage, sAdditionalCode)
{
this.defaultRequest(fCallback, 'Login', {
'Email': sEmail,
'Login': sLogin,
'Password': sPassword,
'Language': sLanguage || '',
'AdditionalCode': sAdditionalCode || '',
'SignMe': bSignMe ? '1' : '0'
});
};
/**
* @param {?Function} fCallback
*/
WebMailAjaxRemoteStorage.prototype.getTwoFactor = function (fCallback)
{
this.defaultRequest(fCallback, 'GetTwoFactorInfo');
};
/**
* @param {?Function} fCallback
*/
WebMailAjaxRemoteStorage.prototype.createTwoFactor = function (fCallback)
{
this.defaultRequest(fCallback, 'CreateTwoFactorSecret');
};
/**
* @param {?Function} fCallback
*/
WebMailAjaxRemoteStorage.prototype.clearTwoFactor = function (fCallback)
{
this.defaultRequest(fCallback, 'ClearTwoFactorInfo');
};
/**
* @param {?Function} fCallback
* @param {string} sCode
*/
WebMailAjaxRemoteStorage.prototype.testTwoFactor = function (fCallback, sCode)
{
this.defaultRequest(fCallback, 'TestTwoFactorInfo', {
'Code': sCode
});
};
/**
* @param {?Function} fCallback
* @param {boolean} bEnable
*/
WebMailAjaxRemoteStorage.prototype.enableTwoFactor = function (fCallback, bEnable)
{
this.defaultRequest(fCallback, 'EnableTwoFactor', {
'Enable': bEnable ? '1' : '0'
});
};
/**
* @param {?Function} fCallback
*/
WebMailAjaxRemoteStorage.prototype.clearTwoFactorInfo = function (fCallback)
{
this.defaultRequest(fCallback, 'ClearTwoFactorInfo');
};
/**
* @param {?Function} fCallback
* @param {string} sEmail

View file

@ -72,6 +72,9 @@ function WebMailDataStorage()
this.signatureToAll = ko.observable(false);
this.replyTo = ko.observable('');
// security
this.enableTwoFactor = ko.observable(false);
// accounts
this.accounts = ko.observableArray([]);
this.accountsLoading = ko.observable(false).extend({'throttle': 100});
@ -434,6 +437,7 @@ WebMailDataStorage.prototype.populateDataOnStart = function()
this.replyTo(RL.settingsGet('ReplyTo'));
this.signature(RL.settingsGet('Signature'));
this.signatureToAll(!!RL.settingsGet('SignatureToAll'));
this.enableTwoFactor(!!RL.settingsGet('EnableTwoFactor'));
this.lastFoldersHash = RL.local().get(Enums.ClientSideKeyName.FoldersLashHash) || '';

View file

@ -49,6 +49,7 @@
@import "Languages.less";
@import "AddAccount.less";
@import "OpenPgpKey.less";
@import "TwoFactor.less";
@import "Identity.less";
@import "AdvancedSearch.less";
@import "MessageList.less";

View file

@ -0,0 +1,7 @@
.popups {
.b-two-factor-test-content {
.modal-header {
background-color: #fff;
}
}
}

View file

@ -15,6 +15,11 @@ function LoginViewModel()
this.password = ko.observable('');
this.signMe = ko.observable(false);
this.additionalCode = ko.observable('');
this.additionalCode.error = ko.observable(false);
this.additionalCode.focused = ko.observable(false);
this.additionalCode.visibility = ko.observable(false);
this.logoImg = Utils.trim(RL.settingsGet('LoginLogo'));
this.loginDescription = Utils.trim(RL.settingsGet('LoginDescription'));
this.logoCss = Utils.trim(RL.settingsGet('LoginCss'));
@ -29,6 +34,8 @@ function LoginViewModel()
this.email.subscribe(function () {
this.emailError(false);
this.additionalCode('');
this.additionalCode.visibility(false);
}, this);
this.login.subscribe(function () {
@ -39,6 +46,14 @@ function LoginViewModel()
this.passwordError(false);
}, this);
this.additionalCode.subscribe(function () {
this.additionalCode.error(false);
}, this);
this.additionalCode.visibility.subscribe(function () {
this.additionalCode.error(false);
}, this);
this.submitRequest = ko.observable(false);
this.submitError = ko.observable('');
@ -68,7 +83,12 @@ function LoginViewModel()
this.emailError('' === Utils.trim(this.email()));
this.passwordError('' === Utils.trim(this.password()));
if (this.emailError() || this.passwordError())
if (this.additionalCode.visibility())
{
this.additionalCode.error('' === Utils.trim(this.additionalCode()));
}
if (this.emailError() || this.passwordError() || this.additionalCode.error())
{
return false;
}
@ -81,12 +101,28 @@ function LoginViewModel()
{
if (oData.Result)
{
RL.loginAndLogoutReload();
if (oData.TwoFactorAuth)
{
this.additionalCode('');
this.additionalCode.visibility(true);
this.additionalCode.focused(true);
this.submitRequest(false);
}
else
{
RL.loginAndLogoutReload();
}
}
else if (oData.ErrorCode)
{
this.submitRequest(false);
this.submitError(Utils.getNotification(oData.ErrorCode));
if ('' === this.submitError())
{
this.submitError(Utils.getNotification(Enums.Notification.UnknownError));
}
}
else
{
@ -100,7 +136,8 @@ function LoginViewModel()
}
}, this), this.email(), this.login(), this.password(), !!this.signMe(),
this.bSendLanguage ? this.mainLanguage() : '');
this.bSendLanguage ? this.mainLanguage() : '',
this.additionalCode.visibility() ? this.additionalCode() : '');
return true;

View file

@ -929,12 +929,12 @@ PopupsComposeViewModel.prototype.onBuild = function ()
if (oEvent && self.modalVisibility() && RL.data().useKeyboardShortcuts())
{
if (self.bAllowCtrlS && oEvent.ctrlKey && Enums.EventKeyCode.S === oEvent.keyCode)
if (self.bAllowCtrlS && oEvent.ctrlKey && !oEvent.shiftKey && !oEvent.altKey && Enums.EventKeyCode.S === oEvent.keyCode)
{
self.saveCommand();
bResult = false;
}
else if (oEvent.ctrlKey && Enums.EventKeyCode.Enter === oEvent.keyCode)
else if (oEvent.ctrlKey && !oEvent.shiftKey && !oEvent.altKey && Enums.EventKeyCode.Enter === oEvent.keyCode)
{
self.sendCommand();
bResult = false;

View file

@ -0,0 +1,55 @@
/* RainLoop Webmail (c) RainLoop Team | Licensed under CC BY-NC-SA 3.0 */
/**
* @constructor
* @extends KnoinAbstractViewModel
*/
function PopupsTwoFactorTestViewModel()
{
KnoinAbstractViewModel.call(this, 'Popups', 'PopupsTwoFactorTest');
var self = this;
this.code = ko.observable('');
this.code.focused = ko.observable(false);
this.code.status = ko.observable(null);
this.testing = ko.observable(false);
// commands
this.testCode = Utils.createCommand(this, function () {
this.testing(true);
RL.remote().testTwoFactor(function (sResult, oData) {
self.testing(false);
self.code.status(Enums.StorageResultType.Success === sResult && oData && oData.Result ? true : false);
}, this.code());
}, function () {
return '' !== this.code() && !this.testing();
});
Knoin.constructorEnd(this);
}
Utils.extendAsViewModel('PopupsTwoFactorTestViewModel', PopupsTwoFactorTestViewModel);
PopupsTwoFactorTestViewModel.prototype.clearPopup = function ()
{
this.code('');
this.code.focused(false);
this.code.status(null);
this.testing(false);
};
PopupsTwoFactorTestViewModel.prototype.onShow = function ()
{
this.clearPopup();
};
PopupsTwoFactorTestViewModel.prototype.onFocus = function ()
{
this.code.focused(true);
};