diff --git a/app/assets/javascripts/app/frontend/models/api/mfa.js b/app/assets/javascripts/app/frontend/models/api/mfa.js new file mode 100644 index 000000000..41e8cce5d --- /dev/null +++ b/app/assets/javascripts/app/frontend/models/api/mfa.js @@ -0,0 +1,33 @@ +class Mfa extends Item { + + constructor(json_obj) { + super(json_obj); + } + + mapContentToLocalProperties(content) { + super.mapContentToLocalProperties(content) + this.name = content.name; + } + + structureParams() { + var params = { + name: this.name, + }; + + _.merge(params, super.structureParams()); + return params; + } + + toJSON() { + return {uuid: this.uuid} + } + + get content_type() { + return "SN|MFA"; + } + + doNotEncrypt() { + return true; + } + +} diff --git a/app/assets/javascripts/app/services/authManager.js b/app/assets/javascripts/app/services/authManager.js index 9763e716e..e4588e5ba 100644 --- a/app/assets/javascripts/app/services/authManager.js +++ b/app/assets/javascripts/app/services/authManager.js @@ -117,7 +117,7 @@ angular.module('app.frontend') } } - this.login = function(url, email, password, ephemeral, callback) { + this.login = function(url, email, password, ephemeral, extraParams, callback) { this.getAuthParamsForEmail(url, email, function(authParams){ if(!authParams || !authParams.pw_cost) { @@ -150,7 +150,7 @@ angular.module('app.frontend') Neeto.crypto.computeEncryptionKeysForUser(_.merge({password: password}, authParams), function(keys){ var requestUrl = url + "/auth/sign_in"; - var params = {password: keys.pw, email: email}; + var params = _.merge({password: keys.pw, email: email}, extraParams); httpManager.postAbsolute(requestUrl, params, function(response){ this.setEphemeral(ephemeral); this.handleAuthResponse(response, email, url, authParams, keys); diff --git a/app/assets/javascripts/app/services/directives/views/accountMenu.js b/app/assets/javascripts/app/services/directives/views/accountMenu.js index 21ddfe81d..83f033aa0 100644 --- a/app/assets/javascripts/app/services/directives/views/accountMenu.js +++ b/app/assets/javascripts/app/services/directives/views/accountMenu.js @@ -89,6 +89,12 @@ class AccountMenu { }) } + $scope.submitMfaForm = function() { + var params = {}; + params[$scope.formData.mfa.payload.mfa_key] = $scope.formData.userMfaCode; + $scope.login(params); + } + $scope.submitAuthForm = function() { if($scope.formData.showLogin) { $scope.login(); @@ -97,19 +103,25 @@ class AccountMenu { } } - $scope.login = function() { + $scope.login = function(extraParams) { $scope.formData.status = "Generating Login Keys..."; $timeout(function(){ - authManager.login($scope.formData.url, $scope.formData.email, $scope.formData.user_password, $scope.formData.ephemeral, function(response){ - if(!response || response.error) { - $scope.formData.status = null; - var error = response ? response.error : {message: "An unknown error occured."} - if(!response || (response && !response.didDisplayAlert)) { - alert(error.message); + authManager.login($scope.formData.url, $scope.formData.email, $scope.formData.user_password, $scope.formData.ephemeral, extraParams, + (response) => { + if(!response || response.error) { + $scope.formData.status = null; + var error = response ? response.error : {message: "An unknown error occured."} + if(error.tag == "mfa-required") { + $timeout(() => { + $scope.formData.showLogin = false; + $scope.formData.mfa = error; + }) + } else if(!response || (response && !response.didDisplayAlert)) { + alert(error.message); + } + } else { + $scope.onAuthSuccess(); } - } else { - $scope.onAuthSuccess(); - } }); }) } diff --git a/app/assets/javascripts/app/services/modelManager.js b/app/assets/javascripts/app/services/modelManager.js index 831b184b8..73a49d80b 100644 --- a/app/assets/javascripts/app/services/modelManager.js +++ b/app/assets/javascripts/app/services/modelManager.js @@ -18,7 +18,7 @@ class ModelManager { this._extensions = []; this.acceptableContentTypes = [ "Note", "Tag", "Extension", "SN|Editor", "SN|Theme", - "SN|Component", "SF|Extension", "SN|UserPreferences" + "SN|Component", "SF|Extension", "SN|UserPreferences", "SF|MFA" ]; } @@ -211,6 +211,8 @@ class ModelManager { item = new Component(json_obj); } else if(json_obj.content_type == "SF|Extension") { item = new SyncAdapter(json_obj); + } else if(json_obj.content_type == "SF|MFA") { + item = new Mfa(json_obj); } else { diff --git a/app/assets/templates/frontend/directives/account-menu.html.haml b/app/assets/templates/frontend/directives/account-menu.html.haml index 87ca42432..099460fd9 100644 --- a/app/assets/templates/frontend/directives/account-menu.html.haml +++ b/app/assets/templates/frontend/directives/account-menu.html.haml @@ -5,7 +5,7 @@ -# Account Section .mb-10 - .step-one{"ng-if" => "!formData.showLogin && !formData.showRegister"} + .step-one{"ng-if" => "!formData.showLogin && !formData.showRegister && !formData.mfa"} %h3 Sign in or register to enable sync and end-to-end encryption. .small-v-space @@ -41,6 +41,11 @@ Merge local data ({{notesAndTagsCount()}} notes and tags) %button.ui-button.block.mt-10{"ng-click" => "submitAuthForm()"} {{formData.showLogin ? "Sign In" : "Register"}} + %form.mt-5{"ng-if" => "formData.mfa"} + %p {{formData.mfa.message}} + %input.form-control.mt-10{:autofocus => 'autofocus', :name => 'mfa', :required => true, 'ng-model' => 'formData.userMfaCode'} + %button.ui-button.block.mt-10{"ng-click" => "submitMfaForm()"} {{"Sign In"}} + .mt-15{"ng-if" => "formData.showRegister"} %h3 No Password Reset. %p.mt-5 Because your notes are encrypted using your password, Standard Notes does not have a password reset option. You cannot forget your password.