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:
77
code/websites/pokedex.online/server/utils/cookie-options.js
Normal file
77
code/websites/pokedex.online/server/utils/cookie-options.js
Normal file
@@ -0,0 +1,77 @@
|
||||
import crypto from 'node:crypto';
|
||||
|
||||
const ONE_DAY_SECONDS = 60 * 60 * 24;
|
||||
const SEVEN_DAYS_SECONDS = ONE_DAY_SECONDS * 7;
|
||||
|
||||
export const COOKIE_NAMES = {
|
||||
sid: 'pdx_sid',
|
||||
csrf: 'pdx_csrf'
|
||||
};
|
||||
|
||||
export function getCookieSecurityConfig(config) {
|
||||
const deploymentTarget = config?.deploymentTarget || process.env.DEPLOYMENT_TARGET;
|
||||
const nodeEnv = config?.nodeEnv || process.env.NODE_ENV;
|
||||
|
||||
const isProdTarget = deploymentTarget === 'production' || nodeEnv === 'production';
|
||||
|
||||
return {
|
||||
secure: isProdTarget,
|
||||
sameSite: 'lax'
|
||||
};
|
||||
}
|
||||
|
||||
export function getSidCookieOptions(config) {
|
||||
const { secure, sameSite } = getCookieSecurityConfig(config);
|
||||
|
||||
return {
|
||||
httpOnly: true,
|
||||
secure,
|
||||
sameSite,
|
||||
path: '/',
|
||||
maxAge: SEVEN_DAYS_SECONDS * 1000
|
||||
};
|
||||
}
|
||||
|
||||
// Legacy cookie options used before widening cookie scope to '/'.
|
||||
// Clearing these prevents browsers from sending multiple cookies with the same
|
||||
// name but different paths (e.g. '/api' and '/'), which can cause session
|
||||
// split-brain.
|
||||
export function getLegacySidCookieOptions(config) {
|
||||
const { secure, sameSite } = getCookieSecurityConfig(config);
|
||||
|
||||
return {
|
||||
httpOnly: true,
|
||||
secure,
|
||||
sameSite,
|
||||
path: '/api',
|
||||
maxAge: SEVEN_DAYS_SECONDS * 1000
|
||||
};
|
||||
}
|
||||
|
||||
export function getCsrfCookieOptions(config) {
|
||||
const { secure, sameSite } = getCookieSecurityConfig(config);
|
||||
|
||||
return {
|
||||
httpOnly: false,
|
||||
secure,
|
||||
sameSite,
|
||||
path: '/',
|
||||
maxAge: ONE_DAY_SECONDS * 1000
|
||||
};
|
||||
}
|
||||
|
||||
export function getLegacyCsrfCookieOptions(config) {
|
||||
const { secure, sameSite } = getCookieSecurityConfig(config);
|
||||
|
||||
return {
|
||||
httpOnly: false,
|
||||
secure,
|
||||
sameSite,
|
||||
path: '/api',
|
||||
maxAge: ONE_DAY_SECONDS * 1000
|
||||
};
|
||||
}
|
||||
|
||||
export function generateToken(bytes = 24) {
|
||||
return crypto.randomBytes(bytes).toString('base64url');
|
||||
}
|
||||
@@ -73,6 +73,36 @@ const REQUIRED_ENV_VARS = {
|
||||
: null
|
||||
},
|
||||
|
||||
// Token encryption key (required for server-side OAuth token storage in production)
|
||||
OAUTH_TOKEN_ENC_KEY: {
|
||||
required: false,
|
||||
description:
|
||||
'Base64-encoded 32-byte key for encrypting OAuth tokens at rest (AES-256-GCM)',
|
||||
validate: (val, env) => {
|
||||
const target = env?.DEPLOYMENT_TARGET;
|
||||
if (target !== 'production') return true;
|
||||
if (!val) {
|
||||
console.error(
|
||||
'❌ OAUTH_TOKEN_ENC_KEY is required in production to encrypt OAuth tokens'
|
||||
);
|
||||
return false;
|
||||
}
|
||||
// Best-effort validation: base64 decode should yield 32 bytes
|
||||
try {
|
||||
const buf = Buffer.from(val, 'base64');
|
||||
return buf.length === 32;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Admin auth
|
||||
ADMIN_PASSWORD: {
|
||||
required: false,
|
||||
description: 'Admin password for /auth/login (recommended for production)'
|
||||
},
|
||||
|
||||
// Challonge OAuth (optional)
|
||||
CHALLONGE_CLIENT_ID: {
|
||||
required: false,
|
||||
@@ -217,6 +247,10 @@ export function getConfig() {
|
||||
secret: process.env.SESSION_SECRET || 'dev-secret-change-in-production'
|
||||
},
|
||||
|
||||
// Admin auth (JWT secret uses session secret for now)
|
||||
secret: process.env.SESSION_SECRET || 'dev-secret-change-in-production',
|
||||
adminPassword: process.env.ADMIN_PASSWORD,
|
||||
|
||||
// Discord User Permissions
|
||||
discord: {
|
||||
adminUsers: process.env.DISCORD_ADMIN_USERS
|
||||
|
||||
Reference in New Issue
Block a user