Repository: settlemint/sdk
Pull request #859: feat: add option to disable proxy endpoints
Author: login: janb87
Labels: name: feat
description: New feature
Created: 2025-03-25T13:21:18Z | Merged: 2025-03-25T14:59:16Z

Required when you want to implement advanced permissions (eg on hasura)

## Summary by Sourcery

Adds options to disable individual proxy endpoints in the proxy middleware.


Diff:
diff --git a/sdk/next/src/middlewares/proxy.test.ts b/sdk/next/src/middlewares/proxy.test.ts
new file mode 100644
index 000000000..2c8350ceb
--- /dev/null
+++ b/sdk/next/src/middlewares/proxy.test.ts
@@ -0,0 +1,126 @@
+import { afterEach, beforeEach, describe, expect, it, mock, spyOn } from "bun:test";
+import { NextRequest, NextResponse } from "next/server.js";
+import { proxyMiddleware } from "./proxy.js";
+
+describe("proxyMiddleware", () => {
+  const originalEnv = process.env;
+
+  beforeEach(() => {
+    process.env = {
+      ...originalEnv,
+      SETTLEMINT_ACCESS_TOKEN: "sm_aat_1234567890",
+      SETTLEMINT_HASURA_ADMIN_SECRET: "test-hasura-secret",
+      SETTLEMINT_HASURA_ENDPOINT: "https://hasura.example.com",
+      SETTLEMINT_PORTAL_GRAPHQL_ENDPOINT: "https://portal.example.com",
+      SETTLEMINT_IPFS_API_ENDPOINT: "https://ipfs-api.example.com",
+      SETTLEMINT_IPFS_GATEWAY_ENDPOINT: "https://ipfs-gateway.example.com",
+      SETTLEMINT_BLOCKSCOUT_GRAPHQL_ENDPOINT: "https://blockscout.example.com",
+      SETTLEMINT_THEGRAPH_SUBGRAPHS_ENDPOINTS: JSON.stringify([
+        "https://thegraph.example.com/subgraphs/name/test-subgraph",
+      ]),
+    };
+  });
+
+  afterEach(() => {
+    process.env = originalEnv;
+    mock.restore();
+  });
+
+  it("should return next response for non-proxy routes", () => {
+    const request = new NextRequest(new URL("https://example.com/api/test"));
+    const nextSpy = spyOn(NextResponse, "next");
+    proxyMiddleware(request);
+    expect(nextSpy).toHaveBeenCalled();
+  });
+
+  it("should add authentication headers for hasura proxy routes", () => {
+    const request = new NextRequest(new URL("https://example.com/proxy/hasura"));
+    const rewriteSpy = spyOn(NextResponse, "rewrite");
+    proxyMiddleware(request);
+    expect(rewriteSpy).toHaveBeenCalledWith(process.env.SETTLEMINT_HASURA_ENDPOINT, {
+      headers: {
+        "x-auth-token": process.env.SETTLEMINT_ACCESS_TOKEN,
+        "x-hasura-admin-secret": process.env.SETTLEMINT_HASURA_ADMIN_SECRET,
+      },
+    });
+  });
+
+  it("should add authentication headers for portal proxy routes", () => {
+    const request = new NextRequest(new URL("https://example.com/proxy/portal"));
+    const rewriteSpy = spyOn(NextResponse, "rewrite");
+    proxyMiddleware(request);
+    expect(rewriteSpy).toHaveBeenCalledWith(process.env.SETTLEMINT_PORTAL_GRAPHQL_ENDPOINT, {
+      headers: {
+        "x-auth-token": process.env.SETTLEMINT_ACCESS_TOKEN,
+      },
+    });
+  });
+
+  it("should add authentication headers for IPFS API proxy routes", () => {
+    const request = new NextRequest(new URL("https://example.com/proxy/ipfs/api"));
+    const rewriteSpy = spyOn(NextResponse, "rewrite");
+    proxyMiddleware(request);
+    expect(rewriteSpy).toHaveBeenCalledWith(process.env.SETTLEMINT_IPFS_API_ENDPOINT, {
+      headers: {
+        "x-auth-token": process.env.SETTLEMINT_ACCESS_TOKEN,
+      },
+    });
+  });
+
+  it("should add authentication headers for IPFS gateway proxy routes", () => {
+    const request = new NextRequest(new URL("https://example.com/proxy/ipfs/gateway"));
+    const rewriteSpy = spyOn(NextResponse, "rewrite");
+    proxyMiddleware(request);
+    expect(rewriteSpy).toHaveBeenCalledWith(process.env.SETTLEMINT_IPFS_GATEWAY_ENDPOINT, {
+      headers: {
+        "x-auth-token": process.env.SETTLEMINT_ACCESS_TOKEN,
+      },
+    });
+  });
+
+  it("should add authentication headers for blockscout proxy routes", () => {
+    const request = new NextRequest(new URL("https://example.com/proxy/blockscout"));
+    const rewriteSpy = spyOn(NextResponse, "rewrite");
+    proxyMiddleware(request);
+    expect(rewriteSpy).toHaveBeenCalledWith(process.env.SETTLEMINT_BLOCKSCOUT_GRAPHQL_ENDPOINT, {
+      headers: {
+        "x-auth-token": process.env.SETTLEMINT_ACCESS_TOKEN,
+      },
+    });
+  });
+
+  it("should handle TheGraph proxy routes with subgraph name", () => {
+    const request = new NextRequest(new URL("https://example.com/proxy/thegraph/graphql/test-subgraph"));
+    const rewriteSpy = spyOn(NextResponse, "rewrite");
+    proxyMiddleware(request);
+    expect(rewriteSpy).toHaveBeenCalledWith(JSON.parse(process.env.SETTLEMINT_THEGRAPH_SUBGRAPHS_ENDPOINTS!)[0], {
+      headers: {
+        "x-auth-token": process.env.SETTLEMINT_ACCESS_TOKEN,
+      },
+    });
+  });
+
+  it("should respect disabled proxy options", () => {
+    const request = new NextRequest(new URL("https://example.com/proxy/hasura"));
+    const nextSpy = spyOn(NextResponse, "next");
+    proxyMiddleware(request, { disableHasuraProxy: true });
+    proxyMiddleware(request);
+    expect(nextSpy).toHaveBeenCalled();
+  });
+
+  it("should remove authorization header if present", () => {
+    const headers = new Headers();
+    headers.set("authorization", "Bearer token123");
+    const request = new NextRequest(new URL("https://example.com/proxy/hasura"), {
+      headers,
+    });
+    const rewriteSpy = spyOn(NextResponse, "rewrite");
+    proxyMiddleware(request);
+    expect(rewriteSpy).toHaveBeenCalledWith(process.env.SETTLEMINT_HASURA_ENDPOINT, {
+      headers: {
+        "x-auth-token": process.env.SETTLEMINT_ACCESS_TOKEN,
+        "x-hasura-admin-secret": process.env.SETTLEMINT_HASURA_ADMIN_SECRET,
+      },
+    });
+  });
+});
diff --git a/sdk/next/src/middlewares/proxy.ts b/sdk/next/src/middlewares/proxy.ts
index 7eba6720e..0782a3ef1 100644
--- a/sdk/next/src/middlewares/proxy.ts
+++ b/sdk/next/src/middlewares/proxy.ts
@@ -10,6 +10,15 @@ const isIpfsGatewayProxyRoute = match(["/proxy/ipfs/gateway", "/proxy/ipfs/gatew
 const isBlockscoutProxyRoute = match(["/proxy/blockscout", "/proxy/blockscout/*path"]);
 const isTheGraphProxyRoute = match(["/proxy/thegraph", "/proxy/thegraph/*path"]);
 
+interface ProxyMiddlewareOptions {
+  disableHasuraProxy?: boolean;
+  disablePortalProxy?: boolean;
+  disableIpfsApiProxy?: boolean;
+  disableIpfsGatewayProxy?: boolean;
+  disableBlockscoutProxy?: boolean;
+  disableTheGraphProxy?: boolean;
+}
+
 /**
  * Middleware function to handle proxy requests by adding appropriate authentication headers.
  *
@@ -24,12 +33,12 @@ const isTheGraphProxyRoute = match(["/proxy/thegraph", "/proxy/thegraph/*path"])
  *   return proxyMiddleware(request);
  * }
  */
-export function proxyMiddleware(request: NextRequest): NextResponse | undefined {
+export function proxyMiddleware(request: NextRequest, options: ProxyMiddlewareOptions = {}): NextResponse | undefined {
   const env = validate(DotEnvSchema, process.env);
   if (!isProxyRoute(request.nextUrl.pathname)) {
     return NextResponse.next();
   }
-  const rewriteUrl = getRewriteUrl(request, env);
+  const rewriteUrl = getRewriteUrl(request, env, options);
   if (!rewriteUrl) {
     return NextResponse.next();
   }
@@ -53,25 +62,25 @@ export function proxyMiddleware(request: NextRequest): NextResponse | undefined
   });
 }
 
-function getRewriteUrl(request: NextRequest, env: DotEnv) {
+function getRewriteUrl(request: NextRequest, env: DotEnv, options: ProxyMiddlewareOptions) {
   const path = request.nextUrl.pathname;
-  if (isHasuraProxyRoute(path)) {
+  if (isHasuraProxyRoute(path) && !options.disableHasuraProxy) {
     return env.SETTLEMINT_HASURA_ENDPOINT;
   }
-  if (isPortalProxyRoute(path)) {
+  if (isPortalProxyRoute(path) && !options.disablePortalProxy) {
     return env.SETTLEMINT_PORTAL_GRAPHQL_ENDPOINT;
   }
-  if (isIpfsApiProxyRoute(path)) {
+  if (isIpfsApiProxyRoute(path) && !options.disableIpfsApiProxy) {
     return env.SETTLEMINT_IPFS_API_ENDPOINT;
   }
-  if (isIpfsGatewayProxyRoute(path)) {
+  if (isIpfsGatewayProxyRoute(path) && !options.disableIpfsGatewayProxy) {
     return env.SETTLEMINT_IPFS_GATEWAY_ENDPOINT;
   }
-  if (isBlockscoutProxyRoute(path)) {
+  if (isBlockscoutProxyRoute(path) && !options.disableBlockscoutProxy) {
     return env.SETTLEMINT_BLOCKSCOUT_GRAPHQL_ENDPOINT;
   }
   const theGraphMatch = isTheGraphProxyRoute(path);
-  if (theGraphMatch) {
+  if (theGraphMatch && !options.disableTheGraphProxy) {
     const { path } = theGraphMatch.params;
     const endpoints = env.SETTLEMINT_THEGRAPH_SUBGRAPHS_ENDPOINTS ?? [];
     if (Array.isArray(path)) {


Reviews:
user: login: sourcery-ai[bot]
body: Hey @janb87 - I've reviewed your changes - here's some feedback:

**Overall Comments**:
- Consider adding a test case to verify that the proxy middleware correctly handles the new disable options.

<details>
<summary>Here's what I looked at during the review</summary>

- 🟢 **General issues**: all looks good
- 🟢 **Security**: all looks good
- 🟢 **Review instructions**: all looks good
- 🟢 **Testing**: all looks good
- 🟢 **Complexity**: all looks good
- 🟢 **Documentation**: all looks good
</details>

***

<details>
<summary>Sourcery is free for open source - if you like our reviews please consider sharing them ✨</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https://sourcery.ai)
- [Facebook](https://www.facebook.com/sharer/sharer.php?u=https://sourcery.ai)

</details>

<sub>
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
</sub>
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T13:21:59Z
user: login: github-advanced-security[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T13:22:34Z
user: login: snigdha920
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T14:58:51Z
user: login: snigdha920
body: Where would we want to implement such an advanced permission? 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T14:59:15Z
user: login: janb87
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-25T15:23:39Z

Review comments:
diff_hunk: @@ -0,0 +1,126 @@
+import { afterEach, beforeEach, describe, expect, it, mock, spyOn } from "bun:test";
+import { NextRequest, NextResponse } from "next/server.js";
+import { proxyMiddleware } from "./proxy.js";
+
+describe("proxyMiddleware", () => {
+  const originalEnv = process.env;
+
+  beforeEach(() => {
+    process.env = {
+      ...originalEnv,
+      SETTLEMINT_ACCESS_TOKEN: "sm_aat_1234567890",
+      SETTLEMINT_HASURA_ADMIN_SECRET: "test-hasura-secret",
+      SETTLEMINT_HASURA_ENDPOINT: "https://hasura.example.com",
+      SETTLEMINT_PORTAL_GRAPHQL_ENDPOINT: "https://portal.example.com",
+      SETTLEMINT_IPFS_API_ENDPOINT: "https://ipfs-api.example.com",
+      SETTLEMINT_IPFS_GATEWAY_ENDPOINT: "https://ipfs-gateway.example.com",
+      SETTLEMINT_BLOCKSCOUT_GRAPHQL_ENDPOINT: "https://blockscout.example.com",
+      SETTLEMINT_THEGRAPH_SUBGRAPHS_ENDPOINTS: JSON.stringify([
+        "https://thegraph.example.com/subgraphs/name/test-subgraph",
+      ]),
+    };
+  });
+
+  afterEach(() => {
+    process.env = originalEnv;
+    mock.restore();
+  });
+
+  it("should return next response for non-proxy routes", () => {
+    const request = new NextRequest(new URL("https://example.com/api/test"));
+    const nextSpy = spyOn(NextResponse, "next");
+    proxyMiddleware(request);
+    expect(nextSpy).toHaveBeenCalled();
+  });
+
+  it("should add authentication headers for hasura proxy routes", () => {
+    const request = new NextRequest(new URL("https://example.com/proxy/hasura"));
+    const rewriteSpy = spyOn(NextResponse, "rewrite");
+    proxyMiddleware(request);
+    expect(rewriteSpy).toHaveBeenCalledWith(process.env.SETTLEMINT_HASURA_ENDPOINT, {
+      headers: {
+        "x-auth-token": process.env.SETTLEMINT_ACCESS_TOKEN,
+        "x-hasura-admin-secret": process.env.SETTLEMINT_HASURA_ADMIN_SECRET,
+      },
+    });
+  });
+
+  it("should add authentication headers for portal proxy routes", () => {
+    const request = new NextRequest(new URL("https://example.com/proxy/portal"));
+    const rewriteSpy = spyOn(NextResponse, "rewrite");
+    proxyMiddleware(request);
+    expect(rewriteSpy).toHaveBeenCalledWith(process.env.SETTLEMINT_PORTAL_GRAPHQL_ENDPOINT, {
+      headers: {
+        "x-auth-token": process.env.SETTLEMINT_ACCESS_TOKEN,
+      },
+    });
+  });
+
+  it("should add authentication headers for IPFS API proxy routes", () => {
+    const request = new NextRequest(new URL("https://example.com/proxy/ipfs/api"));
+    const rewriteSpy = spyOn(NextResponse, "rewrite");
+    proxyMiddleware(request);
+    expect(rewriteSpy).toHaveBeenCalledWith(process.env.SETTLEMINT_IPFS_API_ENDPOINT, {
+      headers: {
+        "x-auth-token": process.env.SETTLEMINT_ACCESS_TOKEN,
+      },
+    });
+  });
+
+  it("should add authentication headers for IPFS gateway proxy routes", () => {
+    const request = new NextRequest(new URL("https://example.com/proxy/ipfs/gateway"));
+    const rewriteSpy = spyOn(NextResponse, "rewrite");
+    proxyMiddleware(request);
+    expect(rewriteSpy).toHaveBeenCalledWith(process.env.SETTLEMINT_IPFS_GATEWAY_ENDPOINT, {
+      headers: {
+        "x-auth-token": process.env.SETTLEMINT_ACCESS_TOKEN,
+      },
+    });
+  });
+
+  it("should add authentication headers for blockscout proxy routes", () => {
+    const request = new NextRequest(new URL("https://example.com/proxy/blockscout"));
+    const rewriteSpy = spyOn(NextResponse, "rewrite");
+    proxyMiddleware(request);
+    expect(rewriteSpy).toHaveBeenCalledWith(process.env.SETTLEMINT_BLOCKSCOUT_GRAPHQL_ENDPOINT, {
+      headers: {
+        "x-auth-token": process.env.SETTLEMINT_ACCESS_TOKEN,
+      },
+    });
+  });
+
+  it("should handle TheGraph proxy routes with subgraph name", () => {
+    const request = new NextRequest(new URL("https://example.com/proxy/thegraph/graphql/test-subgraph"));
+    const rewriteSpy = spyOn(NextResponse, "rewrite");
+    proxyMiddleware(request);
+    expect(rewriteSpy).toHaveBeenCalledWith(JSON.parse(process.env.SETTLEMINT_THEGRAPH_SUBGRAPHS_ENDPOINTS!)[0], {
+      headers: {
+        "x-auth-token": process.env.SETTLEMINT_ACCESS_TOKEN,
+      },
+    });
+  });
+
+  it("should respect disabled proxy options", () => {
+    const request = new NextRequest(new URL("https://example.com/proxy/hasura"));
+    const nextSpy = spyOn(NextResponse, "next");
+    proxyMiddleware(request, { disableHasuraProxy: true });
+    proxyMiddleware(request);
+    expect(nextSpy).toHaveBeenCalled();
+  });
+
+  it("should remove authorization header if present", () => {
+    const headers = new Headers();
+    headers.set("authorization", "Bearer token123");
path: sdk/next/src/middlewares/proxy.test.ts
user: login: github-advanced-security[bot]
body: ## Hard-coded credentials

The hard-coded value "Bearer token123" is used as [authorization header](1).

[Show more details](https://github.com/settlemint/sdk/security/code-scanning/48)
created_at: 2025-03-25T13:22:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,126 @@
+import { afterEach, beforeEach, describe, expect, it, mock, spyOn } from "bun:test";
+import { NextRequest, NextResponse } from "next/server.js";
+import { proxyMiddleware } from "./proxy.js";
+
+describe("proxyMiddleware", () => {
+  const originalEnv = process.env;
+
+  beforeEach(() => {
+    process.env = {
+      ...originalEnv,
+      SETTLEMINT_ACCESS_TOKEN: "sm_aat_1234567890",
+      SETTLEMINT_HASURA_ADMIN_SECRET: "test-hasura-secret",
+      SETTLEMINT_HASURA_ENDPOINT: "https://hasura.example.com",
+      SETTLEMINT_PORTAL_GRAPHQL_ENDPOINT: "https://portal.example.com",
+      SETTLEMINT_IPFS_API_ENDPOINT: "https://ipfs-api.example.com",
+      SETTLEMINT_IPFS_GATEWAY_ENDPOINT: "https://ipfs-gateway.example.com",
+      SETTLEMINT_BLOCKSCOUT_GRAPHQL_ENDPOINT: "https://blockscout.example.com",
+      SETTLEMINT_THEGRAPH_SUBGRAPHS_ENDPOINTS: JSON.stringify([
+        "https://thegraph.example.com/subgraphs/name/test-subgraph",
+      ]),
+    };
+  });
+
+  afterEach(() => {
+    process.env = originalEnv;
+    mock.restore();
+  });
+
+  it("should return next response for non-proxy routes", () => {
+    const request = new NextRequest(new URL("https://example.com/api/test"));
+    const nextSpy = spyOn(NextResponse, "next");
+    proxyMiddleware(request);
+    expect(nextSpy).toHaveBeenCalled();
+  });
+
+  it("should add authentication headers for hasura proxy routes", () => {
+    const request = new NextRequest(new URL("https://example.com/proxy/hasura"));
+    const rewriteSpy = spyOn(NextResponse, "rewrite");
+    proxyMiddleware(request);
+    expect(rewriteSpy).toHaveBeenCalledWith(process.env.SETTLEMINT_HASURA_ENDPOINT, {
+      headers: {
+        "x-auth-token": process.env.SETTLEMINT_ACCESS_TOKEN,
+        "x-hasura-admin-secret": process.env.SETTLEMINT_HASURA_ADMIN_SECRET,
+      },
+    });
+  });
+
+  it("should add authentication headers for portal proxy routes", () => {
+    const request = new NextRequest(new URL("https://example.com/proxy/portal"));
+    const rewriteSpy = spyOn(NextResponse, "rewrite");
+    proxyMiddleware(request);
+    expect(rewriteSpy).toHaveBeenCalledWith(process.env.SETTLEMINT_PORTAL_GRAPHQL_ENDPOINT, {
+      headers: {
+        "x-auth-token": process.env.SETTLEMINT_ACCESS_TOKEN,
+      },
+    });
+  });
+
+  it("should add authentication headers for IPFS API proxy routes", () => {
+    const request = new NextRequest(new URL("https://example.com/proxy/ipfs/api"));
+    const rewriteSpy = spyOn(NextResponse, "rewrite");
+    proxyMiddleware(request);
+    expect(rewriteSpy).toHaveBeenCalledWith(process.env.SETTLEMINT_IPFS_API_ENDPOINT, {
+      headers: {
+        "x-auth-token": process.env.SETTLEMINT_ACCESS_TOKEN,
+      },
+    });
+  });
+
+  it("should add authentication headers for IPFS gateway proxy routes", () => {
+    const request = new NextRequest(new URL("https://example.com/proxy/ipfs/gateway"));
+    const rewriteSpy = spyOn(NextResponse, "rewrite");
+    proxyMiddleware(request);
+    expect(rewriteSpy).toHaveBeenCalledWith(process.env.SETTLEMINT_IPFS_GATEWAY_ENDPOINT, {
+      headers: {
+        "x-auth-token": process.env.SETTLEMINT_ACCESS_TOKEN,
+      },
+    });
+  });
+
+  it("should add authentication headers for blockscout proxy routes", () => {
+    const request = new NextRequest(new URL("https://example.com/proxy/blockscout"));
+    const rewriteSpy = spyOn(NextResponse, "rewrite");
+    proxyMiddleware(request);
+    expect(rewriteSpy).toHaveBeenCalledWith(process.env.SETTLEMINT_BLOCKSCOUT_GRAPHQL_ENDPOINT, {
+      headers: {
+        "x-auth-token": process.env.SETTLEMINT_ACCESS_TOKEN,
+      },
+    });
+  });
+
+  it("should handle TheGraph proxy routes with subgraph name", () => {
+    const request = new NextRequest(new URL("https://example.com/proxy/thegraph/graphql/test-subgraph"));
+    const rewriteSpy = spyOn(NextResponse, "rewrite");
+    proxyMiddleware(request);
+    expect(rewriteSpy).toHaveBeenCalledWith(JSON.parse(process.env.SETTLEMINT_THEGRAPH_SUBGRAPHS_ENDPOINTS!)[0], {
+      headers: {
+        "x-auth-token": process.env.SETTLEMINT_ACCESS_TOKEN,
+      },
+    });
+  });
+
+  it("should respect disabled proxy options", () => {
+    const request = new NextRequest(new URL("https://example.com/proxy/hasura"));
+    const nextSpy = spyOn(NextResponse, "next");
path: sdk/next/src/middlewares/proxy.test.ts
user: login: snigdha920
body: should we also spy on `rewrite` here and check if it is not called?
created_at: 2025-03-25T14:58:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,126 @@
+import { afterEach, beforeEach, describe, expect, it, mock, spyOn } from "bun:test";
+import { NextRequest, NextResponse } from "next/server.js";
+import { proxyMiddleware } from "./proxy.js";
+
+describe("proxyMiddleware", () => {
+  const originalEnv = process.env;
+
+  beforeEach(() => {
+    process.env = {
+      ...originalEnv,
+      SETTLEMINT_ACCESS_TOKEN: "sm_aat_1234567890",
+      SETTLEMINT_HASURA_ADMIN_SECRET: "test-hasura-secret",
+      SETTLEMINT_HASURA_ENDPOINT: "https://hasura.example.com",
+      SETTLEMINT_PORTAL_GRAPHQL_ENDPOINT: "https://portal.example.com",
+      SETTLEMINT_IPFS_API_ENDPOINT: "https://ipfs-api.example.com",
+      SETTLEMINT_IPFS_GATEWAY_ENDPOINT: "https://ipfs-gateway.example.com",
+      SETTLEMINT_BLOCKSCOUT_GRAPHQL_ENDPOINT: "https://blockscout.example.com",
+      SETTLEMINT_THEGRAPH_SUBGRAPHS_ENDPOINTS: JSON.stringify([
+        "https://thegraph.example.com/subgraphs/name/test-subgraph",
+      ]),
+    };
+  });
+
+  afterEach(() => {
+    process.env = originalEnv;
+    mock.restore();
+  });
+
+  it("should return next response for non-proxy routes", () => {
+    const request = new NextRequest(new URL("https://example.com/api/test"));
+    const nextSpy = spyOn(NextResponse, "next");
+    proxyMiddleware(request);
+    expect(nextSpy).toHaveBeenCalled();
+  });
+
+  it("should add authentication headers for hasura proxy routes", () => {
+    const request = new NextRequest(new URL("https://example.com/proxy/hasura"));
+    const rewriteSpy = spyOn(NextResponse, "rewrite");
+    proxyMiddleware(request);
+    expect(rewriteSpy).toHaveBeenCalledWith(process.env.SETTLEMINT_HASURA_ENDPOINT, {
+      headers: {
+        "x-auth-token": process.env.SETTLEMINT_ACCESS_TOKEN,
+        "x-hasura-admin-secret": process.env.SETTLEMINT_HASURA_ADMIN_SECRET,
+      },
+    });
+  });
+
+  it("should add authentication headers for portal proxy routes", () => {
+    const request = new NextRequest(new URL("https://example.com/proxy/portal"));
+    const rewriteSpy = spyOn(NextResponse, "rewrite");
+    proxyMiddleware(request);
+    expect(rewriteSpy).toHaveBeenCalledWith(process.env.SETTLEMINT_PORTAL_GRAPHQL_ENDPOINT, {
+      headers: {
+        "x-auth-token": process.env.SETTLEMINT_ACCESS_TOKEN,
+      },
+    });
+  });
+
+  it("should add authentication headers for IPFS API proxy routes", () => {
+    const request = new NextRequest(new URL("https://example.com/proxy/ipfs/api"));
+    const rewriteSpy = spyOn(NextResponse, "rewrite");
+    proxyMiddleware(request);
+    expect(rewriteSpy).toHaveBeenCalledWith(process.env.SETTLEMINT_IPFS_API_ENDPOINT, {
+      headers: {
+        "x-auth-token": process.env.SETTLEMINT_ACCESS_TOKEN,
+      },
+    });
+  });
+
+  it("should add authentication headers for IPFS gateway proxy routes", () => {
+    const request = new NextRequest(new URL("https://example.com/proxy/ipfs/gateway"));
+    const rewriteSpy = spyOn(NextResponse, "rewrite");
+    proxyMiddleware(request);
+    expect(rewriteSpy).toHaveBeenCalledWith(process.env.SETTLEMINT_IPFS_GATEWAY_ENDPOINT, {
+      headers: {
+        "x-auth-token": process.env.SETTLEMINT_ACCESS_TOKEN,
+      },
+    });
+  });
+
+  it("should add authentication headers for blockscout proxy routes", () => {
+    const request = new NextRequest(new URL("https://example.com/proxy/blockscout"));
+    const rewriteSpy = spyOn(NextResponse, "rewrite");
+    proxyMiddleware(request);
+    expect(rewriteSpy).toHaveBeenCalledWith(process.env.SETTLEMINT_BLOCKSCOUT_GRAPHQL_ENDPOINT, {
+      headers: {
+        "x-auth-token": process.env.SETTLEMINT_ACCESS_TOKEN,
+      },
+    });
+  });
+
+  it("should handle TheGraph proxy routes with subgraph name", () => {
+    const request = new NextRequest(new URL("https://example.com/proxy/thegraph/graphql/test-subgraph"));
+    const rewriteSpy = spyOn(NextResponse, "rewrite");
+    proxyMiddleware(request);
+    expect(rewriteSpy).toHaveBeenCalledWith(JSON.parse(process.env.SETTLEMINT_THEGRAPH_SUBGRAPHS_ENDPOINTS!)[0], {
+      headers: {
+        "x-auth-token": process.env.SETTLEMINT_ACCESS_TOKEN,
+      },
+    });
+  });
+
+  it("should respect disabled proxy options", () => {
+    const request = new NextRequest(new URL("https://example.com/proxy/hasura"));
+    const nextSpy = spyOn(NextResponse, "next");
path: sdk/next/src/middlewares/proxy.test.ts
user: login: janb87
body: will add this in a next pr
created_at: 2025-03-25T15:23:39Z
author_association: CONTRIBUTOR

PR comments:
user: login: sourcery-ai[bot]
created_at: 2025-03-25T13:21:22Z
body: <!-- Generated by sourcery-ai[bot]: start review_guide -->

## Reviewer's Guide by Sourcery

This pull request introduces the ability to disable specific proxy endpoints within the `proxyMiddleware`. This is achieved by adding an optional `options` parameter to the `proxyMiddleware` function, which allows disabling individual proxy routes based on boolean flags.

#### Sequence diagram for proxy middleware with disabled endpoints

```mermaid
sequenceDiagram
  participant Client
  participant proxyMiddleware
  participant getRewriteUrl
  participant HasuraEndpoint
  participant PortalEndpoint
  participant IpfsApiEndpoint
  participant IpfsGatewayEndpoint
  participant BlockscoutEndpoint
  participant TheGraphEndpoint

  Client->>proxyMiddleware: Request to /proxy/hasura
  proxyMiddleware->>getRewriteUrl: getRewriteUrl(request, env, {disableHasuraProxy: true})
  getRewriteUrl-->>proxyMiddleware: null
  proxyMiddleware->>Client: NextResponse.next()

  Client->>proxyMiddleware: Request to /proxy/portal
  proxyMiddleware->>getRewriteUrl: getRewriteUrl(request, env, {disablePortalProxy: true})
  getRewriteUrl-->>proxyMiddleware: null
  proxyMiddleware->>Client: NextResponse.next()

  Client->>proxyMiddleware: Request to /proxy/ipfs/api
  proxyMiddleware->>getRewriteUrl: getRewriteUrl(request, env, {disableIpfsApiProxy: true})
  getRewriteUrl-->>proxyMiddleware: null
  proxyMiddleware->>Client: NextResponse.next()

  Client->>proxyMiddleware: Request to /proxy/ipfs/gateway
  proxyMiddleware->>getRewriteUrl: getRewriteUrl(request, env, {disableIpfsGatewayProxy: true})
  getRewriteUrl-->>proxyMiddleware: null
  proxyMiddleware->>Client: NextResponse.next()

  Client->>proxyMiddleware: Request to /proxy/blockscout
  proxyMiddleware->>getRewriteUrl: getRewriteUrl(request, env, {disableBlockscoutProxy: true})
  getRewriteUrl-->>proxyMiddleware: null
  proxyMiddleware->>Client: NextResponse.next()

  Client->>proxyMiddleware: Request to /proxy/thegraph
  proxyMiddleware->>getRewriteUrl: getRewriteUrl(request, env, {disableTheGraphProxy: true})
  getRewriteUrl-->>proxyMiddleware: null
  proxyMiddleware->>Client: NextResponse.next()
```

### File-Level Changes

| Change | Details | Files |
| ------ | ------- | ----- |
| Introduced options to disable individual proxy routes. | <ul><li>Added `ProxyMiddlewareOptions` interface to define flags for disabling proxy routes.</li><li>Modified `proxyMiddleware` to accept an optional `options` parameter of type `ProxyMiddlewareOptions`.</li><li>Modified `getRewriteUrl` to accept the `options` parameter.</li><li>Added checks within `getRewriteUrl` to respect the disable flags in the `options` when determining the rewrite URL.</li></ul> | `sdk/next/src/middlewares/proxy.ts` |
| Added tests for the proxy middleware. | <ul><li>Created a new test file for the proxy middleware.</li><li>Added tests to verify that the proxy middleware works as expected.</li></ul> | `sdk/next/src/middlewares/proxy.test.ts` |

---

<details>
<summary>Tips and commands</summary>

#### Interacting with Sourcery

- **Trigger a new review:** Comment `@sourcery-ai review` on the pull request.
- **Continue discussions:** Reply directly to Sourcery's review comments.
- **Generate a GitHub issue from a review comment:** Ask Sourcery to create an
  issue from a review comment by replying to it. You can also reply to a
  review comment with `@sourcery-ai issue` to create an issue from it.
- **Generate a pull request title:** Write `@sourcery-ai` anywhere in the pull
  request title to generate a title at any time. You can also comment
  `@sourcery-ai title` on the pull request to (re-)generate the title at any time.
- **Generate a pull request summary:** Write `@sourcery-ai summary` anywhere in
  the pull request body to generate a PR summary at any time exactly where you
  want it. You can also comment `@sourcery-ai summary` on the pull request to
  (re-)generate the summary at any time.
- **Generate reviewer's guide:** Comment `@sourcery-ai guide` on the pull
  request to (re-)generate the reviewer's guide at any time.
- **Resolve all Sourcery comments:** Comment `@sourcery-ai resolve` on the
  pull request to resolve all Sourcery comments. Useful if you've already
  addressed all the comments and don't want to see them anymore.
- **Dismiss all Sourcery reviews:** Comment `@sourcery-ai dismiss` on the pull
  request to dismiss all existing Sourcery reviews. Especially useful if you
  want to start fresh with a new review - don't forget to comment
  `@sourcery-ai review` to trigger a new review!
- **Generate a plan of action for an issue:** Comment `@sourcery-ai plan` on
  an issue to generate a plan of action for it.

#### Customizing Your Experience

Access your [dashboard](https://app.sourcery.ai) to:
- Enable or disable review features such as the Sourcery-generated pull request
  summary, the reviewer's guide, and others.
- Change the review language.
- Add, remove or edit custom review instructions.
- Adjust other review settings.

#### Getting Help

- [Contact our support team](mailto:support@sourcery.ai) for questions or feedback.
- Visit our [documentation](https://docs.sourcery.ai) for detailed guides and information.
- Keep in touch with the Sourcery team by following us on [X/Twitter](https://x.com/SourceryAI), [LinkedIn](https://www.linkedin.com/company/sourcery-ai/) or [GitHub](https://github.com/sourcery-ai).

</details>

<!-- Generated by sourcery-ai[bot]: end review_guide -->
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-03-25T13:22:24Z
body: # 📦 Packages
| Package | NPM | Docker |
| ------- | -------------------- | -------------------- |
| SDK Cli | `@settlemint/sdk-cli@1.2.5-prcc5211f3` |  |
| SDK The Graph | `@settlemint/sdk-thegraph@1.2.5-prcc5211f3` |  |
| SDK Portal | `@settlemint/sdk-portal@1.2.5-prcc5211f3` |  |
| SDK Hasura | `@settlemint/sdk-hasura@1.2.5-prcc5211f3` |  |
| SDK JS | `@settlemint/sdk-js@1.2.5-prcc5211f3` |  |
| SDK Utils | `@settlemint/sdk-utils@1.2.5-prcc5211f3` | |
| SDK Next | `@settlemint/sdk-next@1.2.5-prcc5211f3` | |
| SDK Minio | `@settlemint/sdk-minio@1.2.5-prcc5211f3` | |
| SDK IPFS | `@settlemint/sdk-ipfs@1.2.5-prcc5211f3` | |
| SDK Blockscout | `@settlemint/sdk-blockscout@1.2.5-prcc5211f3` | |
| SDK MCP | `@settlemint/sdk-mcp@1.2.5-prcc5211f3` | |

[^uniqueIdentifier]: Build, test and publish
author_association: NONE
user: login: janb87
created_at: 2025-03-25T15:21:51Z
body: > Where would we want to implement such an advanced permission?

Eg in the kit we don't want to expose users information to regular users. Another use case is the portal where you don't want regular users to create assets.
author_association: CONTRIBUTOR
user: login: janb87
created_at: 2025-03-25T15:23:17Z
body: for the portal I think we should only disable proxy for mutations, will create a separate pr for it
author_association: CONTRIBUTOR