zuplo 6.73.27 → 6.73.28

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.
@@ -16,8 +16,11 @@ npx create-zuplo-api@latest
16
16
  `create-zuplo-api` comes with the following options:
17
17
 
18
18
  - `-v, --version` - Output the current version of create-zuplo-api
19
- - `--eslint` - Initialize with ESLint configuration
20
- - `--prettier` - Initialize with Prettier configuration
19
+ - `--linter <linter>` - The linter to configure (`eslint`, `biome`, `oxlint`, or
20
+ `none`). See
21
+ [Choosing a linter and formatter](#choosing-a-linter-and-formatter)
22
+ - `--formatter <formatter>` - The formatter to configure (`prettier`, `biome`,
23
+ `oxfmt`, or `none`)
21
24
  - `--empty` - Initialize an empty project
22
25
  - `--use-npm` - Explicitly tell the CLI to bootstrap the application using npm
23
26
  - `--use-pnpm` - Explicitly tell the CLI to bootstrap the application using pnpm
@@ -38,6 +41,9 @@ npx create-zuplo-api@latest
38
41
  - `--agents <agents>` - Comma-separated list of AI coding agents to configure
39
42
  (`claude`, `copilot`, `cursor`, `windsurf`, `codex`), or `none` to skip agent
40
43
  setup. See [Configuring AI coding agents](#configuring-ai-coding-agents)
44
+ - `--template <template-name>` - A built-in template to bootstrap the API with
45
+ (`default`, `default-empty`, or `ai-gateway-v2`). Can't be combined with
46
+ `--example` or `--empty`. See [Choosing a template](#choosing-a-template)
41
47
  - `-e, --example <example-name|github-url>` - An example to bootstrap the API
42
48
  with. You can use an example name from the official Zuplo repository or a
43
49
  public GitHub URL. The URL can use any branch and/or subdirectory
@@ -53,8 +59,6 @@ Each boolean option also accepts a `--no-` form that turns the feature off:
53
59
 
54
60
  | Opt-out flag | Effect |
55
61
  | --------------------- | --------------------------------------------------- |
56
- | `--no-eslint` | Skip ESLint configuration |
57
- | `--no-prettier` | Skip Prettier configuration |
58
62
  | `--no-git` | Don't initialize the project as a git repository |
59
63
  | `--no-install` | Don't install packages |
60
64
  | `--no-version-check` | Don't check for an outdated version |
@@ -62,19 +66,84 @@ Each boolean option also accepts a `--no-` form that turns the feature off:
62
66
 
63
67
  These flags matter most in CI and scripted use. `--yes` accepts whatever is
64
68
  saved in your preferences for any option you didn't pass, so a value you chose
65
- once on a workstation can carry into a later run. A `--no-` flag ignores saved
66
- preferences and suppresses the prompt outright, which makes the result the same
67
- on every machine:
69
+ once on a workstation can carry into a later run. A `--no-` flag, or an explicit
70
+ value such as `--linter none`, ignores saved preferences and suppresses the
71
+ prompt outright, which makes the result the same on every machine:
68
72
 
69
73
  ```bash
70
- npx create-zuplo-api@latest my-api --yes --no-eslint --no-prettier --no-install --no-git
74
+ npx create-zuplo-api@latest my-api --yes --linter none --formatter none --no-install --no-git
71
75
  ```
72
76
 
73
77
  If you pass both spellings of the same option, the affirmative flag wins — so
74
- `--eslint --no-eslint` initializes ESLint. `--no-server-project` is the
78
+ `--git --no-git` initializes a git repository. `--no-server-project` is the
75
79
  exception: it always wins, even when combined with `--server-project` or
76
80
  `--account`.
77
81
 
82
+ ### Choosing a template
83
+
84
+ `--template` selects which built-in template the CLI scaffolds from. Without the
85
+ flag you get the `default` template.
86
+
87
+ | Value | What it scaffolds |
88
+ | --------------- | --------------------------------------------------------------------------------------------- |
89
+ | `default` | Sample `/todos` routes, a `hello-world` module, a dev portal in `docs/`, and VS Code settings |
90
+ | `default-empty` | The same project with no routes defined and no VS Code settings |
91
+ | `ai-gateway-v2` | An [AI Gateway](../ai-gateway/introduction.mdx) project, without a dev portal |
92
+
93
+ `--empty` is shorthand for `--template default-empty`.
94
+
95
+ The `ai-gateway-v2` template scaffolds `config/ai.oas.json`, which routes
96
+ `/:app_id/v1/*` to the AI Gateway handler, and `config/policies.json`, which
97
+ declares the policies an application's policy chain can select. It ships no
98
+ `docs/` directory, so the CLI skips the
99
+ [dev portal](../dev-portal/introduction.mdx) workspace:
100
+
101
+ ```bash
102
+ npx create-zuplo-api@latest my-gateway --template ai-gateway-v2
103
+ ```
104
+
105
+ `--template` names a built-in template, which is why it can't be combined with
106
+ `--example` (a project pulled from GitHub) or with `--empty` (use
107
+ `--template default-empty`). Either combination stops the CLI before it writes
108
+ any files, as does a template name that isn't in the table above.
109
+
110
+ ### Choosing a linter and formatter
111
+
112
+ `--linter` and `--formatter` pick the tools the CLI sets up. Without either flag
113
+ the CLI asks, with ESLint and Prettier preselected.
114
+
115
+ | `--linter` | Config file | Dev dependencies |
116
+ | ---------- | ------------------ | ------------------------------------------- |
117
+ | `eslint` | `eslint.config.js` | `eslint`, `@eslint/js`, `typescript-eslint` |
118
+ | `biome` | `biome.json` | `@biomejs/biome` |
119
+ | `oxlint` | `.oxlintrc.json` | `oxlint` |
120
+ | `none` | None | None |
121
+
122
+ | `--formatter` | Config file | Dev dependencies |
123
+ | ----------------- | ------------------ | ---------------- |
124
+ | `prettier` | `.prettierrc.json` | `prettier` |
125
+ | `biome` | `biome.json` | `@biomejs/biome` |
126
+ | `oxfmt` (in beta) | `.oxfmtrc.json` | `oxfmt` |
127
+ | `none` | None | None |
128
+
129
+ A linter adds `lint` and `lint:fix` scripts to `package.json`, and a formatter
130
+ adds `format` and `format:check`. The dev portal workspace in `docs/` gets the
131
+ same `lint` script. The CLI also writes a `.vscode/extensions.json` that
132
+ recommends the editor extensions for the tools you picked.
133
+
134
+ Biome fills both roles from one dependency and a single `biome.json`:
135
+
136
+ ```bash
137
+ npx create-zuplo-api@latest my-api --linter biome --formatter biome
138
+ ```
139
+
140
+ Picking Biome as the linter preselects it as the formatter in the interactive
141
+ prompt. Pairing ESLint with Prettier also installs `eslint-config-prettier`, so
142
+ the linter doesn't fight the formatter.
143
+
144
+ An unrecognized value is an error and prints the valid values. With `--yes` or in
145
+ CI, the CLI uses your saved preferences, or ESLint and Prettier if you have none.
146
+
78
147
  ### Configuring AI coding agents
79
148
 
80
149
  Pass `--agents` to write instruction files and MCP configuration for the coding
@@ -171,8 +240,16 @@ The CLI asks the following questions:
171
240
  ```text
172
241
  ? What is your project named? › my-api
173
242
  ? Create a matching project on portal.zuplo.com? › No / Yes
174
- ? Would you like to use ESLint? › No / Yes
175
- ? Would you like to use Prettier? › No / Yes
243
+ ? Which linter would you like to use? › - Use arrow-keys. Return to submit.
244
+ ❯ ESLint
245
+ Biome
246
+ Oxlint
247
+ None
248
+ ? Which formatter would you like to use? › - Use arrow-keys. Return to submit.
249
+ ❯ Prettier
250
+ Biome
251
+ Oxfmt
252
+ None
176
253
  ? Which AI coding agents would you like to configure? › - Space to select. Return to submit
177
254
  ◯ Claude Code
178
255
  ◯ GitHub Copilot
@@ -101,6 +101,7 @@
101
101
  | upstream-gcp-federated-auth-inbound | Upstream GCP Federated Auth | Authenticates with GCP resources or Google services using Workload Identity Federation allowing secure access to these resources without requiring the use of a service account private key. | api-gateway |
102
102
  | upstream-gcp-jwt-inbound | Upstream GCP Self-Signed JWT | Creates a self-signed JWT token (generated using a Google Service Account JSON) and attaches it to the outgoing request. Useful when calling GCP services like Cloud Endpoints / ESPv2 | api-gateway |
103
103
  | upstream-gcp-service-auth-inbound | Upstream GCP Service Auth | Creates an ID Token from Google's OAuth service and attaches it to the outgoing request. Useful when calling GCP services or Google APIs that are secured with GCP IAM. | api-gateway |
104
+ | upstream-oauth-client-credentials-inbound | Upstream OAuth 2.0 Client Credentials Auth | Fetches an access token from any OAuth 2.0 token endpoint using the client credentials grant and adds it to a header (`Authorization` by default) on the upstream request. Tokens are cached until shortly before they expire. | api-gateway |
104
105
  | upstream-zuplo-jwt-auth-inbound | Upstream Zuplo JWT | Generates a Zuplo JWT token and attaches it to the outgoing request. This policy creates a self-signed JWT using the Zuplo JWT plugin and adds it to the specified header for upstream authentication. | api-gateway |
105
106
  | web-bot-auth-inbound | Web Bot Auth | Authenticate bots using web-bot-auth HTTP Message Signatures. | api-gateway |
106
107
  | xml-to-json-outbound | XML to JSON | Parses XML and converts it to JSON. | api-gateway |
@@ -0,0 +1,124 @@
1
+ This policy authenticates your Zuplo gateway to OAuth 2.0-protected backend
2
+ services by automatically adding an access token to the `Authorization` header
3
+ (or a custom header) of upstream requests. It uses the OAuth 2.0 client
4
+ credentials grant against any token endpoint you configure, so it works with
5
+ Auth0, Okta, Keycloak, Microsoft Entra ID, and any other standards-compliant
6
+ identity provider.
7
+
8
+ ### How It Works
9
+
10
+ The policy performs the following operations:
11
+
12
+ 1. Requests an access token from the configured token endpoint using the client
13
+ credentials grant
14
+ 2. Caches the token for subsequent requests until it nears expiration
15
+ 3. Adds the token to the configured header (default `Authorization`) using the
16
+ `token_type` returned by the token endpoint, falling back to the configured
17
+ `headerScheme` (default `Bearer`)
18
+ 4. Automatically handles token renewal when needed
19
+
20
+ Tokens are cached for `expires_in - expirationOffsetSeconds` seconds. If the
21
+ token endpoint doesn't return an `expires_in` value, the policy caches the token
22
+ conservatively as if it expired after 10 minutes (5 minutes with the default
23
+ `expirationOffsetSeconds` of 300) rather than caching it indefinitely.
24
+
25
+ ### Policy Configuration
26
+
27
+ Configure the policy with your identity provider's token endpoint and client
28
+ credentials:
29
+
30
+ ```json
31
+ {
32
+ "name": "upstream-oauth-client-credentials",
33
+ "export": "UpstreamOAuthClientCredentialsInboundPolicy",
34
+ "module": "$import(@zuplo/runtime)",
35
+ "options": {
36
+ "tokenUrl": "https://your-tenant.us.auth0.com/oauth/token",
37
+ "clientId": "$env(OAUTH_CLIENT_ID)",
38
+ "clientSecret": "$env(OAUTH_CLIENT_SECRET)",
39
+ "audience": "https://api.example.com",
40
+ "scope": "read:orders write:orders"
41
+ }
42
+ }
43
+ ```
44
+
45
+ ### Sending Client Credentials
46
+
47
+ By default (`credentialsIn: "body"`), the client credentials are sent as
48
+ `client_id` and `client_secret` form parameters in the token request body. While
49
+ [RFC 6749 section 2.3.1](https://datatracker.ietf.org/doc/html/rfc6749#section-2.3.1)
50
+ recommends HTTP Basic authentication, `body` is the default because it's the
51
+ method most widely accepted across identity providers (including Auth0, Okta,
52
+ Entra ID, and Keycloak) and avoids credential-encoding differences between
53
+ providers' Basic authentication implementations.
54
+
55
+ Set `credentialsIn` to `header` to send the credentials with HTTP Basic
56
+ authentication instead:
57
+
58
+ ```json
59
+ {
60
+ "options": {
61
+ "tokenUrl": "https://idp.example.com/oauth2/token",
62
+ "clientId": "$env(OAUTH_CLIENT_ID)",
63
+ "clientSecret": "$env(OAUTH_CLIENT_SECRET)",
64
+ "credentialsIn": "header"
65
+ }
66
+ }
67
+ ```
68
+
69
+ ### Provider-Specific Parameters
70
+
71
+ Some identity providers require extra form parameters on the token request. Use
72
+ `additionalParameters` to send them, for example the `resource` parameter:
73
+
74
+ ```json
75
+ {
76
+ "options": {
77
+ "tokenUrl": "https://idp.example.com/oauth2/token",
78
+ "clientId": "$env(OAUTH_CLIENT_ID)",
79
+ "clientSecret": "$env(OAUTH_CLIENT_SECRET)",
80
+ "additionalParameters": {
81
+ "resource": "https://api.example.com"
82
+ }
83
+ }
84
+ }
85
+ ```
86
+
87
+ The `grant_type` parameter is always `client_credentials` and the dedicated
88
+ `clientId`, `clientSecret`, `scope`, and `audience` options always take
89
+ precedence over entries in `additionalParameters`.
90
+
91
+ ### Usage Example
92
+
93
+ Apply the policy to routes that need to call your OAuth-protected backend:
94
+
95
+ ```json
96
+ {
97
+ "paths": {
98
+ "/api/orders": {
99
+ "get": {
100
+ "x-zuplo-route": {
101
+ "policies": {
102
+ "inbound": ["jwt-auth", "upstream-oauth-client-credentials"]
103
+ },
104
+ "handler": {
105
+ "export": "forwardToOrigin",
106
+ "module": "$import(@zuplo/runtime)",
107
+ "options": {
108
+ "baseUrl": "https://api.internal.example.com"
109
+ }
110
+ }
111
+ }
112
+ }
113
+ }
114
+ }
115
+ }
116
+ ```
117
+
118
+ ### Security Considerations
119
+
120
+ - Store the client secret as an environment variable using `$env(VARIABLE_NAME)`
121
+ syntax
122
+ - Grant the OAuth client the minimum scopes required to access your backend
123
+ services
124
+ - Regularly rotate your client secrets according to your security policies
@@ -0,0 +1,18 @@
1
+ Secure your origin server with OAuth 2.0 authentication by automatically adding
2
+ an `Authorization` header to upstream requests. This policy enables your Zuplo
3
+ gateway to authenticate with any OAuth 2.0 identity provider that supports the
4
+ client credentials grant — such as Auth0, Okta, Keycloak, Microsoft Entra ID, or
5
+ your own authorization server.
6
+
7
+ With this policy, you'll benefit from:
8
+
9
+ - **Enhanced Backend Security**: Restrict access to your origin servers to only
10
+ your Zuplo gateway
11
+ - **Simplified Authentication**: Delegate authentication and authorization to
12
+ your gateway without backend code changes
13
+ - **Automatic Token Management**: Handle token acquisition, caching, and renewal
14
+ automatically
15
+ - **Provider Flexibility**: Works with any standards-compliant OAuth 2.0 token
16
+ endpoint, no provider-specific policy required
17
+ - **Credential Security**: Store sensitive client credentials securely in your
18
+ Zuplo environment
@@ -0,0 +1,126 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft-07/schema",
3
+ "$id": "https://cdn.zuplo.com/policies/runtime/schemas/upstream-oauth-client-credentials-inbound.json",
4
+ "type": "object",
5
+ "title": "Upstream OAuth 2.0 Client Credentials Auth",
6
+ "isDeprecated": false,
7
+ "isPaidAddOn": false,
8
+ "isEnterprise": false,
9
+ "isInternal": false,
10
+ "isBeta": false,
11
+ "isHidden": false,
12
+ "requiresAI": false,
13
+ "products": ["api-gateway"],
14
+ "description": "Fetches an access token from any OAuth 2.0 token endpoint using the client credentials grant and adds it to a header (`Authorization` by default) on the upstream request. Tokens are cached until shortly before they expire.",
15
+ "deprecatedMessage": "",
16
+ "required": ["handler"],
17
+ "properties": {
18
+ "handler": {
19
+ "type": "object",
20
+ "default": {},
21
+ "required": ["export", "module", "options"],
22
+ "properties": {
23
+ "export": {
24
+ "const": "UpstreamOAuthClientCredentialsInboundPolicy",
25
+ "description": "The name of the exported type"
26
+ },
27
+ "module": {
28
+ "const": "$import(@zuplo/runtime)",
29
+ "description": "The module containing the policy"
30
+ },
31
+ "options": {
32
+ "title": "UpstreamOAuthClientCredentialsInboundPolicyOptions",
33
+ "type": "object",
34
+ "description": "The options for this policy.",
35
+ "additionalProperties": false,
36
+ "required": ["tokenUrl", "clientId", "clientSecret"],
37
+ "properties": {
38
+ "tokenUrl": {
39
+ "type": "string",
40
+ "examples": ["https://your-tenant.us.auth0.com/oauth/token"],
41
+ "description": "The URL of the OAuth 2.0 token endpoint that issues the access token."
42
+ },
43
+ "clientId": {
44
+ "type": "string",
45
+ "examples": ["my-client-id"],
46
+ "description": "The client ID used to authenticate with the token endpoint."
47
+ },
48
+ "clientSecret": {
49
+ "type": "string",
50
+ "examples": ["$env(OAUTH_CLIENT_SECRET)"],
51
+ "description": "The client secret used to authenticate with the token endpoint."
52
+ },
53
+ "scope": {
54
+ "type": "string",
55
+ "examples": ["read:orders write:orders"],
56
+ "description": "Space-delimited list of scopes to request. When not set, the `scope` parameter is omitted from the token request."
57
+ },
58
+ "audience": {
59
+ "type": "string",
60
+ "examples": ["https://api.example.com"],
61
+ "description": "The value of the `audience` form parameter sent to the token endpoint. Required by some identity providers such as Auth0. When not set, the parameter is omitted from the token request."
62
+ },
63
+ "credentialsIn": {
64
+ "type": "string",
65
+ "title": "OAuthClientCredentialsLocation",
66
+ "default": "body",
67
+ "enum": ["body", "header"],
68
+ "x-advanced": true,
69
+ "description": "Where the client credentials are sent on the token request. `body` sends `client_id` and `client_secret` as form parameters. `header` sends them with HTTP Basic authentication as described in RFC 6749 section 2.3.1."
70
+ },
71
+ "additionalParameters": {
72
+ "type": "object",
73
+ "additionalProperties": {
74
+ "type": "string"
75
+ },
76
+ "x-advanced": true,
77
+ "description": "Additional form parameters to include in the token request, for example `resource` for identity providers that require it."
78
+ },
79
+ "headerName": {
80
+ "type": "string",
81
+ "default": "Authorization",
82
+ "x-advanced": true,
83
+ "description": "The name of the header on the upstream request that the access token is set on."
84
+ },
85
+ "headerScheme": {
86
+ "type": "string",
87
+ "default": "Bearer",
88
+ "x-advanced": true,
89
+ "description": "The scheme that prefixes the access token in the header. When the token response includes a `token_type`, that value is used instead."
90
+ },
91
+ "tokenRetries": {
92
+ "type": "number",
93
+ "default": 3,
94
+ "x-advanced": true,
95
+ "description": "The number of times to retry fetching the token in the event of a failure."
96
+ },
97
+ "expirationOffsetSeconds": {
98
+ "type": "number",
99
+ "default": 300,
100
+ "x-advanced": true,
101
+ "description": "The number of seconds less than the token expiration to cache the token."
102
+ }
103
+ }
104
+ }
105
+ },
106
+ "examples": [
107
+ {
108
+ "export": "UpstreamOAuthClientCredentialsInboundPolicy",
109
+ "module": "$import(@zuplo/runtime)",
110
+ "options": {
111
+ "audience": "https://api.example.com",
112
+ "clientId": "my-client-id",
113
+ "clientSecret": "$env(OAUTH_CLIENT_SECRET)",
114
+ "credentialsIn": "body",
115
+ "expirationOffsetSeconds": 300,
116
+ "headerName": "Authorization",
117
+ "headerScheme": "Bearer",
118
+ "scope": "read:orders write:orders",
119
+ "tokenRetries": 3,
120
+ "tokenUrl": "https://your-tenant.us.auth0.com/oauth/token"
121
+ }
122
+ }
123
+ ]
124
+ }
125
+ }
126
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zuplo",
3
- "version": "6.73.27",
3
+ "version": "6.73.28",
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.73.27",
23
- "@zuplo/core": "6.73.27",
24
- "@zuplo/runtime": "6.73.27",
22
+ "@zuplo/cli": "6.73.28",
23
+ "@zuplo/core": "6.73.28",
24
+ "@zuplo/runtime": "6.73.28",
25
25
  "@zuplo/test": "1.4.0"
26
26
  }
27
27
  }