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

@@ -50,14 +50,10 @@ export const ScopeType = {
* @returns {Object} API client with methods
*/
export function createChallongeV2Client(auth, options = {}) {
const { token, type = AuthType.API_KEY } = auth;
const { token, type = AuthType.API_KEY } = auth || {};
const { communityId: defaultCommunityId, debug = false } = options;
const baseURL = getBaseURL();
if (!token) {
throw new Error('Authentication token is required');
}
// Request tracking for debug mode
let requestCount = 0;
@@ -109,16 +105,15 @@ export function createChallongeV2Client(auth, options = {}) {
...headers
};
// Add authorization header
if (type === AuthType.OAUTH) {
requestHeaders['Authorization'] = `Bearer ${token}`;
} else {
requestHeaders['Authorization'] = token;
}
// No-split-brain: never send Challonge tokens from the browser.
// Backend proxy derives auth from the per-session SID cookie and the Authorization-Type hint.
// (Token is intentionally ignored here.)
const fetchOptions = {
method,
headers: requestHeaders
headers: requestHeaders,
credentials: 'include',
cache: 'no-store'
};
if (body && method !== 'GET') {
@@ -149,16 +144,29 @@ export function createChallongeV2Client(auth, options = {}) {
return null;
}
// Parse response body (prefer JSON when declared)
const contentType = response.headers.get('content-type') || '';
let data;
try {
data = await response.json();
if (contentType.includes('application/json')) {
data = await response.json();
} else {
const text = await response.text();
// Best-effort: if it's actually JSON but wrong content-type, parse it.
data = text;
if (text && (text.startsWith('{') || text.startsWith('['))) {
try {
data = JSON.parse(text);
} catch {
// keep as text
}
}
}
} catch (parseError) {
// If JSON parsing fails, create an error with the status
if (debug)
if (debug) {
console.error('[Challonge v2.1 JSON Parse Error]', parseError);
const error = new Error(
`HTTP ${response.status}: Failed to parse response`
);
}
const error = new Error(`HTTP ${response.status}: Failed to parse response`);
error.status = response.status;
throw error;
}
@@ -186,14 +194,24 @@ export function createChallongeV2Client(auth, options = {}) {
.join('\n');
const error = new Error(errorMessage);
error.status = response.status;
error.errors = errorDetails;
error.response = data;
throw error;
}
// Handle non-JSON:API error format
const error = new Error(
`HTTP ${response.status}: ${data.message || response.statusText}`
);
const messageFromBody =
typeof data === 'string'
? data
: data?.error || data?.message || response.statusText;
const fallbackMessage = response.statusText || 'Request failed';
const finalMessage =
typeof messageFromBody === 'string' && messageFromBody.trim().length === 0
? fallbackMessage
: messageFromBody || fallbackMessage;
const error = new Error(`HTTP ${response.status}: ${finalMessage}`);
error.status = response.status;
error.response = data;
throw error;