blob: 12901c3bf5578d5692402fed14d8af17a0f49292 [file] [log] [blame]
/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
*/
var mpinjs = (function () {
var Mpin, Users = {}, Errors = {}, States = {};
Errors.missingUserId = {code: 0, type: "MISSING_USERID"};
Errors.invalidUserId = {code: 1, type: "INVALID_USERID"};
Errors.missingParams = {code: 2, type: "MISSING_PARAMETERS"};
Errors.identityNotVerified = {code: 3, type: "IDENTITY_NOT_VERIFIED"};
Errors.identityMissing = {code: 4, type: "IDENTITY_MISSING"};
Errors.wrongPin = {code: 5, type: "WRONG_PIN"};
Errors.wrongFlow = {code: 6, type: "WRONG_FLOW"};
Errors.userRevoked = {code: 7, type: "USER_REVOKED"};
Errors.timeoutFinish = {code: 8, type: "TIMEOUT_FINISH"};
Errors.requestExpired = {code: 9, type: "REQUEST_EXPIRED"};
Errors.identityNotAuthorized = {code: 10, type: "IDENTITY_NOT_AUTHORIZED"};
Errors.incorrectAccessNumber = {code: 11, type: "INCORRECT_ACCESS_NUMBER"};
States.invalid = "INVALID";
States.start = "STARTED";
States.active = "ACTIVATED";
States.register = "REGISTERED";
States.block = "BLOCKED";
Mpin = function (options) {
if (!options || !options.server) {
return new Error("Missing server URL");
}
this.opts = options;
this.settings = {};
};
Mpin.prototype.storageKey = "mpinjs";
//supportedProtocols
// temporary until extend with other portocols
// then supported should be object of objects
Mpin.prototype.cfg = {
protocols: {
supported: ["2pass"],
default: "2pass"
}
};
Mpin.prototype.init = function (cb) {
var self = this, _initUrl;
this.recover();
if (this.opts.server.slice(-1) === "/") {
_initUrl = this.opts.server;
} else {
_initUrl = this.opts.server + "/";
}
_initUrl += this.opts.rpsPrefix || "rps";
_initUrl += "/clientSettings";
this.request({url: _initUrl}, function (err, data) {
if (err && cb) {
return cb(err, null);
}
self.ready = true;
self.settings = data;
self.chooseAuthProtocol();
cb && cb(null, true);
});
};
Mpin.prototype.makeNewUser = function (userId, deviceId) {
if (!userId) {
return Errors.missingUserId;
}
this.addToUser(userId, {userId: userId, deviceId: deviceId, state: States.invalid});
return this;
};
Mpin.prototype.startRegistration = function (userId, cb) {
var _reqData = {}, self = this, _userState;
if (!userId) {
return cb ? cb(Errors.missingUserId, null) : {error: 1};
} else if (!this.checkUser(userId)) {
return cb(Errors.invalidUserId, null);
} else if (!this.settings.registerURL) {
return cb({code: Errors.missingParams.code, type: Errors.missingParams.type, message: "Missing registerURL"}, null);
}
//invalid
_userState = this.getUser(userId, "state");
if (_userState !== States.invalid) {
return cb(Errors.wrongFlow, null);
}
_reqData.url = this.generateUrl("register");
_reqData.type = "PUT";
_reqData.data = {
userId: userId,
mobile: 0
};
if (Users[userId].deviceId) {
_reqData.data.deviceName = Users[userId].deviceId;
}
this.request(_reqData, function (err, data) {
if (err) {
return cb(err, null);
}
self.addToUser(userId, {regOTT: data.regOTT, mpinId: data.mpinId, state: States.start});
//force activate
if (data.active) {
self.addToUser(userId, {state: States.active});
}
cb && cb(null, true);
});
};
//request cs1 + cs2
Mpin.prototype.confirmRegistration = function (userId, cb) {
var _cs1Url = "", self = this, _userState;
if (!userId) {
return cb ? cb(Errors.missingUserId, null) : Errors.missingUserId;
} else if (!this.checkUser(userId)) {
return cb(Errors.invalidUserId, null);
} else if (!this.settings.signatureURL) {
return cb({code: Errors.missingParams.code, type: Errors.missingParams.type, message: "Missing signatureURL option."}, null);
}
//started || activated
_userState = this.getUser(userId, "state");
if (_userState !== States.start && _userState !== States.active) {
return cb(Errors.wrongFlow, null);
}
//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 = "";
if (err) {
if (err.status == 401) {
return cb(Errors.identityNotVerified, null);
} else if (err.status == 400) {
return cb(Errors.wrongFlow, null);
}
}
_cs2Url = self.settings.certivoxURL + "clientSecret?" + cs1Data.params;
//req cs2
self.request({url: _cs2Url}, function (err, cs2Data) {
var csHex;
csHex = MPINAuth.addShares(cs2Data.clientSecret, cs1Data.clientSecretShare);
self.addToUser(userId, {csHex: csHex, state: States.active});
cb(null, true);
});
});
};
Mpin.prototype.finishRegistration = function (userId, pin) {
var _user, token;
if (!userId) {
return Errors.missingUserId;
}
_user = this.getUser(userId);
if (_user.state !== States.active || !Users[userId].csHex) {
return Errors.wrongFlow;
}
if (isNaN(pin)) {
pin = this.toHash(pin);
}
token = MPINAuth.calculateMPinToken(Users[userId].mpinId, pin, Users[userId].csHex);
delete Users[userId].csHex;
this.addToUser(userId, {token: token, state: States.register});
return true;
};
//Put user / mpinId
Mpin.prototype.restartRegistration = function (userId, cb) {
var _reqData = {}, self = this, _userState;
if (!userId) {
return cb ? cb(Errors.missingUserId, null) : {error: 1};
} else if (!this.checkUser(userId)) {
return cb(Errors.invalidUserId, null);
} else if (!this.settings.registerURL) {
return cb({code: Errors.missingParams.code, type: Errors.missingParams.type, message: "Missing registerURL"}, null);
}
_userState = this.getUser(userId, "state");
if (_userState !== States.start) {
return cb(Errors.wrongFlow, null);
}
_reqData.url = this.generateUrl("restart", {userId: userId});
_reqData.type = "PUT";
_reqData.data = {
userId: userId,
mobile: 0,
regOTT: Users[userId].regOTT
};
this.request(_reqData, function (err, data) {
if (err) {
return cb(err, null);
}
self.addToUser(userId, {regOTT: data.regOTT, mpinId: data.mpinId});
//force activate
if (data.active) {
self.addToUser(userId, {state: States.active});
}
cb && cb(null, true);
});
};
Mpin.prototype.startAuthentication = function (userId, cb) {
var _tp1Url, self = this, _userState;
if (!userId) {
return cb ? cb(Errors.missingUserId, null) : Errors.missingUserId;
} else if (!this.checkUser(userId)) {
return cb(Errors.invalidUserId, null);
} else if (!this.settings.timePermitsURL || !this.settings.certivoxURL) {
return cb({code: Errors.missingParams.code, type: Errors.missingParams.type, message: "Missing timePermitsURL or/and certivoxURL option."}, null);
}
//registered
_userState = this.getUser(userId, "state");
if (_userState !== States.register) {
return cb(Errors.wrongFlow, null);
}
//checkUser
_tp1Url = this.generateUrl('permit1', {userId: userId});
this.request({url: _tp1Url}, function (err, data) {
if (err) {
if (err.status === 401 || err.status === 403 || err.status === 410) {
return cb(Errors.userRevoked, null);
}
return cb(err, null);
}
var _signature, _tp2Url, _timePermit1, _storageUrl;
_signature = data["signature"];
_timePermit1 = data["timePermit"];
self.addToUser(userId, {currentDate: data['date']});
//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) {
_tp2Url = self.generateUrl('permit2', {userId: userId});
_tp2Url += "&signature=" + _signature;
self._getTimePermit2({userId: userId, permit1: _timePermit1, permit2Url: _tp2Url, date: data.date}, cb); //continue
return;
}
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) {
if (err2) {
if (err2.status === 401 || err2.status === 403 || err2.status === 410) {
return cb(Errors.userRevoked, null);
}
return cb(err2, null);
}
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.chooseAuthProtocol = function () {
var self = this;
this.authProtocol = this.cfg.protocols.default;
// We have 3 arrays:
// 1. Ordered list of protocols sent from the server - this.settings.supportedProtocols
// 2. List of protocols supported by the library - self.cfg.protocols.supported
// 3. List of protocols that the library user would like to support - this.opts.authProtocols
// The goal is to select the first protocol from the server's list (1) that is supported by the library (2) and selected by the lib user (3).
// If the lib user didn't provide any preferences, then we select the first one from the server's list that is supported by the lib.
if (this.settings.supportedProtocols && this.settings.supportedProtocols instanceof Array) {
if (this.opts.authProtocols && this.opts.authProtocols instanceof Array) {
this.settings.supportedProtocols.some(function (value) {
if (self.opts.authProtocols.indexOf(value) !== -1 && self.cfg.protocols.supported.indexOf(value) !== -1) {
self.authProtocol = value;
return true;
}
});
} else {
this.settings.supportedProtocols.some(function (value) {
if (self.cfg.protocols.supported.indexOf(value) !== -1) {
self.authProtocol = value;
return true;
}
});
}
}
};
Mpin.prototype.finishAuthentication = function (userId, pin, cb) {
var _userState;
//registered
_userState = this.getUser(userId, "state");
if (_userState !== States.register) {
return cb(Errors.wrongFlow, null);
} else if (!Users[userId].timePermitHex) {
return cb({code: Errors.wrongFlow.code, type: Errors.wrongFlow.type, message: "Need to call startAuthentication method before this."}, null);
}
// The following checks that the authentication protocol is 2pass.
// This is temporary until the lib supports other protocols
if (this.authProtocol === this.cfg.protocols.default) {
this._pass2Requests({userId: userId, pin: pin}, cb);
}
};
Mpin.prototype.finishAuthenticationOtp = function (userId, pin, cb) {
var _userState;
//registered
_userState = this.getUser(userId, "state");
if (_userState !== States.register) {
return cb(Errors.wrongFlow, null);
} else if (!Users[userId].timePermitHex) {
return cb({code: Errors.wrongFlow.code, type: Errors.wrongFlow.type, message: "Need to call startAuthentication method before this."}, null);
}
// The following checks that the authentication protocol is 2pass.
// This is temporary until the lib supports other protocols
if (this.authProtocol === this.cfg.protocols.default) {
this._pass2Requests({userId: userId, pin: pin, otp: true}, function (err, data) {
if (err) {
return cb(err, null);
}
if (!data.expireTime || !data.ttlSeconds || !data.nowTime) {
return cb(null, null);
}
data.expireTime = data.expireTime / 1000;
data.nowTime = data.nowTime / 1000;
cb(null, data);
});
}
};
Mpin.prototype.finishAuthenticationAN = function (userId, pin, accessNumber, cb) {
var _userState;
//registered
_userState = this.getUser(userId, "state");
if (_userState !== States.register) {
return cb(Errors.wrongFlow, null);
} else if (!Users[userId].timePermitHex) {
return cb({code: Errors.wrongFlow.code, type: Errors.wrongFlow.type, message: "Need to call startAuthentication method before this."}, null);
}
// The following checks that the authentication protocol is 2pass.
// This is temporary until the lib supports other protocols
if (this.authProtocol === this.cfg.protocols.default) {
this._pass2Requests({userId: userId, pin: pin, accessNumber: accessNumber.toString()}, function (err, data) {
if (err) {
return cb(err, null);
}
if (!data.expireTime || !data.ttlSeconds || !data.nowTime) {
return cb(null, null);
}
data.expireTime = data.expireTime / 1000;
data.nowTime = data.nowTime / 1000;
cb(null, data);
});
}
};
Mpin.prototype._pass2Requests = function (opts, cb) {
var userId, pin, otp, accessNumber, self = this, _reqData = {};
userId = opts.userId;
pin = isNaN(opts.pin) ? this.toHash(opts.pin) : opts.pin;
otp = opts.otp || false;
accessNumber = opts.accessNumber || false;
_reqData.url = this.generateUrl("pass1");
_reqData.type = "POST";
_reqData.data = this.getAuthData(userId, pin);
// pass1
this.request(_reqData, function (pass1Err, pass1Data) {
var _req2Data = {}, wid = "0";
_req2Data.url = self.generateUrl("pass2");
_req2Data.type = "POST";
accessNumber && (wid = accessNumber);
_req2Data.data = MPINAuth.pass2Request(pass1Data.y, otp, wid);
_req2Data.data.mpin_id = Users[userId].mpinId;
// pass 2
self.request(_req2Data, function (pass2Err, pass2Data) {
var otpCode;
if (pass2Err) {
return cb(pass2Err, null);
}
otpCode = pass2Data["OTP"] || false;
if (pass2Data && pass2Data["OTP"]) {
delete pass2Data["OTP"];
}
self._authenticate({userId: userId, mpinResponse: pass2Data, otpCode: otpCode, accessNumber: accessNumber}, cb);
});
});
};
Mpin.prototype._authenticate = function (opts, cb) {
var _authData = {}, self = this;
if (opts.accessNumber) {
_authData.url = this.generateUrl("mobileauth");
} else {
_authData.url = this.generateUrl("auth");
}
_authData.type = "POST";
_authData.data = {mpinResponse: opts.mpinResponse};
this.request(_authData, function (authErr, authData) {
if (authErr) {
if (authErr.status === 401) {
return cb(Errors.wrongPin, null);
} else if (authErr.status === 403) {
return cb(Errors.identityNotAuthorized, null);
} else if (authErr.status === 408) {
return cb(Errors.requestExpired, null);
} else if (authErr.status === 410) {
opts.userId && self.addToUser(opts.userId, {state: States.block});
return cb(Errors.wrongPin, null);
} else if (authErr.status === 412) {
return cb(Errors.incorrectAccessNumber, null);
} else {
return cb(Errors.wrongPin, null);
}
}
if (opts.otpCode && authData) {
authData.otp = opts.otpCode;
}
cb && cb(null, authData || null);
});
};
Mpin.prototype.checkAccessNumber = function (accessNumber) {
accessNumber = accessNumber.toString();
if (!this.settings.accessNumberUseCheckSum || accessNumber.length != this.settings.accessNumberDigits) {
return true;
} else {
if (this.settings.cSum === 1) {
return this.checkAccessNumberSum2(accessNumber, 6);
} else {
return this.checkAccessNumberSum(accessNumber);
}
}
};
Mpin.prototype.checkAccessNumberSum = function (accNumber, accLen) {
accLen || (accLen = 1);
var n = parseInt(accNumber.slice(0, accNumber.length - accLen), 10);
var cSum = parseInt(accNumber.slice(accNumber.length - accLen, accNumber.length), 10);
var p = 99991;
var g = 11;
var checkSum = ((n * g) % p) % Math.pow(10, accLen);
return (checkSum === cSum);
};
Mpin.prototype.checkAccessNumberSum2 = function (accNumber, accLen) {
var cSum, checksum, x, w, wid, wid_len, g = 11, sum_d = 0;
wid = accNumber.toString();
wid = wid.substring(0, accNumber.toString().length - 1);
w = accLen + 1;
sum_d = 0;
wid_len = wid.length;
for (var i = 0; i < wid_len; i++) {
x = parseInt(wid[i]);
sum_d += (x * w);
w -= 1;
}
checksum = (g - (sum_d % g)) % g;
checksum = (checksum === 10) ? 0 : checksum;
//get last one digit and compare with checksum result
cSum = accNumber.substr(-1);
cSum = parseInt(cSum);
return (cSum === checksum);
};
Mpin.prototype.getAuthData = function (userId, pin) {
var _auth = {};
_auth.mpin = Users[userId].mpinId;
_auth.token = Users[userId].token;
_auth.timePermit = Users[userId].timePermitHex;
_auth.date = Users[userId].currentDate;
return MPINAuth.pass1Request(_auth.mpin, _auth.token, _auth.timePermit, pin, _auth.date, null);
};
Mpin.prototype.fromHex = function (strData) {
if (!strData || strData.length % 2 != 0)
return '';
strData = strData.toLowerCase();
var digits = '0123456789abcdef';
var result = '';
for (var i = 0; i < strData.length; ) {
var a = digits.indexOf(strData.charAt(i++));
var b = digits.indexOf(strData.charAt(i++));
if (a < 0 || b < 0)
return '';
result += String.fromCharCode(a * 16 + b);
}
return result;
};
Mpin.prototype.toHash = function (strData) {
var hash = 0;
for (var i = 0; i < strData.length; i++) {
hash = ((hash << 5) - hash) + strData.charCodeAt(i);
}
return hash;
};
Mpin.prototype.getAccessNumber = function (cb) {
var self = this, _reqData = {};
_reqData.url = this.generateUrl("getnumber");
_reqData.type = "POST";
this.request(_reqData, function (err, data) {
if (err) {
return cb(err, null);
}
self.webOTT = data.webOTT;
var returnData = {
accessNumber: data.accessNumber,
ttlSeconds: data.ttlSeconds,
localTimeStart: data.localTimeStart / 1000,
localTimeEnd: data.localTimeEnd / 1000
};
cb && cb(null, returnData);
});
};
Mpin.prototype.getQrUrl = function (userId, cb) {
var self = this, _reqData = {};
_reqData.url = this.generateUrl("getqrurl");
_reqData.type = "POST";
_reqData.data = {
prerollid: userId || ""
};
this.request(_reqData, function (err, data) {
if (err) {
return cb(err, null);
}
self.webOTT = data.webOTT;
var returnData = {
qrUrl: data.qrUrl,
ttlSeconds: data.ttlSeconds,
localTimeStart: data.localTimeStart / 1000,
localTimeEnd: data.localTimeEnd / 1000
};
cb && cb(null, returnData);
});
};
Mpin.prototype.waitForMobileAuth = function (timeoutSeconds, requestSeconds, cb, cbStatus) {
var self = this, _reqData = {};
if (!this.webOTT) {
return cb({code: Errors.wrongFlow.code, type: Errors.wrongFlow.type, message: "Need to call getAccessNumber method before this."}, null);
} else if (!timeoutSeconds) {
return cb({code: Errors.missingParams.code, type: Errors.missingParams.type, message: "Missing timeout/expiration period(in seconds)."}, null);
}
self.mobileStatus = self.mobileStatus || '';
if (typeof this.timeoutPeriod === "undefined") {
this.timeoutPeriod = timeoutSeconds * 1000;
}
_reqData.url = this.generateUrl("getaccess");
_reqData.type = "POST";
_reqData.data = {webOTT: this.webOTT};
this.request(_reqData, function (err, data) {
var _requestPeriod;
if (err) {
cb && cb(err, null);
} else {
authOTT = data.authOTT
delete data.authOTT
if (data.status === 'authenticate') {
cbStatus && cbStatus(data);
self._authenticate({mpinResponse: {authOTT: authOTT}}, cb);
} else {
if (self.timeoutPeriod > 0) {
_requestPeriod = requestSeconds ? requestSeconds * 1000 : 3000;
self.timeoutPeriod -= _requestPeriod;
if (data.status !== self.mobileStatus) {
self.mobileStatus = data.status;
cbStatus && cbStatus(data);
}
self.intervalID2 = setTimeout(function () {
self.waitForMobileAuth.call(self, timeoutSeconds, requestSeconds, cb, cbStatus);
}, _requestPeriod);
return;
} else if (self.timeoutPeriod <= 0) {
delete self.timeoutPeriod;
cb && cb(Errors.timeoutFinish, null);
return;
}
}
}
});
};
Mpin.prototype.cancelMobileAuth = function () {
if (this.intervalID2) {
clearInterval(this.intervalID2);
}
if (this.timeoutPeriod) {
delete this.timeoutPeriod;
}
};
Mpin.prototype.generateUrl = function (type, options) {
var url, mpData, mpin_id_bytes, hash_mpin_id_bytes = [], hash_mpin_id_hex;
switch (type) {
case "register":
url = this.settings.registerURL;
break;
case "restart":
url = this.settings.registerURL + "/";
url += Users[options.userId].mpinId;
break;
case "signature":
url = this.settings.signatureURL + "/";
url += Users[options.userId].mpinId;
url += "?regOTT=" + Users[options.userId].regOTT;
break;
case "permit1":
url = this.settings.timePermitsURL + "/";
url += Users[options.userId].mpinId;
break;
case "permit2":
mpData = this.fromHex(Users[options.userId].mpinId);
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);
url = this.settings.certivoxURL + "timePermit";
url += "?app_id=" + this.settings.appID;
url += "&mobile=0";
url += "&hash_mpin_id=" + hash_mpin_id_hex;
break;
case "pass1":
url = this.settings.mpinAuthServerURL + "/pass1";
break;
case "pass2":
url = this.settings.mpinAuthServerURL + "/pass2";
break;
case "auth":
url = this.settings.authenticateURL;
break;
case "mobileauth":
url = this.settings.mobileAuthenticateURL;
break;
case "getnumber":
url = this.settings.getAccessNumberURL;
break;
case "getqrurl":
url = this.settings.getQrUrl;
break;
case "getaccess":
url = this.settings.accessNumberURL;
break;
case "storage":
url = this.settings.timePermitsStorageURL + "/" + this.settings.appID + "/";
url += options.date + "/" + options.storageId;
break;
}
return url;
};
Mpin.prototype.listUsers = function () {
var listUsers = [];
for (var uKey in Users) {
listUsers.push({
userId: Users[uKey].userId,
deviceId: Users[uKey].deviceId || "",
state: Users[uKey].state || ""
});
}
return listUsers;
};
Mpin.prototype.checkUser = function (userId) {
return (Users[userId]) ? true : false;
};
Mpin.prototype.getUser = function (userId, property) {
var _user = {};
if (!userId) {
return Errors.missingUserId;
} else if (!this.checkUser(userId)) {
return Errors.invalidUserId;
}
_user = {
userId: Users[userId].userId,
deviceId: Users[userId].deviceId || "",
state: Users[userId].state
};
if (!property) {
return _user;
} else if (property && _user[property]) {
return _user[property];
}
};
Mpin.prototype.deleteUser = function (userId) {
var mpinData = this.getData(), delMpinId;
if (!userId) {
return Errors.missingUserId;
} else if (!this.checkUser(userId)) {
return Errors.invalidUserId;
}
delMpinId = Users[userId].mpinId;
//memory
delete Users[userId];
//store
delete mpinData.accounts[delMpinId];
this.storeData(mpinData);
};
Mpin.prototype.addToUser = function (userId, userProps, skipSave) {
if (!this.checkUser(userId)) {
//create
Users[userId] = {};
}
//If mpinId has changed, we need to delete the object withthe previous one
if (Users[userId].mpinId && userProps.mpinId && Users[userId].mpinId != userProps.mpinId) {
this.deleteData(userId);
}
for (var uKey in userProps) {
if (userProps[uKey]) {
Users[userId][uKey] = userProps[uKey];
}
}
var _save = !skipSave;
_save && this.setData(userId, userProps);
};
Mpin.prototype.restore = function () {
Users = {};
};
Mpin.prototype.deleteData = function (userId) {
var mpinData = this.getData();
var mpinId = Users[userId].mpinId;
if (!mpinData || !mpinData.accounts[mpinId]) {
return;
}
delete mpinData.accounts[mpinId];
this.storeData(mpinData);
};
Mpin.prototype.setData = function (userId, upData) {
var mpinData = this.getData();
if (!mpinData) {
mpinData = {
version: "4",
accounts: {}
};
}
var mpinId = upData.mpinId || Users[userId].mpinId;
if (!mpinId) {
return false;
}
//update Default Identity
if (!mpinData.accounts[mpinId]) {
mpinData.accounts[mpinId] = {};
}
if (upData.regOTT) {
mpinData.accounts[mpinId].regOTT = upData.regOTT;
}
if (upData.timePermitHex) {
mpinData.accounts[mpinId].MPinPermit = upData.timePermitHex;
}
if (upData.token) {
mpinData.accounts[mpinId].token = upData.token;
}
if (upData.state && Users[userId].mpinId) {
mpinData.accounts[mpinId].state = upData.state;
}
//cache cache
if (upData.timePermitCache) {
mpinData.accounts[mpinId].timePermitCache = upData.timePermitCache;
}
this.storeData(mpinData);
};
Mpin.prototype.storeData = function (mpinData, key) {
var storageKey = key || this.storageKey;
localStorage.setItem(storageKey, JSON.stringify(mpinData));
};
Mpin.prototype.recover = function () {
var userId, userData = {}, mpinData = this.getData(), isOldData = false;
if (!mpinData) {
mpinData = this.getData("mpin");
isOldData = true;
}
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);
if (isOldData || !mpinData.accounts[mpinId].state) {
if (mpinData.accounts[mpinId].token) {
userData.state = States.register;
} else if (mpinData.accounts[mpinId].regOTT) {
userData.state = States.start;
} else {
userData.state = States.invalid;
}
} else {
userData.state = mpinData.accounts[mpinId].state;
}
//call add To user & skip Save
this.addToUser(userId, userData, !isOldData);
}
}
if (isOldData && mpinData && "accounts" in mpinData) {
delete mpinData.accounts;
this.storeData(mpinData, "mpin");
}
};
Mpin.prototype.getData = function (getKey) {
var localKey, mpinData;
localKey = getKey || this.storageKey;
mpinData = localStorage.getItem(localKey);
mpinData = JSON.parse(mpinData);
return mpinData;
};
//{url: url, type: "get post put", data: data}
Mpin.prototype.request = function (options, cb, jsonResponse) {
var _request = new XMLHttpRequest(), _url, _type, _parseJson;
_url = options.url || "";
_type = options.type || "GET";
_parseJson = (typeof jsonResponse !== "undefined") ? jsonResponse : true;
_request.onreadystatechange = function () {
if (_request.readyState === 4 && _request.status === 200) {
if (_parseJson && _request.responseText) {
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);
if (options.data) {
_request.setRequestHeader("Content-Type", "application/json");
_request.send(JSON.stringify(options.data));
} else {
_request.send();
}
};
return Mpin;
})();
//module.exports = mpinjs;
//http://www.matteoagosti.com/blog/2013/02/24/writing-javascript-modules-for-both-browser-and-node/
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined')
module.exports = mpinjs;
else
window.mpinjs = mpinjs;