server overhaul, new jwt strategy, some various patches
This commit is contained in:
@@ -20,12 +20,25 @@ export default class AuthService {
|
||||
// not allowed to use email address that already exists
|
||||
const user = await UserInstance.getOneByEmail(data.email);
|
||||
|
||||
if (user) throw createError('409', 'Email already in use');
|
||||
if (user) {
|
||||
return new ControllerResponse(StatusCode.Conflict, "Email already in use", false);
|
||||
}
|
||||
|
||||
// check that all required fields are populated
|
||||
let missingFields = new Array<string>();
|
||||
let requiredFields: Array<keyof IUser> = ['firstname', 'lastname', 'handle', 'email', 'isadmin', 'password'];
|
||||
for (let field of requiredFields) {
|
||||
if (!(field in data)) {
|
||||
missingFields.push(field as string);
|
||||
}
|
||||
}
|
||||
|
||||
if (missingFields.length) {
|
||||
return new ControllerResponse(StatusCode.BadRequest, `Missing fields in output: ${missingFields.join(", ")}`, false);
|
||||
}
|
||||
|
||||
// hash password and create new user record
|
||||
const salt = await bcrypt.genSalt(12);
|
||||
console.log(salt);
|
||||
console.log(data.password);
|
||||
|
||||
bcrypt.hash(data.password!, salt, (err, hash) => {
|
||||
if (err) throw err;
|
||||
@@ -37,7 +50,7 @@ export default class AuthService {
|
||||
UserInstance.post(newData);
|
||||
})
|
||||
|
||||
return true;
|
||||
return new ControllerResponse(StatusCode.NewContent, "registered successfully", true);
|
||||
} catch (e: any) {
|
||||
throw new Error(e);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
import e, { NextFunction, Request, Response } from "express"
|
||||
import ControllerResponse from "../util/ControllerResponse";
|
||||
import { StatusCode } from "../util/types";
|
||||
import { NextFunction, Request, Response } from "express"
|
||||
import dotenv from "dotenv";
|
||||
import { IUser } from "../schemas";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
export function restrictAccess(req: Request, res: Response, next: NextFunction) {
|
||||
if (req.session.user == undefined) {
|
||||
console.log("restricted")
|
||||
res.send(undefined);
|
||||
res.send("content restricted");
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
||||
export function requireSessionSecret(req: Request, res: Response, next: NextFunction) {
|
||||
const secret = process.env.SESSIONSECRET;
|
||||
|
||||
if (!secret) {
|
||||
res.sendStatus(500);
|
||||
throw new Error("Express secret is undefined");
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
@@ -20,5 +32,11 @@ export function checkFriendStatus(req: Request, res: Response, next: NextFunctio
|
||||
}
|
||||
|
||||
export function checkIsAdmin(req: Request, res: Response, next: NextFunction) {
|
||||
|
||||
const user: IUser | undefined = req.user as IUser;
|
||||
|
||||
if (user.isadmin) {
|
||||
next();
|
||||
} else {
|
||||
res.status(403).send("Unauthorized");
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,11 @@
|
||||
import express from 'express';
|
||||
import cors from 'cors';
|
||||
import dotenv from 'dotenv';
|
||||
import { loaders } from './loaders';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const port = 8080;
|
||||
const port = process.env.PORT || 8080;
|
||||
const app = express();
|
||||
app.use(cors());
|
||||
|
||||
async function main() {
|
||||
await loaders(app);
|
||||
|
||||
@@ -6,39 +6,31 @@ import cors from 'cors';
|
||||
import session from 'express-session';
|
||||
import pgSessionStore from '../db/sessionStore';
|
||||
import { IUser } from '../schemas';
|
||||
import { requireSessionSecret } from '../auth/middlewares';
|
||||
|
||||
declare module "express-session" {
|
||||
const origin = process.env.ORIGIN || 'http://localhost:5173';
|
||||
const secret = process.env.SESSIONSECRET;
|
||||
|
||||
declare module 'express-session' {
|
||||
interface SessionData {
|
||||
user: IUser
|
||||
user?: IUser
|
||||
}
|
||||
}
|
||||
|
||||
export const expressLoader = async (app: Express) => {
|
||||
app.use(cors({
|
||||
origin: process.env.ORIGIN || 'http://localhost:5173',
|
||||
credentials: true
|
||||
}));
|
||||
|
||||
app.use(cors({ origin: origin }));
|
||||
app.use(bodyParser.json());
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
app.use(cookieParser());
|
||||
|
||||
// app.options("*", cors({ origin: 'http://localhost:5173', optionsSuccessStatus: 200 }));
|
||||
// app.use(cors({ origin: "http://localhost:5173", optionsSuccessStatus: 200 }));
|
||||
|
||||
app.use(morgan('tiny'));
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
res.cookie('name', 'express').send('cookie set');
|
||||
})
|
||||
|
||||
const secret = process.env.SESSIONSECRET as string;
|
||||
app.use(requireSessionSecret);
|
||||
|
||||
app.use(session({
|
||||
secret: secret,
|
||||
secret: secret as string,
|
||||
cookie: {
|
||||
maxAge: 8 * 60 * 60 * 1000,
|
||||
secure: false
|
||||
secure: false,
|
||||
httpOnly: false
|
||||
},
|
||||
resave: false,
|
||||
saveUninitialized: false,
|
||||
|
||||
@@ -6,7 +6,7 @@ import { passportLoader } from './passport';
|
||||
|
||||
export const loaders = async (app: Express) => {
|
||||
const expressApp = await expressLoader(app);
|
||||
const passportApp = await passportLoader(expressApp);
|
||||
await passportLoader(expressApp);
|
||||
await swaggerLoader(expressApp);
|
||||
await routes(expressApp, passportApp);
|
||||
await routes(expressApp);
|
||||
}
|
||||
@@ -1,32 +1,35 @@
|
||||
import { Strategy as LocalStrategy } from "passport-local";
|
||||
import passport from "passport";
|
||||
import { Express } from "express";
|
||||
import AuthService from "../auth";
|
||||
import { IUserAuth } from "../schemas";
|
||||
const AuthInstance = new AuthService();
|
||||
import { ExtractJwt, Strategy as JwtStrategy } from "passport-jwt";
|
||||
|
||||
export const passportLoader = async (app: Express) => {
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
|
||||
passport.serializeUser((user, done) => {
|
||||
done(null, user);
|
||||
passport.serializeUser((user: Express.User, done) => {
|
||||
process.nextTick(() => {
|
||||
done(null, user);
|
||||
})
|
||||
})
|
||||
|
||||
passport.deserializeUser((user: IUserAuth, done) => {
|
||||
done(null, user);
|
||||
passport.deserializeUser((user: Express.User, done) => {
|
||||
process.nextTick(() => {
|
||||
done(null, user);
|
||||
})
|
||||
})
|
||||
|
||||
// sign in method with passport local strategy
|
||||
passport.use(new LocalStrategy({
|
||||
usernameField: 'email',
|
||||
passwordField: 'password'
|
||||
}, async (email, password, done) => {
|
||||
// config for jwt strategy
|
||||
let opts = {
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
secretOrKey: 'secret'
|
||||
}
|
||||
|
||||
// jwt strategy
|
||||
passport.use(new JwtStrategy(opts, async (token, done) => {
|
||||
try {
|
||||
const response = await AuthInstance.login({ email, password });
|
||||
return done(null, response);
|
||||
} catch (e: any) {
|
||||
return done(e);
|
||||
return done(null, token.user);
|
||||
} catch (error) {
|
||||
done(error);
|
||||
}
|
||||
}))
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "bash util/build.sh",
|
||||
"seed": "npm run build && ts-node-dev db/seed.ts",
|
||||
"seed": "npm run build && ts-node --files db/seed.ts",
|
||||
"dev": "bash util/dev.sh",
|
||||
"prod": "npm run build && node dist/index.js",
|
||||
"test": "jest --coverage",
|
||||
@@ -15,7 +15,6 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@types/cookie-parser": "^1.4.3",
|
||||
"bcrypt": "^5.1.0",
|
||||
"body-parser": "^1.20.1",
|
||||
"connect-pg-simple": "^8.0.0",
|
||||
@@ -28,8 +27,10 @@
|
||||
"helmet": "^6.0.0",
|
||||
"http-errors": "^2.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"morgan": "^1.10.0",
|
||||
"passport": "^0.6.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
"passport-local": "^1.0.0",
|
||||
"pg": "^8.8.0",
|
||||
"pg-promise": "^10.15.0",
|
||||
@@ -38,16 +39,18 @@
|
||||
"devDependencies": {
|
||||
"@types/bcrypt": "^5.0.0",
|
||||
"@types/connect-pg-simple": "^7.0.0",
|
||||
"@types/cookie-parser": "^1.4.3",
|
||||
"@types/cors": "^2.8.12",
|
||||
"@types/dotenv": "^8.2.0",
|
||||
"@types/express": "^4.17.14",
|
||||
"@types/express-session": "^1.17.5",
|
||||
"@types/express-session": "^1.17.6",
|
||||
"@types/http-errors": "^2.0.1",
|
||||
"@types/jest": "^29.2.4",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/morgan": "^1.9.3",
|
||||
"@types/node": "^18.11.9",
|
||||
"@types/passport": "^1.0.11",
|
||||
"@types/passport-jwt": "^3.0.8",
|
||||
"@types/passport-local": "^1.0.34",
|
||||
"@types/pg": "^8.6.5",
|
||||
"@types/pg-promise": "^5.4.3",
|
||||
@@ -57,6 +60,7 @@
|
||||
"nodemon": "^2.0.20",
|
||||
"supertest": "^6.3.3",
|
||||
"ts-jest": "^29.0.3",
|
||||
"ts-node": "^10.9.1",
|
||||
"ts-node-dev": "^2.0.0",
|
||||
"tslint": "^6.1.3",
|
||||
"typescript": "^4.9.3"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Express, Request, Router } from "express"
|
||||
import { Express, Router } from "express"
|
||||
import { PassportStatic } from "passport";
|
||||
import jwt from "jsonwebtoken";
|
||||
import { IUser, IUserAuth } from "../schemas";
|
||||
import AuthService from "../auth";
|
||||
import { UserCtl } from "../controllers";
|
||||
@@ -12,18 +13,9 @@ const UserInstance = new UserCtl();
|
||||
|
||||
const router = Router();
|
||||
|
||||
export const authRoute = (app: Express, passport: PassportStatic) => {
|
||||
export const authRoute = (app: Express) => {
|
||||
app.use('/auth', router);
|
||||
|
||||
// router.use((req, res, next) => {
|
||||
// console.log(req.isAuthenticated());
|
||||
// console.log(req.session.user);
|
||||
// console.log(req.cookies);
|
||||
// console.log();
|
||||
|
||||
// next();
|
||||
// })
|
||||
|
||||
router.use((req, res, next) => {
|
||||
console.log(req.session);
|
||||
next();
|
||||
@@ -49,7 +41,7 @@ export const authRoute = (app: Express, passport: PassportStatic) => {
|
||||
res.status(200).send({ message: "Cool restricted content!" });
|
||||
})
|
||||
|
||||
router.post('/login', passport.authenticate('local'), async (req, res, next) => {
|
||||
router.post('/login', async (req, res, next) => {
|
||||
try {
|
||||
const data: IUserAuth = req.body;
|
||||
console.log(data);
|
||||
@@ -59,19 +51,27 @@ export const authRoute = (app: Express, passport: PassportStatic) => {
|
||||
if (response.ok) {
|
||||
const user = response.data as IUser;
|
||||
|
||||
req.session.regenerate((err) => {
|
||||
if (err) next(err);
|
||||
req.session.user = user;
|
||||
req.user = user;
|
||||
req.session.user = user;
|
||||
|
||||
req.session.save((err) => {
|
||||
if (err) return next(err);
|
||||
})
|
||||
const safeUserData = {
|
||||
id: user.id,
|
||||
handle: user.handle,
|
||||
email: user.email,
|
||||
datecreated: user.datecreated,
|
||||
datemodified: user.datemodified
|
||||
}
|
||||
|
||||
const token = jwt.sign({ user: safeUserData }, process.env.SESSIONSECRET as string);
|
||||
|
||||
req.session.save((err) => {
|
||||
return next(err);
|
||||
})
|
||||
|
||||
res.cookie('userid', user.id, { maxAge: 1000 * 60 * 60 * 24 });
|
||||
console.log(req.session);
|
||||
|
||||
res.send(response);
|
||||
res.end();
|
||||
res.cookie('token', token, { httpOnly: true });
|
||||
res.json({ token });
|
||||
} else {
|
||||
res.status(401).send({ message: "Login unsuccessful" });
|
||||
}
|
||||
@@ -82,10 +82,11 @@ export const authRoute = (app: Express, passport: PassportStatic) => {
|
||||
|
||||
router.post('/register', async (req, res, next) => {
|
||||
try {
|
||||
const data = req.body;
|
||||
const data: IUser = req.body;
|
||||
const response = await AuthInstance.register(data);
|
||||
if (!response) res.status(400).send({ ok: false });
|
||||
res.status(200).send({ ok: true });
|
||||
response.represent();
|
||||
|
||||
res.status(response.code).send({ ok: response.ok, message: response.data });
|
||||
} catch(e) {
|
||||
next(e);
|
||||
}
|
||||
@@ -93,11 +94,9 @@ export const authRoute = (app: Express, passport: PassportStatic) => {
|
||||
|
||||
router.delete('/logout', async (req, res, next) => {
|
||||
try {
|
||||
req.session.destroy((err) => {
|
||||
if (err) throw err;
|
||||
})
|
||||
res.clearCookie('userid');
|
||||
res.status(204).send({ ok: true });
|
||||
res.clearCookie('connect.sid').clearCookie('token');
|
||||
res.status(204).send("logout successful");
|
||||
res.end();
|
||||
} catch(e) {
|
||||
next(e);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import { Express, Router } from "express";
|
||||
import { restrictAccess } from "../auth/middlewares";
|
||||
import { checkIsAdmin, restrictAccess } from "../auth/middlewares";
|
||||
import CollectionCtl from "../controllers/CollectionCtl";
|
||||
const CollectionInstance = new CollectionCtl();
|
||||
|
||||
const router = Router();
|
||||
|
||||
export const collectionRoute = (app: Express) => {
|
||||
app.use('/collection', router);
|
||||
app.use('/app/collection', router);
|
||||
|
||||
router.get('/:id', restrictAccess, async (req, res, next) => {
|
||||
router.get('/:id', async (req, res, next) => {
|
||||
const { id } = req.params;
|
||||
try {
|
||||
const { code, data } = await CollectionInstance.getOne(id);
|
||||
@@ -19,7 +19,7 @@ export const collectionRoute = (app: Express) => {
|
||||
})
|
||||
|
||||
// implement is admin on this route
|
||||
router.get('/', restrictAccess, async (req, res, next) => {
|
||||
router.get('/', checkIsAdmin, async (req, res, next) => {
|
||||
try {
|
||||
const { code, data } = await CollectionInstance.getAll();
|
||||
res.status(code).send(data);
|
||||
@@ -28,7 +28,7 @@ export const collectionRoute = (app: Express) => {
|
||||
}
|
||||
})
|
||||
|
||||
router.post('/', restrictAccess, async (req, res, next) => {
|
||||
router.post('/', async (req, res, next) => {
|
||||
const data = req.body;
|
||||
console.log(data);
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ const CourseInstance = new CourseCtl();
|
||||
const router = Router();
|
||||
|
||||
export const courseRouter = (app: Express) => {
|
||||
app.use('/course', router);
|
||||
app.use('/app/course', router);
|
||||
|
||||
router.get('/', async (req, res, next) => {
|
||||
try {
|
||||
|
||||
@@ -7,7 +7,7 @@ const UserInstance = new UserCtl();
|
||||
const router = Router();
|
||||
|
||||
export const friendRouter = (app: Express) => {
|
||||
app.use('/friend', router);
|
||||
app.use('/app/friend', router);
|
||||
|
||||
router.use((req, res, next) => {
|
||||
let test = req.session.user;
|
||||
|
||||
@@ -5,7 +5,7 @@ const groceryinstance = new GroceryListCtl();
|
||||
const router = Router();
|
||||
|
||||
export const groceryListRoute = (app: Express) => {
|
||||
app.use('/grocery-list', router);
|
||||
app.use('/app/grocery-list', router);
|
||||
|
||||
router.get('/', async (req, res, next) => {
|
||||
const userid = req.query.userid as string;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import jwt from "jsonwebtoken";
|
||||
import dotenv from 'dotenv';
|
||||
import { Express } from "express"
|
||||
import { PassportStatic } from "passport";
|
||||
import { userRoute } from "./users";
|
||||
import { recipeRoute } from "./recipe";
|
||||
import { collectionRoute } from "./collection";
|
||||
@@ -11,14 +12,38 @@ import { friendRouter } from "./friend";
|
||||
import { cuisineRouter } from "./cuisine";
|
||||
import { courseRouter } from "./course";
|
||||
|
||||
export const routes = async (app: Express, passport: PassportStatic) => {
|
||||
dotenv.config();
|
||||
|
||||
export const routes = async (app: Express) => {
|
||||
// unprotected routes
|
||||
authRoute(app);
|
||||
|
||||
// middleware to check for auth on cookies on each request in protected routes
|
||||
app.use('/app', async (req, res, next) => {
|
||||
// pull jwt from request headers
|
||||
const token = req.headers['authorization']?.split(" ")[1];
|
||||
console.log(token);
|
||||
|
||||
if (!token) {
|
||||
res.status(403).send("Unauthorized");
|
||||
} else {
|
||||
jwt.verify(token, process.env.SESSIONSECRET as string, (err, data) => {
|
||||
if (err) {
|
||||
res.status(403).send(err);
|
||||
} else {
|
||||
console.log(data);
|
||||
req.user = data;
|
||||
next();
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// protected routes
|
||||
userRoute(app);
|
||||
friendRouter(app);
|
||||
recipeRoute(app);
|
||||
ingredientRoute(app);
|
||||
|
||||
// to do: refactor for ctlresponse
|
||||
authRoute(app, passport);
|
||||
collectionRoute(app);
|
||||
subscriptionRoute(app);
|
||||
groceryListRoute(app);
|
||||
|
||||
@@ -7,7 +7,7 @@ const IngredientInstance = new IngredientCtl();
|
||||
const router = Router();
|
||||
|
||||
export const ingredientRoute = (app: Express) => {
|
||||
app.use('/ingredient', router);
|
||||
app.use('/app/ingredient', router);
|
||||
|
||||
router.get('/', async (req, res, next) => {
|
||||
try {
|
||||
|
||||
@@ -8,7 +8,7 @@ const recipectl = new RecipeCtl();
|
||||
const router = Router();
|
||||
|
||||
export const recipeRoute = (app: Express) => {
|
||||
app.use('/recipe', router);
|
||||
app.use('/app/recipe', router);
|
||||
|
||||
router.get('/:id', async (req, res, next) => {
|
||||
const { id } = req.params;
|
||||
|
||||
@@ -5,7 +5,7 @@ const CollectionInstance = new CollectionCtl();
|
||||
const router = Router();
|
||||
|
||||
export const subscriptionRoute = (app: Express) => {
|
||||
app.use('/subscription', router);
|
||||
app.use('/app/subscription', router);
|
||||
|
||||
router.get('/', async (req, res, next) => {
|
||||
// @ts-ignore
|
||||
|
||||
@@ -6,7 +6,7 @@ const router = Router();
|
||||
const userCtl = new UserCtl();
|
||||
|
||||
export const userRoute = (app: Express) => {
|
||||
app.use('/users', router);
|
||||
app.use('/app/users', router);
|
||||
|
||||
// get all users
|
||||
router.get('/', async (req, res) => {
|
||||
|
||||
@@ -19,7 +19,7 @@ export interface IUser extends HasHistory, CanDeactivate {
|
||||
handle: string
|
||||
email: string
|
||||
isadmin: boolean
|
||||
password?: string
|
||||
password: string
|
||||
}
|
||||
|
||||
export interface IUserAuth {
|
||||
|
||||
@@ -8,10 +8,14 @@ export default class ControllerResponse<T> implements CtlResponse<T> {
|
||||
constructor(code: StatusCode, data: T | string, ok?: boolean) {
|
||||
this.code = code
|
||||
this.data = data
|
||||
this.ok = ok || (this.data !== null)
|
||||
this.ok = ok ?? (this.data !== null)
|
||||
}
|
||||
|
||||
send() {
|
||||
return { ok: this.ok, code: this.code, data: this.data }
|
||||
}
|
||||
|
||||
represent() {
|
||||
console.log({ ok: this.ok, code: this.code, data: this.data });
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
#! /bin/bash
|
||||
|
||||
rm -rf dist && mkdir -p dist && cp ./swagger.yaml ./dist && ./node_modules/.bin/tsc --project ./tsconfig.json --watch & ts-node-dev index.ts
|
||||
rm -rf dist && mkdir -p dist && cp ./swagger.yaml ./dist && ./node_modules/.bin/tsc --project ./tsconfig.json --watch & ts-node --files index.ts
|
||||
|
||||
@@ -13,5 +13,6 @@ export enum StatusCode {
|
||||
Unauthorized = 401,
|
||||
Forbidden = 403,
|
||||
NotFound = 404,
|
||||
Conflict = 409,
|
||||
ServerError = 500
|
||||
}
|
||||
Reference in New Issue
Block a user