mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
64 lines
2.9 KiB
JavaScript
64 lines
2.9 KiB
JavaScript
/***********************************************************************************************************************************************************************
|
|
* 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.
|
|
* sameSite:'none' (not 'strict') in production — frontend (Vercel) and this
|
|
* API (Render) are cross-site, so 'strict' drops the cookie on every
|
|
* fetch/XHR call. 'none' requires secure:true, set alongside it below.
|
|
*/
|
|
const csrfProtection = csurf({
|
|
cookie: {
|
|
httpOnly: true,
|
|
secure: process.env.NODE_ENV === 'production',
|
|
sameSite: process.env.NODE_ENV === 'production' ? 'none' : 'lax',
|
|
},
|
|
});
|
|
|
|
/**
|
|
* 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 }; |