Migrate From Bun to Express

This commit is contained in:
Caileb 2025-05-27 16:00:15 -05:00
parent b525cc0dd0
commit d2c014e744
8 changed files with 3054 additions and 668 deletions

View file

@ -658,113 +658,164 @@ async function handleTokenRedirect(request) {
}
function CheckpointMiddleware() {
return async (request) => {
// Check if checkpoint is enabled
if (checkpointConfig.Enabled === false) {
return undefined;
}
const urlObj = new URL(request.url);
const host = request.headers.get('host')?.split(':')[0];
const userAgent = request.headers.get('user-agent') || '';
// 1) Bypass via query keys
for (const { Key, Value, Domains } of checkpointConfig.BypassQueryKeys) {
if (urlObj.searchParams.get(Key) === Value) {
if (!Array.isArray(Domains) || Domains.length === 0 || Domains.includes(host)) {
return undefined;
}
// Return Express-compatible middleware
return {
middleware: async (req, res, next) => {
// Check if checkpoint is enabled
if (checkpointConfig.Enabled === false) {
return next();
}
}
// 2) Bypass via header keys
for (const { Name, Value, Domains } of checkpointConfig.BypassHeaderKeys) {
const headerVal = request.headers.get(Name);
if (headerVal === Value) {
if (!Array.isArray(Domains) || Domains.length === 0 || Domains.includes(host)) {
return undefined;
}
}
}
// Convert Express request to the format expected by checkpoint logic
const request = {
url: `${req.protocol}://${req.get('host')}${req.originalUrl}`,
method: req.method,
headers: {
get: (name) => req.get(name),
entries: () => Object.entries(req.headers).map(([k, v]) => [k, Array.isArray(v) ? v.join(', ') : v])
},
json: () => new Promise((resolve, reject) => {
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', () => {
try {
resolve(JSON.parse(body));
} catch (e) {
reject(e);
}
});
req.on('error', reject);
})
};
// Handle token redirect for URL-token login
const tokenResponse = await handleTokenRedirect(request);
if (tokenResponse) return tokenResponse;
const urlObj = new URL(request.url);
const host = request.headers.get('host')?.split(':')[0];
const userAgent = request.headers.get('user-agent') || '';
// Setup request context
const url = new URL(request.url);
let path = url.pathname;
if (checkpointConfig.SanitizeURLs) {
path = sanitizePath(path);
}
const method = request.method;
// Always allow challenge & verify endpoints
if (method === 'GET' && path === '/api/challenge') {
return handleGetCheckpointChallenge(request);
}
if (method === 'POST' && path === '/api/verify') {
return handleVerifyCheckpoint(request);
}
// Check new exclusion rules
if (checkpointConfig.ExclusionRules && checkpointConfig.ExclusionRules.length > 0) {
for (const rule of checkpointConfig.ExclusionRules) {
// Check if path matches
if (!rule.Path || !path.startsWith(rule.Path)) {
continue;
}
// Check if host matches (if specified)
if (rule.Hosts && rule.Hosts.length > 0 && !rule.Hosts.includes(host)) {
continue;
}
// Check if user agent matches (if specified)
if (rule.UserAgents && rule.UserAgents.length > 0) {
const matchesUA = rule.UserAgents.some((ua) => userAgent.includes(ua));
if (!matchesUA) {
continue;
// 1) Bypass via query keys
for (const { Key, Value, Domains } of checkpointConfig.BypassQueryKeys) {
if (urlObj.searchParams.get(Key) === Value) {
if (!Array.isArray(Domains) || Domains.length === 0 || Domains.includes(host)) {
return next();
}
}
// All conditions match - exclude this request
return undefined;
}
}
// Check file extensions
const ext = path.includes('.') ? path.slice(path.lastIndexOf('.')) : '';
// First check excluded extensions
if (ext && checkpointConfig.HTMLCheckpointExcludedExtensions.includes(ext)) {
return undefined;
}
// Then check if we should only include specific extensions
if (checkpointConfig.HTMLCheckpointIncludedExtensions.length > 0) {
// If extension list is specified and current extension is not in it, skip
if (!checkpointConfig.HTMLCheckpointIncludedExtensions.includes(ext)) {
return undefined;
// 2) Bypass via header keys
for (const { Name, Value, Domains } of checkpointConfig.BypassHeaderKeys) {
const headerVal = request.headers.get(Name);
if (headerVal === Value) {
if (!Array.isArray(Domains) || Domains.length === 0 || Domains.includes(host)) {
return next();
}
}
}
// Handle token redirect for URL-token login
const tokenResponse = await handleTokenRedirect(request);
if (tokenResponse) {
// Convert Response to Express response
res.status(tokenResponse.status);
tokenResponse.headers.forEach((value, key) => {
res.setHeader(key, value);
});
const body = await tokenResponse.text();
return res.send(body);
}
// Setup request context
const url = new URL(request.url);
let path = url.pathname;
if (checkpointConfig.SanitizeURLs) {
path = sanitizePath(path);
}
const method = request.method;
// Always allow challenge & verify endpoints
if (method === 'GET' && path === '/api/challenge') {
const response = await handleGetCheckpointChallenge(request);
res.status(response.status);
response.headers.forEach((value, key) => {
res.setHeader(key, value);
});
const body = await response.text();
return res.send(body);
}
if (method === 'POST' && path === '/api/verify') {
const response = await handleVerifyCheckpoint(request);
res.status(response.status);
response.headers.forEach((value, key) => {
res.setHeader(key, value);
});
const body = await response.text();
return res.send(body);
}
// Check new exclusion rules
if (checkpointConfig.ExclusionRules && checkpointConfig.ExclusionRules.length > 0) {
for (const rule of checkpointConfig.ExclusionRules) {
// Check if path matches
if (!rule.Path || !path.startsWith(rule.Path)) {
continue;
}
// Check if host matches (if specified)
if (rule.Hosts && rule.Hosts.length > 0 && !rule.Hosts.includes(host)) {
continue;
}
// Check if user agent matches (if specified)
if (rule.UserAgents && rule.UserAgents.length > 0) {
const matchesUA = rule.UserAgents.some((ua) => userAgent.includes(ua));
if (!matchesUA) {
continue;
}
}
// All conditions match - exclude this request
return next();
}
}
// Check file extensions
const ext = path.includes('.') ? path.slice(path.lastIndexOf('.')) : '';
// First check excluded extensions
if (ext && checkpointConfig.HTMLCheckpointExcludedExtensions.includes(ext)) {
return next();
}
// Then check if we should only include specific extensions
if (checkpointConfig.HTMLCheckpointIncludedExtensions.length > 0) {
// If extension list is specified and current extension is not in it, skip
if (!checkpointConfig.HTMLCheckpointIncludedExtensions.includes(ext)) {
return next();
}
}
// Validate session token
const cookies = cookie.parse(request.headers.get('cookie') || '');
const tokenCookie = cookies[checkpointConfig.CookieName];
const validation = await validateToken(tokenCookie, request);
if (validation) {
// Active session: bypass checkpoint
return next();
}
// Log new checkpoint flow
console.log(`checkpoint: incoming ${method} ${request.url}`);
console.log(`checkpoint: tokenCookie=${tokenCookie}`);
console.log(`checkpoint: validateToken => ${validation}`);
// Serve interstitial challenge
const response = await serveInterstitial(request);
res.status(response.status);
response.headers.forEach((value, key) => {
res.setHeader(key, value);
});
const body = await response.text();
return res.send(body);
}
// Validate session token
const cookies = cookie.parse(request.headers.get('cookie') || '');
const tokenCookie = cookies[checkpointConfig.CookieName];
const validation = await validateToken(tokenCookie, request);
if (validation) {
// Active session: bypass checkpoint
return undefined;
}
// Log new checkpoint flow
console.log(`checkpoint: incoming ${method} ${request.url}`);
console.log(`checkpoint: tokenCookie=${tokenCookie}`);
console.log(`checkpoint: validateToken => ${validation}`);
// Serve interstitial challenge
return serveInterstitial(request);
};
}