fixes mixed ipv4-ipv6 handling in cidr module

This commit is contained in:
nai-degen 2024-05-24 02:02:21 -05:00
parent 7d517a4c5f
commit 6352df5d5a
5 changed files with 36 additions and 33 deletions

View File

@ -314,10 +314,10 @@ router.post("/maintenance", (req, res) => {
const temps = users.filter((u) => u.type === "temporary");
temps.forEach((user) => {
user.expiresAt = Date.now();
user.disabledReason = "Admin forced expiration."
user.disabledReason = "Admin forced expiration.";
userStore.upsertUser(user);
});
invalidatePowHmacKey()
invalidatePowHmacKey();
flash.type = "success";
flash.message = `${temps.length} temporary users marked for expiration.`;
break;
@ -370,8 +370,8 @@ router.post("/maintenance", (req, res) => {
ipv4RangeMap.set(subnet, userSet);
} else if (parsed.kind() === "ipv6") {
const subnet =
parsed.toNormalizedString().split(":").slice(0, 3).join(":") +
"::/56";
parsed.toNormalizedString().split(":").slice(0, 4).join(":") +
"::/48";
const userSet = ipv6RangeMap.get(subnet) || new Set<string>();
userSet.add(u.token);
ipv6RangeMap.set(subnet, userSet);

View File

@ -158,7 +158,7 @@ function getSelfServiceLinks() {
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>`)
.join(" | ")}</div>`;
}

View File

@ -24,9 +24,9 @@ export function parseCidrs(cidrs: string[] | string): [IPv4 | IPv6, number][] {
.map((input) => {
try {
if (input.includes("/")) {
return ipaddr.parseCIDR(input);
return ipaddr.parseCIDR(input.trim());
} else {
const ip = ipaddr.parse(input);
const ip = ipaddr.parse(input.trim());
return ipaddr.parseCIDR(
`${input}/${ip.kind() === "ipv4" ? 32 : 128}`
);
@ -44,23 +44,25 @@ export function createWhitelistMiddleware(
base: string[] | 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 allowed = matchers.some((cidr) => ip.match(cidr));
if (allowed) {
const match = ipaddr.subnetMatch(ip, ranges, "none");
if (match === name) {
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.updateRanges = (ranges: string[] | string) => {
cidrs = Array.isArray(ranges) ? ranges.slice() : [ranges];
matchers = parseCidrs(cidrs);
log.info({ list: name, matchers }, "IP whitelist configured");
middleware.ranges = cidrs;
middleware.updateRanges = (r: string[] | string) => {
cidrs = Array.isArray(r) ? r.slice() : [r];
const parsed = parseCidrs(cidrs);
ranges = { [name]: parsed };
middleware.ranges = cidrs;
log.info({ list: name, ranges }, "IP whitelist configured");
};
middleware.updateRanges(base);
@ -74,24 +76,27 @@ export function createBlacklistMiddleware(
base: string[] | 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 denied = matchers.some((cidr) => ip.match(cidr));
if (denied) {
const match = ipaddr.subnetMatch(ip, ranges, "none");
if (match === name) {
req.log.warn({ ip: req.ip, list: name }, "Request denied by blacklist");
return res.status(403).json({ error: `Forbidden (by ${name})` });
} else {
return next();
}
return next();
};
middleware.ranges = [] as string[];
middleware.updateRanges = (ranges: string[] | string) => {
cidrs = Array.isArray(ranges) ? ranges.slice() : [ranges];
matchers = parseCidrs(cidrs);
log.info({ list: name, matchers }, "IP blacklist configured");
middleware.ranges = cidrs;
middleware.updateRanges = (r: string[] | string) => {
cidrs = Array.isArray(r) ? r.slice() : [r];
const parsed = parseCidrs(cidrs);
ranges = { [name]: parsed };
middleware.ranges = cidrs;
log.info({ list: name, ranges }, "IP blacklist configured");
};
middleware.updateRanges(base);
blacklists.set(name, middleware);

View File

@ -100,5 +100,3 @@
</head>
<body>
<%- include("partials/shared_flash", { flashData: flash }) %>
</body>
</html>

View File

@ -335,7 +335,7 @@
elapsedTime += (Date.now() - lastUpdateTime) / 1000;
lastUpdateTime = Date.now();
const hashRate = totalHashes / elapsedTime;
const expectedTimeRemaining = (workFactor - totalHashes) / hashRate;
const timeRemaining = (workFactor - totalHashes) / hashRate;
const progress = 100 * (1 - Math.exp(-totalHashes / workFactor));
const formatTime = (time) => {
@ -362,11 +362,11 @@
document.getElementById("captcha-progress").style.width = Math.min(progress, 100) + "%";
document.getElementById("captcha-progress-text").value = `
Average hashes needed: ${workFactor.toLocaleString()}
Solution probability: 1 in ${workFactor.toLocaleString()} hashes
Hashes computed: ${totalHashes.toLocaleString()}${note}
Elapsed time: ${formatTime(elapsedTime)}
Hash rate: ${hashRate.toFixed(2)} H/s
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>