diff --git a/next.config.mjs b/next.config.mjs index 8aa55005a7..e4c9d08baa 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -13,8 +13,6 @@ const withPWA = NextPwa({ /** @type {import('next').NextConfig} */ const nextConfig = { - output: 'export', // static site export - images: { unoptimized: true, }, diff --git a/package.json b/package.json index eea885d6c4..19f40f3446 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "test:coverage": "yarn test --coverage --watchAll=false", "cmp": "./scripts/cmp.sh", "routes": "node scripts/generate-routes.js > src/config/routes.ts && prettier -w src/config/routes.ts && cat src/config/routes.ts", - "css-vars": "ts-node-esm ./scripts/css-vars.ts > ./src/styles/vars.css && prettier -w src/styles/vars.css", + "css-vars": "npx -y tsx ./scripts/css-vars.ts > ./src/styles/vars.css && prettier -w src/styles/vars.css", "generate-types": "typechain --target ethers-v5 --out-dir src/types/contracts ./node_modules/@safe-global/safe-deployments/dist/assets/**/*.json ./node_modules/@safe-global/safe-modules-deployments/dist/assets/**/*.json ./node_modules/@openzeppelin/contracts/build/contracts/ERC20.json ./node_modules/@openzeppelin/contracts/build/contracts/ERC721.json", "postinstall": "yarn generate-types && yarn css-vars", "analyze": "cross-env ANALYZE=true yarn build", @@ -29,7 +29,7 @@ "static-serve": "yarn build && yarn serve" }, "engines": { - "node": ">=16" + "node": ">=18" }, "pre-commit": [ "lint" diff --git a/src/components/terms/clabs.tsx b/src/components/terms/clabs.tsx new file mode 100644 index 0000000000..12ddf1c6fd --- /dev/null +++ b/src/components/terms/clabs.tsx @@ -0,0 +1,14 @@ +import { Typography } from '@mui/material' + +const Terms = () => { + return ( +
+ + Terms and Conditions + +

This app shall not be accessed in any country or region sanctioned by US Law.

+
+ ) +} + +export default Terms diff --git a/src/pages/apps/custom.tsx b/src/pages/apps/custom.tsx index 56932a0511..e839e64c03 100644 --- a/src/pages/apps/custom.tsx +++ b/src/pages/apps/custom.tsx @@ -1,5 +1,5 @@ import { useState } from 'react' -import type { NextPage } from 'next' +import type { NextPage, GetServerSideProps } from 'next' import Head from 'next/head' import { useSafeApps } from '@/hooks/safe-apps/useSafeApps' @@ -8,6 +8,11 @@ import SafeAppList from '@/components/safe-apps/SafeAppList' import SafeAppsSDKLink from '@/components/safe-apps/SafeAppsSDKLink' import { RemoveCustomAppModal } from '@/components/safe-apps/RemoveCustomAppModal' import type { SafeAppData } from '@safe-global/safe-gateway-typescript-sdk' +import { checkForForbiddenRegions } from '@/utils/geo' + +export const getServerSideProps: GetServerSideProps<{}> = async ({ req, res }) => { + return checkForForbiddenRegions(req, res) ?? { props: {} } +} const CustomSafeApps: NextPage = () => { // TODO: create a custom hook instead of use useSafeApps diff --git a/src/pages/apps/index.tsx b/src/pages/apps/index.tsx index 347ec4b99e..25938eda1f 100644 --- a/src/pages/apps/index.tsx +++ b/src/pages/apps/index.tsx @@ -8,6 +8,12 @@ import SafeAppsSDKLink from '@/components/safe-apps/SafeAppsSDKLink' import SafeAppsHeader from '@/components/safe-apps/SafeAppsHeader' import SafeAppList from '@/components/safe-apps/SafeAppList' import { AppRoutes } from '@/config/routes' +import type { GetServerSideProps } from 'next' +import { checkForForbiddenRegions } from '@/utils/geo' + +export const getServerSideProps: GetServerSideProps<{}> = async ({ req, res }) => { + return checkForForbiddenRegions(req, res) ?? { props: {} } +} const SafeApps: NextPage = () => { const router = useRouter() diff --git a/src/pages/apps/open.tsx b/src/pages/apps/open.tsx index 1c83cdd96b..98514286be 100644 --- a/src/pages/apps/open.tsx +++ b/src/pages/apps/open.tsx @@ -1,8 +1,8 @@ -import type { NextPage } from 'next' +import type { NextPage, GetServerSideProps } from 'next' import { useRouter } from 'next/router' import { useCallback } from 'react' import { Box, CircularProgress } from '@mui/material' - +import { checkForForbiddenRegions } from '@/utils/geo' import { useSafeAppUrl } from '@/hooks/safe-apps/useSafeAppUrl' import { useSafeApps } from '@/hooks/safe-apps/useSafeApps' import SafeAppsInfoModal from '@/components/safe-apps/SafeAppsInfoModal' @@ -84,3 +84,7 @@ const SafeApps: NextPage = () => { } export default SafeApps + +export const getServerSideProps: GetServerSideProps<{}> = async ({ req, res }) => { + return checkForForbiddenRegions(req, res) ?? { props: {} } +} diff --git a/src/pages/balances/index.tsx b/src/pages/balances/index.tsx index e42868d988..43e1d95bf7 100644 --- a/src/pages/balances/index.tsx +++ b/src/pages/balances/index.tsx @@ -1,10 +1,11 @@ -import type { NextPage } from 'next' +import type { NextPage, GetServerSideProps } from 'next' import Head from 'next/head' import AssetsTable from '@/components/balances/AssetsTable' import AssetsHeader from '@/components/balances/AssetsHeader' import useBalances from '@/hooks/useBalances' import { useState } from 'react' +import { checkForForbiddenRegions } from '@/utils/geo' import PagePlaceholder from '@/components/common/PagePlaceholder' import NoAssetsIcon from '@/public/images/balances/no-assets.svg' @@ -41,3 +42,7 @@ const Balances: NextPage = () => { } export default Balances + +export const getServerSideProps: GetServerSideProps<{}> = async ({ req, res }) => { + return checkForForbiddenRegions(req, res) ?? { props: {} } +} diff --git a/src/pages/balances/nfts.tsx b/src/pages/balances/nfts.tsx index 35a4d27fa7..929d298f01 100644 --- a/src/pages/balances/nfts.tsx +++ b/src/pages/balances/nfts.tsx @@ -1,5 +1,5 @@ import { type ReactElement, memo } from 'react' -import type { NextPage } from 'next' +import type { NextPage, GetServerSideProps } from 'next' import Head from 'next/head' import { Grid, Skeleton, Typography } from '@mui/material' import AssetsHeader from '@/components/balances/AssetsHeader' @@ -7,6 +7,7 @@ import NftCollections from '@/components/nfts/NftCollections' import SafeAppCard from '@/components/safe-apps/SafeAppCard' import { SafeAppsTag } from '@/config/constants' import { useRemoteSafeApps } from '@/hooks/safe-apps/useRemoteSafeApps' +import { checkForForbiddenRegions } from '@/utils/geo' // `React.memo` requires a `displayName` const NftApps = memo(function NftApps(): ReactElement | null { @@ -62,3 +63,7 @@ const NFTs: NextPage = () => { } export default NFTs + +export const getServerSideProps: GetServerSideProps<{}> = async ({ req, res }) => { + return checkForForbiddenRegions(req, res) ?? { props: {} } +} diff --git a/src/pages/home.tsx b/src/pages/home.tsx index 62590cca4c..f5c524bfc4 100644 --- a/src/pages/home.tsx +++ b/src/pages/home.tsx @@ -1,7 +1,8 @@ -import type { NextPage } from 'next' +import type { NextPage, GetServerSideProps } from 'next' import Head from 'next/head' import Dashboard from '@/components/dashboard' +import { checkForForbiddenRegions } from '@/utils/geo' const Home: NextPage = () => { return ( @@ -18,3 +19,7 @@ const Home: NextPage = () => { } export default Home + +export const getServerSideProps: GetServerSideProps<{}> = async ({ req, res }) => { + return checkForForbiddenRegions(req, res) ?? { props: {} } +} diff --git a/src/pages/new-safe/create.tsx b/src/pages/new-safe/create.tsx index d8f53e2c55..082ee6d6a3 100644 --- a/src/pages/new-safe/create.tsx +++ b/src/pages/new-safe/create.tsx @@ -1,5 +1,7 @@ import Head from 'next/head' -import type { NextPage } from 'next' +import type { NextPage, GetServerSideProps } from 'next' + +import { checkForForbiddenRegions } from '@/utils/geo' import CreateSafe from '@/components/new-safe/create' @@ -16,3 +18,7 @@ const Open: NextPage = () => { } export default Open + +export const getServerSideProps: GetServerSideProps<{}> = async ({ req, res }) => { + return checkForForbiddenRegions(req, res) ?? { props: {} } +} diff --git a/src/pages/new-safe/load.tsx b/src/pages/new-safe/load.tsx index 427436f417..be0636a5d8 100644 --- a/src/pages/new-safe/load.tsx +++ b/src/pages/new-safe/load.tsx @@ -1,7 +1,8 @@ -import type { NextPage } from 'next' +import type { NextPage, GetServerSideProps } from 'next' import Head from 'next/head' import { useRouter } from 'next/router' import LoadSafe, { loadSafeDefaultData } from '@/components/new-safe/load' +import { checkForForbiddenRegions } from '@/utils/geo' const Load: NextPage = () => { const router = useRouter() @@ -24,3 +25,7 @@ const Load: NextPage = () => { } export default Load + +export const getServerSideProps: GetServerSideProps<{}> = async ({ req, res }) => { + return checkForForbiddenRegions(req, res) ?? { props: {} } +} diff --git a/src/pages/share/safe-app.tsx b/src/pages/share/safe-app.tsx index fcf32e74f1..9acba17ee3 100644 --- a/src/pages/share/safe-app.tsx +++ b/src/pages/share/safe-app.tsx @@ -1,11 +1,13 @@ import { useEffect } from 'react' import Head from 'next/head' +import type { GetServerSideProps } from 'next' import { useRouter } from 'next/router' import { Box, CircularProgress } from '@mui/material' import { useSafeAppUrl } from '@/hooks/safe-apps/useSafeAppUrl' import { useChainFromQueryParams } from '@/hooks/safe-apps/useChainFromQueryParams' import { SafeAppLanding } from '@/components/safe-apps/SafeAppLandingPage' import { AppRoutes } from '@/config/routes' +import { checkForForbiddenRegions } from '@/utils/geo' const ShareSafeApp = () => { const router = useRouter() @@ -50,3 +52,7 @@ const ShareSafeApp = () => { } export default ShareSafeApp + +export const getServerSideProps: GetServerSideProps<{}> = async ({ req, res }) => { + return checkForForbiddenRegions(req, res) ?? { props: {} } +} diff --git a/src/pages/terms.tsx b/src/pages/terms.tsx index 369d05aad3..275a5ec738 100644 --- a/src/pages/terms.tsx +++ b/src/pages/terms.tsx @@ -1,6 +1,7 @@ import type { NextPage } from 'next' import Head from 'next/head' import SafeTerms from '@/components/terms' +import ClabsTerms from '@/components/terms/clabs' import { IS_OFFICIAL_HOST } from '@/config/constants' const Imprint: NextPage = () => { @@ -10,7 +11,7 @@ const Imprint: NextPage = () => { {'Safe{Wallet} – Terms'} -
{IS_OFFICIAL_HOST && }
+
{IS_OFFICIAL_HOST ? : }
) } diff --git a/src/pages/transactions/history.tsx b/src/pages/transactions/history.tsx index bfa605e209..c6cccccd3b 100644 --- a/src/pages/transactions/history.tsx +++ b/src/pages/transactions/history.tsx @@ -1,4 +1,4 @@ -import type { NextPage } from 'next' +import type { NextPage, GetServerSideProps } from 'next' import Head from 'next/head' import useTxHistory from '@/hooks/useTxHistory' import PaginatedTxns from '@/components/common/PaginatedTxns' @@ -10,6 +10,7 @@ import ExpandLessIcon from '@mui/icons-material/ExpandLess' import ExpandMoreIcon from '@mui/icons-material/ExpandMore' import TxFilterForm from '@/components/transactions/TxFilterForm' import { useTxFilter } from '@/utils/tx-history-filter' +import { checkForForbiddenRegions } from '@/utils/geo' const History: NextPage = () => { const [filter] = useTxFilter() @@ -45,3 +46,7 @@ const History: NextPage = () => { } export default History + +export const getServerSideProps: GetServerSideProps<{}> = async ({ req, res }) => { + return checkForForbiddenRegions(req, res) ?? { props: {} } +} diff --git a/src/pages/transactions/index.tsx b/src/pages/transactions/index.tsx index 177c1d0e43..4454862118 100644 --- a/src/pages/transactions/index.tsx +++ b/src/pages/transactions/index.tsx @@ -1,3 +1,9 @@ import HistoryPage from './history' +import type { GetServerSideProps } from 'next' +import { checkForForbiddenRegions } from '@/utils/geo' export default HistoryPage + +export const getServerSideProps: GetServerSideProps<{}> = async ({ req, res }) => { + return checkForForbiddenRegions(req, res) ?? { props: {} } +} diff --git a/src/pages/transactions/queue.tsx b/src/pages/transactions/queue.tsx index 8c7c8d0b1f..7870c186a6 100644 --- a/src/pages/transactions/queue.tsx +++ b/src/pages/transactions/queue.tsx @@ -1,4 +1,4 @@ -import type { NextPage } from 'next' +import type { NextPage, GetServerSideProps } from 'next' import Head from 'next/head' import useTxQueue from '@/hooks/useTxQueue' import PaginatedTxns from '@/components/common/PaginatedTxns' @@ -7,6 +7,7 @@ import BatchExecuteButton from '@/components/transactions/BatchExecuteButton' import { Box } from '@mui/material' import { BatchExecuteHoverProvider } from '@/components/transactions/BatchExecuteButton/BatchExecuteHoverProvider' import { usePendingTxsQueue, useShowUnsignedQueue } from '@/hooks/usePendingTxs' +import { checkForForbiddenRegions } from '@/utils/geo' const Queue: NextPage = () => { const showPending = useShowUnsignedQueue() @@ -37,3 +38,7 @@ const Queue: NextPage = () => { } export default Queue + +export const getServerSideProps: GetServerSideProps<{}> = async ({ req, res }) => { + return checkForForbiddenRegions(req, res) ?? { props: {} } +} diff --git a/src/pages/transactions/tx.tsx b/src/pages/transactions/tx.tsx index cc89616eea..3aae3b13a6 100644 --- a/src/pages/transactions/tx.tsx +++ b/src/pages/transactions/tx.tsx @@ -1,8 +1,10 @@ -import type { NextPage } from 'next' +import type { NextPage, GetServerSideProps } from 'next' + import Head from 'next/head' import SingleTx from '@/components/transactions/SingleTx' import Typography from '@mui/material/Typography' +import { checkForForbiddenRegions } from '@/utils/geo' const SingleTransaction: NextPage = () => { return ( @@ -23,3 +25,7 @@ const SingleTransaction: NextPage = () => { } export default SingleTransaction + +export const getServerSideProps: GetServerSideProps<{}> = async ({ req, res }) => { + return checkForForbiddenRegions(req, res) ?? { props: {} } +} diff --git a/src/pages/welcome.tsx b/src/pages/welcome.tsx index 6a6506b4d6..81d589db2e 100644 --- a/src/pages/welcome.tsx +++ b/src/pages/welcome.tsx @@ -1,7 +1,10 @@ -import type { NextPage } from 'next' +import type { NextPage, GetServerSideProps } from 'next' + import Head from 'next/head' import NewSafe from '@/components/welcome/NewSafe' +import { checkForForbiddenRegions } from '@/utils/geo' + const Welcome: NextPage = () => { return ( <> @@ -15,3 +18,7 @@ const Welcome: NextPage = () => { } export default Welcome + +export const getServerSideProps: GetServerSideProps<{}> = async ({ req, res }) => { + return checkForForbiddenRegions(req, res) ?? { props: {} } +} diff --git a/src/utils/geo.ts b/src/utils/geo.ts new file mode 100644 index 0000000000..00cce6a9cd --- /dev/null +++ b/src/utils/geo.ts @@ -0,0 +1,35 @@ +import type { IncomingMessage, OutgoingMessage } from 'http' + +export const HTTP_HEADER_COUNTRY = 'x-vercel-ip-country' +export const HTTP_HEADER_REGION = 'x-vercel-ip-country-region' + +// korea, iran, cuba, syria +const RESTRICTED_COUNTRIES = new Set(['KP', 'IR', 'CU', 'SY']) + +// https://www.iso.org/obp/ui/#iso:code:3166:UA although listed with UA prefix. the header/api recieved that and just used the number +const crimea = '43' +const luhansk = '09' +const donetska = '14' +//https://en.wikipedia.org/wiki/Russian-occupied_territories_of_Ukraine +const RESTRICED_SUBREGION: Record> = { + UA: new Set([crimea, luhansk, donetska]), +} + +export function isForbiddenLand(iso3166Country: string, iso3166Region: string) { + const iso3166CountryUppercase = iso3166Country?.toUpperCase() + return ( + RESTRICTED_COUNTRIES.has(iso3166CountryUppercase) || + RESTRICED_SUBREGION[iso3166CountryUppercase]?.has(iso3166Region) + ) +} + +export function checkForForbiddenRegions(req: IncomingMessage, res: OutgoingMessage) { + const coutry = req.headers[HTTP_HEADER_COUNTRY] as string + const region = req.headers[HTTP_HEADER_REGION] as string + + console.info('country', coutry, 'region', region) + + if (isForbiddenLand(coutry, region)) { + return { redirect: { destination: '/terms', permanent: false } } + } +}