your session
parent
edfd6b08f3
commit
c8ba965329
|
@ -18,6 +18,7 @@
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"express-session": "^1.17.3",
|
"express-session": "^1.17.3",
|
||||||
|
"express-useragent": "^1.0.15",
|
||||||
"fs-extra": "^11.2.0",
|
"fs-extra": "^11.2.0",
|
||||||
"mariadb": "^3.2.3",
|
"mariadb": "^3.2.3",
|
||||||
"passport": "^0.7.0",
|
"passport": "^0.7.0",
|
||||||
|
@ -36,6 +37,7 @@
|
||||||
"@types/cors": "^2.8.17",
|
"@types/cors": "^2.8.17",
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
"@types/express-session": "^1.17.10",
|
"@types/express-session": "^1.17.10",
|
||||||
|
"@types/express-useragent": "^1.0.5",
|
||||||
"@types/fs-extra": "^11.0.4",
|
"@types/fs-extra": "^11.0.4",
|
||||||
"@types/passport-google-oauth20": "^2.0.14",
|
"@types/passport-google-oauth20": "^2.0.14",
|
||||||
"@types/swagger-jsdoc": "^6.0.4",
|
"@types/swagger-jsdoc": "^6.0.4",
|
||||||
|
@ -206,6 +208,15 @@
|
||||||
"@types/express": "*"
|
"@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": {
|
"node_modules/@types/fs-extra": {
|
||||||
"version": "11.0.4",
|
"version": "11.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz",
|
||||||
|
@ -1068,6 +1079,14 @@
|
||||||
"node": ">= 0.6"
|
"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": {
|
"node_modules/express/node_modules/body-parser": {
|
||||||
"version": "1.20.1",
|
"version": "1.20.1",
|
||||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
|
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"express-session": "^1.17.3",
|
"express-session": "^1.17.3",
|
||||||
|
"express-useragent": "^1.0.15",
|
||||||
"fs-extra": "^11.2.0",
|
"fs-extra": "^11.2.0",
|
||||||
"mariadb": "^3.2.3",
|
"mariadb": "^3.2.3",
|
||||||
"passport": "^0.7.0",
|
"passport": "^0.7.0",
|
||||||
|
@ -38,6 +39,7 @@
|
||||||
"@types/cors": "^2.8.17",
|
"@types/cors": "^2.8.17",
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
"@types/express-session": "^1.17.10",
|
"@types/express-session": "^1.17.10",
|
||||||
|
"@types/express-useragent": "^1.0.5",
|
||||||
"@types/fs-extra": "^11.0.4",
|
"@types/fs-extra": "^11.0.4",
|
||||||
"@types/passport-google-oauth20": "^2.0.14",
|
"@types/passport-google-oauth20": "^2.0.14",
|
||||||
"@types/swagger-jsdoc": "^6.0.4",
|
"@types/swagger-jsdoc": "^6.0.4",
|
||||||
|
|
|
@ -6,6 +6,7 @@ import cors from "cors";
|
||||||
import GoogleStrategy from "passport-google-oauth20";
|
import GoogleStrategy from "passport-google-oauth20";
|
||||||
import cookieParser from "cookie-parser";
|
import cookieParser from "cookie-parser";
|
||||||
import session from "express-session";
|
import session from "express-session";
|
||||||
|
import useragent from "express-useragent";
|
||||||
|
|
||||||
import calendarRoutes from "./src/routes/calendarRoutes";
|
import calendarRoutes from "./src/routes/calendarRoutes";
|
||||||
import storeRoutes from "./src/routes/storeRoutes";
|
import storeRoutes from "./src/routes/storeRoutes";
|
||||||
|
@ -51,8 +52,6 @@ const options = {
|
||||||
apis: ["./src/routes/*.ts"],
|
apis: ["./src/routes/*.ts"],
|
||||||
};
|
};
|
||||||
|
|
||||||
app.use(cookieParser());
|
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
session({
|
session({
|
||||||
secret: "keyboard cat",
|
secret: "keyboard cat",
|
||||||
|
@ -108,6 +107,8 @@ passport.deserializeUser(function (user: User, cb) {
|
||||||
|
|
||||||
// TODO: setup cors
|
// TODO: setup cors
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
|
app.use(cookieParser());
|
||||||
|
app.use(useragent.express());
|
||||||
|
|
||||||
app.use(bodyParser.json());
|
app.use(bodyParser.json());
|
||||||
app.use("/api/v1/calendar", calendarRoutes);
|
app.use("/api/v1/calendar", calendarRoutes);
|
||||||
|
|
|
@ -114,7 +114,7 @@ export async function SignUp(req: Request, res: Response) {
|
||||||
);
|
);
|
||||||
|
|
||||||
// create session
|
// create session
|
||||||
saveSession(res, user.user_id, user.username);
|
saveSession(req, res, user.user_id, user.username);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
|
@ -181,7 +181,7 @@ export async function Login(req: Request, res: Response) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// create session
|
// create session
|
||||||
saveSession(res, user.user_id, user.username);
|
saveSession(req, res, user.user_id, user.username);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
res.status(500).send({ err: "invalid request" });
|
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);
|
res.status(200).send(respData);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
@ -470,3 +483,73 @@ export async function UpdateUserProfilePassword(req: Request, res: Response) {
|
||||||
res.status(500).send({ err: "invalid request" });
|
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" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,12 +4,21 @@ import sequelize from "../database/database";
|
||||||
interface SessionAttributes {
|
interface SessionAttributes {
|
||||||
session_id: string;
|
session_id: string;
|
||||||
user_id: string;
|
user_id: string;
|
||||||
|
// needed in the ui to delete the session
|
||||||
|
id: string;
|
||||||
|
browser: string;
|
||||||
|
os: string;
|
||||||
|
last_used: Date;
|
||||||
expires: Date;
|
expires: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
class Session extends Model<SessionAttributes> implements SessionAttributes {
|
class Session extends Model<SessionAttributes> implements SessionAttributes {
|
||||||
declare session_id: string;
|
declare session_id: string;
|
||||||
declare user_id: string;
|
declare user_id: string;
|
||||||
|
declare id: string;
|
||||||
|
declare browser: string;
|
||||||
|
declare os: string;
|
||||||
|
declare last_used: Date;
|
||||||
declare expires: Date;
|
declare expires: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,6 +34,22 @@ Session.init(
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
allowNull: false,
|
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: {
|
expires: {
|
||||||
type: DataTypes.DATE,
|
type: DataTypes.DATE,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
|
|
|
@ -24,5 +24,15 @@ router.post(
|
||||||
sessionProtection,
|
sessionProtection,
|
||||||
userController.UpdateUserProfilePassword
|
userController.UpdateUserProfilePassword
|
||||||
);
|
);
|
||||||
|
router.get(
|
||||||
|
"/profile/sessions",
|
||||||
|
sessionProtection,
|
||||||
|
userController.GetUserProfileSessions
|
||||||
|
);
|
||||||
|
router.delete(
|
||||||
|
"/profile/sessions/:id",
|
||||||
|
sessionProtection,
|
||||||
|
userController.DeleteUserProfileSession
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|
|
@ -41,7 +41,12 @@ export function newUserSession() {
|
||||||
return crypto.randomBytes(USER_SESSION_LENGTH).toString("hex");
|
return crypto.randomBytes(USER_SESSION_LENGTH).toString("hex");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function newSessionId() {
|
||||||
|
return uuidv4();
|
||||||
|
}
|
||||||
|
|
||||||
export async function saveSession(
|
export async function saveSession(
|
||||||
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
userId: string,
|
userId: string,
|
||||||
username: string
|
username: string
|
||||||
|
@ -52,6 +57,10 @@ export async function saveSession(
|
||||||
await Session.create({
|
await Session.create({
|
||||||
user_id: userId,
|
user_id: userId,
|
||||||
session_id: userSession,
|
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),
|
expires: new Date(Date.now() + DEFAULT_SESSION_EXPIRY),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue