zuplo 6.70.69 → 6.70.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/ai-gateway/getting-started.mdx +12 -8
- package/docs/ai-gateway/introduction.mdx +11 -9
- package/docs/articles/api-key-buckets.mdx +4 -2
- package/docs/articles/archiving-requests-to-storage.mdx +4 -4
- package/docs/articles/branch-based-deployments.mdx +10 -8
- package/docs/articles/ci-cd-github/cleanup-on-branch-delete.mdx +52 -31
- package/docs/articles/ci-cd-github/pr-preview-environments.mdx +17 -6
- package/docs/articles/custom-ci-cd-azure.mdx +1 -1
- package/docs/articles/custom-ci-cd-bitbucket.mdx +1 -1
- package/docs/articles/custom-ci-cd-circleci.mdx +1 -1
- package/docs/articles/custom-ci-cd-github.mdx +1 -1
- package/docs/articles/custom-ci-cd-gitlab.mdx +1 -1
- package/docs/articles/graphql.mdx +276 -0
- package/docs/articles/monorepo-deployment.mdx +17 -3
- package/docs/articles/opentelemetry.mdx +5 -2
- package/docs/articles/per-user-rate-limits-using-db.mdx +5 -6
- package/docs/articles/securing-the-gateway-with-client-mtls.mdx +68 -43
- package/docs/articles/step-1-setup-basic-gateway.mdx +1 -3
- package/docs/articles/step-2-add-rate-limiting.mdx +1 -1
- package/docs/articles/testing.mdx +1 -1
- package/docs/articles/troubleshooting.md +7 -3
- package/docs/articles/waf-ddos-akamai.md +35 -16
- package/docs/articles/waf-ddos-aws-waf-shield.mdx +35 -16
- package/docs/articles/waf-ddos-fastly.mdx +10 -7
- package/docs/cli/deploy.mdx +13 -10
- package/docs/cli/deploy.partial.mdx +13 -10
- package/docs/dev-portal/zudoku/components/sidecar-box.mdx +131 -0
- package/docs/dev-portal/zudoku/configuration/api-catalog.md +62 -42
- package/docs/dev-portal/zudoku/configuration/api-reference.md +5 -4
- package/docs/dev-portal/zudoku/configuration/navigation.mdx +70 -7
- package/docs/guides/canary-routing-for-employees.mdx +103 -39
- package/docs/guides/modify-openapi-paths.mdx +3 -3
- package/docs/handlers/legacy-dev-portal-handler.mdx +1 -1
- package/docs/handlers/mcp-server.mdx +13 -11
- package/docs/handlers/url-forward.mdx +5 -1
- package/docs/handlers/url-rewrite.mdx +7 -2
- package/docs/handlers/websocket-handler.mdx +5 -1
- package/docs/mcp-gateway/observability/logging.mdx +19 -12
- package/docs/mcp-server/resources.mdx +27 -15
- package/docs/mcp-server/testing.mdx +0 -2
- package/docs/policies/archive-request-azure-storage-inbound/doc.md +1 -1
- package/docs/policies/archive-response-azure-storage-outbound/doc.md +1 -1
- package/docs/policies/ip-restriction-inbound/policy.ts +1 -1
- package/docs/programmable-api/http-problems.mdx +0 -18
- package/docs/programmable-api/jwt-service-plugin.mdx +131 -109
- package/docs/programmable-api/runtime-behaviors.mdx +4 -2
- package/docs/programmable-api/streaming-zone-cache.mdx +4 -6
- package/docs/programmable-api/web-crypto-apis.mdx +10 -6
- package/package.json +4 -4
- package/docs/errors/get-head-body-error.mdx +0 -41
|
@@ -16,11 +16,13 @@ issued by your API.
|
|
|
16
16
|
|
|
17
17
|
## JWT Token
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
By default, this service issues JWTs using the EdDSA algorithm. This is the
|
|
20
20
|
recommended algorithm for new applications due to its strong security properties
|
|
21
|
-
and performance characteristics. However,
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
and performance characteristics. However, not every library supports EdDSA, so
|
|
22
|
+
you should ensure that your [client library](https://jwt.io/libraries) can
|
|
23
|
+
handle this algorithm. If you need a different algorithm (for example, for
|
|
24
|
+
compatibility with an existing key pair or client library), use the `algorithm`
|
|
25
|
+
configuration option.
|
|
24
26
|
|
|
25
27
|
## Use Cases
|
|
26
28
|
|
|
@@ -68,9 +70,9 @@ export function runtimeInit(runtime: RuntimeExtensions) {
|
|
|
68
70
|
// Custom base path for the issuer endpoint (default: "/__zuplo/issuer")
|
|
69
71
|
basePath: "/custom",
|
|
70
72
|
|
|
71
|
-
// Token expiration time (default:
|
|
73
|
+
// Token expiration time (default: "1h")
|
|
72
74
|
// Can be a number (seconds) or a time span string
|
|
73
|
-
|
|
75
|
+
expiresIn: "5m", // or 300 for seconds
|
|
74
76
|
};
|
|
75
77
|
|
|
76
78
|
const jwtService = new JwtServicePlugin(options);
|
|
@@ -84,8 +86,16 @@ export function runtimeInit(runtime: RuntimeExtensions) {
|
|
|
84
86
|
is `"/__zuplo/issuer"`. This affects the issuer URL and OIDC configuration
|
|
85
87
|
endpoints.
|
|
86
88
|
|
|
87
|
-
- **`
|
|
88
|
-
|
|
89
|
+
- **`algorithm`** (optional): The asymmetric signing algorithm used for issued
|
|
90
|
+
JWTs. Default is `"EdDSA"`. Supported values are `EdDSA`, `RS256`, `RS384`,
|
|
91
|
+
`RS512`, `PS256`, `PS384`, `PS512`, `ES256`, `ES384`, and `ES512`. The
|
|
92
|
+
algorithm must match the configured key pair — for example, an RSA key
|
|
93
|
+
requires an `RS*` or `PS*` value and an Ed25519 key requires `EdDSA`.
|
|
94
|
+
Symmetric algorithms (like `HS256`) aren't supported because the plugin
|
|
95
|
+
publishes a JWKS endpoint.
|
|
96
|
+
|
|
97
|
+
- **`expiresIn`** (optional): Sets the default expiration time for JWTs. Default
|
|
98
|
+
is `"1h"`. Can be either:
|
|
89
99
|
- A **number**: Direct value in seconds (for example, `300` for 5 minutes)
|
|
90
100
|
- A **string**: Time span format (for example, `"5 minutes"`, `"1 hour"`,
|
|
91
101
|
`"7 days"`)
|
|
@@ -101,11 +111,11 @@ export function runtimeInit(runtime: RuntimeExtensions) {
|
|
|
101
111
|
Examples:
|
|
102
112
|
|
|
103
113
|
```ts
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
114
|
+
expiresIn: 300; // 300 seconds
|
|
115
|
+
expiresIn: "5 minutes"; // 5 minutes
|
|
116
|
+
expiresIn: "2 hours"; // 2 hours
|
|
117
|
+
expiresIn: "7 days"; // 7 days
|
|
118
|
+
expiresIn: "30 mins"; // 30 minutes
|
|
109
119
|
```
|
|
110
120
|
|
|
111
121
|
Note: Individual JWT creation can override this default by specifying
|
|
@@ -180,61 +190,44 @@ export default async function (request: ZuploRequest, context: ZuploContext) {
|
|
|
180
190
|
## Validating JWTs in Upstream Services
|
|
181
191
|
|
|
182
192
|
Upstream services can validate the JWTs issued by your Zuplo API by verifying
|
|
183
|
-
the signature and claims.
|
|
184
|
-
|
|
193
|
+
the signature and claims. The examples below use `EdDSA`, the plugin's default
|
|
194
|
+
signing algorithm. If you configured a different algorithm using the `algorithm`
|
|
195
|
+
option, use that value in the `algorithms` list instead.
|
|
185
196
|
|
|
186
197
|
### Node.js/Express Example
|
|
187
198
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
199
|
+
This example uses the [`jose`](https://github.com/panva/jose) library because
|
|
200
|
+
the popular `jsonwebtoken` library doesn't support the EdDSA algorithm.
|
|
201
|
+
|
|
202
|
+
```js title="validate-jwt.mjs"
|
|
203
|
+
import { createRemoteJWKSet, jwtVerify } from "jose";
|
|
191
204
|
|
|
192
205
|
// Replace with your actual Zuplo deployment name or custom domain
|
|
193
206
|
const ISSUER = "https://my-api.zuplo.app/__zuplo/issuer";
|
|
194
207
|
|
|
195
|
-
// Create a
|
|
196
|
-
const
|
|
197
|
-
jwksUri: `${ISSUER}/.well-known/jwks.json`,
|
|
198
|
-
cache: true,
|
|
199
|
-
cacheMaxAge: 600000, // 10 minutes
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
// Function to get the signing key
|
|
203
|
-
function getKey(header, callback) {
|
|
204
|
-
client.getSigningKey(header.kid, function (err, key) {
|
|
205
|
-
if (err) {
|
|
206
|
-
return callback(err);
|
|
207
|
-
}
|
|
208
|
-
const signingKey = key.getPublicKey();
|
|
209
|
-
callback(null, signingKey);
|
|
210
|
-
});
|
|
211
|
-
}
|
|
208
|
+
// Create a remote JWK Set that fetches and caches the public keys
|
|
209
|
+
const JWKS = createRemoteJWKSet(new URL(`${ISSUER}/.well-known/jwks.json`));
|
|
212
210
|
|
|
213
211
|
// Middleware to validate JWT
|
|
214
|
-
function validateJwt(req, res, next) {
|
|
212
|
+
async function validateJwt(req, res, next) {
|
|
215
213
|
const token = req.headers.authorization?.replace("Bearer ", "");
|
|
216
214
|
|
|
217
215
|
if (!token) {
|
|
218
216
|
return res.status(401).json({ error: "No token provided" });
|
|
219
217
|
}
|
|
220
218
|
|
|
221
|
-
|
|
222
|
-
token,
|
|
223
|
-
getKey,
|
|
224
|
-
{
|
|
219
|
+
try {
|
|
220
|
+
const { payload } = await jwtVerify(token, JWKS, {
|
|
225
221
|
issuer: ISSUER,
|
|
226
|
-
algorithms: ["
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
next();
|
|
236
|
-
},
|
|
237
|
-
);
|
|
222
|
+
algorithms: ["EdDSA"],
|
|
223
|
+
});
|
|
224
|
+
req.user = payload;
|
|
225
|
+
next();
|
|
226
|
+
} catch (err) {
|
|
227
|
+
return res
|
|
228
|
+
.status(401)
|
|
229
|
+
.json({ error: "Invalid token", details: err.message });
|
|
230
|
+
}
|
|
238
231
|
}
|
|
239
232
|
|
|
240
233
|
// Example usage
|
|
@@ -248,11 +241,18 @@ app.get("/protected", validateJwt, (req, res) => {
|
|
|
248
241
|
|
|
249
242
|
### Python/FastAPI Example
|
|
250
243
|
|
|
244
|
+
EdDSA validation in PyJWT requires the `cryptography` package. Install PyJWT
|
|
245
|
+
with the crypto extra: `pip install pyjwt[crypto]`.
|
|
246
|
+
|
|
247
|
+
The keys in Zuplo's JWKS don't include a `kid`, so this example loads the key
|
|
248
|
+
directly from the JWKS document rather than using `PyJWKClient`, which only
|
|
249
|
+
matches keys by `kid`.
|
|
250
|
+
|
|
251
251
|
```python title="validate_jwt.py"
|
|
252
252
|
from fastapi import FastAPI, Depends, HTTPException, Security
|
|
253
253
|
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
|
|
254
254
|
import jwt
|
|
255
|
-
from jwt import
|
|
255
|
+
from jwt import PyJWK
|
|
256
256
|
import requests
|
|
257
257
|
|
|
258
258
|
app = FastAPI()
|
|
@@ -262,21 +262,21 @@ security = HTTPBearer()
|
|
|
262
262
|
ISSUER = "https://my-api.zuplo.app/__zuplo/issuer"
|
|
263
263
|
JWKS_URL = f"{ISSUER}/.well-known/jwks.json"
|
|
264
264
|
|
|
265
|
-
|
|
266
|
-
|
|
265
|
+
def get_signing_key() -> PyJWK:
|
|
266
|
+
# Zuplo publishes a single signing key. Consider caching this
|
|
267
|
+
# response briefly to avoid fetching the JWKS on every request.
|
|
268
|
+
jwks = requests.get(JWKS_URL, timeout=10).json()
|
|
269
|
+
return PyJWK.from_dict(jwks["keys"][0])
|
|
267
270
|
|
|
268
271
|
async def validate_token(credentials: HTTPAuthorizationCredentials = Security(security)):
|
|
269
272
|
token = credentials.credentials
|
|
270
273
|
|
|
271
274
|
try:
|
|
272
|
-
# Get the signing key from JWKS
|
|
273
|
-
signing_key = jwks_client.get_signing_key_from_jwt(token)
|
|
274
|
-
|
|
275
275
|
# Verify and decode the token
|
|
276
276
|
payload = jwt.decode(
|
|
277
277
|
token,
|
|
278
|
-
|
|
279
|
-
algorithms=["
|
|
278
|
+
get_signing_key(),
|
|
279
|
+
algorithms=["EdDSA"],
|
|
280
280
|
issuer=ISSUER,
|
|
281
281
|
options={"verify_exp": True}
|
|
282
282
|
)
|
|
@@ -297,9 +297,11 @@ async def protected_route(token_data: dict = Depends(validate_token)):
|
|
|
297
297
|
|
|
298
298
|
### Dynamic OIDC Discovery
|
|
299
299
|
|
|
300
|
-
For more flexible JWT validation, you can
|
|
301
|
-
|
|
302
|
-
the
|
|
300
|
+
For more flexible JWT validation, you can dynamically discover the OIDC
|
|
301
|
+
configuration based on the issuer claim in the JWT. This example fetches the
|
|
302
|
+
issuer's OIDC discovery document to find the JWKS endpoint, then verifies the
|
|
303
|
+
token with the same [`jose`](https://github.com/panva/jose) library used in the
|
|
304
|
+
Node.js example above.
|
|
303
305
|
|
|
304
306
|
:::warning{title="Security Warning"}
|
|
305
307
|
|
|
@@ -310,8 +312,19 @@ ensure you are only allowing tokens from trusted issuers.
|
|
|
310
312
|
|
|
311
313
|
:::
|
|
312
314
|
|
|
313
|
-
```
|
|
314
|
-
import
|
|
315
|
+
```ts title="validate-jwt-dynamic.ts"
|
|
316
|
+
import { createRemoteJWKSet, decodeJwt, jwtVerify } from "jose";
|
|
317
|
+
import type { JWTPayload } from "jose";
|
|
318
|
+
import type { NextFunction, Request, Response } from "express";
|
|
319
|
+
|
|
320
|
+
// Make the verified JWT payload available as req.user
|
|
321
|
+
declare global {
|
|
322
|
+
namespace Express {
|
|
323
|
+
interface Request {
|
|
324
|
+
user?: JWTPayload;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
315
328
|
|
|
316
329
|
const ALLOWED_ISSUERS = [
|
|
317
330
|
"https://my-api.zuplo.app/__zuplo/issuer",
|
|
@@ -319,59 +332,66 @@ const ALLOWED_ISSUERS = [
|
|
|
319
332
|
// Add more allowed issuers as needed
|
|
320
333
|
];
|
|
321
334
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
335
|
+
// Cache the remote JWK Set for each issuer so discovery only runs once.
|
|
336
|
+
// jose handles JWKS caching and key rotation automatically.
|
|
337
|
+
const jwksCache = new Map<string, ReturnType<typeof createRemoteJWKSet>>();
|
|
338
|
+
|
|
339
|
+
async function getJwks(issuer: string) {
|
|
340
|
+
let jwks = jwksCache.get(issuer);
|
|
341
|
+
if (!jwks) {
|
|
342
|
+
// Discover the OIDC configuration for the issuer
|
|
343
|
+
const response = await fetch(`${issuer}/.well-known/openid-configuration`);
|
|
344
|
+
if (!response.ok) {
|
|
345
|
+
throw new Error(`OIDC discovery failed with status ${response.status}`);
|
|
328
346
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
const issuer = payload.iss;
|
|
336
|
-
if (!issuer) {
|
|
337
|
-
throw new Error("No issuer claim in token");
|
|
347
|
+
const metadata = (await response.json()) as {
|
|
348
|
+
issuer?: string;
|
|
349
|
+
jwks_uri?: string;
|
|
350
|
+
};
|
|
351
|
+
if (metadata.issuer !== issuer) {
|
|
352
|
+
throw new Error("Discovery document issuer mismatch");
|
|
338
353
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
if (!ALLOWED_ISSUERS.includes(issuer)) {
|
|
342
|
-
throw new Error(`Issuer ${issuer} isn't allowed`);
|
|
354
|
+
if (!metadata.jwks_uri) {
|
|
355
|
+
throw new Error("Issuer metadata is missing jwks_uri");
|
|
343
356
|
}
|
|
357
|
+
jwks = createRemoteJWKSet(new URL(metadata.jwks_uri));
|
|
358
|
+
jwksCache.set(issuer, jwks);
|
|
359
|
+
}
|
|
360
|
+
return jwks;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
async function validateJwtDynamic(token: string): Promise<JWTPayload> {
|
|
364
|
+
// Read the issuer claim without verifying the signature yet
|
|
365
|
+
const { iss: issuer } = decodeJwt(token);
|
|
366
|
+
if (!issuer) {
|
|
367
|
+
throw new Error("No issuer claim in token");
|
|
368
|
+
}
|
|
344
369
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
.discoveryRequest(issuerUrl)
|
|
349
|
-
.then((response) => oauth.processDiscoveryResponse(issuerUrl, response));
|
|
350
|
-
|
|
351
|
-
// Get the JWKS
|
|
352
|
-
const jwks = await oauth
|
|
353
|
-
.jwksRequest(as)
|
|
354
|
-
.then((response) => oauth.processJwksResponse(response));
|
|
355
|
-
|
|
356
|
-
// Import the JWT and validate it
|
|
357
|
-
const { payload: verifiedPayload, protectedHeader } =
|
|
358
|
-
await oauth.validateJwt(token, jwks, {
|
|
359
|
-
issuer: issuer,
|
|
360
|
-
audience: payload.aud, // Optional: validate audience
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
return verifiedPayload;
|
|
364
|
-
} catch (error) {
|
|
365
|
-
throw new Error(`JWT validation failed: ${error.message}`);
|
|
370
|
+
// Validate the issuer against the allow list before fetching anything
|
|
371
|
+
if (!ALLOWED_ISSUERS.includes(issuer)) {
|
|
372
|
+
throw new Error(`Issuer ${issuer} isn't allowed`);
|
|
366
373
|
}
|
|
374
|
+
|
|
375
|
+
// Verify the signature and standard claims
|
|
376
|
+
const { payload } = await jwtVerify(token, await getJwks(issuer), {
|
|
377
|
+
issuer,
|
|
378
|
+
algorithms: ["EdDSA"],
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
return payload;
|
|
367
382
|
}
|
|
368
383
|
|
|
369
384
|
// Express middleware example
|
|
370
|
-
function validateJwtMiddleware(
|
|
385
|
+
function validateJwtMiddleware(
|
|
386
|
+
req: Request,
|
|
387
|
+
res: Response,
|
|
388
|
+
next: NextFunction,
|
|
389
|
+
) {
|
|
371
390
|
const token = req.headers.authorization?.replace("Bearer ", "");
|
|
372
391
|
|
|
373
392
|
if (!token) {
|
|
374
|
-
|
|
393
|
+
res.status(401).json({ error: "No token provided" });
|
|
394
|
+
return;
|
|
375
395
|
}
|
|
376
396
|
|
|
377
397
|
validateJwtDynamic(token)
|
|
@@ -379,8 +399,10 @@ function validateJwtMiddleware(req, res, next) {
|
|
|
379
399
|
req.user = payload;
|
|
380
400
|
next();
|
|
381
401
|
})
|
|
382
|
-
.catch((error) => {
|
|
383
|
-
res
|
|
402
|
+
.catch((error: Error) => {
|
|
403
|
+
res
|
|
404
|
+
.status(401)
|
|
405
|
+
.json({ error: `JWT validation failed: ${error.message}` });
|
|
384
406
|
});
|
|
385
407
|
}
|
|
386
408
|
|
|
@@ -21,5 +21,7 @@ for `Request.body` specifies that on `GET` and `HEAD` requests the value must be
|
|
|
21
21
|
`null`. Different APIs, networks, and gateways follow this spec to varying
|
|
22
22
|
degrees. In some cases they allow in others they don't.
|
|
23
23
|
|
|
24
|
-
By default, Zuplo
|
|
25
|
-
|
|
24
|
+
By default, Zuplo removes the body from any `GET` or `HEAD` request and adds a
|
|
25
|
+
`zp-body-removed: true` header so your backend knows the body was removed. The
|
|
26
|
+
request then proceeds as normal. For more details, including an example policy
|
|
27
|
+
that rejects these requests, see [zp-body-removed](./zp-body-removed.mdx).
|
|
@@ -75,12 +75,10 @@ export default async function handler(
|
|
|
75
75
|
// Cache the response for 1 hour (3600 seconds)
|
|
76
76
|
await cache.put(cacheKey, streamForCache, 3600);
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
},
|
|
83
|
-
});
|
|
78
|
+
const headers = new Headers(response.headers);
|
|
79
|
+
headers.set("X-Cache", "MISS");
|
|
80
|
+
|
|
81
|
+
return new Response(streamForResponse, { headers });
|
|
84
82
|
}
|
|
85
83
|
```
|
|
86
84
|
|
|
@@ -96,15 +96,19 @@ async function sign(value: string, secret: string) {
|
|
|
96
96
|
|
|
97
97
|
// `mac` is an ArrayBuffer, so you need to make a few changes to get
|
|
98
98
|
// it into a ByteString, and then a Base64-encoded string.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
// must convert "+" to "-" as urls encode "+" as " "
|
|
102
|
-
base64Mac = base64Mac.replaceAll("+", "-");
|
|
103
|
-
|
|
104
|
-
return base64Mac;
|
|
99
|
+
return btoa(String.fromCharCode(...new Uint8Array(mac)));
|
|
105
100
|
}
|
|
106
101
|
```
|
|
107
102
|
|
|
103
|
+
:::note
|
|
104
|
+
|
|
105
|
+
The signature is standard Base64 and can contain the characters `+`, `/`, and
|
|
106
|
+
`=`. If you transport the signature in a URL — for example, as a query parameter
|
|
107
|
+
— encode it with `encodeURIComponent()` first, or convert it to the URL-safe
|
|
108
|
+
Base64 alphabet on both the signing and verifying sides.
|
|
109
|
+
|
|
110
|
+
:::
|
|
111
|
+
|
|
108
112
|
### Verify a Value
|
|
109
113
|
|
|
110
114
|
```ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zuplo",
|
|
3
|
-
"version": "6.70.
|
|
3
|
+
"version": "6.70.70",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The programmable API Gateway",
|
|
6
6
|
"author": "Zuplo, Inc.",
|
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
"zuplo": "zuplo.js"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@zuplo/cli": "6.70.
|
|
23
|
-
"@zuplo/core": "6.70.
|
|
24
|
-
"@zuplo/runtime": "6.70.
|
|
22
|
+
"@zuplo/cli": "6.70.70",
|
|
23
|
+
"@zuplo/core": "6.70.70",
|
|
24
|
+
"@zuplo/runtime": "6.70.70",
|
|
25
25
|
"@zuplo/test": "1.4.0"
|
|
26
26
|
}
|
|
27
27
|
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: GET/HEAD Body Error (GET_HEAD_BODY_ERROR)
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
A GET or HEAD request included a body, which is not allowed. The
|
|
6
|
-
[Fetch specification](https://fetch.spec.whatwg.org/) defines that GET and HEAD
|
|
7
|
-
requests must not have a request body.
|
|
8
|
-
|
|
9
|
-
## Why this happens
|
|
10
|
-
|
|
11
|
-
The HTTP specification states that GET and HEAD requests are intended for
|
|
12
|
-
retrieving resources and should not include a request body. While some HTTP
|
|
13
|
-
clients allow sending a body with GET requests, the Zuplo runtime enforces the
|
|
14
|
-
specification and rejects these requests.
|
|
15
|
-
|
|
16
|
-
## How to fix
|
|
17
|
-
|
|
18
|
-
- **Use a different HTTP method** - If the request needs to send data in the
|
|
19
|
-
body, use `POST`, `PUT`, or `PATCH` instead of `GET` or `HEAD`.
|
|
20
|
-
- **Move data to query parameters** - If the request must remain a `GET`,
|
|
21
|
-
convert the body data to URL query parameters.
|
|
22
|
-
- **Remove the body** - If the body was included unintentionally, remove it from
|
|
23
|
-
the request.
|
|
24
|
-
|
|
25
|
-
## Common causes
|
|
26
|
-
|
|
27
|
-
- **HTTP client defaults** - Some HTTP client libraries or frameworks
|
|
28
|
-
automatically attach a body to requests, even for GET methods. Check the
|
|
29
|
-
client configuration.
|
|
30
|
-
- **Copied request configuration** - A request configuration copied from a POST
|
|
31
|
-
endpoint may still include a body when adapted for a GET endpoint.
|
|
32
|
-
- **Framework behavior** - Certain frontend frameworks or API testing tools may
|
|
33
|
-
silently include an empty body or content-type header on GET requests.
|
|
34
|
-
|
|
35
|
-
:::note
|
|
36
|
-
|
|
37
|
-
This is a client-side issue. Update the request on the calling side to remove
|
|
38
|
-
the body or change the HTTP method. No changes are needed on the Zuplo gateway
|
|
39
|
-
configuration.
|
|
40
|
-
|
|
41
|
-
:::
|