Refactor authentication handling and improve API client security
- Updated OAuth endpoints for Challonge and Discord in platforms configuration. - Implemented session and CSRF cookie initialization in main application entry. - Enhanced Challonge API client to avoid sending sensitive API keys from the browser. - Modified tournament querying to handle new state definitions and improved error handling. - Updated UI components to reflect server-side storage of authentication tokens. - Improved user experience in API Key Manager and Authentication Hub with clearer messaging. - Refactored client credentials management to support asynchronous operations. - Adjusted API client tests to validate new request configurations. - Updated Vite configuration to support session and CSRF handling through proxies.
This commit is contained in:
68
code/websites/pokedex.online/server/middleware/csrf.js
Normal file
68
code/websites/pokedex.online/server/middleware/csrf.js
Normal file
@@ -0,0 +1,68 @@
|
||||
import { COOKIE_NAMES } from '../utils/cookie-options.js';
|
||||
|
||||
const SAFE_METHODS = new Set(['GET', 'HEAD', 'OPTIONS']);
|
||||
|
||||
function getCookieValuesFromHeader(cookieHeader, name) {
|
||||
if (!cookieHeader || typeof cookieHeader !== 'string') return [];
|
||||
const values = [];
|
||||
const pattern = new RegExp(`(?:^|;\\s*)${name}=([^;]*)`, 'g');
|
||||
let match;
|
||||
while ((match = pattern.exec(cookieHeader)) !== null) {
|
||||
values.push(match[1]);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
export function csrfMiddleware(options = {}) {
|
||||
const {
|
||||
cookieName = COOKIE_NAMES.csrf,
|
||||
headerName = 'x-csrf-token',
|
||||
requireOriginCheck = false,
|
||||
allowedOrigin = null
|
||||
} = options;
|
||||
|
||||
return function csrf(req, res, next) {
|
||||
if (SAFE_METHODS.has(req.method)) return next();
|
||||
|
||||
// Optional origin check hardening (recommended in production)
|
||||
if (requireOriginCheck && allowedOrigin) {
|
||||
const origin = req.headers.origin;
|
||||
const referer = req.headers.referer;
|
||||
const ok =
|
||||
(origin && origin === allowedOrigin) ||
|
||||
(!origin && referer && referer.startsWith(allowedOrigin));
|
||||
|
||||
if (!ok) {
|
||||
return res.status(403).json({
|
||||
error: 'CSRF origin check failed',
|
||||
code: 'CSRF_ORIGIN_FAILED'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const csrfCookie = req.cookies?.[cookieName];
|
||||
const csrfHeader = req.headers[headerName];
|
||||
|
||||
// Handle duplicate cookies with the same name (e.g. legacy '/api' path plus
|
||||
// current '/' path). cookie-parser will pick one value, but the browser may
|
||||
// send both. Accept if the header matches ANY provided cookie value.
|
||||
const rawHeader = req.headers?.cookie || '';
|
||||
const rawValues = getCookieValuesFromHeader(rawHeader, cookieName).map(v => {
|
||||
try {
|
||||
return decodeURIComponent(v);
|
||||
} catch {
|
||||
return v;
|
||||
}
|
||||
});
|
||||
const anyMatch = csrfHeader && rawValues.includes(csrfHeader);
|
||||
|
||||
if (!csrfHeader || (!csrfCookie && !anyMatch) || (csrfCookie !== csrfHeader && !anyMatch)) {
|
||||
return res.status(403).json({
|
||||
error: 'CSRF validation failed',
|
||||
code: 'CSRF_FAILED'
|
||||
});
|
||||
}
|
||||
|
||||
return next();
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user