fixes mixed ipv4-ipv6 handling in cidr module
This commit is contained in:
parent
7d517a4c5f
commit
6352df5d5a
|
@ -314,10 +314,10 @@ router.post("/maintenance", (req, res) => {
|
||||||
const temps = users.filter((u) => u.type === "temporary");
|
const temps = users.filter((u) => u.type === "temporary");
|
||||||
temps.forEach((user) => {
|
temps.forEach((user) => {
|
||||||
user.expiresAt = Date.now();
|
user.expiresAt = Date.now();
|
||||||
user.disabledReason = "Admin forced expiration."
|
user.disabledReason = "Admin forced expiration.";
|
||||||
userStore.upsertUser(user);
|
userStore.upsertUser(user);
|
||||||
});
|
});
|
||||||
invalidatePowHmacKey()
|
invalidatePowHmacKey();
|
||||||
flash.type = "success";
|
flash.type = "success";
|
||||||
flash.message = `${temps.length} temporary users marked for expiration.`;
|
flash.message = `${temps.length} temporary users marked for expiration.`;
|
||||||
break;
|
break;
|
||||||
|
@ -370,8 +370,8 @@ router.post("/maintenance", (req, res) => {
|
||||||
ipv4RangeMap.set(subnet, userSet);
|
ipv4RangeMap.set(subnet, userSet);
|
||||||
} else if (parsed.kind() === "ipv6") {
|
} else if (parsed.kind() === "ipv6") {
|
||||||
const subnet =
|
const subnet =
|
||||||
parsed.toNormalizedString().split(":").slice(0, 3).join(":") +
|
parsed.toNormalizedString().split(":").slice(0, 4).join(":") +
|
||||||
"::/56";
|
"::/48";
|
||||||
const userSet = ipv6RangeMap.get(subnet) || new Set<string>();
|
const userSet = ipv6RangeMap.get(subnet) || new Set<string>();
|
||||||
userSet.add(u.token);
|
userSet.add(u.token);
|
||||||
ipv6RangeMap.set(subnet, userSet);
|
ipv6RangeMap.set(subnet, userSet);
|
||||||
|
|
|
@ -158,7 +158,7 @@ function getSelfServiceLinks() {
|
||||||
links.unshift(["Request a user token", "/user/captcha"]);
|
links.unshift(["Request a user token", "/user/captcha"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return `<div class="self-service-links"">${links
|
return `<div class="self-service-links">${links
|
||||||
.map(([text, link]) => `<a target="_blank" href="${link}">${text}</a>`)
|
.map(([text, link]) => `<a target="_blank" href="${link}">${text}</a>`)
|
||||||
.join(" | ")}</div>`;
|
.join(" | ")}</div>`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,9 +24,9 @@ export function parseCidrs(cidrs: string[] | string): [IPv4 | IPv6, number][] {
|
||||||
.map((input) => {
|
.map((input) => {
|
||||||
try {
|
try {
|
||||||
if (input.includes("/")) {
|
if (input.includes("/")) {
|
||||||
return ipaddr.parseCIDR(input);
|
return ipaddr.parseCIDR(input.trim());
|
||||||
} else {
|
} else {
|
||||||
const ip = ipaddr.parse(input);
|
const ip = ipaddr.parse(input.trim());
|
||||||
return ipaddr.parseCIDR(
|
return ipaddr.parseCIDR(
|
||||||
`${input}/${ip.kind() === "ipv4" ? 32 : 128}`
|
`${input}/${ip.kind() === "ipv4" ? 32 : 128}`
|
||||||
);
|
);
|
||||||
|
@ -44,23 +44,25 @@ export function createWhitelistMiddleware(
|
||||||
base: string[] | string
|
base: string[] | string
|
||||||
) {
|
) {
|
||||||
let cidrs: string[] = [];
|
let cidrs: string[] = [];
|
||||||
let matchers: [IPv4 | IPv6, number][] = [];
|
let ranges: Record<string, [IPv4 | IPv6, number][]> = {};
|
||||||
|
|
||||||
const middleware = (req: Request, res: Response, next: NextFunction) => {
|
const middleware: IpCheckMiddleware = (req, res, next) => {
|
||||||
const ip = ipaddr.process(req.ip);
|
const ip = ipaddr.process(req.ip);
|
||||||
const allowed = matchers.some((cidr) => ip.match(cidr));
|
const match = ipaddr.subnetMatch(ip, ranges, "none");
|
||||||
if (allowed) {
|
if (match === name) {
|
||||||
return next();
|
return next();
|
||||||
|
} else {
|
||||||
|
req.log.warn({ ip: req.ip, list: name }, "Request denied by whitelist");
|
||||||
|
res.status(403).json({ error: `Forbidden (by ${name})` });
|
||||||
}
|
}
|
||||||
req.log.warn({ ip: req.ip, list: name }, "Request denied by whitelist");
|
|
||||||
res.status(403).json({ error: `Forbidden (by ${name})` });
|
|
||||||
};
|
};
|
||||||
middleware.ranges = [] as string[];
|
middleware.ranges = cidrs;
|
||||||
middleware.updateRanges = (ranges: string[] | string) => {
|
middleware.updateRanges = (r: string[] | string) => {
|
||||||
cidrs = Array.isArray(ranges) ? ranges.slice() : [ranges];
|
cidrs = Array.isArray(r) ? r.slice() : [r];
|
||||||
matchers = parseCidrs(cidrs);
|
const parsed = parseCidrs(cidrs);
|
||||||
log.info({ list: name, matchers }, "IP whitelist configured");
|
ranges = { [name]: parsed };
|
||||||
middleware.ranges = cidrs;
|
middleware.ranges = cidrs;
|
||||||
|
log.info({ list: name, ranges }, "IP whitelist configured");
|
||||||
};
|
};
|
||||||
|
|
||||||
middleware.updateRanges(base);
|
middleware.updateRanges(base);
|
||||||
|
@ -74,24 +76,27 @@ export function createBlacklistMiddleware(
|
||||||
base: string[] | string
|
base: string[] | string
|
||||||
) {
|
) {
|
||||||
let cidrs: string[] = [];
|
let cidrs: string[] = [];
|
||||||
let matchers: [IPv4 | IPv6, number][] = [];
|
let ranges: Record<string, [IPv4 | IPv6, number][]> = {};
|
||||||
|
|
||||||
const middleware = (req: Request, res: Response, next: NextFunction) => {
|
const middleware: IpCheckMiddleware = (req, res, next) => {
|
||||||
const ip = ipaddr.process(req.ip);
|
const ip = ipaddr.process(req.ip);
|
||||||
const denied = matchers.some((cidr) => ip.match(cidr));
|
const match = ipaddr.subnetMatch(ip, ranges, "none");
|
||||||
if (denied) {
|
if (match === name) {
|
||||||
req.log.warn({ ip: req.ip, list: name }, "Request denied by blacklist");
|
req.log.warn({ ip: req.ip, list: name }, "Request denied by blacklist");
|
||||||
return res.status(403).json({ error: `Forbidden (by ${name})` });
|
return res.status(403).json({ error: `Forbidden (by ${name})` });
|
||||||
|
} else {
|
||||||
|
return next();
|
||||||
}
|
}
|
||||||
return next();
|
|
||||||
};
|
};
|
||||||
middleware.ranges = [] as string[];
|
middleware.ranges = cidrs;
|
||||||
middleware.updateRanges = (ranges: string[] | string) => {
|
middleware.updateRanges = (r: string[] | string) => {
|
||||||
cidrs = Array.isArray(ranges) ? ranges.slice() : [ranges];
|
cidrs = Array.isArray(r) ? r.slice() : [r];
|
||||||
matchers = parseCidrs(cidrs);
|
const parsed = parseCidrs(cidrs);
|
||||||
log.info({ list: name, matchers }, "IP blacklist configured");
|
ranges = { [name]: parsed };
|
||||||
middleware.ranges = cidrs;
|
middleware.ranges = cidrs;
|
||||||
|
log.info({ list: name, ranges }, "IP blacklist configured");
|
||||||
};
|
};
|
||||||
|
|
||||||
middleware.updateRanges(base);
|
middleware.updateRanges(base);
|
||||||
|
|
||||||
blacklists.set(name, middleware);
|
blacklists.set(name, middleware);
|
||||||
|
|
|
@ -100,5 +100,3 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<%- include("partials/shared_flash", { flashData: flash }) %>
|
<%- include("partials/shared_flash", { flashData: flash }) %>
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
|
@ -335,7 +335,7 @@
|
||||||
elapsedTime += (Date.now() - lastUpdateTime) / 1000;
|
elapsedTime += (Date.now() - lastUpdateTime) / 1000;
|
||||||
lastUpdateTime = Date.now();
|
lastUpdateTime = Date.now();
|
||||||
const hashRate = totalHashes / elapsedTime;
|
const hashRate = totalHashes / elapsedTime;
|
||||||
const expectedTimeRemaining = (workFactor - totalHashes) / hashRate;
|
const timeRemaining = (workFactor - totalHashes) / hashRate;
|
||||||
const progress = 100 * (1 - Math.exp(-totalHashes / workFactor));
|
const progress = 100 * (1 - Math.exp(-totalHashes / workFactor));
|
||||||
|
|
||||||
const formatTime = (time) => {
|
const formatTime = (time) => {
|
||||||
|
@ -362,11 +362,11 @@
|
||||||
|
|
||||||
document.getElementById("captcha-progress").style.width = Math.min(progress, 100) + "%";
|
document.getElementById("captcha-progress").style.width = Math.min(progress, 100) + "%";
|
||||||
document.getElementById("captcha-progress-text").value = `
|
document.getElementById("captcha-progress-text").value = `
|
||||||
Average hashes needed: ${workFactor.toLocaleString()}
|
Solution probability: 1 in ${workFactor.toLocaleString()} hashes
|
||||||
Hashes computed: ${totalHashes.toLocaleString()}${note}
|
Hashes computed: ${totalHashes.toLocaleString()}${note}
|
||||||
Elapsed time: ${formatTime(elapsedTime)}
|
Elapsed time: ${formatTime(elapsedTime)}
|
||||||
Hash rate: ${hashRate.toFixed(2)} H/s
|
Hash rate: ${hashRate.toFixed(2)} H/s
|
||||||
Workers: ${workers.length}${isMobileWebkit ? " (iOS/iPadOS detected)" : ""}
|
Workers: ${workers.length}${isMobileWebkit ? " (iOS/iPadOS detected)" : ""}
|
||||||
${active ? `Approx. time remaining: ${formatTime(expectedTimeRemaining)}` : "Verification task stopped"}`.trim();
|
${active ? `Average time remaining: ${formatTime(timeRemaining)}` : "Verification task stopped"}`.trim();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
Loading…
Reference in New Issue