From c8ba96532967d6d42b836fdd8f1609b74d3b9583 Mon Sep 17 00:00:00 2001 From: alex Date: Sat, 27 Jan 2024 21:04:16 +0100 Subject: [PATCH] your session --- package-lock.json | 19 +++++++ package.json | 2 + server.ts | 5 +- src/controllers/userController.ts | 87 ++++++++++++++++++++++++++++++- src/models/session.ts | 25 +++++++++ src/routes/userRoutes.ts | 10 ++++ src/utils/utils.ts | 9 ++++ 7 files changed, 153 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index b332b35..21acc4a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "dotenv": "^16.3.1", "express": "^4.18.2", "express-session": "^1.17.3", + "express-useragent": "^1.0.15", "fs-extra": "^11.2.0", "mariadb": "^3.2.3", "passport": "^0.7.0", @@ -36,6 +37,7 @@ "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/express-session": "^1.17.10", + "@types/express-useragent": "^1.0.5", "@types/fs-extra": "^11.0.4", "@types/passport-google-oauth20": "^2.0.14", "@types/swagger-jsdoc": "^6.0.4", @@ -206,6 +208,15 @@ "@types/express": "*" } }, + "node_modules/@types/express-useragent": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/express-useragent/-/express-useragent-1.0.5.tgz", + "integrity": "sha512-G1zPW6jDj7oGJvMvB8UCIAWznCOafcksYzOg8LINfYmvrXlSGJ6nsJ7O7GlmYMjrgGYvobYaI8IhRCkAKslPJA==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/fs-extra": { "version": "11.0.4", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz", @@ -1068,6 +1079,14 @@ "node": ">= 0.6" } }, + "node_modules/express-useragent": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/express-useragent/-/express-useragent-1.0.15.tgz", + "integrity": "sha512-eq5xMiYCYwFPoekffMjvEIk+NWdlQY9Y38OsTyl13IvA728vKT+q/CSERYWzcw93HGBJcIqMIsZC5CZGARPVdg==", + "engines": { + "node": ">=4.5" + } + }, "node_modules/express/node_modules/body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", diff --git a/package.json b/package.json index f83b969..b8a2d3f 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "dotenv": "^16.3.1", "express": "^4.18.2", "express-session": "^1.17.3", + "express-useragent": "^1.0.15", "fs-extra": "^11.2.0", "mariadb": "^3.2.3", "passport": "^0.7.0", @@ -38,6 +39,7 @@ "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/express-session": "^1.17.10", + "@types/express-useragent": "^1.0.5", "@types/fs-extra": "^11.0.4", "@types/passport-google-oauth20": "^2.0.14", "@types/swagger-jsdoc": "^6.0.4", diff --git a/server.ts b/server.ts index 7abc410..153d6d8 100644 --- a/server.ts +++ b/server.ts @@ -6,6 +6,7 @@ import cors from "cors"; import GoogleStrategy from "passport-google-oauth20"; import cookieParser from "cookie-parser"; import session from "express-session"; +import useragent from "express-useragent"; import calendarRoutes from "./src/routes/calendarRoutes"; import storeRoutes from "./src/routes/storeRoutes"; @@ -51,8 +52,6 @@ const options = { apis: ["./src/routes/*.ts"], }; -app.use(cookieParser()); - app.use( session({ secret: "keyboard cat", @@ -108,6 +107,8 @@ passport.deserializeUser(function (user: User, cb) { // TODO: setup cors app.use(cors()); +app.use(cookieParser()); +app.use(useragent.express()); app.use(bodyParser.json()); app.use("/api/v1/calendar", calendarRoutes); diff --git a/src/controllers/userController.ts b/src/controllers/userController.ts index 7776350..3235462 100644 --- a/src/controllers/userController.ts +++ b/src/controllers/userController.ts @@ -114,7 +114,7 @@ export async function SignUp(req: Request, res: Response) { ); // create session - saveSession(res, user.user_id, user.username); + saveSession(req, res, user.user_id, user.username); }) .catch((err) => { logger.error(err); @@ -181,7 +181,7 @@ export async function Login(req: Request, res: Response) { } // create session - saveSession(res, user.user_id, user.username); + saveSession(req, res, user.user_id, user.username); } catch (error) { logger.error(error); res.status(500).send({ err: "invalid request" }); @@ -272,6 +272,19 @@ export async function GetUser(req: Request, res: Response) { ); } + // update user session last_used + + Session.update( + { + last_used: new Date(), + }, + { + where: { + session_id: session.session_id, + }, + } + ); + res.status(200).send(respData); } catch (error) { logger.error(error); @@ -470,3 +483,73 @@ export async function UpdateUserProfilePassword(req: Request, res: Response) { res.status(500).send({ err: "invalid request" }); } } + +export async function GetUserProfileSessions(req: Request, res: Response) { + try { + const session = await getUserSession(req); + + if (!session) { + return res.status(401).send({ err: "unauthorized" }); + } + + const sessions = await Session.findAll({ + where: { + user_id: session.user_id, + }, + attributes: ["session_id", "id", "browser", "os", "last_used"], + }); + + // set last_used to now if session is the user's current session + + let currentSession = sessions.find( + (sess) => sess.session_id === session.session_id + )?.id; + + // remove session_id from sessions for security reasons + + const sessionsList = sessions.map((sess) => { + return { + id: sess.id, + browser: sess.browser, + os: sess.os, + last_used: sess.last_used, + }; + }); + + res.status(200).json({ + sessions: sessionsList, + currentSession: currentSession, + }); + } catch (error) { + logger.error(error); + res.status(500).send({ err: "invalid request" }); + } +} + +export async function DeleteUserProfileSession(req: Request, res: Response) { + try { + const { id } = req.params; + + if (!id) { + return res.status(400).send({ err: "invalid request" }); + } + + const session = await getUserSession(req); + + if (!session) { + return res.status(401).send({ err: "unauthorized" }); + } + + await Session.destroy({ + where: { + id: id, + user_id: session.user_id, + }, + }); + + res.status(200).send({ msg: "session deleted" }); + } catch (error) { + logger.error(error); + res.status(500).send({ err: "invalid request" }); + } +} diff --git a/src/models/session.ts b/src/models/session.ts index 55ef10f..2be21be 100644 --- a/src/models/session.ts +++ b/src/models/session.ts @@ -4,12 +4,21 @@ import sequelize from "../database/database"; interface SessionAttributes { session_id: string; user_id: string; + // needed in the ui to delete the session + id: string; + browser: string; + os: string; + last_used: Date; expires: Date; } class Session extends Model implements SessionAttributes { declare session_id: string; declare user_id: string; + declare id: string; + declare browser: string; + declare os: string; + declare last_used: Date; declare expires: Date; } @@ -25,6 +34,22 @@ Session.init( type: DataTypes.STRING, allowNull: false, }, + id: { + type: DataTypes.STRING, + allowNull: false, + }, + browser: { + type: DataTypes.STRING, + allowNull: false, + }, + os: { + type: DataTypes.STRING, + allowNull: false, + }, + last_used: { + type: DataTypes.DATE, + allowNull: false, + }, expires: { type: DataTypes.DATE, allowNull: false, diff --git a/src/routes/userRoutes.ts b/src/routes/userRoutes.ts index d21772d..032bf9c 100644 --- a/src/routes/userRoutes.ts +++ b/src/routes/userRoutes.ts @@ -24,5 +24,15 @@ router.post( sessionProtection, userController.UpdateUserProfilePassword ); +router.get( + "/profile/sessions", + sessionProtection, + userController.GetUserProfileSessions +); +router.delete( + "/profile/sessions/:id", + sessionProtection, + userController.DeleteUserProfileSession +); export default router; diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 2da6cd1..f65fd70 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -41,7 +41,12 @@ export function newUserSession() { return crypto.randomBytes(USER_SESSION_LENGTH).toString("hex"); } +export function newSessionId() { + return uuidv4(); +} + export async function saveSession( + req: Request, res: Response, userId: string, username: string @@ -52,6 +57,10 @@ export async function saveSession( await Session.create({ user_id: userId, session_id: userSession, + id: newSessionId(), + browser: req.useragent?.browser as string, + os: req.useragent?.os as string, + last_used: new Date(), expires: new Date(Date.now() + DEFAULT_SESSION_EXPIRY), });