diff --git a/apps/staking/package.json b/apps/staking/package.json index 7bf4dcf86b..a37d482a68 100644 --- a/apps/staking/package.json +++ b/apps/staking/package.json @@ -40,6 +40,7 @@ "clsx": "^2.1.1", "dnum": "^2.13.1", "framer-motion": "^11.3.8", + "ip-range-check": "^0.2.0", "next": "^14.2.5", "pino": "^9.3.2", "proxycheck-ts": "^0.0.11", diff --git a/apps/staking/src/config/server.ts b/apps/staking/src/config/server.ts index 9c83e767b5..adafd096ca 100644 --- a/apps/staking/src/config/server.ts +++ b/apps/staking/src/config/server.ts @@ -54,6 +54,7 @@ export const WALLETCONNECT_PROJECT_ID = demandInProduction( export const MAINNET_RPC = process.env.MAINNET_RPC; export const HERMES_URL = getOr("HERMES_URL", "https://hermes.pyth.network"); export const BLOCKED_REGIONS = transformOr("BLOCKED_REGIONS", fromCsv, []); +export const IP_ALLOWLIST = transformOr("IP_ALLOWLIST", fromCsv, []); export const GOVERNANCE_ONLY_REGIONS = transformOr( "GOVERNANCE_ONLY_REGIONS", fromCsv, diff --git a/apps/staking/src/middleware.ts b/apps/staking/src/middleware.ts index bd90b2f06c..26e8a14a6f 100644 --- a/apps/staking/src/middleware.ts +++ b/apps/staking/src/middleware.ts @@ -1,3 +1,4 @@ +import ipRangeCheck from "ip-range-check"; import { type NextRequest, NextResponse } from "next/server"; import ProxyCheck from "proxycheck-ts"; @@ -10,6 +11,7 @@ import { BLOCKED_REGIONS, GOVERNANCE_ONLY_REGIONS, PROXYCHECK_API_KEY, + IP_ALLOWLIST, } from "./config/server"; const GEO_BLOCKED_PATH = `/${GEO_BLOCKED_SEGMENT}`; @@ -21,22 +23,32 @@ const proxyCheckClient = PROXYCHECK_API_KEY : undefined; export const middleware = async (request: NextRequest) => { - if (await isProxyBlocked(request)) { - return rewrite(request, VPN_BLOCKED_PATH); - } else if (isGovernanceOnlyRegion(request)) { - return rewrite(request, GOVERNANCE_ONLY_PATH); - } else if (isRegionBlocked(request)) { - return rewrite(request, GEO_BLOCKED_PATH); - } else if (isBlockedSegment(request)) { - return rewrite(request, "/not-found"); + if (isIpAllowlisted(request)) { + return isBlockedSegment(request) + ? rewrite(request, "/not-found") + : undefined; } else { - return; + if (await isProxyBlocked(request)) { + return rewrite(request, VPN_BLOCKED_PATH); + } else if (isGovernanceOnlyRegion(request)) { + return rewrite(request, GOVERNANCE_ONLY_PATH); + } else if (isRegionBlocked(request)) { + return rewrite(request, GEO_BLOCKED_PATH); + } else if (isBlockedSegment(request)) { + return rewrite(request, "/not-found"); + } else { + return; + } } }; const rewrite = (request: NextRequest, path: string) => NextResponse.rewrite(new URL(path, request.url)); +const isIpAllowlisted = ({ ip }: NextRequest) => + ip !== undefined && + IP_ALLOWLIST.some((allowedRange) => ipRangeCheck(ip, allowedRange)); + const isGovernanceOnlyRegion = ({ geo }: NextRequest) => geo?.country !== undefined && GOVERNANCE_ONLY_REGIONS.includes(geo.country.toLowerCase()); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b55e205c08..794a9b42c4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -381,6 +381,9 @@ importers: framer-motion: specifier: ^11.3.8 version: 11.3.8(@emotion/is-prop-valid@1.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + ip-range-check: + specifier: ^0.2.0 + version: 0.2.0 next: specifier: ^14.2.5 version: 14.2.6(@babel/core@7.24.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -13853,6 +13856,9 @@ packages: peerDependencies: fp-ts: ^2.5.0 + ip-range-check@0.2.0: + resolution: {integrity: sha512-oaM3l/3gHbLlt/tCWLvt0mj1qUaI+STuRFnUvARGCujK9vvU61+2JsDpmkMzR4VsJhuFXWWgeKKVnwwoFfzCqw==} + ip@2.0.0: resolution: {integrity: sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==} @@ -42721,6 +42727,10 @@ snapshots: dependencies: fp-ts: 2.16.9 + ip-range-check@0.2.0: + dependencies: + ipaddr.js: 1.9.1 + ip@2.0.0: {} ipaddr.js@1.9.1: {}