You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(api-gateway): accept a list of API secrets for verification (#10985)
* feat(api-gateway): accept a list of API secrets for verification
Adds `CUBEJS_API_SECRETS` (comma-separated) as a sibling of
`CUBEJS_API_SECRET`. When set, `CUBEJS_API_SECRETS` takes precedence:
the gateway tries each secret in the list when verifying a JWT and
accepts the token if any of them matches the signature. Signing
behavior is unchanged — callers still sign with whatever single
secret they hold.
This enables zero-downtime rotation of the API secret. During a
rotation window operators can publish the outgoing, current, and
incoming secrets together in `CUBEJS_API_SECRETS`; in-flight tokens
signed by the outgoing secret keep verifying until they expire or
are reissued.
- New optional `apiSecrets: string[]` on `ApiGatewayOptions` and
`CreateOptions` (server-core).
- `createDefaultCheckAuth` iterates `apiSecrets` when present;
otherwise falls back to the singular `apiSecret`. An explicit
`options.key` (used by the playground/system auth path) still
wins, so playground behavior is unchanged.
- JWK auth is unaffected — when `jwkUrl` is configured the secret
is fetched dynamically from JWKS, so iterating a static list
would be meaningless.
- `Cube.apiSecrets()` static accessor mirrors `Cube.apiSecret()`.
Tests cover: accept any listed secret, reject unlisted, list takes
precedence over singular, empty list falls back, playground path
isolated.
* refactor(api-gateway): move multi-secret iteration into default checkAuth
Instead of looping over candidate secrets in the gateway's returned auth
middleware, the default checkAuth implementation now tries each configured
secret internally. The middleware reverts to a single checkAuthFn(auth)
call with one try/catch, matching the original structure.
This keeps the secret-rotation logic where it belongs (the default token
verifier) and avoids invoking the verifier wrapper from an outer loop. JWK
auth resolves its key dynamically by kid, so it remains a single-secret
path. Expiry/nbf failures still short-circuit so the real cause isn't
shadowed by a later "invalid signature".
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(api-gateway): read API secrets via env.ts; cover playground+secrets
Addresses code-review feedback:
1. Route env access through `@cubejs-backend/shared` env.ts instead of
reading process.env directly. Adds `apiSecret` and `apiSecrets`
accessors (the latter trims, drops empties and deduplicates), and
switches OptsHandler and CubejsServer.apiSecret()/apiSecrets() to
getEnv(). Removes the now-redundant apiSecretsEnv helper + its test;
parsing/dedup coverage moves to env.test.ts.
2. Adds an api-gateway test for CUBEJS_API_SECRETS coexisting with
playgroundAuthSecret: playground token and any listed secret are
accepted, while the shadowed singular apiSecret and an unknown secret
are rejected.
3. Verified consistency with cube-runtime: CloudApiGateway does not
override createDefaultCheckAuth/createCheckAuthFn (it only overrides
createCheckAuthSystemFn for a playground-secret list, calling
createDefaultCheckAuth({ key }) per secret). The options.key-first
precedence keeps that path single-key, so no override conflict.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
0 commit comments