fh-wfm-user
Version:
294 lines (253 loc) • 8.57 kB
JavaScript
;
var q = require('q');
var _ = require('lodash');
var config = require('../../config/config-user');
var aes = require('../../security/aes');
var sha256 = require('../../security/sha256');
var policyId;
var userClient;
var UserClient = function(mediator) {
this.mediator = mediator;
this.initComplete = false;
this.readyPromise = this.ready();
this.initPromise = this.init();
//Flag to cache user verification.
this.userVerified = false;
};
/**
*
* Checking for the $fh SDK to be ready before any requests can be made.
*
* @returns {*}
*/
UserClient.prototype.ready = function() {
var self = this;
return q.Promise(function(resolve, reject) {
$fh.on('fhinit', function(error) {
if (error) {
reject(new Error(error));
return;
}
self.appid = $fh.getFHParams().appid;
self.initComplete = true;
resolve();
});
});
};
UserClient.prototype.xhr = function(_options) {
var defaultOptions = {
path: '/',
method: 'get',
contentType: 'application/json'
};
var options = _.defaults(_options, defaultOptions);
var deferred = q.defer();
return this.readyPromise.then(function() {
$fh.cloud(options, function(res) {
deferred.resolve(res);
}, function(message, props) {
var e = new Error(message);
e.props = props;
deferred.reject(e);
});
return deferred.promise;
});
};
/**
* Encrypt the users profile data using AES with a hash of the session key and store the result in localStorage.
* @param profileData {object} - the plaintext to encrypt.
* @param sessionToken {string} - the secret key to encrypt with.
*/
var storeProfile = function(profileData, sessionToken) {
if (sessionToken && profileData) {
var hashedSessionKey = sha256.hash(sessionToken);
var encryptedData = aes.encrypt(profileData, hashedSessionKey);
// set the encrypted data in localStorage
localStorage.setItem('fh.wfm.profileData', encryptedData);
} else {
localStorage.setItem('fh.wfm.profileData', null);
}
};
/**
* Decrypt the users profile data using AES with a hash of the session key.
* @returns object/null
*/
var retrieveProfileData = function() {
var decryptedProfileData;
if (localStorage.getItem('fh_session_token.sessionToken') && localStorage.getItem('fh.wfm.profileData')) {
var ciphertext = localStorage.getItem('fh.wfm.profileData');
var sessionToken = JSON.parse(localStorage.getItem('fh_session_token.sessionToken')).sessionToken;
var hashedSessionKey = sha256.hash(sessionToken);
decryptedProfileData = aes.decrypt(ciphertext, hashedSessionKey);
}
return decryptedProfileData;
};
UserClient.prototype.init = function() {
var self = this;
return self.readyPromise.then(function() {
return self.xhr({
path: config.apiPath + '/config/authpolicy'
}).then(function(_policyId) {
policyId = _policyId;
return policyId;
});
});
};
UserClient.prototype.list = function() {
return this.xhr({
path: config.apiPath
});
};
UserClient.prototype.read = function(id) {
return this.xhr({
path: config.apiPath + '/' + id
});
};
UserClient.prototype.update = function(user) {
return this.xhr({
path: config.apiPath + '/' + user.id,
method: 'put',
data: user
});
};
UserClient.prototype.delete = function(user) {
return this.xhr({
path: config.apiPath + '/' + user.id,
method: 'delete',
data: user
});
};
UserClient.prototype.create = function(user) {
return this.xhr({
path: config.apiPath,
method: 'post',
data: user
});
};
UserClient.prototype.auth = function(username, password) {
var deferred = q.defer();
var self = this;
//There is an attempted login, marking the userVerified flag as false in case the verification fails.
self.userVerified = false;
this.initPromise.then(function() {
$fh.auth({
policyId: policyId,
clientToken: self.appid,
params: {
userId: username,
password: password
}
}, function(res) {
// res.sessionToken; // The platform session identifier
// res.authResponse; // The authetication information returned from the authetication service.
var sessionToken = res.sessionToken;
var profileData = res.authResponse;
if (typeof profileData === 'string' || profileData instanceof String) {
try {
profileData = JSON.parse(profileData);
} catch (e) {
console.error(e);
profileData = JSON.parse(profileData.replace(/,\s/g, ',').replace(/[^,={}]+/g, '"$&"').replace(/=/g, ':'));
}
}
storeProfile(profileData, sessionToken);
//Authentication was successful, mark the userVerified flag as true.
self.userVerified = true;
self.mediator.publish('wfm:auth:profile:change', profileData);
deferred.resolve(res);
}, function(code, errorMsg) {
/* Possible errors:
unknown_policyId - The policyId provided did not match any defined policy. Check the Auth Policies defined. See Auth Policies Administration
user_not_found - The Auth Policy associated with the policyId provided has been set up to require that all users authenticating exist on the platform, but this user does not exists.
user_not_approved - - The Auth Policy associated with the policyId provided has been set up to require that all users authenticating are in a list of approved users, but this user is not in that list.
user_disabled - The user has been disabled from logging in.
user_purge_data - The user has been flagged for data purge and all local data should be deleted.
device_disabled - The device has been disabled. No user or apps can log in from the requesting device.
device_purge_data - The device has been flagged for data purge and all local data should be deleted.
*/
if (typeof errorMsg === 'object') {
deferred.reject(new Error(code)); //for errors other than the given errors above. i.e. internal errors
} else {
deferred.reject(new Error(errorMsg));
}
});
});
return deferred.promise;
};
UserClient.prototype.hasSession = function() {
return this.initPromise.then(function() {
var deferred = q.defer();
$fh.auth.hasSession(function(err, exists) {
if (err) {
deferred.reject(new Error(err));
} else if (exists) {
//user is already authenticated
//optionally we can also verify the session is acutally valid from client. This requires network connection.
deferred.resolve(true);
} else {
deferred.resolve(false);
}
});
return deferred.promise;
});
};
UserClient.prototype.clearSession = function() {
var self = this;
return this.initPromise.then(function() {
self.userVerified = false;
return self.xhr({
path: config.authpolicyPath + "/revokesession",
method: "POST",
body: {}
}).then(function() {
localStorage.clear();
self.mediator.publish('wfm:auth:profile:change', null);
});
});
};
UserClient.prototype.verify = function(forceVerify) {
var self = this;
//If the user has already been verified, then no need to do it again.
if (this.userVerified && !forceVerify) {
return q.when(true);
}
return this.initPromise.then(function() {
return self.xhr({
path: config.authpolicyPath + "/verifysession",
method: "POST",
body: {}
}).then(function(validationResult) {
self.userVerified = validationResult && validationResult.isValid;
return self.userVerified;
});
});
};
UserClient.prototype.getProfile = function() {
return q.when(retrieveProfileData());
};
module.exports = function(mediator, config) {
config = config || {};
//Only want a single user client per application,
//If one already exists, use this one.
if (userClient) {
return userClient;
}
//If the session should be verified every time the app is resumed
//Then the http request should be sent.
//Defaults to being turned on.
if (config.forceSessionVerificationOnResume !== false) {
document.addEventListener("deviceready", function() {
document.addEventListener("resume", function() {
userClient.verify(true).then(function(userVerified) {
if (!userVerified) {
userClient.clearSession();
}
}).catch(function() {
userClient.clearSession();
});
}, false);
}, false);
}
userClient = new UserClient(mediator);
return userClient;
};