Initial commit

Next.js + Express event management app for Hope Family Church.
This commit is contained in:
2026-07-23 15:26:47 +02:00
commit 3d381944d2
246 changed files with 57565 additions and 0 deletions
+122
View File
@@ -0,0 +1,122 @@
const rateLimit = require("express-rate-limit");
const jwt = require('jsonwebtoken');
const prisma = require('../config/db');
// Protect routes - verify token
const protect = async (req, res, next) => {
let token;
// Check if token exists in headers
if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) {
try {
// Get token from header
token = req.headers.authorization.split(' ')[1];
// Verify token
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// Get user from the token (exclude password)
req.user = await prisma.user.findUnique({
where: { id: decoded.id },
select: {
id: true,
name: true,
email: true,
role: true,
isActive: true,
createdAt: true,
updatedAt: true,
phoneNumber: true,
tokenVersion: true
}
});
if (!req.user) {
res.status(401);
return next(new Error('User not found'));
}
if (!req.user.isActive) {
res.status(401);
return next(new Error('User account is deactivated'));
}
// Revocation check — tokenVersion in JWT must match DB
// Old tokens without tokenVersion are treated as version 0
const tokenVer = decoded.tokenVersion ?? 0;
if (tokenVer !== req.user.tokenVersion) {
res.status(401);
return next(new Error('Session has been revoked. Please log in again.'));
}
next();
} catch (error) {
console.error(error);
res.status(401);
return next(new Error('Not authorized, token failed'));
}
} else {
res.status(401);
return next(new Error('Not authorized, no token'));
}
};
// Admin only middleware
const admin = (req, res, next) => {
if (req.user && req.user.role === 'admin') {
next();
} else {
res.status(403);
return next(new Error('Not authorized as an admin'));
}
};
// Staff or higher middleware
const staff = (req, res, next) => {
if (req.user && (req.user.role === 'admin' || req.user.role === 'supervisor' || req.user.role === 'staff')) {
next();
} else {
res.status(403);
return next(new Error('Not authorized as staff'));
}
};
// Supervisor or higher middleware
const supervisor = (req, res, next) => {
if (req.user && (req.user.role === 'admin' || req.user.role === 'supervisor')) {
next();
} else {
res.status(403);
return next(new Error('Not authorized as a supervisor'));
}
};
const loginLimiter = rateLimit({
windowMs: 60 * 1000,
max: 15,
message: "Too many login attempts. Try again later.",
});
// Optional auth — populates req.user if a valid token is present, but never rejects the request
const optionalAuth = async (req, res, next) => {
if (!req.headers.authorization || !req.headers.authorization.startsWith('Bearer')) {
return next();
}
try {
const token = req.headers.authorization.split(' ')[1];
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const user = await prisma.user.findUnique({
where: { id: decoded.id },
select: { id: true, name: true, email: true, role: true, isActive: true, createdAt: true, updatedAt: true, phoneNumber: true, tokenVersion: true }
});
if (user && user.isActive && (decoded.tokenVersion ?? 0) === user.tokenVersion) {
req.user = user;
}
} catch {
// Token invalid or expired — proceed without user
}
next();
};
module.exports = { protect, admin, staff, supervisor, loginLimiter, optionalAuth };