# Go and Node middleware

> Check every request inside your Go or Node app with /v1/gateway/check, including body digests for signed agents.

When there is no nginx in front of your app, or a route needs its body
checked, run the gateway check inside the application. The repository has
two small, dependency-free adapters with tests: `examples/go-middleware`
(Go `net/http`) and `examples/express` (Node 18+, Express or plain
`node:http`). Copy the one you need into your project.

Both call `GET /v1/gateway/check` with the backend secret, verify a signed
agent's `Content-Digest` against the body they read (sha-256 or sha-512) and
send `X-Original-Body-Digest`, strip client-supplied `X-AgentGate-*`
headers, fail closed on errors unless told otherwise, and include a
`/v1/siteverify` helper for form posts.

```go title="Go"
gate := &agentgate.Client{
	Endpoint:   "http://127.0.0.1:18080",          // private AgentGate address
	Credential: os.Getenv("AGENTGATE_CREDENTIAL"), // the site's backend secret, ags_…
}
http.Handle("/api/", gate.Middleware(apiHandler))

// In a handler: the decision.
d, ok := agentgate.FromContext(r.Context()) // d.Decision, d.Reason, d.Agent, d.WouldDecision
```

```js title="Node"
const express = require("express");
const { agentgate, siteVerify } = require("./agentgate");

const opts = { endpoint: "http://127.0.0.1:18080", credential: process.env.AGENTGATE_CREDENTIAL };
const app = express();
app.use("/api", agentgate(opts)); // before body parsers, or after express.raw()
app.post("/api/orders", (req, res) => {
  // req.body is the raw Buffer; req.agentgate is the decision
  res.json({ ok: true, agent: req.headers["x-agentgate-agent"] || null });
});
```

## Behaviour

| AgentGate answers | The middleware |
| --- | --- |
| `204` allow | runs your handler; the body is still readable; the decision is available (`agentgate.FromContext` in Go, `req.agentgate` in Node) and the request carries `X-AgentGate-Decision-ID`, `X-AgentGate-Agent` (verified agent) and `X-AgentGate-Would-Decision` (monitor mode) |
| `401` challenge | answers `401` with `{"error": "…", "reason": "<code>"}` |
| `403` block | answers `403`, or `429` when the reason is `rate_limited` |
| error or timeout | answers `503` (fail closed), unless `FailOpen: true` / `failOpen: true` |

Options: the site key, the maximum body size (default 1 MiB; larger bodies
get `413` without a check), the timeout (2 s by default: Go `HTTPClient`, Node `timeoutMs`),
and, behind a proxy, functions for the client IP and protocol the proxy
vouches for (Go `ClientIP` / `Proto`, Node `clientIP(req)` / `proto(req)`).

## Form posts

A hidden `agentgate_token` field is in the body, so validate it in the route
with the helper:

```go title="Go"
res, err := gate.SiteVerify(ctx, agentgate.SiteVerifyRequest{
	Token: r.FormValue("agentgate_token"), ExpectedAction: "signup",
	ExpectedOrigin: "https://app.example.com", RequestID: operationID,
})
if err != nil { /* AgentGate unavailable: fail closed */ }
if !res.Success { /* res.Error: invalid_token, token_used, … */ }
```

```js title="Node"
const r = await siteVerify(opts, { token: req.body.agentgate_token, expectedAction: "signup",
  expectedOrigin: "https://app.example.com", requestId: operationId });
if (!r.success) return res.status(403).json({ error: r.error }); // throws when AgentGate is down
```

## Tests

- Go: `go test ./examples/go-middleware/...` (mocked AgentGate) and
  `go test -run TestGoMiddlewareAgainstGateway .` (the real endpoint).
- Node: `cd examples/express && node --test` (mocked AgentGate).
