- 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.
69 lines
2.1 KiB
JavaScript
69 lines
2.1 KiB
JavaScript
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();
|
|
};
|
|
}
|