Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type mismatch with Hono req and ip #477

Closed
davidmytton opened this issue Apr 1, 2024 · 2 comments · Fixed by #538
Closed

Type mismatch with Hono req and ip #477

davidmytton opened this issue Apr 1, 2024 · 2 comments · Fixed by #538
Assignees
Labels
bug Something isn't working

Comments

@davidmytton
Copy link
Contributor

davidmytton commented Apr 1, 2024

When trying to parse the IP from the headers in a Node.js Hono route, the ip method has a type error:

Type 'HonoRequest<"/", unknown>' has no properties in common with type 'RequestLike'.ts(2559)
(property) Context<Env, "/", BlankInput>.req: HonoRequest<"/", unknown>

This also errors when using c.req.raw which is a Request.

Type 'Request' has no properties in common with type 'RequestLike'.ts(2559)
(property) HonoRequest<P extends string = "/", I extends {} | undefined = {}>.raw: Request

Example:

import arcjet, { ArcjetHeaders, tokenBucket } from "@arcjet/node";
import { serve } from "@hono/node-server";
import { Hono } from "hono";
import ip from "@arcjet/ip";

const aj = arcjet({
  key: process.env.ARCJET_KEY,
  rules: [
    // Create a token bucket rate limit. Other algorithms are supported.
    tokenBucket({
      mode: "LIVE", // will block requests. Use "DRY_RUN" to log only
      characteristics: ["userId"], // track requests by a custom user ID
      refillRate: 5, // refill 5 tokens per interval
      interval: 10, // refill every 10 seconds
      capacity: 10, // bucket maximum capacity of 10 tokens
    }),
  ],
});

const app = new Hono();

app.get("/", async (c) => {
  const userId = "user123"; // Replace with your authenticated user ID
  
  const ajHeaders = new ArcjetHeaders(c.req.raw.headers);  
  const globalIp = ip(c.req.raw, c.req.raw.headers);

  // Convert ajHeaders to Record<string, string | string[] | undefined>
  const headers: Record<string, string | string[] | undefined> = {};
  ajHeaders.forEach((value, key) => {
    headers[key] = value;
  });

  const details = {
    ip: globalIp,
    method: c.req.method,
    host: headers.host,
    url: c.req.url,
    headers,
  };

  console.log("Details: ", details);

  const decision = await aj.protect(details, { userId, requested: 5 }); // Deduct 5 tokens from the bucket
  console.log("Arcjet decision", decision);

  return c.json({
    ok: true,
    message: "Hello Hono!",
  });
});

const port = 3000;
console.log(`Server is running on port ${port}`);

serve({
  fetch: app.fetch,
  port,
});
@davidmytton davidmytton added the bug Something isn't working label Apr 1, 2024
@davidmytton
Copy link
Contributor Author

@blaine-arcjet we could fix this specifically, or we could create a new issue to add a proper Hono SDK because I had to manually construct the request in the details const. Hono is a light wrapper over a range of runtimes so it quite a nice web framework.

@blaine-arcjet
Copy link
Contributor

Some preliminary research here:

  • RequestLike might be bad naming, because it's not Request-like, it is a type that smooths over NextRequest (which is an enhanced Request) and NextApiRequest (which is an enhanced IncomingMessage). A Hono req.raw has none of the required information.
  • As such req.raw can't be used but instead I followed Access the raw Node.js APIs to inform the Hono constructor that the env contains HttpBindings. This allows me to do const decision = await aj.protect(c.env.incoming) because incoming is an IncomingMessage which is needed by the node bindings.

Full example:

import arcjet, { tokenBucket } from "@arcjet/node";
import { serve, type HttpBindings } from "@hono/node-server";
import { Hono } from "hono";

const aj = arcjet({
  key: process.env.ARCJET_KEY,
  rules: [
    // Create a token bucket rate limit. Other algorithms are supported.
    tokenBucket({
      mode: "LIVE", // will block requests. Use "DRY_RUN" to log only
      characteristics: ["userId"], // track requests by a custom user ID
      refillRate: 5, // refill 5 tokens per interval
      interval: 10, // refill every 10 seconds
      capacity: 10, // bucket maximum capacity of 10 tokens
    }),
  ],
});

const app = new Hono<{ Bindings: HttpBindings }>();

app.get("/", async (c) => {
  const userId = "user123"; // Replace with your authenticated user ID

  const decision = await aj.protect(c.env.incoming, { userId, requested: 5 }); // Deduct 5 tokens from the bucket
  console.log("Arcjet decision", decision);

  return c.json({
    ok: true,
    message: "Hello Hono!",
  });
});

const port = 3000;
console.log(`Server is running on port ${port}`);

serve({
  fetch: app.fetch,
  port,
});

@trunk-io trunk-io bot closed this as completed in #538 Apr 9, 2024
trunk-io bot pushed a commit that referenced this issue Apr 9, 2024
Closes #477 

This adds an example to show how to use Hono with their Node.js Adapter and the Arcjet Node.js SDK.

Note: This doesn't resolve the issue with Hono + Bun, since we'll need to investigate an SDK for Bun itself.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants