Skip to content

Commit

Permalink
[ECO-2505] Use Vercel headers to get location (#421)
Browse files Browse the repository at this point in the history
Co-authored-by: Matt <[email protected]>
  • Loading branch information
CRBl69 and xbtmatt authored Nov 28, 2024
1 parent a470875 commit 5256c7f
Show file tree
Hide file tree
Showing 7 changed files with 35 additions and 62 deletions.
4 changes: 0 additions & 4 deletions src/typescript/example.env
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,6 @@ ALLOWLISTER3K_URL="http://localhost:3000"
# A list of ISO 3166-2 codes of countries and regions to geoblock.
GEOBLOCKED='{"countries":[],"regions":[]}'

# The vpnapi.io API key.
# If GEOBLOCKED is set and non-empty, this needs to be set as well.
VPNAPI_IO_API_KEY=""

# The private key of the contract publisher.
# No "0x" at the start.
# Useful in testing only.
Expand Down
5 changes: 4 additions & 1 deletion src/typescript/frontend/src/app/verify_status/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import VerifyStatusPage from "components/pages/verify_status/VerifyStatusPage";
import { headers } from "next/headers";

const Verify = async () => {
return <VerifyStatusPage />;
const country = headers().get("x-vercel-ip-country");
const region = headers().get("x-vercel-ip-country-region");
return <VerifyStatusPage country={country} region={region} />;
};

export default Verify;
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@ const checkmarkOrX = (checkmark: boolean) => (
/>
);

export const ClientVerifyPage = () => {
export const ClientVerifyPage = ({
country,
region,
}: {
country: string | null;
region: string | null;
}) => {
const { account } = useAptos();
const { connected, disconnect } = useWallet();
const [galxe, setGalxe] = useState(false);
Expand Down Expand Up @@ -64,7 +70,7 @@ export const ClientVerifyPage = () => {
</motion.div>
)}
<ButtonWithConnectWalletFallback forceAllowConnect={true} arrow={false}>
<div className="flex flex-col uppercase mt-[20ch] gap-1">
<div className="flex flex-col uppercase mt-[30ch] gap-1">
<div>
Wallet address:{" "}
<span className="text-warning">
Expand All @@ -74,6 +80,8 @@ export const ClientVerifyPage = () => {
<div>Galxe: {checkmarkOrX(galxe)}</div>
<div>Custom allowlist: {checkmarkOrX(customAllowlisted)}</div>
<div>Passes geoblocking: {checkmarkOrX(!geoblocked)}</div>
<div>Country: {country ?? "unknown"}</div>
<div>Region: {region ?? "unknown"}</div>
<a
className="underline text-ec-blue"
href={process.env.NEXT_PUBLIC_GALXE_CAMPAIGN_REDIRECT}
Expand Down
5 changes: 3 additions & 2 deletions src/typescript/frontend/src/configs/local-storage-keys.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { parseJSON, stringifyJSON } from "utils";
import packages from "../../package.json";
import { MS_IN_ONE_DAY } from "components/charts/const";

const LOCAL_STORAGE_KEYS = {
theme: `${packages.name}_theme`,
Expand All @@ -8,10 +9,10 @@ const LOCAL_STORAGE_KEYS = {
settings: `${packages.name}_settings`,
};

const LOCAL_STORAGE_CACHE_TIME = {
export const LOCAL_STORAGE_CACHE_TIME = {
theme: Infinity,
language: Infinity,
geoblocking: 7 * 24 * 60 * 60 * 1000, // 7 days.
geoblocking: MS_IN_ONE_DAY,
settings: Infinity,
};

Expand Down
11 changes: 7 additions & 4 deletions src/typescript/frontend/src/hooks/use-is-user-geoblocked.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { useQuery } from "@tanstack/react-query";
import { MS_IN_ONE_DAY } from "components/charts/const";
import { readLocalStorageCache, writeLocalStorageCache } from "configs/local-storage-keys";
import {
LOCAL_STORAGE_CACHE_TIME,
readLocalStorageCache,
writeLocalStorageCache,
} from "configs/local-storage-keys";
import { isUserGeoblocked } from "utils/geolocation";

const SEVEN_DAYS_MS = 7 * MS_IN_ONE_DAY;
const FETCH_IS_GEOBLOCKED_CACHE_TIME = LOCAL_STORAGE_CACHE_TIME["geoblocking"];

const useIsUserGeoblocked = (args?: { explicitlyGeoblocked: boolean }) => {
// In some cases we may want to know if the query's return value is explicitly true.
Expand All @@ -20,7 +23,7 @@ const useIsUserGeoblocked = (args?: { explicitlyGeoblocked: boolean }) => {

return geoblocked;
},
staleTime: SEVEN_DAYS_MS,
staleTime: FETCH_IS_GEOBLOCKED_CACHE_TIME,
placeholderData: (prev) => prev,
});

Expand Down
9 changes: 0 additions & 9 deletions src/typescript/frontend/src/lib/server-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,8 @@ export const GEOBLOCKED: { countries: string[]; regions: string[] } = JSON.parse
);
export const GEOBLOCKING_ENABLED = GEOBLOCKED.countries.length > 0 || GEOBLOCKED.regions.length > 0;

if (GEOBLOCKING_ENABLED) {
if (process.env.VPNAPI_IO_API_KEY === "undefined") {
throw new Error(
"Geoblocking is enabled but environment variable VPNAPI_IO_API_KEY is undefined."
);
}
}

export const ALLOWLISTER3K_URL: string | undefined = process.env.ALLOWLISTER3K_URL;
export const REVALIDATION_TIME: number = Number(process.env.REVALIDATION_TIME);
export const VPNAPI_IO_API_KEY: string = process.env.VPNAPI_IO_API_KEY!;
export const PRE_LAUNCH_TEASER: boolean = process.env.PRE_LAUNCH_TEASER === "true";

if (
Expand Down
51 changes: 11 additions & 40 deletions src/typescript/frontend/src/utils/geolocation.ts
Original file line number Diff line number Diff line change
@@ -1,62 +1,33 @@
"use server";

import { GEOBLOCKED, GEOBLOCKING_ENABLED, VPNAPI_IO_API_KEY } from "lib/server-env";
import { GEOBLOCKED, GEOBLOCKING_ENABLED } from "lib/server-env";
import { headers } from "next/headers";

export type Location = {
country: string;
region: string;
countryCode: string;
regionCode: string;
vpn: boolean;
};

const isDisallowedLocation = (location: Location) => {
if (GEOBLOCKED.countries.includes(location.countryCode)) {
const isDisallowedLocation = ({ countryCode, regionCode }: Location) => {
if (GEOBLOCKED.countries.includes(countryCode)) {
return true;
}
const isoCode = `${location.countryCode}-${location.regionCode}`;
const isoCode = `${countryCode}-${regionCode}`;
if (GEOBLOCKED.regions.includes(isoCode)) {
return true;
}
return false;
};

export const isUserGeoblocked = async () => {
const ip = headers().get("x-real-ip");
if (!GEOBLOCKING_ENABLED) return false;
if (ip === "undefined" || typeof ip === "undefined" || ip === "null" || ip === null) {
const country = headers().get("x-vercel-ip-country");
const region = headers().get("x-vercel-ip-country-region");
if (typeof country !== "string" || typeof region !== "string") {
return true;
}
let location: Location;
try {
location = await getLocation(ip);
} catch (_) {
return true;
}
if (location.vpn) {
return true;
}
return isDisallowedLocation(location);
};

const ONE_DAY = 604800;

const getLocation: (ip: string) => Promise<Location> = async (ip) => {
if (ip === "undefined" || typeof ip === "undefined") {
throw new Error("IP is undefined");
}
const queryResult = await fetch(`https://vpnapi.io/api/${ip}?key=${VPNAPI_IO_API_KEY}`, {
next: { revalidate: ONE_DAY },
}).then((res) => res.json());

const data = {
country: queryResult.location.country,
region: queryResult.location.region,
countryCode: queryResult.location.country_code,
regionCode: queryResult.location.region_code,
vpn: queryResult.security.vpn,
};

return data;
return isDisallowedLocation({
countryCode: country,
regionCode: region,
});
};

0 comments on commit 5256c7f

Please sign in to comment.