const User = require('../models/User'); const {body, validationResult} = require('express-validator'); const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); const crypto = require('crypto'); const sharp = require('sharp'); const {secretKey} = require('./../authentication'); const {_sr} = require('../plugins/serverResponses'); const {res404, res500, hasWhiteSpaces, isLatinCharactersWithSymbol, generateRandomDigits} = require('../plugins/controllersHelperFunctions'); const fs = require('fs'); const nodemailer = require('nodemailer'); const Category = require('../models/Category'); const Logger = require('../models/Logger') ////// variables const profileWidth = 500; const profileHeight = 500; const profileQuality = 100; const hostURL = 'localhost:6688'; const limit = 20; const select = '-password -token'; /** @todo add private to query */ /** auth --------------------------------------------- */ /////////////////////////////////////////////////////////////////////// login module.exports.login = [ [ body('username') .if((value, {req}) => { return req.body.password && req.body.password.length >= 4 }) .custom((value, {req}) => { var test = value; return User.findOne({username: value}) .then(user => { if (!user) return Promise.reject(_sr['fa'].not_found.password) else return true }) }), body('password') .notEmpty().withMessage(_sr['fa'].required.password) .bail() .isLength({min: 4}).withMessage(_sr['fa'].min_char.min4), body('remember_me') .exists().withMessage(_sr['fa'].required.remember_me) .bail() .isBoolean().withMessage(_sr['fa'].format.boolean) ], async (req, res) => { const errors = validationResult(req) if (!errors.isEmpty()) return res.status(422).json({validation: errors.mapped()}) try { var {username, password, remember_me} = req.body; /** get user */ var user = await User.findOne({$or: [{username: username}, {email: username}]}); const logger = await Logger.findOne({user:user._id}) if (!user.registration_done) { return res.status(403).json({message: `اکانت شما غیرفعال می باشد لطفا با ادمین تماس بگیرید`}); } const isMatch = await bcrypt.compare(password,user.password) /**check user */ if (user) { /** check password */ if (isMatch) { if(logger){ await Logger.findOneAndUpdate({user:user._id},{$set:{LastLogin:new Date()}},{new:true}) }else { const newLogger = new Logger({ user:user, LastLogin:new Date() }) await newLogger.save(); } /** if have token and it true show result else generate new token */ if (user.token) { jwt.verify(user.token.replace(/^Bearer\s/, ''), secretKey, async (err, decoeded) => { if (err || !decoeded) { /** generate new token */ var token = await jwt.sign({_id: user._id}, secretKey, {expiresIn: remember_me ? '7d' : '1h'}); user.token = token; await user.save(); return res.json({token: user.token}); } else { return res.json({token: user.token}); } }); } else { /** generate new token */ var token = await jwt.sign({_id: user._id}, secretKey, {expiresIn: remember_me ? '7d' : '1h'}); user.token = token; await user.save(); return res.json({token: user.token}); } } else { return res.status(422).json({validation: {username: {msg: _sr['fa'].not_found.password}}}); } } else { return res.status(422).json({validation: {username: {msg: _sr['fa'].not_found.password}}}); } } catch (error) { return res500(res, error?.message); } } ] /////////////////////////////////////////////////////////////////////// logout module.exports.logout = [ async (req, res) => { const token = req.headers.authorization?.replace(/^Bearer\s/, ''); if (token) { User.findOneAndUpdate({token: token}, {token: null}, (err, oldData) => { if (err || !oldData) res.status(401).json({message: 'Your not logged in'}) else return res.json({message: 'You are logged out.'}) }) } else { return res.status(401).json({message: 'Your not logged in'}) } } ] /////////////////////////////////////////////////////////////////////// get user module.exports.getuser = [ async (req, res) => { const token = req.headers.authorization?.replace(/^Bearer\s/, ''); // check if token is not destroyed var user = await User.findOne({token: token}).select('-password'); if (!user) { return res.status(401).json({message: 'unauthorized'}); } if (!user.registration_done) { return res.status(401).json({message: `اکانت شما غیرفعال می باشد لطفا با ادمین تماس بگیرید`}); } //verifies secret and checks if the token is expired try { var checkToken = await jwt.verify(user.token.replace(/^Bearer\s/, ''), secretKey); if (!checkToken) { return res.status(401).json({message: 'unauthorized'}); } return res.json({user: user}); } catch (error) { return res.status(401).json({message: 'unauthorized'}); } } ] /** end auth --------------------------------------------- */ /////////////////////////////////////////////////////////////////////// admin module.exports.registerAdmin = [ [ body('firstName') .notEmpty().withMessage(_sr['fa'].required.first_name), body('lastName') .notEmpty().withMessage(_sr['fa'].required.last_name), body('mobileNumber') .if((value, {req}) => { return req.body.mobileNumber }) .custom((value, {req}) => { if (!_.isEmpty(value) && !lodash.isNumeric(value)) { return Promise.reject(_sr['fa'].format.number); } return User.findOne({mobileNumber: value}) .then(user => { if (user) return Promise.reject(_sr['fa'].duplicated.phoneNumber); else return true }) }), // body('email') // .optional().isEmail().withMessage(_sr['fa'].format.email) // .if((value, {req}) => { // return req.body.email // }) // .custom((value, {req}) => { // return User.findOne({email: value}) // .then(user => { // if (user) return Promise.reject(_sr['fa'].duplicated.email) // else return true // }) // }), body('username') .isLength({min: 4}).withMessage(_sr['fa'].min_char.min4) .bail() .custom((value, {req}) => { if (hasWhiteSpaces(value)) return Promise.reject(_sr['fa'].response.whiteSpace) else return true }) .bail() .custom((value, {req}) => { if (!isLatinCharactersWithSymbol(value)) return Promise.reject(_sr['fa'].response.latinChar) else return true }) .bail() .custom((value, {req}) => { return User.findOne({username: value}) .then(user => { if (user) return Promise.reject(_sr['fa'].duplicated.username) else return true }) }), body('password') .isLength({min: 4}).withMessage(_sr['fa'].min_char.min4) .custom((value, {req}) => { if (value === req.body.password_confirmation) return Promise.resolve() else return Promise.reject(_sr['fa'].response.passwords_not_match) }), body('permissions') .if((value, {req}) => { return req.body.specialPermissions; }) .custom((value, {req}) => { var permission = _.isString(value) ? JSON.parse(value) : value; if (!_.isArray(permission)) { return Promise.reject(`این فیلد باید از نوع آرایه باشد`); } if (!permission.length) { return Promise.reject(`شما باید یک دسترسی انتخاب کنید`); } if (permission.length && !permission.includes('superAdmin') && (!permission.includes('news') && !permission.includes('categories'))) { return Promise.reject(`شما باید همراه با دسترسی مخصوص، دسترسی مدیریت صفحات یا اخبار را ارسال کنید`); } else { return true } }), body('specialPermissions') .optional() .custom((value, {req}) => { var specialPermissions = _.isString(value) && !_.isEmpty(value) ? JSON.parse(value) : value; if (!_.isArray(specialPermissions)) { return Promise.reject(`این فیلد باید از نوع آرایه باشد`); } return true }), body('portals') .custom((value, {req}) => { var portals = _.isString(value) ? JSON.parse(value) : value; if (!_.isArray(portals)) { return Promise.reject(`این فیلد باید از نوع آرایه باشد`); } if (!portals.length) { return Promise.reject(`شما باید محدوده دسترسی را مشخص کنید`); } let isValid = portals.every((item) => { return portal.includes(item); }); if (isValid) { return true; } else { return Promise.reject(`${_sr['fa'].optional.onlyValid} => ${portal}`); } }), body('registration_done') .isBoolean().withMessage(_sr['fa'].format.boolean), ], async (req, res) => { const errors = validationResult(req) if (!errors.isEmpty()) return res.status(422).json({validation: errors.mapped()}) try { var { username, password, firstName, lastName, email, mobileNumber, permissions, specialPermissions, registration_done, portals } = req.body; /** this code fix problem with array in form data */ permissions = _.isString(permissions) ? JSON.parse(permissions) : permissions; specialPermissions = _.isString(specialPermissions) ? JSON.parse(specialPermissions) : specialPermissions; portals = _.isString(portals) ? JSON.parse(portals) : portals; var data = { username, firstName, lastName, email, mobileNumber, permissions, registration_done, private: false, _creator: req.user?._id || null, _updatedBy: req.user?._id || null, specialPermissions: permissions.includes('superAdmin') ? [] : specialPermissions, portals: [...new Set(portals)] }; /** * if we have pic upload to server */ if (req.files && req.files.profilePic) { const image = req.files.profilePic; if (!_sr.supportedImageFormats.includes(image.mimetype.split('/')[1])) { return res.status(422).json({validation: {profilePic: {msg: _sr['fa'].format.image}}}); } const imageName = 'user_' + Date.now() + '_' + username + '.' + image.mimetype.split('/')[1]; data.profilePic = imageName try { const target = sharp(image.data) await target .resize(profileWidth, profileHeight, { fit: 'cover' }) .jpeg({ quality: profileQuality }) .toFile(`./static/uploads/images/users/${imageName}`) } catch (e) { return res500(res, e?.message) } } /** * generate salt */ var salt = bcrypt.genSaltSync(10); /** * hash password with salt */ var hashPassword = bcrypt.hashSync(password, salt); if (!hashPassword) { return res500(res, `error in hash`); } data.password = hashPassword; /** * create new admin */ const newAdmin = await User.create(data); /** * if we have error show him */ if (!newAdmin) { return res500(`error in register admin`); } /** * show result */ return res.status(201).json({ message: _sr['fa'].response.success_save }) } catch (error) { return res500(res, error?.message); } } ] /////////////////////////////////////////////////////////////////////// get user module.exports.getUserByAdmin = [ async (req, res) => { try { const userId = req.params.id; /** get one user */ const user = await User.findById(userId).populate('specialPermissions').select(select); /** if not exist show message to client */ if (!user) { return res404(res, `کاربر مورد نظر پیدا نشد`); } /** show result */ return res.json(user); } catch (error) { res500(res, error?.message); } } ] /////////////////////////////////////////////////////////////////////// get all user module.exports.getAllUsers = [ async (req, res) => { try { /** get all user */ const users = await User.paginate({private: false}, {page: req.query.page, limit, select}); /** if not exist show message to client */ if (!users) { return res500(res, `مشکلی در گرفتن اطلاعات پیش آمده لطفا دوباره تلاش کنید`); } /** show result */ return res.json(users); } catch (error) { return res500(res, error?.message); } } ] module.exports.changeUserStatus = [ async (req, res) => { try { const userId = req.params.id let user = await User.findOne({private: false , _id : userId}); if (!user) { return res404(res, `کاربر مورد نظر پیدا نشد`); } user.registration_done = !user.registration_done; user.save(); /** show result */ return res.json(_sr['fa'].response.success_save); } catch (error) { return res500(res, `مشکلی در اجرای درخواست شما بوجود امده است`); } } ] /////////////////////////////////////////////////////////////////////// delete one user module.exports.deleteOneUser = [ async (req, res) => { try { const userId = req.params.id; /** get one user */ const user = await User.findOne({_id: userId}); /** if not exist show message to client */ if (!user) { return res404(res, `کاربر مورد نظر پیدا نشد`); } if (user.private) { return res.status(403).json({message: `شما نمیتوانید این کاربر را حذف کنید`}); } /** * if exist old pic delete it from file */ if (user.profilePic) { var oldPic = `./static${user.profilePic}`; if (fs.existsSync(oldPic)) { fs.unlink(oldPic, (err) => { if (err) return res500(res, err?.message); }); } } /** delete user */ await user.deleteOne(); /** show result */ return res.json(user); } catch (error) { res500(res, error?.message); } } ] /////////////////////////////////////////////////////////////////////// update user module.exports.updateUser = [ [ body('firstName') .notEmpty().withMessage(_sr['fa'].required.first_name), body('lastName') .notEmpty().withMessage(_sr['fa'].required.last_name), body('mobileNumber') .if((value, {req}) => { return req.body.mobileNumber }) .custom((value, {req}) => { if (!_.isEmpty(value) && !lodash.isNumeric(value)) { return Promise.reject(_sr['fa'].format.number); } return true; }), // body('email') // .optional().isEmail().withMessage(_sr['fa'].format.email), body('permissions') .if((value, {req}) => { return req.body.specialPermissions; }) .custom((value, {req}) => { let permission = _.isString(value) ? JSON.parse(value) : value; let specialPermissions = _.isString(req.body.specialPermissions) ? JSON.parse(req.body.specialPermissions) : req.body.specialPermissions; if (!_.isArray(permission)) { return Promise.reject(`این فیلد باید از نوع آرایه باشد`); } if (!permission.length) { return Promise.reject(`شما باید یک دسترسی انتخاب کنید`); } if (specialPermissions.length !== 0 && !permission.includes('superAdmin') && (!permission.includes('news') && !permission.includes('categories'))) { return Promise.reject(`شما باید همراه با دسترسی مخصوص، دسترسی مدیریت صفحات یا اخبار را ارسال کنید`); } else { return true } }), body('specialPermissions') .optional() .custom((value, {req}) => { var specialPermissions = _.isString(value) && !_.isEmpty(value) ? JSON.parse(value) : value; if (!_.isArray(specialPermissions)) { return Promise.reject(`این فیلد باید از نوع آرایه باشد`); } return true }), body('portals') .custom((value, {req}) => { var portals = _.isString(value) ? JSON.parse(value) : value; if (!_.isArray(portals)) { return Promise.reject(`این فیلد باید از نوع آرایه باشد`); } if (!portals.length) { return Promise.reject(`شما باید محدوده دسترسی را مشخص کنید`); } let isValid = portals.every((item) => { return portal.includes(item); }); if (isValid) { return true; } else { return Promise.reject(`${_sr['fa'].optional.onlyValid} => ${portal}`); } }), body('registration_done') .isBoolean().withMessage(_sr['fa'].format.boolean), ], async (req, res) => { const errors = validationResult(req) if (!errors.isEmpty()) return res.status(422).json({validation: errors.mapped()}) try { const userId = req.params.id; var { firstName, lastName, email, mobileNumber, permissions, specialPermissions, registration_done, portals } = req.body; /** this code fix problem with array in form data */ permissions = _.isString(permissions) ? JSON.parse(permissions) : permissions; specialPermissions = _.isString(specialPermissions) ? JSON.parse(specialPermissions) : specialPermissions; portals = _.isString(portals) ? JSON.parse(portals) : portals; /** get one user */ var user = await User.findById(userId); /** if not exist show message to client */ if (!user) { return res404(res, `کاربر مورد نظر پیدا نشد`); } if (!user.private && !req.user.private) { return res.status(403).json({message: `شما نمیتوانید این کاربر را آپدیت کنید`}); } /** check new email is dupllicate or not */ if (user.email !== email) { var checkDupplicate = await User.find({email: email}); if (checkDupplicate.length) { return res.status(422).json({validation: {email: {msg: `${_sr['fa'].duplicated.email}`}}}); } } /** check new mobileNumber is dupllicate or not */ if (user.mobileNumber !== mobileNumber) { var checkDupplicate = await User.find({mobileNumber: mobileNumber}); if (checkDupplicate.length) { return res.status(422).json({validation: {mobileNumber: {msg: `${_sr['fa'].duplicated.phoneNumber}`}}}); } } user.firstName = firstName; user.lastName = lastName; user.password = user.password; user.scope = user.scope; user.token = user.token; user.mobileNumber = mobileNumber; user.email = email; user.registration_done = registration_done; user._creator = req.user._id; user.username = user.username; user.permissions = permissions; user.specialPermissions = permissions?.includes('superAdmin') ? [] : specialPermissions; user.private = user.private; user.portals = [...new Set(portals)] if (req.files && req.files.profilePic) { const image = req.files.profilePic; if (!_sr.supportedImageFormats.includes(image.mimetype.split('/')[1])) { return res.status(422).json({validation: {profilePic: {msg: _sr['fa'].format.image}}}); } const imageName = 'user_' + Date.now() + '_' + user.username + '.' + image.mimetype.split('/')[1]; try { const target = sharp(image.data) await target .resize(profileWidth, profileHeight, { fit: 'cover' }) .jpeg({ quality: profileQuality }) .toFile(`./static/uploads/images/users/${imageName}`); /** * if exist old pic delete it from file */ if (user.profilePic) { var oldPic = `./static${user.profilePic}`; if (fs.existsSync(oldPic)) { fs.unlink(oldPic, (err) => { if (err) return res500(res, err?.message); }); } } } catch (e) { return res500(res, e?.message); } user.profilePic = imageName; } await user.save(); /** show result */ return res.json(user); } catch (error) { res500(res, error?.message); } } ] /////////////////////////////////////////////////////////////////////// change password module.exports.changePassword = [ [ body('password') .isLength({min: 4}).withMessage(_sr['fa'].min_char.min4) .custom((value, {req}) => { if (value === req.body.password_confirmation) return Promise.resolve() else return Promise.reject(_sr['fa'].response.passwords_not_match) }), ], async (req, res) => { const errors = validationResult(req) if (!errors.isEmpty()) return res.status(422).json({validation: errors.mapped()}) try { const userId = req.params.id; const password = req.body.password; /** get one user */ var user = await User.findById(userId); /** if not exist show message to client */ if (!user) { return res404(res, `کاربر مورد نظر پیدا نشد`); } if (!user.private && !req.user.private) { return res.status(403).json({message: `شما نمی توانید رمز عبور این کاربر را تغییر دهید`}); } /** * generate salt */ var salt = bcrypt.genSaltSync(10); /** * hash password with salt */ var hashPassword = bcrypt.hashSync(password, salt); if (!hashPassword) { return res500(res, `error in hash`); } user.password = hashPassword; user.token = null await user.save(); /** show result */ return res.json({message: _sr['fa'].response.success_save}); } catch (error) { res500(res, error?.message); } } ] /////////////////////////////////////////////////////////////////////// get profile(get self) module.exports.getProfile = [ async (req, res) => { try { const userId = req.user._id; /** get one user */ const user = await User.findById(userId).select(select); /** if not exist show message to client */ if (!user) { return res404(res, `کاربر مورد نظر پیدا نشد`); } /** show result */ return res.json(user); } catch (error) { res500(res, error?.message); } } ] /////////////////////////////////////////////////////////////////////// update profile(get self) module.exports.updateProfile = [ [ body('firstName') .notEmpty().withMessage(_sr['fa'].required.first_name), body('lastName') .notEmpty().withMessage(_sr['fa'].required.last_name), body('mobileNumber') .isNumeric().withMessage(_sr['fa'].format.number), // body('email') // .isEmail().withMessage(_sr['fa'].format.email), ], async (req, res) => { const errors = validationResult(req) if (!errors.isEmpty()) return res.status(422).json({validation: errors.mapped()}) try { const userId = req.user._id; const { firstName, lastName, email, mobileNumber, } = req.body; /** get one user */ var user = await User.findById(userId); /** if not exist show message to client */ if (!user) { return res404(res, `کاربر مورد نظر پیدا نشد`); } /** check new email is dupllicate or not */ if (user.email != email) { var checkDupplicate = await User.find({email: email}); if (checkDupplicate.length) { return res.status(422).json({validation: {email: {msg: `${_sr['fa'].duplicated.email}`}}}); } } /** check new mobileNumber is dupllicate or not */ if (user.mobileNumber != mobileNumber) { var checkDupplicate = await User.find({mobileNumber: mobileNumber}); if (checkDupplicate.length) { return res.status(422).json({validation: {mobileNumber: {msg: `${_sr['fa'].duplicated.phoneNumber}`}}}); } } user.firstName = firstName; user.lastName = lastName; user.password = user.password; user.scope = user.scope; user.token = user.token; user.mobileNumber = mobileNumber; user.email = email; user.registration_done = user.registration_done; user._creator = req.user._id; user.username = user.username; user.permissions = user.permissions; user.specialPermissions = user.specialPermissions; user.private = user.private; /** if exist a picture */ if (req.files && req.files.profilePic) { const image = req.files.profilePic; if (!_sr.supportedImageFormats.includes(image.mimetype.split('/')[1])) { return res.status(422).json({validation: {profilePic: {msg: _sr['fa'].format.image}}}); } const imageName = 'user_' + Date.now() + '_' + user.username + '.' + image.mimetype.split('/')[1]; try { const target = sharp(image.data) await target .resize(profileWidth, profileHeight, { fit: 'cover' }) .jpeg({ quality: profileQuality }) .toFile(`./static/uploads/images/users/${imageName}`); /** * if exist old pic delete it from file */ if (user.profilePic) { var oldPic = `./static${user.profilePic}`; if (fs.existsSync(oldPic)) { fs.unlink(oldPic, (err) => { if (err) return res500(res, err?.message); }); } } } catch (e) { return res500(res, e?.message); } user.profilePic = imageName; } await user.save(); /** show result */ return res.json(user); } catch (error) { res500(res, error?.message); } } ] /////////////////////////////////////////////////////////////////////// change password (profile) module.exports.changePasswordProfile = [ [ body('password') .isLength({min: 4}).withMessage(_sr['fa'].min_char.min4) .custom((value, {req}) => { if (value === req.body.password_confirmation) return Promise.resolve() else return Promise.reject(_sr['fa'].response.passwords_not_match) }), ], async (req, res) => { const errors = validationResult(req) if (!errors.isEmpty()) return res.status(422).json({validation: errors.mapped()}) try { const userId = req.user._id; const password = req.body.password; /** get one user */ var user = await User.findById(userId); /** if not exist show message to client */ if (!user) { return res404(res, `کاربر مورد نظر پیدا نشد`); } /** * generate salt */ var salt = bcrypt.genSaltSync(10); /** * hash password with salt */ var hashPassword = bcrypt.hashSync(password, salt); if (!hashPassword) { return res500(res, `مشکلی در تغییر پسورد پیش آمده است`); } user.password = hashPassword; user.token = null await user.save(); /** show result */ return res.json({message: _sr['fa'].response.success_save}); } catch (error) { res500(res, error?.message); } } ] /////////////////////////////////////////////////////////////////////// forget password generate token module.exports.forgetPassGenerateToken = [ [ body('email') .optional().isEmail().withMessage(_sr['fa'].format.email) .if((value, {req}) => { return req.body.email }) .custom((value, {req}) => { return User.findOne({email: value}) .then(user => { if (!user) return Promise.reject(_sr['fa'].not_found.email) else return true }) }), ], async (req, res) => { const errors = validationResult(req) if (!errors.isEmpty()) return res.status(422).json({validation: errors.mapped()}) try { const email = req.body.email; /** get one user */ var user = await User.findOne({email: email}); /** if not exist show message to client */ if (!user) { return res404(res, `کابر مورد نظر پیدا نشد`); } const token = crypto.randomBytes(20).toString('hex'); ////////////////////////////////////////////////////////////////////////////////////////////////////////////////// let transporter = nodemailer.createTransport({ host: "www.rubynaturalco.com", port: 587, secure: false, // true for 465, false for other ports auth: { user: 'recovery@rubynaturalco.com', pass: 'jd-m2?3ao?b(' }, tls: { rejectUnauthorized: false } }) const emailHTMLTemplate = `
` // send mail with defined transport object let info = await transporter.sendMail({ from: '"Ostandari Portal"