From d9d339882be60c72ffd8b5dd0b5adb163f447229 Mon Sep 17 00:00:00 2001 From: Rory& Date: Tue, 28 Apr 2026 03:53:45 +0200 Subject: [PATCH] Register: cache blocked IPs --- assets/openapi.json | Bin 969886 -> 971256 bytes assets/schemas.json | Bin 437368 -> 438781 bytes src/api/routes/auth/register.ts | 48 +++++++++++++++++++++++++----- src/api/routes/reporting/index.ts | 13 ++++---- 4 files changed, 47 insertions(+), 14 deletions(-) diff --git a/assets/openapi.json b/assets/openapi.json index 095d60d3386423cfccdc0bb22af08a60d1d18dee..aa965def46d36edb1c561baab6d42db438b77946 100644 GIT binary patch delta 546 zcmZY6O=uHA6bEo-cJ?zLtOsLBjfrj3Xt9yDv{Djk8j%)66AykIVk^d2q?n4hdTczz z9+VOo$R9;&q>6aaEsHLC>OopOh`p5}1#g01n?nx;L7c{;hu`7-nR)Ygv+y9by_m9p zB@i2>0q6dqJq%ugBpst~@U64q#R(t&b`6i)sO)IpRfa}LX@i|mj-xU{#&B|gYJ9@8 zoIlOQ9GX8#ZYMv*Yl(I=SEVEFbe2?O$T^AJyxPvW(gk{eVV^Uc`h&^Q~6(ZZ(5Ri~9M??GU_&>M-7I z&}nY(jv&(|GaL=;8k_+wjM{A)4>f?CRWn&uGU4KpX&R!QIQ*X zI-+;fXNl<+e$(-8C-3`6VDD!PW{<&eqC-lzGM*V%-QG^=91-Ok^8b?RXOtoLeO!us mp0ji&$b#Zpy1K6m;%m&gY*H08K^F|cC-{Ycn@t7_3x5GCbiz9T delta 814 zcmb`FOK1~O6o$F?&SWMrF;Ot3rA=>29~Rpt_Mw5aC8b8YkUprOF0`FC6BA5khD?eZ z(c-3?V8ttQ6r~ipa2tp?hzJGkN+kuCf)q7oVXAQ z5YB(=at_x;C!%Ag%=?G{Hg1uAsFuknR5e=yma)(W(>&P`M(Qf;c6D%}(Yk1VC?pL9 zW1$#5I-Zgxg)V6{nby=yR;NjY>Wc|EE$IPCO9mFytlkY$Q3w|>~3|wyH1*Vo=?Df}VK`k9ag~7_klrci$;;f=xi>IWk;P;|Y*nWv_ zFzwX@(+UpQ+~ONp{;6ofFnU7G7xU=hW3%GH;UY|a!gDZv68AHWYh#<|c!>CM7pPIx z4*52F6y~Q8$2_xsYt{#mCb5})f7@Mq7-vy4XhW#8zUTRfYi$=q(s&A}2X(-m634^b zDt16#$KIMbtIjmmDp=btxZNZC>F)i(!{!1xWX(D7_{d=+Z1cgiL985huxK`Y#;Yx+ zWFx0LdlywDDIVEM~7){IBgc<|?Bl{5uKIiPlxabr1 zVmkhg3&8Ru5#f3t8QvF5ROEx~!??}N1<84|FP8o-aRfZ`=n`z-w4E?#21(0%^MZ)) zi7Semv*0akrs5WS?f*`I{n%}QYDsVh#^`@X<%RW0wgv`7M0_h7+`s930iXb(|DD;RqC@E$z)zu!gveU3CA#^J3~ zpWDBOGpwE92E3ypC*LQ1i-RF)-uKJz4<_KubIq*}CU3cwm0==Vt<&yIdz2xYrn-!^ zN8pT*DFo_F$jw*fT7vo9m#;;-HcE3a3F$}nk(R0b;e9G@kW3Wz7#I3ju~5icrTm#= zmQ|cDlq5+`;Nv|y4f8Lz7H{iFUDWo&_KUdm<^=AxST8n~={DFuwUjKc2W0kJ6#8}^ z#0M&7QSBoE4D@KbPz`edb2pt*XNSqP8)|%(eHH4*3$oVOdZ}7;9M7Ndo5&t!8M*Pv z2PaGq!S2vWJPXnTFfWTsh|SSnye-nSYHA<<= delta 843 zcmb_ZT}V@580P!V`J9Bu9O;tzv(HJAm2GOmi2g=dQKG9TFQW3eo#AMnGj_I&NuXC1 zMiF1^y(~e9E+)c|4=z62V;P=L1+Ruf$at^i^p-yw6ln0!X^o>T}99~ zmrL$&`dmJ`ac@wORT>S^z+gxVM|2ucsqP(624&qLhXRg%EuuHGBXhmKIm44Uu36Cr z^9N#Q7wjgsaJ%~NJx0~z$%)41aYp8QAlFGcS$Eo$JUQPXz|EJa7jiMd#M;wi?8k$W z&_BnGGxdDpzJ06VCsZeS)dEEq-=s6)_x==7->Wn=EycM=TQq&vlJGm z1+r{Hpt~GNERkIUOP_ET7(-a%sq@%tGj{K02sG1~i|(m7(%V=9Da}os2#fY+xjCDSYO5+yBwy#tg1T|E$H#r~T}y(F7Ia xMCQY@xOSF%Q|MjEMTiN@Cvu39{yVE##=nyKb~pXQPV8G)>*g_@Z!8h7{sbjxJNp0t diff --git a/src/api/routes/auth/register.ts b/src/api/routes/auth/register.ts index 5c40ce1d8..79929f5eb 100644 --- a/src/api/routes/auth/register.ts +++ b/src/api/routes/auth/register.ts @@ -17,7 +17,7 @@ */ import { route, verifyCaptcha } from "@spacebar/api"; -import { Config, FieldErrors, Invite, User, ValidRegistrationToken, generateToken, IpDataClient, AbuseIpDbClient } from "@spacebar/util"; +import { Config, FieldErrors, Invite, User, ValidRegistrationToken, generateToken, IpDataClient, AbuseIpDbClient, TimeSpan } from "@spacebar/util"; import bcrypt from "bcrypt"; import { Request, Response, Router } from "express"; import { HTTPError } from "lambert-server"; @@ -26,6 +26,15 @@ import { RegisterSchema } from "@spacebar/schemas"; const router: Router = Router({ mergeParams: true }); +const recentlyBlockedIps: { + [ip: string]: { + firstHit: Date; + lastHit: Date; + hits: number; + reason: string; + }; +} = {}; + router.post( "/", route({ @@ -82,7 +91,7 @@ router.post( throw FieldErrors({ email: { code: "DISABLED", - message: "registration is disabled on this instance", + message: "Registration is disabled on this instance", }, }); } @@ -124,20 +133,44 @@ router.post( } } + //region IP checks + const cacheBlockedIp = (ip: string, reason: string) => { + recentlyBlockedIps[ip] = { + firstHit: new Date(), + lastHit: new Date(), + hits: 0, + reason, + }; + console.log(`[Register] ${ip} blocked from registration:`, reason); + }; + + if (!regTokenUsed && recentlyBlockedIps[ip]) { + if (new TimeSpan(recentlyBlockedIps[ip].firstHit.getTime(), new Date().getTime()).totalHours >= 24) delete recentlyBlockedIps[ip]; + else { + recentlyBlockedIps[ip].lastHit = new Date(); + recentlyBlockedIps[ip].hits++; + console.log( + `[Register] ${ip} blocked from registration: blocked since ${recentlyBlockedIps[ip].firstHit} with ${recentlyBlockedIps[ip].hits} hits and reason:`, + recentlyBlockedIps[ip].reason, + ); + throw new HTTPError("Your IP is blocked from registration"); + } + } + if (!regTokenUsed && register.enableAbuseIpDb) { const blacklist = await AbuseIpDbClient.getBlacklist(); if (blacklist) { const entry = blacklist.data.find((e) => e.ipAddress === ip); if (entry && entry.abuseConfidenceScore >= register.blockAbuseIpDbAboveScore) { - console.log(`[Register] ${ip} blocked from registration: AbuseIPDB score ${entry.abuseConfidenceScore} >= ${register.blockAbuseIpDbAboveScore} (BLACKLIST)`); + cacheBlockedIp(ip, `AbuseIPDB score ${entry.abuseConfidenceScore} >= ${register.blockAbuseIpDbAboveScore} (BLACKLIST)`); throw new HTTPError("Your IP is blocked from registration"); } } const checkIp = await AbuseIpDbClient.checkIpAddress(ip); if (checkIp?.data && checkIp.data.abuseConfidenceScore >= register.blockAbuseIpDbAboveScore) { - console.log(`[Register] ${ip} blocked from registration: AbuseIPDB score ${checkIp.data.abuseConfidenceScore} >= ${register.blockAbuseIpDbAboveScore} (CHECK)`); + cacheBlockedIp(ip, `AbuseIPDB score ${checkIp.data.abuseConfidenceScore} >= ${register.blockAbuseIpDbAboveScore} (CHECK)`); throw new HTTPError("Your IP is blocked from registration"); } } @@ -153,25 +186,26 @@ router.post( .map(([key]) => key.replace("is_", "")); const blockedCategories = new Set(categories).intersection(new Set(register.blockIpDataCoThreatTypes)); if (blockedCategories.size > 0) { - console.log(`[Register] ${ip} blocked from registration: IPData.co threat types ${Array.from(blockedCategories).join(", ")}`); + cacheBlockedIp(ip, `IPData.co threat types ${Array.from(blockedCategories).join(", ")}`); throw new HTTPError("Your IP is blocked from registration"); } if (ipData.asn.type && register.blockAsnTypes.includes(ipData.asn.type)) { - console.log(`[Register] ${ip} blocked from registration: IPData.co ASN type ${ipData.asn.type} is blocked`); + cacheBlockedIp(ip, `IPData.co ASN type ${ipData.asn.type} is blocked`); throw new HTTPError("Your IP is blocked from registration"); } else if (!ipData.asn.type) { console.log("[Register] IPData.co response missing asn.type field", ipData); } if (ipData.asn.asn && register.blockAsns.includes(ipData.asn.asn)) { - console.log(`[Register] ${ip} blocked from registration: IPData.co ASN ${ipData.asn.name} is blocked`); + cacheBlockedIp(ip, `IPData.co ASN ${ipData.asn.name} is blocked`); throw new HTTPError("Your IP is blocked from registration"); } else if (!ipData.asn.asn) { console.log("[Register] IPData.co response missing asn.asn field", ipData); } } } + //endregion // TODO: gift_code_sku_id? // TODO: check password strength diff --git a/src/api/routes/reporting/index.ts b/src/api/routes/reporting/index.ts index 76b5169ab..27d284d70 100644 --- a/src/api/routes/reporting/index.ts +++ b/src/api/routes/reporting/index.ts @@ -16,14 +16,13 @@ along with this program. If not, see . */ -import { route } from "@spacebar/api"; -import { Request, Response, Router } from "express"; -import { ReportMenuType, ReportMenuTypeNames } from "../../../schemas/api/reports/ReportMenu"; -import path from "node:path"; -import { HTTPError } from "lambert-server"; -import { CreateReportSchema } from "../../../schemas/api/reports/CreateReport"; -import { FieldErrors } from "@spacebar/util"; import fs from "node:fs"; +import path from "node:path"; +import { Request, Response, Router } from "express"; +import { HTTPError } from "lambert-server"; +import { route } from "@spacebar/api"; +import { ReportMenuType, ReportMenuTypeNames, CreateReportSchema } from "@spacebar/schemas"; +import { FieldErrors } from "@spacebar/util"; const router = Router({ mergeParams: true }); if (process.env.LOG_ROUTES !== "false") console.log("[Server] Registering reporting menu routes...");