import { Request, Response } from "express"; import logger from "../logger/logger"; import User from "../models/user"; import { isAccountNameValid, isLanguageCodeValid, isPasswordValid, isUsernameValid, } from "../validator/validator"; import { CALENDAR_MAX_FUTURE_BOOKING_DAYS, CALENDAR_MAX_SERVICE_DURATION, CALENDAR_MIN_EARLIEST_BOOKING_TIME, Roles, USER_ANALYTICS_ENABLED_DEFAULT, } from "../utils/constants"; import { decodeBase64, getUserSession, hashPassword, matchPassword, newStoreId, newUserId, saveSession, } from "../utils/utils"; import Store from "../models/store"; import Session from "../models/session"; export async function SignUp(req: Request, res: Response) { try { let { username, accountName, password, language } = req.body; // validate request if ( !username || !accountName || !password || !language || !isUsernameValid(username) || !isAccountNameValid(accountName) || !isLanguageCodeValid(language) ) { return res.status(400).send({ err: "invalid request" }); } accountName = accountName.toLowerCase(); // check if user already exists const existingUser = await User.findOne({ where: { account_name: accountName, }, }); if (existingUser) { logger.debug( "User already exists with this accountName: %s", accountName ); return res.status(400).send({ err: "invalid request" }); } // decode password const decodedPassword = decodeBase64(password); if (!isPasswordValid(decodedPassword)) { logger.debug("Password is not valid"); return res.status(400).send({ err: "invalid request" }); } // hash password const hashedPassword = await hashPassword(decodedPassword); // create store let userId = newUserId(); Store.create({ store_id: newStoreId(), owner_user_id: userId, name: username, calendar_max_future_booking_days: CALENDAR_MAX_FUTURE_BOOKING_DAYS, calendar_min_earliest_booking_time: CALENDAR_MIN_EARLIEST_BOOKING_TIME, calendar_max_service_duration: CALENDAR_MAX_SERVICE_DURATION, }) .then(async (store) => { logger.debug( "Store created with storeId: %s storeName: %s", store.store_id, store.name ); // create user await User.create({ user_id: userId, store_id: store.store_id, role: Roles.Master, account_name: accountName, username: username, password: hashedPassword, language: language, analytics_enabled: USER_ANALYTICS_ENABLED_DEFAULT, }) .then((user) => { logger.debug( "User created with accountName: %s username: %s", user.account_name, user.username ); // create session saveSession(req, res, user.user_id, user.username); }) .catch((err) => { logger.error(err); res.status(500).send({ err: "invalid request" }); }); }) .catch((err) => { logger.error(err); res.status(500).send({ err: "invalid request" }); }); } catch (error) { logger.error(error); res.status(500).send({ err: "invalid request" }); } } export async function Login(req: Request, res: Response) { try { let { accountName, password } = req.body; // validate request if (!accountName || !password) { return res.status(400).send({ err: "invalid request" }); } accountName = accountName.toLowerCase(); if (!isAccountNameValid(accountName)) { return res.status(400).send({ err: "invalid request" }); } // check if user exists const user = await User.findOne({ where: { account_name: accountName, }, }); if (!user) { logger.debug( "User does not exist with this accountName: %s", accountName ); return res.status(400).send({ err: "invalid request" }); } // decode password const decodedPassword = decodeBase64(password); if (!isPasswordValid(decodedPassword)) { logger.debug("Password is not valid"); return res.status(400).send({ err: "invalid request" }); } // compare password const match = await matchPassword(decodedPassword, user.password); if (!match) { logger.debug("Password is not valid"); return res.status(400).send({ err: "invalid request" }); } // create session saveSession(req, res, user.user_id, user.username); } catch (error) { logger.error(error); res.status(500).send({ err: "invalid request" }); } } export async function Logout(req: Request, res: Response) { try { const session = await getUserSession(req); if (!session) { return res.status(401).send({ err: "unauthorized" }); } await session.destroy(); res.status(200).send({ msg: "logout successful" }); } catch (error) { logger.error(error); res.status(500).send({ err: "invalid request" }); } } export async function GetUser(req: Request, res: Response) { try { const session = await getUserSession(req); if (!session) { return res.status(401).send({ err: "unauthorized" }); } const user = await User.findOne({ where: { user_id: session.user_id, }, attributes: [ "user_id", "username", "store_id", "language", "analytics_enabled", ], }); if (!user) { return res.status(401).send({ err: "unauthorized" }); } const stores = await Store.findAll({ where: { owner_user_id: user.user_id, }, attributes: ["store_id", "name"], }); let respData = { user: user, stores: stores, // only temporary until we have a proper permissions system permissions: [] as string[], }; // if user is not a store master, then check if user is a worker if (!stores || stores.length === 0) { const store = await Store.findOne({ where: { store_id: user.store_id, }, attributes: ["store_id", "name"], }); if (!store) { return res.status(401).send({ err: "unauthorized" }); } stores.push(store); respData.stores = stores; respData.permissions.push("calendar"); } else { respData.permissions.push( "settings", "employees", "services", "calendar", "website" ); } // update user session last_used Session.update( { last_used: new Date(), }, { where: { session_id: session.session_id, }, } ); res.status(200).send(respData); } catch (error) { logger.error(error); res.status(500).send({ err: "invalid request" }); } } export async function IsAccountNameAvailable(req: Request, res: Response) { try { let { accountName } = req.body; // validate request if (!accountName) { return res.status(400).send({ err: "invalid request" }); } accountName = accountName.toLowerCase(); if (!isAccountNameValid(accountName)) { return res.status(400).send({ err: "invalid request" }); } // check if user exists const user = await User.findOne({ where: { account_name: accountName, }, }); if (user) { logger.debug( "User already exists with this accountName: %s", accountName ); return res.status(400).send({ err: "invalid request" }); } res.status(200).send({ msg: "account name available" }); } catch (error) { logger.error(error); res.status(500).send({ err: "invalid request" }); } } export async function GetUserProfileSettings(req: Request, res: Response) { try { const session = await getUserSession(req); if (!session) { return res.status(401).send({ err: "unauthorized" }); } const user = await User.findOne({ where: { user_id: session.user_id, }, attributes: ["language", "analytics_enabled", "username", "account_name"], }); res.status(200).json(user); } catch (error) { logger.error(error); res.status(500).send({ err: "invalid request" }); } } export async function UpdateUserProfileSettings(req: Request, res: Response) { try { const { language, analyticsEnabled, username, accountName } = req.body; if ( !language && analyticsEnabled === undefined && !username && !accountName ) { return res.status(400).send({ err: "invalid request" }); } const session = await getUserSession(req); if (!session) { return res.status(401).send({ err: "unauthorized" }); } const user = await User.findOne({ where: { user_id: session.user_id, }, }); if (!user) { return res.status(401).send({ err: "unauthorized" }); } if (language) { user.language = language; } if (analyticsEnabled !== undefined) { user.analytics_enabled = analyticsEnabled; } if (username) { if (!isUsernameValid(username)) { return res.status(400).send({ err: "invalid request" }); } user.username = username; } if (accountName) { if (!isAccountNameValid(accountName)) { return res.status(400).send({ err: "invalid request" }); } user.account_name = accountName; } await user.save(); res.status(200).send({ msg: "user profile settings updated" }); } catch (error) { logger.error(error); res.status(500).send({ err: "invalid request" }); } } export async function UpdateUserProfilePassword(req: Request, res: Response) { try { const { currentPassword, newPassword } = req.body; if (!currentPassword || !newPassword) { return res.status(400).send({ err: "invalid request" }); } const session = await getUserSession(req); if (!session) { return res.status(401).send({ err: "unauthorized" }); } const user = await User.findOne({ where: { user_id: session.user_id, }, attributes: ["password"], }); if (!user) { return res.status(401).send({ err: "unauthorized" }); } const decodedCurrentPassword = decodeBase64(currentPassword); const match = await matchPassword(decodedCurrentPassword, user.password); if (!match) { return res.status(400).send({ err: "invalid request" }); } const decodedPassword = decodeBase64(newPassword); if (!isPasswordValid(decodedPassword)) { return res.status(400).send({ err: "invalid request" }); } const hashedPassword = await hashPassword(decodedPassword); // update user password await User.update( { password: hashedPassword, }, { where: { user_id: session.user_id, }, } ); // delete all sessions of this user by deleting all sessions with this user_id await Session.destroy({ where: { user_id: session.user_id, }, }); res.status(200).send({ msg: "user password updated" }); } catch (error) { logger.error(error); res.status(500).send({ err: "invalid request" }); } } export async function GetUserProfileSessions(req: Request, res: Response) { try { const session = await getUserSession(req); if (!session) { return res.status(401).send({ err: "unauthorized" }); } const sessions = await Session.findAll({ where: { user_id: session.user_id, }, attributes: ["session_id", "id", "browser", "os", "last_used"], }); // set last_used to now if session is the user's current session let currentSession = sessions.find( (sess) => sess.session_id === session.session_id )?.id; // remove session_id from sessions for security reasons const sessionsList = sessions.map((sess) => { return { id: sess.id, browser: sess.browser, os: sess.os, last_used: sess.last_used, }; }); res.status(200).json({ sessions: sessionsList, currentSession: currentSession, }); } catch (error) { logger.error(error); res.status(500).send({ err: "invalid request" }); } } export async function DeleteUserProfileSession(req: Request, res: Response) { try { const { id } = req.params; if (!id) { return res.status(400).send({ err: "invalid request" }); } const session = await getUserSession(req); if (!session) { return res.status(401).send({ err: "unauthorized" }); } await Session.destroy({ where: { id: id, user_id: session.user_id, }, }); res.status(200).send({ msg: "session deleted" }); } catch (error) { logger.error(error); res.status(500).send({ err: "invalid request" }); } }