Skip to content

Commit

Permalink
Merge pull request #1898 from cprussin/block-vpn
Browse files Browse the repository at this point in the history
feat(staking): add vpn detection and blocking
  • Loading branch information
cprussin authored Sep 11, 2024
2 parents 2edeeba + a5242c7 commit 1e27b38
Show file tree
Hide file tree
Showing 10 changed files with 105 additions and 366 deletions.
1 change: 1 addition & 0 deletions apps/staking/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"dnum": "^2.13.1",
"next": "^14.2.5",
"pino": "^9.3.2",
"proxycheck-ts": "^0.0.11",
"react": "^18.3.1",
"react-aria": "^3.34.3",
"react-aria-components": "^1.3.3",
Expand Down
1 change: 0 additions & 1 deletion apps/staking/src/app/blocked/page.tsx

This file was deleted.

1 change: 1 addition & 0 deletions apps/staking/src/app/region-blocked/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { RegionBlocked as default } from "../../components/Blocked";
1 change: 1 addition & 0 deletions apps/staking/src/app/vpn-blocked/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { VpnBlocked as default } from "../../components/Blocked";
18 changes: 14 additions & 4 deletions apps/staking/src/components/Blocked/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
import { LinkButton } from "../Button";

export const Blocked = () => (
export const RegionBlocked = () => (
<Blocked reason="This program is currently unavailable to users in your region" />
);

export const VpnBlocked = () => (
<Blocked reason="You cannot access this app via a VPN. Please disable your VPN and try aagin." />
);

type Props = {
reason: string;
};

const Blocked = ({ reason }: Props) => (
<main className="grid size-full place-content-center py-20 text-center">
<h1 className="mb-8 text-4xl font-semibold text-pythpurple-400">
{"We're sorry"}
</h1>
<p className="mb-20 text-lg">
This program is currently unavailable to users in your region
</p>
<p className="mb-20 text-lg">{reason}</p>
<LinkButton
className="place-self-center px-24 py-3"
href="https://www.pyth.network"
Expand Down
10 changes: 8 additions & 2 deletions apps/staking/src/components/WalletButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ import {
SubmenuTrigger,
} from "react-aria-components";

import { BLOCKED_SEGMENT } from "../../config/isomorphic";
import {
REGION_BLOCKED_SEGMENT,
VPN_BLOCKED_SEGMENT,
} from "../../config/isomorphic";
import {
StateType as ApiStateType,
type States,
Expand All @@ -49,8 +52,11 @@ type Props = Omit<ComponentProps<typeof Button>, "onClick" | "children">;

export const WalletButton = (props: Props) => {
const segment = useSelectedLayoutSegment();
const isBlocked =
segment === REGION_BLOCKED_SEGMENT || segment === VPN_BLOCKED_SEGMENT;

// eslint-disable-next-line unicorn/no-null
return segment === BLOCKED_SEGMENT ? null : <WalletButtonImpl {...props} />;
return isBlocked ? null : <WalletButtonImpl {...props} />;
};

const WalletButtonImpl = (props: Props) => {
Expand Down
10 changes: 9 additions & 1 deletion apps/staking/src/config/isomorphic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,12 @@ export const IS_PRODUCTION_BUILD = process.env.NODE_ENV === "production";
*
* Don't change unless you also change the relevant app route path to match.
*/
export const BLOCKED_SEGMENT = "blocked";
export const REGION_BLOCKED_SEGMENT = "region-blocked";

/**
* Similar to `REGION_BLOCKED_SEGMENT`; this is where vpn-blocked traffic will
* be rewritten to.
*
* Don't change unless you also change the relevant app route path to match.
*/
export const VPN_BLOCKED_SEGMENT = "vpn-blocked";
1 change: 1 addition & 0 deletions apps/staking/src/config/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const BLOCKED_REGIONS =
: process.env.BLOCKED_REGIONS.split(",").map((region) =>
region.toLowerCase().trim(),
);
export const PROXYCHECK_API_KEY = demandInProduction("PROXYCHECK_API_KEY");

class MissingEnvironmentError extends Error {
constructor(name: string) {
Expand Down
43 changes: 34 additions & 9 deletions apps/staking/src/middleware.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,47 @@
import { type NextRequest, NextResponse } from "next/server";
import ProxyCheck from "proxycheck-ts";

import { BLOCKED_SEGMENT } from "./config/isomorphic";
import { BLOCKED_REGIONS } from "./config/server";
import {
REGION_BLOCKED_SEGMENT,
VPN_BLOCKED_SEGMENT,
} from "./config/isomorphic";
import { BLOCKED_REGIONS, PROXYCHECK_API_KEY } from "./config/server";

export const middleware = (request: NextRequest) => {
if (blockRequest(request)) {
return NextResponse.rewrite(new URL(`/${BLOCKED_SEGMENT}`, request.url));
} else if (request.nextUrl.pathname.startsWith("/blocked")) {
return NextResponse.rewrite(new URL("/not-found", request.url));
const proxyCheckClient = PROXYCHECK_API_KEY
? new ProxyCheck({ api_key: PROXYCHECK_API_KEY })
: undefined;

export const middleware = async (request: NextRequest) => {
if (isRegionBlocked(request)) {
return NextResponse.rewrite(
new URL(`/${REGION_BLOCKED_SEGMENT}`, request.url),
);
} else if (await isProxyBlocked(request)) {
return NextResponse.rewrite(
new URL(`/${VPN_BLOCKED_SEGMENT}`, request.url),
);
} else {
return;
const { pathname } = request.nextUrl;
return pathname.startsWith(`/${REGION_BLOCKED_SEGMENT}`) ||
pathname.startsWith(`/${VPN_BLOCKED_SEGMENT}`)
? NextResponse.rewrite(new URL("/not-found", request.url))
: undefined;
}
};

const blockRequest = (request: NextRequest) =>
const isRegionBlocked = (request: NextRequest) =>
request.geo?.country !== undefined &&
BLOCKED_REGIONS.includes(request.geo.country.toLowerCase());

const isProxyBlocked = async ({ ip }: NextRequest) => {
if (proxyCheckClient === undefined || ip === undefined) {
return false;
} else {
const result = await proxyCheckClient.checkIP(ip, { vpn: 2 });
return result[ip]?.proxy === "yes";
}
};

export const config = {
matcher: [
"/((?!_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)",
Expand Down
Loading

0 comments on commit 1e27b38

Please sign in to comment.