'use strict'; const User = require('../model/user'), UserModelFactory = require('../model/user_model_factory'), App = require('../model/application'), cache = require('../helpers/mem_cache'), util = require('util'), jwt = require('jsonwebtoken'), verifyAsync = util.promisify(jwt.verify), moment = require('moment'), ObjectId = require('mongodb').ObjectId, { Errors, UserTypes, DEFAULT_LANG } = require('../helpers/constants'), { AppError, AppParamError, AppAuthError } = require('../helpers/app_error'), mailer = require('../helpers/mailer'), env = require('../helpers/env'), subscriptionCtl = require("./subscription"); async function ensureParentExists(user) { if (!user || user.kind <= 1 || !user.parent || !ObjectId.isValid(user.parent)) return; const pUser = await User.findById(ObjectId(user.parent), null, { lean: true }); if (!pUser) AppError.throw(Errors.PARENT_NOT_EXIST); return pUser; } async function clearTempData(uid) { if (ObjectId.isValid(uid)) await App.deleteMany({ byUser: ObjectId(uid), $or: [{ status: -1 }, { status: 0 }] }); } async function createUser_post(req, res) { const _user = req.body; delete _user._id; await ensureParentExists(_user); const newUser = new User(_user); const user = await newUser.save(); res.json(user); } async function getUser_get(req, res) { const _user = await User.findById(req.params.userId, null, { lean: true }).populate({ path: 'Country', select: 'code name -_id' }); res.json(_user); } async function updateUser_put(req, res) { const _user = req.body, userId = req.params.userId; if (!_user || !ObjectId.isValid(userId)) AppParamError.throw(); delete req.body._id; let kind = _user.kind; if (!kind) { const theUser = await User.findById(userId, 'kind', { lean: true }); if (!theUser) AppError.throw(Errors.USER_NOT_FOUND); kind = theUser.kind; } const UserModel = UserModelFactory.create(kind); const user = await UserModel.findOneAndUpdate({ _id: ObjectId(userId) }, _user, { new: true, lean: true }); res.json(user); } async function deleteUser(req, res) { const _id = req.params.userId; if (!_id || !ObjectId.isValid(_id)) AppParamError.throw(); const user = await User.findByIdAndRemove({ _id: ObjectId(_id) }); if (user) await user.remove(); cache.delete(_id); res.json({ ok: true }); } async function login_post(req, res) { if (!req.body || !req.body.username || !req.body.password) AppAuthError.throw(Errors.WRONG_CREDENTIAL); const _options = { username: { $regex: new RegExp(`^${req.body.username}$`, 'i') }, password: req.body.password }; const _user = await User.findOne(_options, null, { lean: true }); if (!_user) AppAuthError.throw(Errors.WRONG_CREDENTIAL); if (_user.kind == UserTypes.DEVICE && (req.body.dev && req.body.dev == 'web')) AppAuthError.throw(Errors.INVALID_ACCOUNT); const hasParent = Boolean(_user && _user.parent && _user.kind >= 2); // The applicator user let _parentUser; if (hasParent) { _parentUser = await User.findOne({ _id: _user.parent }, null, { lean: true }); } else if (_user.kind === UserTypes.ADMIN || _user.kind === UserTypes.APP) { _parentUser = _user; } if (hasParent && !_parentUser) AppAuthError.throw(Errors.WRONG_CREDENTIAL); await clearTempData(_user._id); const isActive = hasParent ? (_parentUser.active && _user.active) : _user.active; let authUser; if (isActive) { const premium = _parentUser.premium || 0; // To be removed soon const token = jwt.sign({ uid: _user._id, ut: _user.kind }, env.TOKEN_SECRET, {}); // const memberInfo = {}; const memberInfo = _user.kind !== UserTypes.ADMIN ? await subscriptionCtl.resolvePaymentUser(_parentUser) : {}; let userLang = _user.lang; // #1518, Make sure only appy the language not it has not ever selected. if (!userLang) { const inUserLang = req.body.lang && req.body.lang.toLowerCase(); userLang = inUserLang && req.body.lang && ['en', 'pt', 'es'].includes(inUserLang) ? inUserLang : DEFAULT_LANG; } if (_user.kind === UserTypes.DEVICE) { authUser = { token: token }; } else { authUser = { _id: _user._id, token: token, roles: [_user.kind], pui: _parentUser ? _parentUser._id : _user._id, lang: userLang, billable: _parentUser.billable, pre: premium, membership: memberInfo }; } // Update user last login info await User.updateOne({ _id: _user._id }, { loggedInAt: Date.now(), lang: userLang }, { timestamps: false }); // Set the authenticated user info into the session cache cache.set(_user._id.toHexString(), { puid: _parentUser._id.toHexString(), billable: _parentUser.billable, premium: premium, membership: memberInfo, lang: userLang || DEFAULT_LANG, ts: moment.utc().unix(), kind: _user.kind }); } else { AppAuthError.throw(Errors.ACC_INACTIVE); } res.json(authUser); } async function isUserNameExists_post(req, res) { if (!req.body || !req.body.username) AppParamError.throw(); const user = await User.findOne({ username: ({ $regex: new RegExp(`^${req.body.username}$`, 'i') }) }); res.json(user ? 1 : 0); } async function search_post(req, res) { const _options = { markedDelete: { $ne: true } }; if (req.body.byPuid && ObjectId.isValid(req.body.byPuid)) _options.parent = ObjectId(req.body.byPuid); else AppParamError.throw(); if (req.body.kind) _options.kind = req.body.kind; else _options.kind = { $in: [UserTypes.APP_ADM, UserTypes.OFFICER, UserTypes.INSPECTOR] }; const users = await User.find(_options, '-password').lean(); res.json(users || []); } async function clearTempData_post(req, res) { await clearTempData(req.uid); if (cache.get(req.uid)) cache.delete(req.uid); res.json({ ok: true }); } async function setUserLanguage_post(req, res) { const lang = req.body.lang || DEFAULT_LANG; const user = await User.findByIdAndUpdate(ObjectId(req.uid), { $set: { lang: String(lang).trim() } }, { new: true, lean: true }); // Update user language in the cache const cUser = cache.get(req.uid); if (cUser && cUser.lang && cUser.lang != user.lang) { cUser.lang = user.lang; cache.set(req.uid, cUser); } res.json(user); } async function getUserDetail_post(req, res) { const uname = req.body.username; if (!req.body || !uname) return res.json(null); const user = await User.findOne({ username: { $regex: new RegExp(`^${uname}$`, 'i') } }, { password: 0 }).populate('parent', 'username').lean(); res.json(user); } function getHostFromReq(req) { let host = `https://${req.hostname}`; if (!req.app.isProd) host += ':4200'; return host; } async function mailPwdReset_post(req, res) { let userEmail = req.body.email; if (!userEmail) return res.json(null); else userEmail = userEmail.trim(); const user = await User.findOne({ username: userEmail }); if (!user) AppError.throw(Errors.USER_NOT_FOUND); // Create the reset token to use within the reset password email link const payload = { id: user._id, email: userEmail }; const secret = `${user.password}-${user.createdAt.getTime()}`; const token = jwt.sign(payload, secret, { expiresIn: env.PWD_RESET_VALID_TIME || '3h' }); await mailer.sendMail( 'reset-password', { locale: user.lang || DEFAULT_LANG, host: getHostFromReq(req), id: payload.id, token: token }, userEmail ); res.json({ result: 1 }); } async function getResetPwdToken_get(req, res) { if (!req.params || !req.params.id || !req.params.token) AppParamError.throw(); const user = await User.findOne({ _id: ObjectId(req.params.id) }).lean(); if (!user) AppError.throw(Errors.USER_NOT_FOUND); const secret = `${user.password}-${user.createdAt.getTime()}`; const verRes = await verifyAsync(req.params.token, secret); res.json({ id: verRes.id, token: req.params.token }); } async function resetPassword(req, res) { if (!req.body.id) AppParamError.throw(); const user = await User.findOne({ _id: ObjectId(req.body.id) }); if (!user) AppError.throw(Errors.USER_NOT_FOUND); const _user = user; const secret = `${user.get('password')}-${user.get('createdAt').getTime()}`; await jwt.verify(req.body.token, secret); _user.password = req.body.password; await _user.save(); await mailer.sendMail( 'password-reset', { locale: _user.lang || DEFAULT_LANG, host: getHostFromReq(req), username: _user.username }, _user.username, ); res.json({ ok: true }); } module.exports = { isUserNameExists_post, createUser_post, getUser_get, updateUser_put, deleteUser, login_post, search_post, clearTempData_post, setUserLanguage_post, getUserDetail_post, mailPwdReset_post, getResetPwdToken_get, resetPassword, ensureParentExists, clearTempData, getHostFromReq }