diff --git a/src/controllers/userController.ts b/src/controllers/userController.ts index a0780dc..a4a0ecc 100644 --- a/src/controllers/userController.ts +++ b/src/controllers/userController.ts @@ -31,7 +31,7 @@ import Feedback from "../models/feedback"; export async function SignUp(req: Request, res: Response) { try { - let { username, accountName, password, language } = req.body; + let { username, accountName, password, language, rememberMe } = req.body; // validate request @@ -42,7 +42,8 @@ export async function SignUp(req: Request, res: Response) { !language || !isUsernameValid(username) || !isAccountNameValid(accountName) || - !isLanguageCodeValid(language) + !isLanguageCodeValid(language) || + rememberMe === undefined ) { return res.status(400).send({ err: "invalid request" }); } @@ -58,10 +59,6 @@ export async function SignUp(req: Request, res: Response) { }); if (existingUser) { - logger.debug( - "User already exists with this accountName: %s", - accountName - ); return res.status(400).send({ err: "invalid request" }); } @@ -70,7 +67,6 @@ export async function SignUp(req: Request, res: Response) { const decodedPassword = decodeBase64(password); if (!isPasswordValid(decodedPassword)) { - logger.debug("Password is not valid"); return res.status(400).send({ err: "invalid request" }); } @@ -91,12 +87,6 @@ export async function SignUp(req: Request, res: Response) { 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({ @@ -108,16 +98,11 @@ export async function SignUp(req: Request, res: Response) { password: hashedPassword, language: language, analytics_enabled: USER_ANALYTICS_ENABLED_DEFAULT, + state: ACCOUNT_STATE.ACTIVE, }) .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); + saveSession(req, res, user.user_id, user.username, rememberMe); }) .catch((err) => { logger.error(err); @@ -136,11 +121,11 @@ export async function SignUp(req: Request, res: Response) { export async function Login(req: Request, res: Response) { try { - let { accountName, password } = req.body; + let { accountName, password, rememberMe } = req.body; // validate request - if (!accountName || !password) { + if (!accountName) { return res.status(400).send({ err: "invalid request" }); } @@ -155,6 +140,7 @@ export async function Login(req: Request, res: Response) { where: { account_name: accountName, }, + attributes: ["user_id", "password", "state"], }); if (!user) { @@ -165,6 +151,15 @@ export async function Login(req: Request, res: Response) { return res.status(400).send({ err: "invalid request" }); } + // if password not provided, then send user state + // user is on the login page on the first step of the login process + // and only needs to enter their account name to get the user state to know what to do next + + if (password === undefined) { + console.debug("Password not provided"); + return res.status(200).send({ state: user.state }); + } + // decode password const decodedPassword = decodeBase64(password); @@ -185,7 +180,7 @@ export async function Login(req: Request, res: Response) { // check user state - if (user.state === ACCOUNT_STATE.DELETED) { + if (user.state === ACCOUNT_STATE.PENDING_DELETION) { // update user state back to active User.update( @@ -201,7 +196,7 @@ export async function Login(req: Request, res: Response) { } // create session - saveSession(req, res, user.user_id, user.username); + saveSession(req, res, user.user_id, user.username, rememberMe); } catch (error) { logger.error(error); res.status(500).send({ err: "invalid request" }); @@ -337,10 +332,6 @@ export async function IsAccountNameAvailable(req: Request, res: Response) { }); if (user) { - logger.debug( - "User already exists with this accountName: %s", - accountName - ); return res.status(400).send({ err: "invalid request" }); } @@ -607,11 +598,11 @@ export async function DeleteUserProfile(req: Request, res: Response) { return res.status(400).send({ err: "invalid request" }); } - // set user state to deleted + // set user state to pending deletion await User.update( { - state: ACCOUNT_STATE.DELETED, + state: ACCOUNT_STATE.PENDING_DELETION, }, { where: { diff --git a/src/controllers/usersController.ts b/src/controllers/usersController.ts index 3a23c31..00cb571 100644 --- a/src/controllers/usersController.ts +++ b/src/controllers/usersController.ts @@ -14,7 +14,11 @@ import { newUserId, } from "../utils/utils"; import User from "../models/user"; -import { Roles, USER_ANALYTICS_ENABLED_DEFAULT } from "../utils/constants"; +import { + ACCOUNT_STATE, + Roles, + USER_ANALYTICS_ENABLED_DEFAULT, +} from "../utils/constants"; import Store from "../models/store"; export async function AddEmployee(req: Request, res: Response) { @@ -27,6 +31,7 @@ export async function AddEmployee(req: Request, res: Response) { calendarMaxFutureBookingDays, calendarMinEarliestBookingTime, language, + passwordSetOnInitLogging, } = req.body; // validate request @@ -35,19 +40,22 @@ export async function AddEmployee(req: Request, res: Response) { !storeId || !username || !accountName || - !password || + passwordSetOnInitLogging === undefined || + (!password && passwordSetOnInitLogging === false) || !language || !isLanguageCodeValid(language) ) { + logger.debug("Invalid request"); return res.status(400).send({ err: "invalid request" }); } + logger.debug("Request is valid %s %s", passwordSetOnInitLogging, password); + // verify if requester is a store master const requesterSession = await getUserSession(req); if (!requesterSession) { - logger.debug("Requester session not found"); return res.status(401).send({ err: "unauthorized" }); } @@ -58,12 +66,10 @@ export async function AddEmployee(req: Request, res: Response) { }); if (!store) { - logger.debug("Store not found"); return res.status(400).send({ err: "invalid request" }); } if (store.owner_user_id !== requesterSession.user_id) { - logger.debug("Requester is not the store owner"); return res.status(401).send({ err: "unauthorized" }); } @@ -87,42 +93,60 @@ export async function AddEmployee(req: Request, res: Response) { }); 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 user const userId = newUserId(); - await User.create({ + let newUser = { user_id: userId, store_id: storeId, role: Roles.Worker, account_name: accountName, username: username, - password: hashedPassword, calendar_max_future_booking_days: calendarMaxFutureBookingDays, calendar_min_earliest_booking_time: calendarMinEarliestBookingTime, language: language, analytics_enabled: USER_ANALYTICS_ENABLED_DEFAULT, - }) + state: passwordSetOnInitLogging + ? ACCOUNT_STATE.INIT_LOGIN + : ACCOUNT_STATE.ACTIVE, + }; + + if (!passwordSetOnInitLogging) { + // decode password + + const decodedPassword = decodeBase64(password); + + if (!isPasswordValid(decodedPassword)) { + return res.status(400).send({ err: "invalid request" }); + } + + // hash password + + const hashedPassword = await hashPassword(decodedPassword); + + newUser = { + ...newUser, + password: hashedPassword, + } as { + user_id: string; + store_id: any; + role: string; + account_name: any; + username: any; + calendar_max_future_booking_days: any; + calendar_min_earliest_booking_time: any; + language: any; + analytics_enabled: boolean; + password: string; + state: number; + }; + } + + await User.create(newUser) .then(() => res.status(200).send({ msg: "success" })) .catch((err) => { logger.error(err); diff --git a/src/middleware/authMiddleware.ts b/src/middleware/authMiddleware.ts index 66f89ef..2a09faf 100644 --- a/src/middleware/authMiddleware.ts +++ b/src/middleware/authMiddleware.ts @@ -8,5 +8,11 @@ export async function sessionProtection(req: Request, res: any, next: any) { return res.status(401).send({ err: "unauthorized" }); } + // check if session is expired + + if (session.expires < new Date()) { + return res.status(401).send({ err: "unauthorized" }); + } + next(); } diff --git a/src/models/user.ts b/src/models/user.ts index 162a348..84c9fb1 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -8,12 +8,12 @@ interface UserAttributes { role: string; account_name: string; username: string; - password: string; + password?: string; // can be null if user is created by store owner - password is set by user when they first login calendar_max_future_booking_days?: number; calendar_min_earliest_booking_time?: number; calendar_using_primary_calendar?: boolean; language: string; - state?: number; // like active, deleted, etc + state: number; // like active, deleted, etc analytics_enabled: boolean; } @@ -58,7 +58,7 @@ User.init( }, password: { type: DataTypes.STRING, - allowNull: false, + // allowNull defaults to true }, calendar_max_future_booking_days: { type: DataTypes.INTEGER, @@ -78,7 +78,7 @@ User.init( }, state: { type: DataTypes.INTEGER, - // allowNull defaults to true + allowNull: false, }, analytics_enabled: { type: DataTypes.BOOLEAN, diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 8cd4662..1307c8c 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -1,4 +1,5 @@ export const DEFAULT_SESSION_EXPIRY = 365 * 24 * 60 * 60 * 1000; // 365 days +export const SESSION_EXPIRY_NOT_REMEMBER_ME = 60 * 60 * 1000; // 1 hour export const USER_SESSION_LENGTH = 32; export const USERNAME_MIN_LENGTH = 3; @@ -44,7 +45,9 @@ export const VALID_LANGUAGE_CODES = ["en", "de"]; export enum ACCOUNT_STATE { ACTIVE = 0, // everything is fine - DELETED = 1, // account still exists but is marked as deleted, can be restored or will be deleted after a certain time + PENDING_DELETION = 1, // account still exists but is marked as deleted, can be restored or will be deleted after a certain time + INIT_LOGIN = 2, // account is created but password is not set yet + BANNED = 3, // account is banned, cannot login } export const FEEDBACK_MIN_LENGTH = 3; diff --git a/src/utils/utils.ts b/src/utils/utils.ts index d41f25e..3f883b9 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -6,6 +6,7 @@ import Session from "../models/session"; import { DEFAULT_SESSION_EXPIRY, HEADER_X_AUTHORIZATION, + SESSION_EXPIRY_NOT_REMEMBER_ME, USER_SESSION_LENGTH, } from "./constants"; @@ -53,7 +54,8 @@ export async function saveSession( req: Request, res: Response, userId: string, - username: string + username: string, + rememberMe: boolean ) { try { const userSession = newUserSession(); @@ -65,7 +67,10 @@ export async function saveSession( browser: req.useragent?.browser as string, os: req.useragent?.os as string, last_used: new Date(), - expires: new Date(Date.now() + DEFAULT_SESSION_EXPIRY), + expires: new Date( + Date.now() + + (rememberMe ? DEFAULT_SESSION_EXPIRY : SESSION_EXPIRY_NOT_REMEMBER_ME) + ), }); res.status(200).json({ diff --git a/src/validator/validator.ts b/src/validator/validator.ts index 7084ce7..77027ec 100644 --- a/src/validator/validator.ts +++ b/src/validator/validator.ts @@ -1,4 +1,3 @@ -import logger from "../logger/logger"; import { USERNAME_MIN_LENGTH, USERNAME_MAX_LENGTH,