Compare commits

...

3 Commits

Author SHA1 Message Date
Sascha Kühl 2ba7778901 start adding tests 2025-03-13 15:57:54 +01:00
Sascha Kühl c30daf099d removed unused parameter variable 2025-03-13 08:59:02 +01:00
Sascha Kühl 42391cdf7f added refresh token functionality 2025-03-13 08:57:27 +01:00
25 changed files with 3927 additions and 55 deletions

View File

@ -1,3 +0,0 @@
module.exports = {
secret: "bullpen-secret-key"
};

3772
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,10 @@
{
"name": "bullpen-backend",
"version": "1.0.0",
"main": "index.js",
"main": "src/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node index.js",
"start": "node src/index.js",
"setup-example-db": "node database/setup.js"
},
"engines": {
@ -23,9 +23,14 @@
"pg": "^8.13.3",
"pg-hstore": "^2.3.4",
"sequelize": "^6.37.5",
"sqlite3": "^5.1.7"
"sqlite3": "^5.1.7",
"uuid": "^11.1.0"
},
"devDependencies": {
"sequelize-cli": "^6.6.2"
"cross-env": "^7.0.3",
"dotenv": "^16.4.7",
"jest": "^29.7.0",
"sequelize-cli": "^6.6.2",
"supertest": "^7.0.0"
}
}

View File

@ -0,0 +1,10 @@
module.exports = {
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 config = require("../config/auth.config");
const User = db.user;
const Role = db.role;
const { user: User, role: Role, refreshToken: RefreshToken } = db;
const Op = db.Sequelize.Op;
@ -47,16 +46,12 @@ exports.signin = (req, res) => {
email: req.body.email
}
})
.then(user => {
.then(async (user) => {
if (!user) {
return res.status(404).send({ message: "User Not found." });
}
const passwordIsValid = user.validPassword(req.body.password);
// const passwordIsValid = bcrypt.compareSync(
// req.body.password,
// user.password
// );
if (!passwordIsValid) {
return res.status(401).send({
@ -70,9 +65,11 @@ exports.signin = (req, res) => {
{
algorithm: 'HS256',
allowInsecureKeySizes: true,
expiresIn: 86400, // 24 hours
expiresIn: config.jwtExpiration
});
let refreshToken = await RefreshToken.createToken(user);
const authorities = [];
user.getRoles().then(roles => {
for (let i = 0; i < roles.length; i++) {
@ -83,7 +80,8 @@ exports.signin = (req, res) => {
username: user.username,
email: user.email,
roles: authorities,
accessToken: token
accessToken: token,
refreshToken: refreshToken
});
});
})
@ -91,3 +89,43 @@ exports.signin = (req, res) => {
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

@ -72,3 +72,5 @@ function initial() {
{ name: 'Slurve', abbreviation: 'SLV' },
]);
}
module.exports = app;

View File

@ -3,29 +3,33 @@ const config = require("../config/auth.config.js");
const db = require("../models");
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"];
if (!token) {
return res.status(403).send({
message: "No token provided!"
});
return res.status(403).send({ message: "No token provided!" });
}
jwt.verify(token,
config.secret,
(err, decoded) => {
if (err) {
return res.status(401).send({
message: "Unauthorized!",
});
}
req.userId = decoded.id;
next();
});
jwt.verify(token, config.secret, (err, decoded) => {
if (err) {
return catchError(err, res);
}
req.userId = decoded.id;
next();
});
};
isAdmin = (req, res, next) => {
const isAdmin = (req, res, next) => {
User.findByPk(req.userId).then(user => {
user.getRoles().then(roles => {
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.getRoles().then(roles => {
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.getRoles().then(roles => {
for (let i = 0; i < roles.length; i++) {
@ -87,4 +91,4 @@ const authJwt = {
isModerator: isModerator,
isModeratorOrAdmin: isModeratorOrAdmin
};
module.exports = authJwt;
module.exports = authJwt;

View File

@ -29,11 +29,12 @@ const db = {};
db.Sequelize = Sequelize;
db.sequelize = sequelize;
db.user = require("../models/user.model.js")(sequelize);
db.role = require("../models/role.model.js")(sequelize);
db.pitchType = require("../models/pitchType.model.js")(sequelize);
db.pitch = require("../models/pitch.model.js")(sequelize);
db.bullpenSession = require("../models/bullpenSession.model.js")(sequelize);
db.user = require("./user.model.js")(sequelize);
db.role = require("./role.model.js")(sequelize);
db.pitchType = require("./pitchType.model.js")(sequelize);
db.pitch = require("./pitch.model.js")(sequelize);
db.bullpenSession = require("./bullpenSession.model.js")(sequelize);
db.refreshToken = require("./refreshToken.model.js")(sequelize);
db.role.belongsToMany(db.user, {
through: "UserRoles"
@ -41,6 +42,12 @@ db.role.belongsToMany(db.user, {
db.user.belongsToMany(db.role, {
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"];
@ -51,4 +58,4 @@ db.bullpenSession.belongsTo(db.user, {
db.pitch.belongsTo(db.bullpenSession);
db.pitch.belongsTo(db.pitchType);
module.exports = db;
module.exports = db;

View File

@ -0,0 +1,36 @@
const { DataTypes } = require('sequelize');
const config = require("../config/auth.config");
const { v4: uuidv4 } = require("uuid");
module.exports = (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

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

View File

@ -0,0 +1,7 @@
const signupUser = {
firstName: "Hans",
lastName: "Zimmer",
dateOfBirth: "1956-11-23",
email: "hans.zimmer@email.com",
password: "secret123"
}

19
backend/test/user.test.js Normal file
View File

@ -0,0 +1,19 @@
const request = require("supertest")
const app = require("../src/index")
const { signupUser } = require("data/user.test.data")
require("dotenv").config();
describe("GET /api/auth/signup", () => {
it("should signup a user", async () => {
return request(app)
.post("/api/auth/signup")
.send(signupUser)
.expect('Content-Type', /json/)
.expect(200)
.then((res) => {
expect(res.statusCode).toBe(200);
})
});
});