zuplo 6.68.18 → 6.68.24
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/articles/accounts/roles-and-permissions.mdx +0 -7
- package/docs/articles/certificate-pinning.mdx +82 -0
- package/docs/articles/custom-domains.mdx +3 -4
- package/docs/articles/log-plugin-splunk.mdx +1 -1
- package/docs/cli/list.mdx +36 -0
- package/docs/concepts/api-errors.md +294 -0
- package/docs/dev-portal/zudoku/configuration/authentication-auth0.md +6 -3
- package/docs/dev-portal/zudoku/configuration/authentication-openid.md +110 -0
- package/docs/dev-portal/zudoku/configuration/authentication.md +5 -2
- package/docs/dev-portal/zudoku/guides/redirects.md +185 -0
- package/docs/dev-portal/zudoku/openapi-extensions/x-code-samples.md +57 -0
- package/docs/dev-portal/zudoku/openapi-extensions/x-display-name.md +31 -0
- package/docs/dev-portal/zudoku/openapi-extensions/x-mcp-server.md +104 -0
- package/docs/dev-portal/zudoku/openapi-extensions/x-mcp.md +171 -0
- package/docs/dev-portal/zudoku/openapi-extensions/x-tag-groups.md +65 -0
- package/docs/dev-portal/zudoku/openapi-extensions/x-zudoku-collapsed.md +35 -0
- package/docs/dev-portal/zudoku/openapi-extensions/x-zudoku-collapsible.md +35 -0
- package/docs/dev-portal/zudoku/openapi-extensions/x-zudoku-playground-enabled.md +43 -0
- package/docs/handlers/websocket-handler.mdx +2 -2
- package/docs/mcp-server/graphql.mdx +0 -7
- package/docs/programmable-api/jwt-service-plugin.mdx +0 -7
- package/docs/programmable-api/streaming-zone-cache.mdx +0 -7
- package/package.json +4 -4
|
@@ -4,13 +4,6 @@ title: Role Permissions
|
|
|
4
4
|
|
|
5
5
|
<EnterpriseFeature name="Role Based Access Control" />
|
|
6
6
|
|
|
7
|
-
:::info{title="Beta"}
|
|
8
|
-
|
|
9
|
-
The specific permissions of each role are currently in beta and may change
|
|
10
|
-
without notice.
|
|
11
|
-
|
|
12
|
-
:::
|
|
13
|
-
|
|
14
7
|
Accounts in Zuplo can have multiple members with different roles. Each account
|
|
15
8
|
member can be a role that defines the permissions they have in the account.
|
|
16
9
|
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Certificate Pinning
|
|
3
|
+
sidebar_label: Certificate Pinning
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Certificate pinning is a security technique where a client validates a server's
|
|
7
|
+
TLS certificate against a known copy (or public key hash) stored locally in the
|
|
8
|
+
client application. While this can mitigate certain classes of man-in-the-middle
|
|
9
|
+
attacks, it's generally not recommended for modern APIs and is
|
|
10
|
+
[especially problematic](https://scotthelme.co.uk/why-we-need-to-do-more-to-reduce-certificate-lifetimes/)
|
|
11
|
+
for services that use short-lived, automatically rotated certificates.
|
|
12
|
+
|
|
13
|
+
:::warning
|
|
14
|
+
|
|
15
|
+
Zuplo strongly discourages certificate pinning for APIs running on Zuplo-managed
|
|
16
|
+
custom domains. Certificates are short-lived and rotate automatically on a
|
|
17
|
+
schedule outside of your control, which can break pinned clients without
|
|
18
|
+
warning.
|
|
19
|
+
|
|
20
|
+
:::
|
|
21
|
+
|
|
22
|
+
## Why pinning is discouraged on Zuplo
|
|
23
|
+
|
|
24
|
+
By default, Zuplo manages SSL certificates for your custom domain through
|
|
25
|
+
Cloudflare. These certificates are issued by either Google Trust Services or
|
|
26
|
+
Let's Encrypt and have the following properties:
|
|
27
|
+
|
|
28
|
+
- Certificates are issued for **90 days**.
|
|
29
|
+
- Certificates are automatically renewed approximately **30 days before
|
|
30
|
+
expiry**.
|
|
31
|
+
- Rotation is **not guaranteed to follow a strict 90-day cadence**. We may
|
|
32
|
+
rotate certificates earlier for security, operational, or infrastructure
|
|
33
|
+
reasons.
|
|
34
|
+
- Rotation happens without advance notification to the gateway owner.
|
|
35
|
+
|
|
36
|
+
Because rotation is automatic and the exact schedule isn't under your control,
|
|
37
|
+
any client that pins a specific certificate or public key can stop working at
|
|
38
|
+
any time. For most production APIs, this risk far outweighs the marginal
|
|
39
|
+
security benefit pinning provides.
|
|
40
|
+
|
|
41
|
+
## Recommended alternatives
|
|
42
|
+
|
|
43
|
+
If you or your clients are concerned about man-in-the-middle attacks or
|
|
44
|
+
unauthorized certificate issuance, use these alternatives instead of pinning:
|
|
45
|
+
|
|
46
|
+
- **[HTTP Strict Transport Security (HSTS)](https://https.cio.gov/hsts/)** to
|
|
47
|
+
force HTTPS and prevent protocol downgrade attacks.
|
|
48
|
+
- **[CAA DNS records](./custom-domains.mdx#caa-records)** to restrict which
|
|
49
|
+
certificate authorities can issue certificates for your domain.
|
|
50
|
+
|
|
51
|
+
## If a client insists on pinning
|
|
52
|
+
|
|
53
|
+
Pinning is strongly discouraged, but if a client application insists on it, they
|
|
54
|
+
can self-serve. The public portion of the certificate is returned on every TLS
|
|
55
|
+
handshake, so anyone connecting to your domain can retrieve it using standard
|
|
56
|
+
tools like `openssl` or `curl`. Zuplo doesn't need to send the certificate and
|
|
57
|
+
has no record of who has downloaded it.
|
|
58
|
+
|
|
59
|
+
If a client goes down this path, they should be aware that:
|
|
60
|
+
|
|
61
|
+
- Certificates rotate automatically and can change at any time.
|
|
62
|
+
- Pinning the Subject Public Key Info (SPKI) hash is more resilient than pinning
|
|
63
|
+
the full certificate, but still not guaranteed to survive rotation.
|
|
64
|
+
- The client is responsible for monitoring the certificate and updating their
|
|
65
|
+
pins before the next rotation breaks their application.
|
|
66
|
+
|
|
67
|
+
## Using your own long-lived SSL certificate
|
|
68
|
+
|
|
69
|
+
If you truly need full control over certificate rotation, the only supported
|
|
70
|
+
option is to supply your own SSL certificate for your domain and have Zuplo
|
|
71
|
+
install it. Contact [support@zuplo.com](mailto:support@zuplo.com) to arrange
|
|
72
|
+
this.
|
|
73
|
+
|
|
74
|
+
:::caution
|
|
75
|
+
|
|
76
|
+
Using a custom, long-lived SSL certificate shifts all renewal responsibility to
|
|
77
|
+
you. Expired certificates are a common cause of production outages. Before going
|
|
78
|
+
down this path, verify that you have an established process for tracking
|
|
79
|
+
expiration, renewing certificates ahead of time, and delivering the updated
|
|
80
|
+
certificate to Zuplo.
|
|
81
|
+
|
|
82
|
+
:::
|
|
@@ -166,10 +166,9 @@ Certificates are issued for 90 days and are automatically renewed approximately
|
|
|
166
166
|
|
|
167
167
|
Certificate pinning isn't recommended for Zuplo APIs as the certificates are
|
|
168
168
|
issued for short periods of time and renewed automatically. If you or your end
|
|
169
|
-
clients require certificate pinning,
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
for most use cases.)
|
|
169
|
+
clients require certificate pinning, see the dedicated
|
|
170
|
+
[Certificate Pinning](./certificate-pinning.mdx) page for the trade-offs,
|
|
171
|
+
alternatives, and options for retrieving or managing your own certificate.
|
|
173
172
|
|
|
174
173
|
:::
|
|
175
174
|
|
package/docs/cli/list.mdx
CHANGED
|
@@ -23,6 +23,22 @@ sidebar_label: list
|
|
|
23
23
|
"deprecated": false,
|
|
24
24
|
"hidden": false
|
|
25
25
|
},
|
|
26
|
+
{
|
|
27
|
+
"name": "output",
|
|
28
|
+
"type": "string",
|
|
29
|
+
"description": "Output format",
|
|
30
|
+
"default": "default",
|
|
31
|
+
"required": false,
|
|
32
|
+
"deprecated": false,
|
|
33
|
+
"hidden": false,
|
|
34
|
+
"alias": [
|
|
35
|
+
"o"
|
|
36
|
+
],
|
|
37
|
+
"choices": [
|
|
38
|
+
"default",
|
|
39
|
+
"json"
|
|
40
|
+
]
|
|
41
|
+
},
|
|
26
42
|
{
|
|
27
43
|
"name": "self-hosted-endpoint",
|
|
28
44
|
"type": "string",
|
|
@@ -30,6 +46,18 @@ sidebar_label: list
|
|
|
30
46
|
"required": false,
|
|
31
47
|
"deprecated": false,
|
|
32
48
|
"hidden": false
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"name": "show-details",
|
|
52
|
+
"type": "boolean",
|
|
53
|
+
"description": "Include deployment metadata in the output",
|
|
54
|
+
"default": false,
|
|
55
|
+
"required": false,
|
|
56
|
+
"deprecated": false,
|
|
57
|
+
"hidden": false,
|
|
58
|
+
"alias": [
|
|
59
|
+
"d"
|
|
60
|
+
]
|
|
33
61
|
}
|
|
34
62
|
]}
|
|
35
63
|
examples={[
|
|
@@ -37,6 +65,14 @@ sidebar_label: list
|
|
|
37
65
|
"$0 list",
|
|
38
66
|
"List all deployed environments for your project"
|
|
39
67
|
],
|
|
68
|
+
[
|
|
69
|
+
"$0 list --show-details",
|
|
70
|
+
"List all deployed environments with project and deployment names"
|
|
71
|
+
],
|
|
72
|
+
[
|
|
73
|
+
"$0 list --output json",
|
|
74
|
+
"List deployed environments as JSON"
|
|
75
|
+
],
|
|
40
76
|
[
|
|
41
77
|
"$0 list --account my-account --project my-project",
|
|
42
78
|
"Explicitly specify the account and project"
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: API Errors
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Well-designed API errors are as important as the successful responses your API
|
|
6
|
+
returns. A good error response tells the caller what went wrong, whether the
|
|
7
|
+
problem is on their side or yours, and what they can do about it. Zuplo
|
|
8
|
+
encourages every API to return standard, actionable error messages so that
|
|
9
|
+
developers integrating with your API spend less time guessing and more time
|
|
10
|
+
building.
|
|
11
|
+
|
|
12
|
+
This page explains the error format Zuplo uses by default, how it shows up in
|
|
13
|
+
the gateway, and how to customize the shape of error responses when your API has
|
|
14
|
+
its own conventions.
|
|
15
|
+
|
|
16
|
+
## Why standard errors matter
|
|
17
|
+
|
|
18
|
+
When every endpoint invents its own error shape, client code becomes brittle.
|
|
19
|
+
Developers have to special-case each response, parse ad-hoc fields, and guess at
|
|
20
|
+
whether a failure is retryable. Standardizing errors across your API produces
|
|
21
|
+
three concrete benefits:
|
|
22
|
+
|
|
23
|
+
- **Faster integration** -- consumers write one error handler that works
|
|
24
|
+
everywhere.
|
|
25
|
+
- **Better observability** -- logs, dashboards, and tools can parse errors
|
|
26
|
+
consistently.
|
|
27
|
+
- **Clearer contracts** -- your OpenAPI document can describe errors using the
|
|
28
|
+
same schema for every operation.
|
|
29
|
+
|
|
30
|
+
A good error response is short, machine-readable, and specific. It identifies
|
|
31
|
+
the kind of problem, says what happened in human terms, and includes enough
|
|
32
|
+
context (a request ID, a field name, a retry hint) that the caller can take the
|
|
33
|
+
next step without opening a support ticket.
|
|
34
|
+
|
|
35
|
+
## The Problem Details format
|
|
36
|
+
|
|
37
|
+
Zuplo defaults to the [Problem Details for HTTP APIs](https://httpproblems.com/)
|
|
38
|
+
format defined by [RFC 7807](https://datatracker.ietf.org/doc/html/rfc7807).
|
|
39
|
+
Problem Details is a small, widely adopted JSON schema for representing errors
|
|
40
|
+
from HTTP APIs. Responses use the `application/problem+json` content type and
|
|
41
|
+
follow a consistent shape.
|
|
42
|
+
|
|
43
|
+
A typical Problem Details response from Zuplo looks like this:
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"type": "https://httpproblems.com/http-status/401",
|
|
48
|
+
"title": "Unauthorized",
|
|
49
|
+
"status": 401,
|
|
50
|
+
"instance": "/v1/widgets",
|
|
51
|
+
"trace": {
|
|
52
|
+
"timestamp": "2026-04-19T17:13:31.352Z",
|
|
53
|
+
"requestId": "28f2d802-8e27-49c8-970d-39d90ef0ac61",
|
|
54
|
+
"buildId": "eb9ef87d-b55d-446e-9fdd-13c209c01b95"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The standard fields are:
|
|
60
|
+
|
|
61
|
+
- **`type`** -- a URI that identifies the kind of problem. Every occurrence of
|
|
62
|
+
the same problem should share the same `type`.
|
|
63
|
+
- **`title`** -- a short, human-readable summary that should stay consistent for
|
|
64
|
+
a given `type`.
|
|
65
|
+
- **`status`** -- the HTTP status code, duplicated in the body so clients that
|
|
66
|
+
log only the payload still see it.
|
|
67
|
+
- **`detail`** -- a human-readable explanation of this particular occurrence.
|
|
68
|
+
This is the field that varies from request to request.
|
|
69
|
+
- **`instance`** -- a URI or path that identifies the specific request that
|
|
70
|
+
produced the error.
|
|
71
|
+
|
|
72
|
+
Problem Details also allows **extensions** -- arbitrary additional fields that
|
|
73
|
+
carry problem-specific data. Zuplo uses extensions to include a `trace` object
|
|
74
|
+
containing the request ID, build ID, and timestamp on every error, which makes
|
|
75
|
+
support requests easy to correlate with logs.
|
|
76
|
+
|
|
77
|
+
:::tip
|
|
78
|
+
|
|
79
|
+
Keep `title` stable for a given error type and put request-specific information
|
|
80
|
+
in `detail` or in extensions. Clients match on `type` and `title`; humans read
|
|
81
|
+
`detail`.
|
|
82
|
+
|
|
83
|
+
:::
|
|
84
|
+
|
|
85
|
+
## How Zuplo uses Problem Details
|
|
86
|
+
|
|
87
|
+
Zuplo's built-in policies, handlers, and system errors all return Problem
|
|
88
|
+
Details responses out of the box. When an inbound policy rejects a request --
|
|
89
|
+
for example, the
|
|
90
|
+
[API Key Authentication policy](../policies/api-key-inbound.mdx) when a key is
|
|
91
|
+
missing, or the [Rate Limiting policy](../policies/rate-limit-inbound.mdx) when
|
|
92
|
+
a caller exceeds their quota -- the response body is a Problem Details object
|
|
93
|
+
with a `type`, `title`, `status`, and `trace`. The same is true for system
|
|
94
|
+
responses like unmatched routes and unsupported HTTP methods.
|
|
95
|
+
|
|
96
|
+
This means that consumers of a Zuplo-fronted API get a consistent error contract
|
|
97
|
+
for free across gateway errors, even before you write any custom code.
|
|
98
|
+
|
|
99
|
+
### Returning Problem Details from custom code
|
|
100
|
+
|
|
101
|
+
When you write a [custom handler](../handlers/custom-handler.mdx) or a
|
|
102
|
+
[custom policy](../articles/policies.mdx), return Problem Details responses
|
|
103
|
+
using the `HttpProblems` helper from `@zuplo/runtime`. The helper has a method
|
|
104
|
+
for every HTTP status code and automatically fills in `type`, `title`, `status`,
|
|
105
|
+
`instance`, and `trace`.
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import { HttpProblems, ZuploContext, ZuploRequest } from "@zuplo/runtime";
|
|
109
|
+
|
|
110
|
+
export default async function (request: ZuploRequest, context: ZuploContext) {
|
|
111
|
+
if (!request.user) {
|
|
112
|
+
return HttpProblems.unauthorized(request, context);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return request;
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
For the full list of methods and options, see the
|
|
120
|
+
[HttpProblems helper reference](../programmable-api/http-problems.mdx).
|
|
121
|
+
|
|
122
|
+
### Adding context with `detail` and extensions
|
|
123
|
+
|
|
124
|
+
Override the default fields when you have something more useful to say. Use
|
|
125
|
+
`detail` for a human-readable explanation of the specific failure, and use
|
|
126
|
+
extension members for structured data that clients can act on.
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
return HttpProblems.badRequest(request, context, {
|
|
130
|
+
title: "Invalid value for query parameter 'take'",
|
|
131
|
+
detail:
|
|
132
|
+
"The take parameter must be a number less than 100. The provided value was 'hello'.",
|
|
133
|
+
extensions: {
|
|
134
|
+
parameter: "take",
|
|
135
|
+
providedValue: "hello",
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The `title` stays consistent for every instance of this error, while `detail`
|
|
141
|
+
and the `parameter` extension tell the caller exactly what to fix.
|
|
142
|
+
|
|
143
|
+
### Throwing runtime errors
|
|
144
|
+
|
|
145
|
+
If your code throws rather than returning a response, use `RuntimeError` and
|
|
146
|
+
`ConfigurationError` to attach structured context that the gateway can surface.
|
|
147
|
+
Thrown errors are converted to Problem Details responses automatically, and any
|
|
148
|
+
`extensionMembers` you attach flow through to the response body.
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
import { RuntimeError } from "@zuplo/runtime";
|
|
152
|
+
|
|
153
|
+
throw new RuntimeError({
|
|
154
|
+
message: "Upstream database timed out",
|
|
155
|
+
extensionMembers: {
|
|
156
|
+
service: "orders-db",
|
|
157
|
+
timeoutMs: 5000,
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
See [Runtime Errors](../programmable-api/runtime-errors.mdx) for details on both
|
|
163
|
+
error classes and patterns for mapping them to problem responses.
|
|
164
|
+
|
|
165
|
+
## Customizing the error response format
|
|
166
|
+
|
|
167
|
+
Problem Details is the default, but it isn't the only option. If your API
|
|
168
|
+
already has an established error schema, or if you want to wrap every error in a
|
|
169
|
+
custom envelope, Zuplo provides two levels of customization.
|
|
170
|
+
|
|
171
|
+
### Per-response overrides
|
|
172
|
+
|
|
173
|
+
The simplest customization is to override fields on individual responses.
|
|
174
|
+
`HttpProblems` lets you change `title`, `detail`, `type`, `instance`, and add
|
|
175
|
+
arbitrary extensions without giving up the standard format.
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
return HttpProblems.tooManyRequests(
|
|
179
|
+
request,
|
|
180
|
+
context,
|
|
181
|
+
{
|
|
182
|
+
type: "https://errors.example.com/rate-limit-exceeded",
|
|
183
|
+
detail: "You've exceeded the 1000 requests per hour plan limit.",
|
|
184
|
+
extensions: {
|
|
185
|
+
plan: "free",
|
|
186
|
+
upgradeUrl: "https://example.com/upgrade",
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
"Retry-After": "3600",
|
|
191
|
+
},
|
|
192
|
+
);
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
This approach keeps the Problem Details shape while letting you customize types,
|
|
196
|
+
link to documentation, and include plan-specific or caller-specific metadata.
|
|
197
|
+
|
|
198
|
+
### Formatting every error with `ProblemResponseFormatter`
|
|
199
|
+
|
|
200
|
+
For more control, use the
|
|
201
|
+
[`ProblemResponseFormatter`](../programmable-api/problem-response-formatter.mdx)
|
|
202
|
+
to build problem responses directly. This is useful when you want to compute the
|
|
203
|
+
problem body yourself -- for example, mapping an upstream error payload into
|
|
204
|
+
your own error taxonomy.
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
import { ProblemResponseFormatter } from "@zuplo/runtime";
|
|
208
|
+
|
|
209
|
+
const problemDetails = {
|
|
210
|
+
type: "https://errors.example.com/validation-failed",
|
|
211
|
+
title: "Validation Failed",
|
|
212
|
+
status: 400,
|
|
213
|
+
detail: "The request body contains invalid fields.",
|
|
214
|
+
instance: request.url,
|
|
215
|
+
extensions: {
|
|
216
|
+
code: "VAL_001",
|
|
217
|
+
fields: ["email", "phone"],
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
return ProblemResponseFormatter.format(problemDetails, request, context);
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Replacing the error format globally
|
|
225
|
+
|
|
226
|
+
To change the shape of every error response the gateway returns -- including
|
|
227
|
+
errors raised by built-in policies -- register a global error handler in your
|
|
228
|
+
[runtime extensions](../programmable-api/runtime-extensions.mdx). The handler
|
|
229
|
+
runs for any unhandled error in the pipeline and returns the response of your
|
|
230
|
+
choice.
|
|
231
|
+
|
|
232
|
+
```ts
|
|
233
|
+
import { RuntimeExtensions } from "@zuplo/runtime";
|
|
234
|
+
|
|
235
|
+
export function runtimeInit(runtime: RuntimeExtensions) {
|
|
236
|
+
runtime.addErrorHandler(async (error, request, context) => {
|
|
237
|
+
return new Response(
|
|
238
|
+
JSON.stringify({
|
|
239
|
+
error: {
|
|
240
|
+
code: "internal_error",
|
|
241
|
+
message: error.message,
|
|
242
|
+
requestId: context.requestId,
|
|
243
|
+
},
|
|
244
|
+
}),
|
|
245
|
+
{
|
|
246
|
+
status: 500,
|
|
247
|
+
headers: { "content-type": "application/json" },
|
|
248
|
+
},
|
|
249
|
+
);
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Global error handlers let you keep Problem Details internally while projecting a
|
|
255
|
+
different schema to your callers, or replace the format entirely if your API has
|
|
256
|
+
an established error contract.
|
|
257
|
+
|
|
258
|
+
:::caution
|
|
259
|
+
|
|
260
|
+
Replacing the default format means you also take responsibility for including
|
|
261
|
+
trace information, preserving status codes, and documenting the new schema in
|
|
262
|
+
your OpenAPI document. Most APIs are best served by customizing Problem Details
|
|
263
|
+
fields rather than replacing the format.
|
|
264
|
+
|
|
265
|
+
:::
|
|
266
|
+
|
|
267
|
+
### Customizing specific system errors
|
|
268
|
+
|
|
269
|
+
Some gateway behaviors have dedicated extension points for error customization.
|
|
270
|
+
For example, you can replace the default 404 response by registering a
|
|
271
|
+
[not-found handler](../programmable-api/not-found-handler.mdx), which is useful
|
|
272
|
+
for serving custom error pages or matching your API's error schema on unmatched
|
|
273
|
+
routes.
|
|
274
|
+
|
|
275
|
+
## Choosing an approach
|
|
276
|
+
|
|
277
|
+
| Scenario | Approach |
|
|
278
|
+
| ---------------------------------------------------- | -------------------------------------------------------------- |
|
|
279
|
+
| Return a one-off error from a handler or policy | `HttpProblems` with `detail` and extensions |
|
|
280
|
+
| Build problem responses from external error payloads | `ProblemResponseFormatter.format()` |
|
|
281
|
+
| Attach context to thrown errors | `RuntimeError` with `extensionMembers` |
|
|
282
|
+
| Replace the error schema for every response | Global error handler via `runtime.addErrorHandler` |
|
|
283
|
+
| Customize only the 404 response | [Not-found handler](../programmable-api/not-found-handler.mdx) |
|
|
284
|
+
|
|
285
|
+
## Related resources
|
|
286
|
+
|
|
287
|
+
- [HttpProblems helper](../programmable-api/http-problems.mdx)
|
|
288
|
+
- [ProblemResponseFormatter](../programmable-api/problem-response-formatter.mdx)
|
|
289
|
+
- [Runtime Errors](../programmable-api/runtime-errors.mdx)
|
|
290
|
+
- [Not-found Handler](../programmable-api/not-found-handler.mdx)
|
|
291
|
+
- [Runtime Extensions](../programmable-api/runtime-extensions.mdx)
|
|
292
|
+
- [Custom Handlers](../handlers/custom-handler.mdx)
|
|
293
|
+
- [Policies](../articles/policies.mdx)
|
|
294
|
+
- [RFC 7807: Problem Details for HTTP APIs](https://datatracker.ietf.org/doc/html/rfc7807)
|
|
@@ -34,7 +34,10 @@ If you don't have an Auth0 account, you can sign up for a
|
|
|
34
34
|
- Production: `https://your-site.com/oauth/callback`
|
|
35
35
|
- Preview (wildcard): `https://*.your-domain.com/oauth/callback`
|
|
36
36
|
- Local Development: `http://localhost:3000/oauth/callback`
|
|
37
|
-
- **Allowed Logout URLs**:
|
|
37
|
+
- **Allowed Logout URLs**:
|
|
38
|
+
- Production: `https://your-site.com/oauth/logout-callback`
|
|
39
|
+
- Preview (wildcard): `https://*.your-domain.com/oauth/logout-callback`
|
|
40
|
+
- Local Development: `http://localhost:3000/oauth/logout-callback`
|
|
38
41
|
|
|
39
42
|
- **Allowed Web Origins**:
|
|
40
43
|
- Production: `https://your-site.com`
|
|
@@ -112,8 +115,8 @@ To enable logout for your Auth0 application:
|
|
|
112
115
|
|
|
113
116
|
1. Ensure your **Allowed Logout URLs** are configured in Auth0 (see
|
|
114
117
|
[Configure Auth0 Application](#setup-steps) above)
|
|
115
|
-
2. The logout URL
|
|
116
|
-
production)
|
|
118
|
+
2. The logout URL must use the `/oauth/logout-callback` path (e.g.,
|
|
119
|
+
`https://your-site.com/oauth/logout-callback` for production)
|
|
117
120
|
|
|
118
121
|
For older tenants, you may need to enable **RP-Initiated Logout** in your tenant settings. See the
|
|
119
122
|
[Auth0 logout documentation](https://auth0.com/docs/authenticate/login/logout/log-users-out-of-auth0)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: OpenID Connect (OIDC)
|
|
3
|
+
sidebar_label: OpenID Connect
|
|
4
|
+
description:
|
|
5
|
+
Configure any OpenID Connect compliant identity provider (Okta, Keycloak, Authentik, etc.) as the
|
|
6
|
+
authentication provider for Zudoku.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
Dev Portal supports any identity provider that implements the
|
|
10
|
+
[OpenID Connect](https://openid.net/specs/openid-connect-core-1_0.html) protocol via the generic
|
|
11
|
+
`openid` provider type. This includes Okta, Keycloak, Authentik, Ory, ZITADEL, AWS Cognito, Google
|
|
12
|
+
Identity, and most enterprise IdPs.
|
|
13
|
+
|
|
14
|
+
## Configuration
|
|
15
|
+
|
|
16
|
+
Add the `authentication` property to your [Dev Portal configuration](./overview.md):
|
|
17
|
+
|
|
18
|
+
```typescript title="zudoku.config.ts"
|
|
19
|
+
{
|
|
20
|
+
// ...
|
|
21
|
+
authentication: {
|
|
22
|
+
type: "openid",
|
|
23
|
+
clientId: "<your-client-id>",
|
|
24
|
+
issuer: "<the-issuer-url>",
|
|
25
|
+
scopes: ["openid", "profile", "email"], // Optional
|
|
26
|
+
},
|
|
27
|
+
// ...
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
| Option | Required | Description |
|
|
32
|
+
| ---------- | -------- | -------------------------------------------------------------------------------------------- |
|
|
33
|
+
| `clientId` | Yes | The OAuth client ID issued by your provider. |
|
|
34
|
+
| `issuer` | Yes | The issuer URL. Dev Portal discovers endpoints from `<issuer>/.well-known/openid-configuration`. |
|
|
35
|
+
| `scopes` | No | Scopes to request. Defaults to `["openid", "profile", "email"]`. |
|
|
36
|
+
|
|
37
|
+
## Provider Setup
|
|
38
|
+
|
|
39
|
+
Register Dev Portal as a public SPA / single page application client in your identity provider and set:
|
|
40
|
+
|
|
41
|
+
- Callback / Redirect URI to `https://your-site.com/oauth/callback`
|
|
42
|
+
- For local development, add `http://localhost:3000/oauth/callback`
|
|
43
|
+
- If your provider supports wildcards, add `https://*.your-domain.com/oauth/callback` for preview
|
|
44
|
+
environments
|
|
45
|
+
- Add your site origin to the list of allowed CORS origins
|
|
46
|
+
- Enable the `Authorization Code` grant with PKCE and the `Refresh Token` grant
|
|
47
|
+
|
|
48
|
+
### Okta
|
|
49
|
+
|
|
50
|
+
1. In the Okta admin console go to **Applications** → **Applications** → **Create App Integration**.
|
|
51
|
+
2. Select **OIDC - OpenID Connect** and **Single Page Application**.
|
|
52
|
+
3. Set **Sign-in redirect URIs** to `https://your-site.com/oauth/callback` (add
|
|
53
|
+
`http://localhost:3000/oauth/callback` for local development).
|
|
54
|
+
4. Under **Assignments**, assign the users or groups that should have access.
|
|
55
|
+
5. After creating the app, copy the **Client ID**. Your issuer is your Okta domain, for example
|
|
56
|
+
`https://your-tenant.okta.com` or a custom authorization server like
|
|
57
|
+
`https://your-tenant.okta.com/oauth2/default`.
|
|
58
|
+
6. Under **Security** → **API** → **Trusted Origins**, add your site origin for both CORS and
|
|
59
|
+
Redirect.
|
|
60
|
+
|
|
61
|
+
```typescript title="zudoku.config.ts"
|
|
62
|
+
{
|
|
63
|
+
authentication: {
|
|
64
|
+
type: "openid",
|
|
65
|
+
clientId: "<your-okta-client-id>",
|
|
66
|
+
issuer: "https://your-tenant.okta.com/oauth2/default",
|
|
67
|
+
scopes: ["openid", "profile", "email"],
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Keycloak
|
|
73
|
+
|
|
74
|
+
Use the realm issuer URL:
|
|
75
|
+
|
|
76
|
+
```typescript title="zudoku.config.ts"
|
|
77
|
+
{
|
|
78
|
+
authentication: {
|
|
79
|
+
type: "openid",
|
|
80
|
+
clientId: "zudoku",
|
|
81
|
+
issuer: "https://keycloak.example.com/realms/<your-realm>",
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
In the realm, create a client with **Client type** `OpenID Connect`, **Access type** `public`, and
|
|
87
|
+
enable **Standard Flow** (Authorization Code).
|
|
88
|
+
|
|
89
|
+
## Verifying the Issuer
|
|
90
|
+
|
|
91
|
+
You can confirm your issuer URL is correct by opening `<issuer>/.well-known/openid-configuration` in
|
|
92
|
+
a browser. It should return a JSON document listing `authorization_endpoint`, `token_endpoint`,
|
|
93
|
+
`userinfo_endpoint`, and `jwks_uri`.
|
|
94
|
+
|
|
95
|
+
## User Profile
|
|
96
|
+
|
|
97
|
+
After sign-in Dev Portal calls the provider's
|
|
98
|
+
[UserInfo endpoint](https://openid.net/specs/openid-connect-core-1_0.html#UserInfo) and reads
|
|
99
|
+
`name`, `email`, `picture`, and `email_verified` from the response. Map these claims in your
|
|
100
|
+
provider if they are not emitted by default.
|
|
101
|
+
|
|
102
|
+
## Troubleshooting
|
|
103
|
+
|
|
104
|
+
- **Discovery fails**: verify `<issuer>/.well-known/openid-configuration` resolves and matches the
|
|
105
|
+
`issuer` value in the document.
|
|
106
|
+
- **CORS errors on token / userinfo**: add your site origin to the provider's allowed origins.
|
|
107
|
+
- **Redirect URI mismatch**: the URI registered with the provider must match the Dev Portal origin
|
|
108
|
+
exactly, including protocol and port.
|
|
109
|
+
- **Missing profile fields**: ensure `profile` and `email` scopes are granted and that the provider
|
|
110
|
+
includes `name`, `email`, and `picture` claims in the UserInfo response.
|
|
@@ -15,8 +15,8 @@ authentication provider you use.
|
|
|
15
15
|
|
|
16
16
|
## Authentication Providers
|
|
17
17
|
|
|
18
|
-
Dev Portal supports Clerk, Auth0, Supabase, Firebase, Azure B2C, and any OpenID provider
|
|
19
|
-
|
|
18
|
+
Dev Portal supports Clerk, Auth0, Supabase, Firebase, Azure B2C, and any OpenID Connect provider
|
|
19
|
+
(including Okta, Keycloak, Authentik, and PingFederate).
|
|
20
20
|
|
|
21
21
|
Not seeing your authentication provider? [Let us know](https://github.com/zuplo/zudoku/issues)
|
|
22
22
|
|
|
@@ -96,6 +96,9 @@ When configuring your OpenID provider, you will need to set the following:
|
|
|
96
96
|
By default, the scopes "openid", "profile", and "email" are requested. You can customize these by
|
|
97
97
|
providing your own array of scopes.
|
|
98
98
|
|
|
99
|
+
For provider-specific guides (Okta, Keycloak, etc.), see the
|
|
100
|
+
[OpenID Connect setup page](./authentication-openid.md).
|
|
101
|
+
|
|
99
102
|
### Firebase
|
|
100
103
|
|
|
101
104
|
For Firebase authentication, you will need your Firebase project configuration. You can find this in
|