const MongoDB = require("../mongoDB.js"); const { client_logger } = require('../utils/analyticsLogger'); const bcrypt = require('bcrypt'); const crypto = require('crypto'); const { getSessionId, getUserId, getProfileId } = require('../utils/sessionUtils.js'); const { cookiesOptions } = require('../config/cookiesOptions'); const Notifications = require("../notifications"); // Object Definitions const Post = require("../def/post.js") const Profile = require("../def/profile.js"); // Function to Singup new users. An user is a combination of a user obj and a profile. // When new users are subscribed, they have a single profile, which is the personal one. // Other profiles can be link to that user, like groups or courses. const signup = async function (req, res) { const username = req.query.username || req.body.username; const password = req.query.password || req.body.password; const email = req.query.email || req.body.email; const profile = req.query.profile || req.body.profile; if (!username || !password || !email) return res.json({ status: "Incomplete information!" }); // Check if the new user has an invitation. const DB = await MongoDB.getDB; if (!await DB.getInvitation(email)) { client_logger.capture({ distinctId: 'app_level', event: 'server@' + req.method + '@' + req.originalUrl + '@NotInvited', properties: { username: username, email: email, } }); return res.json({ status: "Not invitation found!" }); } let isUserAlreadyRegistered = await DB.getUser(email); if (isUserAlreadyRegistered && isUserAlreadyRegistered._id) return res.json({ status: "This user is already registered" }); // Hash password to be stored on the DB. // TODO: I think this is missing a Salt factor to improve security const hashedPassword = await bcrypt.hash(password, 10); const newUserObject = await DB.newUser({ username: username.toLowerCase(), email: email.toLowerCase(), password: hashedPassword }); // If newUserObject it's an error message, we check by looking toLowerCase function if (!newUserObject.toLowerCase) { let user = { userid: newUserObject.insertedId, profile: profile, } // Filter the provided information by the template, and adding to the DB, and login. const userObj = new Profile(user); await DB.newProfile(userObj); client_logger.capture({ distinctId: newUserObject.insertedId, event: 'server@' + req.method + '@' + req.originalUrl, }); // TODO: this might fail, add catch scenarios return await login(req, res); } // Error return client_logger.capture({ distinctId: 'app_level', event: 'server@' + req.method + '@' + req.originalUrl + '@error', properties: { ustatus: newUserObject, } }); return res.json({ status: newUserObject }) } // Function for user Login, it returns the coockies/json for user_id session_id and profile_id const login = async function (req, res) { // Check if user is already logged in and redirect to root if so. const session_id = getSessionId(req); const user_sid = getUserId(req); if (session_id && user_sid) { const userInfo = await DB.checkSessionOnDB(session_id, user_sid); if (userInfo) return res.redirect('/'); } const username = req.body.username || req.query.username; const password = req.body.password || req.query.password || ""; const DB = await MongoDB.getDB; const user = await DB.getUser(username); if (!user) { client_logger.capture({ distinctId: 'app_level', event: 'server@' + req.method + '@' + req.originalUrl + '@userNotFound', properties: { username: username, } }); return res.json({ status: "user not founded" }); } // TODO: Also add salt parameter here. const isSamePassword = await bcrypt.compare(password, user.password); if (!isSamePassword) return res.json({ status: "incorrect password" }); try { // Store a new session loging on DB, and use ID as session ID const sessionObj = await DB.newSession(user._id); // Create coockies with information for Auth res.cookie('user_sid', user._id, cookiesOptions); res.cookie('session_id', sessionObj.insertedId, cookiesOptions); // Chooses the most recent update profile as current active profile const latestUpdatedProfile = await DB.latestProfile(user._id); res.cookie('profile_id', latestUpdatedProfile._id, cookiesOptions); client_logger.identify({ distinctId: user._id, properties: { name: latestUpdatedProfile.profile.firstName, } }); client_logger.capture({ distinctId: user._id, event: 'server@' + req.method + '@' + req.originalUrl, }); return res.json({ status: "ok", user_sid: user._id, session_id: sessionObj.insertedId, profile_id: latestUpdatedProfile._id }); } catch (error) { console.error(error); client_logger.capture({ distinctId: 'app_level', event: 'server@' + req.method + '@' + req.originalUrl + '@error', }); return res.json({ status: "Error on this User Profile, please contact admin." }); } } // route for user logout const logout = async function (req, res) { const session_id = getSessionId(req); const user_sid = getUserId(req); if (session_id && user_sid) { res.clearCookie('session_id'); res.clearCookie('user_sid'); //remove from DB const DB = await MongoDB.getDB; DB.removeSession(session_id); client_logger.capture({ distinctId: user_sid, event: 'server@' + req.method + '@' + req.originalUrl, }); res.redirect('/'); } else { res.redirect('/login'); } } // Util function for generating new random password for users. // TODO: Probably we can do someting better here. function generatePassword() { var length = 8, charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", retVal = ""; for (var i = 0, n = charset.length; i < length; ++i) { retVal += charset.charAt(Math.floor(Math.random() * n)); } return retVal; } const resetPassword = async function (req, res) { const session_id = getSessionId(req); const user_sid = getUserId(req); const DB = await MongoDB.getDB; if (session_id && user_sid) { // Sadly reusing this endpoint to change password to legged in users. // TODO: Move change password logic to its own endpoint. const userInfo = await DB.checkSessionOnDB(session_id, user_sid); if (userInfo) { const password = req.body.password; const hashedPassword = await bcrypt.hash(password, 10); // TODO: Add salt to password here as well. DB.resetUserPassword(userInfo.username, hashedPassword); return res.json({ status: "ok", details: 'password changed!' // This should be an enum that syncs with clients. }); } } // Logic for non-logged in users. const username = req.body.username; const user = await DB.getUser(username); if (!user) return res.json({ status: "user not founded" }); const password = generatePassword(); const hashedPassword = await bcrypt.hash(password, 10); // TODO: Add salt to password here as well. // TODO: We need to limit this to every 2 hours or something like this. // TODO: Move this template to the Notif file. DB.resetUserPassword(username, hashedPassword); Notifications.sendEmail(username, "Your new credentials", `

Hello,

This is your new password: ${password}

Log in

Blessings

Emmanuel International Ministries

`) client_logger.capture({ distinctId: user._id, event: 'server@' + req.method + '@' + req.originalUrl, properties: { username: username, } }); return res.json({ status: "ok", details: 'Check your email for new password' // Enum of details? }); } module.exports = { signup, login, logout, resetPassword, }