Compare commits

...

2 Commits

Author SHA1 Message Date
Owen f079714caf Dont redirect when the browser agent is not real 2026-08-04 10:07:52 -04:00
Owen efd2792197 bump default rate limit 2026-08-03 17:57:36 -04:00
2 changed files with 53 additions and 5 deletions
+2 -2
View File
@@ -266,13 +266,13 @@ export const configSchema = z
.positive() .positive()
.gt(0) .gt(0)
.optional() .optional()
.default(10), .default(30),
burst: z burst: z
.number() .number()
.positive() .positive()
.gt(0) .gt(0)
.optional() .optional()
.default(16) .default(50)
}) })
.optional() .optional()
.prefault({}) .prefault({})
+51 -3
View File
@@ -127,6 +127,9 @@ export async function verifyResourceSession(
// Extract HTTP Basic Auth credentials if present // Extract HTTP Basic Auth credentials if present
const clientHeaderAuth = extractBasicAuth(headers); const clientHeaderAuth = extractBasicAuth(headers);
const clientUserAgent = headers?.["user-agent"] || headers?.["User-Agent"];
const clientIsBrowser = isBrowserUserAgent(clientUserAgent);
const clientIp = requestIp const clientIp = requestIp
? stripPortFromHost(requestIp, badgerVersion) ? stripPortFromHost(requestIp, badgerVersion)
: undefined; : undefined;
@@ -313,9 +316,14 @@ export async function verifyResourceSession(
return allowed(res, undefined, dontStripSession); return allowed(res, undefined, dontStripSession);
} }
const redirectPath = `/auth/resource/${encodeURIComponent( // Only offer a browser redirect to clients that can actually follow one and log in
resource.resourceGuid // (an interactive browser). Non-browser clients (curl, scripts, bots, etc.) just get
)}?redirect=${encodeURIComponent(originalRequestURL)}`; // an unauthorized response from Badger instead of a login redirect URL.
const redirectPath = clientIsBrowser
? `/auth/resource/${encodeURIComponent(
resource.resourceGuid
)}?redirect=${encodeURIComponent(originalRequestURL)}`
: undefined;
// check for access token in headers // check for access token in headers
if ( if (
@@ -1476,6 +1484,46 @@ async function getCountryCodeFromIp(ip: string): Promise<string | undefined> {
return cachedCountryCode; return cachedCountryCode;
} }
// Permissive by default: only reject known non-browser clients or a missing
// User-Agent (real browsers always send one). This avoids blocking real
// browsers whose UA string doesn't match a hardcoded allow-list.
const NON_BROWSER_USER_AGENT_PATTERNS = [
/curl/,
/wget/,
/python-requests/,
/python-urllib/,
/go-http-client/,
/okhttp/,
/axios/,
/node-fetch/,
/postmanruntime/,
/insomnia/,
/libwww-perl/,
/java\//,
/ruby/,
/php/,
/bot/,
/spider/,
/crawler/,
/headlesschrome/,
/phantomjs/,
/httpclient/,
/prometheus/,
/go-resty/,
/apache-httpclient/,
/scrapy/
];
function isBrowserUserAgent(userAgent: string | undefined): boolean {
if (!userAgent) {
return false;
}
const ua = userAgent.toLowerCase();
return !NON_BROWSER_USER_AGENT_PATTERNS.some((pattern) => pattern.test(ua));
}
function extractBasicAuth( function extractBasicAuth(
headers: Record<string, string> | undefined headers: Record<string, string> | undefined
): string | undefined { ): string | undefined {