From b76303c45f122cc0731e92dd4a0020d6f50a0902 Mon Sep 17 00:00:00 2001 From: BrajamohanDas-afk Date: Fri, 5 Dec 2025 01:17:26 +0530 Subject: [PATCH] Refactor: Replace getIpAddress with req.ip --- assets/openapi.json | Bin 856601 -> 856601 bytes assets/schemas.json | Bin 380213 -> 380213 bytes src/api/middlewares/RateLimit.ts | 48 ++++---------------- src/api/routes/auth/forgot.ts | 15 ++---- src/api/routes/auth/location-metadata.ts | 4 +- src/api/routes/auth/login.ts | 45 ++++-------------- src/api/routes/auth/register.ts | 8 ++-- src/api/routes/auth/verify/index.ts | 12 ++--- src/api/routes/guilds/#guild_id/bans.ts | 4 +- src/api/routes/guilds/#guild_id/bulk-ban.ts | 4 +- src/api/routes/guilds/#guild_id/regions.ts | 9 +--- src/api/routes/invites/index.ts | 4 +- src/api/routes/voice/regions.ts | 4 +- src/api/util/utility/ipAddress.ts | 33 ++------------ 14 files changed, 48 insertions(+), 142 deletions(-) diff --git a/assets/openapi.json b/assets/openapi.json index 5d73449d2ca0367103f39f941831459302b60123..39ca2eec6aff21e7f664221a050fb737fd007e9a 100644 GIT binary patch delta 310 zcmbQa#AN0YlMPNR)A#RZ7HXc!x_u@qUliE(|`Lg%1&<3keIALg=>1&6GnN6oajRy(dqNdu*tQ{Jz?A~_k>C1+4O`? zX12{9EP;&eA*Yyum}Ps&DOR;jjONoBw{XZ$e{h3|bNYv;OqyWUWCsiT>94EUjKI9@ z{|g~kET$Lu vut-lgum#%CJ(W#>+1$W*GNY+N`{ikDK+F!r96-zo#9Tnkz5Vhu9=B=$V$yYE delta 302 zcmbQa#AN0YlMPNR+y93z*ivD+Iqv;JV6UckmGvAzEeqX+AB``wPsx?NO(gftY1`)G1blP0VJN7SkD@G3iZCFx8sA!J5fx z`UD*&*2xJ)M$;9-Sva@**RpL8XR diff --git a/assets/schemas.json b/assets/schemas.json index 3e597f0945a1d9b9f941f27b3b0e4bfdb62181c6..4fe2ea4019bececb2da9195bb8cca057f527bce9 100644 GIT binary patch delta 296 zcmdlwOML4ramMQDiq6au&Do6Gvl$tst0ph_BC)-pnDH3nbb)MEkLd?aGP6&vR}-83 zVUpf-19e8$=?1%*#HI%*uWRqn%2x@Hkl_ud-{Z@jGWVR zD_K>iU+7>FoScv$I(K(%k;Jnw3YG$=qN%V?S#X$RH3S y1fptz6O-oj4YwGDrw3RwDhb8MJCx=XC+8O=Cgr3SJAl**HnK==ubIHQ(*^*i*kOVI delta 275 zcmdlwOML4ramMQHx2>477#k(GN-|1UP4Craa^J4d$ymrVy8&_m2-M-C9CRmgI-qi z=?X!NqSGe`GAT?B(A3%frJ9vnn90&?I%7X;6UYD%BLuF96~wZheqkN6l2ClSLup=d Za(+Q#Qch~I1IV(%$&%Y^Ca~_b0RYq. */ -import { getIpAdress } from "@spacebar/api"; import { Config, getRights, listenEvent } from "@spacebar/util"; import { NextFunction, Request, Response, Router } from "express"; import { API_PREFIX_TRAILING_SLASH } from "./Authentication"; @@ -65,21 +64,14 @@ export default function rateLimit(opts: { if (rights.has("BYPASS_RATE_LIMITS")) return next(); } - const bucket_id = - opts.bucket || - req.originalUrl.replace(API_PREFIX_TRAILING_SLASH, ""); - let executor_id = getIpAdress(req); + const bucket_id = opts.bucket || req.originalUrl.replace(API_PREFIX_TRAILING_SLASH, ""); + let executor_id = req.ip || "127.0.0.1"; if (!opts.onlyIp && req.user_id) executor_id = req.user_id; let max_hits = opts.count; if (opts.bot && req.user_bot) max_hits = opts.bot; - if (opts.GET && ["GET", "OPTIONS", "HEAD"].includes(req.method)) - max_hits = opts.GET; - else if ( - opts.MODIFY && - ["POST", "DELETE", "PATCH", "PUT"].includes(req.method) - ) - max_hits = opts.MODIFY; + if (opts.GET && ["GET", "OPTIONS", "HEAD"].includes(req.method)) max_hits = opts.GET; + else if (opts.MODIFY && ["POST", "DELETE", "PATCH", "PUT"].includes(req.method)) max_hits = opts.MODIFY; const offender = Cache.get(executor_id + bucket_id); @@ -104,18 +96,13 @@ export default function rateLimit(opts: { } res.set("X-RateLimit-Reset", `${reset}`); - res.set( - "X-RateLimit-Reset-After", - `${Math.max(0, Math.ceil(resetAfterSec))}`, - ); + res.set("X-RateLimit-Reset-After", `${Math.max(0, Math.ceil(resetAfterSec))}`); if (offender.blocked) { const global = bucket_id === "global"; // each block violation pushes the expiry one full window further reset += opts.window * 1000; - offender.expires_at = new Date( - offender.expires_at.getTime() + opts.window * 1000, - ); + offender.expires_at = new Date(offender.expires_at.getTime() + opts.window * 1000); resetAfterMs = reset - Date.now(); resetAfterSec = Math.ceil(resetAfterMs / 1000); @@ -129,10 +116,7 @@ export default function rateLimit(opts: { res .status(429) .set("X-RateLimit-Remaining", "0") - .set( - "Retry-After", - `${Math.max(0, Math.ceil(resetAfterSec))}`, - ) + .set("Retry-After", `${Math.max(0, Math.ceil(resetAfterSec))}`) // TODO: error rate limit message translation .send({ message: "You are being rate limited.", @@ -156,11 +140,7 @@ export default function rateLimit(opts: { // check if error and increment error rate limit if (res.statusCode >= 400 && opts.error) { return hitRoute(hitRouteOpts); - } else if ( - res.statusCode >= 200 && - res.statusCode < 300 && - opts.success - ) { + } else if (res.statusCode >= 200 && res.statusCode < 300 && opts.success) { return hitRoute(hitRouteOpts); } }); @@ -213,18 +193,10 @@ export async function initRateLimits(app: Router) { app.use("/webhooks/:webhook_id", rateLimit(routes.webhook)); app.use("/channels/:channel_id", rateLimit(routes.channel)); app.use("/auth/login", rateLimit(routes.auth.login)); - app.use( - "/auth/register", - rateLimit({ onlyIp: true, success: true, ...routes.auth.register }), - ); + app.use("/auth/register", rateLimit({ onlyIp: true, success: true, ...routes.auth.register })); } -async function hitRoute(opts: { - executor_id: string; - bucket_id: string; - max_hits: number; - window: number; -}) { +async function hitRoute(opts: { executor_id: string; bucket_id: string; max_hits: number; window: number }) { const id = opts.executor_id + opts.bucket_id; let limit = Cache.get(id); if (!limit) { diff --git a/src/api/routes/auth/forgot.ts b/src/api/routes/auth/forgot.ts index 12bc9cb07..6e613820f 100644 --- a/src/api/routes/auth/forgot.ts +++ b/src/api/routes/auth/forgot.ts @@ -16,10 +16,10 @@ along with this program. If not, see . */ -import { getIpAdress, route, verifyCaptcha } from "@spacebar/api"; +import { route, verifyCaptcha } from "@spacebar/api"; import { Config, Email, User } from "@spacebar/util"; import { Request, Response, Router } from "express"; -import { ForgotPasswordSchema } from "@spacebar/schemas" +import { ForgotPasswordSchema } from "@spacebar/schemas"; const router = Router({ mergeParams: true }); router.post( @@ -38,10 +38,7 @@ router.post( const config = Config.get(); - if ( - config.passwordReset.requireCaptcha && - config.security.captcha.enabled - ) { + if (config.passwordReset.requireCaptcha && config.security.captcha.enabled) { const { sitekey, service } = config.security.captcha; if (!captcha_key) { return res.status(400).json({ @@ -51,7 +48,7 @@ router.post( }); } - const ip = getIpAdress(req); + const ip = req.ip; const verify = await verifyCaptcha(captcha_key, ip); if (!verify.success) { return res.status(400).json({ @@ -71,9 +68,7 @@ router.post( if (user && user.email) { Email.sendResetPassword(user, user.email).catch((e) => { - console.error( - `Failed to send password reset email to ${user.username}#${user.discriminator} (${user.id}): ${e}`, - ); + console.error(`Failed to send password reset email to ${user.username}#${user.discriminator} (${user.id}): ${e}`); }); } }, diff --git a/src/api/routes/auth/location-metadata.ts b/src/api/routes/auth/location-metadata.ts index a08c98abe..92f47909d 100644 --- a/src/api/routes/auth/location-metadata.ts +++ b/src/api/routes/auth/location-metadata.ts @@ -16,7 +16,7 @@ along with this program. If not, see . */ -import { getIpAdress, route } from "@spacebar/api"; +import { route } from "@spacebar/api"; import { IpDataClient } from "@spacebar/util"; import { Request, Response, Router } from "express"; const router = Router({ mergeParams: true }); @@ -33,7 +33,7 @@ router.get( async (req: Request, res: Response) => { //TODO //Note: It's most likely related to legal. At the moment Discord hasn't finished this too - const country_code = (await IpDataClient.getIpInfo(getIpAdress(req)))?.country_code; + const country_code = (await IpDataClient.getIpInfo(req.ip!))?.country_code; res.json({ consent_required: false, country_code: country_code, diff --git a/src/api/routes/auth/login.ts b/src/api/routes/auth/login.ts index 06e3fe43e..c8ef62731 100644 --- a/src/api/routes/auth/login.ts +++ b/src/api/routes/auth/login.ts @@ -16,19 +16,12 @@ along with this program. If not, see . */ -import { getIpAdress, route, verifyCaptcha } from "@spacebar/api"; -import { - Config, - FieldErrors, - User, - WebAuthn, - generateToken, - generateWebAuthnTicket, -} from "@spacebar/util"; +import { route, verifyCaptcha } from "@spacebar/api"; +import { Config, FieldErrors, User, WebAuthn, generateToken, generateWebAuthnTicket } from "@spacebar/util"; import bcrypt from "bcrypt"; import crypto from "crypto"; import { Request, Response, Router } from "express"; -import { LoginSchema } from "@spacebar/schemas" +import { LoginSchema } from "@spacebar/schemas"; const router: Router = Router({ mergeParams: true }); export default router; @@ -61,7 +54,7 @@ router.post( }); } - const ip = getIpAdress(req); + const ip = req.ip; const verify = await verifyCaptcha(captcha_key, ip); if (!verify.success) { return res.status(400).json({ @@ -74,17 +67,7 @@ router.post( const user = await User.findOneOrFail({ where: [{ phone: login }, { email: login }], - select: [ - "data", - "id", - "disabled", - "deleted", - "totp_secret", - "mfa_enabled", - "webauthn_enabled", - "security_keys", - "verified", - ], + select: ["data", "id", "disabled", "deleted", "totp_secret", "mfa_enabled", "webauthn_enabled", "security_keys", "verified"], relations: ["security_keys", "settings"], }).catch(() => { throw FieldErrors({ @@ -100,10 +83,7 @@ router.post( }); // the salt is saved in the password refer to bcrypt docs - const same_password = await bcrypt.compare( - password, - user.data.hash || "", - ); + const same_password = await bcrypt.compare(password, user.data.hash || ""); if (!same_password) { throw FieldErrors({ login: { @@ -122,8 +102,7 @@ router.post( throw FieldErrors({ login: { code: "ACCOUNT_LOGIN_VERIFICATION_EMAIL", - message: - "Email verification is required, please check your email.", + message: "Email verification is required, please check your email.", }, }); } @@ -152,9 +131,7 @@ router.post( const challenge = JSON.stringify({ publicKey: { ...options, - challenge: Buffer.from(options.challenge).toString( - "base64", - ), + challenge: Buffer.from(options.challenge).toString("base64"), allowCredentials: user.security_keys.map((x) => ({ id: x.key_id, type: "public-key", @@ -178,10 +155,8 @@ router.post( if (undelete) { // undelete refers to un'disable' here - if (user.disabled) - await User.update({ id: user.id }, { disabled: false }); - if (user.deleted) - await User.update({ id: user.id }, { deleted: false }); + if (user.disabled) await User.update({ id: user.id }, { disabled: false }); + if (user.deleted) await User.update({ id: user.id }, { deleted: false }); } else { if (user.deleted) return res.status(400).json({ diff --git a/src/api/routes/auth/register.ts b/src/api/routes/auth/register.ts index 418f0a529..d4b061be2 100644 --- a/src/api/routes/auth/register.ts +++ b/src/api/routes/auth/register.ts @@ -16,7 +16,7 @@ along with this program. If not, see . */ -import { getIpAdress, route, verifyCaptcha } from "@spacebar/api"; +import { route, verifyCaptcha } from "@spacebar/api"; import { Config, FieldErrors, Invite, User, ValidRegistrationToken, generateToken, IpDataClient, AbuseIpDbClient } from "@spacebar/util"; import bcrypt from "bcrypt"; import { Request, Response, Router } from "express"; @@ -38,7 +38,7 @@ router.post( async (req: Request, res: Response) => { const body = req.body as RegisterSchema; const { register, security, limits } = Config.get(); - const ip = getIpAdress(req); + const ip = req.ip!; // Reg tokens // They're a one time use token that bypasses registration limits ( rates, disabled reg, etc ) @@ -143,7 +143,7 @@ router.post( const ipData = await IpDataClient.getIpInfo(ip); if (ipData) { - if(!ipData.threat) { + if (!ipData.threat) { console.log("Invalid IPData.co response, missing threat field", ipData); } const categories = Object.entries(ipData.threat) @@ -287,7 +287,7 @@ router.post( }, })) >= limits.absoluteRate.register.limit ) { - console.log(`Global register ratelimit exceeded for ${getIpAdress(req)}, ${req.body.username}, ${req.body.invite || "No invite given"}`); + console.log(`Global register ratelimit exceeded for ${req.ip}, ${req.body.username}, ${req.body.invite || "No invite given"}`); throw FieldErrors({ email: { code: "TOO_MANY_REGISTRATIONS", diff --git a/src/api/routes/auth/verify/index.ts b/src/api/routes/auth/verify/index.ts index b85120d84..d8e343cd2 100644 --- a/src/api/routes/auth/verify/index.ts +++ b/src/api/routes/auth/verify/index.ts @@ -16,14 +16,8 @@ along with this program. If not, see . */ -import { getIpAdress, route, verifyCaptcha } from "@spacebar/api"; -import { - checkToken, - Config, - FieldErrors, - generateToken, - User, -} from "@spacebar/util"; +import { route, verifyCaptcha } from "@spacebar/api"; +import { checkToken, Config, FieldErrors, generateToken, User } from "@spacebar/util"; import { Request, Response, Router } from "express"; const router = Router({ mergeParams: true }); @@ -67,7 +61,7 @@ router.post( }); } - const ip = getIpAdress(req); + const ip = req.ip; const verify = await verifyCaptcha(captcha_key, ip); if (!verify.success) { return res.status(400).json({ diff --git a/src/api/routes/guilds/#guild_id/bans.ts b/src/api/routes/guilds/#guild_id/bans.ts index e253bef9f..973f212dd 100644 --- a/src/api/routes/guilds/#guild_id/bans.ts +++ b/src/api/routes/guilds/#guild_id/bans.ts @@ -16,7 +16,7 @@ along with this program. If not, see . */ -import { getIpAdress, route } from "@spacebar/api"; +import { route } from "@spacebar/api"; import { Ban, DiscordApiErrors, GuildBanAddEvent, GuildBanRemoveEvent, Member, User, emitEvent } from "@spacebar/util"; import { Request, Response, Router } from "express"; import { HTTPError } from "lambert-server"; @@ -215,7 +215,7 @@ router.put( const ban = Ban.create({ user_id: banned_user_id, guild_id: guild_id, - ip: getIpAdress(req), + ip: req.ip, executor_id: req.user_id, reason: req.body.reason, // || otherwise empty }); diff --git a/src/api/routes/guilds/#guild_id/bulk-ban.ts b/src/api/routes/guilds/#guild_id/bulk-ban.ts index 3d3ed1c39..a564e2d48 100644 --- a/src/api/routes/guilds/#guild_id/bulk-ban.ts +++ b/src/api/routes/guilds/#guild_id/bulk-ban.ts @@ -16,7 +16,7 @@ along with this program. If not, see . */ -import { getIpAdress, route } from "@spacebar/api"; +import { route } from "@spacebar/api"; import { Ban, DiscordApiErrors, GuildBanAddEvent, Member, User, emitEvent } from "@spacebar/util"; import { Request, Response, Router } from "express"; import { HTTPError } from "lambert-server"; @@ -81,7 +81,7 @@ router.post( const ban = Ban.create({ user_id: banned_user_id, guild_id: guild_id, - ip: getIpAdress(req), + ip: req.ip, executor_id: req.user_id, reason: req.body.reason, // || otherwise empty }); diff --git a/src/api/routes/guilds/#guild_id/regions.ts b/src/api/routes/guilds/#guild_id/regions.ts index a7fad818d..a3c5a81b6 100644 --- a/src/api/routes/guilds/#guild_id/regions.ts +++ b/src/api/routes/guilds/#guild_id/regions.ts @@ -16,7 +16,7 @@ along with this program. If not, see . */ -import { getIpAdress, getVoiceRegions, route } from "@spacebar/api"; +import { getVoiceRegions, route } from "@spacebar/api"; import { Guild } from "@spacebar/util"; import { Request, Response, Router } from "express"; @@ -38,12 +38,7 @@ router.get( const { guild_id } = req.params; const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); //TODO we should use an enum for guild's features and not hardcoded strings - return res.json( - await getVoiceRegions( - getIpAdress(req), - guild.features.includes("VIP_REGIONS"), - ), - ); + return res.json(await getVoiceRegions(req.ip!, guild.features.includes("VIP_REGIONS"))); }, ); diff --git a/src/api/routes/invites/index.ts b/src/api/routes/invites/index.ts index c77d36eaa..87715f35e 100644 --- a/src/api/routes/invites/index.ts +++ b/src/api/routes/invites/index.ts @@ -16,7 +16,7 @@ along with this program. If not, see . */ -import { getIpAdress, route } from "@spacebar/api"; +import { route } from "@spacebar/api"; import { Ban, DiscordApiErrors, emitEvent, getPermission, Guild, Invite, InviteDeleteEvent, PublicInviteRelation, User } from "@spacebar/util"; import { Request, Response, Router } from "express"; import { HTTPError } from "lambert-server"; @@ -83,7 +83,7 @@ router.post( const ban = await Ban.findOne({ where: [ { guild_id: guild_id, user_id: req.user_id }, - { guild_id: guild_id, ip: getIpAdress(req) }, + { guild_id: guild_id, ip: req.ip }, ], }); diff --git a/src/api/routes/voice/regions.ts b/src/api/routes/voice/regions.ts index 9fdd6fdef..ba2612316 100644 --- a/src/api/routes/voice/regions.ts +++ b/src/api/routes/voice/regions.ts @@ -16,7 +16,7 @@ along with this program. If not, see . */ -import { getIpAdress, getVoiceRegions, route } from "@spacebar/api"; +import { getVoiceRegions, route } from "@spacebar/api"; import { Request, Response, Router } from "express"; const router: Router = Router({ mergeParams: true }); @@ -31,7 +31,7 @@ router.get( }, }), async (req: Request, res: Response) => { - res.json(await getVoiceRegions(getIpAdress(req), true)); //vip true? + res.json(await getVoiceRegions(req.ip!, true)); //vip true? }, ); diff --git a/src/api/util/utility/ipAddress.ts b/src/api/util/utility/ipAddress.ts index 914999a71..19408253d 100644 --- a/src/api/util/utility/ipAddress.ts +++ b/src/api/util/utility/ipAddress.ts @@ -16,41 +16,16 @@ along with this program. If not, see . */ -import { Config } from "@spacebar/util"; -import { Request } from "express"; - -export function getIpAdress(req: Request): string { - // TODO: express can do this (trustProxies: true)? - - return req.ip!; -} - type Location = { latitude: number; longitude: number }; -export function distanceBetweenLocations( - loc1: Location, - loc2: Location, -): number { - return distanceBetweenCoords( - loc1.latitude, - loc1.longitude, - loc2.latitude, - loc2.longitude, - ); +export function distanceBetweenLocations(loc1: Location, loc2: Location): number { + return distanceBetweenCoords(loc1.latitude, loc1.longitude, loc2.latitude, loc2.longitude); } //Haversine function -function distanceBetweenCoords( - lat1: number, - lon1: number, - lat2: number, - lon2: number, -) { +function distanceBetweenCoords(lat1: number, lon1: number, lat2: number, lon2: number) { const p = 0.017453292519943295; // Math.PI / 180 const c = Math.cos; - const a = - 0.5 - - c((lat2 - lat1) * p) / 2 + - (c(lat1 * p) * c(lat2 * p) * (1 - c((lon2 - lon1) * p))) / 2; + const a = 0.5 - c((lat2 - lat1) * p) / 2 + (c(lat1 * p) * c(lat2 * p) * (1 - c((lon2 - lon1) * p))) / 2; return 12742 * Math.asin(Math.sqrt(a)); // 2 * R; R = 6371 km }