From 9759e86921e64e0f9536ff0bdf977a3f4703c2b3 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Tue, 23 Dec 2025 12:35:03 -0500 Subject: [PATCH] add stripPortFromHost and reuse everywhere --- server/lib/ip.ts | 33 ++++++++++++++++++++++ server/private/lib/logAccessAudit.ts | 15 ++-------- server/routers/auth/pollDeviceWebAuth.ts | 27 ++---------------- server/routers/auth/startDeviceWebAuth.ts | 27 ++---------------- server/routers/badger/exchangeSession.ts | 22 ++------------- server/routers/badger/logRequestAudit.ts | 22 ++------------- server/routers/badger/verifySession.ts | 34 ++--------------------- 7 files changed, 45 insertions(+), 135 deletions(-) diff --git a/server/lib/ip.ts b/server/lib/ip.ts index 280939f2..21ec78c1 100644 --- a/server/lib/ip.ts +++ b/server/lib/ip.ts @@ -4,6 +4,7 @@ import { and, eq, isNotNull } from "drizzle-orm"; import config from "@server/lib/config"; import z from "zod"; import logger from "@server/logger"; +import semver from "semver"; interface IPRange { start: bigint; @@ -683,3 +684,35 @@ export function parsePortRangeString( return result; } + +export function stripPortFromHost(ip: string, badgerVersion?: string): string { + const isNewerBadger = + badgerVersion && + semver.valid(badgerVersion) && + semver.gte(badgerVersion, "1.3.1"); + + if (isNewerBadger) { + return ip; + } + + if (ip.startsWith("[") && ip.includes("]")) { + // if brackets are found, extract the IPv6 address from between the brackets + const ipv6Match = ip.match(/\[(.*?)\]/); + if (ipv6Match) { + return ipv6Match[1]; + } + } + + // Check if it looks like IPv4 (contains dots and matches IPv4 pattern) + // IPv4 format: x.x.x.x where x is 0-255 + const ipv4Pattern = /^(\d{1,3}\.){3}\d{1,3}/; + if (ipv4Pattern.test(ip)) { + const lastColonIndex = ip.lastIndexOf(":"); + if (lastColonIndex !== -1) { + return ip.substring(0, lastColonIndex); + } + } + + // Return as is + return ip; +} diff --git a/server/private/lib/logAccessAudit.ts b/server/private/lib/logAccessAudit.ts index 5c423c60..33dcaf1f 100644 --- a/server/private/lib/logAccessAudit.ts +++ b/server/private/lib/logAccessAudit.ts @@ -17,6 +17,7 @@ import logger from "@server/logger"; import { and, eq, lt } from "drizzle-orm"; import cache from "@server/lib/cache"; import { calculateCutoffTimestamp } from "@server/lib/cleanupLogs"; +import { stripPortFromHost } from "@server/lib/ip"; async function getAccessDays(orgId: string): Promise { // check cache first @@ -116,19 +117,7 @@ export async function logAccessAudit(data: { } const clientIp = data.requestIp - ? (() => { - if ( - data.requestIp.startsWith("[") && - data.requestIp.includes("]") - ) { - // if brackets are found, extract the IPv6 address from between the brackets - const ipv6Match = data.requestIp.match(/\[(.*?)\]/); - if (ipv6Match) { - return ipv6Match[1]; - } - } - return data.requestIp; - })() + ? stripPortFromHost(data.requestIp) : undefined; const countryCode = data.requestIp diff --git a/server/routers/auth/pollDeviceWebAuth.ts b/server/routers/auth/pollDeviceWebAuth.ts index a5c71362..30d7183e 100644 --- a/server/routers/auth/pollDeviceWebAuth.ts +++ b/server/routers/auth/pollDeviceWebAuth.ts @@ -10,6 +10,7 @@ import { eq, and, gt } from "drizzle-orm"; import { createSession, generateSessionToken } from "@server/auth/sessions/app"; import { encodeHexLowerCase } from "@oslojs/encoding"; import { sha256 } from "@oslojs/crypto/sha2"; +import { stripPortFromHost } from "@server/lib/ip"; const paramsSchema = z.object({ code: z.string().min(1, "Code is required") @@ -27,30 +28,6 @@ export type PollDeviceWebAuthResponse = { token?: string; }; -// Helper function to extract IP from request (same as in startDeviceWebAuth) -function extractIpFromRequest(req: Request): string | undefined { - const ip = req.ip || req.socket.remoteAddress; - if (!ip) { - return undefined; - } - - // Handle IPv6 format [::1] or IPv4 format - if (ip.startsWith("[") && ip.includes("]")) { - const ipv6Match = ip.match(/\[(.*?)\]/); - if (ipv6Match) { - return ipv6Match[1]; - } - } - - // Handle IPv4 with port (split at last colon) - const lastColonIndex = ip.lastIndexOf(":"); - if (lastColonIndex !== -1) { - return ip.substring(0, lastColonIndex); - } - - return ip; -} - export async function pollDeviceWebAuth( req: Request, res: Response, @@ -70,7 +47,7 @@ export async function pollDeviceWebAuth( try { const { code } = parsedParams.data; const now = Date.now(); - const requestIp = extractIpFromRequest(req); + const requestIp = req.ip ? stripPortFromHost(req.ip) : undefined; // Hash the code before querying const hashedCode = hashDeviceCode(code); diff --git a/server/routers/auth/startDeviceWebAuth.ts b/server/routers/auth/startDeviceWebAuth.ts index 85fb5262..e28e750c 100644 --- a/server/routers/auth/startDeviceWebAuth.ts +++ b/server/routers/auth/startDeviceWebAuth.ts @@ -12,6 +12,7 @@ import { TimeSpan } from "oslo"; import { maxmindLookup } from "@server/db/maxmind"; import { encodeHexLowerCase } from "@oslojs/encoding"; import { sha256 } from "@oslojs/crypto/sha2"; +import { stripPortFromHost } from "@server/lib/ip"; const bodySchema = z .object({ @@ -39,30 +40,6 @@ function hashDeviceCode(code: string): string { return encodeHexLowerCase(sha256(new TextEncoder().encode(code))); } -// Helper function to extract IP from request -function extractIpFromRequest(req: Request): string | undefined { - const ip = req.ip; - if (!ip) { - return undefined; - } - - // Handle IPv6 format [::1] or IPv4 format - if (ip.startsWith("[") && ip.includes("]")) { - const ipv6Match = ip.match(/\[(.*?)\]/); - if (ipv6Match) { - return ipv6Match[1]; - } - } - - // Handle IPv4 with port (split at last colon) - const lastColonIndex = ip.lastIndexOf(":"); - if (lastColonIndex !== -1) { - return ip.substring(0, lastColonIndex); - } - - return ip; -} - // Helper function to get city from IP (if available) async function getCityFromIp(ip: string): Promise { try { @@ -112,7 +89,7 @@ export async function startDeviceWebAuth( const hashedCode = hashDeviceCode(code); // Extract IP from request - const ip = extractIpFromRequest(req); + const ip = req.ip ? stripPortFromHost(req.ip) : undefined; // Get city (optional, may return undefined) const city = ip ? await getCityFromIp(ip) : undefined; diff --git a/server/routers/badger/exchangeSession.ts b/server/routers/badger/exchangeSession.ts index 4017cfea..bde5518b 100644 --- a/server/routers/badger/exchangeSession.ts +++ b/server/routers/badger/exchangeSession.ts @@ -19,6 +19,7 @@ import { import { SESSION_COOKIE_EXPIRES as RESOURCE_SESSION_COOKIE_EXPIRES } from "@server/auth/sessions/resource"; import config from "@server/lib/config"; import { response } from "@server/lib/response"; +import { stripPortFromHost } from "@server/lib/ip"; const exchangeSessionBodySchema = z.object({ requestToken: z.string(), @@ -62,26 +63,7 @@ export async function exchangeSession( cleanHost = cleanHost.slice(0, -1 * matched.length); } - const clientIp = requestIp - ? (() => { - if (requestIp.startsWith("[") && requestIp.includes("]")) { - const ipv6Match = requestIp.match(/\[(.*?)\]/); - if (ipv6Match) { - return ipv6Match[1]; - } - } - - const ipv4Pattern = /^(\d{1,3}\.){3}\d{1,3}/; - if (ipv4Pattern.test(requestIp)) { - const lastColonIndex = requestIp.lastIndexOf(":"); - if (lastColonIndex !== -1) { - return requestIp.substring(0, lastColonIndex); - } - } - - return requestIp; - })() - : undefined; + const clientIp = requestIp ? stripPortFromHost(requestIp) : undefined; const [resource] = await db .select() diff --git a/server/routers/badger/logRequestAudit.ts b/server/routers/badger/logRequestAudit.ts index 80e3f419..aade2f98 100644 --- a/server/routers/badger/logRequestAudit.ts +++ b/server/routers/badger/logRequestAudit.ts @@ -3,6 +3,7 @@ import logger from "@server/logger"; import { and, eq, lt } from "drizzle-orm"; import cache from "@server/lib/cache"; import { calculateCutoffTimestamp } from "@server/lib/cleanupLogs"; +import { stripPortFromHost } from "@server/lib/ip"; /** @@ -208,26 +209,7 @@ export async function logRequestAudit( } const clientIp = body.requestIp - ? (() => { - if ( - body.requestIp.startsWith("[") && - body.requestIp.includes("]") - ) { - // if brackets are found, extract the IPv6 address from between the brackets - const ipv6Match = body.requestIp.match(/\[(.*?)\]/); - if (ipv6Match) { - return ipv6Match[1]; - } - } - - // ivp4 - // split at last colon - const lastColonIndex = body.requestIp.lastIndexOf(":"); - if (lastColonIndex !== -1) { - return body.requestIp.substring(0, lastColonIndex); - } - return body.requestIp; - })() + ? stripPortFromHost(body.requestIp) : undefined; // Add to buffer instead of writing directly to DB diff --git a/server/routers/badger/verifySession.ts b/server/routers/badger/verifySession.ts index ca7c913e..0da83c03 100644 --- a/server/routers/badger/verifySession.ts +++ b/server/routers/badger/verifySession.ts @@ -21,7 +21,7 @@ import { resourceSessions } from "@server/db"; import config from "@server/lib/config"; -import { isIpInCidr } from "@server/lib/ip"; +import { isIpInCidr, stripPortFromHost } from "@server/lib/ip"; import { response } from "@server/lib/response"; import logger from "@server/logger"; import HttpCode from "@server/types/HttpCode"; @@ -110,37 +110,7 @@ export async function verifyResourceSession( const clientHeaderAuth = extractBasicAuth(headers); const clientIp = requestIp - ? (() => { - const isNewerBadger = - badgerVersion && - semver.valid(badgerVersion) && - semver.gte(badgerVersion, "1.3.1"); - - if (isNewerBadger) { - return requestIp; - } - - if (requestIp.startsWith("[") && requestIp.includes("]")) { - // if brackets are found, extract the IPv6 address from between the brackets - const ipv6Match = requestIp.match(/\[(.*?)\]/); - if (ipv6Match) { - return ipv6Match[1]; - } - } - - // Check if it looks like IPv4 (contains dots and matches IPv4 pattern) - // IPv4 format: x.x.x.x where x is 0-255 - const ipv4Pattern = /^(\d{1,3}\.){3}\d{1,3}/; - if (ipv4Pattern.test(requestIp)) { - const lastColonIndex = requestIp.lastIndexOf(":"); - if (lastColonIndex !== -1) { - return requestIp.substring(0, lastColonIndex); - } - } - - // Return as is - return requestIp; - })() + ? stripPortFromHost(requestIp, badgerVersion) : undefined; logger.debug("Client IP:", { clientIp });