mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-28 15:56:39 +00:00
add stripPortFromHost and reuse everywhere
This commit is contained in:
@@ -4,6 +4,7 @@ import { and, eq, isNotNull } from "drizzle-orm";
|
|||||||
import config from "@server/lib/config";
|
import config from "@server/lib/config";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
|
import semver from "semver";
|
||||||
|
|
||||||
interface IPRange {
|
interface IPRange {
|
||||||
start: bigint;
|
start: bigint;
|
||||||
@@ -683,3 +684,35 @@ export function parsePortRangeString(
|
|||||||
|
|
||||||
return result;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import logger from "@server/logger";
|
|||||||
import { and, eq, lt } from "drizzle-orm";
|
import { and, eq, lt } from "drizzle-orm";
|
||||||
import cache from "@server/lib/cache";
|
import cache from "@server/lib/cache";
|
||||||
import { calculateCutoffTimestamp } from "@server/lib/cleanupLogs";
|
import { calculateCutoffTimestamp } from "@server/lib/cleanupLogs";
|
||||||
|
import { stripPortFromHost } from "@server/lib/ip";
|
||||||
|
|
||||||
async function getAccessDays(orgId: string): Promise<number> {
|
async function getAccessDays(orgId: string): Promise<number> {
|
||||||
// check cache first
|
// check cache first
|
||||||
@@ -116,19 +117,7 @@ export async function logAccessAudit(data: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const clientIp = data.requestIp
|
const clientIp = data.requestIp
|
||||||
? (() => {
|
? stripPortFromHost(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;
|
|
||||||
})()
|
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const countryCode = data.requestIp
|
const countryCode = data.requestIp
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { eq, and, gt } from "drizzle-orm";
|
|||||||
import { createSession, generateSessionToken } from "@server/auth/sessions/app";
|
import { createSession, generateSessionToken } from "@server/auth/sessions/app";
|
||||||
import { encodeHexLowerCase } from "@oslojs/encoding";
|
import { encodeHexLowerCase } from "@oslojs/encoding";
|
||||||
import { sha256 } from "@oslojs/crypto/sha2";
|
import { sha256 } from "@oslojs/crypto/sha2";
|
||||||
|
import { stripPortFromHost } from "@server/lib/ip";
|
||||||
|
|
||||||
const paramsSchema = z.object({
|
const paramsSchema = z.object({
|
||||||
code: z.string().min(1, "Code is required")
|
code: z.string().min(1, "Code is required")
|
||||||
@@ -27,30 +28,6 @@ export type PollDeviceWebAuthResponse = {
|
|||||||
token?: string;
|
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(
|
export async function pollDeviceWebAuth(
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
@@ -70,7 +47,7 @@ export async function pollDeviceWebAuth(
|
|||||||
try {
|
try {
|
||||||
const { code } = parsedParams.data;
|
const { code } = parsedParams.data;
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const requestIp = extractIpFromRequest(req);
|
const requestIp = req.ip ? stripPortFromHost(req.ip) : undefined;
|
||||||
|
|
||||||
// Hash the code before querying
|
// Hash the code before querying
|
||||||
const hashedCode = hashDeviceCode(code);
|
const hashedCode = hashDeviceCode(code);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { TimeSpan } from "oslo";
|
|||||||
import { maxmindLookup } from "@server/db/maxmind";
|
import { maxmindLookup } from "@server/db/maxmind";
|
||||||
import { encodeHexLowerCase } from "@oslojs/encoding";
|
import { encodeHexLowerCase } from "@oslojs/encoding";
|
||||||
import { sha256 } from "@oslojs/crypto/sha2";
|
import { sha256 } from "@oslojs/crypto/sha2";
|
||||||
|
import { stripPortFromHost } from "@server/lib/ip";
|
||||||
|
|
||||||
const bodySchema = z
|
const bodySchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -39,30 +40,6 @@ function hashDeviceCode(code: string): string {
|
|||||||
return encodeHexLowerCase(sha256(new TextEncoder().encode(code)));
|
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)
|
// Helper function to get city from IP (if available)
|
||||||
async function getCityFromIp(ip: string): Promise<string | undefined> {
|
async function getCityFromIp(ip: string): Promise<string | undefined> {
|
||||||
try {
|
try {
|
||||||
@@ -112,7 +89,7 @@ export async function startDeviceWebAuth(
|
|||||||
const hashedCode = hashDeviceCode(code);
|
const hashedCode = hashDeviceCode(code);
|
||||||
|
|
||||||
// Extract IP from request
|
// Extract IP from request
|
||||||
const ip = extractIpFromRequest(req);
|
const ip = req.ip ? stripPortFromHost(req.ip) : undefined;
|
||||||
|
|
||||||
// Get city (optional, may return undefined)
|
// Get city (optional, may return undefined)
|
||||||
const city = ip ? await getCityFromIp(ip) : undefined;
|
const city = ip ? await getCityFromIp(ip) : undefined;
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
import { SESSION_COOKIE_EXPIRES as RESOURCE_SESSION_COOKIE_EXPIRES } from "@server/auth/sessions/resource";
|
import { SESSION_COOKIE_EXPIRES as RESOURCE_SESSION_COOKIE_EXPIRES } from "@server/auth/sessions/resource";
|
||||||
import config from "@server/lib/config";
|
import config from "@server/lib/config";
|
||||||
import { response } from "@server/lib/response";
|
import { response } from "@server/lib/response";
|
||||||
|
import { stripPortFromHost } from "@server/lib/ip";
|
||||||
|
|
||||||
const exchangeSessionBodySchema = z.object({
|
const exchangeSessionBodySchema = z.object({
|
||||||
requestToken: z.string(),
|
requestToken: z.string(),
|
||||||
@@ -62,26 +63,7 @@ export async function exchangeSession(
|
|||||||
cleanHost = cleanHost.slice(0, -1 * matched.length);
|
cleanHost = cleanHost.slice(0, -1 * matched.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
const clientIp = requestIp
|
const clientIp = requestIp ? stripPortFromHost(requestIp) : undefined;
|
||||||
? (() => {
|
|
||||||
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 [resource] = await db
|
const [resource] = await db
|
||||||
.select()
|
.select()
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import logger from "@server/logger";
|
|||||||
import { and, eq, lt } from "drizzle-orm";
|
import { and, eq, lt } from "drizzle-orm";
|
||||||
import cache from "@server/lib/cache";
|
import cache from "@server/lib/cache";
|
||||||
import { calculateCutoffTimestamp } from "@server/lib/cleanupLogs";
|
import { calculateCutoffTimestamp } from "@server/lib/cleanupLogs";
|
||||||
|
import { stripPortFromHost } from "@server/lib/ip";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
||||||
@@ -208,26 +209,7 @@ export async function logRequestAudit(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const clientIp = body.requestIp
|
const clientIp = body.requestIp
|
||||||
? (() => {
|
? stripPortFromHost(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;
|
|
||||||
})()
|
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
// Add to buffer instead of writing directly to DB
|
// Add to buffer instead of writing directly to DB
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import {
|
|||||||
resourceSessions
|
resourceSessions
|
||||||
} from "@server/db";
|
} from "@server/db";
|
||||||
import config from "@server/lib/config";
|
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 { response } from "@server/lib/response";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
@@ -110,37 +110,7 @@ export async function verifyResourceSession(
|
|||||||
const clientHeaderAuth = extractBasicAuth(headers);
|
const clientHeaderAuth = extractBasicAuth(headers);
|
||||||
|
|
||||||
const clientIp = requestIp
|
const clientIp = requestIp
|
||||||
? (() => {
|
? stripPortFromHost(requestIp, badgerVersion)
|
||||||
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;
|
|
||||||
})()
|
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
logger.debug("Client IP:", { clientIp });
|
logger.debug("Client IP:", { clientIp });
|
||||||
|
|||||||
Reference in New Issue
Block a user