employees
parent
dae4b4a1ed
commit
366e3f3b23
|
@ -12,6 +12,7 @@
|
|||
"bcrypt": "^5.1.1",
|
||||
"body-parser": "^1.20.2",
|
||||
"concurrently": "^8.2.2",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
"mariadb": "^3.2.3",
|
||||
|
@ -24,6 +25,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@types/bcrypt": "^5.0.2",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/swagger-jsdoc": "^6.0.4",
|
||||
"@types/swagger-ui-express": "^4.1.6",
|
||||
|
@ -134,6 +136,15 @@
|
|||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/cors": {
|
||||
"version": "2.8.17",
|
||||
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz",
|
||||
"integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/debug": {
|
||||
"version": "4.1.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
|
||||
|
@ -648,6 +659,18 @@
|
|||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
|
||||
},
|
||||
"node_modules/cors": {
|
||||
"version": "2.8.5",
|
||||
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
||||
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
|
||||
"dependencies": {
|
||||
"object-assign": "^4",
|
||||
"vary": "^1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/date-fns": {
|
||||
"version": "2.30.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
"bcrypt": "^5.1.1",
|
||||
"body-parser": "^1.20.2",
|
||||
"concurrently": "^8.2.2",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
"mariadb": "^3.2.3",
|
||||
|
@ -26,6 +27,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@types/bcrypt": "^5.0.2",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/swagger-jsdoc": "^6.0.4",
|
||||
"@types/swagger-ui-express": "^4.1.6",
|
||||
|
|
|
@ -2,8 +2,10 @@ import express, { Express } from "express";
|
|||
import dotenv from "dotenv";
|
||||
import bodyParser from "body-parser";
|
||||
import swaggerUI from "swagger-ui-express";
|
||||
import cors from "cors";
|
||||
|
||||
import userRoutes from "./src/routes/userRoutes";
|
||||
import usersRoutes from "./src/routes/usersRoutes";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
|
@ -39,8 +41,11 @@ const options = {
|
|||
apis: ["./src/routes/*.ts"],
|
||||
};
|
||||
|
||||
// TODO: add cors
|
||||
app.use(cors());
|
||||
app.use(bodyParser.json());
|
||||
app.use("/api/v1/user", userRoutes);
|
||||
app.use("/api/v1/users", usersRoutes);
|
||||
|
||||
const specs = swaggerJsDoc(options);
|
||||
app.use("/api-docs", swaggerUI.serve, swaggerUI.setup(specs));
|
||||
|
|
|
@ -1,22 +1,28 @@
|
|||
import { Request, Response } from "express";
|
||||
import logger from "../logger/logger";
|
||||
import User from "../models/user";
|
||||
import { isPasswordValid, isUsernameValid } from "../validator/validator";
|
||||
import {
|
||||
isAccountNameValid,
|
||||
isPasswordValid,
|
||||
isUsernameValid,
|
||||
} from "../validator/validator";
|
||||
import { Roles } from "../utils/constants";
|
||||
import {
|
||||
decodeBase64,
|
||||
getUserSession,
|
||||
hashPassword,
|
||||
matchPassword,
|
||||
newUserId,
|
||||
saveSession,
|
||||
} from "../utils/utils";
|
||||
|
||||
export async function SignUp(req: Request, res: Response) {
|
||||
try {
|
||||
const { username, password } = req.body;
|
||||
let { username, accountName, password } = req.body;
|
||||
|
||||
// validate request
|
||||
|
||||
if (!username || !password) {
|
||||
if (!username || !accountName || !password) {
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
|
@ -24,16 +30,25 @@ export async function SignUp(req: Request, res: Response) {
|
|||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
if (!isAccountNameValid(accountName)) {
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
accountName = accountName.toLowerCase();
|
||||
|
||||
// check if user already exists
|
||||
|
||||
const existingUser = await User.findOne({
|
||||
where: {
|
||||
username: username,
|
||||
account_name: accountName,
|
||||
},
|
||||
});
|
||||
|
||||
if (existingUser) {
|
||||
logger.debug("User already exists with this username: %s", username);
|
||||
logger.debug(
|
||||
"User already exists with this accountName: %s",
|
||||
accountName
|
||||
);
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
|
@ -46,6 +61,8 @@ export async function SignUp(req: Request, res: Response) {
|
|||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
// hash password
|
||||
|
||||
const hashedPassword = await hashPassword(decodedPassword);
|
||||
|
||||
// create user
|
||||
|
@ -53,12 +70,18 @@ export async function SignUp(req: Request, res: Response) {
|
|||
await User.create({
|
||||
user_id: newUserId(),
|
||||
role: Roles.Master,
|
||||
account_name: accountName,
|
||||
username: username,
|
||||
password: hashedPassword,
|
||||
})
|
||||
.then((user) => {
|
||||
logger.debug("User created with username: %s", user.username);
|
||||
logger.debug(
|
||||
"User created with accountName: %s username: %s",
|
||||
user.account_name,
|
||||
user.username
|
||||
);
|
||||
|
||||
// create session
|
||||
saveSession(res, user.user_id, user.username);
|
||||
})
|
||||
.catch((err) => {
|
||||
|
@ -70,3 +93,102 @@ export async function SignUp(req: Request, res: Response) {
|
|||
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(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,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(401).send({ err: "unauthorized" });
|
||||
}
|
||||
|
||||
res.status(200).send({ username: user.username });
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
res.status(500).send({ err: "invalid request" });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
import { Request, Response } from "express";
|
||||
import logger from "../logger/logger";
|
||||
import {
|
||||
isAccountNameValid,
|
||||
isPasswordValid,
|
||||
isUsernameValid,
|
||||
} from "../validator/validator";
|
||||
import {
|
||||
decodeBase64,
|
||||
getUserSession,
|
||||
hashPassword,
|
||||
newUserId,
|
||||
} from "../utils/utils";
|
||||
import User from "../models/user";
|
||||
import { Roles } from "../utils/constants";
|
||||
|
||||
export async function AddEmployee(req: Request, res: Response) {
|
||||
try {
|
||||
let { username, accountName, password } = req.body;
|
||||
|
||||
// validate request
|
||||
|
||||
if (!username || !accountName || !password) {
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
// 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" });
|
||||
}
|
||||
|
||||
const requester = await User.findOne({
|
||||
where: {
|
||||
user_id: requesterSession.user_id,
|
||||
},
|
||||
});
|
||||
|
||||
if (!requester) {
|
||||
logger.debug("Requester not found");
|
||||
return res.status(401).send({ err: "unauthorized" });
|
||||
}
|
||||
|
||||
if (requester.role !== Roles.Master) {
|
||||
logger.debug("Requester is not a store master");
|
||||
return res.status(401).send({ err: "unauthorized" });
|
||||
}
|
||||
|
||||
// validate username and account name
|
||||
|
||||
accountName = accountName.toLowerCase();
|
||||
|
||||
if (!isUsernameValid(username)) {
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
if (!isAccountNameValid(accountName)) {
|
||||
return res.status(400).send({ err: "invalid request" });
|
||||
}
|
||||
|
||||
// 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 user
|
||||
|
||||
await User.create({
|
||||
user_id: newUserId(),
|
||||
role: Roles.Worker,
|
||||
account_name: accountName,
|
||||
username: username,
|
||||
password: hashedPassword,
|
||||
master_user_id: requester.user_id,
|
||||
})
|
||||
.then(() => {
|
||||
res.status(200).send({ msg: "success" });
|
||||
})
|
||||
.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 GetEmployees(req: Request, res: Response) {
|
||||
try {
|
||||
const requesterSession = await getUserSession(req);
|
||||
|
||||
if (!requesterSession) {
|
||||
logger.debug("Requester session not found");
|
||||
return res.status(401).send({ err: "unauthorized" });
|
||||
}
|
||||
|
||||
// find all employees of the requester and select only the username and account name
|
||||
|
||||
const employees = await User.findAll({
|
||||
where: {
|
||||
master_user_id: requesterSession.user_id,
|
||||
},
|
||||
attributes: ["username", "account_name"],
|
||||
});
|
||||
|
||||
// simulate a delay
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 4000));
|
||||
|
||||
res.status(200).send({ employees: employees });
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
res.status(500).send({ err: "invalid request" });
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
import { Request } from "express";
|
||||
import { getUserSession } from "../utils/utils";
|
||||
|
||||
export async function sessionProtection(req: Request, res: any, next: any) {
|
||||
const session = await getUserSession(req);
|
||||
|
||||
if (!session) {
|
||||
return res.status(401).send({ err: "unauthorized" });
|
||||
}
|
||||
|
||||
next();
|
||||
}
|
|
@ -5,6 +5,7 @@ interface UserAttributes {
|
|||
user_id: string;
|
||||
master_user_id?: string;
|
||||
role: string;
|
||||
account_name: string;
|
||||
username: string;
|
||||
password: string;
|
||||
calendar_settings?: string;
|
||||
|
@ -14,6 +15,7 @@ class User extends Model<UserAttributes> implements UserAttributes {
|
|||
declare user_id: string;
|
||||
declare master_user_id: string;
|
||||
declare role: string;
|
||||
declare account_name: string;
|
||||
declare username: string;
|
||||
declare password: string;
|
||||
declare calendar_settings: string;
|
||||
|
@ -35,6 +37,10 @@ User.init(
|
|||
type: DataTypes.STRING,
|
||||
// allowNull defaults to true
|
||||
},
|
||||
account_name: {
|
||||
type: DataTypes.STRING,
|
||||
unique: true,
|
||||
},
|
||||
username: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
|
|
|
@ -2,7 +2,11 @@ import express from "express";
|
|||
const router = express.Router();
|
||||
|
||||
import * as userController from "../controllers/userController";
|
||||
import { sessionProtection } from "../middleware/authMiddleware";
|
||||
|
||||
router.post("/signup", userController.SignUp);
|
||||
router.post("/auth/signup", userController.SignUp);
|
||||
router.post("/auth/login", userController.Login);
|
||||
router.delete("/auth/logout", sessionProtection, userController.Logout);
|
||||
router.get("/", sessionProtection, userController.GetUser);
|
||||
|
||||
export default router;
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
import express from "express";
|
||||
const router = express.Router();
|
||||
|
||||
import * as usersController from "../controllers/usersController";
|
||||
|
||||
router.post("/", usersController.AddEmployee);
|
||||
router.get("/", usersController.GetEmployees);
|
||||
|
||||
export default router;
|
|
@ -3,9 +3,15 @@ export const DEFAULT_SESSION_EXPIRY = 365 * 24 * 60 * 60 * 1000; // 365 days
|
|||
export const USERNAME_MIN_LENGTH = 3;
|
||||
export const USERNAME_MAX_LENGTH = 20;
|
||||
|
||||
export const ACCOUNT_NAME_MIN_LENGTH = 3;
|
||||
export const ACCOUNT_NAME_MAX_LENGTH = 20;
|
||||
|
||||
export const PASSWORD_MIN_LENGTH = 8;
|
||||
export const PASSWORD_MAX_LENGTH = 64;
|
||||
|
||||
// Header name for the session ID
|
||||
export const HEADER_X_AUTHORIZATION: string = "x-authorization";
|
||||
|
||||
export const Roles = {
|
||||
// admin of the whole system independent of stores
|
||||
Admin: "admin",
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import crypto from "crypto";
|
||||
import bcrypt from "bcrypt";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { Response } from "express";
|
||||
import { Request, Response } from "express";
|
||||
import Session from "../models/session";
|
||||
import { DEFAULT_SESSION_EXPIRY } from "./constants";
|
||||
import { DEFAULT_SESSION_EXPIRY, HEADER_X_AUTHORIZATION } from "./constants";
|
||||
|
||||
export async function matchPassword(decodedPassword: string, password: string) {
|
||||
return await bcrypt.compare(decodedPassword, password);
|
||||
|
@ -48,3 +48,17 @@ export async function saveSession(
|
|||
res.status(500).send({ err: "invalid request" });
|
||||
}
|
||||
}
|
||||
|
||||
export async function getUserSession(req: Request) {
|
||||
const sessionId = req.get(HEADER_X_AUTHORIZATION);
|
||||
|
||||
if (!sessionId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await Session.findOne({
|
||||
where: {
|
||||
session_id: sessionId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ import {
|
|||
USERNAME_MAX_LENGTH,
|
||||
PASSWORD_MIN_LENGTH,
|
||||
PASSWORD_MAX_LENGTH,
|
||||
ACCOUNT_NAME_MIN_LENGTH,
|
||||
ACCOUNT_NAME_MAX_LENGTH,
|
||||
} from "../utils/constants";
|
||||
|
||||
// TODO: regex for username
|
||||
|
@ -13,6 +15,14 @@ export function isUsernameValid(username: string) {
|
|||
);
|
||||
}
|
||||
|
||||
// TODO: regex for account name
|
||||
export function isAccountNameValid(accountName: string) {
|
||||
return (
|
||||
accountName.length >= ACCOUNT_NAME_MIN_LENGTH &&
|
||||
accountName.length <= ACCOUNT_NAME_MAX_LENGTH
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: regex for password
|
||||
export function isPasswordValid(password: string) {
|
||||
return (
|
||||
|
|
Loading…
Reference in New Issue