From e827964688408d47654c59713805b9f2be9d55d8 Mon Sep 17 00:00:00 2001 From: Torsten Schulz Date: Thu, 17 Jul 2025 13:56:34 +0200 Subject: [PATCH] Fixed multiple bugs --- backend/controllers/authController.js | 32 ++++++------- backend/middleware/authMiddleware.js | 33 +++++++------ backend/models/User.js | 4 ++ backend/models/index.js | 2 + backend/server.js | 3 +- backend/services/authService.js | 31 ++++++++----- backend/utils/userUtils.js | 67 ++++++++++++++++++--------- 7 files changed, 105 insertions(+), 67 deletions(-) diff --git a/backend/controllers/authController.js b/backend/controllers/authController.js index c325670..0d870f2 100644 --- a/backend/controllers/authController.js +++ b/backend/controllers/authController.js @@ -23,26 +23,24 @@ const activate = async (req, res, next) => { } }; -const loginUser = async (req, res) => { - const { username, password } = req.body; - const user = await User.findOne({ where: { username } }); - if (!user || !(await user.validatePassword(password))) { - return res.status(401).json({ message: 'Ungültige Anmeldedaten' }); +const loginUser = async (req, res, next) => { + try { + const { email, password } = req.body; + const result = await login(email, password); + res.status(200).json(result); + } catch (error) { + next(error); } - const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET, { expiresIn: '1h' }); - await UserToken.create({ - userId: user.id, - token, - expiresAt: new Date(Date.now() + 3600 * 1000), - }); - res.json({ token }); }; -const logoutUser = async (req, res) => { - const token = req.headers['authorization']?.split(' ')[1]; - if (!token) return res.status(400).json({ message: 'Token fehlt' }); - await UserToken.destroy({ where: { token } }); - res.json({ message: 'Logout erfolgreich' }); +const logoutUser = async (req, res, next) => { + try { + const token = req.headers['authorization']?.split(' ')[1]; + const result = await logout(token); + res.status(200).json(result); + } catch (error) { + next(error); + } }; export { registerUser, activate, loginUser, logoutUser }; diff --git a/backend/middleware/authMiddleware.js b/backend/middleware/authMiddleware.js index aa62e4d..2e5d43c 100644 --- a/backend/middleware/authMiddleware.js +++ b/backend/middleware/authMiddleware.js @@ -1,18 +1,23 @@ -import User from '../models/User.js'; +import jwt from 'jsonwebtoken'; +import UserToken from '../models/UserToken.js'; export const authenticate = async (req, res, next) => { - try { - const { userid: userId, authcode: authCode } = req.headers; - if (!userId || !authCode) { - return res.status(401).json({ error: 'Unauthorized: Missing credentials' }); - } - const user = await User.findOne({ where: { email: userId, authCode: authCode } }); - if (!user) { - return res.status(401).json({ error: 'Unauthorized: Invalid credentials' }); - } - next(); - } catch(error) { - console.log(error); - return res.status(500).json({ error: 'Internal Server Error at auth' }); + let token = req.headers['authorization']?.split(' ')[1]; + if (!token) { + token = req.headers['authcode']; + } + if (!token) { + return res.status(401).json({ error: 'Unauthorized: Token fehlt' }); + } + try { + const decoded = jwt.verify(token, process.env.JWT_SECRET); + const tokenRecord = await UserToken.findOne({ where: { token } }); + if (!tokenRecord || tokenRecord.expiresAt < new Date()) { + return res.status(401).json({ error: 'Unauthorized: Invalid credentials' }); } + req.user = { id: decoded.userId }; + next(); + } catch (err) { + return res.status(401).json({ error: 'Unauthorized: Invalid credentials' }); + } }; \ No newline at end of file diff --git a/backend/models/User.js b/backend/models/User.js index da09f19..ba26007 100644 --- a/backend/models/User.js +++ b/backend/models/User.js @@ -63,4 +63,8 @@ const User = sequelize.define('User', { }, }); +User.prototype.validatePassword = function(password) { + return bcrypt.compare(password, this.password); +}; + export default User; diff --git a/backend/models/index.js b/backend/models/index.js index 7a00d96..974cec8 100644 --- a/backend/models/index.js +++ b/backend/models/index.js @@ -27,6 +27,7 @@ import TournamentMember from './TournamentMember.js'; import TournamentMatch from './TournamentMatch.js'; import TournamentResult from './TournamentResult.js'; import Accident from './Accident.js'; +import UserToken from './UserToken.js'; User.hasMany(Log, { foreignKey: 'userId' }); Log.belongsTo(User, { foreignKey: 'userId' }); @@ -203,4 +204,5 @@ export { TournamentMatch, TournamentResult, Accident, + UserToken, }; diff --git a/backend/server.js b/backend/server.js index 64a5e51..6bd8f3d 100644 --- a/backend/server.js +++ b/backend/server.js @@ -8,7 +8,7 @@ import { DiaryNote, DiaryTag, MemberDiaryTag, DiaryDateTag, DiaryMemberNote, DiaryMemberTag, PredefinedActivity, DiaryDateActivity, Match, League, Team, Group, GroupActivity, Tournament, TournamentGroup, TournamentMatch, TournamentResult, - TournamentMember, Accident + TournamentMember, Accident, UserToken } from './models/index.js'; import authRoutes from './routes/authRoutes.js'; import clubRoutes from './routes/clubRoutes.js'; @@ -99,6 +99,7 @@ app.get('*', (req, res) => { await TournamentMatch.sync({ alter: true }); await TournamentResult.sync({ alter: true }); await Accident.sync({ alter: true }); + await UserToken.sync({ alter: true }); app.listen(port, () => { console.log(`Server is running on http://localhost:${port}`); diff --git a/backend/services/authService.js b/backend/services/authService.js index bdaab8a..f5d0f78 100644 --- a/backend/services/authService.js +++ b/backend/services/authService.js @@ -1,6 +1,7 @@ import bcrypt from 'bcrypt'; import jwt from 'jsonwebtoken'; import User from '../models/User.js'; +import UserToken from '../models/UserToken.js'; import { sendActivationEmail } from './emailService.js'; const register = async (email, password) => { @@ -25,22 +26,28 @@ const activateUser = async (activationCode) => { }; const login = async (email, password) => { + if (!email || !password) { + throw { status: 400, message: 'Email und Passwort sind erforderlich.' }; + } const user = await User.findOne({ where: { email } }); - if (!user || !user.isActive) throw new Error('Invalid email or password.'); - const isPasswordValid = await bcrypt.compare(password, user.password); - if (!isPasswordValid) throw new Error('Invalid email or password!'); - const token = jwt.sign({ userId: user.hashedId }, process.env.JWT_SECRET, { expiresIn: '1h' }); - user.authCode = token; - await user.save(); + if (!user || !(await bcrypt.compare(password, user.password))) { + throw { status: 401, message: 'Ungültige Anmeldedaten' }; + } + const token = jwt.sign({ userId: user.id }, process.env.JWT_SECRET, { expiresIn: '1h' }); + await UserToken.create({ + userId: user.id, + token, + expiresAt: new Date(Date.now() + 3600 * 1000), + }); return { token }; }; -const logout = async(userId, authToken) => { - const user = await User.findOne({ where: { id: userId, authToken: authToken }}); - if (!user) { - throw new Error('not found'); +const logout = async (token) => { + if (!token) { + throw { status: 400, message: 'Token fehlt' }; } - user.update({ authToken: null }); -} + await UserToken.destroy({ where: { token } }); + return { message: 'Logout erfolgreich' }; +}; export { register, activateUser, login, logout }; diff --git a/backend/utils/userUtils.js b/backend/utils/userUtils.js index a897b64..81608a8 100644 --- a/backend/utils/userUtils.js +++ b/backend/utils/userUtils.js @@ -1,24 +1,45 @@ -import User from '../models/User.js' -import UserClub from '../models/UserClub.js'; +import jwt from 'jsonwebtoken'; +import { Op } from 'sequelize'; +import User from '../models/User.js'; +import UserToken from '../models/UserToken.js'; +import UserClub from '../models/UserClub.js'; // <-- hier hinzufügen import HttpError from '../exceptions/HttpError.js'; import { config } from 'dotenv'; +config(); // sorgt dafür, dass process.env.JWT_SECRET geladen wird -export const getUserByToken = async(token) => { +export const getUserByToken = async (token) => { try { - const user = await User.findOne({ - where: [ - {auth_code: token} - ] - }); - return user; - } catch (error) { - console.log(error); - const err = new HttpError('noaccess', 403); - throw err; - } -} + // 1. JWT validieren + const payload = jwt.verify(token, process.env.JWT_SECRET); -export const hasUserClubAccess = async(userId, clubId) => { + // 2. Token-Eintrag prüfen (existiert und nicht abgelaufen) + const stored = await UserToken.findOne({ + where: { + token, + expiresAt: { [Op.gt]: new Date() } + } + }); + if (!stored) { + throw new HttpError('Token abgelaufen oder ungültig', 401); + } + + // 3. User laden + const user = await User.findByPk(payload.userId); + if (!user) { + throw new HttpError('Benutzer nicht gefunden', 404); + } + + return user; + } catch (err) { + console.error(err); + // Falls es ein HttpError ist, einfach weiterwerfen + if (err instanceof HttpError) throw err; + // ansonsten pauschal „noaccess“ + throw new HttpError('noaccess', 403); + } +}; + +export const hasUserClubAccess = async (userId, clubId) => { try { console.log('[hasUserClubAccess]'); const userClub = await UserClub.findOne({ @@ -29,23 +50,23 @@ export const hasUserClubAccess = async(userId, clubId) => { } }); return userClub !== null; - } catch(error) { + } catch (error) { console.log(error); throw new HttpError('notfound', 500); } console.log('---- no user found'); } -export const checkAccess = async(userToken, clubId) => { +export const checkAccess = async (userToken, clubId) => { try { const user = await getUserByToken(userToken); - if (!await hasUserClubAccess(user.id, clubId)) { + const hasAccess = await hasUserClubAccess(user.id, clubId); + if (!hasAccess) { console.log('no club access'); - const err = new HttpError('noaccess', 403); - throw err; + throw new HttpError('noaccess', 403); } } catch (error) { console.log(error); - throw error; + throw error; } -} \ No newline at end of file +};