customer-dashboard-api/src/controllers/userController.ts

556 lines
13 KiB
TypeScript

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" });
}
}