Add mobile authentication flow
Project: http://git-wip-us.apache.org/repos/asf/incubator-milagro-mfa-js-lib/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-milagro-mfa-js-lib/commit/f84d8c7b Tree: http://git-wip-us.apache.org/repos/asf/incubator-milagro-mfa-js-lib/tree/f84d8c7b Diff: http://git-wip-us.apache.org/repos/asf/incubator-milagro-mfa-js-lib/diff/f84d8c7b Branch: refs/heads/add-documentation Commit: f84d8c7b790c79ef9be6b8aff149b04d5e663e02 Parents: f27fba9 Author: Boyan Bakov <[email protected]> Authored: Thu Dec 10 17:17:13 2015 +0200 Committer: Vladislav Mitov <[email protected]> Committed: Fri Dec 18 18:55:25 2015 +0200 ---------------------------------------------------------------------- lib/mpin.js | 236 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 174 insertions(+), 62 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-milagro-mfa-js-lib/blob/f84d8c7b/lib/mpin.js ---------------------------------------------------------------------- diff --git a/lib/mpin.js b/lib/mpin.js index 143916b..879b461 100644 --- a/lib/mpin.js +++ b/lib/mpin.js @@ -8,6 +8,7 @@ var mpinjs = (function () { Errors[4] = "IDENTITY_MISSING"; Errors[5] = "WRONG_PIN"; Errors[6] = "WRONG_FLOW"; + Errors[7] = "TIMEOUT_FINISH"; /// Status.invalid = "INVALID"; @@ -22,6 +23,8 @@ var mpinjs = (function () { } this.opts = options; + + this.recover(); }; Mpin.prototype.storageKey = "mpinLib"; @@ -46,11 +49,7 @@ var mpinjs = (function () { return {code: 0, type: Errors[0]}; } - Users[userId] = { - userId: userId, - deviceId: deviceId || "", - status: Status.invalid - }; + this.addToUser(userId, {userId: userId, deviceId: deviceId, status: Status.invalid}); return this; }; @@ -77,8 +76,6 @@ var mpinjs = (function () { return cb(err, null); } - self.identity = data.mpinId; - self.addToUser(userId, {regOTT: data.regOTT, mpinId: data.mpinId, status: Status.start}); //force activate @@ -90,7 +87,6 @@ var mpinjs = (function () { }); }; - //request cs1 + cs2 Mpin.prototype.confirmRegistration = function (userId, cb) { var _cs1Url = "", self = this, _userStatus; @@ -100,7 +96,7 @@ var mpinjs = (function () { } else if (!this.checkUser(userId)) { return cb({code: 1, type: Errors[1]}, null); } else if (!this.opts.signatureURL) { - return cb({code: 2, type: Errors[2], message: "Missing signatureURL"}, null); + return cb({code: 2, type: Errors[2], message: "Missing signatureURL option."}, null); } //started || activated @@ -109,7 +105,12 @@ var mpinjs = (function () { return cb({code: 3, type: Errors[3]}, null); } - _cs1Url = this.generateUrl('signature', userId); + //already set. + if (Users[userId].csHex) { + return cb(null, true); + } + + _cs1Url = this.generateUrl('signature', {userId: userId}); //req cs1 this.request({url: _cs1Url}, function (err, cs1Data) { var _cs2Url = ""; @@ -156,7 +157,7 @@ var mpinjs = (function () { tokenHex = MPINAuth.calculateMPinToken(Users[userId].mpinId, pin, Users[userId].csHex); this.addToUser(userId, {tokenHex: tokenHex, status: Status.register}); - return tokenHex; + return true; }; //Put user / mpinId @@ -168,11 +169,26 @@ var mpinjs = (function () { Mpin.prototype.startAuthentication = function (userId, cb) { - var _tp1Url, self = this; + var _tp1Url, self = this, _userStatus; + + if (!userId || typeof userId !== "string") { + return cb ? cb({code: 0, type: Errors[0]}, null) : {error: 0, type: Errors[0]}; + } else if (!this.checkUser(userId)) { + return cb({code: 1, type: Errors[1]}, null); + } else if (!this.opts.timePermitsURL || !this.opts.certivoxURL) { + return cb({code: 2, type: Errors[2], message: "Missing timePermitsURL or/and certivoxURL option."}, null); + } + + //registered + _userStatus = this.getUser(userId, "status"); + if (_userStatus !== Status.register) { + return cb({code: 3, type: Errors[3]}, null); + } + //checkUser _tp1Url = this.generateUrl('permit1'); this.request({url: _tp1Url}, function (err, data) { - var _signature, _tp2Url, _timePermit1; + var _signature, _tp2Url, _timePermit1, _storageUrl; _signature = data["signature"]; _timePermit1 = data["timePermit"]; @@ -181,20 +197,51 @@ var mpinjs = (function () { _tp2Url = self.generateUrl('permit2'); _tp2Url += "&signature=" + _signature; - - self.request({url: _tp2Url}, function (err2, data2) { - var _timePermit2, timePermitHex; - _timePermit2 = data2["timePermit"]; - timePermitHex = MPINAuth.addShares(_timePermit1, _timePermit2); + //check cache if exist + if (Users[userId].timePermitCache && Users[userId].timePermitCache.date === data.date) { + var _timePermit2 = Users[userId].timePermitCache.timePermit; + var timePermitHex = MPINAuth.addShares(_timePermit1, _timePermit2); self.addToUser(userId, {timePermitHex: timePermitHex}); + cb && cb(null, true); //exit with cache permit2 + return; + } else { + _storageUrl = self.generateUrl("storage", {date: data.date, storageId: data.storageId}); + + self.request({url: _storageUrl}, function (storErr, storData) { + if (storErr) { + self._getTimePermit2({userId: userId, permit1: _timePermit1, permit2Url: _tp2Url, date: data.date}, cb); //continue + return; + } - cb && cb(null, true); - }); + var _timePermit2 = storData; + var timePermitHex = MPINAuth.addShares(_timePermit1, _timePermit2); + + self.addToUser(userId, {timePermitHex: timePermitHex, timePermitCache: {date: data.date, timePermit: _timePermit2}}); + + cb && cb(null, true); //exit with storage permit2 + }, false); + } + }); + }; + Mpin.prototype._getTimePermit2 = function (options, cb) { + var self = this, _timePermit1 = options.permit1; + this.request({url: options.permit2Url}, function (err2, data2) { + var _timePermit2, timePermitHex, _permitCache = {}; + _timePermit2 = data2["timePermit"]; + timePermitHex = MPINAuth.addShares(_timePermit1, _timePermit2); + + _permitCache.date = options.date; + _permitCache.timePermit = data2["timePermit"]; + + self.addToUser(options.userId, {timePermitHex: timePermitHex, timePermitCache: _permitCache}); + + cb && cb(null, true); }); }; + Mpin.prototype.finishAuthentication = function (userId, aPin, cb) { var self = this, _reqData = {}; @@ -230,7 +277,7 @@ var mpinjs = (function () { } } - console.log("auth Data :::", authData); + cb && cb(null, true); }); }); }); @@ -239,7 +286,7 @@ var mpinjs = (function () { Mpin.prototype.getAuthData = function (userId, aPin) { var _auth = {}; - _auth.mpin = this.identity; + _auth.mpin = this.getIdentity(); _auth.token = Users[userId].tokenHex; _auth.timePermit = Users[userId].timePermitHex; _auth.date = Users[userId].currentDate; @@ -273,56 +320,54 @@ var mpinjs = (function () { if (err) { return cb(err, null); } - console.log("OTT :::", data.webOTT); self.webOTT = data.webOTT; - cb(null, {accessNumber: data.accessNumber}); + cb && cb(null, {accessNumber: data.accessNumber}); }); }; - Mpin.prototype.getAccess = function (cb) { + Mpin.prototype.waitForMobileAuth = function (timeoutSeconds, cb) { var self = this, _reqData = {}; - if (!this.webOTT) { return cb({code: 6, type: Errors[6], message: "Need to call getAccessNumber method before this."}, null); + } else if (!timeoutPeriod) { + return cb({code: 2, type: Errors[2], message: "Missing timeout/expiration period(in seconds)."}, null); } + + + this.timeoutPeriod || (this.timeoutPeriod = timeoutSeconds * 1000); + _reqData.url = this.generateUrl("getaccess"); _reqData.type = "POST"; _reqData.data = {webOTT: this.webOTT}; this.request(_reqData, function (err, data) { if (err) { - if (err.status === 401) { + if (err.status === 401 && self.timeoutPeriod > 0) { + self.timeoutPeriod -= 3000; self.intervalID2 = setTimeout(function () { - self.getAccess.call(self, cb); + self.waitForMobileAuth.call(self, timeoutSeconds, cb); }, 3000); return; + } else if (self.timeoutPeriod <= 0) { + cb && cb({code: 7, type: Errors[7]}, null); + return; } } - /* - * self.intervalID2 = setTimeout(function () { - self.getAccess.call(self); - }, 3000); - * - */ - console.log("data :::", err, data); - - cb(null, true); - - + cb && cb(null, true); }); }; - Mpin.prototype.stopAccess = function () { + Mpin.prototype.cancelMobileAuth = function () { if (this.intervalID2) { clearInterval(this.intervalID2); } }; - Mpin.prototype.generateUrl = function (type, userId) { + Mpin.prototype.generateUrl = function (type, options) { var url, mpData, mpin_id_bytes, hash_mpin_id_bytes = [], hash_mpin_id_hex; switch (type) { @@ -331,15 +376,15 @@ var mpinjs = (function () { break; case "signature": url = this.opts.signatureURL + "/"; - url += Users[userId].mpinId; - url += "?regOTT=" + Users[userId].regOTT; + url += Users[options.userId].mpinId; + url += "?regOTT=" + Users[options.userId].regOTT; break; case "permit1": url = this.opts.timePermitsURL + "/"; - url += this.identity; + url += this.getIdentity(); break; case "permit2": - mpData = this.fromHex(this.identity); + mpData = this.fromHex(this.getIdentity()); mpin_id_bytes = MPIN.stringtobytes(mpData); hash_mpin_id_bytes = MPIN.HASH_ID(mpin_id_bytes); hash_mpin_id_hex = MPIN.bytestostring(hash_mpin_id_bytes); @@ -363,19 +408,23 @@ var mpinjs = (function () { case "getaccess": url = this.opts.accessNumberURL; break; + case "storage": + url = this.opts.timePermitsStorageURL + "/" + this.opts.appID + "/"; + url += options.date + "/" + options.storageId; + //return that.opts.timePermitsStorageURL + "/" + that.opts.appID + "/" + date + "/" + storageId; + break; } return url; }; - Mpin.prototype.listUsers = function () { var listUsers = {}; for (var uKey in Users) { listUsers[uKey] = { userId: Users[uKey].userId, - deviceId: Users[uKey].deviceId, - status: Users[uKey].status + deviceId: Users[uKey].deviceId || "", + status: Users[uKey].status || "" }; } return listUsers; @@ -395,7 +444,7 @@ var mpinjs = (function () { _user = { userId: Users[userId].userId, - deviceId: Users[userId].deviceId, + deviceId: Users[userId].deviceId || "", status: Users[userId].status }; @@ -406,16 +455,30 @@ var mpinjs = (function () { } }; - Mpin.prototype.addToUser = function (userId, userProps) { - if (!this.checkUser(userId)) { + //remove this + Mpin.prototype.getUsers = function () { + return Users; + }; + + Mpin.prototype.addToUser = function (userId, userProps, skipSave) { + var _save; + if (!this.checkUser(userId) && !userProps.userId) { return false; } + _save = !skipSave; + + //create + if (userProps.userId) { + Users[userId] = {}; + } for (var uKey in userProps) { - Users[userId][uKey] = userProps[uKey]; + if (userProps[uKey]) { + Users[userId][uKey] = userProps[uKey]; + } } - this.setData(userId, userProps); + _save && this.setData(userId, userProps); }; Mpin.prototype.restore = function () { @@ -423,8 +486,7 @@ var mpinjs = (function () { }; Mpin.prototype.setData = function (userId, upData) { - var mpinData; - mpinData = JSON.parse(localStorage.getItem(this.storageKey)); + var mpinData = this.getData(); if (!mpinData) { mpinData = { @@ -454,33 +516,83 @@ var mpinjs = (function () { mpinData.accounts[mpinId].token = upData.tokenHex; } + if (upData.status && Users[userId].mpinId) { + var mpinId = Users[userId].mpinId; + mpinData.accounts[mpinId].status = upData.status; + } + + //cache cache + if (upData.timePermitCache) { + var mpinId = Users[userId].mpinId; + mpinData.accounts[mpinId].timePermitCache = upData.timePermitCache; + } + localStorage.setItem(this.storageKey, JSON.stringify(mpinData)); }; + Mpin.prototype.getIdentity = function () { + var mpinData = this.getData(); + return mpinData.defaultIdentity; + }; + + Mpin.prototype.recover = function () { + var userId, userData = {}, mpinData = this.getData(); + + if (mpinData && "accounts" in mpinData) { + for (var mpinId in mpinData.accounts) { + userId = (JSON.parse(this.fromHex(mpinId))).userID; + + userData = {}; + userData.userId = userId; + userData.mpinId = mpinId; + + mpinData.accounts[mpinId].regOTT && (userData.regOTT = mpinData.accounts[mpinId].regOTT); + mpinData.accounts[mpinId].token && (userData.token = mpinData.accounts[mpinId].token); + mpinData.accounts[mpinId].MPinPermit && (userData.MPinPermit = mpinData.accounts[mpinId].MPinPermit); + mpinData.accounts[mpinId].timePermitCache && (userData.timePermitCache = mpinData.accounts[mpinId].timePermitCache); + + mpinData.accounts[mpinId].status && (userData.status = mpinData.accounts[mpinId].status); + + //call add To user & skip Save + this.addToUser(userId, userData, true); + } + } + }; + Mpin.prototype.getData = function () { var mpinData; - mpinData = JSON.parse(localStorage.getItem(this.storageKey)); + mpinData = localStorage.getItem(this.storageKey); + mpinData = JSON.parse(mpinData); return mpinData; }; //{url: url, type: "get post put", data: data} - Mpin.prototype.request = function (options, cb) { - var _request = new XMLHttpRequest(), _url, _type, _data; + Mpin.prototype.request = function (options, cb, jsonResponse) { + var _request = new XMLHttpRequest(), _url, _type, _parseJson; _url = options.url || ""; _type = options.type || "GET"; - _data = options.data || ""; + + _parseJson = jsonResponse || true; _request.onreadystatechange = function () { if (_request.readyState === 4 && _request.status === 200) { - cb(null, JSON.parse(_request.responseText)); + if (_parseJson) { + cb(null, JSON.parse(_request.responseText)); + } else { + cb(null, _request.responseText); + } } else if (_request.readyState === 4) { cb({status: _request.status}, null); } }; _request.open(_type, _url, true); - _request.setRequestHeader("Content-Type", "application/json"); - _request.send(JSON.stringify(_data || "")); + if (options.data) { + _request.setRequestHeader("Content-Type", "application/json"); + _request.send(JSON.stringify(options.data)); + } else { + _request.send(); + } }; return Mpin;
