authentication

main
alex 2024-01-28 16:17:33 +01:00
parent 7ede8eda45
commit 5c26804cc4
7 changed files with 91 additions and 63 deletions

View File

@ -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: {

View File

@ -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,19 +93,34 @@ 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" });
}
// 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
const decodedPassword = decodeBase64(password);
if (!isPasswordValid(decodedPassword)) {
logger.debug("Password is not valid");
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);
// create user
const userId = newUserId();
await User.create({
user_id: userId,
store_id: storeId,
role: Roles.Worker,
account_name: accountName,
username: username,
newUser = {
...newUser,
password: hashedPassword,
calendar_max_future_booking_days: calendarMaxFutureBookingDays,
calendar_min_earliest_booking_time: calendarMinEarliestBookingTime,
language: language,
analytics_enabled: USER_ANALYTICS_ENABLED_DEFAULT,
})
} 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);

View File

@ -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();
}

View File

@ -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,

View File

@ -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;

View File

@ -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({

View File

@ -1,4 +1,3 @@
import logger from "../logger/logger";
import {
USERNAME_MIN_LENGTH,
USERNAME_MAX_LENGTH,