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:
96
code/websites/pokedex.online/server/middleware/sid.js
Normal file
96
code/websites/pokedex.online/server/middleware/sid.js
Normal file
@@ -0,0 +1,96 @@
|
||||
import crypto from 'node:crypto';
|
||||
import {
|
||||
COOKIE_NAMES,
|
||||
generateToken,
|
||||
getLegacyCsrfCookieOptions,
|
||||
getLegacySidCookieOptions,
|
||||
getSidCookieOptions
|
||||
} from '../utils/cookie-options.js';
|
||||
|
||||
function signSid(sessionSecret, sid) {
|
||||
return crypto
|
||||
.createHmac('sha256', sessionSecret)
|
||||
.update(sid)
|
||||
.digest('base64url');
|
||||
}
|
||||
|
||||
function parseAndVerifySignedSid(sessionSecret, signedValue) {
|
||||
if (!signedValue || typeof signedValue !== 'string') return null;
|
||||
const idx = signedValue.lastIndexOf('.');
|
||||
if (idx <= 0) return null;
|
||||
|
||||
const sid = signedValue.slice(0, idx);
|
||||
const sig = signedValue.slice(idx + 1);
|
||||
if (!sid || !sig) return null;
|
||||
|
||||
const expected = signSid(sessionSecret, sid);
|
||||
try {
|
||||
if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
|
||||
return null;
|
||||
}
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
return sid;
|
||||
}
|
||||
|
||||
function getCookieValuesFromHeader(cookieHeader, name) {
|
||||
if (!cookieHeader || typeof cookieHeader !== 'string') return [];
|
||||
|
||||
// Multiple cookies with the same name can exist if older cookies were scoped
|
||||
// to a different path (e.g. '/api') than newer ones ('/').
|
||||
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 sidMiddleware({ sessionSecret, config }) {
|
||||
if (!sessionSecret) {
|
||||
throw new Error('sidMiddleware requires sessionSecret');
|
||||
}
|
||||
|
||||
return function sid(req, res, next) {
|
||||
// If older cookies (scoped to '/api') exist alongside newer cookies
|
||||
// (scoped to '/'), browsers may send both. Some parsers will then pick the
|
||||
// "wrong" one depending on header order, causing auth to appear connected
|
||||
// in one request and missing in another.
|
||||
const rawCookieHeader = req.headers?.cookie || '';
|
||||
if (rawCookieHeader.includes(`${COOKIE_NAMES.sid}=`)) {
|
||||
res.clearCookie(COOKIE_NAMES.sid, getLegacySidCookieOptions(config));
|
||||
}
|
||||
if (rawCookieHeader.includes(`${COOKIE_NAMES.csrf}=`)) {
|
||||
res.clearCookie(COOKIE_NAMES.csrf, getLegacyCsrfCookieOptions(config));
|
||||
}
|
||||
|
||||
const signedCandidates = getCookieValuesFromHeader(
|
||||
rawCookieHeader,
|
||||
COOKIE_NAMES.sid
|
||||
);
|
||||
const signedFromParser = req.cookies?.[COOKIE_NAMES.sid];
|
||||
if (signedFromParser) signedCandidates.push(signedFromParser);
|
||||
|
||||
// If multiple signed SIDs are present (legacy '/api' cookie + current '/'),
|
||||
// browsers tend to send the more-specific path cookie first.
|
||||
// Prefer the last valid SID to bias towards the newer '/' cookie.
|
||||
let sid = null;
|
||||
for (let i = signedCandidates.length - 1; i >= 0; i -= 1) {
|
||||
const signed = signedCandidates[i];
|
||||
sid = parseAndVerifySignedSid(sessionSecret, signed);
|
||||
if (sid) break;
|
||||
}
|
||||
|
||||
if (!sid) {
|
||||
sid = generateToken(24);
|
||||
const signedSid = `${sid}.${signSid(sessionSecret, sid)}`;
|
||||
res.cookie(COOKIE_NAMES.sid, signedSid, getSidCookieOptions(config));
|
||||
}
|
||||
|
||||
req.sid = sid;
|
||||
next();
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user