Initial commit
Next.js + Express event management app for Hope Family Church.
This commit is contained in:
@@ -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 };
|
||||
@@ -0,0 +1,20 @@
|
||||
const { safeErrorMessage } = require('../utils/errorUtils');
|
||||
|
||||
// Not found error handler — don't echo the URL back (leaks route structure)
|
||||
const notFound = (req, res, next) => {
|
||||
const error = new Error('Not Found');
|
||||
res.status(404);
|
||||
next(error);
|
||||
};
|
||||
|
||||
// General error handler
|
||||
const errorHandler = (err, req, res, next) => {
|
||||
const statusCode = res.statusCode === 200 ? 500 : res.statusCode;
|
||||
|
||||
res.status(statusCode).json({
|
||||
message: safeErrorMessage(err),
|
||||
stack: process.env.NODE_ENV === 'production' ? undefined : err.stack,
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = { notFound, errorHandler };
|
||||
Reference in New Issue
Block a user