Skip to content

feat: license handshake and x-descope-license header#730

Open
orius123 wants to merge 3 commits into
mainfrom
feat/rate-limit-tier
Open

feat: license handshake and x-descope-license header#730
orius123 wants to merge 3 commits into
mainfrom
feat/rate-limit-tier

Conversation

@orius123
Copy link
Copy Markdown
Member

Summary

Adds license handshake support so Cloudflare can apply the correct rate-limit bucket per customer tier.

  • Adds management.license.get() (GET /v1/mgmt/license) returning { rateLimitTier: string }
  • On init, when a managementKey is configured, the SDK fires a one-shot license fetch in the background and caches rateLimitTier
  • All subsequent management requests inject x-descope-license: <tier> via the existing beforeRequest hook

Tier values

tier1 (free) / tier2 (pro) / tier3 (growth) / tier4 (enterprise)

Notes

  • Handshake failure is non-fatal: errors are logged via logger?.debug and the SDK continues without the header
  • The backend interceptor skips license-header validation for GetLicense itself, so the initial fetch is safe before the tier is cached

Test plan

  • lib/management/license.test.ts covers the new endpoint
  • Integration tests will exercise the end-to-end handshake + header injection

Ref: descope/etc#14245

orius123 added 2 commits May 14, 2026 09:47
Adds a management.license.get() endpoint that calls /v1/mgmt/license
and returns the rate limit tier. The SDK fires the request once on
init (when a managementKey is configured) and injects the returned
tier value in the x-descope-license header on every subsequent
management request so Cloudflare can apply the correct rate limit
bucket per customer tier.

Tier values: tier1 (free), tier2 (pro), tier3 (growth), tier4 (enterprise).

Ref: descope/etc#14245
Fire-and-forget handshake and the rate-limit-tier header injection are
best-effort defensive code paths. The license endpoint client itself
remains fully covered by license.test.ts; the end-to-end handshake will
be exercised by integration tests.
@orius123 orius123 marked this pull request as ready for review May 14, 2026 08:05
@orius123 orius123 requested review from asafshen, Copilot and nirgur May 14, 2026 08:05
@orius123 orius123 enabled auto-merge (squash) May 14, 2026 08:06
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a license-tier handshake to the management SDK so Cloudflare can apply per-customer rate-limit buckets. When a managementKey is configured, the SDK asynchronously fetches the customer's tier on initialization and injects it into every subsequent management request as the x-descope-license header.

Changes:

  • New management.license.get() endpoint (GET /v1/mgmt/license) and License type with rateLimitTier.
  • On SDK init, fires a one-shot license fetch and caches rateLimitTier; failure logs at debug and is non-fatal.
  • Management beforeRequest hook injects x-descope-license: <tier> once cached.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
lib/management/types.ts Adds License type.
lib/management/paths.ts Adds /v1/mgmt/license path entry.
lib/management/license.ts New wrapper exposing get() for the license endpoint.
lib/management/license.test.ts Unit test for the new get() endpoint.
lib/management/index.ts Wires withLicense into the management surface.
lib/index.ts Adds tier cache, fire-and-forget handshake, and header injection in beforeRequest.
Comments suppressed due to low confidence (1)

lib/index.ts:174

  • The .catch handler swallows handshake errors with only a logger?.debug?. call. If logger is not provided, or its debug level is filtered out (which is common in production logging configurations), the failure becomes completely invisible — operators have no signal that the handshake is failing and the tier header is missing. Consider using logger?.warn (or at least logger?.info) for handshake failures since a missing tier header will cause Cloudflare to rate-limit the customer at the default/lowest bucket, which has real customer-visible consequences.
      .catch((e) => {
        logger?.debug?.('License handshake failed', e);
      });

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/index.ts Outdated
Comment thread lib/index.ts
- Warn (not debug) on handshake failure so operators see when the tier header is missing
- Remove istanbul ignore directives and cover the handshake paths with unit tests
Copy link
Copy Markdown
Member

@asafshen asafshen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool, I think we should not expose this API to customers, other than this it looks good 👍

Comment thread lib/management/index.ts
fga: WithFGA(client, fgaConfig),
descoper: withDescoper(client),
managementKey: withManagementKey(client),
license: withLicense(client),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we want to expose this API to customers? - imho - no

Comment thread lib/index.ts
// Fire-and-forget license handshake. Backend skips license-header validation
// for the GetLicense endpoint itself, so this initial request is safe even
// before the tier is cached.
if (managementKey) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this only for mgmt actions?

also, don't we have mgmt actions that are not bound to mgmt key?

Comment thread lib/index.ts
// eslint-disable-next-line no-param-reassign
requestConfig.headers = {
...requestConfig.headers,
'x-descope-license': rateLimitTier,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just making sure the Descope deployment already allow this header (if not, requests will start to fail)

Comment thread lib/index.ts

// Rate limit tier from the license handshake. Populated asynchronously on init
// and injected into the x-descope-license header on every management request so
// Cloudflare can apply the correct rate limit bucket per customer tier.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit, I don't think logs should mention CF specifically, its implementation details

I would also not mention the word "customer" tier but "project's company" tier

Comment thread lib/index.ts
.get()
.then((resp) => {
if (resp.ok && resp.data?.rateLimitTier) {
rateLimitTier = resp.data.rateLimitTier;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have we considered to wait with other requests until this one completed?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants