mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
initial
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* File Name: auth.middleware.js
|
||||
* Type of Program: Middleware
|
||||
* Description: JWT authentication guard.
|
||||
* Reads the Bearer token from the Authorization header,
|
||||
* verifies it, attaches decoded payload to req.user,
|
||||
* and verifies the session is still active in the DB.
|
||||
* Author: rgrgogu
|
||||
* Date Created: Oct. 6, 2025
|
||||
***********************************************************************************************************************************************************************
|
||||
* HOW TO USE:
|
||||
* const { authenticate } = require('../middleware/auth.middleware');
|
||||
* router.get('/profile', authenticate, handler);
|
||||
***********************************************************************************************************************************************************************/
|
||||
const { verifyAccessToken } = require('../utils/token.util');
|
||||
const mdl_Users = require('../models/users/users.mdl');
|
||||
const R = require('../utils/response.util');
|
||||
|
||||
/**
|
||||
* Validates JWT and loads user from DB.
|
||||
* Attaches the full user record to req.user.
|
||||
*/
|
||||
const authenticate = async (req, res, next) => {
|
||||
try {
|
||||
const authHeader = req.headers.authorization;
|
||||
if (!authHeader?.startsWith('Bearer '))
|
||||
return R.error(res, 'No token provided.', 401);
|
||||
|
||||
const token = authHeader.split(' ')[1];
|
||||
const decoded = verifyAccessToken(token);
|
||||
|
||||
const user = await mdl_Users.findByPk(decoded.user_id, {
|
||||
attributes: { exclude: ['password', 'otp_code', 'otp_expires_at'] },
|
||||
});
|
||||
|
||||
if (!user) return R.error(res, 'User not found.', 401);
|
||||
if (!user.is_active) return R.error(res, 'Account is deactivated.', 403);
|
||||
|
||||
req.user = user;
|
||||
next();
|
||||
} catch (err) {
|
||||
if (err.name === 'TokenExpiredError')
|
||||
return R.error(res, 'Token expired. Please log in again.', 401);
|
||||
return R.error(res, 'Invalid token.', 401);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { authenticate };
|
||||
@@ -0,0 +1,53 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* File Name: csrf.middleware.js
|
||||
* Type of Program: Middleware
|
||||
* Description: CSRF protection using the `csurf` package (Double Submit Cookie pattern).
|
||||
* - csrfProtection → the csurf middleware instance (attach to state-changing routes)
|
||||
* - getCsrfToken → GET /csrf-token handler — sends the token to the client
|
||||
* - csrfErrorHandler → catches EBADCSRFTOKEN and returns a 403
|
||||
*
|
||||
* NOTE: Because we use stateless JWT (no server sessions), CSRF is only
|
||||
* relevant for cookie-based flows (e.g., CSRF token embedded in form headers).
|
||||
* For REST / SPA clients, the standard practice is to omit CSRF and rely on
|
||||
* the Authorization Bearer header (which is already CSRF-safe by design).
|
||||
* This file keeps CSRF available for SSR / hybrid flows.
|
||||
*
|
||||
* Author: rgrgogu
|
||||
* Date Created: Oct. 6, 2025
|
||||
***********************************************************************************************************************************************************************
|
||||
* HOW TO USE:
|
||||
* // 1. Mount the token endpoint (public):
|
||||
* app.get('/api/csrf-token', getCsrfToken);
|
||||
*
|
||||
* // 2. Apply to state-mutating cookie-based routes:
|
||||
* router.post('/login', csrfProtection, loginHandler);
|
||||
*
|
||||
* // 3. Register the error handler AFTER all routes:
|
||||
* app.use(csrfErrorHandler);
|
||||
***********************************************************************************************************************************************************************/
|
||||
const csurf = require('csurf');
|
||||
const R = require('../utils/response.util');
|
||||
|
||||
/** csurf instance — stores token in a signed cookie */
|
||||
const csrfProtection = csurf({ cookie: { httpOnly: true, sameSite: 'strict' } });
|
||||
|
||||
/**
|
||||
* GET /api/csrf-token
|
||||
* Returns the CSRF token the client must echo back on state-changing requests
|
||||
* via the `X-CSRF-Token` header or `_csrf` body field.
|
||||
*/
|
||||
const getCsrfToken = (req, res) => {
|
||||
res.json({ csrfToken: req.csrfToken() });
|
||||
};
|
||||
|
||||
/**
|
||||
* Error handler for invalid / missing CSRF tokens.
|
||||
* Must be registered as Express error-handling middleware (4 args).
|
||||
*/
|
||||
const csrfErrorHandler = (err, req, res, next) => {
|
||||
if (err.code === 'EBADCSRFTOKEN')
|
||||
return R.error(res, 'Invalid or missing CSRF token.', 403);
|
||||
next(err);
|
||||
};
|
||||
|
||||
module.exports = { csrfProtection, getCsrfToken, csrfErrorHandler };
|
||||
@@ -0,0 +1,65 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* File Name: rateLimiter.middleware.js
|
||||
* Type of Program: Middleware
|
||||
* Description: Express-rate-limit configurations for different route tiers.
|
||||
* - globalLimiter → applied to ALL routes (1000 req / 15 min)
|
||||
* - authLimiter → applied to login/register (20 req / 15 min)
|
||||
* - otpLimiter → applied to OTP send/verify (5 req / 15 min)
|
||||
* - sensitiveOpsLimiter → password change, account delete (10 req / hour)
|
||||
* Author: rgrgogu
|
||||
* Date Created: Oct. 6, 2025
|
||||
***********************************************************************************************************************************************************************
|
||||
* HOW TO USE:
|
||||
* const { authLimiter } = require('../middleware/rateLimiter.middleware');
|
||||
* router.post('/login', authLimiter, loginHandler);
|
||||
***********************************************************************************************************************************************************************/
|
||||
const rateLimit = require('express-rate-limit');
|
||||
|
||||
const windowMs15 = 15 * 60 * 1000; // 15 minutes
|
||||
|
||||
/** Applied globally in server.js */
|
||||
const globalLimiter = rateLimit({
|
||||
windowMs: windowMs15,
|
||||
max: 1000,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
message: { status: 'error', message: 'Too many requests, please try again later.' },
|
||||
});
|
||||
|
||||
/** Login & register routes */
|
||||
const authLimiter = rateLimit({
|
||||
windowMs: windowMs15,
|
||||
max: 20,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
message: { status: 'error', message: 'Too many auth attempts. Please wait 15 minutes.' },
|
||||
});
|
||||
|
||||
/** OTP send / verify */
|
||||
const otpLimiter = rateLimit({
|
||||
windowMs: windowMs15,
|
||||
max: 5,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
message: { status: 'error', message: 'Too many OTP requests. Please wait 15 minutes.' },
|
||||
});
|
||||
|
||||
/** Password change, account delete */
|
||||
const sensitiveOpsLimiter = rateLimit({
|
||||
windowMs: 60 * 60 * 1000, // 1 hour
|
||||
max: 10,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
message: { status: 'error', message: 'Too many sensitive operations. Please wait 1 hour.' },
|
||||
});
|
||||
|
||||
/** Admin routes — tighter than global */
|
||||
const adminLimiter = rateLimit({
|
||||
windowMs: windowMs15,
|
||||
max: 200,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
message: { status: 'error', message: 'Too many admin requests. Please wait 15 minutes.' },
|
||||
});
|
||||
|
||||
module.exports = { globalLimiter, authLimiter, otpLimiter, sensitiveOpsLimiter, adminLimiter };
|
||||
@@ -0,0 +1,77 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* File Name: rbac.middleware.js
|
||||
* Type of Program: Middleware
|
||||
* Description: Role-Based Access Control (RBAC) guards.
|
||||
* Hierarchy: admin > staff > user (client)
|
||||
*
|
||||
* Exported guards:
|
||||
* - requireClient() → acc_type in ['user', 'staff', 'admin']
|
||||
* - requireStaff() → acc_type in ['staff', 'admin']
|
||||
* - requireAdmin() → acc_type === 'admin' only
|
||||
* - requireOwnerOrStaff() → owns the resource OR is staff/admin
|
||||
* - requireOwnerOrAdmin() → owns the resource OR is admin
|
||||
*
|
||||
* Author: rgrgogu
|
||||
* Date Created: Oct. 6, 2025
|
||||
***********************************************************************************************************************************************************************
|
||||
* HOW TO USE:
|
||||
* const { requireAdmin, requireStaff } = require('../middleware/rbac.middleware');
|
||||
* // Must be used AFTER authenticate middleware
|
||||
* router.get('/users', authenticate, requireStaff(), handler);
|
||||
* router.delete('/users/:id', authenticate, requireAdmin(), handler);
|
||||
***********************************************************************************************************************************************************************/
|
||||
const R = require('../utils/response.util');
|
||||
|
||||
const ROLES = { user: 1, staff: 2, admin: 3 };
|
||||
|
||||
/**
|
||||
* Generic role guard factory.
|
||||
* @param {string[]} allowed - list of acc_type values that can pass
|
||||
*/
|
||||
const requireRole = (allowed) => (req, res, next) => {
|
||||
if (!req.user)
|
||||
return R.error(res, 'Authentication required.', 401);
|
||||
|
||||
if (!allowed.includes(req.user.acc_type))
|
||||
return R.error(res, 'You do not have permission to access this resource.', 403);
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
/** Any logged-in user (client, staff, admin) */
|
||||
const requireClient = () => requireRole(['user', 'staff', 'admin']);
|
||||
|
||||
/** Staff or Admin */
|
||||
const requireStaff = () => requireRole(['staff', 'admin']);
|
||||
|
||||
/** Admin only */
|
||||
const requireAdmin = () => requireRole(['admin']);
|
||||
|
||||
/**
|
||||
* Allows resource owner OR staff/admin.
|
||||
* Reads the owner's user_id from req.params.user_id or req.params.id.
|
||||
*/
|
||||
const requireOwnerOrStaff = () => (req, res, next) => {
|
||||
if (!req.user) return R.error(res, 'Authentication required.', 401);
|
||||
const targetId = Number(req.params.user_id || req.params.id);
|
||||
const isOwner = req.user.user_id === targetId;
|
||||
const elevated = ['staff', 'admin'].includes(req.user.acc_type);
|
||||
if (!isOwner && !elevated)
|
||||
return R.error(res, 'You do not have permission.', 403);
|
||||
next();
|
||||
};
|
||||
|
||||
/**
|
||||
* Allows resource owner OR admin only.
|
||||
*/
|
||||
const requireOwnerOrAdmin = () => (req, res, next) => {
|
||||
if (!req.user) return R.error(res, 'Authentication required.', 401);
|
||||
const targetId = Number(req.params.user_id || req.params.id);
|
||||
const isOwner = req.user.user_id === targetId;
|
||||
const isAdmin = req.user.acc_type === 'admin';
|
||||
if (!isOwner && !isAdmin)
|
||||
return R.error(res, 'You do not have permission.', 403);
|
||||
next();
|
||||
};
|
||||
|
||||
module.exports = { requireClient, requireStaff, requireAdmin, requireOwnerOrStaff, requireOwnerOrAdmin };
|
||||
@@ -0,0 +1,25 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* File Name: validate.middleware.js
|
||||
* Type of Program: Middleware
|
||||
* Description: express-validator result checker.
|
||||
* Pairs with validators/*.validator.js — those files define the rules,
|
||||
* this file intercepts the request if any rule failed and returns 422.
|
||||
* Author: rgrgogu
|
||||
* Date Created: Oct. 6, 2025
|
||||
***********************************************************************************************************************************************************************
|
||||
* HOW TO USE:
|
||||
* const { registerValidator } = require('../../validators/auth.validator');
|
||||
* const { validate } = require('../../middleware/validate.middleware');
|
||||
* router.post('/register', ...registerValidator, validate, handler);
|
||||
***********************************************************************************************************************************************************************/
|
||||
const { validationResult } = require('express-validator');
|
||||
const R = require('../utils/response.util');
|
||||
|
||||
const validate = (req, res, next) => {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty())
|
||||
return R.validationError(res, errors.array());
|
||||
next();
|
||||
};
|
||||
|
||||
module.exports = { validate };
|
||||
Reference in New Issue
Block a user