middleware.ts 2.7 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. import ipRangeCheck from "ip-range-check";
  2. import { type NextRequest, NextResponse } from "next/server";
  3. import ProxyCheck from "proxycheck-ts";
  4. import {
  5. GEO_BLOCKED_SEGMENT,
  6. GOVERNANCE_ONLY_SEGMENT,
  7. VPN_BLOCKED_SEGMENT,
  8. } from "./config/isomorphic";
  9. import {
  10. BLOCKED_REGIONS,
  11. GOVERNANCE_ONLY_REGIONS,
  12. PROXYCHECK_API_KEY,
  13. IP_ALLOWLIST,
  14. VPN_ORGANIZATION_ALLOWLIST,
  15. } from "./config/server";
  16. const GEO_BLOCKED_PATH = `/${GEO_BLOCKED_SEGMENT}`;
  17. const GOVERNANCE_ONLY_PATH = `/${GOVERNANCE_ONLY_SEGMENT}`;
  18. const VPN_BLOCKED_PATH = `/${VPN_BLOCKED_SEGMENT}`;
  19. const proxyCheckClient = PROXYCHECK_API_KEY
  20. ? new ProxyCheck({ api_key: PROXYCHECK_API_KEY })
  21. : undefined;
  22. export const middleware = async (request: NextRequest) => {
  23. if (isIpAllowlisted(request)) {
  24. return isBlockedSegment(request)
  25. ? rewrite(request, "/not-found")
  26. : undefined;
  27. } else {
  28. if (await isProxyBlocked(request)) {
  29. return rewrite(request, VPN_BLOCKED_PATH);
  30. } else if (isGovernanceOnlyRegion(request)) {
  31. return rewrite(request, GOVERNANCE_ONLY_PATH);
  32. } else if (isRegionBlocked(request)) {
  33. return rewrite(request, GEO_BLOCKED_PATH);
  34. } else if (isBlockedSegment(request)) {
  35. return rewrite(request, "/not-found");
  36. } else {
  37. return;
  38. }
  39. }
  40. };
  41. const rewrite = (request: NextRequest, path: string) =>
  42. NextResponse.rewrite(new URL(path, request.url));
  43. const isIpAllowlisted = ({ ip }: NextRequest) =>
  44. ip !== undefined &&
  45. IP_ALLOWLIST.some((allowedRange) => ipRangeCheck(ip, allowedRange));
  46. const isGovernanceOnlyRegion = ({ geo }: NextRequest) =>
  47. geo?.country !== undefined &&
  48. GOVERNANCE_ONLY_REGIONS.includes(geo.country.toLowerCase());
  49. const isRegionBlocked = ({ geo }: NextRequest) =>
  50. geo?.country !== undefined &&
  51. BLOCKED_REGIONS.includes(geo.country.toLowerCase());
  52. const isProxyBlocked = async ({ ip }: NextRequest) => {
  53. if (proxyCheckClient === undefined || ip === undefined) {
  54. return false;
  55. } else {
  56. const response = await proxyCheckClient.checkIP(ip, { vpn: 2 });
  57. const result = response[ip];
  58. return (
  59. result &&
  60. result.proxy === "yes" &&
  61. !VPN_ORGANIZATION_ALLOWLIST.includes(result.organisation)
  62. );
  63. }
  64. };
  65. const isBlockedSegment = ({ nextUrl: { pathname } }: NextRequest) =>
  66. pathname.startsWith(VPN_BLOCKED_PATH) ||
  67. pathname.startsWith(GEO_BLOCKED_PATH) ||
  68. pathname.startsWith(GOVERNANCE_ONLY_PATH);
  69. export const config = {
  70. // Next.js requires that this is a static string and fails to read it if it's
  71. // a String.raw, so let's disable this rule
  72. // eslint-disable-next-line unicorn/prefer-string-raw
  73. matcher: ["/((?!_next/static|_next/image|api/|terms-of-service|.*\\.).*)"],
  74. };