diff --git a/server/routers/badger/verifySession.ts b/server/routers/badger/verifySession.ts index 39415023..8e120dbf 100644 --- a/server/routers/badger/verifySession.ts +++ b/server/routers/badger/verifySession.ts @@ -151,12 +151,22 @@ export async function verifyResourceSession( } // check the rules - if ( - resource.applyRules && - (await checkRules(resource.resourceId, clientIp, path)) - ) { - logger.debug("Resource allowed because rules are satisfied"); - return allowed(res); + if (resource.applyRules) { + const action = await checkRules( + resource.resourceId, + clientIp, + path + ); + + if (action == "ACCEPT") { + logger.debug("Resource allowed by rule"); + return allowed(res); + } else if (action == "DROP") { + logger.debug("Resource denied by rule"); + return notAllowed(res); + } + + // otherwise its undefined and we pass } const redirectUrl = `${config.getRawConfig().app.dashboard_url}/auth/resource/${encodeURIComponent(resource.resourceId)}?redirect=${encodeURIComponent(originalRequestURL)}`; @@ -456,7 +466,7 @@ async function checkRules( resourceId: number, clientIp: string | undefined, path: string | undefined -): Promise { +): Promise<"ACCEPT" | "DROP" | undefined> { const ruleCacheKey = `rules:${resourceId}`; let rules: ResourceRule[] | undefined = cache.get(ruleCacheKey); @@ -472,36 +482,40 @@ async function checkRules( if (rules.length === 0) { logger.debug("No rules found for resource", resourceId); - return false; + return; } for (const rule of rules) { - if (clientIp && rule.match == "IP" && isIpInCidr(clientIp, rule.value)) { - return rule.action == "ACCEPT"; + if ( + clientIp && + rule.match == "IP" && + isIpInCidr(clientIp, rule.value) + ) { + return rule.action as "ACCEPT" | "DROP"; } else if (path && rule.match == "PATH") { // rule.value is a regex, match on the path and see if it matches const re = urlGlobToRegex(rule.value); if (re.test(path)) { - return rule.action == "ACCEPT"; + return rule.action as "ACCEPT" | "DROP"; } } } - return false; + return; } -function urlGlobToRegex(pattern: string): RegExp { +function urlGlobToRegex(pattern: string): RegExp { // Remove leading slash if present (we'll add it to the regex pattern) - pattern = pattern.startsWith('/') ? pattern.slice(1) : pattern; - + pattern = pattern.startsWith("/") ? pattern.slice(1) : pattern; + // Escape special regex characters except * - const escapedPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&'); - + const escapedPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&"); + // Replace * with regex pattern for any valid URL segment characters - const regexPattern = escapedPattern.replace(/\*/g, '[a-zA-Z0-9_-]+'); - + const regexPattern = escapedPattern.replace(/\*/g, "[a-zA-Z0-9_-]+"); + // Create the final pattern that: // 1. Optionally matches leading slash // 2. Matches the entire string return new RegExp(`^/?${regexPattern}$`); - } \ No newline at end of file +}