authentication
parent
7ede8eda45
commit
5c26804cc4
|
@ -31,7 +31,7 @@ import Feedback from "../models/feedback";
|
||||||
|
|
||||||
export async function SignUp(req: Request, res: Response) {
|
export async function SignUp(req: Request, res: Response) {
|
||||||
try {
|
try {
|
||||||
let { username, accountName, password, language } = req.body;
|
let { username, accountName, password, language, rememberMe } = req.body;
|
||||||
|
|
||||||
// validate request
|
// validate request
|
||||||
|
|
||||||
|
@ -42,7 +42,8 @@ export async function SignUp(req: Request, res: Response) {
|
||||||
!language ||
|
!language ||
|
||||||
!isUsernameValid(username) ||
|
!isUsernameValid(username) ||
|
||||||
!isAccountNameValid(accountName) ||
|
!isAccountNameValid(accountName) ||
|
||||||
!isLanguageCodeValid(language)
|
!isLanguageCodeValid(language) ||
|
||||||
|
rememberMe === undefined
|
||||||
) {
|
) {
|
||||||
return res.status(400).send({ err: "invalid request" });
|
return res.status(400).send({ err: "invalid request" });
|
||||||
}
|
}
|
||||||
|
@ -58,10 +59,6 @@ export async function SignUp(req: Request, res: Response) {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (existingUser) {
|
if (existingUser) {
|
||||||
logger.debug(
|
|
||||||
"User already exists with this accountName: %s",
|
|
||||||
accountName
|
|
||||||
);
|
|
||||||
return res.status(400).send({ err: "invalid request" });
|
return res.status(400).send({ err: "invalid request" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +67,6 @@ export async function SignUp(req: Request, res: Response) {
|
||||||
const decodedPassword = decodeBase64(password);
|
const decodedPassword = decodeBase64(password);
|
||||||
|
|
||||||
if (!isPasswordValid(decodedPassword)) {
|
if (!isPasswordValid(decodedPassword)) {
|
||||||
logger.debug("Password is not valid");
|
|
||||||
return res.status(400).send({ err: "invalid request" });
|
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,
|
calendar_max_service_duration: CALENDAR_MAX_SERVICE_DURATION,
|
||||||
})
|
})
|
||||||
.then(async (store) => {
|
.then(async (store) => {
|
||||||
logger.debug(
|
|
||||||
"Store created with storeId: %s storeName: %s",
|
|
||||||
store.store_id,
|
|
||||||
store.name
|
|
||||||
);
|
|
||||||
|
|
||||||
// create user
|
// create user
|
||||||
|
|
||||||
await User.create({
|
await User.create({
|
||||||
|
@ -108,16 +98,11 @@ export async function SignUp(req: Request, res: Response) {
|
||||||
password: hashedPassword,
|
password: hashedPassword,
|
||||||
language: language,
|
language: language,
|
||||||
analytics_enabled: USER_ANALYTICS_ENABLED_DEFAULT,
|
analytics_enabled: USER_ANALYTICS_ENABLED_DEFAULT,
|
||||||
|
state: ACCOUNT_STATE.ACTIVE,
|
||||||
})
|
})
|
||||||
.then((user) => {
|
.then((user) => {
|
||||||
logger.debug(
|
|
||||||
"User created with accountName: %s username: %s",
|
|
||||||
user.account_name,
|
|
||||||
user.username
|
|
||||||
);
|
|
||||||
|
|
||||||
// create session
|
// create session
|
||||||
saveSession(req, res, user.user_id, user.username);
|
saveSession(req, res, user.user_id, user.username, rememberMe);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
|
@ -136,11 +121,11 @@ export async function SignUp(req: Request, res: Response) {
|
||||||
|
|
||||||
export async function Login(req: Request, res: Response) {
|
export async function Login(req: Request, res: Response) {
|
||||||
try {
|
try {
|
||||||
let { accountName, password } = req.body;
|
let { accountName, password, rememberMe } = req.body;
|
||||||
|
|
||||||
// validate request
|
// validate request
|
||||||
|
|
||||||
if (!accountName || !password) {
|
if (!accountName) {
|
||||||
return res.status(400).send({ err: "invalid request" });
|
return res.status(400).send({ err: "invalid request" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,6 +140,7 @@ export async function Login(req: Request, res: Response) {
|
||||||
where: {
|
where: {
|
||||||
account_name: accountName,
|
account_name: accountName,
|
||||||
},
|
},
|
||||||
|
attributes: ["user_id", "password", "state"],
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
|
@ -165,6 +151,15 @@ export async function Login(req: Request, res: Response) {
|
||||||
return res.status(400).send({ err: "invalid request" });
|
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
|
// decode password
|
||||||
|
|
||||||
const decodedPassword = decodeBase64(password);
|
const decodedPassword = decodeBase64(password);
|
||||||
|
@ -185,7 +180,7 @@ export async function Login(req: Request, res: Response) {
|
||||||
|
|
||||||
// check user state
|
// check user state
|
||||||
|
|
||||||
if (user.state === ACCOUNT_STATE.DELETED) {
|
if (user.state === ACCOUNT_STATE.PENDING_DELETION) {
|
||||||
// update user state back to active
|
// update user state back to active
|
||||||
|
|
||||||
User.update(
|
User.update(
|
||||||
|
@ -201,7 +196,7 @@ export async function Login(req: Request, res: Response) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// create session
|
// create session
|
||||||
saveSession(req, res, user.user_id, user.username);
|
saveSession(req, res, user.user_id, user.username, rememberMe);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
res.status(500).send({ err: "invalid request" });
|
res.status(500).send({ err: "invalid request" });
|
||||||
|
@ -337,10 +332,6 @@ export async function IsAccountNameAvailable(req: Request, res: Response) {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
logger.debug(
|
|
||||||
"User already exists with this accountName: %s",
|
|
||||||
accountName
|
|
||||||
);
|
|
||||||
return res.status(400).send({ err: "invalid request" });
|
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" });
|
return res.status(400).send({ err: "invalid request" });
|
||||||
}
|
}
|
||||||
|
|
||||||
// set user state to deleted
|
// set user state to pending deletion
|
||||||
|
|
||||||
await User.update(
|
await User.update(
|
||||||
{
|
{
|
||||||
state: ACCOUNT_STATE.DELETED,
|
state: ACCOUNT_STATE.PENDING_DELETION,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
where: {
|
where: {
|
||||||
|
|
|
@ -14,7 +14,11 @@ import {
|
||||||
newUserId,
|
newUserId,
|
||||||
} from "../utils/utils";
|
} from "../utils/utils";
|
||||||
import User from "../models/user";
|
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";
|
import Store from "../models/store";
|
||||||
|
|
||||||
export async function AddEmployee(req: Request, res: Response) {
|
export async function AddEmployee(req: Request, res: Response) {
|
||||||
|
@ -27,6 +31,7 @@ export async function AddEmployee(req: Request, res: Response) {
|
||||||
calendarMaxFutureBookingDays,
|
calendarMaxFutureBookingDays,
|
||||||
calendarMinEarliestBookingTime,
|
calendarMinEarliestBookingTime,
|
||||||
language,
|
language,
|
||||||
|
passwordSetOnInitLogging,
|
||||||
} = req.body;
|
} = req.body;
|
||||||
|
|
||||||
// validate request
|
// validate request
|
||||||
|
@ -35,19 +40,22 @@ export async function AddEmployee(req: Request, res: Response) {
|
||||||
!storeId ||
|
!storeId ||
|
||||||
!username ||
|
!username ||
|
||||||
!accountName ||
|
!accountName ||
|
||||||
!password ||
|
passwordSetOnInitLogging === undefined ||
|
||||||
|
(!password && passwordSetOnInitLogging === false) ||
|
||||||
!language ||
|
!language ||
|
||||||
!isLanguageCodeValid(language)
|
!isLanguageCodeValid(language)
|
||||||
) {
|
) {
|
||||||
|
logger.debug("Invalid request");
|
||||||
return res.status(400).send({ err: "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
|
// verify if requester is a store master
|
||||||
|
|
||||||
const requesterSession = await getUserSession(req);
|
const requesterSession = await getUserSession(req);
|
||||||
|
|
||||||
if (!requesterSession) {
|
if (!requesterSession) {
|
||||||
logger.debug("Requester session not found");
|
|
||||||
return res.status(401).send({ err: "unauthorized" });
|
return res.status(401).send({ err: "unauthorized" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,12 +66,10 @@ export async function AddEmployee(req: Request, res: Response) {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!store) {
|
if (!store) {
|
||||||
logger.debug("Store not found");
|
|
||||||
return res.status(400).send({ err: "invalid request" });
|
return res.status(400).send({ err: "invalid request" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (store.owner_user_id !== requesterSession.user_id) {
|
if (store.owner_user_id !== requesterSession.user_id) {
|
||||||
logger.debug("Requester is not the store owner");
|
|
||||||
return res.status(401).send({ err: "unauthorized" });
|
return res.status(401).send({ err: "unauthorized" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,19 +93,34 @@ export async function AddEmployee(req: Request, res: Response) {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (existingUser) {
|
if (existingUser) {
|
||||||
logger.debug(
|
|
||||||
"User already exists with this accountName: %s",
|
|
||||||
accountName
|
|
||||||
);
|
|
||||||
return res.status(400).send({ err: "invalid request" });
|
return res.status(400).send({ err: "invalid request" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// create user
|
||||||
|
|
||||||
|
const userId = newUserId();
|
||||||
|
|
||||||
|
let newUser = {
|
||||||
|
user_id: userId,
|
||||||
|
store_id: storeId,
|
||||||
|
role: Roles.Worker,
|
||||||
|
account_name: accountName,
|
||||||
|
username: username,
|
||||||
|
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
|
// decode password
|
||||||
|
|
||||||
const decodedPassword = decodeBase64(password);
|
const decodedPassword = decodeBase64(password);
|
||||||
|
|
||||||
if (!isPasswordValid(decodedPassword)) {
|
if (!isPasswordValid(decodedPassword)) {
|
||||||
logger.debug("Password is not valid");
|
|
||||||
return res.status(400).send({ err: "invalid request" });
|
return res.status(400).send({ err: "invalid request" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,22 +128,25 @@ export async function AddEmployee(req: Request, res: Response) {
|
||||||
|
|
||||||
const hashedPassword = await hashPassword(decodedPassword);
|
const hashedPassword = await hashPassword(decodedPassword);
|
||||||
|
|
||||||
// create user
|
newUser = {
|
||||||
|
...newUser,
|
||||||
const userId = newUserId();
|
|
||||||
|
|
||||||
await User.create({
|
|
||||||
user_id: userId,
|
|
||||||
store_id: storeId,
|
|
||||||
role: Roles.Worker,
|
|
||||||
account_name: accountName,
|
|
||||||
username: username,
|
|
||||||
password: hashedPassword,
|
password: hashedPassword,
|
||||||
calendar_max_future_booking_days: calendarMaxFutureBookingDays,
|
} as {
|
||||||
calendar_min_earliest_booking_time: calendarMinEarliestBookingTime,
|
user_id: string;
|
||||||
language: language,
|
store_id: any;
|
||||||
analytics_enabled: USER_ANALYTICS_ENABLED_DEFAULT,
|
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" }))
|
.then(() => res.status(200).send({ msg: "success" }))
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
|
|
|
@ -8,5 +8,11 @@ export async function sessionProtection(req: Request, res: any, next: any) {
|
||||||
return res.status(401).send({ err: "unauthorized" });
|
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();
|
next();
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,12 +8,12 @@ interface UserAttributes {
|
||||||
role: string;
|
role: string;
|
||||||
account_name: string;
|
account_name: string;
|
||||||
username: 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_max_future_booking_days?: number;
|
||||||
calendar_min_earliest_booking_time?: number;
|
calendar_min_earliest_booking_time?: number;
|
||||||
calendar_using_primary_calendar?: boolean;
|
calendar_using_primary_calendar?: boolean;
|
||||||
language: string;
|
language: string;
|
||||||
state?: number; // like active, deleted, etc
|
state: number; // like active, deleted, etc
|
||||||
analytics_enabled: boolean;
|
analytics_enabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ User.init(
|
||||||
},
|
},
|
||||||
password: {
|
password: {
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
allowNull: false,
|
// allowNull defaults to true
|
||||||
},
|
},
|
||||||
calendar_max_future_booking_days: {
|
calendar_max_future_booking_days: {
|
||||||
type: DataTypes.INTEGER,
|
type: DataTypes.INTEGER,
|
||||||
|
@ -78,7 +78,7 @@ User.init(
|
||||||
},
|
},
|
||||||
state: {
|
state: {
|
||||||
type: DataTypes.INTEGER,
|
type: DataTypes.INTEGER,
|
||||||
// allowNull defaults to true
|
allowNull: false,
|
||||||
},
|
},
|
||||||
analytics_enabled: {
|
analytics_enabled: {
|
||||||
type: DataTypes.BOOLEAN,
|
type: DataTypes.BOOLEAN,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
export const DEFAULT_SESSION_EXPIRY = 365 * 24 * 60 * 60 * 1000; // 365 days
|
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 USER_SESSION_LENGTH = 32;
|
||||||
|
|
||||||
export const USERNAME_MIN_LENGTH = 3;
|
export const USERNAME_MIN_LENGTH = 3;
|
||||||
|
@ -44,7 +45,9 @@ export const VALID_LANGUAGE_CODES = ["en", "de"];
|
||||||
|
|
||||||
export enum ACCOUNT_STATE {
|
export enum ACCOUNT_STATE {
|
||||||
ACTIVE = 0, // everything is fine
|
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;
|
export const FEEDBACK_MIN_LENGTH = 3;
|
||||||
|
|
|
@ -6,6 +6,7 @@ import Session from "../models/session";
|
||||||
import {
|
import {
|
||||||
DEFAULT_SESSION_EXPIRY,
|
DEFAULT_SESSION_EXPIRY,
|
||||||
HEADER_X_AUTHORIZATION,
|
HEADER_X_AUTHORIZATION,
|
||||||
|
SESSION_EXPIRY_NOT_REMEMBER_ME,
|
||||||
USER_SESSION_LENGTH,
|
USER_SESSION_LENGTH,
|
||||||
} from "./constants";
|
} from "./constants";
|
||||||
|
|
||||||
|
@ -53,7 +54,8 @@ export async function saveSession(
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
userId: string,
|
userId: string,
|
||||||
username: string
|
username: string,
|
||||||
|
rememberMe: boolean
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const userSession = newUserSession();
|
const userSession = newUserSession();
|
||||||
|
@ -65,7 +67,10 @@ export async function saveSession(
|
||||||
browser: req.useragent?.browser as string,
|
browser: req.useragent?.browser as string,
|
||||||
os: req.useragent?.os as string,
|
os: req.useragent?.os as string,
|
||||||
last_used: new Date(),
|
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({
|
res.status(200).json({
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import logger from "../logger/logger";
|
|
||||||
import {
|
import {
|
||||||
USERNAME_MIN_LENGTH,
|
USERNAME_MIN_LENGTH,
|
||||||
USERNAME_MAX_LENGTH,
|
USERNAME_MAX_LENGTH,
|
||||||
|
|
Loading…
Reference in New Issue