Accounts.validateLoginAttempt(function(attempt){ var allowed = [ 'login', 'verifyEmail', 'resetPassword' ]; if (_.contains(allowed, attempt.methodName) && attempt.type == 'resume'){ return true; } return false; });
var generateLoginToken = function(){ var stampedToken = Accounts._generateStampedLoginToken(); return [ stampedToken, Accounts._hashStampedToken(stampedToken) ]; }; var saveLoginToken = function(userId){ return Meteor.wrapAsync(function(userId, tokens, cb){ // In tokens array first is stamped, second is hashed // Save hashed to Mongo Meteor.users.update(userId, { $push: { 'services.resume.loginTokens': tokens[1] } }, function(error){ if (error){ cb(new Meteor.Error(500, 'Couldnt save login token into user profile')); }else{ // Return stamped to user cb && cb(null, [200,tokens[0].token]); } }); })(userId, generateLoginToken()); };
Meteor.methods({ 'LoginProcedure': function(username, pswdDigest, code, hash){ //Here perform some checks //I'll leave it up to you //Something to prevent NoSQL-Injections etc. ... //Now check if user already exists var user = Meteor.users.findOne({ '$or': [ { 'username': username }, { 'emails.address': username } ] }); if (!user) throw new Meteor.Error(404, 'fail'); //Now password checks //Explanations about this are right after the code var password = {digest: pswdDigest, algorithm: 'sha-256'}; var pswdCheck = Accounts._checkPassword(user, password); if (pswdCheck.error) throw new Meteor.Error(403,'fail'); //Next check if two-factor is enabled //If it's not, just generate token and return it //Else start the procedure... if (!user.twoFactorEnabled){ //Use function defined above return saveLoginToken(user._id); }else{ //Step 3-7 ... } } });
Meteor.methods({ 'LoginProcedure': function(username, pswdDigest, code, hash){ //Steps 1-2 ... if (!user.twoFactorEnabled){ //Steps 1-2 ... }else{ if (code && hash){ //Step 6-7 ... }else(hash){ //That part is for continuing previous session //New code will not be sent, but client-side app //will receive special response code and open the pop-up var session = TwoFactorSessions.findOne({ hash: hash, username: username }); if (session){ //Lets use some imaginary validation function //that you will define by your own in your project validateSession(session, user); return [401, hash]; }else{ // Couldnt find, return error throw new Meteor.Error(404, 'No session'); } }else{ //Generated code, i'll leave it up to you var newCode = <code here>; //The now date can be used as hash, just timestamp var now = new Date(); var hash = +now; //Save it to special collection for suspended sign-in processes TwoFactorSessions.insert({ hash: hash, code: newCode, username: username, sent: now }); // Wrap async task return Meteor.wrapAsync(function(user, hash, code, startTime, cb){ // Send code using Twilio to the phone number of user Twilio.messages.create({ to: user.phone, from: '+000000000000', body: 'Hi! Code - '+code }, function(error, message){ if (error){ // Return error with Twilio cb && cb(new Meteor.Error(500, 'Twilio error')); }else{ // Return 403, saying that SMS has been sent // hash, which user will send to us with code to identify his TF session cb && cb(null, [403, hash]); } }); })(user, hash, newCode, now); } } } });
Template.signIn.events({ ... 'submit #signInForm': function(e) { e.preventDefault(); //Here go your methods for retreiving //username/email and password var username = ...; var password = ...; var pswdDigest = Package.sha.SHA256(password); // Check if there is previous Two-Factor session var sessionHash = Storage.get('two-factor-auth-hash'); if (sessionHash){ //Validate it maybe? //We have additional value here, code expiration time var valid = validateItHereAsYouWant(); if (!valid) sessionHash = null; } //Now actual login procedure start Meteor.call('LoginProcedure', username, pswdDigest, null, sessionHash, function(error, response){ if (error){ if (error.error === 400){ // That code would mean that session is invalid Storage.remove('two-factor-auth-hash'); // Show some alerts here } }else if (response[0]===200){ // That response code would mean that // two-factor authentication is turned off // and client received new login token immediately // right after passing simple username/password check Meteor.loginWithToken(response[1], function(err){ if(err){ alert('Problem!'); }else{ Router.go('Account'); } }); }else if (response[0]===403){ // That response code would mean that second factor code is sent // Open modal window with code input field $('#modal').modal(); // Save hash into storage for continuation Storage.set('two-factor-auth-hash', response[1]); // Show alert saying the code was sent }else if (response[0]===401){ // Open modal window with code input field $('#modal').modal(); // Show alert that there is previous code that awaits input } } ... 'click #modal-code-submit': function(e){ e.preventDefault(); // Read the code, get the id hash var code = $('#modal-code-input').val(); var hash = Storage.get('two-factor-auth-hash'); // Again get the values inside fields // i mean username and password ... // Throught the net, only the digest should go var pswdDigest = Package.sha.SHA256(pswd); // Perform login again, but with code and id hash Meteor.call('LoginProcedure', username, pswdDigest, code, hash, function(error, response){ if (error){ if (error.error === 400){ // That error code would mean that session is invalid Storage.remove('two-factor-auth-hash'); Storage.remove('two-factor-auth-ttl'); $('#modal').modal('toggle'); // Show some error alerts } }else if (response[0]===200){ // Seems like ok, login token received Storage.remove('two-factor-auth-hash'); // Login Meteor.loginWithToken(response[1]); } }); } });
Meteor.methods({ 'LoginProcedure': function(username, pswdDigest, code, hash){ //Steps 1-2 ... if (!user.twoFactorEnabled){ //Steps 1-2 ... }else{ if (code && hash){ //All 4 arguments present here //First factor has already been passed since we're here //Process second factor var session = TwoFactorSessions.findOne({ hash: hash, username: username }); if (session){ //Lets use some imaginary validation function //that you will define by your own in your project validateSession(session, user, code); // Passed all checks // Update two-factor session with submitted date TwoFactorSessions.update({ hash: hash }, { $set: { submitted: new Date() } }); // Generate and save login token using // previously defined function (look for it in steps 1-2) return saveLoginToken(user._id); }else{ // Couldnt find, return error throw new Meteor.Error(404, 'twoFactor.invalidHash'); } }else(hash){ //Step 3-5 ... }else{ //Step 3-5 ... } } } });
Source: https://habr.com/ru/post/252593/
All Articles