|
@@ -151,12 +151,22 @@ export async function verifyResourceSession(
|
|
}
|
|
}
|
|
|
|
|
|
// check the rules
|
|
// 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)}`;
|
|
const redirectUrl = `${config.getRawConfig().app.dashboard_url}/auth/resource/${encodeURIComponent(resource.resourceId)}?redirect=${encodeURIComponent(originalRequestURL)}`;
|
|
@@ -456,7 +466,7 @@ async function checkRules(
|
|
resourceId: number,
|
|
resourceId: number,
|
|
clientIp: string | undefined,
|
|
clientIp: string | undefined,
|
|
path: string | undefined
|
|
path: string | undefined
|
|
-): Promise<boolean> {
|
|
|
|
|
|
+): Promise<"ACCEPT" | "DROP" | undefined> {
|
|
const ruleCacheKey = `rules:${resourceId}`;
|
|
const ruleCacheKey = `rules:${resourceId}`;
|
|
|
|
|
|
let rules: ResourceRule[] | undefined = cache.get(ruleCacheKey);
|
|
let rules: ResourceRule[] | undefined = cache.get(ruleCacheKey);
|
|
@@ -472,36 +482,40 @@ async function checkRules(
|
|
|
|
|
|
if (rules.length === 0) {
|
|
if (rules.length === 0) {
|
|
logger.debug("No rules found for resource", resourceId);
|
|
logger.debug("No rules found for resource", resourceId);
|
|
- return false;
|
|
|
|
|
|
+ return;
|
|
}
|
|
}
|
|
|
|
|
|
for (const rule of rules) {
|
|
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") {
|
|
} else if (path && rule.match == "PATH") {
|
|
// rule.value is a regex, match on the path and see if it matches
|
|
// rule.value is a regex, match on the path and see if it matches
|
|
const re = urlGlobToRegex(rule.value);
|
|
const re = urlGlobToRegex(rule.value);
|
|
if (re.test(path)) {
|
|
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)
|
|
// 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 *
|
|
// 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
|
|
// 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:
|
|
// Create the final pattern that:
|
|
// 1. Optionally matches leading slash
|
|
// 1. Optionally matches leading slash
|
|
// 2. Matches the entire string
|
|
// 2. Matches the entire string
|
|
return new RegExp(`^/?${regexPattern}$`);
|
|
return new RegExp(`^/?${regexPattern}$`);
|
|
- }
|
|
|
|
|
|
+}
|