diff --git a/.prettierignore b/.prettierignore index 9764e56de..829d0eadb 100644 --- a/.prettierignore +++ b/.prettierignore @@ -6,6 +6,7 @@ docs/keto/cli/ docs/kratos/cli/ docs/oathkeeper/cli/ package-lock.json +rate-limits.json .docusaurus/ build/ .next diff --git a/docs/guides/load-performance-testing.mdx b/docs/guides/load-performance-testing.mdx new file mode 100644 index 000000000..8db2a8afe --- /dev/null +++ b/docs/guides/load-performance-testing.mdx @@ -0,0 +1,11 @@ +--- +id: load-performance-testing +title: Load and performance testing +sidebar_label: Load and performance testing +--- + +Load testing, stress testing, and performance testing against Ory Network require prior written approval. Unauthorized load +testing may be detected as abusive traffic and result in temporary blocking of your project or IP addresses. + +For eligibility, request procedures, and requirements, see the +[Load Testing Policy](https://www.ory.sh/legal/load-testing-policy). diff --git a/docs/guides/rate-limit-endpoint.mdx b/docs/guides/rate-limit-endpoint.mdx new file mode 100644 index 000000000..4954a7746 --- /dev/null +++ b/docs/guides/rate-limit-endpoint.mdx @@ -0,0 +1,59 @@ +--- +id: rate-limits-endpoint +title: Endpoint rate limits for Ory Network +sidebar_label: Endpoint rate limits +--- + +Endpoint-based rate limits apply to individual API endpoints regardless of your project rate limits. They protect specific +endpoints against brute-force and credential stuffing attacks, which typically originate from a limited set of IP addresses or JA4 +fingerprints. + +Benefits: + +- Enhanced security—Restricts requests from specific sources, making attacks significantly harder to succeed +- Bot protection—Differentiates genuine users from harmful automated activity +- Granular control—Fine-tunes security for individual endpoints without compromising user experience + +## Types of endpoint-based protection + +Ory implements two types of endpoint-based protection: + +- **Volumetric**: Limits the total amount of traffic over time. +- **Inflight**: Limits the number of concurrent active requests. + +### Volumetric rate limits + +Volumetric rate limits analyze incoming request patterns based on: + +- Source identification—IP addresses and JA3/JA4 fingerprints +- Request frequency—Detects volumetric attacks and system overwhelm attempts +- Authentication status—Different limits for authenticated vs. unauthenticated requests +- HTTP method—Varying limits based on GET, POST, etc. + +### Inflight rate limits + +Inflight rate limits protect critical endpoints from concurrent request attacks. By preventing multiple requests to the same +resource at once, they eliminate race conditions, ensure data consistency, and let critical operations complete safely. + +The following endpoints are protected by rate limits. + +| Type | Endpoint | HTTP Methods | Ratelimit Key | Action: enforced vs report-only | +| :------- | :------------------------------------------ | :----------------------- | :----------------------------------------------- | :------------------------------------- | +| Inflight | `/admin/identities` | `POST`, `PATCH` | `{project_id} + {full_path}` | Blocks concurrent requests (enforced) | +| Inflight | `/admin/identities/{id}` | `PUT`, `PATCH`, `DELETE` | `{project_id} + {full_path}` | Blocks concurrent requests (enforced) | +| Inflight | `/admin/identities/{id}/credentials/{type}` | `DELETE` | `{project_id} + {full_path}` | Blocks concurrent requests (enforced) | +| Inflight | `/admin/identities/{id}/sessions` | `DELETE` | `{project_id} + {full_path}` | Blocks concurrent requests (enforced) | +| Inflight | `/admin/sessions/{id}` | `DELETE` | `{project_id} + {full_path}` | Logs concurrent requests (report-only) | +| Inflight | `/admin/sessions/{id}/extend` | `PATCH` | `{project_id} + {full_path}` | Logs concurrent requests (report-only) | +| Inflight | `/self-service/recovery` | `POST` | `{project_id} + {path} + "/" + {email\|flow_id}` | Logs concurrent requests (report-only) | + +:::note + +Enforced-endpoints return HTTP 429 when the rate limit is exceeded. Report-only-endpoints currently only log rate limit +violations; they don't block requests. GET, OPTIONS, and HEAD requests are exempt from rate limiting. + +::: + +### Configuration and rule management + +The endpoint-based rate limit rules are set and managed by Ory. These rules aren't directly configurable by customers. diff --git a/docs/guides/rate-limits-legacy.mdx b/docs/guides/rate-limits-legacy.mdx new file mode 100644 index 000000000..3418f2c9f --- /dev/null +++ b/docs/guides/rate-limits-legacy.mdx @@ -0,0 +1,333 @@ +--- +id: rate-limits-legacy +title: Ory Network rate limits - legacy +sidebar_label: Rate limits - legacy +--- + +:::info + +This page describes the legacy project rate limit policy, which applies to existing Ory Network customers who haven't been +migrated to the new rate limit policy yet. If you're a new customer or have already been migrated, see the +[rate limits - new](/docs/guides/rate-limits-new). See [Rate limits](/docs/guides/rate-limits) to learn about both policies and +the migration plan. Endpoint rate limits have not changed. + +::: + +This page provides a high-level overview of the rate limiting mechanisms employed by Ory to ensure system security and +availability. Rate limiting protects your applications against abuse and attacks, prevents service disruptions, and ensures fair +usage for all our customers. + +## Types of rate limits + +Ory implements two main rate limit types: + +- **Project rate limits**: Based on your subscription plan and environment (Production, Staging, or Development). These control + the overall request volume your projects can make to Ory's APIs. +- **Endpoint-based rate limits**: Additional security controls that protect specific endpoints against attacks like brute-force, + credential stuffing, and concurrent request abuse, regardless of your project limits. + +## Project rate limits in workspaces + +With the introduction of workspaces in Ory Network, rate limits are now applied to projects based on their assigned environment +and the workspace's subscription plan. This approach ensures fair resource allocation and maintains the stability of the Ory +Network across different usage scenarios. + +### How project rate limits work in workspaces + +Rate limits for each project are determined by two main factors: + +- Workspace subscription—Your subscription plan (Developer, Production, Growth, or Enterprise) sets the baseline for your rate + limits. +- Project environment—Within each workspace, projects can be assigned to Production, Staging, or Development environments, each + with specific rate limit configurations. + +For a detailed explanation of workspaces and environments, see our [Workspaces and environments guide](/docs/guides/workspaces). + +### Rate limit structure + +Each rate limit policy includes two limits: + +- Burst limit—Maximum requests per second (rps), allowing for short traffic spikes. +- Sustained limit—Maximum requests per minute (rpm), ensuring consistent performance over time. + +## Monitor rate limit headers + +Ory Network includes rate limit information in API response headers. Use these headers to avoid exceeding the applicable rate +limit. Your client must handle these responses to maintain service quality. + +| Header | Description | +| ----------------------- | --------------------------------------------------------------------------------------- | +| `x-ratelimit-limit` | The rate limit ceiling(s) for the current request, including burst and sustained limits | +| `x-ratelimit-remaining` | Number of requests remaining in the current window | +| `x-ratelimit-reset` | Number of seconds until the rate limit window resets | + +Example header values: + +```shell +x-ratelimit-limit: 10, 10;w=1, 300;w=60 +x-ratelimit-remaining: 8 +x-ratelimit-reset: 1 +``` + +The `x-ratelimit-limit` header follows the +[IETF RateLimit header fields draft](https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/), where `w=1` +indicates a 1-second window and `w=60` indicates a 60-second window. Use these headers to throttle requests proactively and reduce +the likelihood of hitting 429 errors. + +## How to handle 429 responses + +When your client receives a `429 Too Many Requests` response, you've exceeded the applicable rate limit. Your client must handle +these responses to maintain service quality. + +Your implementation must: + +- **Detect 429 responses**: Monitor for HTTP 429 status codes on all API calls. +- **Implement exponential backoff**: When receiving a 429, pause and retry with increasing delays (for example: 1s, 2s, 4s, 8s). +- **Respect rate limit headers**: Check `x-ratelimit-remaining` and `x-ratelimit-reset`, when available, to throttle requests + proactively. +- **Avoid retry storms**: Don't retry failed requests in a tight loop. + +### Exponential backoff strategy + +When a request returns `429`, back off before retrying. Prefer the server's `x-ratelimit-reset` header when it's present, fall +back to exponential backoff capped at 30 seconds otherwise, and always add jitter so concurrent clients don't retry in lockstep. + +```jsx +async function callApiWithBackoff(request, maxRetries = 5) { + for (let attempt = 0; attempt < maxRetries; attempt++) { + const response = await fetch(request) + if (response.status !== 429) return response + + const resetAfter = response.headers.get("x-ratelimit-reset") + const baseDelay = resetAfter ? parseInt(resetAfter, 10) * 1000 : Math.min(Math.pow(2, attempt) * 1000, 30000) // cap at 30s + + const jitter = Math.random() * 1000 + await new Promise((resolve) => setTimeout(resolve, baseDelay + jitter)) + } + throw new Error("Max retries exceeded") +} +``` + +You can also throttle proactively using `x-ratelimit-remaining` to slow down before hitting a 429: + +```jsx +async function callApiWithThrottle(request) { + const response = await fetch(request) + const remaining = parseInt(response.headers.get("x-ratelimit-remaining"), 10) + const resetIn = parseInt(response.headers.get("x-ratelimit-reset"), 10) + + if (remaining < 5 && resetIn > 0) { + const paceDelay = (resetIn * 1000) / Math.max(remaining, 1) + await new Promise((resolve) => setTimeout(resolve, paceDelay)) + } + return response +} +``` + +Clients that repeatedly exceed rate limits without proper backoff may have their API access temporarily blocked. For high-volume +use cases that exceed your plan's limits, open a support ticket via the [Ory Console](https://console.ory.com/support) or email +[support@ory.com](mailto:support@ory.com). + +### Determine your project's rate limits + +To identify the rate limits that apply to your project: + +1. Check your workspace subscription plan (Developer, Production, Growth, or Enterprise). +2. Identify the environment (Production, Staging, or Development) assigned to your project. +3. Refer to the tables below based on your subscription plan and project environment. + +### Rate limit tables by subscription plan + +#### Developer plan rate limits + +| Environment | Path / Bucket | Burst (rps) | Sustained (rpm) | +| :---------- | :-------------------------------- | ----------: | --------------: | +| Development | `/sessions/whoami` | 10 | 300 | +| | `/admin/oauth2/introspect` | 10 | 300 | +| | `/relation-tuples/check` | 10 | 300 | +| | `GET /admin/identities` | 1 | 10 | +| | `POST /admin/identities` | 1 | 10 | +| | `PATCH /admin/identities` | 1 | 10 | +| | `POST /admin/recovery/*` | 1 | 10 | +| | `POST /self-service/registration` | 1 | 10 | +| | `POST /self-service/recovery` | 1 | 10 | +| | `POST /self-service/settings` | 1 | 10 | +| | `POST /self-service/verification` | 1 | 10 | +| | `/scim/**` | 1 | 10 | +| | `*` | 5 | 150 | + +:::note + +For Developer plans, all environments (Production, Staging, Development) use the same rate limits. + +::: + +#### Production plan rate limits + +| Environment | Path / Bucket | Burst (rps) | Sustained (rpm) | +| :-------------------- | :-------------------------------- | ----------: | --------------: | +| Production | `/sessions/whoami` | 80 | 1800 | +| | `/admin/oauth2/introspect` | 80 | 1800 | +| | `/relation-tuples/check` | 80 | 1800 | +| | `GET /admin/identities` | 10 | 300 | +| | `POST /admin/recovery/*` | 10 | 30 | +| | `/scim/**` | 10 | 300 | +| | `*` | 40 | 900 | +| Development / Staging | `/sessions/whoami` | 10 | 300 | +| | `/admin/oauth2/introspect` | 10 | 300 | +| | `/relation-tuples/check` | 10 | 300 | +| | `GET /admin/identities` | 1 | 10 | +| | `POST /admin/identities` | 1 | 10 | +| | `PATCH /admin/identities` | 1 | 10 | +| | `POST /admin/recovery/*` | 1 | 10 | +| | `POST /self-service/registration` | 1 | 10 | +| | `POST /self-service/recovery` | 1 | 10 | +| | `POST /self-service/settings` | 1 | 10 | +| | `POST /self-service/verification` | 1 | 10 | +| | `/scim/**` | 1 | 10 | +| | `*` | 5 | 150 | + +:::note + +Production plan rate limits also apply to the Legacy `Essential` plan. + +::: + +#### Growth plan rate limits + +| Environment | Path / Bucket | Burst (rps) | Sustained (rpm) | +| :-------------------- | :-------------------------------- | ----------: | --------------: | +| Production | `/sessions/whoami` | 800 | 18000 | +| | `/admin/oauth2/introspect` | 800 | 18000 | +| | `/relation-tuples/check` | 800 | 18000 | +| | `GET /admin/identities` | 20 | 600 | +| | `POST /admin/recovery/*` | 10 | 300 | +| | `/scim/**` | 10 | 300 | +| | `*` | 400 | 9000 | +| Development / Staging | `/sessions/whoami` | 10 | 300 | +| | `/admin/oauth2/introspect` | 10 | 300 | +| | `/relation-tuples/check` | 10 | 300 | +| | `GET /admin/identities` | 1 | 10 | +| | `POST /admin/identities` | 1 | 10 | +| | `PATCH /admin/identities` | 1 | 10 | +| | `POST /admin/recovery/*` | 1 | 10 | +| | `POST /self-service/registration` | 1 | 10 | +| | `POST /self-service/recovery` | 1 | 10 | +| | `POST /self-service/settings` | 1 | 10 | +| | `POST /self-service/verification` | 1 | 10 | +| | `/scim/**` | 1 | 10 | +| | `*` | 5 | 150 | + +:::note + +Growth plan rate limits also apply to the legacy `Scale` plan. + +::: + +#### Enterprise plan rate limits + +The Enterprise plan has the same default rate limits as the Growth plan. If your use case requires higher limits, +[get in touch with us to discuss your requirements](https://ory.com/contact). + +| Environment | Path / Bucket | Burst (rps) | Sustained (rpm) | +| :-------------------- | :-------------------------------- | ----------: | --------------: | +| Production | `/sessions/whoami` | 1200 | 36000 | +| | `/admin/oauth2/introspect` | 1200 | 36000 | +| | `/relation-tuples/check` | 1200 | 36000 | +| | `GET /admin/identities` | 60 | 1200 | +| | `POST /admin/recovery/*` | 20 | 600 | +| | `/scim/**` | 20 | 600 | +| | `*` | 800 | 18000 | +| Development / Staging | `/sessions/whoami` | 10 | 300 | +| | `/admin/oauth2/introspect` | 10 | 300 | +| | `/relation-tuples/check` | 10 | 300 | +| | `GET /admin/identities` | 1 | 10 | +| | `POST /admin/identities` | 1 | 10 | +| | `PATCH /admin/identities` | 1 | 10 | +| | `POST /admin/recovery/*` | 1 | 10 | +| | `POST /self-service/registration` | 1 | 10 | +| | `POST /self-service/recovery` | 1 | 10 | +| | `POST /self-service/settings` | 1 | 10 | +| | `POST /self-service/verification` | 1 | 10 | +| | `/scim/**` | 1 | 10 | +| | `*` | 5 | 150 | + +## Endpoint-based rate limits + +Endpoint-based rate limits are controls applied to individual API endpoints within your Ory projects. Unlike project rate limits, +which govern overall project request volumes, endpoint-based rate limits focus on safeguarding specific functionalities against +abuse. + +:::note + +Endpoint-based rate limits operate independently from project rate limits in workspaces. While project rate limits control overall +request volumes based on your subscription and environment, endpoint-based rate limits provide additional security for specific +endpoints regardless of your project rate limit values. + +::: + +### Purpose of endpoint-based rate limits + +Endpoint-based rate limits protect individual endpoints against common attack vectors like brute-force and credential stuffing. +These attacks typically involve numerous attempts to guess credentials or exploit vulnerabilities, often from a limited set of IP +addresses or JA4 fingerprints. + +Benefits: + +- Enhanced security—Restricts requests from specific sources, making attacks significantly harder to succeed +- Bot protection—Differentiates genuine users from harmful automated activity +- Granular control—Fine-tunes security for individual endpoints without compromising user experience + +### Types of endpoint-based protection + +Ory implements two layers of endpoint-based protection: + +#### Volumetric rate limits + +Analyzes incoming request patterns based on: + +- Source identification—IP addresses and JA3/JA4 fingerprints +- Request frequency—Detects volumetric attacks and system overwhelm attempts +- Authentication status—Different limits for authenticated vs. unauthenticated requests +- HTTP method—Varying limits based on GET, POST, etc. + +#### Inflight rate limits + +Inflight rate limits protect critical endpoints from concurrent request attacks. By preventing multiple requests to the same +resource at once, it eliminates race conditions, ensures data consistency, and lets critical operations complete safely. + +:::note + +These limits mainly protect against write requests to the same resource happening in parallel — usually caused by implementation +issues. + +::: + +### Protected endpoints + +The following endpoints are protected by different types of rate limiting: + +| Type | Endpoint | HTTP Methods | Ratelimit Key | Action | +| :------- | :------------------------------------------ | :----------------------- | :----------------------------------------------- | :------------------------------------- | +| Inflight | `/admin/identities` | `POST`, `PATCH` | `{project_id} + {full_path}` | Blocks concurrent requests (enforced) | +| Inflight | `/admin/identities/{id}` | `PUT`, `PATCH`, `DELETE` | `{project_id} + {full_path}` | Blocks concurrent requests (enforced) | +| Inflight | `/admin/identities/{id}/credentials/{type}` | `DELETE` | `{project_id} + {full_path}` | Blocks concurrent requests (enforced) | +| Inflight | `/admin/identities/{id}/sessions` | `DELETE` | `{project_id} + {full_path}` | Blocks concurrent requests (enforced) | +| Inflight | `/admin/sessions/{id}` | `DELETE` | `{project_id} + {full_path}` | Logs concurrent requests (report-only) | +| Inflight | `/admin/sessions/{id}/extend` | `PATCH` | `{project_id} + {full_path}` | Logs concurrent requests (report-only) | +| Inflight | `/self-service/recovery` | `POST` | `{project_id} + {path} + "/" + {email\|flow_id}` | Logs concurrent requests (report-only) | + +:::note + +Report-only endpoints are observed over a period of time before enforcement is enabled. They currently log rate limit violations +for monitoring purposes but don't block requests, while enforced endpoints return HTTP 429 when rate limits are exceeded. GET, +OPTIONS, and HEAD requests are exempt from rate limiting. + +::: + +### Configuration and management + +#### Rule management + +The endpoint-based rate limit rules are set and managed by Ory. These rules aren't directly configurable by Enterprise and Growth +customers yet. diff --git a/docs/guides/rate-limits-new.mdx b/docs/guides/rate-limits-new.mdx new file mode 100644 index 000000000..01d0f4a48 --- /dev/null +++ b/docs/guides/rate-limits-new.mdx @@ -0,0 +1,111 @@ +--- +id: rate-limits-new +title: Ory Network rate limits - new +sidebar_label: Rate limits - New +--- + +:::info + +There is a new project rate limit policy, which applies to all new Ory Network customers and to existing customers after they've +been migrated. If you're an existing customer and haven't received a migration notice yet, see the +[rate limits - legacy](/docs/guides/rate-limits-legacy). See [Rate limits](/docs/guides/rate-limits) to learn about both policies +and the migration plan. Endpoint-based rate limits have not changed. + +::: + +Ory uses rate limits to protect your applications against abuse, attacks, and service disruptions, and to maintain fair resource +allocation and network stability. + +## Types of rate limits + +Ory uses two types of rate limits: + +- **Project rate limits**: Control the overall request volume your projects can make to Ory APIs, based on your subscription tier + and project environment. See [Project rate limits](./rate-limits-project) for more information. +- **Endpoint-based rate limits**: Control traffic to individual endpoints to protect against volumetric attacks, brute-force + attempts, and concurrent request abuse—regardless of your project rate limits. See + [Endpoint-based rate limits](./rate-limits-endpoint) for more information. + +## Monitor rate limit headers + +Ory Network includes rate limit information in API response headers. Use these headers to avoid exceeding the applicable rate +limit. Your client must handle these responses to maintain service quality. + +| Header | Description | +| ----------------------- | --------------------------------------------------------------------------------------- | +| `x-ratelimit-limit` | The rate limit ceiling(s) for the current request, including burst and sustained limits | +| `x-ratelimit-remaining` | Number of requests remaining in the current window | +| `x-ratelimit-reset` | Number of seconds until the rate limit window resets | + +Example header values: + +```shell +x-ratelimit-limit: 10, 10;w=1, 300;w=60 +x-ratelimit-remaining: 8 +x-ratelimit-reset: 1 +``` + +The `x-ratelimit-limit` header follows the +[IETF RateLimit header fields draft](https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/), where `w=1` +indicates a 1-second window and `w=60` indicates a 60-second window. Use these headers to throttle requests proactively and reduce +the likelihood of hitting 429 errors. + +## How to handle 429 responses + +When your client receives a `429 Too Many Requests` response, you've exceeded the applicable rate limit. Your client must handle +these responses to maintain service quality. + +Your implementation must: + +- **Detect 429 responses**: Monitor for HTTP 429 status codes on all API calls. +- **Back off before retrying**: Prefer the server's `x-ratelimit-reset` header when available; fall back to exponential backoff + capped at 30 seconds. Always add jitter so concurrent clients don't retry in lockstep. +- **Throttle proactively**: Check `x-ratelimit-remaining` and `x-ratelimit-reset` to slow down before you hit a 429. +- **Avoid retry storms**: Don't retry failed requests in a tight loop. + +### Exponential backoff strategy + +When a request returns `429`, back off before retrying. Prefer the server's `x-ratelimit-reset` header when it's present, fall +back to exponential backoff capped at 30 seconds otherwise, and always add jitter so concurrent clients don't retry in lockstep. + +```jsx +async function callApiWithBackoff(request, maxRetries = 5) { + for (let attempt = 0; attempt < maxRetries; attempt++) { + const response = await fetch(request) + if (response.status !== 429) return response + + const resetAfter = response.headers.get("x-ratelimit-reset") + const baseDelay = resetAfter ? parseInt(resetAfter, 10) * 1000 : Math.min(Math.pow(2, attempt) * 1000, 30000) // cap at 30s + + const jitter = Math.random() * 1000 + await new Promise((resolve) => setTimeout(resolve, baseDelay + jitter)) + } + throw new Error("Max retries exceeded") +} +``` + +You can also throttle proactively using `x-ratelimit-remaining` to slow down before hitting a 429: + +```jsx +async function callApiWithThrottle(request) { + const response = await fetch(request) + const remaining = parseInt(response.headers.get("x-ratelimit-remaining"), 10) + const resetIn = parseInt(response.headers.get("x-ratelimit-reset"), 10) + + if (remaining < 5 && resetIn > 0) { + const paceDelay = (resetIn * 1000) / Math.max(remaining, 1) + await new Promise((resolve) => setTimeout(resolve, paceDelay)) + } + return response +} +``` + +Clients that repeatedly exceed rate limits without proper backoff may have their API access temporarily blocked. For high-volume +use cases that exceed your plan's limits, open a support ticket via the [Ory Console](https://console.ory.com/support) or email +[support@ory.com](mailto:support@ory.com). + +## Load testing + +Load testing against the Ory Network requires prior written approval. Unauthorized tests will be detected and may result in +temporary blocking. To request an approved window, open a support ticket via the [Ory Console](https://console.ory.com/support) or +email [support@ory.com](mailto:support@ory.com). diff --git a/docs/guides/rate-limits-project.mdx b/docs/guides/rate-limits-project.mdx new file mode 100644 index 000000000..713170d19 --- /dev/null +++ b/docs/guides/rate-limits-project.mdx @@ -0,0 +1,51 @@ +--- +id: rate-limits-project +title: Project rate limits +sidebar_label: Project rate limits +--- + +import RateLimitsTable from "@site/src/components/RateLimitsTable" + +Each project has a set of rate limit buckets. A bucket is a named group of API endpoints that share the same rate limit threshold. +When a request comes in, Ory resolves which bucket the endpoint belongs to and applies the threshold for that bucket. + +Bucket thresholds are determined by two factors: + +- **Subscription tier**: The project's subscription tier (Developer, Production, Growth, or Enterprise). +- **Project environment**: The project's environment (Production, Staging, or Development). + +For a detailed explanation of tiers and environments, see our [Workspaces and environments guide](/docs/guides/workspaces). + +## Rate limits per bucket + +Buckets follow a `{service}-{access}-{threshold}` naming pattern. For example: + +- `kratos-public-high`: for endpoints with a high rate limit allowance +- `hydra-public-medium`: for endpoints with a moderate rate limit allowance +- `hydra-admin-low`: for endpoints with a low rate limit allowance + +:::info + +A bucket counter is shared across all endpoints in the same bucket. For example, `PUT /admin/relation-tuples` and  +`DELETE /admin/relation-tuples` both belong to `keto-admin-low`, so every call to either endpoint counts against the same limit. +Plan your request volumes accordingly. + +::: + +You will see two rate limits for each bucket: + +- **Burst limit**: Maximum requests per second (rps), allowing for short traffic spikes. +- **Sustained limit**: Maximum requests per minute (rpm), ensuring consistent performance over time. + +## Identify the rate limits that apply to your project + +In the **Project rate limit table** below: + +1. Select your subscription tier from the **Tier** dropdown. Options are Developer, Production, Growth, or Enterprise. +2. Select your project environment from the **Environment** dropdown. Options are Production, Staging, or Development. +3. To search by API path, enter the API path into the **Search API path** box. The endpoint appears highlighted. Look to see which + bucket it belongs to for its rate limit. + +### Project rate limit table + + diff --git a/docs/guides/rate-limits.mdx b/docs/guides/rate-limits.mdx index 03023cc3f..b7cd0fb8c 100644 --- a/docs/guides/rate-limits.mdx +++ b/docs/guides/rate-limits.mdx @@ -1,247 +1,54 @@ --- id: rate-limits -title: Understand Ory Network rate limiting +title: Ory Network rate limiting sidebar_label: Rate limits --- -This page provides a high-level overview of the rate limiting mechanisms employed by Ory to ensure system security and -availability. Rate limiting protects your applications against abuse and attacks, prevents service disruptions, and ensures fair -usage for all our customers. +Ory Network uses rate limits to protect your applications against abuse and ensure fair resource allocation across all customers. +Ory is currently migrating to a new project rate limiting policy. New workspaces are automatically on the new policy; existing +customers are being migrated by subscription tier. -## Types of rate limits +## Which rate limit system applies to you? -Ory implements two main rate limit types: +| You are... | Your system | +| ------------------------------------------------------ | -------------------------------------------- | +| A new customer (workspace created on or after June 15) | [Rate limits - new](./rate-limits-new) | +| An existing customer, migration not yet completed | [Rate limits - legacy](./rate-limits-legacy) | +| An existing customer, migration completed | [Rate limits - new](./rate-limits-new) | -1. Project rate limits: Based on your subscription plan and environment (Production, Staging, or Development). These control the - overall request volume your projects can make to Ory's APIs. -2. Endpoint-based rate limits: Additional security controls that protect specific endpoints against attacks like brute-force, - credential stuffing, and concurrent request abuse, regardless of your project limits. +:::tip -## Project rate limits in workspaces - -With the introduction of workspaces in Ory Network, rate limits are now applied to projects based on their assigned environment -and the workspace's subscription plan. This approach ensures fair resource allocation and maintains the stability of the Ory -Network across different usage scenarios. - -### How project rate limits work in workspaces - -Rate limits for each project are determined by two main factors: - -1. Workspace subscription: Your subscription plan (Developer, Production, Growth, or Enterprise) sets the baseline for your rate - limits. -2. Project environment: Within each workspace, projects can be assigned to Production, Staging, or Development environments, each - with specific rate limit configurations. - -For a detailed explanation of workspaces and environments, see our [Workspaces and environments guide](/docs/guides/workspaces). - -### Rate limit structure - -Each rate limit policy includes two limits: - -1. Burst limit: Maximum requests per second (rps), allowing for short traffic spikes. -2. Sustained limit: Maximum requests per minute (rpm), ensuring consistent performance over time. - -### Determine your project's rate limits - -To identify the rate limits that apply to your project: - -1. Check your workspace subscription plan (Developer, Production, Growth, or Enterprise). -2. Identify the environment (Production, Staging, or Development) assigned to your project. -3. Refer to the tables below based on your subscription plan and project environment. - -### Rate limit tables by subscription plan - -#### Developer plan rate limits - -| Environment | Path / Bucket | Burst (rps) | Sustained (rpm) | -| :---------- | :-------------------------------- | ----------: | --------------: | -| Development | `/sessions/whoami` | 10 | 300 | -| | `/admin/oauth2/introspect` | 10 | 300 | -| | `/relation-tuples/check` | 10 | 300 | -| | `GET /admin/identities` | 1 | 10 | -| | `POST /admin/identities` | 1 | 10 | -| | `PATCH /admin/identities` | 1 | 10 | -| | `POST /admin/recovery/*` | 1 | 10 | -| | `POST /self-service/registration` | 1 | 10 | -| | `POST /self-service/recovery` | 1 | 10 | -| | `POST /self-service/settings` | 1 | 10 | -| | `POST /self-service/verification` | 1 | 10 | -| | `/scim/**` | 1 | 10 | -| | `*` | 5 | 150 | - -:::note - -For Developer plans, all environments (Production, Staging, Development) use the same rate limits. +You can check which policy your workspace is on in the Ory Console under **Settings → Rate Limits**, or by checking your migration +notification email. ::: -#### Production plan rate limits - -| Environment | Path / Bucket | Burst (rps) | Sustained (rpm) | -| :-------------------- | :-------------------------------- | ----------: | --------------: | -| Production | `/sessions/whoami` | 80 | 1800 | -| | `/admin/oauth2/introspect` | 80 | 1800 | -| | `/relation-tuples/check` | 80 | 1800 | -| | `GET /admin/identities` | 10 | 300 | -| | `POST /admin/recovery/*` | 10 | 30 | -| | `/scim/**` | 10 | 300 | -| | `*` | 40 | 900 | -| Development / Staging | `/sessions/whoami` | 10 | 300 | -| | `/admin/oauth2/introspect` | 10 | 300 | -| | `/relation-tuples/check` | 10 | 300 | -| | `GET /admin/identities` | 1 | 10 | -| | `POST /admin/identities` | 1 | 10 | -| | `PATCH /admin/identities` | 1 | 10 | -| | `POST /admin/recovery/*` | 1 | 10 | -| | `POST /self-service/registration` | 1 | 10 | -| | `POST /self-service/recovery` | 1 | 10 | -| | `POST /self-service/settings` | 1 | 10 | -| | `POST /self-service/verification` | 1 | 10 | -| | `/scim/**` | 1 | 10 | -| | `*` | 5 | 150 | - -:::note - -Production plan rate limits also apply to the Legacy `Essential` plan. - -::: - -#### Growth plan rate limits - -| Environment | Path / Bucket | Burst (rps) | Sustained (rpm) | -| :-------------------- | :-------------------------------- | ----------: | --------------: | -| Production | `/sessions/whoami` | 800 | 18000 | -| | `/admin/oauth2/introspect` | 800 | 18000 | -| | `/relation-tuples/check` | 800 | 18000 | -| | `GET /admin/identities` | 20 | 600 | -| | `POST /admin/recovery/*` | 10 | 300 | -| | `/scim/**` | 10 | 300 | -| | `*` | 400 | 9000 | -| Development / Staging | `/sessions/whoami` | 10 | 300 | -| | `/admin/oauth2/introspect` | 10 | 300 | -| | `/relation-tuples/check` | 10 | 300 | -| | `GET /admin/identities` | 1 | 10 | -| | `POST /admin/identities` | 1 | 10 | -| | `PATCH /admin/identities` | 1 | 10 | -| | `POST /admin/recovery/*` | 1 | 10 | -| | `POST /self-service/registration` | 1 | 10 | -| | `POST /self-service/recovery` | 1 | 10 | -| | `POST /self-service/settings` | 1 | 10 | -| | `POST /self-service/verification` | 1 | 10 | -| | `/scim/**` | 1 | 10 | -| | `*` | 5 | 150 | - -:::note - -Growth plan rate limits also apply to the legacy `Scale` plan. - -::: +## What's changing? -#### Enterprise plan rate limits +The legacy rate limit policy applied project rate limits per endpoint-path and limits were fixed per subscription plan and +environment. The new project rate limit policy is a more structured model that distributes project rate limits across different +types of API operations. API operations are now organized into **buckets** based on service, access level, and rate limit +threshold. This allows the Ory platform to handle traffic more efficiently while giving you clearer and more consistent behavior +when interacting with the APIs. -The Enterprise plan has the same default rate limits as the Growth plan. If your use case requires higher limits, -[get in touch with us to discuss your requirements](https://ory.com/contact). +## Migration plan -| Environment | Path / Bucket | Burst (rps) | Sustained (rpm) | -| :-------------------- | :-------------------------------- | ----------: | --------------: | -| Production | `/sessions/whoami` | 1200 | 36000 | -| | `/admin/oauth2/introspect` | 1200 | 36000 | -| | `/relation-tuples/check` | 1200 | 36000 | -| | `GET /admin/identities` | 60 | 1200 | -| | `POST /admin/recovery/*` | 20 | 600 | -| | `/scim/**` | 20 | 600 | -| | `*` | 800 | 18000 | -| Development / Staging | `/sessions/whoami` | 10 | 300 | -| | `/admin/oauth2/introspect` | 10 | 300 | -| | `/relation-tuples/check` | 10 | 300 | -| | `GET /admin/identities` | 1 | 10 | -| | `POST /admin/identities` | 1 | 10 | -| | `PATCH /admin/identities` | 1 | 10 | -| | `POST /admin/recovery/*` | 1 | 10 | -| | `POST /self-service/registration` | 1 | 10 | -| | `POST /self-service/recovery` | 1 | 10 | -| | `POST /self-service/settings` | 1 | 10 | -| | `POST /self-service/verification` | 1 | 10 | -| | `/scim/**` | 1 | 10 | -| | `*` | 5 | 150 | +The new rate limits will be introduced gradually to ensure a smooth transition. No action is required on your end. Ory will notify +you before your workspace is migrated. During the migration, your project rate limit behavior remains unchanged until the cutover +completes. -## Endpoint-based rate limits +Migration schedule: -Endpoint-based rate limits are controls applied to individual API endpoints within your Ory projects. Unlike project rate limits, -which govern overall project request volumes, endpoint-based rate limits focus on safeguarding specific functionalities against -abuse. - -:::note - -Endpoint-based rate limits operate independently from project rate limits in workspaces. While project rate limits control overall -request volumes based on your subscription and environment, endpoint-based rate limits provide additional security for specific -endpoints regardless of your project rate limit values. - -::: - -### Purpose of endpoint-based rate limits - -Endpoint-based rate limits protect individual endpoints against common attack vectors like brute-force and credential stuffing. -These attacks typically involve numerous attempts to guess credentials or exploit vulnerabilities, often from a limited set of IP -addresses or JA4 fingerprints. - -Benefits: - -- Enhanced security: Restricts requests from specific sources, making attacks significantly harder to succeed -- Bot protection: Differentiates genuine users from harmful automated activity -- Granular control: Fine-tunes security for individual endpoints without compromising user experience - -### Types of endpoint-based protection - -Ory implements two layers of endpoint-based protection: - -#### Volumetric rate limits - -Analyzes incoming request patterns based on: - -- Source identification: IP addresses and JA3/JA4 fingerprints -- Request frequency: Detects volumetric attacks and system overwhelm attempts -- Authentication status: Different limits for authenticated vs. unauthenticated requests -- HTTP method: Varying limits based on GET, POST, etc. - -#### Inflight rate limits - -Inflight rate limits protect critical endpoints from concurrent request attacks. By preventing multiple requests to the same -resource at once, it eliminates race conditions, ensures data consistency, and lets critical operations complete safely. - -:::note - -These limits mainly protect against write requests to the same resource happening in parallel — usually caused by implementation -issues. - -::: - -### Protected endpoints - -The following endpoints are protected by different types of rate limiting: - -| Type | Endpoint | HTTP Methods | Ratelimit Key | Action | -| :--------- | :------------------------------------------ | :----------------------- | :----------------------------------------------- | :------------------------------------- | -| Volumetric | | | | To be added later | -| Inflight | `/admin/identities` | `POST`, `PATCH` | `{project_id} + {full_path}` | Blocks concurrent requests (enforced) | -| Inflight | `/admin/identities/{id}` | `PUT`, `PATCH`, `DELETE` | `{project_id} + {full_path}` | Blocks concurrent requests (enforced) | -| Inflight | `/admin/identities/{id}/credentials/{type}` | `DELETE` | `{project_id} + {full_path}` | Blocks concurrent requests (enforced) | -| Inflight | `/admin/identities/{id}/sessions` | `DELETE` | `{project_id} + {full_path}` | Blocks concurrent requests (enforced) | -| Inflight | `/admin/sessions/{id}` | `DELETE` | `{project_id} + {full_path}` | Logs concurrent requests (report-only) | -| Inflight | `/admin/sessions/{id}/extend` | `PATCH` | `{project_id} + {full_path}` | Logs concurrent requests (report-only) | -| Inflight | `/self-service/recovery` | `POST` | `{project_id} + {path} + "/" + {email\|flow_id}` | Logs concurrent requests (report-only) | - -:::note - -Report-only endpoints are observed over a period of time before enforcement is enabled. They currently log rate limit violations -for monitoring purposes but don't block requests, while enforced endpoints return HTTP 429 when rate limits are exceeded. GET, -OPTIONS, and HEAD requests are exempt from rate limiting. - -::: +| Phase | Plan | Date | +| ----- | ------------------------------------------------ | --------------- | +| 1 | New workspaces and existing Developer workspaces | Week of June 15 | +| 2 | Existing Production workspaces | Week of June 22 | +| 3 | Existing Growth workspaces | Week of June 29 | +| 4 | Existing Enterprise workspaces | Week of July 6 | -### Configuration and management +The types of rate limits, for project-based and endpoint-based, remain the same in both rate limit policies. -#### Rule management +## Learn more about rate limits -The endpoint-based rate limit rules are set and managed by Ory. These rules aren't directly configurable by Enterprise and Growth -customers yet. +- [Rate limits - new](./rate-limits-new)—applies to new customers and migrated workspaces +- [Rate limits - legacy](./rate-limits-legacy)—applies to existing customers pending migration diff --git a/docusaurus.config.ts b/docusaurus.config.ts index 7de6b09f5..8bc550ab8 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -168,6 +168,7 @@ const config: Config = { "@docusaurus/plugin-content-pages", require.resolve("./src/plugins/docusaurus-polyfill"), + require.resolve("./src/plugins/docusaurus-rate-limits-data/index.ts"), // require.resolve("./src/plugins/docusaurus-static-fonts"), [ "@docusaurus/plugin-sitemap", diff --git a/package-lock.json b/package-lock.json index 0565a500b..182556521 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@docusaurus/plugin-content-docs": "3.10.0", "@docusaurus/plugin-content-pages": "3.10.0", "@docusaurus/plugin-sitemap": "3.10.0", + "@docusaurus/plugin-svgr": "^3.9.2", "@docusaurus/preset-classic": "3.10.0", "@docusaurus/theme-classic": "3.10.0", "@docusaurus/theme-search-algolia": "3.10.0", @@ -27,7 +28,7 @@ "@tanstack/react-query": "^5.64.2", "@vercel/speed-insights": "2.0.0", "@xyflow/react": "^12.10.1", - "axios": "^1.13.2", + "axios": "^1.16.0", "buffer": "^6.0.3", "classnames": "2.5.1", "clsx": "2.1.0", @@ -8491,12 +8492,12 @@ } }, "node_modules/axios": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.0.tgz", - "integrity": "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz", + "integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.11", + "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "proxy-from-env": "^2.1.0" } diff --git a/package.json b/package.json index 88d936a7d..edd4dac1d 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "@docusaurus/plugin-content-docs": "3.10.0", "@docusaurus/plugin-content-pages": "3.10.0", "@docusaurus/plugin-sitemap": "3.10.0", + "@docusaurus/plugin-svgr": "^3.9.2", "@docusaurus/preset-classic": "3.10.0", "@docusaurus/theme-classic": "3.10.0", "@docusaurus/theme-search-algolia": "3.10.0", @@ -50,7 +51,7 @@ "@tanstack/react-query": "^5.64.2", "@vercel/speed-insights": "2.0.0", "@xyflow/react": "^12.10.1", - "axios": "^1.13.2", + "axios": "^1.16.0", "buffer": "^6.0.3", "classnames": "2.5.1", "clsx": "2.1.0", diff --git a/sidebars-network.ts b/sidebars-network.ts index 50725c3b7..f4aeb95e0 100644 --- a/sidebars-network.ts +++ b/sidebars-network.ts @@ -201,6 +201,7 @@ const networkSidebar = [ "kratos/passwordless/one-time-code", "kratos/passwordless/passkeys", "kratos/passwordless/passkeys-mobile", + "kratos/passwordless/deviceauthn", "kratos/organizations/organizations", "kratos/emails-sms/custom-email-templates", ], diff --git a/sidebars.ts b/sidebars.ts index 2b4bb3f07..34e4a858a 100644 --- a/sidebars.ts +++ b/sidebars.ts @@ -73,7 +73,30 @@ const api: SidebarItemsConfig = [ "concepts/personal-access-token", "guides/cors", "guides/api-rest-pagination", - "guides/rate-limits", + { + type: "category", + label: "Rate limits", + link: { + type: "doc", + id: "guides/rate-limits", + }, + items: [ + "guides/rate-limits-legacy", + { + type: "category", + label: "Rate limits - New", + link: { + type: "doc", + id: "guides/rate-limits-new", + }, + items: [ + "guides/rate-limits-project", + "guides/rate-limits-endpoint", + ], + }, + ], + }, + "guides/load-performance-testing", "guides/ip-allowlist", "api/eventual-consistency", "kratos/reference/jsonnet", diff --git a/src/components/RateLimitsTable/index.tsx b/src/components/RateLimitsTable/index.tsx new file mode 100644 index 000000000..1eadfca6c --- /dev/null +++ b/src/components/RateLimitsTable/index.tsx @@ -0,0 +1,261 @@ +// Copyright © 2022 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +import useBaseUrl from "@docusaurus/useBaseUrl" +import React, { useMemo, useState, useEffect } from "react" +import type { Env, RateLimitsData, Tier } from "./types" + +const TIERS: Tier[] = ["Developer", "Production", "Growth", "Enterprise"] +const ENVS: Env[] = ["Development", "Staging", "Production"] +const SEARCH_DEBOUNCE_MS = 250 +const HIDDEN_METHODS = ["OPTIONS", "HEAD"] + +function useRateLimitsData(): { + data: RateLimitsData | null + loading: boolean + error: string | null +} { + const baseUrl = useBaseUrl("rate-limits.json") + const [data, setData] = useState(null) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + React.useEffect(() => { + setLoading(true) + setError(null) + fetch(baseUrl) + .then((res) => { + if (!res.ok) + throw new Error(`Failed to load rate limits: ${res.status}`) + return res.json() + }) + .then(setData) + .catch((e) => setError(e instanceof Error ? e.message : String(e))) + .finally(() => setLoading(false)) + }, [baseUrl]) + + return { data, loading, error } +} + +export interface RateLimitsTableProps { + /** Pre-select subscription tier (e.g. on tier subpages). */ + initialTier?: Tier + /** Pre-select project environment. */ + initialEnv?: Env +} + +export default function RateLimitsTable({ + initialTier = "Growth", + initialEnv = "Production", +}: RateLimitsTableProps): React.ReactElement { + const { data, loading, error } = useRateLimitsData() + const [tier, setTier] = useState(initialTier) + const [env, setEnv] = useState(initialEnv) + const [pathSearch, setPathSearch] = useState("") + const [pathSearchDebounced, setPathSearchDebounced] = useState("") + + useEffect(() => { + const t = setTimeout( + () => setPathSearchDebounced(pathSearch), + SEARCH_DEBOUNCE_MS, + ) + return () => clearTimeout(t) + }, [pathSearch]) + + React.useEffect(() => { + setTier(initialTier) + setEnv(initialEnv) + }, [initialTier, initialEnv]) + + const filteredThresholds = useMemo(() => { + if (!data) return [] + return data.thresholds.filter((t) => t.tier === tier && t.env === env) + }, [data, tier, env]) + + const bucketToEndpoints = useMemo(() => { + if (!data) return new Map>() + const m = new Map>() + for (const e of data.endpoints.filter( + (e) => !HIDDEN_METHODS.includes(e.method), + )) { + const list = m.get(e.bucket) ?? [] + list.push({ method: e.method, path: e.path }) + m.set(e.bucket, list) + } + return m + }, [data]) + + const searchQuery = pathSearchDebounced.trim().toLowerCase() + + const endpointMatchesSearch = useMemo(() => { + if (!searchQuery) return () => false + return (e: { method: string; path: string }) => + e.path.toLowerCase().includes(searchQuery) || + e.method.toLowerCase().includes(searchQuery) || + `${e.method} ${e.path}`.toLowerCase().includes(searchQuery) + }, [searchQuery]) + + const bucketsWithSearchMatch = useMemo(() => { + if (!searchQuery || !data) return null + const set = new Set() + for (const e of data.endpoints) { + if (endpointMatchesSearch(e)) set.add(e.bucket) + } + return set + }, [data, searchQuery, endpointMatchesSearch]) + + const displayedThresholds = useMemo(() => { + if (!bucketsWithSearchMatch) return filteredThresholds + return filteredThresholds.filter((t) => + bucketsWithSearchMatch.has(t.bucket), + ) + }, [filteredThresholds, bucketsWithSearchMatch]) + + if (loading) { + return ( +

+ Loading rate limits data… +

+ ) + } + if (error) { + return ( +

+ Error loading rate limits: {error} +

+ ) + } + if (!data) { + return ( +

+ No rate limits data available. +

+ ) + } + + return ( +
+
+ + + +
+ +
+

+ Thresholds per bucket ({tier} / {env}) + {searchQuery && ( + + (filtered by search) + + )} +

+
+ + + + + + + + + + + {displayedThresholds.map((t) => { + const endpoints = bucketToEndpoints.get(t.bucket) ?? [] + const endpointDisplay = endpoints.length + ? endpoints.map((e) => { + const isMatch = endpointMatchesSearch(e) + return ( +
+ {e.path}{" "} + + ({e.method}) + +
+ ) + }) + : null + return ( + + + + + + + ) + })} + +
+ Endpoint(s) + + Bucket + + Sustained (rpm) + + Burst (rps) +
+ {endpointDisplay} + + {t.bucket} + + {t.rpm} + + {t.rps} +
+
+ {searchQuery && displayedThresholds.length === 0 && ( +

+ No buckets match the search "{pathSearchDebounced.trim()} + ". +

+ )} +
+
+ ) +} diff --git a/src/components/RateLimitsTable/types.ts b/src/components/RateLimitsTable/types.ts new file mode 100644 index 000000000..56236727b --- /dev/null +++ b/src/components/RateLimitsTable/types.ts @@ -0,0 +1,24 @@ +// Copyright © 2022 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +export interface EndpointRow { + method: string + path: string + bucket: string +} + +export interface ThresholdRow { + bucket: string + tier: string + env: string + rpm: number + rps: number +} + +export interface RateLimitsData { + endpoints: EndpointRow[] + thresholds: ThresholdRow[] +} + +export type Tier = "Developer" | "Production" | "Growth" | "Enterprise" +export type Env = "Development" | "Staging" | "Production" diff --git a/src/lib/rate-limits/csv-provider.ts b/src/lib/rate-limits/csv-provider.ts new file mode 100644 index 000000000..21c6db772 --- /dev/null +++ b/src/lib/rate-limits/csv-provider.ts @@ -0,0 +1,117 @@ +// Copyright © 2022 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +import * as fs from "fs" +import * as path from "path" +import type { + EndpointRow, + Env, + GetThresholdsOptions, + RateLimitsProvider, + ThresholdRow, + Tier, +} from "./types" +import { ENV_FROM_CSV, TIER_FROM_CSV } from "./types" + +const ENDPOINTS_CSV = "bucket-to-endpoints.csv" +const THRESHOLDS_CSV = "bucket-to-threshold.csv" + +/** + * Resolve data directory. When running from Docusaurus plugin, pass siteDir so paths resolve correctly. + */ +function getDataDir(siteDir?: string): string { + if (siteDir) { + return path.join(siteDir, "src", "lib", "rate-limits", "data") + } + return path.join(__dirname, "data") +} + +function parseCsv(content: string): string[][] { + const lines = content.trim().split(/\r?\n/) + return lines.map((line) => line.split(",").map((cell) => cell.trim())) +} + +/** + * For duplicate (bucket, tier, env) we keep the first row (CSV order). + */ +function dedupeThresholds(rows: ThresholdRow[]): ThresholdRow[] { + const key = (r: ThresholdRow) => `${r.bucket}|${r.tier}|${r.env}` + const seen = new Set() + return rows.filter((r) => { + const k = key(r) + if (seen.has(k)) return false + seen.add(k) + return true + }) +} + +export interface CsvProviderOptions { + /** Site directory (e.g. from Docusaurus plugin context) so CSV paths resolve correctly. */ + siteDir?: string +} + +export function createCsvProvider( + options?: CsvProviderOptions, +): RateLimitsProvider { + const dataDir = getDataDir(options?.siteDir) + const endpointsPath = path.join(dataDir, ENDPOINTS_CSV) + const thresholdsPath = path.join(dataDir, THRESHOLDS_CSV) + + return { + async getEndpointsByBucket(): Promise { + const raw = fs.readFileSync(endpointsPath, "utf-8") + const rows = parseCsv(raw) + const [header, ...dataRows] = rows + if ( + !header || + header[0] !== "Method" || + header[1] !== "Path" || + header[2] !== "Bucket" + ) { + throw new Error(`Unexpected endpoints CSV header: ${header?.join(",")}`) + } + return dataRows + .filter((r) => r.length >= 3 && r[0] && r[1] && r[2]) + .map((r) => ({ method: r[0], path: r[1], bucket: r[2] })) + }, + + async getThresholds( + options?: GetThresholdsOptions, + ): Promise { + const raw = fs.readFileSync(thresholdsPath, "utf-8") + const rows = parseCsv(raw) + const [header, ...dataRows] = rows + if ( + !header || + header[0] !== "bucketname" || + header[1] !== "tier" || + header[2] !== "env" || + header[3] !== "rpm" || + header[4] !== "rps" + ) { + throw new Error( + `Unexpected thresholds CSV header: ${header?.join(",")}`, + ) + } + let result: ThresholdRow[] = dataRows + .filter((r) => r.length >= 5 && r[0] && r[1] && r[2]) + .map((r) => { + const tier = TIER_FROM_CSV[r[1]] ?? (r[1] as Tier) + const env = ENV_FROM_CSV[r[2]] ?? (r[2] as Env) + return { + bucket: r[0], + tier, + env, + rpm: parseInt(r[3], 10) || 0, + rps: parseInt(r[4], 10) || 0, + } + }) + result = dedupeThresholds(result) + if (options?.tier) result = result.filter((r) => r.tier === options.tier) + if (options?.env) result = result.filter((r) => r.env === options.env) + if (options?.bucket) + result = result.filter((r) => r.bucket === options.bucket) + return result + }, + } +} diff --git a/src/lib/rate-limits/data/bucket-to-endpoints.csv b/src/lib/rate-limits/data/bucket-to-endpoints.csv new file mode 100644 index 000000000..12364d9cd --- /dev/null +++ b/src/lib/rate-limits/data/bucket-to-endpoints.csv @@ -0,0 +1,218 @@ +Method,Path,Bucket +GET,/.well-known/openid-configuration,hydra-public-high +HEAD,/.well-known/openid-configuration,hydra-public-high +GET,/.well-known/jwks.json,hydra-public-high +GET,/.well-known/ory/webauthn.js,hydra-public-high +HEAD,/oauth2/auth,hydra-public-high +OPTIONS,/oauth2/auth,hydra-public-high +GET,/oauth2/consent,hydra-public-high +POST,/oauth2/device/auth,hydra-public-high +GET,/oauth2/fallbacks/logout/callback,hydra-public-high +OPTIONS,/oauth2/token,hydra-public-high +GET,/admin/clients,hydra-admin-medium +GET,/admin/trust/grants/jwt-bearer/issuers,hydra-admin-medium +POST,/credentials,hydra-admin-medium +GET,/admin/keys/{set},hydra-admin-medium +GET,/admin/keys/{set}/{kid},hydra-admin-medium +GET,/admin/trust/grants/jwt-bearer/issuers/{id},hydra-admin-medium +POST,/admin/clients,hydra-admin-low +PUT,/admin/clients/{id}/lifespans,hydra-admin-low +DELETE,/admin/oauth2/auth/sessions/consent,hydra-admin-low +DELETE,/admin/oauth2/auth/sessions/login,hydra-admin-low +DELETE,/admin/oauth2/tokens,hydra-admin-low +POST,/admin/trust/grants/jwt-bearer/issuers,hydra-admin-low +PUT,/admin/clients/{id},hydra-admin-low +PATCH,/admin/clients/{id},hydra-admin-low +DELETE,/admin/clients/{id},hydra-admin-low +POST,/admin/keys/{set},hydra-admin-low +PUT,/admin/keys/{set},hydra-admin-low +DELETE,/admin/keys/{set},hydra-admin-low +PUT,/admin/keys/{set}/{kid},hydra-admin-low +DELETE,/admin/keys/{set}/{kid},hydra-admin-low +DELETE,/admin/trust/grants/jwt-bearer/issuers/{id},hydra-admin-low +OPTIONS,/admin/clients,hydra-admin-high +GET,/admin/oauth2/auth/requests/consent,hydra-admin-high +PUT,/admin/oauth2/auth/requests/consent/accept,hydra-admin-high +PUT,/admin/oauth2/auth/requests/consent/reject,hydra-admin-high +PUT,/admin/oauth2/auth/requests/device/accept,hydra-admin-high +GET,/admin/oauth2/auth/requests/login,hydra-admin-high +PUT,/admin/oauth2/auth/requests/login/accept,hydra-admin-high +PUT,/admin/oauth2/auth/requests/login/reject,hydra-admin-high +GET,/admin/oauth2/auth/requests/logout,hydra-admin-high +PUT,/admin/oauth2/auth/requests/logout/accept,hydra-admin-high +PUT,/admin/oauth2/auth/requests/logout/reject,hydra-admin-high +GET,/admin/oauth2/auth/sessions/consent,hydra-admin-high +POST,/admin/oauth2/introspect,hydra-admin-high +GET,/oauth2/device/verify,hydra-admin-high +GET,/admin/clients/{id},hydra-admin-high +OPTIONS,/admin/clients/{id},hydra-admin-high +GET,/oauth2/register/{id},hydra-admin-high +GET,/oauth2/auth,hydra-public-medium +POST,/oauth2/auth,hydra-public-medium +POST,/oauth2/revoke,hydra-public-medium +GET,/oauth2/sessions/logout,hydra-public-medium +POST,/oauth2/sessions/logout,hydra-public-medium +POST,/oauth2/token,hydra-public-medium +GET,/userinfo,hydra-public-medium +POST,/oauth2/register,hydra-public-low +PUT,/oauth2/register/{id},hydra-public-low +DELETE,/oauth2/register/{id},hydra-public-low +PUT,/admin/relation-tuples,keto-admin-low +PATCH,/admin/relation-tuples,keto-admin-low +DELETE,/admin/relation-tuples,keto-admin-low +GET,/namespaces,keto-admin-low +POST,/ory.keto.relation_tuples.v1alpha2.WriteService/TransactRelationTuples,keto-admin-low +POST,/opl/syntax/check,keto-admin-medium +GET,/relation-tuples,keto-admin-medium +GET,/relation-tuples/expand,keto-admin-medium +POST,/ory.keto.relation_tuples.v1alpha2.CheckService/BatchCheck,keto-public-high +POST,/ory.keto.relation_tuples.v1alpha2.CheckService/Check,keto-public-high +POST,/relation-tuples/batch/check,keto-public-high +GET,/relation-tuples/check,keto-public-high +POST,/relation-tuples/check,keto-public-high +GET,/relation-tuples/check/openapi,keto-public-high +POST,/relation-tuples/check/openapi,keto-public-high +GET,/admin/courier/messages,kratos-admin-low +POST,/admin/identities,kratos-admin-low +PATCH,/admin/identities,kratos-admin-low +DELETE,/admin/identities/{id}/sessions,kratos-admin-low +POST,/admin/recovery/code,kratos-admin-low +POST,/admin/recovery/link,kratos-admin-low +PATCH,/admin/sessions/{id}/extend,kratos-admin-low +POST,/scim/{name}/v2/Groups,kratos-admin-low +POST,/scim/{name}/v2/Users,kratos-admin-low +PUT,/admin/identities/{id},kratos-admin-low +PATCH,/admin/identities/{id},kratos-admin-low +DELETE,/admin/identities/{id},kratos-admin-low +DELETE,/admin/identities/{id}/credentials/{type},kratos-admin-low +DELETE,/admin/sessions/{id},kratos-admin-low +OPTIONS,/admin/sessions,kratos-admin-low +POST,/admin/sessions,kratos-admin-low +PUT,/scim/{name}/v2/Groups/{id},kratos-admin-low +PATCH,/scim/{name}/v2/Groups/{id},kratos-admin-low +DELETE,/scim/{name}/v2/Groups/{id},kratos-admin-low +PUT,/scim/{name}/v2/Users/{id},kratos-admin-low +PATCH,/scim/{name}/v2/Users/{id},kratos-admin-low +DELETE,/scim/{name}/v2/Users/{id},kratos-admin-low +GET,/admin/identities,kratos-admin-medium +GET,/admin/identities/{id}/sessions,kratos-admin-medium +GET,/admin/sessions,kratos-admin-medium +GET,/schemas,kratos-admin-medium +GET,/scim/{name}/v2/Groups,kratos-admin-medium +GET,/scim/{name}/v2/Schemas,kratos-admin-medium +GET,/scim/{name}/v2/ServiceProviderConfig,kratos-admin-medium +GET,/scim/{name}/v2/Users,kratos-admin-medium +GET,/admin/courier/messages/{id},kratos-admin-medium +GET,/admin/identities/by/external/{externalID},kratos-admin-medium +GET,/schemas/{id},kratos-admin-medium +GET,/scim/{name}/v2/Groups/{id},kratos-admin-medium +GET,/scim/{name}/v2/Schemas/{id},kratos-admin-medium +GET,/scim/{name}/v2/Users/{id},kratos-admin-medium +GET,/self-service/errors,kratos-public-high +GET,/self-service/fed-cm/parameters,kratos-public-high +HEAD,/self-service/fed-cm/parameters,kratos-public-high +OPTIONS,/self-service/fed-cm/token,kratos-public-high +GET,/self-service/login,kratos-public-high +OPTIONS,/self-service/login,kratos-public-high +HEAD,/self-service/login/browser,kratos-public-high +OPTIONS,/self-service/login/browser,kratos-public-high +GET,/self-service/login/flows,kratos-public-high +OPTIONS,/self-service/login/flows,kratos-public-high +GET,/self-service/logout,kratos-public-high +OPTIONS,/self-service/logout,kratos-public-high +OPTIONS,/self-service/logout/browser,kratos-public-high +GET,/self-service/methods/oidc/callback,kratos-public-high +GET,/self-service/recovery,kratos-public-high +HEAD,/self-service/recovery,kratos-public-high +OPTIONS,/self-service/recovery,kratos-public-high +HEAD,/self-service/recovery/browser,kratos-public-high +OPTIONS,/self-service/recovery/browser,kratos-public-high +GET,/self-service/recovery/flows,kratos-public-high +OPTIONS,/self-service/recovery/flows,kratos-public-high +GET,/self-service/registration,kratos-public-high +OPTIONS,/self-service/registration,kratos-public-high +HEAD,/self-service/registration/browser,kratos-public-high +OPTIONS,/self-service/registration/browser,kratos-public-high +GET,/self-service/registration/flows,kratos-public-high +GET,/self-service/settings,kratos-public-high +OPTIONS,/self-service/settings,kratos-public-high +GET,/self-service/settings/flows,kratos-public-high +GET,/self-service/verification,kratos-public-high +HEAD,/self-service/verification,kratos-public-high +OPTIONS,/self-service/verification,kratos-public-high +HEAD,/self-service/verification/browser,kratos-public-high +GET,/self-service/verification/flows,kratos-public-high +OPTIONS,/self-service/verification/flows,kratos-public-high +OPTIONS,/sessions,kratos-public-high +GET,/sessions/whoami,kratos-public-high +HEAD,/self-service/methods/oidc/callback/{id},kratos-public-high +HEAD,/self-service/methods/oidc/callback/{name},kratos-public-high +POST,/self-service/fed-cm/token,kratos-public-low +POST,/self-service/login,kratos-public-low +POST,/self-service/recovery,kratos-public-low +POST,/self-service/registration,kratos-public-low +POST,/self-service/settings,kratos-public-low +POST,/self-service/verification,kratos-public-low +DELETE,/sessions,kratos-public-low +DELETE,/sessions/{id},kratos-public-low +GET,/self-service/login/api,kratos-public-medium +GET,/self-service/login/browser,kratos-public-medium +POST,/self-service/login/browser,kratos-public-medium +DELETE,/self-service/logout/api,kratos-public-medium +GET,/self-service/logout/browser,kratos-public-medium +GET,/self-service/recovery/api,kratos-public-medium +GET,/self-service/recovery/browser,kratos-public-medium +GET,/self-service/registration/api,kratos-public-medium +GET,/self-service/registration/browser,kratos-public-medium +GET,/self-service/settings/api,kratos-public-medium +GET,/self-service/settings/browser,kratos-public-medium +GET,/self-service/verification/api,kratos-public-medium +GET,/self-service/verification/browser,kratos-public-medium +GET,/sessions,kratos-public-medium +GET,/sessions/token-exchange,kratos-public-medium +GET,/self-service/methods/oidc/callback/{id},kratos-public-medium +GET,/self-service/methods/oidc/callback/{name},kratos-public-medium +GET,/self-service/methods/oidc/organizations/{id},kratos-public-medium +GET,/self-service/methods/saml/callback/{name},kratos-public-medium +GET,/self-service/methods/saml/organizations/{id},kratos-public-medium +GET,/admin/identities/{id},kratos-admin-high +OPTIONS,/admin/identities/{id},kratos-admin-high +GET,/admin/sessions/{id},kratos-admin-high +GET,/api/v1/dsync,polis-public-high +GET,/api/v1/dsync/events,polis-public-high +GET,/api/v1/dsync/groups,polis-public-high +GET,/api/v1/dsync/product,polis-public-high +GET,/api/v1/dsync/setuplinks,polis-public-high +GET,/api/v1/dsync/setuplinks/product,polis-public-high +GET,/api/v1/dsync/users,polis-public-high +GET,/api/v1/identity-federation,polis-public-high +GET,/api/v1/identity-federation/product,polis-public-high +GET,/api/v1/sso,polis-public-high +GET,/api/v1/sso/product,polis-public-high +GET,/api/v1/sso-traces,polis-public-high +GET,/api/v1/sso-traces/product,polis-public-high +GET,/oauth/userinfo,polis-public-high +GET,/saml/.well-known/idp-metadata,polis-public-high +GET,/saml/.well-known/sp-metadata,polis-public-high +GET,/saml/.well-known/saml.cer,polis-public-high +GET,/saml/api/error,polis-public-high +GET,/api/v1/dsync/groups/{groupId},polis-public-high +GET,/api/v1/dsync/groups/{groupId}/members,polis-public-high +GET,/api/v1/dsync/users/{userId},polis-public-high +DELETE,/api/v1/dsync/setuplinks,polis-public-medium +DELETE,/api/v1/identity-federation,polis-public-medium +DELETE,/api/v1/sso,polis-public-medium +PATCH,/api/v1/identity-federation,polis-public-medium +PATCH,/api/v1/sso,polis-public-medium +POST,/api/v1/dsync,polis-public-medium +POST,/api/v1/dsync/setuplinks,polis-public-medium +POST,/api/v1/identity-federation,polis-public-medium +POST,/api/v1/sso,polis-public-medium +POST,/oauth/token,polis-public-medium +GET,/saml/api/identity-federation/sso,polis-public-medium +POST,/saml/api/identity-federation/sso,polis-public-medium +GET,/saml/api/oauth/authorize,polis-public-medium +POST,/saml/api/oauth/authorize,polis-public-medium +GET,/saml/api/oauth/oidc,polis-public-medium +POST,/saml/api/oauth/saml,polis-public-medium +PATCH,/api/v1/dsync/{directoryId},polis-public-medium diff --git a/src/lib/rate-limits/data/bucket-to-threshold.csv b/src/lib/rate-limits/data/bucket-to-threshold.csv new file mode 100644 index 000000000..9b7bef10f --- /dev/null +++ b/src/lib/rate-limits/data/bucket-to-threshold.csv @@ -0,0 +1,205 @@ +bucketname,tier,env,rpm,rps +hydra-admin-high,Develop,dev,80,5 +hydra-admin-high,Develop,prod,80,5 +hydra-admin-high,Develop,stage,80,5 +hydra-admin-high,Enterprise,dev,175,8 +hydra-admin-high,Enterprise,prod,8750,351 +hydra-admin-high,Enterprise,stage,175,8 +hydra-admin-high,Growth,dev,175,8 +hydra-admin-high,Growth,prod,4300,173 +hydra-admin-high,Growth,stage,175,8 +hydra-admin-high,Production,dev,175,8 +hydra-admin-high,Production,prod,175,8 +hydra-admin-high,Production,stage,175,8 +hydra-admin-low,Develop,dev,15,5 +hydra-admin-low,Develop,prod,15,5 +hydra-admin-low,Develop,stage,15,5 +hydra-admin-low,Enterprise,dev,30,5 +hydra-admin-low,Enterprise,prod,700,29 +hydra-admin-low,Enterprise,stage,30,5 +hydra-admin-low,Growth,dev,30,5 +hydra-admin-low,Growth,prod,350,15 +hydra-admin-low,Growth,stage,30,5 +hydra-admin-low,Production,dev,30,5 +hydra-admin-low,Production,prod,35,5 +hydra-admin-low,Production,stage,30,5 +hydra-admin-medium,Develop,dev,15,5 +hydra-admin-medium,Develop,prod,15,5 +hydra-admin-medium,Develop,stage,15,5 +hydra-admin-medium,Enterprise,dev,30,5 +hydra-admin-medium,Enterprise,prod,125,6 +hydra-admin-medium,Enterprise,stage,30,5 +hydra-admin-medium,Growth,dev,30,5 +hydra-admin-medium,Growth,prod,60,5 +hydra-admin-medium,Growth,stage,30,5 +hydra-admin-medium,Production,dev,30,5 +hydra-admin-medium,Production,prod,30,5 +hydra-admin-medium,Production,stage,30,5 +hydra-public-high,Develop,dev,60,5 +hydra-public-high,Develop,prod,60,5 +hydra-public-high,Develop,stage,60,5 +hydra-public-high,Enterprise,dev,125,6 +hydra-public-high,Enterprise,prod,1800,73 +hydra-public-high,Enterprise,stage,125,6 +hydra-public-high,Growth,dev,125,6 +hydra-public-high,Growth,prod,600,25 +hydra-public-high,Growth,stage,125,6 +hydra-public-high,Production,dev,125,6 +hydra-public-high,Production,prod,200,9 +hydra-public-high,Production,stage,125,6 +hydra-public-low,Develop,dev,15,5 +hydra-public-low,Develop,prod,15,5 +hydra-public-low,Develop,stage,15,5 +hydra-public-low,Enterprise,dev,30,5 +hydra-public-low,Enterprise,prod,450,19 +hydra-public-low,Enterprise,stage,30,5 +hydra-public-low,Growth,dev,30,5 +hydra-public-low,Growth,prod,150,7 +hydra-public-low,Growth,stage,30,5 +hydra-public-low,Production,dev,30,5 +hydra-public-low,Production,prod,45,5 +hydra-public-low,Production,stage,30,5 +hydra-public-medium,Develop,dev,125,6 +hydra-public-medium,Develop,prod,125,6 +hydra-public-medium,Develop,stage,125,6 +hydra-public-medium,Enterprise,dev,250,11 +hydra-public-medium,Enterprise,prod,3600,145 +hydra-public-medium,Enterprise,stage,250,11 +hydra-public-medium,Growth,dev,250,11 +hydra-public-medium,Growth,prod,1200,49 +hydra-public-medium,Growth,stage,250,11 +hydra-public-medium,Production,dev,250,11 +hydra-public-medium,Production,prod,375,16 +hydra-public-medium,Production,stage,250,11 +keto-admin-low,Develop,dev,80,5 +keto-admin-low,Develop,prod,80,5 +keto-admin-low,Develop,stage,80,5 +keto-admin-low,Enterprise,dev,175,8 +keto-admin-low,Enterprise,prod,700,29 +keto-admin-low,Enterprise,stage,175,8 +keto-admin-low,Growth,dev,175,8 +keto-admin-low,Growth,prod,350,15 +keto-admin-low,Growth,stage,175,8 +keto-admin-low,Production,dev,175,8 +keto-admin-low,Production,prod,175,8 +keto-admin-low,Production,stage,175,8 +keto-admin-medium,Develop,dev,125,6 +keto-admin-medium,Develop,prod,125,6 +keto-admin-medium,Develop,stage,125,6 +keto-admin-medium,Enterprise,dev,250,11 +keto-admin-medium,Enterprise,prod,2200,89 +keto-admin-medium,Enterprise,stage,250,11 +keto-admin-medium,Growth,dev,250,11 +keto-admin-medium,Growth,prod,1100,45 +keto-admin-medium,Growth,stage,250,11 +keto-admin-medium,Production,dev,250,11 +keto-admin-medium,Production,prod,550,23 +keto-admin-medium,Production,stage,250,11 +keto-public-high,Develop,dev,250,11 +keto-public-high,Develop,prod,250,11 +keto-public-high,Develop,stage,250,11 +keto-public-high,Enterprise,dev,500,21 +keto-public-high,Enterprise,prod,7500,301 +keto-public-high,Enterprise,stage,500,21 +keto-public-high,Growth,dev,500,21 +keto-public-high,Growth,prod,2500,101 +keto-public-high,Growth,stage,500,21 +keto-public-high,Production,dev,500,21 +keto-public-high,Production,prod,750,31 +keto-public-high,Production,stage,500,21 +kratos-admin-high,Develop,dev,40,5 +kratos-admin-high,Develop,prod,40,5 +kratos-admin-high,Develop,stage,40,5 +kratos-admin-high,Enterprise,dev,80,5 +kratos-admin-high,Enterprise,prod,1000,41 +kratos-admin-high,Enterprise,stage,80,5 +kratos-admin-high,Growth,dev,80,5 +kratos-admin-high,Growth,prod,500,21 +kratos-admin-high,Growth,stage,80,5 +kratos-admin-high,Production,dev,80,5 +kratos-admin-high,Production,prod,80,5 +kratos-admin-high,Production,stage,80,5 +kratos-admin-low,Develop,dev,80,5 +kratos-admin-low,Develop,prod,80,5 +kratos-admin-low,Develop,stage,80,5 +kratos-admin-low,Enterprise,dev,175,8 +kratos-admin-low,Enterprise,prod,850,35 +kratos-admin-low,Enterprise,stage,175,8 +kratos-admin-low,Growth,dev,175,8 +kratos-admin-low,Growth,prod,425,18 +kratos-admin-low,Growth,stage,175,8 +kratos-admin-low,Production,dev,175,8 +kratos-admin-low,Production,prod,175,8 +kratos-admin-low,Production,stage,175,8 +kratos-admin-medium,Develop,dev,25,5 +kratos-admin-medium,Develop,prod,25,5 +kratos-admin-medium,Develop,stage,25,5 +kratos-admin-medium,Enterprise,dev,50,5 +kratos-admin-medium,Enterprise,prod,400,17 +kratos-admin-medium,Enterprise,stage,50,5 +kratos-admin-medium,Growth,dev,50,5 +kratos-admin-medium,Growth,prod,200,9 +kratos-admin-medium,Growth,stage,50,5 +kratos-admin-medium,Production,dev,50,5 +kratos-admin-medium,Production,prod,90,5 +kratos-admin-medium,Production,stage,50,5 +kratos-public-high,Develop,dev,450,19 +kratos-public-high,Develop,prod,450,19 +kratos-public-high,Develop,stage,450,19 +kratos-public-high,Enterprise,dev,900,37 +kratos-public-high,Enterprise,prod,15000,601 +kratos-public-high,Enterprise,stage,900,37 +kratos-public-high,Growth,dev,900,37 +kratos-public-high,Growth,prod,4500,181 +kratos-public-high,Growth,stage,900,37 +kratos-public-high,Production,dev,900,37 +kratos-public-high,Production,prod,1400,57 +kratos-public-high,Production,stage,900,37 +kratos-public-low,Develop,dev,20,5 +kratos-public-low,Develop,prod,20,5 +kratos-public-low,Develop,stage,20,5 +kratos-public-low,Enterprise,dev,40,5 +kratos-public-low,Enterprise,prod,600,25 +kratos-public-low,Enterprise,stage,40,5 +kratos-public-low,Growth,dev,40,5 +kratos-public-low,Growth,prod,200,9 +kratos-public-low,Growth,stage,40,5 +kratos-public-low,Production,dev,40,5 +kratos-public-low,Production,prod,60,5 +kratos-public-low,Production,stage,40,5 +kratos-public-medium,Develop,dev,100,5 +kratos-public-medium,Develop,prod,100,5 +kratos-public-medium,Develop,stage,100,5 +kratos-public-medium,Enterprise,dev,200,9 +kratos-public-medium,Enterprise,prod,3000,121 +kratos-public-medium,Enterprise,stage,200,9 +kratos-public-medium,Growth,dev,200,9 +kratos-public-medium,Growth,prod,1000,41 +kratos-public-medium,Growth,stage,200,9 +kratos-public-medium,Production,dev,200,9 +kratos-public-medium,Production,prod,325,14 +kratos-public-medium,Production,stage,200,9 +polis-public-high,Develop,dev,15,5 +polis-public-high,Develop,prod,15,5 +polis-public-high,Develop,stage,15,5 +polis-public-high,Enterprise,dev,30,5 +polis-public-high,Enterprise,prod,450,19 +polis-public-high,Enterprise,stage,30,5 +polis-public-high,Growth,dev,30,5 +polis-public-high,Growth,prod,150,7 +polis-public-high,Growth,stage,30,5 +polis-public-high,Production,dev,30,5 +polis-public-high,Production,prod,45,5 +polis-public-high,Production,stage,30,5 +polis-public-medium,Develop,dev,20,5 +polis-public-medium,Develop,prod,20,5 +polis-public-medium,Develop,stage,20,5 +polis-public-medium,Enterprise,dev,40,5 +polis-public-medium,Enterprise,prod,600,25 +polis-public-medium,Enterprise,stage,40,5 +polis-public-medium,Growth,dev,40,5 +polis-public-medium,Growth,prod,200,9 +polis-public-medium,Growth,stage,40,5 +polis-public-medium,Production,dev,40,5 +polis-public-medium,Production,prod,60,5 +polis-public-medium,Production,stage,40,5 diff --git a/src/lib/rate-limits/index.ts b/src/lib/rate-limits/index.ts new file mode 100644 index 000000000..396f23ba1 --- /dev/null +++ b/src/lib/rate-limits/index.ts @@ -0,0 +1,14 @@ +// Copyright © 2022 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +export type { + EndpointRow, + Env, + GetThresholdsOptions, + RateLimitsProvider, + ThresholdRow, + Tier, +} from "./types" +export { ENV_FROM_CSV, TIER_FROM_CSV } from "./types" +export { getProvider, setProvider } from "./provider" +export { createCsvProvider } from "./csv-provider" diff --git a/src/lib/rate-limits/provider.ts b/src/lib/rate-limits/provider.ts new file mode 100644 index 000000000..a285a31a2 --- /dev/null +++ b/src/lib/rate-limits/provider.ts @@ -0,0 +1,25 @@ +// Copyright © 2022 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +import type { RateLimitsProvider } from "./types" +import { createCsvProvider } from "./csv-provider" + +let defaultProvider: RateLimitsProvider | null = null + +/** + * Returns the active rate limits provider. Default is the CSV provider. + * When RATE_LIMITS_API_BASE (or equivalent) is set, can be switched to API provider later. + */ +export function getProvider(): RateLimitsProvider { + if (!defaultProvider) { + defaultProvider = createCsvProvider() + } + return defaultProvider +} + +/** + * For tests or when you want to inject a different provider. + */ +export function setProvider(provider: RateLimitsProvider): void { + defaultProvider = provider +} diff --git a/src/lib/rate-limits/types.ts b/src/lib/rate-limits/types.ts new file mode 100644 index 000000000..55e262682 --- /dev/null +++ b/src/lib/rate-limits/types.ts @@ -0,0 +1,65 @@ +// Copyright © 2022 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +/** + * Subscription tier (doc-friendly; CSV has "Develop" which we normalize to "Developer"). + */ +export type Tier = "Developer" | "Production" | "Growth" | "Enterprise" + +/** + * Project environment (doc-friendly; CSV has dev/prod/stage which we normalize). + */ +export type Env = "Development" | "Staging" | "Production" + +/** + * One row from the bucket-to-endpoints CSV: method + path → bucket. + */ +export interface EndpointRow { + method: string + path: string + bucket: string +} + +/** + * One row from the bucket-to-threshold CSV: bucket + tier + env → rpm, rps. + * Tier and env are normalized to doc-friendly values. + */ +export interface ThresholdRow { + bucket: string + tier: Tier + env: Env + rpm: number + rps: number +} + +/** + * Options to filter getThresholds() results. + */ +export interface GetThresholdsOptions { + tier?: Tier + env?: Env + bucket?: string +} + +/** + * Provider interface: same shape for CSV or future API provider. + */ +export interface RateLimitsProvider { + getEndpointsByBucket(): Promise + getThresholds(options?: GetThresholdsOptions): Promise +} + +/** CSV tier value → doc-friendly Tier */ +export const TIER_FROM_CSV: Record = { + Develop: "Developer", + Production: "Production", + Growth: "Growth", + Enterprise: "Enterprise", +} + +/** CSV env value → doc-friendly Env */ +export const ENV_FROM_CSV: Record = { + dev: "Development", + prod: "Production", + stage: "Staging", +} diff --git a/src/plugins/docusaurus-polyfill/index.js b/src/plugins/docusaurus-polyfill/index.js index cc82f50fa..944d60861 100644 --- a/src/plugins/docusaurus-polyfill/index.js +++ b/src/plugins/docusaurus-polyfill/index.js @@ -1,10 +1,30 @@ +const NodePolyfillPlugin = require("node-polyfill-webpack-plugin") + // eslint-disable-next-line module.exports = function (context, options) { return { name: "docusaurus-polyfill", - // eslint-disable-next-line configureWebpack(config, isServer, utils) { return { + mergeStrategy: { "module.rules": "prepend" }, + plugins: [ + new NodePolyfillPlugin(), + ], + resolve: { + fallback: { + buffer: require.resolve("buffer/"), + }, + }, + module: { + rules: [ + { + test: /\.m?js$/, + resolve: { + fullySpecified: false, + }, + }, + ], + }, resolve: { fallback: { path: require.resolve("path-browserify"), diff --git a/src/plugins/docusaurus-rate-limits-data/index.ts b/src/plugins/docusaurus-rate-limits-data/index.ts new file mode 100644 index 000000000..e7f96b04a --- /dev/null +++ b/src/plugins/docusaurus-rate-limits-data/index.ts @@ -0,0 +1,28 @@ +// Copyright © 2022 Ory Corp +// SPDX-License-Identifier: Apache-2.0 + +import * as fs from "fs" +import * as path from "path" +import type { PluginModule } from "@docusaurus/types" +import { createCsvProvider } from "../../lib/rate-limits/csv-provider" + +const plugin: PluginModule = async (context) => { + const siteDir = context.siteDir + const provider = createCsvProvider({ siteDir }) + const [endpoints, thresholds] = await Promise.all([ + provider.getEndpointsByBucket(), + provider.getThresholds(), + ]) + const staticDir = path.join(siteDir, "src", "static") + if (!fs.existsSync(staticDir)) { + fs.mkdirSync(staticDir, { recursive: true }) + } + const outPath = path.join(staticDir, "rate-limits.json") + fs.writeFileSync(outPath, JSON.stringify({ endpoints, thresholds }), "utf-8") + + return { + name: "docusaurus-rate-limits-data", + } +} + +export default plugin diff --git a/src/sidebar-old.ts b/src/sidebar-old.ts deleted file mode 100644 index a5cd37deb..000000000 --- a/src/sidebar-old.ts +++ /dev/null @@ -1,1356 +0,0 @@ -// Copyright © 2026 Ory Corp -// SPDX-License-Identifier: Apache-2.0 - -import { - SidebarItem, - SidebarItemConfig, -} from "@docusaurus/plugin-content-docs/src/sidebars/types" - -type SidebarItemsConfig = SidebarItemConfig[] - -const homeLink: SidebarItem = { - type: "link", - href: "/welcome", - className: "all-docs-link", - label: "Go to Start Page", -} - -const polisLink: SidebarItem = { - type: "link", - href: "/polis", - className: "all-docs-link", - label: "Go to Polis Page", -} - -const oidcSSO: SidebarItemConfig = { - type: "category", - label: "OpenID Connect SSO", - link: { - type: "doc", - id: "guides/oauth2-openid-connect", - }, - items: [ - "kratos/social-signin/overview", - { - type: "category", - label: "Social Sign-in Providers", - items: [ - "kratos/social-signin/generic", - "kratos/social-signin/ory", - "kratos/social-signin/google", - "kratos/social-signin/facebook", - "kratos/social-signin/microsoft", - "kratos/social-signin/github", - "kratos/social-signin/apple", - "kratos/social-signin/gitlab", - "kratos/social-signin/auth0", - "kratos/social-signin/salesforce", - "kratos/social-signin/slack", - "kratos/social-signin/spotify", - "kratos/social-signin/discord", - "kratos/social-signin/twitch", - "kratos/social-signin/netid", - "kratos/social-signin/yandex", - "kratos/social-signin/vk", - "kratos/social-signin/dingtalk", - "kratos/social-signin/lark", - "kratos/social-signin/patreon", - "kratos/social-signin/linkedin", - "kratos/social-signin/x-twitter", - "kratos/social-signin/line", - "kratos/social-signin/amazon", - "kratos/social-signin/uaepass", - ], - }, - "kratos/social-signin/data-mapping", - "kratos/social-signin/upstream-mfa", - "kratos/social-signin/account-linking", - "kratos/social-signin/get-tokens", - "identities/sign-in/social-sign-in/redirect-url", - "kratos/social-signin/native-apps", - "kratos/social-signin/oidc-pkce", - "kratos/social-signin/fedcm", - ], -} - -const api: SidebarItemsConfig = [ - homeLink, - "reference/api", - - // "ecosystem/api-design", - { - type: "category", - label: "Operations", - collapsed: false, - collapsible: false, - link: { - type: "generated-index", - slug: "/category/operations-reference", - }, - items: [ - "concepts/personal-access-token", - "guides/cors", - "guides/api-rest-pagination", - "guides/rate-limits", - "guides/ip-allowlist", - "api/eventual-consistency", - "kratos/reference/jsonnet", - "guides/user-impersonation", - ], - }, -] - -const polisApi: SidebarItemsConfig = [polisLink, "polis/reference/api"] - -const sdk: SidebarItemsConfig = [ - homeLink, - "sdk", - { - type: "category", - label: "Identity SDKs", - collapsed: false, - collapsible: false, - link: { - type: "doc", - id: "kratos/sdk/overview", - }, - items: ["kratos/self-hosted/go"], - }, - { - type: "category", - label: "OAuth2 SDKs", - collapsed: false, - collapsible: false, - link: { - type: "doc", - id: "hydra/sdk/overview", - }, - items: ["hydra/self-hosted/go", "hydra/self-hosted/js"], - }, - { - type: "category", - label: "Permission SDKs", - collapsed: false, - collapsible: false, - link: { - type: "doc", - id: "keto/sdk/overview", - }, - items: ["keto/self-hosted/go", "keto/sdk/python"], - }, -] -const cli: SidebarItemsConfig = [ - homeLink, - { - type: "category", - label: "Ory CLI reference", - collapsed: false, - collapsible: false, - link: { - type: "generated-index", - }, - items: [ - "guides/cli/installation", - "guides/cli/cli-basics", - "guides/cli/identity-cli", - "guides/cli/oauth2-cli", - "guides/cli/configure-permission-service", - "guides/cli/proxy-and-tunnel", - "guides/cli/config-with-cli", - { - type: "category", - label: "CLI commands reference", - link: { - type: "generated-index", - }, - items: [ - { - type: "autogenerated", - dirName: "cli", - }, - ], - }, - ], - }, -] - -const quickstart: SidebarItemsConfig = [ - "welcome", - "intro", - "getting-started/local-development", - "ecosystem/mcp", - { - type: "category", - label: "Quickstart", - collapsed: false, - collapsible: false, - link: { - type: "doc", - id: "getting-started/overview", - }, - items: [ - { - type: "autogenerated", - dirName: "getting-started/integrate-auth", - }, - ], - }, - { - type: "category", - label: "Migrate to Ory", - collapsed: false, - collapsible: false, - items: [ - { - type: "category", - label: "Migrate to Ory Network", - collapsed: true, - collapsible: true, - items: [ - { - type: "category", - label: "Phase 1: Plan & prepare", - collapsed: true, - collapsible: true, - items: [ - "migrate-to-ory/migrate/index", - "migrate-to-ory/migrate/map-to-orycap", - "migrate-to-ory/migrate/migrate-strategies", - //"migrate-to-ory/migrate/faq-migrate", - ], - }, - { - type: "category", - label: "Phase 2: Migrate", - collapsed: true, - collapsible: true, - items: [ - "migrate-to-ory/migrate/create-project", - "migrate-to-ory/migrate/design-id-schema", - "migrate-to-ory/migrate/integrate-frontend", - "migrate-to-ory/migrate/integrate-backend", - "migrate-to-ory/migrate/migrate-identities", - ], - }, - { - type: "category", - label: "Phase 3: Test & validate", - collapsed: true, - collapsible: true, - items: ["migrate-to-ory/migrate/test-validate"], - }, - { - type: "category", - label: "Phase 4: Go live", - collapsed: true, - collapsible: true, - items: ["migrate-to-ory/migrate/go-live"], - }, - ], - }, - "migrate-to-ory/auth0", - ], - }, -] - -const troubleshooting: SidebarItemsConfig = [ - homeLink, - { - type: "category", - label: "Troubleshooting", - collapsible: false, - collapsed: false, - link: { - type: "generated-index", - slug: "/category/troubleshooting", - }, - items: [ - { - type: "autogenerated", - dirName: "troubleshooting", - }, - "oauth2-oidc/identity-provider-integration-settings", - "guides/upgrade/sdk-v1", - "hydra/debug", - "hydra/debug/logout", - "hydra/debug/token-endpoint-auth-method", - ], - }, -] - -const operations: SidebarItemsConfig = [ - homeLink, - { - type: "category", - label: "Operations", - link: { - type: "doc", - id: "guides/operations", - }, - collapsed: false, - collapsible: false, - items: [ - "guides/workspaces", - "console/single-sign-on", - "console/roles-and-permissions", - "guides/custom-domains", - "console/usage-billing", - "guides/manage-project-via-api", - "console/change-owner", - "guides/gitops", - "actions/live-events", - ], - }, -] - -const kratos: SidebarItemsConfig = [ - homeLink, - "identities/index", - { - type: "category", - label: "Get Started", - collapsed: true, - link: { - type: "doc", - id: "identities/get-started/index", - }, - items: [ - "identities/get-started/setup", - "identities/get-started/sign-up", - "identities/get-started/sign-in", - "identities/get-started/sign-out", - "identities/get-started/session-management", - "identities/get-started/account-recovery", - "identities/get-started/mfa", - "identities/get-started/social-sign-in", - "identities/get-started/passwordless", - ], - }, - { - type: "category", - label: "Concepts", - collapsed: false, - collapsible: false, - link: { - type: "generated-index", - }, - items: [ - "security-model", - "identities/native-browser", - "concepts/redirects", - "kratos/hooks/configure-hooks", - ], - }, - { - type: "category", - label: "Guides", - collapsed: false, - collapsible: false, - link: { - type: "generated-index", - }, - items: [ - { - type: "category", - label: "Authentication", - link: { - type: "generated-index", - slug: "guides/authentication", - }, - items: [ - "kratos/concepts/credentials", - "kratos/concepts/credentials/username-email-password", - "kratos/passwordless/passwordless", - "kratos/passwordless/one-time-code", - "kratos/passwordless/passkeys", - "kratos/passwordless/passkeys-mobile", - "kratos/passwordless/deviceauthn", - "kratos/organizations/organizations", - "kratos/emails-sms/custom-email-templates", - ], - }, - oidcSSO, - { - type: "category", - label: "Flows", - link: { - type: "doc", - id: "kratos/self-service", - }, - items: [ - "kratos/concepts/browser-redirect-flow-completion", - "kratos/self-service/flows/user-registration", - "kratos/self-service/flows/user-login", - "kratos/self-service/flows/user-logout", - "kratos/self-service/flows/user-settings", - "kratos/self-service/flows/verify-email-account-activation", - "kratos/self-service/flows/CAPTCHA", - "kratos/self-service/flows/account-recovery-password-reset", - "kratos/self-service/flows/user-facing-errors", - ], - }, - { - type: "category", - label: "Session", - link: { - type: "doc", - id: "kratos/session-management/overview", - }, - items: [ - "kratos/session-management/session-management", - "identities/sign-in/check-session", - "kratos/session-management/session-lifespan", - "kratos/session-management/refresh-extend-sessions", - "kratos/session-management/revoke-sessions-hook", - "identities/session-to-jwt-cors", - "concepts/cache", - ], - }, - { - type: "category", - label: "Multi-factor authentication", - link: { - type: "doc", - id: "kratos/mfa/overview", - }, - items: [ - "kratos/mfa/lookup-secrets", - "kratos/mfa/totp", - "kratos/mfa/webauthn-fido-yubikey", - "kratos/mfa/mfa-via-sms", - "kratos/mfa/step-up-authentication", - ], - }, - { - type: "category", - label: "Emails and SMS", - collapsed: true, - collapsible: true, - link: { - type: "doc", - id: "guides/email-sms", - }, - items: [ - "kratos/emails-sms/sending-emails-smtp", - "kratos/emails-sms/sending-sms", - ], - }, - { - type: "category", - label: "Ory Actions", - link: { - type: "doc", - id: "guides/integrate-with-ory-cloud-through-webhooks", - }, - items: [ - { - type: "category", - label: "Identity management", - items: [ - "identities/sign-in/actions", - "actions/revoke-active-sessions", - "actions/session", - "actions/require-verified-address", - { - type: "category", - label: "Integrations", - items: [ - "actions/integrations/hubspot", - "actions/integrations/mailchimp", - "actions/integrations/segment", - ], - }, - ], - }, - ], - }, - { - type: "category", - label: "Search", - items: [ - "kratos/manage-identities/search/identity-search-console", - "kratos/manage-identities/search/identity-search-api", - ], - }, - { - type: "category", - label: "Identity management", - link: { - type: "doc", - id: "kratos/manage-identities/overview", - }, - items: [ - "kratos/manage-identities/create-users-identities", - "kratos/manage-identities/import-user-accounts-identities", - "identities/model/identity-state", - "kratos/manage-identities/invite-users", - "kratos/manage-identities/account-recovery", - "kratos/manage-identities/export-user-accounts-identities", - - { - type: "category", - label: "SCIM", - link: { - type: "doc", - id: "kratos/manage-identities/scim", - }, - items: [ - "kratos/manage-identities/scim/ms-entra", - "kratos/manage-identities/scim/okta", - "kratos/manage-identities/scim/google-workspace", - ], - }, - - "kratos/manage-identities/external-id", - ], - }, - { - type: "category", - label: "Identity schema", - link: { - type: "doc", - id: "kratos/manage-identities/identity-schema", - }, - items: [ - "kratos/manage-identities/managing-users-identities-metadata", - "identities/model/manage-identity-schema", - "kratos/manage-identities/customize-identity-schema", - "identities/model/identity-schema-selection", - "kratos/manage-identities/best-practices", - ], - }, - - { - type: "category", - label: "User interface", - link: { - type: "doc", - id: "kratos/bring-your-own-ui/custom-ui-overview", - }, - items: [ - "account-experience/index", - "kratos/bring-your-own-ui/configure-ory-to-use-your-ui", - "kratos/bring-your-own-ui/custom-vs-built-in-ui", - { - type: "doc", - id: "elements/index", - label: "Ory Elements", - className: "external-link", - }, - { - type: "category", - label: "Build your own UI", - link: { - type: "doc", - id: "getting-started/custom-ui", - }, - items: [ - "kratos/bring-your-own-ui/custom-ui-basic-integration", - "kratos/bring-your-own-ui/custom-ui-advanced-integration", - "kratos/concepts/ui-user-interface", - { - type: "doc", - id: "elements/index", - label: "Ory Elements", - className: "external-link", - }, - ], - }, - ], - }, - ], - }, - { - type: "category", - label: "Configuration", - collapsed: false, - collapsible: false, - items: [ - "identities/sign-in/two-step-registration", - "identities/sign-in/identifier-first-authentication", - "identities/sign-in/login-hint", - "identities/sign-in/actions", - "identities/sign-in/code-submission-limit", - ], - }, - { - type: "category", - label: "Self-Hosted", - collapsed: false, - collapsible: false, - link: { - type: "doc", - id: "kratos/quickstart", - }, - items: [ - "kratos/install", - "kratos/quickstart", - { - type: "category", - label: "Configuration", - items: [ - "kratos/configuring", - "kratos/guides/configuring-cookies", - "kratos/guides/multi-domain-cookies", - "self-hosted/kratos/configuration/password", - "kratos/self-hosted/mfa", - "kratos/guides/setting-up-cors", - "self-hosted/kratos/configuration/oidc", - "kratos/guides/setting-up-password-hashing-parameters", - "kratos/guides/select-cipher-algorithm", - "kratos/self-hosted/email-http", - "kratos/reference/configuration-editor", - ], - }, - { - type: "category", - label: "Guides", - items: [ - "kratos/guides/docker", - "kratos/guides/deploy-kratos-example", - "kratos/guides/upgrade", - "kratos/guides/production", - "kratos/guides/multi-tenancy-multitenant", - "self-hosted/operations/scalability", - "kratos/self-hosted/mail-courier-templates", - "kratos/guides/tracing", - "kratos/guides/zero-trust-iap-proxy-identity-access-proxy", - "kratos/guides/https-tls", - "kratos/guides/hosting-own-have-i-been-pwned-api", - "kratos/guides/secret-key-rotation", - { - type: "category", - label: "Troubleshooting", - items: [ - { - type: "autogenerated", - dirName: "kratos/debug", - }, - ], - }, - ], - }, - { - type: "category", - label: "Reference", - items: [ - "kratos/reference/api", - { - "Command Line Interface (CLI)": [ - { - type: "autogenerated", - dirName: "kratos/cli", - }, - ], - }, - { - SDK: ["kratos/sdk/overview", "kratos/self-hosted/go"], - }, - "kratos/reference/configuration", - "kratos/reference/json-schema-json-paths", - "kratos/reference/html-forms", - ], - }, - ], - }, -] - -const hydra: SidebarItemsConfig = [ - homeLink, - "oauth2-oidc/index", - { - type: "category", - label: "Start", - collapsed: false, - collapsible: false, - link: { - type: "doc", - id: "getting-started/ory-network-oauth2", - }, - items: [ - "getting-started/oauth2-openid/expressjs", - "hydra/guides/using-oauth2", - ], - }, - { - type: "category", - label: "Concepts", - collapsed: false, - collapsible: false, - link: { - type: "doc", - id: "oauth2-oidc/index", - }, - items: [ - "oauth2-oidc/overview/oauth2-concepts", - "oauth2-oidc/overview/oidc-concepts", - "hydra/concepts/before-oauth2", - "hydra/security-architecture", - ], - }, - - { - type: "category", - label: "Guides", - collapsed: false, - collapsible: false, - link: { - type: "doc", - id: "hydra/guides/oauth2-token-introspection", - }, - items: [ - { - type: "category", - label: "Flows", - link: { - type: "doc", - id: "oauth2-oidc/authorization-code-flow", - }, - items: [ - "oauth2-oidc/authorization-code-flow", - "oauth2-oidc/client-credentials", - "oauth2-oidc/device-authorization", - "oauth2-oidc/resource-owner-password-grant", - "oauth2-oidc/refresh-token-grant", - "oauth2-oidc/userinfo-oidc", - "oauth2-oidc/oidc-logout", - "oauth2-oidc/wellknown-endpoint-discovery", - ], - }, - { - type: "category", - label: "Token management", - link: { - type: "generated-index", - slug: "guides/token-management", - }, - items: [ - "hydra/guides/oauth2-token-introspection", - "oauth2-oidc/revoke-consent", - "oauth2-oidc/skip-consent", - "oauth2-oidc/jwt-access-token", - "hydra/guides/audiences", - "hydra/guides/jwt", - "hydra/guides/client-token-expiration", - "hydra/guides/graceful-token-refresh", - "oauth2-oidc/claims-scope", - ], - }, - { - type: "category", - label: "Ory Actions", - items: ["hydra/guides/claims-at-refresh"], - }, - { - type: "category", - label: "Client management", - items: [ - "hydra/guides/oauth2-clients", - "hydra/jwks", - "hydra/guides/openid", - "oauth2-oidc/issuer-url", - ], - }, - { - type: "category", - label: "User Interface", - link: { - type: "doc", - id: "hydra/guides/custom-ui-oauth2", - }, - items: ["oauth2-oidc/custom-login-consent/flow", "hydra/guides/logout"], - }, - ], - }, - - { - type: "category", - label: "Self-Hosted", - collapsed: false, - collapsible: false, - items: [ - "hydra/self-hosted/install", - "hydra/self-hosted/quickstart", - - { - type: "category", - label: "Configuration", - items: [ - "hydra/reference/configuration", - "hydra/reference/configuration-editor", - "hydra/self-hosted/configure-deploy", - "hydra/self-hosted/dependencies-environment", - "hydra/self-hosted/production", - "hydra/self-hosted/hsm-support", - "self-hosted/operations/scalability", - "hydra/self-hosted/merge-multiple-db-secrets", - "hydra/self-hosted/gitlab", - "hydra/self-hosted/secrets-key-rotation", - "hydra/self-hosted/kubernetes-helm-chart", - "hydra/self-hosted/ssl-https-tls", - "self-hosted/operations/tracing", - "hydra/guides/cookies", - "hydra/guides/cors", - "self-hosted/hydra/debug/csrf", - ], - }, - { - type: "category", - label: "Guides", - items: [ - "hydra/self-hosted/deploy-hydra-example", - "hydra/self-hosted/upgrade", - ], - }, - { - type: "category", - label: "Reference", - items: [ - "hydra/reference/api", - { - "Command Line Interface (CLI)": [ - { - type: "autogenerated", - dirName: "hydra/cli", - }, - ], - }, - { - SDK: [ - "hydra/sdk/overview", - "hydra/self-hosted/go", - "hydra/self-hosted/js", - ], - }, - ], - }, - ], - }, -] - -const keto: SidebarItemsConfig = [ - homeLink, - "keto/index", - { - type: "category", - label: "Start", - collapsed: false, - collapsible: false, - link: { - type: "doc", - id: "guides/permissions/overview", - }, - items: ["keto/quickstart", "keto/examples/olymp-file-sharing"], - }, - { - type: "category", - label: "Concepts", - collapsed: false, - collapsible: false, - link: { - type: "doc", - id: "keto/index", - }, - items: [ - "keto/guides/rbac", - { - type: "autogenerated", - dirName: "keto/concepts", - }, - "keto/reference/ory-permission-language", - ], - }, - { - type: "category", - label: "Guides", - collapsed: false, - collapsible: false, - items: [ - "keto/guides/simple-access-check-guide", - "keto/modeling/create-permission-model", - "keto/guides/list-api-display-objects", - "keto/guides/expand-api-display-who-has-access", - ], - }, - { - type: "category", - label: "Self-Hosted", - collapsed: false, - collapsible: false, - items: [ - "keto/install", - "keto/quickstart", - { - type: "category", - label: "Configuration", - items: [ - "keto/reference/configuration", - "keto/reference/configuration-editor", - ], - }, - { - type: "category", - label: "Guides", - items: [ - "keto/guides/production", - "keto/guides/v0.7-migration", - "keto/guides/migrating-legacy-policies", - "keto/guides/upgrade", - ], - }, - { - Reference: [ - "keto/reference/rest-api", - "keto/reference/proto-api", - { - "Command Line Interface (CLI)": [ - { - type: "autogenerated", - dirName: "keto/cli", - }, - ], - }, - { - SDK: ["keto/sdk/overview", "keto/self-hosted/go"], - }, - ], - }, - ], - }, -] - -const polis: SidebarItemsConfig = [ - homeLink, - "polis/index", - { - type: "category", - label: "Self-Hosted", - collapsed: false, - collapsible: false, - items: [ - "polis/install", - "polis/quickstart", - "polis/reference/api", - { - type: "category", - label: "Enterprise SSO", - collapsed: false, - collapsible: false, - link: { - type: "doc", - id: "polis/saml-federation/index", - }, - items: [ - { - type: "category", - label: "Concepts", - link: { - type: "doc", - id: "polis/sso-flow/index", - }, - items: [ - "polis/sso-flow/index", - "polis/sso-flow/example-flow", - "polis/security", - ], - }, - { - type: "category", - label: "Configuration", - items: [ - "polis/deploy/env-variables", - "polis/guides/configuring-saml-sso", - "polis/guides/login-with-saml-sso", - "polis/deploy/pre-loaded-connections", - ], - }, - { - type: "category", - label: "Guides", - link: { - type: "doc", - id: "polis/guides/service", - }, - items: [ - "polis/guides/service", - "polis/guides/npm-library", - "polis/upgrade", - "polis/guides/examples", - "polis/sbom", - "polis/container-signing", - "polis/events", - "polis/observability", - { - type: "category", - label: "Frameworks", - items: [ - { - type: "autogenerated", - dirName: "polis/guides/frameworks", - }, - ], - }, - { - type: "category", - label: "Deployment", - items: [ - { - type: "autogenerated", - dirName: "polis/guides/deployment", - }, - ], - }, - ], - }, - { - type: "category", - label: "SAML SSO Providers", - link: { - type: "doc", - id: "polis/sso-providers/index", - }, - items: [ - { - type: "autogenerated", - dirName: "polis/sso-providers", - }, - ], - }, - ], - }, - { - type: "category", - label: "Directory Sync", - collapsed: false, - collapsible: false, - link: { - type: "doc", - id: "polis/directory-sync/index", - }, - items: [ - "polis/directory-sync/quickstart", - "polis/directory-sync/api-reference", - { - type: "category", - label: "Guides", - collapsed: true, - link: { - type: "generated-index", - slug: "polis/directory-sync/guides", - }, - items: [ - "polis/directory-sync/webhooks", - "polis/directory-sync/events", - "polis/directory-sync/observability", - "polis/directory-sync/examples", - "polis/directory-sync/faq", - { - type: "category", - label: "Directory providers", - link: { - type: "generated-index", - slug: "polis/directory-sync/providers", - }, - items: [ - { - type: "autogenerated", - dirName: "polis/directory-sync/providers", - }, - ], - }, - { - type: "category", - label: "Frameworks", - items: [ - { - type: "autogenerated", - dirName: "polis/directory-sync/guides", - }, - ], - }, - ], - }, - ], - }, - { - type: "category", - label: "Admin Portal", - collapsed: false, - collapsible: false, - link: { - type: "doc", - id: "polis/admin-portal/index", - }, - items: [ - { - type: "category", - label: "Guides", - collapsed: true, - link: { - type: "generated-index", - slug: "polis/admin-portal/guides", - }, - items: [ - "polis/admin-portal/enterprise-sso", - "polis/admin-portal/setup-links-enterprise-sso", - "polis/admin-portal/directory-sync", - "polis/admin-portal/setup-links-directory-sync", - ], - }, - ], - }, - ], - }, -] - -const oathkeeper: SidebarItemsConfig = [ - homeLink, - "oathkeeper/index", - "oathkeeper/install", - "oathkeeper/reference/api", - { - type: "category", - label: "Concepts", - collapsed: false, - collapsible: false, - items: [ - "oathkeeper/api-access-rules", - { - type: "category", - - label: "Handlers", - items: [ - "oathkeeper/pipeline", - "oathkeeper/pipeline/authn", - "oathkeeper/pipeline/authz", - "oathkeeper/pipeline/mutator", - "oathkeeper/pipeline/error", - ], - }, - "oathkeeper/grpc-middleware", - ], - }, - { - type: "category", - label: "Guides", - collapsed: false, - collapsible: false, - items: [ - "oathkeeper/configure-deploy", - "oathkeeper/guides/proxy-websockets", - "oathkeeper/guides/traefik-proxy-integration", - "oathkeeper/guides/upgrade", - ], - }, - { - type: "category", - label: "Reference", - collapsed: false, - collapsible: false, - items: [ - "oathkeeper/reference/configuration", - "oathkeeper/reference/configuration-editor", - { - "Command Line Interface (CLI)": [ - { - type: "autogenerated", - dirName: "oathkeeper/cli", - }, - ], - }, - "oathkeeper/sdk", - ], - }, -] - -const opensource: SidebarItemsConfig = [ - homeLink, - // "ecosystem/projects", - { - type: "category", - label: "Open source", - items: [ - "open-source", - // "ecosystem/community", - // "ecosystem/contributing", - // "open-source/commitment", - // "ecosystem/software-architecture-philosophy", - { - type: "category", - label: "Guidelines", - items: [ - "open-source/guidelines/rest-api-guidelines", - "kratos/guides/e2e-integration-tests", - ], - }, - ], - }, - { - type: "doc", - label: "Ory Kratos", - id: "kratos/quickstart", - }, - { - type: "doc", - label: "Ory Hydra", - id: "hydra/self-hosted/quickstart", - }, - { - type: "doc", - label: "Ory Keto", - id: "keto/quickstart", - }, - { - type: "doc", - label: "Ory Polis", - id: "polis/quickstart", - }, - { - type: "doc", - label: "Ory Oathkeeper", - id: "oathkeeper/index", - }, - "self-hosted/deployment", - //"ecosystem/configuring", - { - type: "category", - label: "Operations and SRE", - items: [ - { - type: "autogenerated", - dirName: "self-hosted/operations", - }, - ], - }, - // "ecosystem/upgrading", - // "ecosystem/sqa", - // "ecosystem/changelog", - { - type: "link", - label: "Search", - href: "https://www.ory.com/docs/search", - }, -] - -const oel: SidebarItemsConfig = [ - homeLink, - "self-hosted/oel/index", - "self-hosted/oel/quickstart", - { - type: "category", - label: "Ory Hydra OAuth2", - items: [ - "self-hosted/oel/oauth2/migrate", - "self-hosted/oel/oauth2/upgrade", - "self-hosted/oel/oauth2/changelog", - "self-hosted/oel/oauth2/token-prefix", - "self-hosted/oel/oauth2/stateless-jwt", - "self-hosted/oel/oauth2/migrate-postgresql-ttl", - "self-hosted/oel/oauth2/revert-database-migrations", - "self-hosted/oel/oauth2/configuration", - ], - }, - { - type: "category", - label: "Ory Oathkeeper Zero Trust", - items: [ - "self-hosted/oel/oathkeeper/upgrade-oathkeeper", - "self-hosted/oel/oathkeeper/changelog", - "self-hosted/oel/oathkeeper/configuration", - ], - }, - { - type: "category", - label: "Ory Kratos Identities", - items: [ - "self-hosted/oel/kratos/upgrade", - "self-hosted/oel/kratos/changelog", - "self-hosted/oel/kratos/configuration", - ], - }, - { - type: "category", - label: "Ory Keto Permissions", - items: [ - "self-hosted/oel/keto/changelog", - "self-hosted/oel/keto/configuration", - ], - }, - { - type: "category", - label: "Ory Polis", - items: ["self-hosted/oel/polis/changelog"], - }, - "self-hosted/oel/monitoring/monitoring", - "self-hosted/oel/high-performance-pooling", -] - -const security: SidebarItemsConfig = [ - homeLink, - "security-compliance/compliance-and-certifications", - "security-compliance/personal-data-location", - //"ecosystem/security", - "kratos/concepts/security", - "security-compliance/token-formats", - "concepts/password-policy", - "hydra/security-architecture", - "security-compliance/gdpr", -] - -const elements: SidebarItemsConfig = [ - homeLink, - "elements/index", - { - type: "category", - label: "Guides", - collapsible: false, - link: { - type: "generated-index", - title: "Quickstarts", - description: "Learn how to use Ory Elements in your application!", - slug: "/elements/guides", - keywords: ["guides"], - }, - items: [ - { - type: "autogenerated", - dirName: "elements/guides", - }, - ], - }, - { - type: "category", - label: "Reference", - collapsible: false, - link: { - type: "generated-index", - title: "Ory Elements Reference", - description: "Learn about the most important Ory Elements concepts!", - slug: "/elements/reference", - keywords: ["guides"], - }, - items: [ - ...require("../docs/elements/reference/typedoc-sidebar.ts").items, - "elements/css-reference", - ], - }, -] - -module.exports = { - quickstart, - troubleshooting, - operations, - opensource, - oel, - kratos, - hydra, - keto, - polis, - oathkeeper, - api, - polisApi, - sdk, - cli, - security, - elements, -} diff --git a/src/static/rate-limits.json b/src/static/rate-limits.json new file mode 100644 index 000000000..934967187 --- /dev/null +++ b/src/static/rate-limits.json @@ -0,0 +1 @@ +{"endpoints":[{"method":"GET","path":"/.well-known/openid-configuration","bucket":"hydra-public-high"},{"method":"HEAD","path":"/.well-known/openid-configuration","bucket":"hydra-public-high"},{"method":"GET","path":"/.well-known/jwks.json","bucket":"hydra-public-high"},{"method":"GET","path":"/.well-known/ory/webauthn.js","bucket":"hydra-public-high"},{"method":"HEAD","path":"/oauth2/auth","bucket":"hydra-public-high"},{"method":"OPTIONS","path":"/oauth2/auth","bucket":"hydra-public-high"},{"method":"GET","path":"/oauth2/consent","bucket":"hydra-public-high"},{"method":"POST","path":"/oauth2/device/auth","bucket":"hydra-public-high"},{"method":"GET","path":"/oauth2/fallbacks/logout/callback","bucket":"hydra-public-high"},{"method":"OPTIONS","path":"/oauth2/token","bucket":"hydra-public-high"},{"method":"GET","path":"/admin/clients","bucket":"hydra-admin-medium"},{"method":"GET","path":"/admin/trust/grants/jwt-bearer/issuers","bucket":"hydra-admin-medium"},{"method":"POST","path":"/credentials","bucket":"hydra-admin-medium"},{"method":"GET","path":"/admin/keys/{set}","bucket":"hydra-admin-medium"},{"method":"GET","path":"/admin/keys/{set}/{kid}","bucket":"hydra-admin-medium"},{"method":"GET","path":"/admin/trust/grants/jwt-bearer/issuers/{id}","bucket":"hydra-admin-medium"},{"method":"POST","path":"/admin/clients","bucket":"hydra-admin-low"},{"method":"PUT","path":"/admin/clients/{id}/lifespans","bucket":"hydra-admin-low"},{"method":"DELETE","path":"/admin/oauth2/auth/sessions/consent","bucket":"hydra-admin-low"},{"method":"DELETE","path":"/admin/oauth2/auth/sessions/login","bucket":"hydra-admin-low"},{"method":"DELETE","path":"/admin/oauth2/tokens","bucket":"hydra-admin-low"},{"method":"POST","path":"/admin/trust/grants/jwt-bearer/issuers","bucket":"hydra-admin-low"},{"method":"PUT","path":"/admin/clients/{id}","bucket":"hydra-admin-low"},{"method":"PATCH","path":"/admin/clients/{id}","bucket":"hydra-admin-low"},{"method":"DELETE","path":"/admin/clients/{id}","bucket":"hydra-admin-low"},{"method":"POST","path":"/admin/keys/{set}","bucket":"hydra-admin-low"},{"method":"PUT","path":"/admin/keys/{set}","bucket":"hydra-admin-low"},{"method":"DELETE","path":"/admin/keys/{set}","bucket":"hydra-admin-low"},{"method":"PUT","path":"/admin/keys/{set}/{kid}","bucket":"hydra-admin-low"},{"method":"DELETE","path":"/admin/keys/{set}/{kid}","bucket":"hydra-admin-low"},{"method":"DELETE","path":"/admin/trust/grants/jwt-bearer/issuers/{id}","bucket":"hydra-admin-low"},{"method":"OPTIONS","path":"/admin/clients","bucket":"hydra-admin-high"},{"method":"GET","path":"/admin/oauth2/auth/requests/consent","bucket":"hydra-admin-high"},{"method":"PUT","path":"/admin/oauth2/auth/requests/consent/accept","bucket":"hydra-admin-high"},{"method":"PUT","path":"/admin/oauth2/auth/requests/consent/reject","bucket":"hydra-admin-high"},{"method":"PUT","path":"/admin/oauth2/auth/requests/device/accept","bucket":"hydra-admin-high"},{"method":"GET","path":"/admin/oauth2/auth/requests/login","bucket":"hydra-admin-high"},{"method":"PUT","path":"/admin/oauth2/auth/requests/login/accept","bucket":"hydra-admin-high"},{"method":"PUT","path":"/admin/oauth2/auth/requests/login/reject","bucket":"hydra-admin-high"},{"method":"GET","path":"/admin/oauth2/auth/requests/logout","bucket":"hydra-admin-high"},{"method":"PUT","path":"/admin/oauth2/auth/requests/logout/accept","bucket":"hydra-admin-high"},{"method":"PUT","path":"/admin/oauth2/auth/requests/logout/reject","bucket":"hydra-admin-high"},{"method":"GET","path":"/admin/oauth2/auth/sessions/consent","bucket":"hydra-admin-high"},{"method":"POST","path":"/admin/oauth2/introspect","bucket":"hydra-admin-high"},{"method":"GET","path":"/oauth2/device/verify","bucket":"hydra-admin-high"},{"method":"GET","path":"/admin/clients/{id}","bucket":"hydra-admin-high"},{"method":"OPTIONS","path":"/admin/clients/{id}","bucket":"hydra-admin-high"},{"method":"GET","path":"/oauth2/register/{id}","bucket":"hydra-admin-high"},{"method":"GET","path":"/oauth2/auth","bucket":"hydra-public-medium"},{"method":"POST","path":"/oauth2/auth","bucket":"hydra-public-medium"},{"method":"POST","path":"/oauth2/revoke","bucket":"hydra-public-medium"},{"method":"GET","path":"/oauth2/sessions/logout","bucket":"hydra-public-medium"},{"method":"POST","path":"/oauth2/sessions/logout","bucket":"hydra-public-medium"},{"method":"POST","path":"/oauth2/token","bucket":"hydra-public-medium"},{"method":"GET","path":"/userinfo","bucket":"hydra-public-medium"},{"method":"POST","path":"/oauth2/register","bucket":"hydra-public-low"},{"method":"PUT","path":"/oauth2/register/{id}","bucket":"hydra-public-low"},{"method":"DELETE","path":"/oauth2/register/{id}","bucket":"hydra-public-low"},{"method":"PUT","path":"/admin/relation-tuples","bucket":"keto-admin-low"},{"method":"PATCH","path":"/admin/relation-tuples","bucket":"keto-admin-low"},{"method":"DELETE","path":"/admin/relation-tuples","bucket":"keto-admin-low"},{"method":"GET","path":"/namespaces","bucket":"keto-admin-low"},{"method":"POST","path":"/ory.keto.relation_tuples.v1alpha2.WriteService/TransactRelationTuples","bucket":"keto-admin-low"},{"method":"POST","path":"/opl/syntax/check","bucket":"keto-admin-medium"},{"method":"GET","path":"/relation-tuples","bucket":"keto-admin-medium"},{"method":"GET","path":"/relation-tuples/expand","bucket":"keto-admin-medium"},{"method":"POST","path":"/ory.keto.relation_tuples.v1alpha2.CheckService/BatchCheck","bucket":"keto-public-high"},{"method":"POST","path":"/ory.keto.relation_tuples.v1alpha2.CheckService/Check","bucket":"keto-public-high"},{"method":"POST","path":"/relation-tuples/batch/check","bucket":"keto-public-high"},{"method":"GET","path":"/relation-tuples/check","bucket":"keto-public-high"},{"method":"POST","path":"/relation-tuples/check","bucket":"keto-public-high"},{"method":"GET","path":"/relation-tuples/check/openapi","bucket":"keto-public-high"},{"method":"POST","path":"/relation-tuples/check/openapi","bucket":"keto-public-high"},{"method":"GET","path":"/admin/courier/messages","bucket":"kratos-admin-low"},{"method":"POST","path":"/admin/identities","bucket":"kratos-admin-low"},{"method":"PATCH","path":"/admin/identities","bucket":"kratos-admin-low"},{"method":"DELETE","path":"/admin/identities/{id}/sessions","bucket":"kratos-admin-low"},{"method":"POST","path":"/admin/recovery/code","bucket":"kratos-admin-low"},{"method":"POST","path":"/admin/recovery/link","bucket":"kratos-admin-low"},{"method":"PATCH","path":"/admin/sessions/{id}/extend","bucket":"kratos-admin-low"},{"method":"POST","path":"/scim/{name}/v2/Groups","bucket":"kratos-admin-low"},{"method":"POST","path":"/scim/{name}/v2/Users","bucket":"kratos-admin-low"},{"method":"PUT","path":"/admin/identities/{id}","bucket":"kratos-admin-low"},{"method":"PATCH","path":"/admin/identities/{id}","bucket":"kratos-admin-low"},{"method":"DELETE","path":"/admin/identities/{id}","bucket":"kratos-admin-low"},{"method":"DELETE","path":"/admin/identities/{id}/credentials/{type}","bucket":"kratos-admin-low"},{"method":"DELETE","path":"/admin/sessions/{id}","bucket":"kratos-admin-low"},{"method":"PUT","path":"/scim/{name}/v2/Groups/{id}","bucket":"kratos-admin-low"},{"method":"PATCH","path":"/scim/{name}/v2/Groups/{id}","bucket":"kratos-admin-low"},{"method":"DELETE","path":"/scim/{name}/v2/Groups/{id}","bucket":"kratos-admin-low"},{"method":"PUT","path":"/scim/{name}/v2/Users/{id}","bucket":"kratos-admin-low"},{"method":"PATCH","path":"/scim/{name}/v2/Users/{id}","bucket":"kratos-admin-low"},{"method":"DELETE","path":"/scim/{name}/v2/Users/{id}","bucket":"kratos-admin-low"},{"method":"GET","path":"/admin/identities","bucket":"kratos-admin-medium"},{"method":"GET","path":"/admin/identities/{id}/sessions","bucket":"kratos-admin-medium"},{"method":"GET","path":"/admin/sessions","bucket":"kratos-admin-medium"},{"method":"GET","path":"/schemas","bucket":"kratos-admin-medium"},{"method":"GET","path":"/scim/{name}/v2/Groups","bucket":"kratos-admin-medium"},{"method":"GET","path":"/scim/{name}/v2/Schemas","bucket":"kratos-admin-medium"},{"method":"GET","path":"/scim/{name}/v2/ServiceProviderConfig","bucket":"kratos-admin-medium"},{"method":"GET","path":"/scim/{name}/v2/Users","bucket":"kratos-admin-medium"},{"method":"GET","path":"/admin/courier/messages/{id}","bucket":"kratos-admin-medium"},{"method":"GET","path":"/admin/identities/by/external/{externalID}","bucket":"kratos-admin-medium"},{"method":"GET","path":"/schemas/{id}","bucket":"kratos-admin-medium"},{"method":"GET","path":"/scim/{name}/v2/Groups/{id}","bucket":"kratos-admin-medium"},{"method":"GET","path":"/scim/{name}/v2/Schemas/{id}","bucket":"kratos-admin-medium"},{"method":"GET","path":"/scim/{name}/v2/Users/{id}","bucket":"kratos-admin-medium"},{"method":"GET","path":"/self-service/errors","bucket":"kratos-public-high"},{"method":"GET","path":"/self-service/fed-cm/parameters","bucket":"kratos-public-high"},{"method":"HEAD","path":"/self-service/fed-cm/parameters","bucket":"kratos-public-high"},{"method":"OPTIONS","path":"/self-service/fed-cm/token","bucket":"kratos-public-high"},{"method":"GET","path":"/self-service/login","bucket":"kratos-public-high"},{"method":"OPTIONS","path":"/self-service/login","bucket":"kratos-public-high"},{"method":"HEAD","path":"/self-service/login/browser","bucket":"kratos-public-high"},{"method":"OPTIONS","path":"/self-service/login/browser","bucket":"kratos-public-high"},{"method":"GET","path":"/self-service/login/flows","bucket":"kratos-public-high"},{"method":"OPTIONS","path":"/self-service/login/flows","bucket":"kratos-public-high"},{"method":"GET","path":"/self-service/logout","bucket":"kratos-public-high"},{"method":"OPTIONS","path":"/self-service/logout","bucket":"kratos-public-high"},{"method":"OPTIONS","path":"/self-service/logout/browser","bucket":"kratos-public-high"},{"method":"GET","path":"/self-service/methods/oidc/callback","bucket":"kratos-public-high"},{"method":"GET","path":"/self-service/recovery","bucket":"kratos-public-high"},{"method":"HEAD","path":"/self-service/recovery","bucket":"kratos-public-high"},{"method":"OPTIONS","path":"/self-service/recovery","bucket":"kratos-public-high"},{"method":"HEAD","path":"/self-service/recovery/browser","bucket":"kratos-public-high"},{"method":"OPTIONS","path":"/self-service/recovery/browser","bucket":"kratos-public-high"},{"method":"GET","path":"/self-service/recovery/flows","bucket":"kratos-public-high"},{"method":"OPTIONS","path":"/self-service/recovery/flows","bucket":"kratos-public-high"},{"method":"GET","path":"/self-service/registration","bucket":"kratos-public-high"},{"method":"OPTIONS","path":"/self-service/registration","bucket":"kratos-public-high"},{"method":"HEAD","path":"/self-service/registration/browser","bucket":"kratos-public-high"},{"method":"OPTIONS","path":"/self-service/registration/browser","bucket":"kratos-public-high"},{"method":"GET","path":"/self-service/registration/flows","bucket":"kratos-public-high"},{"method":"GET","path":"/self-service/settings","bucket":"kratos-public-high"},{"method":"OPTIONS","path":"/self-service/settings","bucket":"kratos-public-high"},{"method":"GET","path":"/self-service/settings/flows","bucket":"kratos-public-high"},{"method":"GET","path":"/self-service/verification","bucket":"kratos-public-high"},{"method":"HEAD","path":"/self-service/verification","bucket":"kratos-public-high"},{"method":"OPTIONS","path":"/self-service/verification","bucket":"kratos-public-high"},{"method":"HEAD","path":"/self-service/verification/browser","bucket":"kratos-public-high"},{"method":"GET","path":"/self-service/verification/flows","bucket":"kratos-public-high"},{"method":"OPTIONS","path":"/self-service/verification/flows","bucket":"kratos-public-high"},{"method":"OPTIONS","path":"/sessions","bucket":"kratos-public-high"},{"method":"GET","path":"/sessions/whoami","bucket":"kratos-public-high"},{"method":"HEAD","path":"/self-service/methods/oidc/callback/{id}","bucket":"kratos-public-high"},{"method":"HEAD","path":"/self-service/methods/oidc/callback/{name}","bucket":"kratos-public-high"},{"method":"POST","path":"/self-service/fed-cm/token","bucket":"kratos-public-low"},{"method":"POST","path":"/self-service/login","bucket":"kratos-public-low"},{"method":"POST","path":"/self-service/recovery","bucket":"kratos-public-low"},{"method":"POST","path":"/self-service/registration","bucket":"kratos-public-low"},{"method":"POST","path":"/self-service/settings","bucket":"kratos-public-low"},{"method":"POST","path":"/self-service/verification","bucket":"kratos-public-low"},{"method":"DELETE","path":"/sessions","bucket":"kratos-public-low"},{"method":"DELETE","path":"/sessions/{id}","bucket":"kratos-public-low"},{"method":"GET","path":"/self-service/login/api","bucket":"kratos-public-medium"},{"method":"GET","path":"/self-service/login/browser","bucket":"kratos-public-medium"},{"method":"POST","path":"/self-service/login/browser","bucket":"kratos-public-medium"},{"method":"DELETE","path":"/self-service/logout/api","bucket":"kratos-public-medium"},{"method":"GET","path":"/self-service/logout/browser","bucket":"kratos-public-medium"},{"method":"GET","path":"/self-service/recovery/api","bucket":"kratos-public-medium"},{"method":"GET","path":"/self-service/recovery/browser","bucket":"kratos-public-medium"},{"method":"GET","path":"/self-service/registration/api","bucket":"kratos-public-medium"},{"method":"GET","path":"/self-service/registration/browser","bucket":"kratos-public-medium"},{"method":"GET","path":"/self-service/settings/api","bucket":"kratos-public-medium"},{"method":"GET","path":"/self-service/settings/browser","bucket":"kratos-public-medium"},{"method":"GET","path":"/self-service/verification/api","bucket":"kratos-public-medium"},{"method":"GET","path":"/self-service/verification/browser","bucket":"kratos-public-medium"},{"method":"GET","path":"/sessions","bucket":"kratos-public-medium"},{"method":"GET","path":"/sessions/token-exchange","bucket":"kratos-public-medium"},{"method":"GET","path":"/self-service/methods/oidc/callback/{id}","bucket":"kratos-public-medium"},{"method":"GET","path":"/self-service/methods/oidc/callback/{name}","bucket":"kratos-public-medium"},{"method":"GET","path":"/self-service/methods/oidc/organizations/{id}","bucket":"kratos-public-medium"},{"method":"GET","path":"/self-service/methods/saml/callback/{name}","bucket":"kratos-public-medium"},{"method":"GET","path":"/self-service/methods/saml/organizations/{id}","bucket":"kratos-public-medium"},{"method":"GET","path":"/admin/identities/{id}","bucket":"kratos-admin-high"},{"method":"OPTIONS","path":"/admin/identities/{id}","bucket":"kratos-admin-high"},{"method":"GET","path":"/admin/sessions/{id}","bucket":"kratos-admin-high"},{"method":"GET","path":"/api/v1/dsync","bucket":"polis-public-high"},{"method":"GET","path":"/api/v1/dsync/events","bucket":"polis-public-high"},{"method":"GET","path":"/api/v1/dsync/groups","bucket":"polis-public-high"},{"method":"GET","path":"/api/v1/dsync/product","bucket":"polis-public-high"},{"method":"GET","path":"/api/v1/dsync/setuplinks","bucket":"polis-public-high"},{"method":"GET","path":"/api/v1/dsync/setuplinks/product","bucket":"polis-public-high"},{"method":"GET","path":"/api/v1/dsync/users","bucket":"polis-public-high"},{"method":"GET","path":"/api/v1/identity-federation","bucket":"polis-public-high"},{"method":"GET","path":"/api/v1/identity-federation/product","bucket":"polis-public-high"},{"method":"GET","path":"/api/v1/sso","bucket":"polis-public-high"},{"method":"GET","path":"/api/v1/sso/product","bucket":"polis-public-high"},{"method":"GET","path":"/api/v1/sso-traces","bucket":"polis-public-high"},{"method":"GET","path":"/api/v1/sso-traces/product","bucket":"polis-public-high"},{"method":"GET","path":"/oauth/userinfo","bucket":"polis-public-high"},{"method":"GET","path":"/saml/.well-known/idp-metadata","bucket":"polis-public-high"},{"method":"GET","path":"/saml/.well-known/sp-metadata","bucket":"polis-public-high"},{"method":"GET","path":"/saml/.well-known/saml.cer","bucket":"polis-public-high"},{"method":"GET","path":"/saml/api/error","bucket":"polis-public-high"},{"method":"GET","path":"/api/v1/dsync/groups/{groupId}","bucket":"polis-public-high"},{"method":"GET","path":"/api/v1/dsync/groups/{groupId}/members","bucket":"polis-public-high"},{"method":"GET","path":"/api/v1/dsync/users/{userId}","bucket":"polis-public-high"},{"method":"DELETE","path":"/api/v1/dsync/setuplinks","bucket":"polis-public-medium"},{"method":"DELETE","path":"/api/v1/identity-federation","bucket":"polis-public-medium"},{"method":"DELETE","path":"/api/v1/sso","bucket":"polis-public-medium"},{"method":"PATCH","path":"/api/v1/identity-federation","bucket":"polis-public-medium"},{"method":"PATCH","path":"/api/v1/sso","bucket":"polis-public-medium"},{"method":"POST","path":"/api/v1/dsync","bucket":"polis-public-medium"},{"method":"POST","path":"/api/v1/dsync/setuplinks","bucket":"polis-public-medium"},{"method":"POST","path":"/api/v1/identity-federation","bucket":"polis-public-medium"},{"method":"POST","path":"/api/v1/sso","bucket":"polis-public-medium"},{"method":"POST","path":"/oauth/token","bucket":"polis-public-medium"},{"method":"GET","path":"/saml/api/identity-federation/sso","bucket":"polis-public-medium"},{"method":"POST","path":"/saml/api/identity-federation/sso","bucket":"polis-public-medium"},{"method":"GET","path":"/saml/api/oauth/authorize","bucket":"polis-public-medium"},{"method":"POST","path":"/saml/api/oauth/authorize","bucket":"polis-public-medium"},{"method":"GET","path":"/saml/api/oauth/oidc","bucket":"polis-public-medium"},{"method":"POST","path":"/saml/api/oauth/saml","bucket":"polis-public-medium"},{"method":"PATCH","path":"/api/v1/dsync/{directoryId}","bucket":"polis-public-medium"}],"thresholds":[{"bucket":"hydra-admin-high","tier":"Developer","env":"Development","rpm":80,"rps":5},{"bucket":"hydra-admin-high","tier":"Developer","env":"Production","rpm":80,"rps":5},{"bucket":"hydra-admin-high","tier":"Developer","env":"Staging","rpm":80,"rps":5},{"bucket":"hydra-admin-high","tier":"Enterprise","env":"Development","rpm":175,"rps":8},{"bucket":"hydra-admin-high","tier":"Enterprise","env":"Production","rpm":8750,"rps":351},{"bucket":"hydra-admin-high","tier":"Enterprise","env":"Staging","rpm":175,"rps":8},{"bucket":"hydra-admin-high","tier":"Growth","env":"Development","rpm":175,"rps":8},{"bucket":"hydra-admin-high","tier":"Growth","env":"Production","rpm":4300,"rps":173},{"bucket":"hydra-admin-high","tier":"Growth","env":"Staging","rpm":175,"rps":8},{"bucket":"hydra-admin-high","tier":"Production","env":"Development","rpm":175,"rps":8},{"bucket":"hydra-admin-high","tier":"Production","env":"Production","rpm":175,"rps":8},{"bucket":"hydra-admin-high","tier":"Production","env":"Staging","rpm":175,"rps":8},{"bucket":"hydra-admin-low","tier":"Developer","env":"Development","rpm":15,"rps":5},{"bucket":"hydra-admin-low","tier":"Developer","env":"Production","rpm":15,"rps":5},{"bucket":"hydra-admin-low","tier":"Developer","env":"Staging","rpm":15,"rps":5},{"bucket":"hydra-admin-low","tier":"Enterprise","env":"Development","rpm":30,"rps":5},{"bucket":"hydra-admin-low","tier":"Enterprise","env":"Production","rpm":700,"rps":29},{"bucket":"hydra-admin-low","tier":"Enterprise","env":"Staging","rpm":30,"rps":5},{"bucket":"hydra-admin-low","tier":"Growth","env":"Development","rpm":30,"rps":5},{"bucket":"hydra-admin-low","tier":"Growth","env":"Production","rpm":350,"rps":15},{"bucket":"hydra-admin-low","tier":"Growth","env":"Staging","rpm":30,"rps":5},{"bucket":"hydra-admin-low","tier":"Production","env":"Development","rpm":30,"rps":5},{"bucket":"hydra-admin-low","tier":"Production","env":"Production","rpm":35,"rps":5},{"bucket":"hydra-admin-low","tier":"Production","env":"Staging","rpm":30,"rps":5},{"bucket":"hydra-admin-medium","tier":"Developer","env":"Development","rpm":15,"rps":5},{"bucket":"hydra-admin-medium","tier":"Developer","env":"Production","rpm":15,"rps":5},{"bucket":"hydra-admin-medium","tier":"Developer","env":"Staging","rpm":15,"rps":5},{"bucket":"hydra-admin-medium","tier":"Enterprise","env":"Development","rpm":30,"rps":5},{"bucket":"hydra-admin-medium","tier":"Enterprise","env":"Production","rpm":125,"rps":6},{"bucket":"hydra-admin-medium","tier":"Enterprise","env":"Staging","rpm":30,"rps":5},{"bucket":"hydra-admin-medium","tier":"Growth","env":"Development","rpm":30,"rps":5},{"bucket":"hydra-admin-medium","tier":"Growth","env":"Production","rpm":60,"rps":5},{"bucket":"hydra-admin-medium","tier":"Growth","env":"Staging","rpm":30,"rps":5},{"bucket":"hydra-admin-medium","tier":"Production","env":"Development","rpm":30,"rps":5},{"bucket":"hydra-admin-medium","tier":"Production","env":"Production","rpm":30,"rps":5},{"bucket":"hydra-admin-medium","tier":"Production","env":"Staging","rpm":30,"rps":5},{"bucket":"hydra-public-high","tier":"Developer","env":"Development","rpm":60,"rps":5},{"bucket":"hydra-public-high","tier":"Developer","env":"Production","rpm":60,"rps":5},{"bucket":"hydra-public-high","tier":"Developer","env":"Staging","rpm":60,"rps":5},{"bucket":"hydra-public-high","tier":"Enterprise","env":"Development","rpm":125,"rps":6},{"bucket":"hydra-public-high","tier":"Enterprise","env":"Production","rpm":1800,"rps":73},{"bucket":"hydra-public-high","tier":"Enterprise","env":"Staging","rpm":125,"rps":6},{"bucket":"hydra-public-high","tier":"Growth","env":"Development","rpm":125,"rps":6},{"bucket":"hydra-public-high","tier":"Growth","env":"Production","rpm":600,"rps":25},{"bucket":"hydra-public-high","tier":"Growth","env":"Staging","rpm":125,"rps":6},{"bucket":"hydra-public-high","tier":"Production","env":"Development","rpm":125,"rps":6},{"bucket":"hydra-public-high","tier":"Production","env":"Production","rpm":200,"rps":9},{"bucket":"hydra-public-high","tier":"Production","env":"Staging","rpm":125,"rps":6},{"bucket":"hydra-public-low","tier":"Developer","env":"Development","rpm":15,"rps":5},{"bucket":"hydra-public-low","tier":"Developer","env":"Production","rpm":15,"rps":5},{"bucket":"hydra-public-low","tier":"Developer","env":"Staging","rpm":15,"rps":5},{"bucket":"hydra-public-low","tier":"Enterprise","env":"Development","rpm":30,"rps":5},{"bucket":"hydra-public-low","tier":"Enterprise","env":"Production","rpm":450,"rps":19},{"bucket":"hydra-public-low","tier":"Enterprise","env":"Staging","rpm":30,"rps":5},{"bucket":"hydra-public-low","tier":"Growth","env":"Development","rpm":30,"rps":5},{"bucket":"hydra-public-low","tier":"Growth","env":"Production","rpm":150,"rps":7},{"bucket":"hydra-public-low","tier":"Growth","env":"Staging","rpm":30,"rps":5},{"bucket":"hydra-public-low","tier":"Production","env":"Development","rpm":30,"rps":5},{"bucket":"hydra-public-low","tier":"Production","env":"Production","rpm":45,"rps":5},{"bucket":"hydra-public-low","tier":"Production","env":"Staging","rpm":30,"rps":5},{"bucket":"hydra-public-medium","tier":"Developer","env":"Development","rpm":125,"rps":6},{"bucket":"hydra-public-medium","tier":"Developer","env":"Production","rpm":125,"rps":6},{"bucket":"hydra-public-medium","tier":"Developer","env":"Staging","rpm":125,"rps":6},{"bucket":"hydra-public-medium","tier":"Enterprise","env":"Development","rpm":250,"rps":11},{"bucket":"hydra-public-medium","tier":"Enterprise","env":"Production","rpm":3600,"rps":145},{"bucket":"hydra-public-medium","tier":"Enterprise","env":"Staging","rpm":250,"rps":11},{"bucket":"hydra-public-medium","tier":"Growth","env":"Development","rpm":250,"rps":11},{"bucket":"hydra-public-medium","tier":"Growth","env":"Production","rpm":1200,"rps":49},{"bucket":"hydra-public-medium","tier":"Growth","env":"Staging","rpm":250,"rps":11},{"bucket":"hydra-public-medium","tier":"Production","env":"Development","rpm":250,"rps":11},{"bucket":"hydra-public-medium","tier":"Production","env":"Production","rpm":375,"rps":16},{"bucket":"hydra-public-medium","tier":"Production","env":"Staging","rpm":250,"rps":11},{"bucket":"keto-admin-low","tier":"Developer","env":"Development","rpm":80,"rps":5},{"bucket":"keto-admin-low","tier":"Developer","env":"Production","rpm":80,"rps":5},{"bucket":"keto-admin-low","tier":"Developer","env":"Staging","rpm":80,"rps":5},{"bucket":"keto-admin-low","tier":"Enterprise","env":"Development","rpm":175,"rps":8},{"bucket":"keto-admin-low","tier":"Enterprise","env":"Production","rpm":700,"rps":29},{"bucket":"keto-admin-low","tier":"Enterprise","env":"Staging","rpm":175,"rps":8},{"bucket":"keto-admin-low","tier":"Growth","env":"Development","rpm":175,"rps":8},{"bucket":"keto-admin-low","tier":"Growth","env":"Production","rpm":350,"rps":15},{"bucket":"keto-admin-low","tier":"Growth","env":"Staging","rpm":175,"rps":8},{"bucket":"keto-admin-low","tier":"Production","env":"Development","rpm":175,"rps":8},{"bucket":"keto-admin-low","tier":"Production","env":"Production","rpm":175,"rps":8},{"bucket":"keto-admin-low","tier":"Production","env":"Staging","rpm":175,"rps":8},{"bucket":"keto-admin-medium","tier":"Developer","env":"Development","rpm":125,"rps":6},{"bucket":"keto-admin-medium","tier":"Developer","env":"Production","rpm":125,"rps":6},{"bucket":"keto-admin-medium","tier":"Developer","env":"Staging","rpm":125,"rps":6},{"bucket":"keto-admin-medium","tier":"Enterprise","env":"Development","rpm":250,"rps":11},{"bucket":"keto-admin-medium","tier":"Enterprise","env":"Production","rpm":2200,"rps":89},{"bucket":"keto-admin-medium","tier":"Enterprise","env":"Staging","rpm":250,"rps":11},{"bucket":"keto-admin-medium","tier":"Growth","env":"Development","rpm":250,"rps":11},{"bucket":"keto-admin-medium","tier":"Growth","env":"Production","rpm":1100,"rps":45},{"bucket":"keto-admin-medium","tier":"Growth","env":"Staging","rpm":250,"rps":11},{"bucket":"keto-admin-medium","tier":"Production","env":"Development","rpm":250,"rps":11},{"bucket":"keto-admin-medium","tier":"Production","env":"Production","rpm":550,"rps":23},{"bucket":"keto-admin-medium","tier":"Production","env":"Staging","rpm":250,"rps":11},{"bucket":"keto-public-high","tier":"Developer","env":"Development","rpm":250,"rps":11},{"bucket":"keto-public-high","tier":"Developer","env":"Production","rpm":250,"rps":11},{"bucket":"keto-public-high","tier":"Developer","env":"Staging","rpm":250,"rps":11},{"bucket":"keto-public-high","tier":"Enterprise","env":"Development","rpm":500,"rps":21},{"bucket":"keto-public-high","tier":"Enterprise","env":"Production","rpm":7500,"rps":301},{"bucket":"keto-public-high","tier":"Enterprise","env":"Staging","rpm":500,"rps":21},{"bucket":"keto-public-high","tier":"Growth","env":"Development","rpm":500,"rps":21},{"bucket":"keto-public-high","tier":"Growth","env":"Production","rpm":2500,"rps":101},{"bucket":"keto-public-high","tier":"Growth","env":"Staging","rpm":500,"rps":21},{"bucket":"keto-public-high","tier":"Production","env":"Development","rpm":500,"rps":21},{"bucket":"keto-public-high","tier":"Production","env":"Production","rpm":750,"rps":31},{"bucket":"keto-public-high","tier":"Production","env":"Staging","rpm":500,"rps":21},{"bucket":"kratos-admin-high","tier":"Developer","env":"Development","rpm":40,"rps":5},{"bucket":"kratos-admin-high","tier":"Developer","env":"Production","rpm":40,"rps":5},{"bucket":"kratos-admin-high","tier":"Developer","env":"Staging","rpm":40,"rps":5},{"bucket":"kratos-admin-high","tier":"Enterprise","env":"Development","rpm":80,"rps":5},{"bucket":"kratos-admin-high","tier":"Enterprise","env":"Production","rpm":1000,"rps":41},{"bucket":"kratos-admin-high","tier":"Enterprise","env":"Staging","rpm":80,"rps":5},{"bucket":"kratos-admin-high","tier":"Growth","env":"Development","rpm":80,"rps":5},{"bucket":"kratos-admin-high","tier":"Growth","env":"Production","rpm":500,"rps":21},{"bucket":"kratos-admin-high","tier":"Growth","env":"Staging","rpm":80,"rps":5},{"bucket":"kratos-admin-high","tier":"Production","env":"Development","rpm":80,"rps":5},{"bucket":"kratos-admin-high","tier":"Production","env":"Production","rpm":80,"rps":5},{"bucket":"kratos-admin-high","tier":"Production","env":"Staging","rpm":80,"rps":5},{"bucket":"kratos-admin-low","tier":"Developer","env":"Development","rpm":80,"rps":5},{"bucket":"kratos-admin-low","tier":"Developer","env":"Production","rpm":80,"rps":5},{"bucket":"kratos-admin-low","tier":"Developer","env":"Staging","rpm":80,"rps":5},{"bucket":"kratos-admin-low","tier":"Enterprise","env":"Development","rpm":175,"rps":8},{"bucket":"kratos-admin-low","tier":"Enterprise","env":"Production","rpm":850,"rps":35},{"bucket":"kratos-admin-low","tier":"Enterprise","env":"Staging","rpm":175,"rps":8},{"bucket":"kratos-admin-low","tier":"Growth","env":"Development","rpm":175,"rps":8},{"bucket":"kratos-admin-low","tier":"Growth","env":"Production","rpm":425,"rps":18},{"bucket":"kratos-admin-low","tier":"Growth","env":"Staging","rpm":175,"rps":8},{"bucket":"kratos-admin-low","tier":"Production","env":"Development","rpm":175,"rps":8},{"bucket":"kratos-admin-low","tier":"Production","env":"Production","rpm":175,"rps":8},{"bucket":"kratos-admin-low","tier":"Production","env":"Staging","rpm":175,"rps":8},{"bucket":"kratos-admin-medium","tier":"Developer","env":"Development","rpm":25,"rps":5},{"bucket":"kratos-admin-medium","tier":"Developer","env":"Production","rpm":25,"rps":5},{"bucket":"kratos-admin-medium","tier":"Developer","env":"Staging","rpm":25,"rps":5},{"bucket":"kratos-admin-medium","tier":"Enterprise","env":"Development","rpm":50,"rps":5},{"bucket":"kratos-admin-medium","tier":"Enterprise","env":"Production","rpm":400,"rps":17},{"bucket":"kratos-admin-medium","tier":"Enterprise","env":"Staging","rpm":50,"rps":5},{"bucket":"kratos-admin-medium","tier":"Growth","env":"Development","rpm":50,"rps":5},{"bucket":"kratos-admin-medium","tier":"Growth","env":"Production","rpm":200,"rps":9},{"bucket":"kratos-admin-medium","tier":"Growth","env":"Staging","rpm":50,"rps":5},{"bucket":"kratos-admin-medium","tier":"Production","env":"Development","rpm":50,"rps":5},{"bucket":"kratos-admin-medium","tier":"Production","env":"Production","rpm":90,"rps":5},{"bucket":"kratos-admin-medium","tier":"Production","env":"Staging","rpm":50,"rps":5},{"bucket":"kratos-public-high","tier":"Developer","env":"Development","rpm":450,"rps":19},{"bucket":"kratos-public-high","tier":"Developer","env":"Production","rpm":450,"rps":19},{"bucket":"kratos-public-high","tier":"Developer","env":"Staging","rpm":450,"rps":19},{"bucket":"kratos-public-high","tier":"Enterprise","env":"Development","rpm":900,"rps":37},{"bucket":"kratos-public-high","tier":"Enterprise","env":"Production","rpm":15000,"rps":601},{"bucket":"kratos-public-high","tier":"Enterprise","env":"Staging","rpm":900,"rps":37},{"bucket":"kratos-public-high","tier":"Growth","env":"Development","rpm":900,"rps":37},{"bucket":"kratos-public-high","tier":"Growth","env":"Production","rpm":4500,"rps":181},{"bucket":"kratos-public-high","tier":"Growth","env":"Staging","rpm":900,"rps":37},{"bucket":"kratos-public-high","tier":"Production","env":"Development","rpm":900,"rps":37},{"bucket":"kratos-public-high","tier":"Production","env":"Production","rpm":1400,"rps":57},{"bucket":"kratos-public-high","tier":"Production","env":"Staging","rpm":900,"rps":37},{"bucket":"kratos-public-low","tier":"Developer","env":"Development","rpm":20,"rps":5},{"bucket":"kratos-public-low","tier":"Developer","env":"Production","rpm":20,"rps":5},{"bucket":"kratos-public-low","tier":"Developer","env":"Staging","rpm":20,"rps":5},{"bucket":"kratos-public-low","tier":"Enterprise","env":"Development","rpm":40,"rps":5},{"bucket":"kratos-public-low","tier":"Enterprise","env":"Production","rpm":600,"rps":25},{"bucket":"kratos-public-low","tier":"Enterprise","env":"Staging","rpm":40,"rps":5},{"bucket":"kratos-public-low","tier":"Growth","env":"Development","rpm":40,"rps":5},{"bucket":"kratos-public-low","tier":"Growth","env":"Production","rpm":200,"rps":9},{"bucket":"kratos-public-low","tier":"Growth","env":"Staging","rpm":40,"rps":5},{"bucket":"kratos-public-low","tier":"Production","env":"Development","rpm":40,"rps":5},{"bucket":"kratos-public-low","tier":"Production","env":"Production","rpm":60,"rps":5},{"bucket":"kratos-public-low","tier":"Production","env":"Staging","rpm":40,"rps":5},{"bucket":"kratos-public-medium","tier":"Developer","env":"Development","rpm":100,"rps":5},{"bucket":"kratos-public-medium","tier":"Developer","env":"Production","rpm":100,"rps":5},{"bucket":"kratos-public-medium","tier":"Developer","env":"Staging","rpm":100,"rps":5},{"bucket":"kratos-public-medium","tier":"Enterprise","env":"Development","rpm":200,"rps":9},{"bucket":"kratos-public-medium","tier":"Enterprise","env":"Production","rpm":3000,"rps":121},{"bucket":"kratos-public-medium","tier":"Enterprise","env":"Staging","rpm":200,"rps":9},{"bucket":"kratos-public-medium","tier":"Growth","env":"Development","rpm":200,"rps":9},{"bucket":"kratos-public-medium","tier":"Growth","env":"Production","rpm":1000,"rps":41},{"bucket":"kratos-public-medium","tier":"Growth","env":"Staging","rpm":200,"rps":9},{"bucket":"kratos-public-medium","tier":"Production","env":"Development","rpm":200,"rps":9},{"bucket":"kratos-public-medium","tier":"Production","env":"Production","rpm":325,"rps":14},{"bucket":"kratos-public-medium","tier":"Production","env":"Staging","rpm":200,"rps":9},{"bucket":"polis-public-high","tier":"Developer","env":"Development","rpm":15,"rps":5},{"bucket":"polis-public-high","tier":"Developer","env":"Production","rpm":15,"rps":5},{"bucket":"polis-public-high","tier":"Developer","env":"Staging","rpm":15,"rps":5},{"bucket":"polis-public-high","tier":"Enterprise","env":"Development","rpm":30,"rps":5},{"bucket":"polis-public-high","tier":"Enterprise","env":"Production","rpm":450,"rps":19},{"bucket":"polis-public-high","tier":"Enterprise","env":"Staging","rpm":30,"rps":5},{"bucket":"polis-public-high","tier":"Growth","env":"Development","rpm":30,"rps":5},{"bucket":"polis-public-high","tier":"Growth","env":"Production","rpm":150,"rps":7},{"bucket":"polis-public-high","tier":"Growth","env":"Staging","rpm":30,"rps":5},{"bucket":"polis-public-high","tier":"Production","env":"Development","rpm":30,"rps":5},{"bucket":"polis-public-high","tier":"Production","env":"Production","rpm":45,"rps":5},{"bucket":"polis-public-high","tier":"Production","env":"Staging","rpm":30,"rps":5},{"bucket":"polis-public-medium","tier":"Developer","env":"Development","rpm":20,"rps":5},{"bucket":"polis-public-medium","tier":"Developer","env":"Production","rpm":20,"rps":5},{"bucket":"polis-public-medium","tier":"Developer","env":"Staging","rpm":20,"rps":5},{"bucket":"polis-public-medium","tier":"Enterprise","env":"Development","rpm":40,"rps":5},{"bucket":"polis-public-medium","tier":"Enterprise","env":"Production","rpm":600,"rps":25},{"bucket":"polis-public-medium","tier":"Enterprise","env":"Staging","rpm":40,"rps":5},{"bucket":"polis-public-medium","tier":"Growth","env":"Development","rpm":40,"rps":5},{"bucket":"polis-public-medium","tier":"Growth","env":"Production","rpm":200,"rps":9},{"bucket":"polis-public-medium","tier":"Growth","env":"Staging","rpm":40,"rps":5},{"bucket":"polis-public-medium","tier":"Production","env":"Development","rpm":40,"rps":5},{"bucket":"polis-public-medium","tier":"Production","env":"Production","rpm":60,"rps":5},{"bucket":"polis-public-medium","tier":"Production","env":"Staging","rpm":40,"rps":5}]} \ No newline at end of file