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:
2026-02-03 12:50:11 -05:00
parent 161b758a1b
commit 700c1cbbbe
39 changed files with 2434 additions and 999 deletions

View 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();
};
}