diff --git a/server/db/schema.ts b/server/db/schema.ts index 2ae27497..7c8c2739 100644 --- a/server/db/schema.ts +++ b/server/db/schema.ts @@ -414,3 +414,4 @@ export type ResourceOtp = InferSelectModel; export type ResourceAccessToken = InferSelectModel; export type ResourceWhitelist = InferSelectModel; export type VersionMigration = InferSelectModel; +export type BadgerRule = InferSelectModel; \ No newline at end of file diff --git a/server/routers/badger/verifySession.ts b/server/routers/badger/verifySession.ts index 5830d805..a40cfd21 100644 --- a/server/routers/badger/verifySession.ts +++ b/server/routers/badger/verifySession.ts @@ -6,6 +6,8 @@ import { fromError } from "zod-validation-error"; import { response } from "@server/lib/response"; import db from "@server/db"; import { + BadgerRule, + badgerRules, ResourceAccessToken, ResourcePassword, resourcePassword, @@ -28,6 +30,7 @@ import logger from "@server/logger"; import { verifyResourceAccessToken } from "@server/auth/verifyResourceAccessToken"; import NodeCache from "node-cache"; import { generateSessionToken } from "@server/auth/sessions/app"; +import { isIpInCidr } from "@server/lib/ip"; // We'll see if this speeds anything up const cache = new NodeCache({ @@ -79,6 +82,7 @@ export async function verifyResourceSession( host, originalRequestURL, requestIp, + path, accessToken: token } = parsedBody.data; @@ -146,6 +150,15 @@ export async function verifyResourceSession( return allowed(res); } + // check the rules + if ( + resource.applyRules && + (await checkRules(resource.resourceId, clientIp, path)) + ) { + logger.debug("Resource allowed because rules are satisfied"); + return allowed(res); + } + const redirectUrl = `${config.getRawConfig().app.dashboard_url}/auth/resource/${encodeURIComponent(resource.resourceId)}?redirect=${encodeURIComponent(originalRequestURL)}`; // check for access token @@ -438,3 +451,41 @@ async function isUserAllowedToAccessResource( return false; } + +async function checkRules( + resourceId: number, + clientIp: string | undefined, + path: string | undefined +): Promise { + const ruleCacheKey = `rules:${resourceId}`; + + let rules: BadgerRule[] | undefined = cache.get(ruleCacheKey); + + if (!rules) { + rules = await db + .select() + .from(badgerRules) + .where(eq(badgerRules.resourceId, resourceId)); + + cache.set(ruleCacheKey, rules); + } + + if (rules.length === 0) { + logger.debug("No rules found for resource", resourceId); + return false; + } + + for (const rule of rules) { + if (clientIp && rule.match == "IP" && isIpInCidr(clientIp, rule.value)) { + return rule.action == "ACCEPT"; + } else if (path && rule.match == "PATH") { + // rule.value is a regex, match on the path and see if it matches + const re = new RegExp(rule.value); + if (re.test(path)) { + return rule.action == "ACCEPT"; + } + } + } + + return false; +}