UNPKG

scalra

Version:

node.js framework to prototype and scale rapidly

779 lines (651 loc) 23.4 kB
// // user.js // // basic user management functions // // methods: // register(account, password, user_data) // create a new user account // login(account, password, onDone, requester, conn) // login by account // loginByID(login_id, data, onDone, conn) // login by guest // logout(account, onDone) // logout of an existing account // getPass(type, data) // retrieve password by email // setPass(password, token) // set a new password for a given token // getUser(account, onDone) // get user data for a given user (by account name) // setUser(account, data, onDone) // set user data back to DB // getEmail(account, onDone) // get user's email // setEmail(account, email, onDone) // set user's email // loginLocal(server, account, password, onDone, requester) // login to a local server // addLocal(account, local, onDone) // add an access account to a local (non-cloud) server // createToken(uid, token_source, onDone) // create a new token for a given uid // revokeToken(uid, token, onDone) // revoke an existing token for a given uid // return code: // 0 operation success // 1 operation fail // 2 operation error // // default encryption type (0 is 'sha512', see SR.Settings.ENCRYPT_TYPES) var l_enc_type = 0; // list of logined accounts (account -> user's full data) var l_logins = SR.State.get('user.logins'); // get a reference to system states var l_states = SR.State.get(SR.Settings.DB_NAME_SYSTEM); // keep track of password resest requests (token to uid mapping) var l_resets = SR.State.get('user.resets'); // // init functions // var l_validateUID = function () { // check if data exists or will init it if (l_states.hasOwnProperty('uid_count') === false) { LOG.warn('no users found, user id (uid) counter init to 0', 'user.js'); l_states['uid_count'] = 0; } else LOG.warn('accounts created so far: ' + l_states['uid_count'], 'user.js'); }; // // helper functions // // generate a next unique ID for user // TODO: get it from memory var l_getUID = function (onDone) { l_validateUID(); var uid = ++l_states['uid_count']; // store back to DB // check existing users SR.DB.setData(SR.Settings.DB_NAME_SYSTEM, l_states, // success function (data) { LOG.warn('uid generated: ' + uid); onDone(uid); }, // fail function () { LOG.error('uid generation failed with DB'); onDone(undefined); }); } var l_encryptPass = exports.encryptPass = function (original, salt) { //salt = salt || UTIL.createUUID(); var salt = 'hydra'; return UTIL.hash(original + salt, SR.Settings.ENCRYPT_TYPES[l_enc_type]); } // store & remove user data to cache var l_addLogin = function (account, data, conn) { //if (l_logins.hasOwnProperty(account) === true) //return false; // check if user's unique data exists if (typeof data.data !== 'object') { LOG.error('data field does not exist, cannot add login data'); return false; } LOG.warn('account: ' + account + ' data:', 'addLogin'); LOG.warn(data); // attach login name to connection // NOTE: we attach connection object to the data stored in memory (not clean?) if (conn) { // NOTE: we use session because this request could come from an HTTP request // that does not have a persistent connectino record in SR.Conn SR.Conn.setSessionName(conn, account); data._conn = conn; } l_logins[account] = data; LOG.warn('user [' + account + '] login success, total count: ' + Object.keys(l_logins).length, 'user'); delete data._conn; //console.log(data); // error check: make sure lastStatus field exists if (data.hasOwnProperty('lastStatus') === false || data.lastStatus === null) data.lastStatus = {loginCount: 0}; data.lastStatus.loginIP = conn.host; data.lastStatus.loginCount = data.lastStatus.loginCount + 1; data.lastStatus.time = conn.time; SR.DB.updateData(SR.Settings.DB_NAME_ACCOUNT, {account: account}, data, function () { }, function () { }); return true; } var l_removeLogin = function (account) { if (l_logins.hasOwnProperty(account) === false) return false; // remove login name from connection (if any) var data = l_logins[account]; if (data.hasOwnProperty('_conn')) { //SR.Conn.setConnName(data._conn.connID, ''); SR.Conn.unsetSessionName(data._conn); delete data['_conn']; } delete l_logins[account]; LOG.warn('user [' + account + '] logout success, total count: ' + Object.keys(l_logins).length, 'user'); return true; } // update a given user's data field, returns true/false for result var l_updateUser = function (query, field, data, onDone) { var action = {}; action[field] = (typeof data === 'string' ? data : data.data); if (typeof data === 'object' && data.login_id) { delete data.login_id; } SR.DB.getData(SR.Settings.DB_NAME_ACCOUNT, {account: data.account}, function (dat) { // TODO: remove lastStatus here? if (dat) { action.lastStatus = dat.lastStatus; } SR.DB.updateData(SR.Settings.DB_NAME_ACCOUNT, query, action, function () { UTIL.safeCall(onDone, null); }, function () { var err = new Error("updataData fail"); err.name = "l_updateUser Error"; UTIL.safeCall(onDone, err); }); }, function(dat) { //LOG.warn("no existing data"); }); } // // public methods // // /*account, password, email, user_data, onDone*/ // register new account exports.register = function (arg) { // print basic info to confirm LOG.warn('register new account: ' + arg.account + ' pass: ' + arg.password + ' e-mail: ' + arg.email, 'user.register'); // check existing users SR.DB.getData(SR.Settings.DB_NAME_ACCOUNT, {account: arg.account}, // DB query success function (data) { if (data !== null) { var err = new Error("ACCOUNT_EXISTS: " + arg.account); err.name = "register Error"; err.code = 1; return UTIL.safeCall(arg.onDone, err); } // generate unique user_id l_getUID(function (uid) { // encode password var enc_pass = l_encryptPass(arg.password); // NOTE: by default a user is a normal user, user 'role' can later be customized var store_data = { uid: uid, account: arg.account, password: enc_pass, pass_tokens: {}, enc_type: l_enc_type, email: arg.email, groups: arg.groups, permissions: arg.permissions, data: arg.data, //application-level cannot modify except this data lastStatus: arg.lastStatus }; LOG.warn('store new account entry for: ' + arg.account); SR.DB.setData(SR.Settings.DB_NAME_ACCOUNT, store_data, function (data) { LOG.warn('register success, data:'); LOG.warn(data); UTIL.safeCall(arg.onDone, null, {code: 0, msg: 'ACCOUNT_REGISTER_SUCCESS: ' + store_data.account}); }, function () { var err = new Error("ACCOUNT_REGISTER_ERROR: " + store_data.account); err.name = "register Error"; err.code = 2; UTIL.safeCall(arg.onDone, err); }); }); }, // fail function () { var err = new Error("ACCOUNT_REGISTER_ERROR: " + store_data.account); err.name = "register Error"; err.code = 2; return UTIL.safeCall(arg.onDone, err); } ); } // login an existing account // NOTE: account & password can either be a string/string pair or number/string pair, // in the latter case it's actuall checked against uid + pass_tokens // 'requester' is an optional parameter, indicating which server is asking for this login exports.login = function (account, password, onDone, requester, conn) { LOG.warn('login request: ' + account + ' pwd: ' + password + ' requester: ' + requester); var by_account = (typeof account === 'string'); var query = (by_account ? {account: account} : {uid: account}); //LOG.warn('by_account: ' + by_account + ' query: '); //LOG.warn(query); // check existing users SR.DB.getData(SR.Settings.DB_NAME_ACCOUNT, query, // success, data already exists function (data) { //console.log("login_data"); //console.log(data); if (data === null) { var err = new Error("ACCOUNT_NOT_FOUND " + account); err.name = "login Error"; err.code = 1; return UTIL.safeCall(onDone, err); } // by default we assume authentication fails and return token does not exist var result = false; var token = undefined; // determine to check password or check token if (by_account) result = (l_encryptPass(password) === data.password); else // NOTE: check if the requester of the token was the same as the one logined previously result = (data.pass_tokens.hasOwnProperty(password)); // check login is success or fail if (result === true) { l_addLogin(account, data, conn); // generate unique token and store it if requested if (requester) { var l_createTokenDone = function (l_createToken_err, token) { if (l_createToken_err) { var err = new Error("ACCOUNT_LOGIN_TOKENSTORE_FAIL: " + account); err.name = "login Error"; err.code = 1; UTIL.safeCall(onDone, err); } else { UTIL.safeCall(onDone, null, {code: 0, msg: 'ACCOUNT_LOGIN_SUCCESS: ' + account, uid: data.uid, token: token, data: data}); } }; l_createToken(data.uid, requester, l_createTokenDone); } else { return UTIL.safeCall(onDone, null, {code: 0, msg: 'ACCOUNT_LOGIN_SUCCESS: ' + account, data: data}); } } else { var err = new Error("ACCOUNT_LOGIN_PASSFAIL: " + account); err.name = "login Error"; err.code = 1; return UTIL.safeCall(onDone, err); } }, function () { var err = new Error("ACCOUNT_LOGIN_DBERROR: " + account); err.name = "login Error"; err.code = 2; UTIL.safeCall(onDone, err); } ); } // login an existing account exports.loginByID = function (temp_id, data, onDone, conn) { if (l_addLogin(temp_id, data, conn) === false) { var err = new Error("ACCOUNT_LOGIN_EXISTS: " + temp_id); err.name = "loginByID Error"; err.code = 1; UTIL.safeCall(onDone, err); } else { UTIL.safeCall(onDone, null, {code: 0, msg: 'ACCOUNT_LOGIN_SUCCESS: ' + temp_id}); } } // logout of an existing account exports.logout = function (account, onDone) { if (l_removeLogin(account) === false) { var err = new Error("ACCOUNT_LOGOUT_NOT_LOGIN: " + account); err.name = "logout Error"; err.code = 1; UTIL.safeCall(onDone, err); } else { // TODO: save modified account data to DB ? // or.. it's always been saved to DB continously UTIL.safeCall(onDone, null, {code: 0, msg: 'ACCOUNT_LOGOUT_SUCCESS: ' + account}); } } // get password for an existing account exports.getPass = function (type, name, onDone) { // NOTE ignore type for now, assume 'email' always // check existing users var query = (type === 'account' ? {account: name} : {email: name}); SR.DB.getData(SR.Settings.DB_NAME_ACCOUNT, query, // success, data already exists function (data) { if (data !== null) { var projectName = UTIL.userSettings('projectName'); var token = UTIL.createToken(); var url = UTIL.userSettings('reset_url') + '?token=' + token; // store token for this user account LOG.warn('storing password reset token [' + token + '] for account: ' + data.account + ' uid: ' + data.uid); l_resets[token] = data.uid; // send to user's email account // NOTE: customize this UTIL.emailText({ to: data.email, type: 'html', subject: '[' + projectName + '] Password Reset', text: 'Password reset requested. Please click: <a href="' + url + '">' + url + '</a>'}); return UTIL.safeCall(onDone, null, {code: 0, msg: 'ACCOUNT_GETPASS_SUCCESS: ' + data.email}); } var err = new Error("ACCOUNT_GETPASS_NOT_REGISTERED: " + name); err.name = "getPass Error"; err.code = 1; UTIL.safeCall(onDone, err); }, function () { var err = new Error("ACCOUNT_GETPASS_DB_ERROR"); err.name = "getPass Error"; err.code = 1; UTIL.safeCall(onDone, err); } ); } // set new password for a given token exports.setPass = function (password, token, onDone) { // check if reset was requested & registered previously if (l_resets.hasOwnProperty(token) === false) { var err = new Error("ACCOUNT_SETPASS_INVALID_TOKEN: " + token); err.name = "setPass Error"; err.code = 1; UTIL.safeCall(onDone, err); } else { // get uid var uid = l_resets[token]; var enc_pass = l_encryptPass(password); var onUpdated = function (error) { if (error) { var err = new Error("ACCOUNT_SETPASS_ERROR: " + uid); err.name = "setPass Error"; err.code = 2; LOG.warn('setPass error for: ' + uid, 'user.setPass'); UTIL.safeCall(onDone, err); } else { LOG.warn('setPass success for: ' + uid, 'user.setPass'); // success, remove token delete l_resets[token]; UTIL.safeCall(onDone, null, {code: 0, msg: 'ACCOUNT_SETPASS_SUCCESS: ' + uid}); } }; l_updateUser({uid: uid}, 'password', enc_pass, onUpdated); } } // get login data exports.getLogin = function (account, onDone) { if (l_logins.hasOwnProperty(account) === true) { UTIL.safeCall(onDone, null, l_logins[account]); return l_logins[account]; } else { UTIL.safeCall(onDone, 'account not found'); return undefined; } } // get user data for a given logined user (by account name) exports.getUser = function (account, onDone) { if (l_logins.hasOwnProperty(account) === true) { UTIL.safeCall(onDone, null, l_logins[account].data); } else { var onSuccess = function (data) { //LOG.warn('query result: ', 'SR.User'); //LOG.warn(data); if (data !== null) { if (account instanceof Array) { var results = {}; for (var i=0; i < data.length; i++) { if (data[i] && data[i].hasOwnProperty('data')) { results[data[i].account] = data[i].data; } } UTIL.safeCall(onDone, null, results); } else { // return user's data UTIL.safeCall(onDone, null, data.data); } } else { var err = new Error("account [" + account + "] does not exist, cannot get account data"); err.name = "getUser Error"; LOG.warn('account [' + account + '] does not exist, cannot get account data'); UTIL.safeCall(onDone, err); } }; // query DB for the user // check for single query or multiple query // ref: https://groups.google.com/forum/#!topic/mongodb-user/o3C4_E4Ig7k if (account instanceof Array) { SR.DB.getArray(SR.Settings.DB_NAME_ACCOUNT, onSuccess, onDone, {account: {$in: account}}); } else { SR.DB.getData(SR.Settings.DB_NAME_ACCOUNT, {account: account}, onSuccess, onDone); } } } // store user data back to DB exports.setUser = function (account, data, onDone) { if (l_logins.hasOwnProperty(account) === false) { var err = new Error("account [" + account + "] not login, cannot set account data"); err.name = "setUser Error"; LOG.warn('account [' + account + '] not login, cannot set account data'); UTIL.safeCall(onDone, err); } if (typeof data !== 'object') { var err = new Error("user data not provided as an object"); err.name = "setUser Error"; LOG.error('user data not provided as an object'); UTIL.safeCall(onDone, err); } console.log("============data"); console.log(data); // update to memory if (l_logins[account] && l_logins[account].data) l_logins[account].data = data; // perform DB write-back var onUpdated = function (error) { if (error) { var err = new Error("set custom data for account [" + account + "] fail"); err.name = "setUser Error"; LOG.error('set custom data for account [' + account + '] fail', 'user'); UTIL.safeCall(onDone, err); } else { LOG.warn('set custom data for account [' + account + '] success', 'user'); UTIL.safeCall(onDone, null); } }; l_updateUser({account: account}, 'data', l_logins[account], onUpdated); } // get user e-mail exports.getEmail = function (account, onDone) { if (l_logins.hasOwnProperty(account) === true) { UTIL.safeCall(onDone, null, l_logins[account].email); } else { // TODO: combine query with getUser? // query DB for the user var query = {account: account}; var onSuccess = function (data) { if (data !== null) { // return user's data UTIL.safeCall(onDone, null, data.email); } else { var err = new Error("account [" + account + "] does not exist, cannot get email"); err.name = "getEmail Error"; LOG.warn('account [' + account + '] does not exist, cannot get email'); UTIL.safeCall(onDone, err); } }; var onFail = function () { var err = new Error("accessing DB fail"); err.name = "getEmail Error"; UTIL.safeCall(onDone, err); }; SR.DB.getData(SR.Settings.DB_NAME_ACCOUNT, query, onSuccess, onFail); } } // set user e-mail exports.setEmail = function (account, new_email, onDone) { if (l_logins.hasOwnProperty(account) === false) { var err = new Error("account [" + account + "] not login, cannot set user email"); err.name = "setEmail Error"; LOG.warn('account [' + account + '] not login, cannot set user email'); return UTIL.safeCall(onDone, err); } l_logins[account].email = new_email; // store to DB l_updateUser({account: account}, 'email', new_email, onDone); } // get user permission exports.getGroups = function (account, onDone) { if (typeof account !== 'string' || account === '') { return UTIL.safeCall(onDone, 'account not provided'); } if (l_logins.hasOwnProperty(account) === true) { UTIL.safeCall(onDone, null, l_logins[account].groups); } else { // TODO: combine query with getUser? // query DB for the user var query = {account: account}; var onSuccess = function (data) { if (data !== null) { // return user's data UTIL.safeCall(onDone, null, data.groups); } else { var err = new Error("account [" + account + "] does not exist, cannot get email"); err.name = "getEmail Error"; LOG.warn('account [' + account + '] does not exist, cannot get email'); UTIL.safeCall(onDone, err); } }; var onFail = function () { var err = new Error("accessing DB fail"); err.name = "getEmail Error"; UTIL.safeCall(onDone, err); }; SR.DB.getData(SR.Settings.DB_NAME_ACCOUNT, query, onSuccess, onFail); } } // set user permission exports.setGroups = function (account, new_groups, onDone) { /* if (l_logins.hasOwnProperty(account) === false) { var err = new Error("account [" + account + "] not login"); err.name = "setGroups Error"; LOG.warn('account [' + account + '] not login'); return UTIL.safeCall(onDone, err); }*/ // l_logins[account].groups = new_groups; // check for correctness if (typeof account === 'undefined' || account === '') { return onDone('account not provided'); } if (new_groups instanceof Array === false) { return onDone('no groups provided'); } // remove empty groups var groups = []; for (var i=0; i < new_groups.length; i++) { if (new_groups[i] !== '') groups.push(new_groups[i]); } var onUpdated = function (err) { if (err) { LOG.error(err); onDone(err); } else { UTIL.safeCall(onDone); } } // store to DB l_updateUser({account: account}, 'groups', { account: account, data: groups }, onUpdated); } // login to a local server // return 'undefined' for error, or actual response from remote server // TODO: change knowledge of SR_LOCAL_ACCOUNT at this level, as user is used by login.js handler var l_loginLocal = exports.loginLocal = function (server, account, password, onDone, requester) { // try to login to local server first to verify account correctness var login_id = UTIL.createUUID(); var login_info = {account: account, password: password, requester: requester}; SR.RPC.remoteEvent(server, 'SR_LOGIN_ACCOUNT', {login_id: login_id, data: login_info}, onDone); } // add an access account to a local (non-cloud) server // account: which user to login // local: {account: 'string', password: 'string'} login crediential for remote local server exports.addLocal = function (account, local, onDone) { var server = local.server; delete local.server; // build callback to store local account if it's verified var storeLocal = function (uid, token) { // TODO: check if local account already exists, or replace existing one // multiple accounts storable for one local server // NOTE: field name is a variable // ref: http://stackoverflow.com/questions/11133912/how-to-use-a-variable-as-a-field-name-in-mongodb-native-findandmodify var field = 'data.accounts.' + server + '.' + uid; var onUpdated = function (error) { if (error) { var err = new Error("ADD_LOCAL_ACCOUNT_FAIL: " + account); err.name = "addLocal Error"; err.code = 1; UTIL.safeCall(onDone, err); } else { UTIL.safeCall(onDone, null, {code: 0, msg: 'ADD_LOCAL_ACCOUNT_SUCCESS: ' + account}); } }; l_updateUser({account: account}, field, token, onUpdated); } // NOTE: by sending the optional 'requester' at the end, this will enable remote server to return a login token back // NOTE: the authentication passed can be either account/password or uid/token // in which the latter 'uid' is a number l_loginLocal(server, local.account, local.password, function (response) { // if login is successful, store local account info, otherwise return error // TODO: store login_id as well (?) LOG.warn('l_loginLocal response: '); LOG.warn(response); // generate failure response if remoteEvent fails if (response === undefined) { var err = new Error("ADD_LOCAL_ACCOUNT_FAIL: server [" + server + "] not online"); err.name = "addLocal Error"; err.code = 1; return UTIL.safeCall(onDone, err); } if (response.code === 0) { LOG.warn('local login success for [' + server + '], store local account info'); storeLocal(response.uid, response.token); } UTIL.safeCall(onDone, null, response); }, account + '@' + SR.Settings.IP_LOBBY); } // add/revoke a pass token // returns the token or undefined if token store fail var l_createToken = function (uid, token_source, onDone) { // generate a valid token to be returned var token = UTIL.createToken(); // store token back to DB var onUpdated = function (error) { if (error) { var err = new Error(error.toString()); err.name = "l_createToken Error"; UTIL.safeCall(onDone, err); } else { LOG.warn('pass_token [' + token + '] stored'); UTIL.safeCall(onDone, null, token); } }; l_updateUser({uid: uid}, 'pass_tokens.' + token, token_source, onUpdated); } var l_revokeToken = exports.revokeToken = function (uid, token, onDone) { var query = {uid: uid}; var field = 'pass_tokens.' + token; SR.DB.removeField(SR.Settings.DB_NAME_ACCOUNT, query, field, function () { LOG.warn('pass_token [' + token + '] removed'); UTIL.safeCall(onDone, null, token); }, function () { var err = new Error("accessing DB fail"); err.name = "revokeToken Error"; UTIL.safeCall(onDone, err); }); }