userlogin.service.js
Tue Feb 27 2024 04:43:34 GMT+0000 (Coordinated Universal Time)
Saved by @minhgiau998
import config from 'config'; import fetch from 'node-fetch'; // import jwt from 'jsonwebtoken'; import moment from 'moment'; import bcrypt from 'bcrypt'; import ejwt from '../../../helper/encrypted-jwt'; import logger from '../../../helper/logger'; import db from '../../../server/database'; import * as util from '../../../helper/util'; import { modifyStatus } from '../../model/user/userinfo.model'; import * as UserLogin from '../../model/user/userlogin.model'; import * as userInfoService from '../../service/user/userinfo.service'; import * as codeInfoService from '../../service/code/codeinfo.service'; import * as userGroupService from '../../service/user/usergroup.service'; import * as companyService from '../../service/user/company.service'; import CustomError from '../../../helper/error'; import sendMail from './email.service'; const cacheOptions = { name: 'logininfo', time: 3600, force: false, }; /** * get getRefreshJWT * * @param {String} id * @param {Boolean} rememberme * @returns {String} */ export const getRefreshJWT = (row, expirationTime) => { // cookie : milliseconds, jwt : seconds const defaultTime = parseInt(config.get('jwt.expiration'), 10) * 1000; const expirationTime1 = parseInt((expirationTime || defaultTime) / 1000, 10); // "Bearer "+ const token = ejwt.sign( config.get('jwt.secretkey'), { userId: row.userId, email: row.email, srcloc: row.srcloc, userType: row.userType, rememberMe: row.rememberMe || false, }, config.get('jwt.encryption'), { expiresIn: expirationTime1 }, ); // console.log('token', token); return token; }; /** * set RefreshToken * * @param {object} userInfo * @param {object} req * @param {object} res * @returns {String} */ const setRefreshToken = async (userInfo, req, res) => { // check remember me const rememberMe = userInfo.rememberMe || false; // calculate expire time let expirationTimeRefreshToken = parseInt(config.get('jwt.expiration1'), 10) * 1000; let expiresRefreshToken = new Date(); expiresRefreshToken = new Date(Date.now() + expirationTimeRefreshToken); // set up for expire if (rememberMe) { expirationTimeRefreshToken = parseInt(config.get('jwt.expiration2'), 10) * 1000; expiresRefreshToken = new Date(Date.now() + expirationTimeRefreshToken); } // console.log('expiresRefreshToken', expiresRefreshToken); // get token const refreshToken = getRefreshJWT(userInfo, expirationTimeRefreshToken); const params = []; // update user login info params[0] = util.replaceUndefined(refreshToken); params[1] = util.replaceUndefined(expiresRefreshToken.toGMTString()); params[2] = util.replaceUndefined(userInfo.userId); params[3] = util.replaceUndefined(userInfo.srcloc); await db.execute(UserLogin.refresh(), params, cacheOptions); // console.log('refreshToken', refreshToken); util.addCookie(req, res, 'auth.remember-me', refreshToken, '', expirationTimeRefreshToken); return { refreshToken, expires: expiresRefreshToken }; }; /** * login user * * @param {Object} userInfo * @param {Object} req * @param {Object} res * @returns {Object} */ export const loginUser = async (userInfo, req, res) => { logger.debug(`userlogin.loginUser : ${userInfo.email}`); // createLog(req, ['loginUser', userInfo.srcloc, '', '', userInfo.userId]); const params = []; params[0] = util.replaceUndefined(userInfo.email); params[1] = util.replaceUndefined(userInfo.srcloc); const rowUser = await db.findOne(UserLogin.loginUser(), params, cacheOptions); if (util.isEmpty(rowUser)) { logger.error(`loginUser not found : ${userInfo.email}`); if (userInfo.srcloc === 'A') { throw new CustomError('not-found', 24); } else { throw new CustomError('login-error', 10924); } // throw new CustomError('not-found', userInfo.srcloc === 'A' ? 24 : 10910); } // is locked user ? if (parseInt(rowUser.userStatus, 10) === 17003) { logger.error(`loginUser locked : ${userInfo.email}, ${rowUser.userStatus}`); const rowFail = await db.findOne(UserLogin.getFailed(), [rowUser.userId], cacheOptions); if (!util.isEmpty(rowFail)) { const tdiff = parseInt(rowFail.tdiff, 10); if (tdiff === 0) { // vulerabliity No.9 rollback // if (userInfo.srcloc === 'A') { // throw new CustomError('locked-account', 11); // } else { // throw new CustomError('login-error', 10924); // } throw new CustomError('locked-account', userInfo.srcloc === 'A' ? 11 : 10921); } else { await util.to(db.execute(modifyStatus(), [17001, rowUser.userId], cacheOptions)); rowUser.userStatus = 17001; } } } // compare password const pass = bcrypt.compareSync(userInfo.pwd, rowUser.pwd); // invalid password if (!pass) { // check previous failed history const rowFail = await db.findOne(UserLogin.getFailed(), [rowUser.userId], cacheOptions); if (util.isEmpty(rowFail)) { await db.execute(UserLogin.createFailed(), [rowUser.userId], cacheOptions); } else { const failCount = parseInt(rowFail.failed_count, 10); logger.error(`loginUser invalid password : ${userInfo.email}, ${failCount}`); // too many failed user if (failCount >= 5) { const [, vResult] = await util.to(db.execute(modifyStatus(), [17003, rowUser.userId], cacheOptions)); if (vResult.affectedRows === 1 || vResult.changedRows === 1) { let locale = rowUser.basicLanguage || 'en'; if ('en,ja,zh,it'.indexOf(locale) < 0) { locale = 'en'; } // send email const templateId = 14; const row = {}; row.userId = rowUser.userId; row.receiver_email = rowUser.email; row.receiver_name = rowUser.nickname; row.basicLanguage = locale; row.locale = locale; const [, returnValue] = await util.to(sendMail(req, row, templateId)); if (returnValue.code !== 0) { logger.error(`modify ${rowUser.email} : failed to send email`); vResult.info = 'locked but failed to send email'; } } //set field count = 0 await db.execute(UserLogin.updateFailCount(),[rowUser.userId], cacheOptions); if (userInfo.srcloc === 'A') { throw new CustomError('not-found', 24); } else { throw new CustomError('locked-account', 10921); } // throw new CustomError('invalid-password', userInfo.srcloc === 'A' ? 13 : 10924); } else { await db.execute(UserLogin.updateFailed(), [rowUser.userId], cacheOptions); } } if (userInfo.srcloc === 'A') { throw new CustomError('not-found', 24); } else { throw new CustomError('login-error', 10924); } // throw new CustomError('invalid-password', userInfo.srcloc === 'A' ? 13 : 10924); } // is not active ? if (parseInt(rowUser.userStatus, 10) !== 17001) { logger.error(`loginUser not active : ${userInfo.email}, ${rowUser.userStatus}`); if (userInfo.srcloc === 'A') { throw new CustomError('not-found', 24); } else { throw new CustomError('login-error', 10924); } // throw new CustomError('not-active', userInfo.srcloc === 'A' ? 12 : 10916); } // is not plan ? // if (rowUser.srcloc === 'A' && (rowUser.validPlan === 'N' || parseInt(rowUser.planId, 10) > 30001)) { // After the Expire Date, I will not be able to log in from APEX. if (config.get('serverConfig.mode') !== 'staging') { if (rowUser.srcloc === 'A' && parseInt(rowUser.planId, 10) > 30001) { logger.error(`loginUser not plan : ${userInfo.email}, ${rowUser.planId}, ${rowUser.validPlan}`); if (userInfo.srcloc === 'A') { throw new CustomError('not-found', 24); } else { throw new CustomError('login-error', 10924); } // throw new CustomError('invalid-plan', userInfo.srcloc === 'A' ? 14 : 10927); } } req.session.save(); const isForce = userInfo.force || false; // if (config.get('serverConfig.mode') === 'production') { // compare cookie expire date // user may be not logout or do not access during long time. // if expires date is later than current, user is already login except user is super admin. const sessUser = await db.findOne(UserLogin.selectSession(), [userInfo.userId, userInfo.srcloc, rowUser.userType], cacheOptions); if (!util.isEmpty(sessUser)) { // console.log('moment().unix()', moment().unix(), sessUser.expires); // console.log('moment().unix()', sessUser.session_id, req.sessionID); if (sessUser.session_id !== req.sessionID) { // throw new CustomError('multiple-logins (same session)', 10); // } if (moment().unix() < sessUser.expires) { // user is not super admin and user's token is not empty if ((sessUser.session_id !== req.sessionID)) { // parseInt(rowUser.userType, 10) !== 15000 && logger.info(`loginUser already : ${userInfo.email}`); if (isForce) { await db.execute(UserLogin.deleteSessionByUserId(), [userInfo.userId, userInfo.srcloc, rowUser.userType], cacheOptions); req.session.save(); } else { res.status(500).send({ status: false, name: 'login', code: userInfo.srcloc === 'A' ? 10 : 10410, message: 'multiple-logins', // stack: err.stack, }); return; // throw new CustomError('multiple-logins', userInfo.srcloc === 'A' ? 10 : 10410); } } } } } // } // clear failed history await db.execute(UserLogin.deleteFailed(), [rowUser.userId], cacheOptions); const params2 = []; // update user login info params2[0] = util.replaceUndefined(rowUser.userId); params2[1] = util.replaceUndefined(rowUser.srcloc); await db.execute(UserLogin.login(), params2, cacheOptions); // get latest user login info const returnUser = await db.findOne(UserLogin.loginUser(), [rowUser.userId, rowUser.srcloc], cacheOptions); returnUser.pwd = undefined; if (config.get('serverConfig.mode') !== 'test') { returnUser.accessToken = undefined; returnUser.expires = undefined; returnUser.refreshToken = undefined; } returnUser.rememberMe = userInfo.rememberMe || false; req.session.isAdmin = userInfo.isAdmin || 'N'; req.session.user = returnUser; // set remember-me to cookie const [, refresh] = await util.to(setRefreshToken(returnUser, req, res)); if (config.get('serverConfig.mode') === 'test') { returnUser.refreshToken = refresh.refreshToken; } // update email of user_session req.session.save(); await db.execute(UserLogin.modifySession(), [rowUser.userId, rowUser.srcloc, rowUser.userType, req.sessionID], cacheOptions); if (rowUser.srcloc === 'A') { returnUser.userType = parseInt((parseInt(returnUser.userType, 10) - 15000) / 10, 10); returnUser.userRole = parseInt(returnUser.userRole, 10) - 16000; returnUser.userStatus = parseInt(returnUser.userStatus, 10) - 17000; } // eslint-disable-next-line consistent-return return returnUser; }; /** * login user infor by Auth0 * * @param {Object} userInfo * @param {Object} req * @param {Object} res * @returns {Object} */ export const loginUserApex = async (userInfo, req, res) => { logger.debug(`userlogin.loginUser : ${userInfo.email}`); const body = new URLSearchParams(); body.append("client_id", config.get('auth0Apex.AUTH0_CLIENT_ID')); body.append("client_secret", config.get('auth0Apex.AUTH0_CLIENT_SECRET')); body.append("audience", `${config.get('auth0Apex.AUTH0_DOMAIN')}/api/v2/`); body.append("grant_type", config.get('auth0Apex.GRANT_TYPE')); body.append("realm", config.get('auth0Apex.REALM')); body.append("scope", config.get('auth0Apex.SCOPE')); body.append("username", userInfo.email); body.append("password", userInfo.pwd); let responseAuth0 = false; let userInfoAuth0 = {}; try { const response = await fetch(`${config.get('auth0.AUTH0_DOMAIN')}/oauth/token`, { method: "POST", body, headers: { "Cache-Control": "no-cache", "Content-Type": "application/x-www-form-urlencoded", }, }); responseAuth0 = response.status === 200; const data = await response.json(); if (!responseAuth0) { throw new CustomError("not-found", 24); } else { // data user in Auth0 const { id_token } = data; userInfoAuth0 = JSON.parse( Buffer.from(id_token.split(".")[1], "base64").toString() ); } } catch (err) { throw new CustomError("not-found", 24); } let rowUser = await db.findOne( UserLogin.loginUserAuth0(), [userInfoAuth0.sub, userInfo.srcloc], cacheOptions ); if (util.isEmpty(rowUser)) { logger.error(`loginUser not found : ${userInfo.email}`); if (userInfo.srcloc === "A") { throw new CustomError("not-found", 24); } else { throw new CustomError("login-error", 10924); } } rowUser = { ...rowUser, email: userInfoAuth0.email, userName: userInfoAuth0.family_name.concat(' ', userInfoAuth0.given_name), basicLanguage: userInfoAuth0.lang.code || 'en', srcloc: userInfo.srcloc, }; // is not active ? if (parseInt(rowUser.userStatus, 10) !== 17001) { logger.error( `loginUser not active : ${userInfo.email}, ${rowUser.userStatus}` ); if (userInfo.srcloc === "A") { throw new CustomError("not-found", 24); } else { throw new CustomError("login-error", 10924); } // throw new CustomError('not-active', userInfo.srcloc === 'A' ? 12 : 10916); } // is not plan ? // if (rowUser.srcloc === 'A' && (rowUser.validPlan === 'N' || parseInt(rowUser.planId, 10) > 30001)) { // After the Expire Date, I will not be able to log in from APEX. if (rowUser.srcloc === "A" && parseInt(rowUser.planId, 10) > 30001) { logger.error( `loginUser not plan : ${userInfo.email}, ${rowUser.planId}, ${rowUser.validPlan}` ); if (userInfo.srcloc === "A") { throw new CustomError("not-found", 24); } else { throw new CustomError("login-error", 10924); } // throw new CustomError('invalid-plan', userInfo.srcloc === 'A' ? 14 : 10927); } req.session.save(); const isForce = userInfo.force || false; const sessUser = await db.findOne( UserLogin.selectSession(), [rowUser.userId, rowUser.srcloc, rowUser.userType], cacheOptions ); if (!util.isEmpty(sessUser)) { if (sessUser.session_id !== req.sessionID) { if (moment().unix() < sessUser.expires) { // user is not super admin and user's token is not empty if (sessUser.session_id !== req.sessionID) { logger.info(`loginUser already : ${userInfo.email}`); if (isForce) { await db.execute( UserLogin.deleteSessionByUserId(), [rowUser.userId, userInfo.srcloc, rowUser.userType], cacheOptions ); req.session.save(); } else { res.status(500).send({ status: false, name: "login", code: userInfo.srcloc === "A" ? 10 : 10410, message: "multiple-logins", // stack: err.stack, }); return; } } } } } const params2 = []; // update user login info params2[0] = util.replaceUndefined(rowUser.userId); params2[1] = util.replaceUndefined(rowUser.srcloc); await db.execute(UserLogin.login(), params2, cacheOptions); if (config.get("serverConfig.mode") !== "test") { rowUser.accessToken = undefined; rowUser.expires = undefined; rowUser.refreshToken = undefined; } rowUser.rememberMe = userInfo.rememberMe || false; req.session.isAdmin = userInfo.isAdmin || "N"; req.session.user = rowUser; // set remember-me to cookie const [, refresh] = await util.to(setRefreshToken(rowUser, req, res)); if (config.get("serverConfig.mode") === "test") { rowUser.refreshToken = refresh.refreshToken; } // update email of user_session req.session.save(); await db.execute( UserLogin.modifySession(), [rowUser.userId, rowUser.srcloc, rowUser.userType, req.sessionID], cacheOptions ); if (rowUser.srcloc === "A") { rowUser.userType = parseInt( (parseInt(rowUser.userType, 10) - 15000) / 10, 10 ); rowUser.userRole = parseInt(rowUser.userRole, 10) - 16000; rowUser.userStatus = parseInt(rowUser.userStatus, 10) - 17000; } // eslint-disable-next-line consistent-return return rowUser; }; export const getUserByCodeAuth0 = async (code, req, res) => { const body = new URLSearchParams(); body.append("client_id", config.get('auth0.AUTH0_CLIENT_ID')); body.append("client_secret", config.get('auth0.AUTH0_CLIENT_SECRET')); body.append("audience", config.get('auth0.AUDIENCE')); body.append("grant_type", config.get('auth0.GRANT_TYPE')); body.append("redirect_uri", `https://${req.headers.host}/api/v1/login/auth0`); body.append("scope", config.get('auth0.SCOPE')); body.append("code", code); let userAuth0 = {}; try { const response = await fetch(`${config.get('auth0.AUTH0_DOMAIN')}/oauth/token`, { method: "POST", body, headers: { "Cache-Control": "no-cache", "Content-Type": "application/x-www-form-urlencoded", }, }); const responseAuth0 = response.status === 200; const data = await response.json(); if (!responseAuth0) { throw new CustomError("not-found", 24); } else { // data user in Auth0 const { id_token } = data; userAuth0 = JSON.parse( Buffer.from(id_token.split(".")[1], "base64").toString() ); } return userAuth0 } catch (err) { throw new CustomError("not-found", 24); } }; export const loginUserAuth0 = async (userInfo, req, res) => { logger.debug(`userlogin.loginUserAuth0 : ${userInfo.email}`); const rowUser = userInfo; req.session.save(); // check user exist let user = await db.findOne(UserLogin.checkUserExist(), [userInfo.sub], cacheOptions); if (util.isEmpty(user)) { // register Account const guest = {} guest.firstName = userInfo.family_name guest.lastName = userInfo.given_name guest.userStatus = 17001 guest.basicLanguage = userInfo.lang.code if (userInfo.user_type.code === 'Shima') { guest.userType = 15040 guest.userRole = 16040 } else { guest.userType = 15030 guest.userRole = 16030 } guest.auth0Id = userInfo.sub const [err, vResult] = await util.to(userInfoService.create(guest)); if (err) { throw new CustomError('Register Guest is error', 24); } const [, groupId] = await util.to(codeInfoService.getSeqId('COMPANY')); await util.to(userGroupService.create({ groupId, email: null, userType: guest.userType, userRole: guest.userRole, mailId: '', userId: vResult.insertId })); if (userInfo.user_type.code === 'Shima') { const company = {} company.companyName = userInfo.company_en company.companyId = groupId const [err1] = await util.to(companyService.create(company)); if (err1) { error.message = err1.message; logger.error(error); return Response.error(res, error, 500); } await util.to(companyService.createPlan(req, { companyId: groupId, planId: 30001 })); } user = await db.findOne(UserLogin.checkUserExist(), [userInfo.sub], cacheOptions); } const sessUser = await db.findOne(UserLogin.selectSession(), [user.userId, userInfo.srcloc, user.userType], cacheOptions); if (!util.isEmpty(sessUser)) { if (sessUser.session_id !== req.sessionID) { if (moment().unix() < sessUser.expires) { // user is not super admin and user's token is not empty if ((sessUser.session_id !== req.sessionID)) { // parseInt(rowUser.userType, 10) !== 15000 && logger.info(`loginUser already : ${userInfo.email}`); await db.execute(UserLogin.deleteSessionByUserId(), [user.userId, userInfo.srcloc, user.userType], cacheOptions); req.session.save(); } } } } let returnUser = {} // check company const company = await db.findOne(UserLogin.checkCompanyExist(), [user.userId], cacheOptions); if (util.isEmpty(company)) { returnUser = await db.findOne(UserLogin.loginUserAuth0WithoutCompany(), [userInfo.sub, userInfo.srcloc], cacheOptions); } else { returnUser = await db.findOne(UserLogin.loginUserAuth0(), [userInfo.sub, userInfo.srcloc], cacheOptions); } // modify information returnUser.userName = rowUser.family_name.concat(' ', rowUser.given_name) returnUser.basicLanguage = rowUser.lang.code || 'en' returnUser.email = rowUser.email returnUser.srcloc = userInfo.srcloc returnUser.address1 = rowUser.direction returnUser.tel = rowUser.tel if (returnUser.userType === 15030) { returnUser.countryCode = rowUser.country.code } if (config.get('serverConfig.mode') !== 'test') { returnUser.accessToken = undefined; returnUser.expires = undefined; returnUser.refreshToken = undefined; } returnUser.rememberMe = userInfo.rememberMe || false; req.session.isAdmin = userInfo.isAdmin || 'N'; req.session.user = returnUser; // set remember-me to cookie const [, refresh] = await util.to(setRefreshToken(returnUser, req, res)); if (config.get('serverConfig.mode') === 'test') { returnUser.refreshToken = refresh.refreshToken; } // update information of user_session req.session.save(); await db.execute(UserLogin.modifySession(), [returnUser.userId, userInfo.srcloc, returnUser.userType, req.sessionID], cacheOptions); return returnUser; }; /** * logout user * * @param {Object} userInfo * @returns {Object} */ export const logoutUser = async (userInfo) => { logger.debug(`userlogin.logoutUser : ${userInfo.userId}`); // createLog(req, ['logoutUser', userInfo.srcloc, '', '', userInfo.userId]); await db.execute(UserLogin.deleteSessionByUserId(), [userInfo.userId, userInfo.srcloc, userInfo.userType], cacheOptions); const params2 = []; params2[0] = util.replaceUndefined(userInfo.userId); params2[1] = util.replaceUndefined(userInfo.srcloc); const rows = await db.execute(UserLogin.logout(), params2, cacheOptions); return rows; }; /** * logout user * * @param {Object} userInfo * @returns {Object} */ export const logoutUserDeleteMe = async (userInfo) => { logger.debug(`userlogin.logoutUserDeleteMe : ${userInfo.userId}`); // createLog(req, ['logoutUser', userInfo.srcloc, '', '', userInfo.userId]); await db.execute(UserLogin.deleteSessionByUserId(), [userInfo.userId, userInfo.srcloc, userInfo.userType], cacheOptions); const params2 = []; params2[0] = util.replaceUndefined(userInfo.userId); params2[1] = util.replaceUndefined(userInfo.srcloc); const rows = await db.execute(UserLogin.logoutOnly(), params2, cacheOptions); return rows; }; /** * refresh user * * @param {Object} userInfo * @returns {Object} */ export const refresh = async (userInfo, req, res) => { logger.debug(`userlogin.refresh : ${userInfo.userId}`); const params = []; params[0] = util.replaceUndefined(userInfo.userId); params[1] = util.replaceUndefined(userInfo.srcloc); const rowUser = await db.findOne(UserLogin.loginUserById(), params, cacheOptions); if (util.isEmpty(rowUser)) { logger.error(`user not found : ${userInfo.userId}`); throw new CustomError('not-found', 24); } await db.execute(UserLogin.deleteSessionByUserId(), [rowUser.userId, rowUser.srcloc, rowUser.userType], cacheOptions); req.session.save(); // get latest user login info const returnUser = await db.findOne(UserLogin.loginUser(), [rowUser.userId, rowUser.srcloc], cacheOptions); returnUser.pwd = undefined; // returnUser.refreshToken = refreshToken.refreshToken; if (config.get('serverConfig.mode') !== 'test') { returnUser.accessToken = undefined; returnUser.expires = undefined; returnUser.refreshToken = undefined; } // modify information returnUser.userName = userInfo.family_name.concat(' ', rowUser.given_name) returnUser.basicLanguage = userInfo.lang.code || 'en' returnUser.email = userInfo.email returnUser.srcloc = userInfo.srcloc returnUser.rememberMe = userInfo.rememberMe || false; req.session.user = returnUser; await util.to(setRefreshToken(returnUser, req, res)); req.session.save(); await db.execute(UserLogin.modifySession(), [rowUser.userId, rowUser.srcloc, rowUser.userType, req.sessionID], cacheOptions); if (rowUser.srcloc === 'A') { returnUser.userType = parseInt((parseInt(returnUser.userType, 10) - 15000) / 10, 10); returnUser.userRole = parseInt(returnUser.userRole, 10) - 16000; returnUser.userStatus = parseInt(returnUser.userStatus, 10) - 17000; } const params2 = []; // update user login info params2[0] = util.replaceUndefined(rowUser.userId); params2[1] = util.replaceUndefined(rowUser.srcloc); await db.execute(UserLogin.login(), params2, cacheOptions); return returnUser; }; /** * reload user * * @param {Object} sessionId * @returns {Object} */ export const reload = async (sessionId) => { // logger.debug(`userlogin.reload : ${sessionId}`); const returnUser = await db.findOne(UserLogin.selectSessionById(), [sessionId], cacheOptions); if (util.isEmpty(returnUser)) { return false; } return true; }; /** * getSession * * @param {Object} sessionId * @returns {Object} */ export const getSession = async (sessionId) => { // logger.debug(`userlogin.reload : ${sessionId}`); const returnUser = await db.findOne(UserLogin.selectSessionById(), [sessionId], cacheOptions); return returnUser; }; /** * get refreshtoken * * @param {Object} sessionId * @returns {Object} */ export const getRefreshToken = async (pInfo) => { // logger.debug(`userlogin.reload : ${sessionId}`); const returnUser = await db.findOne(UserLogin.loginUser(), [pInfo.userId, pInfo.srcloc], cacheOptions); return returnUser; };
Comments