added refresh token functionality

This commit is contained in:
Sascha Kühl 2025-03-13 08:57:27 +01:00
parent adb27445ff
commit 42391cdf7f
8 changed files with 146 additions and 38 deletions

View File

@ -1,3 +1,10 @@
module.exports = { module.exports = {
secret: "bullpen-secret-key" secret: "bullpen-secret-key",
// jwtExpiration: 3600, // 1 hour
// jwtRefreshExpiration: 86400, // 24 hours
/* for test */
jwtExpiration: 60, // 1 minute
jwtRefreshExpiration: 120 // 2 minutes
}; };

View File

@ -1,7 +1,6 @@
const db = require("../models"); const db = require("../models");
const config = require("../config/auth.config"); const config = require("../config/auth.config");
const User = db.user; const { user: User, role: Role, refreshToken: RefreshToken } = db;
const Role = db.role;
const Op = db.Sequelize.Op; const Op = db.Sequelize.Op;
@ -47,16 +46,12 @@ exports.signin = (req, res) => {
email: req.body.email email: req.body.email
} }
}) })
.then(user => { .then(async (user) => {
if (!user) { if (!user) {
return res.status(404).send({ message: "User Not found." }); return res.status(404).send({ message: "User Not found." });
} }
const passwordIsValid = user.validPassword(req.body.password); const passwordIsValid = user.validPassword(req.body.password);
// const passwordIsValid = bcrypt.compareSync(
// req.body.password,
// user.password
// );
if (!passwordIsValid) { if (!passwordIsValid) {
return res.status(401).send({ return res.status(401).send({
@ -70,9 +65,11 @@ exports.signin = (req, res) => {
{ {
algorithm: 'HS256', algorithm: 'HS256',
allowInsecureKeySizes: true, allowInsecureKeySizes: true,
expiresIn: 86400, // 24 hours expiresIn: config.jwtExpiration
}); });
let refreshToken = await RefreshToken.createToken(user);
const authorities = []; const authorities = [];
user.getRoles().then(roles => { user.getRoles().then(roles => {
for (let i = 0; i < roles.length; i++) { for (let i = 0; i < roles.length; i++) {
@ -83,7 +80,8 @@ exports.signin = (req, res) => {
username: user.username, username: user.username,
email: user.email, email: user.email,
roles: authorities, roles: authorities,
accessToken: token accessToken: token,
refreshToken: refreshToken
}); });
}); });
}) })
@ -91,3 +89,43 @@ exports.signin = (req, res) => {
res.status(500).send({ message: err.message }); res.status(500).send({ message: err.message });
}); });
}; };
exports.refreshToken = async (req, res) => {
const { refreshToken: requestToken } = req.body;
if (requestToken == null) {
return res.status(403).json({ message: "Refresh Token is required!" });
}
try {
let refreshToken = await RefreshToken.findOne({ where: { token: requestToken } });
console.log(refreshToken)
if (!refreshToken) {
res.status(403).json({ message: "Refresh token is not in database!" });
return;
}
if (RefreshToken.verifyExpiration(refreshToken)) {
RefreshToken.destroy({ where: { id: refreshToken.id } });
res.status(403).json({
message: "Refresh token was expired. Please make a new signin request",
});
return;
}
const user = await refreshToken.getUser();
let newAccessToken = jwt.sign({ id: user.id }, config.secret, {
expiresIn: config.jwtExpiration,
});
return res.status(200).json({
accessToken: newAccessToken,
refreshToken: refreshToken.token,
});
} catch (err) {
return res.status(500).send({ message: err });
}
};

View File

@ -3,29 +3,33 @@ const config = require("../config/auth.config.js");
const db = require("../models"); const db = require("../models");
const User = db.user; const User = db.user;
verifyToken = (req, res, next) => { const { TokenExpiredError } = jwt;
const catchError = (err, res) => {
if (err instanceof TokenExpiredError) {
return res.status(401).send({ message: "Unauthorized! Access Token was expired!" });
}
return res.sendStatus(401).send({ message: "Unauthorized!" });
}
const verifyToken = (req, res, next) => {
let token = req.headers["x-access-token"]; let token = req.headers["x-access-token"];
if (!token) { if (!token) {
return res.status(403).send({ return res.status(403).send({ message: "No token provided!" });
message: "No token provided!"
});
} }
jwt.verify(token, jwt.verify(token, config.secret, (err, decoded) => {
config.secret,
(err, decoded) => {
if (err) { if (err) {
return res.status(401).send({ return catchError(err, res);
message: "Unauthorized!",
});
} }
req.userId = decoded.id; req.userId = decoded.id;
next(); next();
}); });
}; };
isAdmin = (req, res, next) => { const isAdmin = (req, res, next) => {
User.findByPk(req.userId).then(user => { User.findByPk(req.userId).then(user => {
user.getRoles().then(roles => { user.getRoles().then(roles => {
for (let i = 0; i < roles.length; i++) { for (let i = 0; i < roles.length; i++) {
@ -42,7 +46,7 @@ isAdmin = (req, res, next) => {
}); });
}; };
isModerator = (req, res, next) => { const isModerator = (req, res, next) => {
User.findByPk(req.userId).then(user => { User.findByPk(req.userId).then(user => {
user.getRoles().then(roles => { user.getRoles().then(roles => {
for (let i = 0; i < roles.length; i++) { for (let i = 0; i < roles.length; i++) {
@ -59,7 +63,7 @@ isModerator = (req, res, next) => {
}); });
}; };
isModeratorOrAdmin = (req, res, next) => { const isModeratorOrAdmin = (req, res, next) => {
User.findByPk(req.userId).then(user => { User.findByPk(req.userId).then(user => {
user.getRoles().then(roles => { user.getRoles().then(roles => {
for (let i = 0; i < roles.length; i++) { for (let i = 0; i < roles.length; i++) {

View File

@ -34,6 +34,7 @@ db.role = require("../models/role.model.js")(sequelize);
db.pitchType = require("../models/pitchType.model.js")(sequelize); db.pitchType = require("../models/pitchType.model.js")(sequelize);
db.pitch = require("../models/pitch.model.js")(sequelize); db.pitch = require("../models/pitch.model.js")(sequelize);
db.bullpenSession = require("../models/bullpenSession.model.js")(sequelize); db.bullpenSession = require("../models/bullpenSession.model.js")(sequelize);
db.refreshToken = require("../models/refreshToken.model.js")(sequelize);
db.role.belongsToMany(db.user, { db.role.belongsToMany(db.user, {
through: "UserRoles" through: "UserRoles"
@ -41,6 +42,12 @@ db.role.belongsToMany(db.user, {
db.user.belongsToMany(db.role, { db.user.belongsToMany(db.role, {
through: "UserRoles" through: "UserRoles"
}); });
db.refreshToken.belongsTo(db.user, {
foreignKey: 'userId', targetKey: 'id'
});
db.user.hasOne(db.refreshToken, {
foreignKey: 'userId', targetKey: 'id'
});
db.ROLES = ["user", "admin", "moderator"]; db.ROLES = ["user", "admin", "moderator"];

View File

@ -0,0 +1,36 @@
const { DataTypes } = require('sequelize');
const config = require("../config/auth.config");
const { v4: uuidv4 } = require("uuid");
module.exports = (sequelize, Sequelize) => {
const RefreshToken = sequelize.define("refreshToken", {
token: {
type: DataTypes.STRING,
},
expiryDate: {
type: DataTypes.DATE,
},
});
RefreshToken.createToken = async function (user) {
let expiredAt = new Date();
expiredAt.setSeconds(expiredAt.getSeconds() + config.jwtRefreshExpiration);
let _token = uuidv4();
let refreshToken = await this.create({
token: _token,
userId: user.id,
expiryDate: expiredAt.getTime(),
});
return refreshToken.token;
};
RefreshToken.verifyExpiration = (token) => {
return token.expiryDate.getTime() < new Date().getTime();
};
return RefreshToken;
};

View File

@ -18,7 +18,8 @@
"pg": "^8.13.3", "pg": "^8.13.3",
"pg-hstore": "^2.3.4", "pg-hstore": "^2.3.4",
"sequelize": "^6.37.5", "sequelize": "^6.37.5",
"sqlite3": "^5.1.7" "sqlite3": "^5.1.7",
"uuid": "^11.1.0"
}, },
"devDependencies": { "devDependencies": {
"sequelize-cli": "^6.6.2" "sequelize-cli": "^6.6.2"
@ -3258,6 +3259,15 @@
"node": ">= 10.0.0" "node": ">= 10.0.0"
} }
}, },
"node_modules/sequelize/node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"license": "MIT",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/serve-static": { "node_modules/serve-static": {
"version": "1.16.2", "version": "1.16.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
@ -3891,12 +3901,16 @@
} }
}, },
"node_modules/uuid": { "node_modules/uuid": {
"version": "8.3.2", "version": "11.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"license": "MIT", "license": "MIT",
"bin": { "bin": {
"uuid": "dist/bin/uuid" "uuid": "dist/esm/bin/uuid"
} }
}, },
"node_modules/validator": { "node_modules/validator": {

View File

@ -23,7 +23,8 @@
"pg": "^8.13.3", "pg": "^8.13.3",
"pg-hstore": "^2.3.4", "pg-hstore": "^2.3.4",
"sequelize": "^6.37.5", "sequelize": "^6.37.5",
"sqlite3": "^5.1.7" "sqlite3": "^5.1.7",
"uuid": "^11.1.0"
}, },
"devDependencies": { "devDependencies": {
"sequelize-cli": "^6.6.2" "sequelize-cli": "^6.6.2"

View File

@ -20,4 +20,5 @@ module.exports = function(app) {
); );
app.post("/api/auth/signin", controller.signin); app.post("/api/auth/signin", controller.signin);
app.post("/api/auth/refreshtoken", controller.refreshToken);
}; };