From 0ec2d4ff75ea01689252d32b7239366e504afe05 Mon Sep 17 00:00:00 2001 From: DmitryAnansky Date: Thu, 20 Aug 2026 15:59:18 +0300 Subject: [PATCH 01/19] chore(other): align cafe api description wirh dcr rfc7591 and oauth rfcs6749 --- openapi/cafe.yaml | 28 +++-- .../components/parameters/OAuth2ClientId.yaml | 7 ++ .../responses/OAuth2BadRequest.yaml | 7 ++ .../responses/OAuth2ServerError.yaml | 5 + .../responses/OAuth2Unauthorized.yaml | 14 +++ .../schemas/AuthorizationServerMetadata.yaml | 61 ++++++++++ openapi/components/schemas/OAuth2Client.yaml | 82 ++++++++++--- openapi/components/schemas/OAuthError.yaml | 28 +++++ .../schemas/RegisterClientObject.yaml | 67 ++++++++--- openapi/components/schemas/TokenResponse.yaml | 29 +++++ .../schemas/UpdateClientObject.yaml | 20 ++++ openapi/paths/oauth2_register.yaml | 56 +++++---- openapi/paths/oauth2_register_{clientId}.yaml | 85 ++++++++++++++ openapi/paths/oauth2_revoke.yaml | 58 +++++++++ openapi/paths/oauth2_token.yaml | 110 ++++++++++++++++++ ...well-known_oauth-authorization-server.yaml | 54 +++++++++ redocly.yaml | 2 +- 17 files changed, 646 insertions(+), 67 deletions(-) create mode 100644 openapi/components/parameters/OAuth2ClientId.yaml create mode 100644 openapi/components/responses/OAuth2BadRequest.yaml create mode 100644 openapi/components/responses/OAuth2ServerError.yaml create mode 100644 openapi/components/responses/OAuth2Unauthorized.yaml create mode 100644 openapi/components/schemas/AuthorizationServerMetadata.yaml create mode 100644 openapi/components/schemas/OAuthError.yaml create mode 100644 openapi/components/schemas/TokenResponse.yaml create mode 100644 openapi/components/schemas/UpdateClientObject.yaml create mode 100644 openapi/paths/oauth2_register_{clientId}.yaml create mode 100644 openapi/paths/oauth2_revoke.yaml create mode 100644 openapi/paths/oauth2_token.yaml create mode 100644 openapi/paths/well-known_oauth-authorization-server.yaml diff --git a/openapi/cafe.yaml b/openapi/cafe.yaml index a43275d..159d837 100644 --- a/openapi/cafe.yaml +++ b/openapi/cafe.yaml @@ -41,6 +41,14 @@ paths: $ref: paths/revenue.yaml /oauth2/register: $ref: paths/oauth2_register.yaml + /oauth2/register/{clientId}: + $ref: paths/oauth2_register_{clientId}.yaml + /oauth2/token: + $ref: paths/oauth2_token.yaml + /oauth2/revoke: + $ref: paths/oauth2_revoke.yaml + /.well-known/oauth-authorization-server: + $ref: paths/well-known_oauth-authorization-server.yaml webhooks: order-notification: $ref: webhooks/order-notification.yaml @@ -50,20 +58,18 @@ components: type: oauth2 description: | OAuth2 authorization for API access. The token endpoint accepts `grant_type=authorization_code`, `grant_type=client_credentials`, and `grant_type=refresh_token`. + Standard OAuth2 client libraries can drive these flows unmodified; server capabilities are discoverable from the [RFC 8414 metadata endpoint](https://api.cafe.redocly.com/.well-known/oauth-authorization-server). - ### Differences from the OAuth2 specifications + ### Protocol behavior - A standard OAuth2 client library can drive these flows, with the following to account for. - - Two behaviors do not conform to the specifications: - - - **Errors use RFC 9457 problem+json, not RFC 6749 Section 5.2.** Failures return `application/problem+json` with `type`, `title`, `status`, and `instance`. There is no `error` or `error_description` field, so the standard codes (`invalid_grant`, `invalid_client`, `unsupported_grant_type`) never appear — branch on the HTTP status and `title` instead. A refresh token that is expired, already rotated, or unrecognized returns `400` with a `title` of `Refresh token has expired` or `Invalid refresh token`, where a conformant server would return `error: invalid_grant`. - - **`refresh_token` is not a registrable grant type.** RFC 7591 Section 2 lists it, but `/oauth2/register` accepts only `authorization_code` and `client_credentials` in `grantTypes`. Refreshing requires no registration: holding a refresh token issued to the client is the authorization. A consequence is that refresh capability cannot be disabled per client — every `authorization_code` grant returns a refresh token, so a client intended for a shared or public device cannot be registered without one. - - Two are choices the specifications leave to the server: - - - **Refresh tokens rotate on every use.** A successful refresh retires the token presented and returns a replacement in `refresh_token`, as RFC 6749 Section 6 permits and the OAuth2 Security Best Current Practice recommends. Store the new value; the old one stops working. Refresh tokens expire 30 days after they are issued, and rotation restarts that window. The authorization code flow returns a refresh token with every access token; the client credentials flow returns none (RFC 6749 Section 4.4.3). + - **Errors follow RFC 6749 Section 5.2 and RFC 7591 Section 3.2.2.** The OAuth2 endpoints return `{"error": ..., "error_description": ...}` with the standard codes (`invalid_grant`, `invalid_client`, `unsupported_grant_type`, `invalid_scope`, `invalid_client_metadata`, ...). The rest of the API uses RFC 9457 problem+json. + - **Client authentication.** Both `client_secret_basic` (HTTP Basic per RFC 6749 Section 2.3.1) and `client_secret_post` (credentials in the form body) are accepted at the token and revocation endpoints, but not both in one request. + - **PKCE (RFC 7636) is supported** for the authorization code flow with the `S256` and `plain` challenge methods (`S256` recommended). When an authorization request carries a `code_challenge`, the token exchange requires the matching `code_verifier`. + - **Refresh tokens rotate on every use.** A successful refresh retires the token presented and returns a replacement in `refresh_token`, as RFC 6749 Section 6 permits and RFC 9700 recommends. Store the new value; the old one stops working. Refresh tokens expire 30 days after they are issued, and rotation restarts that window. The authorization code flow returns a refresh token only when the client is registered for the `refresh_token` grant type; the client credentials flow returns none (RFC 6749 Section 4.4.3). + - **Authorization responses carry `iss`** (RFC 9207) alongside `code` and `state`. + - **Tokens can be revoked** at the [revocation endpoint](https://api.cafe.redocly.com/oauth2/revoke) (RFC 7009), and client registrations managed via RFC 7592 using the `registration_access_token`. - **`scope` accepts commas.** The space-delimited form required by RFC 6749 is always accepted and recommended; comma-separated values are additionally tolerated. + oauth2MetadataUrl: https://api.cafe.redocly.com/.well-known/oauth-authorization-server flows: authorizationCode: authorizationUrl: https://api.cafe.redocly.com/oauth2/authorize diff --git a/openapi/components/parameters/OAuth2ClientId.yaml b/openapi/components/parameters/OAuth2ClientId.yaml new file mode 100644 index 0000000..866a5a3 --- /dev/null +++ b/openapi/components/parameters/OAuth2ClientId.yaml @@ -0,0 +1,7 @@ +name: clientId +in: path +required: true +description: The client identifier issued at registration. +schema: + type: string +example: client_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d diff --git a/openapi/components/responses/OAuth2BadRequest.yaml b/openapi/components/responses/OAuth2BadRequest.yaml new file mode 100644 index 0000000..cae087a --- /dev/null +++ b/openapi/components/responses/OAuth2BadRequest.yaml @@ -0,0 +1,7 @@ +description: | + OAuth2 error (RFC 6749 Section 5.2 / RFC 7591 Section 3.2.2). + The `error` field carries the standard OAuth2 error code. +content: + application/json: + schema: + $ref: ../schemas/OAuthError.yaml diff --git a/openapi/components/responses/OAuth2ServerError.yaml b/openapi/components/responses/OAuth2ServerError.yaml new file mode 100644 index 0000000..d4fd6cc --- /dev/null +++ b/openapi/components/responses/OAuth2ServerError.yaml @@ -0,0 +1,5 @@ +description: Unexpected server error, reported with the `server_error` error code. +content: + application/json: + schema: + $ref: ../schemas/OAuthError.yaml diff --git a/openapi/components/responses/OAuth2Unauthorized.yaml b/openapi/components/responses/OAuth2Unauthorized.yaml new file mode 100644 index 0000000..5804af5 --- /dev/null +++ b/openapi/components/responses/OAuth2Unauthorized.yaml @@ -0,0 +1,14 @@ +description: | + Client authentication failed (`error: invalid_client`, RFC 6749 Section 5.2) + or the presented bearer token is invalid (`error: invalid_token`, RFC 6750). + The response carries a `WWW-Authenticate` challenge naming the expected + authentication scheme. +headers: + WWW-Authenticate: + description: Authentication challenge, e.g. `Basic realm="redocly-cafe"` or `Bearer realm="redocly-cafe", error="invalid_token"`. + schema: + type: string +content: + application/json: + schema: + $ref: ../schemas/OAuthError.yaml diff --git a/openapi/components/schemas/AuthorizationServerMetadata.yaml b/openapi/components/schemas/AuthorizationServerMetadata.yaml new file mode 100644 index 0000000..c7aed6f --- /dev/null +++ b/openapi/components/schemas/AuthorizationServerMetadata.yaml @@ -0,0 +1,61 @@ +type: object +description: OAuth2 authorization server metadata per RFC 8414. +properties: + issuer: + type: string + format: uri + description: The authorization server's issuer identifier. + authorization_endpoint: + type: string + format: uri + token_endpoint: + type: string + format: uri + registration_endpoint: + type: string + format: uri + description: Dynamic client registration endpoint (RFC 7591). + revocation_endpoint: + type: string + format: uri + description: Token revocation endpoint (RFC 7009). + scopes_supported: + type: array + items: + type: string + response_types_supported: + type: array + items: + type: string + response_modes_supported: + type: array + items: + type: string + grant_types_supported: + type: array + items: + type: string + token_endpoint_auth_methods_supported: + type: array + items: + type: string + revocation_endpoint_auth_methods_supported: + type: array + items: + type: string + code_challenge_methods_supported: + type: array + description: PKCE code challenge methods supported (RFC 7636). + items: + type: string + authorization_response_iss_parameter_supported: + type: boolean + description: Whether authorization responses carry the `iss` parameter (RFC 9207). + service_documentation: + type: string + format: uri +required: + - issuer + - authorization_endpoint + - token_endpoint + - response_types_supported diff --git a/openapi/components/schemas/OAuth2Client.yaml b/openapi/components/schemas/OAuth2Client.yaml index dd98342..1b53966 100644 --- a/openapi/components/schemas/OAuth2Client.yaml +++ b/openapi/components/schemas/OAuth2Client.yaml @@ -1,36 +1,81 @@ type: object -description: OAuth2 client registration response. Per RFC 7591, includes the client identifier, secret, timestamps, and all registered client metadata. +description: | + OAuth2 client registration response. Contains the fields required by + RFC 7591 Section 3.2.1 in their standard snake_case form, plus this API's + historical camelCase aliases carrying the same values. properties: - clientId: + client_id: type: string description: Client identifier issued by the authorization server. - clientSecret: + client_secret: type: string - description: Client secret issued by the authorization server. - clientIdIssuedAt: + description: Client secret issued by the authorization server. Store it securely. + client_id_issued_at: type: integer format: int64 - description: Time when the client_id is issued, represented as seconds since epoch (RFC7591). - clientSecretExpiresAt: + description: Time the client_id was issued, as seconds since epoch (RFC 7591). + client_secret_expires_at: type: integer format: int64 - description: Time at which the client_secret expires, represented as seconds since epoch. 0 indicates the secret does not expire (RFC 7591). - name: + description: Time the client_secret expires, as seconds since epoch. 0 indicates the secret does not expire (RFC 7591). + registration_client_uri: + type: string + format: uri + description: URL of the client configuration endpoint for managing this registration (RFC 7592). + registration_access_token: + type: string + description: Bearer token for the client configuration endpoint (RFC 7592). Store it securely. + client_name: type: string description: Client name (registered metadata). - redirectUris: + redirect_uris: type: array items: type: string format: uri - description: List of redirect URIs (registered metadata). + description: Registered redirect URIs. + grant_types: + type: array + items: + type: string + enum: + - authorization_code + - client_credentials + - refresh_token + description: Registered grant types. + scope: + type: string + description: Space-separated registered scopes. + clientId: + type: string + description: Alias for `client_id`. + clientSecret: + type: string + description: Alias for `client_secret`. + clientIdIssuedAt: + type: integer + format: int64 + description: Alias for `client_id_issued_at`. + clientSecretExpiresAt: + type: integer + format: int64 + description: Alias for `client_secret_expires_at`. registrationClientUri: type: string format: uri - description: URL of the client configuration endpoint for managing this client registration (RFC 7592). + description: Alias for `registration_client_uri`. registrationAccessToken: type: string - description: Access token to be used at the client configuration endpoint for managing this client registration (RFC 7592). + description: Alias for `registration_access_token`. + name: + type: string + description: Alias for `client_name`. + redirectUris: + type: array + items: + type: string + format: uri + description: Alias for `redirect_uris`. scopes: type: array items: @@ -41,7 +86,7 @@ properties: - orders:read - orders:write - revenue:read - description: List of scopes (registered metadata). + description: Alias for `scope`, as an array instead of a space-separated string. grantTypes: type: array items: @@ -49,8 +94,15 @@ properties: enum: - authorization_code - client_credentials - description: List of grant types (registered metadata). + - refresh_token + description: Alias for `grant_types`. required: + - client_id + - client_secret + - client_id_issued_at + - client_secret_expires_at + - registration_client_uri + - registration_access_token - clientId - clientSecret - clientIdIssuedAt diff --git a/openapi/components/schemas/OAuthError.yaml b/openapi/components/schemas/OAuthError.yaml new file mode 100644 index 0000000..6113c5f --- /dev/null +++ b/openapi/components/schemas/OAuthError.yaml @@ -0,0 +1,28 @@ +type: object +description: | + OAuth2 error response, as defined by RFC 6749 Section 5.2 (token endpoint), + RFC 7591 Section 3.2.2 (registration endpoint), and RFC 7009 (revocation endpoint). + The OAuth2 endpoints return this shape instead of the `application/problem+json` + format used by the rest of the API. +properties: + error: + type: string + description: Machine-readable error code. + enum: + - invalid_request + - invalid_client + - invalid_grant + - unauthorized_client + - unsupported_grant_type + - unsupported_response_type + - invalid_scope + - invalid_client_metadata + - invalid_redirect_uri + - invalid_token + - access_denied + - server_error + error_description: + type: string + description: Human-readable explanation of the error. +required: + - error diff --git a/openapi/components/schemas/RegisterClientObject.yaml b/openapi/components/schemas/RegisterClientObject.yaml index d95876d..4678b6f 100644 --- a/openapi/components/schemas/RegisterClientObject.yaml +++ b/openapi/components/schemas/RegisterClientObject.yaml @@ -1,15 +1,60 @@ type: object +description: | + Client registration metadata per RFC 7591 Section 2. + Standard snake_case metadata names (`client_name`, `redirect_uris`, `grant_types`, `scope`) + and this API's historical camelCase aliases (`name`, `redirectUris`, `grantTypes`, `scopes`) + are both accepted. When both spellings of a field are present, the camelCase one wins. + All fields are optional. properties: + client_name: + type: string + description: Human-readable client name, shown on the authorization consent screen. + redirect_uris: + type: array + items: + type: string + format: uri + description: | + Redirect URIs for the `authorization_code` grant. Compared with exact string + matching at authorization time (RFC 6749 Section 3.1.2.3). Required when the + effective grant types include `authorization_code`. + scope: + type: string + description: Space-separated list of scopes. Defaults to all available scopes. + example: menu:read orders:read orders:write + grant_types: + type: array + items: + type: string + enum: + - authorization_code + - client_credentials + - refresh_token + default: + - authorization_code + - client_credentials + - refresh_token + description: | + Grant types the client may use. A refresh token is issued with `authorization_code` + grant responses only when `refresh_token` is included here; the `client_credentials` + grant never returns one (RFC 6749 Section 4.4.3). + token_endpoint_auth_method: + type: string + enum: + - client_secret_basic + - client_secret_post + description: | + Requested client authentication method for the token endpoint. Informational: + the server accepts both methods for every client. name: type: string - description: Client name. + description: Alias for `client_name`. redirectUris: type: array items: type: string format: uri - default: [] - description: List of redirect URIs (optional, defaults to empty array). + description: Alias for `redirect_uris`. scopes: type: array items: @@ -20,13 +65,7 @@ properties: - orders:read - orders:write - revenue:read - default: - - menu:read - - menu:write - - orders:read - - orders:write - - revenue:read - description: List of scopes. + description: Alias for `scope`, as an array instead of a space-separated string. grantTypes: type: array items: @@ -34,9 +73,5 @@ properties: enum: - authorization_code - client_credentials - default: - - authorization_code - - client_credentials - description: List of grant types. `refresh_token` is not registrable; any client holding a refresh token may present it at the token endpoint. -required: - - name + - refresh_token + description: Alias for `grant_types`. diff --git a/openapi/components/schemas/TokenResponse.yaml b/openapi/components/schemas/TokenResponse.yaml new file mode 100644 index 0000000..feac046 --- /dev/null +++ b/openapi/components/schemas/TokenResponse.yaml @@ -0,0 +1,29 @@ +type: object +description: Access token response per RFC 6749 Section 5.1. +properties: + access_token: + type: string + description: The issued access token (opaque string). + token_type: + type: string + description: Type of the issued token. + const: Bearer + expires_in: + type: integer + description: Access token lifetime in seconds. + example: 3600 + refresh_token: + type: string + description: | + Refresh token, returned for the `authorization_code` grant when the client + is registered for the `refresh_token` grant type, and on every refresh + (tokens rotate on use). Never returned for the `client_credentials` grant + (RFC 6749 Section 4.4.3). + scope: + type: string + description: Space-separated scopes granted to the token. + example: menu:read orders:read +required: + - access_token + - token_type + - expires_in diff --git a/openapi/components/schemas/UpdateClientObject.yaml b/openapi/components/schemas/UpdateClientObject.yaml new file mode 100644 index 0000000..c4fda2c --- /dev/null +++ b/openapi/components/schemas/UpdateClientObject.yaml @@ -0,0 +1,20 @@ +description: | + Client registration update request per RFC 7592 Section 2.2: the full set of + registration metadata, plus the `client_id` of the client being updated. + Omitted metadata fields are reset to their registration defaults. +allOf: + - $ref: ./RegisterClientObject.yaml + - type: object + properties: + client_id: + type: string + description: Must match the client being updated. Credentials cannot be changed through this endpoint. + client_secret: + type: string + description: Optional; if present, must match the client's current secret. + clientId: + type: string + description: Alias for `client_id`. + clientSecret: + type: string + description: Alias for `client_secret`. diff --git a/openapi/paths/oauth2_register.yaml b/openapi/paths/oauth2_register.yaml index 1e00427..bd612a7 100644 --- a/openapi/paths/oauth2_register.yaml +++ b/openapi/paths/oauth2_register.yaml @@ -3,26 +3,34 @@ post: - Authorization summary: Create OAuth2 client description: | - Register a new OAuth2 client for dynamic client registration. - This endpoint implements the Dynamic Client Registration Protocol (RFC 7591), using camelCase field names instead of the RFC's snake_case convention (e.g., `redirectUris` instead of `redirect_uris`, `grantTypes` instead of `grant_types`). - The `name` field is required. Other fields are optional. If not provided: + Register a new OAuth2 client, implementing the Dynamic Client Registration Protocol (RFC 7591). + The standard snake_case metadata names (`client_name`, `redirect_uris`, `grant_types`, `scope`) + are accepted, as are this API's historical camelCase aliases (`name`, `redirectUris`, `grantTypes`, `scopes`), + so off-the-shelf DCR clients work without modification. - - `redirectUris` defaults to an empty array. Note: When using the `authorization_code` grant type, `redirectUris` must be provided (per RFC 7591 Section 2). - - `scopes` defaults to all available scopes (menu:read, menu:write, orders:read, orders:write, revenue:read) - - `grantTypes` defaults to `authorization_code` and `client_credentials` + All fields are optional. If not provided: - These defaults interact: a request that supplies only `name` pairs `authorization_code` with an empty `redirectUris`, which is not a usable combination. - Supply `redirectUris` explicitly to register the `authorization_code` grant, or set `grantTypes` to `client_credentials` alone for a client that needs no redirect URI. + - `scope` defaults to all available scopes (`menu:read menu:write orders:read orders:write revenue:read`) + - `grant_types` defaults to `authorization_code`, `client_credentials`, and `refresh_token` - Refresh tokens require no registration and `refresh_token` is not a value you can register in `grantTypes`. - The token endpoint returns a refresh token alongside every access token it issues for the `authorization_code` grant, and accepts `grant_type=refresh_token` from any client presenting a refresh token issued to it. - The `client_credentials` grant returns no refresh token (RFC 6749 Section 4.4.3); those clients request a new access token with their own credentials instead. + `redirect_uris` is required whenever the effective grant types include `authorization_code` + (RFC 7591 Section 2) — including when `grant_types` is omitted, since the default includes it. + Register with `grant_types: ["client_credentials"]` for a client that needs no redirect URI. - Returns the registered client information per RFC 7591, including: + Refresh token behavior follows the registered grant types: the token endpoint returns a refresh + token with `authorization_code` grant responses only when the client registered the + `refresh_token` grant type. The `client_credentials` grant never returns one (RFC 6749 Section 4.4.3); + those clients request a new access token with their own credentials instead. - - `clientId` and `clientSecret` (must be stored securely) - - `clientIdIssuedAt` and `clientSecretExpiresAt` timestamps - - All registered client metadata (name, redirectUris, scopes, grantTypes) + Returns the registered client information per RFC 7591 Section 3.2.1, including: + + - `client_id` and `client_secret` (must be stored securely) + - `client_id_issued_at` and `client_secret_expires_at` timestamps + - `registration_client_uri` and `registration_access_token` for managing the registration (RFC 7592) + - All registered client metadata + + Errors use the RFC 7591 Section 3.2.2 format: `400` with `error` set to + `invalid_redirect_uri` or `invalid_client_metadata` and a human-readable `error_description`. operationId: registerOAuth2Client security: [] requestBody: @@ -33,6 +41,7 @@ post: $ref: ../components/schemas/RegisterClientObject.yaml examples: RegisterClientObject: + summary: Client credentials only (camelCase aliases) dataValue: name: auth scopes: @@ -44,16 +53,15 @@ post: grantTypes: - client_credentials RegisterClientForAuthorizationCode: + summary: Authorization code with refresh tokens (RFC 7591 names) dataValue: - name: pos-terminal - redirectUris: + client_name: pos-terminal + redirect_uris: - https://api.cafe.redocly.com/callback - scopes: - - menu:read - - orders:read - - orders:write - grantTypes: + scope: menu:read orders:read orders:write + grant_types: - authorization_code + - refresh_token responses: '201': description: OAuth2 client registered successfully. @@ -62,6 +70,6 @@ post: schema: $ref: ../components/schemas/OAuth2Client.yaml '400': - $ref: ../components/responses/BadRequest.yaml + $ref: ../components/responses/OAuth2BadRequest.yaml '500': - $ref: ../components/responses/InternalServerError.yaml + $ref: ../components/responses/OAuth2ServerError.yaml diff --git a/openapi/paths/oauth2_register_{clientId}.yaml b/openapi/paths/oauth2_register_{clientId}.yaml new file mode 100644 index 0000000..34f373c --- /dev/null +++ b/openapi/paths/oauth2_register_{clientId}.yaml @@ -0,0 +1,85 @@ +parameters: + - $ref: ../components/parameters/OAuth2ClientId.yaml +get: + tags: + - Authorization + summary: Retrieve OAuth2 client + description: | + Read the current registration of an OAuth2 client (RFC 7592 Section 2.1). + Authenticate with the `registration_access_token` returned at registration, + as a Bearer token in the `Authorization` header. An invalid or missing token + returns `401` without revealing whether the client exists. + operationId: getOAuth2Client + security: [] + responses: + '200': + description: Current client registration. + content: + application/json: + schema: + $ref: ../components/schemas/OAuth2Client.yaml + '401': + $ref: ../components/responses/OAuth2Unauthorized.yaml + '500': + $ref: ../components/responses/OAuth2ServerError.yaml +put: + tags: + - Authorization + summary: Update OAuth2 client + description: | + Replace the registration metadata of an OAuth2 client (RFC 7592 Section 2.2). + Authenticate with the `registration_access_token` as a Bearer token. + + This is a full replacement, not a merge: omitted metadata fields are reset to + their registration defaults. Include `client_id` matching the client being + updated; if `client_secret` is included it must match the current secret. + Credentials cannot be rotated through this endpoint. + operationId: updateOAuth2Client + security: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: ../components/schemas/UpdateClientObject.yaml + examples: + UpdateClient: + dataValue: + client_id: client_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d + client_name: pos-terminal-v2 + redirect_uris: + - https://api.cafe.redocly.com/callback + scope: menu:read orders:read + grant_types: + - authorization_code + - refresh_token + responses: + '200': + description: Updated client registration. + content: + application/json: + schema: + $ref: ../components/schemas/OAuth2Client.yaml + '400': + $ref: ../components/responses/OAuth2BadRequest.yaml + '401': + $ref: ../components/responses/OAuth2Unauthorized.yaml + '500': + $ref: ../components/responses/OAuth2ServerError.yaml +delete: + tags: + - Authorization + summary: Delete OAuth2 client + description: | + Deprovision an OAuth2 client (RFC 7592 Section 2.3). Authenticate with the + `registration_access_token` as a Bearer token. Deleting a client also + invalidates all of its tokens and pending authorization codes. + operationId: deleteOAuth2Client + security: [] + responses: + '204': + description: Client registration deleted. + '401': + $ref: ../components/responses/OAuth2Unauthorized.yaml + '500': + $ref: ../components/responses/OAuth2ServerError.yaml diff --git a/openapi/paths/oauth2_revoke.yaml b/openapi/paths/oauth2_revoke.yaml new file mode 100644 index 0000000..211cd20 --- /dev/null +++ b/openapi/paths/oauth2_revoke.yaml @@ -0,0 +1,58 @@ +post: + tags: + - Authorization + summary: Revoke token + description: | + OAuth2 token revocation endpoint (RFC 7009). Revokes an access or refresh + token issued to the authenticated client. Access and refresh tokens issued + together are revoked together. + + Authenticate with HTTP Basic or with `client_id` and `client_secret` in the + request body, the same as the token endpoint. + + `token_type_hint` is an optional lookup optimization; both token types are + searched regardless. Per RFC 7009 Section 2.2 the endpoint returns `200` + even when the token is unknown or already revoked, so it cannot be used to + probe for valid tokens. + operationId: revokeToken + security: [] + requestBody: + required: true + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + token: + type: string + description: The access or refresh token to revoke. + token_type_hint: + type: string + enum: + - access_token + - refresh_token + description: Optional hint about the token type. + client_id: + type: string + description: Client identifier. Omit when authenticating with HTTP Basic. + client_secret: + type: string + description: Client secret. Omit when authenticating with HTTP Basic. + required: + - token + examples: + RevokeAccessToken: + dataValue: + token: YOUR_ACCESS_TOKEN + token_type_hint: access_token + client_id: client_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d + client_secret: YOUR_CLIENT_SECRET + responses: + '200': + description: The token has been revoked or was already invalid. + '400': + $ref: ../components/responses/OAuth2BadRequest.yaml + '401': + $ref: ../components/responses/OAuth2Unauthorized.yaml + '500': + $ref: ../components/responses/OAuth2ServerError.yaml diff --git a/openapi/paths/oauth2_token.yaml b/openapi/paths/oauth2_token.yaml new file mode 100644 index 0000000..060fd87 --- /dev/null +++ b/openapi/paths/oauth2_token.yaml @@ -0,0 +1,110 @@ +post: + tags: + - Authorization + summary: Create access token + description: | + OAuth2 token endpoint (RFC 6749 Section 3.2). Supports the + `authorization_code`, `client_credentials`, and `refresh_token` grant types. + + **Client authentication.** Authenticate with HTTP Basic + (`Authorization: Basic base64(client_id:client_secret)`) or with `client_id` + and `client_secret` in the request body — not both at once (RFC 6749 Section 2.3.1). + + **PKCE (RFC 7636).** When the authorization request included a + `code_challenge`, the matching `code_verifier` is required here; a mismatch + or a `code_verifier` for a code issued without a challenge is rejected with + `invalid_grant`. `S256` and `plain` challenge methods are supported. + + **Refresh tokens.** Rotate on every use: a successful `refresh_token` grant + retires the presented token and returns a replacement. Refresh tokens expire + 30 days after issuance; rotation restarts that window. The + `authorization_code` grant returns a refresh token only when the client is + registered for the `refresh_token` grant type; the `client_credentials` + grant never returns one (RFC 6749 Section 4.4.3). + + **Scopes.** `scope` is space-separated per RFC 6749 (comma-separated values + are additionally tolerated) and must be a subset of the scopes previously + granted (authorization code or refresh token) or registered (client credentials). + + Errors follow RFC 6749 Section 5.2: `400` (or `401` for `invalid_client`) + with the standard `error` codes such as `invalid_grant`, `invalid_client`, + `unauthorized_client`, `unsupported_grant_type`, and `invalid_scope`. + operationId: createAccessToken + security: [] + requestBody: + required: true + content: + application/x-www-form-urlencoded: + schema: + type: object + properties: + grant_type: + type: string + enum: + - authorization_code + - client_credentials + - refresh_token + description: The OAuth2 grant type. + client_id: + type: string + description: Client identifier. Omit when authenticating with HTTP Basic. + client_secret: + type: string + description: Client secret. Omit when authenticating with HTTP Basic. + code: + type: string + description: The authorization code (required for `authorization_code`). + redirect_uri: + type: string + format: uri + description: Must match the redirect URI used in the authorization request (required for `authorization_code`). + code_verifier: + type: string + minLength: 43 + maxLength: 128 + description: PKCE code verifier (RFC 7636); required when the authorization request included a `code_challenge`. + refresh_token: + type: string + description: The refresh token (required for `refresh_token`). + scope: + type: string + description: Space-separated scopes to request; defaults to the previously granted or registered scopes. + required: + - grant_type + examples: + ClientCredentials: + summary: Client credentials grant + dataValue: + grant_type: client_credentials + client_id: client_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d + client_secret: YOUR_CLIENT_SECRET + scope: menu:read orders:read + AuthorizationCode: + summary: Authorization code exchange with PKCE + dataValue: + grant_type: authorization_code + client_id: client_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d + client_secret: YOUR_CLIENT_SECRET + code: AUTHORIZATION_CODE + redirect_uri: https://api.cafe.redocly.com/callback + code_verifier: dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk + RefreshToken: + summary: Refresh token grant + dataValue: + grant_type: refresh_token + client_id: client_1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d + client_secret: YOUR_CLIENT_SECRET + refresh_token: YOUR_REFRESH_TOKEN + responses: + '200': + description: Token issued successfully. The response is not cacheable (it is served with `Cache-Control` set to `no-store`). + content: + application/json: + schema: + $ref: ../components/schemas/TokenResponse.yaml + '400': + $ref: ../components/responses/OAuth2BadRequest.yaml + '401': + $ref: ../components/responses/OAuth2Unauthorized.yaml + '500': + $ref: ../components/responses/OAuth2ServerError.yaml diff --git a/openapi/paths/well-known_oauth-authorization-server.yaml b/openapi/paths/well-known_oauth-authorization-server.yaml new file mode 100644 index 0000000..d9d8e02 --- /dev/null +++ b/openapi/paths/well-known_oauth-authorization-server.yaml @@ -0,0 +1,54 @@ +get: + tags: + - Authorization + summary: Retrieve authorization server metadata + description: | + OAuth2 authorization server metadata (RFC 8414). Lists the authorization, + token, registration, and revocation endpoints along with the supported + scopes, grant types, client authentication methods, and PKCE code challenge + methods, so clients can discover the server's capabilities instead of + hard-coding them. + operationId: getAuthorizationServerMetadata + security: [] + responses: + '200': + description: Authorization server metadata. + content: + application/json: + schema: + $ref: ../components/schemas/AuthorizationServerMetadata.yaml + examples: + Metadata: + dataValue: + issuer: https://api.cafe.redocly.com + authorization_endpoint: https://api.cafe.redocly.com/oauth2/authorize + token_endpoint: https://api.cafe.redocly.com/oauth2/token + registration_endpoint: https://api.cafe.redocly.com/oauth2/register + revocation_endpoint: https://api.cafe.redocly.com/oauth2/revoke + scopes_supported: + - menu:read + - menu:write + - orders:read + - orders:write + - revenue:read + response_types_supported: + - code + response_modes_supported: + - query + grant_types_supported: + - authorization_code + - client_credentials + - refresh_token + token_endpoint_auth_methods_supported: + - client_secret_basic + - client_secret_post + revocation_endpoint_auth_methods_supported: + - client_secret_basic + - client_secret_post + code_challenge_methods_supported: + - S256 + - plain + authorization_response_iss_parameter_supported: true + service_documentation: https://cafe.redocly.com/openapi/cafe + '500': + $ref: ../components/responses/OAuth2ServerError.yaml diff --git a/redocly.yaml b/redocly.yaml index da0e796..c7373f6 100644 --- a/redocly.yaml +++ b/redocly.yaml @@ -14,7 +14,7 @@ apis: assertions: defined: true assertions: - pattern: /^(Create|Get|Retrieve|Replace|Update|Delete|Login|Send|Callback|Cancel|List|Partially update).*[^\.]$/ + pattern: /^(Create|Get|Retrieve|Replace|Update|Delete|Login|Send|Callback|Cancel|List|Partially update|Revoke).*[^\.]$/ logo: srcSet: "./images/cafe-logo-black.svg light, ./images/cafe-logo-white.svg dark" From cd3b893f608cdebe33f61720b813df7e20cd0da3 Mon Sep 17 00:00:00 2001 From: DmitryAnansky Date: Thu, 20 Aug 2026 18:24:53 +0300 Subject: [PATCH 02/19] fix(other): registerClientObject accepts token_endpoint_auth_method missing --- openapi/components/schemas/OAuth2Client.yaml | 17 +++++++++++++++++ .../schemas/RegisterClientObject.yaml | 13 +++++++++++-- openapi/paths/oauth2_register.yaml | 1 + 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/openapi/components/schemas/OAuth2Client.yaml b/openapi/components/schemas/OAuth2Client.yaml index 1b53966..d35a35b 100644 --- a/openapi/components/schemas/OAuth2Client.yaml +++ b/openapi/components/schemas/OAuth2Client.yaml @@ -46,6 +46,15 @@ properties: scope: type: string description: Space-separated registered scopes. + token_endpoint_auth_method: + type: string + enum: + - client_secret_basic + - client_secret_post + description: | + Registered token endpoint authentication method. Defaults to `client_secret_basic` + when not requested at registration (RFC 7591 Section 2). Informational: the token + and revocation endpoints accept both methods for every client regardless. clientId: type: string description: Alias for `client_id`. @@ -96,6 +105,12 @@ properties: - client_credentials - refresh_token description: Alias for `grant_types`. + tokenEndpointAuthMethod: + type: string + enum: + - client_secret_basic + - client_secret_post + description: Alias for `token_endpoint_auth_method`. required: - client_id - client_secret @@ -103,9 +118,11 @@ required: - client_secret_expires_at - registration_client_uri - registration_access_token + - token_endpoint_auth_method - clientId - clientSecret - clientIdIssuedAt - clientSecretExpiresAt - registrationClientUri - registrationAccessToken + - tokenEndpointAuthMethod diff --git a/openapi/components/schemas/RegisterClientObject.yaml b/openapi/components/schemas/RegisterClientObject.yaml index 4678b6f..75be1d2 100644 --- a/openapi/components/schemas/RegisterClientObject.yaml +++ b/openapi/components/schemas/RegisterClientObject.yaml @@ -43,9 +43,12 @@ properties: enum: - client_secret_basic - client_secret_post + default: client_secret_basic description: | - Requested client authentication method for the token endpoint. Informational: - the server accepts both methods for every client. + Requested client authentication method for the token endpoint, registered as + client metadata and echoed in registration responses. Defaults to + `client_secret_basic` (RFC 7591 Section 2). Informational: the token and + revocation endpoints accept both methods for every client regardless. name: type: string description: Alias for `client_name`. @@ -75,3 +78,9 @@ properties: - client_credentials - refresh_token description: Alias for `grant_types`. + tokenEndpointAuthMethod: + type: string + enum: + - client_secret_basic + - client_secret_post + description: Alias for `token_endpoint_auth_method`. diff --git a/openapi/paths/oauth2_register.yaml b/openapi/paths/oauth2_register.yaml index bd612a7..a8834f2 100644 --- a/openapi/paths/oauth2_register.yaml +++ b/openapi/paths/oauth2_register.yaml @@ -12,6 +12,7 @@ post: - `scope` defaults to all available scopes (`menu:read menu:write orders:read orders:write revenue:read`) - `grant_types` defaults to `authorization_code`, `client_credentials`, and `refresh_token` + - `token_endpoint_auth_method` defaults to `client_secret_basic` (RFC 7591 Section 2); both supported methods are accepted at the token endpoint regardless of the registered value `redirect_uris` is required whenever the effective grant types include `authorization_code` (RFC 7591 Section 2) — including when `grant_types` is omitted, since the default includes it. From b88e51e76a87038d8099a750fea1e2ffad0a3696 Mon Sep 17 00:00:00 2001 From: DmitryAnansky Date: Thu, 20 Aug 2026 18:37:59 +0300 Subject: [PATCH 03/19] chore(other): changes after review --- openapi/cafe.yaml | 7 +++++++ openapi/paths/oauth2_register_{clientId}.yaml | 9 ++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/openapi/cafe.yaml b/openapi/cafe.yaml index 159d837..a723a29 100644 --- a/openapi/cafe.yaml +++ b/openapi/cafe.yaml @@ -94,3 +94,10 @@ components: name: X-API-Key in: header description: API key for internal operations. + RegistrationAccessToken: + type: http + scheme: bearer + description: >- + Registration access token from the client registration response + (RFC 7592), sent as a Bearer token to authenticate requests to the + client configuration endpoint (`/oauth2/register/{clientId}`). diff --git a/openapi/paths/oauth2_register_{clientId}.yaml b/openapi/paths/oauth2_register_{clientId}.yaml index 34f373c..f0abebc 100644 --- a/openapi/paths/oauth2_register_{clientId}.yaml +++ b/openapi/paths/oauth2_register_{clientId}.yaml @@ -10,7 +10,8 @@ get: as a Bearer token in the `Authorization` header. An invalid or missing token returns `401` without revealing whether the client exists. operationId: getOAuth2Client - security: [] + security: + - RegistrationAccessToken: [] responses: '200': description: Current client registration. @@ -35,7 +36,8 @@ put: updated; if `client_secret` is included it must match the current secret. Credentials cannot be rotated through this endpoint. operationId: updateOAuth2Client - security: [] + security: + - RegistrationAccessToken: [] requestBody: required: true content: @@ -75,7 +77,8 @@ delete: `registration_access_token` as a Bearer token. Deleting a client also invalidates all of its tokens and pending authorization codes. operationId: deleteOAuth2Client - security: [] + security: + - RegistrationAccessToken: [] responses: '204': description: Client registration deleted. From 6f4cae9a29f6eba18d1e7542571b951d99f2a04a Mon Sep 17 00:00:00 2001 From: DmitryAnansky Date: Thu, 20 Aug 2026 18:49:06 +0300 Subject: [PATCH 04/19] fix(other): the client must include client_id in the request --- openapi/components/schemas/UpdateClientObject.yaml | 10 ++++++++-- openapi/paths/oauth2_register_{clientId}.yaml | 5 +++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/openapi/components/schemas/UpdateClientObject.yaml b/openapi/components/schemas/UpdateClientObject.yaml index c4fda2c..e9dd023 100644 --- a/openapi/components/schemas/UpdateClientObject.yaml +++ b/openapi/components/schemas/UpdateClientObject.yaml @@ -1,6 +1,7 @@ description: | Client registration update request per RFC 7592 Section 2.2: the full set of - registration metadata, plus the `client_id` of the client being updated. + registration metadata, plus the `client_id` of the client being updated, + which is required (either as `client_id` or its `clientId` alias). Omitted metadata fields are reset to their registration defaults. allOf: - $ref: ./RegisterClientObject.yaml @@ -8,7 +9,7 @@ allOf: properties: client_id: type: string - description: Must match the client being updated. Credentials cannot be changed through this endpoint. + description: Required (RFC 7592 Section 2.2); must match the client being updated. Credentials cannot be changed through this endpoint. client_secret: type: string description: Optional; if present, must match the client's current secret. @@ -18,3 +19,8 @@ allOf: clientSecret: type: string description: Alias for `client_secret`. +anyOf: + - required: + - client_id + - required: + - clientId diff --git a/openapi/paths/oauth2_register_{clientId}.yaml b/openapi/paths/oauth2_register_{clientId}.yaml index f0abebc..38239da 100644 --- a/openapi/paths/oauth2_register_{clientId}.yaml +++ b/openapi/paths/oauth2_register_{clientId}.yaml @@ -32,8 +32,9 @@ put: Authenticate with the `registration_access_token` as a Bearer token. This is a full replacement, not a merge: omitted metadata fields are reset to - their registration defaults. Include `client_id` matching the client being - updated; if `client_secret` is included it must match the current secret. + their registration defaults. The request must include `client_id` (or the + `clientId` alias) matching the client being updated (RFC 7592 Section 2.2); + if `client_secret` is included it must match the current secret. Credentials cannot be rotated through this endpoint. operationId: updateOAuth2Client security: From 4e514e2154833baa2e02a36541c7f833f2f6f419 Mon Sep 17 00:00:00 2001 From: DmitryAnansky Date: Thu, 20 Aug 2026 19:04:58 +0300 Subject: [PATCH 05/19] fix(other): redirect_uris is not marked as required for authorization_code --- .redocly.lint-ignore.yaml | 6 +++ .../schemas/RegisterClientObject.yaml | 50 ++++++++++++++++++- 2 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 .redocly.lint-ignore.yaml diff --git a/.redocly.lint-ignore.yaml b/.redocly.lint-ignore.yaml new file mode 100644 index 0000000..570447d --- /dev/null +++ b/.redocly.lint-ignore.yaml @@ -0,0 +1,6 @@ +# This file instructs Redocly's linter to ignore the rules contained for specific parts of your API. +# See https://redocly.com/docs/cli/ for more information. +openapi/paths/oauth2_register_{clientId}.yaml: + no-invalid-media-type-examples: + - >- + #/put/requestBody/content/application~1json/examples/UpdateClient/dataValue/client_id diff --git a/openapi/components/schemas/RegisterClientObject.yaml b/openapi/components/schemas/RegisterClientObject.yaml index 75be1d2..51b8b16 100644 --- a/openapi/components/schemas/RegisterClientObject.yaml +++ b/openapi/components/schemas/RegisterClientObject.yaml @@ -4,7 +4,9 @@ description: | Standard snake_case metadata names (`client_name`, `redirect_uris`, `grant_types`, `scope`) and this API's historical camelCase aliases (`name`, `redirectUris`, `grantTypes`, `scopes`) are both accepted. When both spellings of a field are present, the camelCase one wins. - All fields are optional. + All fields are optional, with one conditional exception enforced by the schema below: + `redirect_uris` is required whenever the effective grant types include `authorization_code` — + including when `grant_types` is omitted, since the default includes it (RFC 7591 Section 2). properties: client_name: type: string @@ -84,3 +86,49 @@ properties: - client_secret_basic - client_secret_post description: Alias for `token_endpoint_auth_method`. +# RFC 7591 Section 2: redirect-based grants require redirect URIs. The condition +# mirrors the server's alias handling — grantTypes wins over grant_types when +# both are present, and omitting both applies the default, which includes +# authorization_code. +if: + anyOf: + - required: + - grantTypes + properties: + grantTypes: + contains: + const: authorization_code + - allOf: + - not: + required: + - grantTypes + properties: + grantTypes: {} + - required: + - grant_types + properties: + grant_types: + contains: + const: authorization_code + - not: + anyOf: + - required: + - grantTypes + properties: + grantTypes: {} + - required: + - grant_types + properties: + grant_types: {} +then: + anyOf: + - required: + - redirect_uris + properties: + redirect_uris: + minItems: 1 + - required: + - redirectUris + properties: + redirectUris: + minItems: 1 From 1c60d01f79688a5ee22330e6ea29d7934918b79b Mon Sep 17 00:00:00 2001 From: DmitryAnansky Date: Fri, 21 Aug 2026 12:13:25 +0300 Subject: [PATCH 06/19] chore(other): update description enumerating standard metadata --- openapi/components/schemas/RegisterClientObject.yaml | 7 ++++--- openapi/paths/oauth2_register.yaml | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/openapi/components/schemas/RegisterClientObject.yaml b/openapi/components/schemas/RegisterClientObject.yaml index 51b8b16..0f5b71a 100644 --- a/openapi/components/schemas/RegisterClientObject.yaml +++ b/openapi/components/schemas/RegisterClientObject.yaml @@ -1,9 +1,10 @@ type: object description: | Client registration metadata per RFC 7591 Section 2. - Standard snake_case metadata names (`client_name`, `redirect_uris`, `grant_types`, `scope`) - and this API's historical camelCase aliases (`name`, `redirectUris`, `grantTypes`, `scopes`) - are both accepted. When both spellings of a field are present, the camelCase one wins. + Standard snake_case metadata names (`client_name`, `redirect_uris`, `grant_types`, `scope`, + `token_endpoint_auth_method`) and this API's camelCase aliases (`name`, `redirectUris`, + `grantTypes`, `scopes`, `tokenEndpointAuthMethod`) are both accepted. + When both spellings of a field are present, the camelCase one wins. All fields are optional, with one conditional exception enforced by the schema below: `redirect_uris` is required whenever the effective grant types include `authorization_code` — including when `grant_types` is omitted, since the default includes it (RFC 7591 Section 2). diff --git a/openapi/paths/oauth2_register.yaml b/openapi/paths/oauth2_register.yaml index a8834f2..c73cd87 100644 --- a/openapi/paths/oauth2_register.yaml +++ b/openapi/paths/oauth2_register.yaml @@ -4,8 +4,8 @@ post: summary: Create OAuth2 client description: | Register a new OAuth2 client, implementing the Dynamic Client Registration Protocol (RFC 7591). - The standard snake_case metadata names (`client_name`, `redirect_uris`, `grant_types`, `scope`) - are accepted, as are this API's historical camelCase aliases (`name`, `redirectUris`, `grantTypes`, `scopes`), + The standard snake_case metadata names (`client_name`, `redirect_uris`, `grant_types`, `scope`, `token_endpoint_auth_method`) + are accepted, as are this API's camelCase aliases (`name`, `redirectUris`, `grantTypes`, `scopes`, `tokenEndpointAuthMethod`), so off-the-shelf DCR clients work without modification. All fields are optional. If not provided: From 7621def9194c92051bc34df1bbfecd50cadb217d Mon Sep 17 00:00:00 2001 From: DmitryAnansky Date: Fri, 21 Aug 2026 12:30:18 +0300 Subject: [PATCH 07/19] chore(other): changes after review --- openapi/components/schemas/RegisterClientObject.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/openapi/components/schemas/RegisterClientObject.yaml b/openapi/components/schemas/RegisterClientObject.yaml index 0f5b71a..e857243 100644 --- a/openapi/components/schemas/RegisterClientObject.yaml +++ b/openapi/components/schemas/RegisterClientObject.yaml @@ -24,6 +24,7 @@ properties: scope: type: string description: Space-separated list of scopes. Defaults to all available scopes. + default: menu:read menu:write orders:read orders:write revenue:read example: menu:read orders:read orders:write grant_types: type: array From 899137836b034d9b67cb35503542cb213ca3c26b Mon Sep 17 00:00:00 2001 From: DmitryAnansky Date: Fri, 21 Aug 2026 12:44:05 +0300 Subject: [PATCH 08/19] chore(other): update pattern --- openapi/components/schemas/RegisterClientObject.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/openapi/components/schemas/RegisterClientObject.yaml b/openapi/components/schemas/RegisterClientObject.yaml index e857243..c7d18ae 100644 --- a/openapi/components/schemas/RegisterClientObject.yaml +++ b/openapi/components/schemas/RegisterClientObject.yaml @@ -25,6 +25,7 @@ properties: type: string description: Space-separated list of scopes. Defaults to all available scopes. default: menu:read menu:write orders:read orders:write revenue:read + pattern: '^(menu:read|menu:write|orders:read|orders:write|revenue:read)( (menu:read|menu:write|orders:read|orders:write|revenue:read))*$' example: menu:read orders:read orders:write grant_types: type: array From 85b9d26d536faab0b1874e4c333d8aea7aec453e Mon Sep 17 00:00:00 2001 From: DmitryAnansky Date: Fri, 21 Aug 2026 13:23:47 +0300 Subject: [PATCH 09/19] chore(other): changes after review --- @theme/ext/use-configure-replay.ts | 11 +-- openapi/components/schemas/OAuth2Client.yaml | 82 +++----------------- openapi/paths/oauth2_register.yaml | 1 + 3 files changed, 19 insertions(+), 75 deletions(-) diff --git a/@theme/ext/use-configure-replay.ts b/@theme/ext/use-configure-replay.ts index aaee45a..2774f23 100644 --- a/@theme/ext/use-configure-replay.ts +++ b/@theme/ext/use-configure-replay.ts @@ -28,16 +28,17 @@ type ClientCredentials = { clientId: string; clientSecret: string }; let clientCredentialsPromise: Promise | null = null; async function registerClient(): Promise { + // Standard RFC 7591 dynamic client registration request and response fields. const registerResponse = await fetch(`${BASE_URL}/oauth2/register`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ - name: CLIENT_NAME, - redirectUris: [`${BASE_URL}/callback`], - scopes: SCOPES, - grantTypes: ['client_credentials'], + client_name: CLIENT_NAME, + redirect_uris: [`${BASE_URL}/callback`], + scope: SCOPES.join(' '), + grant_types: ['client_credentials'], }), }); @@ -45,7 +46,7 @@ async function registerClient(): Promise { throw new Error(`Client registration failed with status ${registerResponse.status}`); } - const { clientId, clientSecret } = await registerResponse.json(); + const { client_id: clientId, client_secret: clientSecret } = await registerResponse.json(); return { clientId, clientSecret }; } diff --git a/openapi/components/schemas/OAuth2Client.yaml b/openapi/components/schemas/OAuth2Client.yaml index d35a35b..c8afc65 100644 --- a/openapi/components/schemas/OAuth2Client.yaml +++ b/openapi/components/schemas/OAuth2Client.yaml @@ -1,8 +1,8 @@ type: object description: | - OAuth2 client registration response. Contains the fields required by - RFC 7591 Section 3.2.1 in their standard snake_case form, plus this API's - historical camelCase aliases carrying the same values. + OAuth2 client information response per RFC 7591 Section 3.2.1, using the + standard snake_case field names. Returned by the registration endpoint and + the RFC 7592 client configuration endpoint. properties: client_id: type: string @@ -13,21 +13,21 @@ properties: client_id_issued_at: type: integer format: int64 - description: Time the client_id was issued, as seconds since epoch (RFC 7591). + description: Time when the client_id is issued, represented as seconds since epoch (RFC 7591). client_secret_expires_at: type: integer format: int64 - description: Time the client_secret expires, as seconds since epoch. 0 indicates the secret does not expire (RFC 7591). + description: Time at which the client_secret expires, represented as seconds since epoch. 0 indicates the secret does not expire (RFC 7591). registration_client_uri: type: string format: uri - description: URL of the client configuration endpoint for managing this registration (RFC 7592). + description: URL of the client configuration endpoint for managing this client registration (RFC 7592). registration_access_token: type: string description: Bearer token for the client configuration endpoint (RFC 7592). Store it securely. client_name: type: string - description: Client name (registered metadata). + description: Client name (registered metadata). Omitted for clients registered without a name. redirect_uris: type: array items: @@ -55,62 +55,8 @@ properties: Registered token endpoint authentication method. Defaults to `client_secret_basic` when not requested at registration (RFC 7591 Section 2). Informational: the token and revocation endpoints accept both methods for every client regardless. - clientId: - type: string - description: Alias for `client_id`. - clientSecret: - type: string - description: Alias for `client_secret`. - clientIdIssuedAt: - type: integer - format: int64 - description: Alias for `client_id_issued_at`. - clientSecretExpiresAt: - type: integer - format: int64 - description: Alias for `client_secret_expires_at`. - registrationClientUri: - type: string - format: uri - description: Alias for `registration_client_uri`. - registrationAccessToken: - type: string - description: Alias for `registration_access_token`. - name: - type: string - description: Alias for `client_name`. - redirectUris: - type: array - items: - type: string - format: uri - description: Alias for `redirect_uris`. - scopes: - type: array - items: - type: string - enum: - - menu:read - - menu:write - - orders:read - - orders:write - - revenue:read - description: Alias for `scope`, as an array instead of a space-separated string. - grantTypes: - type: array - items: - type: string - enum: - - authorization_code - - client_credentials - - refresh_token - description: Alias for `grant_types`. - tokenEndpointAuthMethod: - type: string - enum: - - client_secret_basic - - client_secret_post - description: Alias for `token_endpoint_auth_method`. +# client_name is the only conditional field: it is omitted for clients +# registered without a name. Everything else is always returned. required: - client_id - client_secret @@ -118,11 +64,7 @@ required: - client_secret_expires_at - registration_client_uri - registration_access_token + - redirect_uris + - grant_types + - scope - token_endpoint_auth_method - - clientId - - clientSecret - - clientIdIssuedAt - - clientSecretExpiresAt - - registrationClientUri - - registrationAccessToken - - tokenEndpointAuthMethod diff --git a/openapi/paths/oauth2_register.yaml b/openapi/paths/oauth2_register.yaml index c73cd87..2b7af4b 100644 --- a/openapi/paths/oauth2_register.yaml +++ b/openapi/paths/oauth2_register.yaml @@ -7,6 +7,7 @@ post: The standard snake_case metadata names (`client_name`, `redirect_uris`, `grant_types`, `scope`, `token_endpoint_auth_method`) are accepted, as are this API's camelCase aliases (`name`, `redirectUris`, `grantTypes`, `scopes`, `tokenEndpointAuthMethod`), so off-the-shelf DCR clients work without modification. + Responses use only the standard snake_case names (RFC 7591 Section 3.2.1). All fields are optional. If not provided: From a56d098be606c29e4d46939487845bebf81447ac Mon Sep 17 00:00:00 2001 From: DmitryAnansky Date: Fri, 21 Aug 2026 16:38:54 +0300 Subject: [PATCH 10/19] chore(other): changes after review --- .../schemas/RegisterClientObject.yaml | 38 +++++++++---------- .../schemas/UpdateClientObject.yaml | 4 +- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/openapi/components/schemas/RegisterClientObject.yaml b/openapi/components/schemas/RegisterClientObject.yaml index c7d18ae..ad38580 100644 --- a/openapi/components/schemas/RegisterClientObject.yaml +++ b/openapi/components/schemas/RegisterClientObject.yaml @@ -4,7 +4,7 @@ description: | Standard snake_case metadata names (`client_name`, `redirect_uris`, `grant_types`, `scope`, `token_endpoint_auth_method`) and this API's camelCase aliases (`name`, `redirectUris`, `grantTypes`, `scopes`, `tokenEndpointAuthMethod`) are both accepted. - When both spellings of a field are present, the camelCase one wins. + When both spellings of a field are present, the standard snake_case one takes precedence. All fields are optional, with one conditional exception enforced by the schema below: `redirect_uris` is required whenever the effective grant types include `authorization_code` — including when `grant_types` is omitted, since the default includes it (RFC 7591 Section 2). @@ -56,13 +56,13 @@ properties: revocation endpoints accept both methods for every client regardless. name: type: string - description: Alias for `client_name`. + description: Alias for `client_name`; used when `client_name` is absent. redirectUris: type: array items: type: string format: uri - description: Alias for `redirect_uris`. + description: Alias for `redirect_uris`; used when `redirect_uris` is absent. scopes: type: array items: @@ -73,7 +73,7 @@ properties: - orders:read - orders:write - revenue:read - description: Alias for `scope`, as an array instead of a space-separated string. + description: Alias for `scope`, as an array instead of a space-separated string; used when `scope` is absent. grantTypes: type: array items: @@ -82,47 +82,47 @@ properties: - authorization_code - client_credentials - refresh_token - description: Alias for `grant_types`. + description: Alias for `grant_types`; used when `grant_types` is absent. tokenEndpointAuthMethod: type: string enum: - client_secret_basic - client_secret_post - description: Alias for `token_endpoint_auth_method`. + description: Alias for `token_endpoint_auth_method`; used when `token_endpoint_auth_method` is absent. # RFC 7591 Section 2: redirect-based grants require redirect URIs. The condition -# mirrors the server's alias handling — grantTypes wins over grant_types when -# both are present, and omitting both applies the default, which includes -# authorization_code. +# mirrors the server's alias handling — the standard grant_types wins over the +# grantTypes alias when both are present, and omitting both applies the default, +# which includes authorization_code. if: anyOf: - required: - - grantTypes + - grant_types properties: - grantTypes: + grant_types: contains: const: authorization_code - allOf: - not: required: - - grantTypes + - grant_types properties: - grantTypes: {} + grant_types: {} - required: - - grant_types + - grantTypes properties: - grant_types: + grantTypes: contains: const: authorization_code - not: anyOf: - - required: - - grantTypes - properties: - grantTypes: {} - required: - grant_types properties: grant_types: {} + - required: + - grantTypes + properties: + grantTypes: {} then: anyOf: - required: diff --git a/openapi/components/schemas/UpdateClientObject.yaml b/openapi/components/schemas/UpdateClientObject.yaml index e9dd023..7c7325f 100644 --- a/openapi/components/schemas/UpdateClientObject.yaml +++ b/openapi/components/schemas/UpdateClientObject.yaml @@ -15,10 +15,10 @@ allOf: description: Optional; if present, must match the client's current secret. clientId: type: string - description: Alias for `client_id`. + description: Alias for `client_id`; used when `client_id` is absent. clientSecret: type: string - description: Alias for `client_secret`. + description: Alias for `client_secret`; used when `client_secret` is absent. anyOf: - required: - client_id From c264ace478aef812c703a3f374f8744e9fe3eabc Mon Sep 17 00:00:00 2001 From: DmitryAnansky Date: Fri, 21 Aug 2026 16:55:07 +0300 Subject: [PATCH 11/19] chore(other): changes after review --- @theme/ext/use-configure-replay.ts | 1 - openapi/paths/oauth2_register.yaml | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/@theme/ext/use-configure-replay.ts b/@theme/ext/use-configure-replay.ts index 2774f23..d396e67 100644 --- a/@theme/ext/use-configure-replay.ts +++ b/@theme/ext/use-configure-replay.ts @@ -36,7 +36,6 @@ async function registerClient(): Promise { }, body: JSON.stringify({ client_name: CLIENT_NAME, - redirect_uris: [`${BASE_URL}/callback`], scope: SCOPES.join(' '), grant_types: ['client_credentials'], }), diff --git a/openapi/paths/oauth2_register.yaml b/openapi/paths/oauth2_register.yaml index 2b7af4b..4568359 100644 --- a/openapi/paths/oauth2_register.yaml +++ b/openapi/paths/oauth2_register.yaml @@ -15,6 +15,10 @@ post: - `grant_types` defaults to `authorization_code`, `client_credentials`, and `refresh_token` - `token_endpoint_auth_method` defaults to `client_secret_basic` (RFC 7591 Section 2); both supported methods are accepted at the token endpoint regardless of the registered value + This server registers confidential clients only: every client is issued a `client_secret`, + and `token_endpoint_auth_method: none` (public clients such as SPAs or mobile apps, + RFC 7591 Section 2) is not supported by design. + `redirect_uris` is required whenever the effective grant types include `authorization_code` (RFC 7591 Section 2) — including when `grant_types` is omitted, since the default includes it. Register with `grant_types: ["client_credentials"]` for a client that needs no redirect URI. From f31ba061019e2885e99a08d1d084dc1c76f15495 Mon Sep 17 00:00:00 2001 From: DmitryAnansky Date: Fri, 21 Aug 2026 17:41:47 +0300 Subject: [PATCH 12/19] chore(other):changes after review --- .../schemas/RegisterClientObject.yaml | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/openapi/components/schemas/RegisterClientObject.yaml b/openapi/components/schemas/RegisterClientObject.yaml index ad38580..93c6b1a 100644 --- a/openapi/components/schemas/RegisterClientObject.yaml +++ b/openapi/components/schemas/RegisterClientObject.yaml @@ -123,6 +123,9 @@ if: - grantTypes properties: grantTypes: {} +# The effective redirect list follows the same precedence: redirect_uris when +# present, otherwise the redirectUris alias — and the effective one must be +# non-empty. then: anyOf: - required: @@ -130,8 +133,14 @@ then: properties: redirect_uris: minItems: 1 - - required: - - redirectUris - properties: - redirectUris: - minItems: 1 + - allOf: + - not: + required: + - redirect_uris + properties: + redirect_uris: {} + - required: + - redirectUris + properties: + redirectUris: + minItems: 1 From 095c9c3e71ee24dc1e47fa8dee471768354c65e5 Mon Sep 17 00:00:00 2001 From: DmitryAnansky Date: Thu, 27 Aug 2026 15:27:35 +0300 Subject: [PATCH 13/19] chore: changes after review --- openapi/cafe.yaml | 3 +- .../responses/OAuth2Unauthorized.yaml | 6 +- openapi/components/schemas/OAuth2Client.yaml | 22 +++---- openapi/components/schemas/OAuthError.yaml | 6 +- .../schemas/RegisterClientObject.yaml | 49 +++++++-------- openapi/components/schemas/TokenResponse.yaml | 6 +- .../schemas/UpdateClientObject.yaml | 17 +++--- openapi/paths/oauth2_register.yaml | 24 +++----- openapi/paths/oauth2_register_{clientId}.yaml | 18 +++--- openapi/paths/oauth2_revoke.yaml | 6 +- openapi/paths/oauth2_token.yaml | 59 ++++++++++--------- ...well-known_oauth-authorization-server.yaml | 8 +-- 12 files changed, 110 insertions(+), 114 deletions(-) diff --git a/openapi/cafe.yaml b/openapi/cafe.yaml index a723a29..7f982c6 100644 --- a/openapi/cafe.yaml +++ b/openapi/cafe.yaml @@ -58,7 +58,8 @@ components: type: oauth2 description: | OAuth2 authorization for API access. The token endpoint accepts `grant_type=authorization_code`, `grant_type=client_credentials`, and `grant_type=refresh_token`. - Standard OAuth2 client libraries can drive these flows unmodified; server capabilities are discoverable from the [RFC 8414 metadata endpoint](https://api.cafe.redocly.com/.well-known/oauth-authorization-server). + Standard OAuth2 client libraries can drive these flows unmodified. + Server capabilities are discoverable from the [RFC 8414 metadata endpoint](https://api.cafe.redocly.com/.well-known/oauth-authorization-server). ### Protocol behavior diff --git a/openapi/components/responses/OAuth2Unauthorized.yaml b/openapi/components/responses/OAuth2Unauthorized.yaml index 5804af5..2f18635 100644 --- a/openapi/components/responses/OAuth2Unauthorized.yaml +++ b/openapi/components/responses/OAuth2Unauthorized.yaml @@ -1,8 +1,6 @@ description: | - Client authentication failed (`error: invalid_client`, RFC 6749 Section 5.2) - or the presented bearer token is invalid (`error: invalid_token`, RFC 6750). - The response carries a `WWW-Authenticate` challenge naming the expected - authentication scheme. + Client authentication failed (`error: invalid_client`, RFC 6749 Section 5.2) or the presented bearer token is invalid (`error: invalid_token`, RFC 6750). + The response carries a `WWW-Authenticate` challenge naming the expected authentication scheme. headers: WWW-Authenticate: description: Authentication challenge, e.g. `Basic realm="redocly-cafe"` or `Bearer realm="redocly-cafe", error="invalid_token"`. diff --git a/openapi/components/schemas/OAuth2Client.yaml b/openapi/components/schemas/OAuth2Client.yaml index c8afc65..9724880 100644 --- a/openapi/components/schemas/OAuth2Client.yaml +++ b/openapi/components/schemas/OAuth2Client.yaml @@ -1,15 +1,15 @@ type: object description: | - OAuth2 client information response per RFC 7591 Section 3.2.1, using the - standard snake_case field names. Returned by the registration endpoint and - the RFC 7592 client configuration endpoint. + OAuth2 client information response per RFC 7591 Section 3.2.1, using the standard snake_case field names. + Returned by the registration endpoint and the RFC 7592 client configuration endpoint. properties: client_id: type: string description: Client identifier issued by the authorization server. client_secret: type: string - description: Client secret issued by the authorization server. Store it securely. + description: Client secret issued by the authorization server. + Store it securely. client_id_issued_at: type: integer format: int64 @@ -17,17 +17,20 @@ properties: client_secret_expires_at: type: integer format: int64 - description: Time at which the client_secret expires, represented as seconds since epoch. 0 indicates the secret does not expire (RFC 7591). + description: Time at which the client_secret expires, represented as seconds since epoch. + 0 indicates the secret does not expire (RFC 7591). registration_client_uri: type: string format: uri description: URL of the client configuration endpoint for managing this client registration (RFC 7592). registration_access_token: type: string - description: Bearer token for the client configuration endpoint (RFC 7592). Store it securely. + description: Bearer token for the client configuration endpoint (RFC 7592). + Store it securely. client_name: type: string - description: Client name (registered metadata). Omitted for clients registered without a name. + description: Client name (registered metadata). + Omitted for clients registered without a name. redirect_uris: type: array items: @@ -52,9 +55,8 @@ properties: - client_secret_basic - client_secret_post description: | - Registered token endpoint authentication method. Defaults to `client_secret_basic` - when not requested at registration (RFC 7591 Section 2). Informational: the token - and revocation endpoints accept both methods for every client regardless. + Registered token endpoint authentication method. Defaults to `client_secret_basic` when not requested at registration (RFC 7591 Section 2). + Informational: the token and revocation endpoints accept both methods for every client regardless. # client_name is the only conditional field: it is omitted for clients # registered without a name. Everything else is always returned. required: diff --git a/openapi/components/schemas/OAuthError.yaml b/openapi/components/schemas/OAuthError.yaml index 6113c5f..0eba061 100644 --- a/openapi/components/schemas/OAuthError.yaml +++ b/openapi/components/schemas/OAuthError.yaml @@ -1,9 +1,7 @@ type: object description: | - OAuth2 error response, as defined by RFC 6749 Section 5.2 (token endpoint), - RFC 7591 Section 3.2.2 (registration endpoint), and RFC 7009 (revocation endpoint). - The OAuth2 endpoints return this shape instead of the `application/problem+json` - format used by the rest of the API. + OAuth2 error response, as defined by RFC 6749 Section 5.2 (token endpoint), RFC 7591 Section 3.2.2 (registration endpoint), and RFC 7009 (revocation endpoint). + The OAuth2 endpoints return this shape instead of the `application/problem+json` format used by the rest of the API. properties: error: type: string diff --git a/openapi/components/schemas/RegisterClientObject.yaml b/openapi/components/schemas/RegisterClientObject.yaml index 93c6b1a..e8d084b 100644 --- a/openapi/components/schemas/RegisterClientObject.yaml +++ b/openapi/components/schemas/RegisterClientObject.yaml @@ -1,29 +1,26 @@ type: object description: | Client registration metadata per RFC 7591 Section 2. - Standard snake_case metadata names (`client_name`, `redirect_uris`, `grant_types`, `scope`, - `token_endpoint_auth_method`) and this API's camelCase aliases (`name`, `redirectUris`, - `grantTypes`, `scopes`, `tokenEndpointAuthMethod`) are both accepted. - When both spellings of a field are present, the standard snake_case one takes precedence. - All fields are optional, with one conditional exception enforced by the schema below: - `redirect_uris` is required whenever the effective grant types include `authorization_code` — - including when `grant_types` is omitted, since the default includes it (RFC 7591 Section 2). + Standard snake_case metadata names (`client_name`, `redirect_uris`, `grant_types`, `scope`, `token_endpoint_auth_method`) and this API's camelCase aliases (`name`, `redirectUris`, `grantTypes`, `scopes`, `tokenEndpointAuthMethod`) are both accepted. + When both field name variants are present, the standard snake_case one takes precedence. + All fields are optional, with one conditional exception enforced by the schema below: `redirect_uris` is required whenever the effective grant types include `authorization_code`, including when `grant_types` is omitted, since authorization_code is part of the default (RFC 7591 Section 2). properties: client_name: type: string - description: Human-readable client name, shown on the authorization consent screen. + description: Human-readable client name, displayed on the authorization consent screen. redirect_uris: type: array items: type: string format: uri description: | - Redirect URIs for the `authorization_code` grant. Compared with exact string - matching at authorization time (RFC 6749 Section 3.1.2.3). Required when the - effective grant types include `authorization_code`. + Redirect URIs for the `authorization_code` grant. + Compared with exact string matching at authorization time (RFC 6749 Section 3.1.2.3). + Required when the effective grant types include `authorization_code`. scope: type: string - description: Space-separated list of scopes. Defaults to all available scopes. + description: Space-separated list of scopes. + Defaults to all available scopes. default: menu:read menu:write orders:read orders:write revenue:read pattern: '^(menu:read|menu:write|orders:read|orders:write|revenue:read)( (menu:read|menu:write|orders:read|orders:write|revenue:read))*$' example: menu:read orders:read orders:write @@ -40,9 +37,9 @@ properties: - client_credentials - refresh_token description: | - Grant types the client may use. A refresh token is issued with `authorization_code` - grant responses only when `refresh_token` is included here; the `client_credentials` - grant never returns one (RFC 6749 Section 4.4.3). + Grant types the client may use. + A refresh token is issued with `authorization_code` grant responses only when `refresh_token` is included here. + The `client_credentials` grant never returns one (RFC 6749 Section 4.4.3). token_endpoint_auth_method: type: string enum: @@ -50,19 +47,20 @@ properties: - client_secret_post default: client_secret_basic description: | - Requested client authentication method for the token endpoint, registered as - client metadata and echoed in registration responses. Defaults to - `client_secret_basic` (RFC 7591 Section 2). Informational: the token and - revocation endpoints accept both methods for every client regardless. + Requested client authentication method for the token endpoint, registered as client metadata and echoed in registration responses. + Defaults to `client_secret_basic` (RFC 7591 Section 2). + The token and revocation endpoints accept both methods for every client regardless. name: type: string - description: Alias for `client_name`; used when `client_name` is absent. + description: Alias for `client_name`. + Used when `client_name` is absent. redirectUris: type: array items: type: string format: uri - description: Alias for `redirect_uris`; used when `redirect_uris` is absent. + description: Alias for `redirect_uris`. + Used when `redirect_uris` is absent. scopes: type: array items: @@ -73,7 +71,8 @@ properties: - orders:read - orders:write - revenue:read - description: Alias for `scope`, as an array instead of a space-separated string; used when `scope` is absent. + description: Alias for `scope`, as an array instead of a space-separated string. + Used when `scope` is absent. grantTypes: type: array items: @@ -82,13 +81,15 @@ properties: - authorization_code - client_credentials - refresh_token - description: Alias for `grant_types`; used when `grant_types` is absent. + description: Alias for `grant_types` + Used when `grant_types` is absent. tokenEndpointAuthMethod: type: string enum: - client_secret_basic - client_secret_post - description: Alias for `token_endpoint_auth_method`; used when `token_endpoint_auth_method` is absent. + description: Alias for `token_endpoint_auth_method`. + Used when `token_endpoint_auth_method` is absent. # RFC 7591 Section 2: redirect-based grants require redirect URIs. The condition # mirrors the server's alias handling — the standard grant_types wins over the # grantTypes alias when both are present, and omitting both applies the default, diff --git a/openapi/components/schemas/TokenResponse.yaml b/openapi/components/schemas/TokenResponse.yaml index feac046..ec45d03 100644 --- a/openapi/components/schemas/TokenResponse.yaml +++ b/openapi/components/schemas/TokenResponse.yaml @@ -15,10 +15,8 @@ properties: refresh_token: type: string description: | - Refresh token, returned for the `authorization_code` grant when the client - is registered for the `refresh_token` grant type, and on every refresh - (tokens rotate on use). Never returned for the `client_credentials` grant - (RFC 6749 Section 4.4.3). + Refresh token, returned for the `authorization_code` grant when the client is registered for the `refresh_token` grant type, and on every refresh (tokens rotate on use). + Never returned for the `client_credentials` grant (RFC 6749 Section 4.4.3). scope: type: string description: Space-separated scopes granted to the token. diff --git a/openapi/components/schemas/UpdateClientObject.yaml b/openapi/components/schemas/UpdateClientObject.yaml index 7c7325f..db1dce5 100644 --- a/openapi/components/schemas/UpdateClientObject.yaml +++ b/openapi/components/schemas/UpdateClientObject.yaml @@ -1,7 +1,5 @@ description: | - Client registration update request per RFC 7592 Section 2.2: the full set of - registration metadata, plus the `client_id` of the client being updated, - which is required (either as `client_id` or its `clientId` alias). + Client registration update request per RFC 7592 Section 2.2: the full set of registration metadata, plus the `client_id` of the client being updated, which is required (either as `client_id` or its `clientId` alias). Omitted metadata fields are reset to their registration defaults. allOf: - $ref: ./RegisterClientObject.yaml @@ -9,16 +7,21 @@ allOf: properties: client_id: type: string - description: Required (RFC 7592 Section 2.2); must match the client being updated. Credentials cannot be changed through this endpoint. + description: Required (RFC 7592 Section 2.2). + Must match the client being updated. + Credentials cannot be changed through this endpoint. client_secret: type: string - description: Optional; if present, must match the client's current secret. + description: Optional. + If present, must match the client's current secret. clientId: type: string - description: Alias for `client_id`; used when `client_id` is absent. + description: Alias for `client_id` + Used when `client_id` is absent. clientSecret: type: string - description: Alias for `client_secret`; used when `client_secret` is absent. + description: Alias for `client_secret`. + Used when `client_secret` is absent. anyOf: - required: - client_id diff --git a/openapi/paths/oauth2_register.yaml b/openapi/paths/oauth2_register.yaml index 4568359..7175035 100644 --- a/openapi/paths/oauth2_register.yaml +++ b/openapi/paths/oauth2_register.yaml @@ -4,29 +4,24 @@ post: summary: Create OAuth2 client description: | Register a new OAuth2 client, implementing the Dynamic Client Registration Protocol (RFC 7591). - The standard snake_case metadata names (`client_name`, `redirect_uris`, `grant_types`, `scope`, `token_endpoint_auth_method`) - are accepted, as are this API's camelCase aliases (`name`, `redirectUris`, `grantTypes`, `scopes`, `tokenEndpointAuthMethod`), - so off-the-shelf DCR clients work without modification. + The standard snake_case metadata names (`client_name`, `redirect_uris`, `grant_types`, `scope`, `token_endpoint_auth_method`) are accepted, as are this API's camelCase aliases (`name`, `redirectUris`, `grantTypes`, `scopes`, `tokenEndpointAuthMethod`), so off-the-shelf DCR clients work without modification. Responses use only the standard snake_case names (RFC 7591 Section 3.2.1). - All fields are optional. If not provided: + All fields are optional. + If not provided: - `scope` defaults to all available scopes (`menu:read menu:write orders:read orders:write revenue:read`) - `grant_types` defaults to `authorization_code`, `client_credentials`, and `refresh_token` - `token_endpoint_auth_method` defaults to `client_secret_basic` (RFC 7591 Section 2); both supported methods are accepted at the token endpoint regardless of the registered value - This server registers confidential clients only: every client is issued a `client_secret`, - and `token_endpoint_auth_method: none` (public clients such as SPAs or mobile apps, - RFC 7591 Section 2) is not supported by design. + This server registers confidential clients only: every client is issued a `client_secret`, and `token_endpoint_auth_method: none` (public clients such as SPAs or mobile apps, RFC 7591 Section 2) is not supported by design. - `redirect_uris` is required whenever the effective grant types include `authorization_code` - (RFC 7591 Section 2) — including when `grant_types` is omitted, since the default includes it. + `redirect_uris` is required whenever the effective grant types include `authorization_code` (RFC 7591 Section 2), including when `grant_types` is omitted, since the default includes it. Register with `grant_types: ["client_credentials"]` for a client that needs no redirect URI. - Refresh token behavior follows the registered grant types: the token endpoint returns a refresh - token with `authorization_code` grant responses only when the client registered the - `refresh_token` grant type. The `client_credentials` grant never returns one (RFC 6749 Section 4.4.3); - those clients request a new access token with their own credentials instead. + Refresh token behavior follows the registered grant types: the token endpoint returns a refresh token with `authorization_code` grant responses only when the client registered the `refresh_token` grant type. + The `client_credentials` grant never returns refresh tokens (RFC 6749 Section 4.4.3). + Those clients request a new access token with their own credentials instead. Returns the registered client information per RFC 7591 Section 3.2.1, including: @@ -35,8 +30,7 @@ post: - `registration_client_uri` and `registration_access_token` for managing the registration (RFC 7592) - All registered client metadata - Errors use the RFC 7591 Section 3.2.2 format: `400` with `error` set to - `invalid_redirect_uri` or `invalid_client_metadata` and a human-readable `error_description`. + Errors use the RFC 7591 Section 3.2.2 format: `400` with `error` set to `invalid_redirect_uri` or `invalid_client_metadata` and a human-readable `error_description`. operationId: registerOAuth2Client security: [] requestBody: diff --git a/openapi/paths/oauth2_register_{clientId}.yaml b/openapi/paths/oauth2_register_{clientId}.yaml index 38239da..31088ab 100644 --- a/openapi/paths/oauth2_register_{clientId}.yaml +++ b/openapi/paths/oauth2_register_{clientId}.yaml @@ -6,9 +6,8 @@ get: summary: Retrieve OAuth2 client description: | Read the current registration of an OAuth2 client (RFC 7592 Section 2.1). - Authenticate with the `registration_access_token` returned at registration, - as a Bearer token in the `Authorization` header. An invalid or missing token - returns `401` without revealing whether the client exists. + Authenticate with the `registration_access_token` returned at registration, as a Bearer token in the `Authorization` header. + An invalid or missing token returns `401` without revealing whether the client exists. operationId: getOAuth2Client security: - RegistrationAccessToken: [] @@ -31,10 +30,9 @@ put: Replace the registration metadata of an OAuth2 client (RFC 7592 Section 2.2). Authenticate with the `registration_access_token` as a Bearer token. - This is a full replacement, not a merge: omitted metadata fields are reset to - their registration defaults. The request must include `client_id` (or the - `clientId` alias) matching the client being updated (RFC 7592 Section 2.2); - if `client_secret` is included it must match the current secret. + This is a full replacement, not a merge: omitted metadata fields are reset to their registration defaults. + The request must include `client_id` (or the `clientId` alias) matching the client being updated (RFC 7592 Section 2.2). + If `client_secret` is included it must match the current secret. Credentials cannot be rotated through this endpoint. operationId: updateOAuth2Client security: @@ -74,9 +72,9 @@ delete: - Authorization summary: Delete OAuth2 client description: | - Deprovision an OAuth2 client (RFC 7592 Section 2.3). Authenticate with the - `registration_access_token` as a Bearer token. Deleting a client also - invalidates all of its tokens and pending authorization codes. + Deprovision an OAuth2 client (RFC 7592 Section 2.3). + Authenticate with the `registration_access_token` as a Bearer token. + Deleting a client also invalidates all of its tokens and pending authorization codes. operationId: deleteOAuth2Client security: - RegistrationAccessToken: [] diff --git a/openapi/paths/oauth2_revoke.yaml b/openapi/paths/oauth2_revoke.yaml index 211cd20..adca78c 100644 --- a/openapi/paths/oauth2_revoke.yaml +++ b/openapi/paths/oauth2_revoke.yaml @@ -34,10 +34,12 @@ post: description: Optional hint about the token type. client_id: type: string - description: Client identifier. Omit when authenticating with HTTP Basic. + description: Client identifier. + Omit when authenticating with HTTP Basic. client_secret: type: string - description: Client secret. Omit when authenticating with HTTP Basic. + description: Client secret. + Omit when authenticating with HTTP Basic. required: - token examples: diff --git a/openapi/paths/oauth2_token.yaml b/openapi/paths/oauth2_token.yaml index 060fd87..64e0682 100644 --- a/openapi/paths/oauth2_token.yaml +++ b/openapi/paths/oauth2_token.yaml @@ -3,32 +3,28 @@ post: - Authorization summary: Create access token description: | - OAuth2 token endpoint (RFC 6749 Section 3.2). Supports the - `authorization_code`, `client_credentials`, and `refresh_token` grant types. + OAuth2 token endpoint (RFC 6749 Section 3.2). + Supports the `authorization_code`, `client_credentials`, and `refresh_token` grant types. - **Client authentication.** Authenticate with HTTP Basic - (`Authorization: Basic base64(client_id:client_secret)`) or with `client_id` - and `client_secret` in the request body — not both at once (RFC 6749 Section 2.3.1). + **Client authentication.** + Authenticate with HTTP Basic (`Authorization: Basic base64(client_id:client_secret)`) or with `client_id` and `client_secret` in the request body — not both at once (RFC 6749 Section 2.3.1). - **PKCE (RFC 7636).** When the authorization request included a - `code_challenge`, the matching `code_verifier` is required here; a mismatch - or a `code_verifier` for a code issued without a challenge is rejected with - `invalid_grant`. `S256` and `plain` challenge methods are supported. + **PKCE (RFC 7636).** + When the authorization request included a `code_challenge`, the matching `code_verifier` is required here. + A mismatch or a `code_verifier` for a code issued without a challenge is rejected with `invalid_grant`. + `S256` and `plain` challenge methods are supported. - **Refresh tokens.** Rotate on every use: a successful `refresh_token` grant - retires the presented token and returns a replacement. Refresh tokens expire - 30 days after issuance; rotation restarts that window. The - `authorization_code` grant returns a refresh token only when the client is - registered for the `refresh_token` grant type; the `client_credentials` - grant never returns one (RFC 6749 Section 4.4.3). + **Refresh tokens.** + Rotate on every use: a successful `refresh_token` grant retires the presented token and returns a replacement. + Refresh tokens expire 30 days after issuance; rotation restarts that window. + The `authorization_code` grant returns a refresh token only when the client is registered for the `refresh_token` grant type. + The `client_credentials` grant never returns one (RFC 6749 Section 4.4.3). - **Scopes.** `scope` is space-separated per RFC 6749 (comma-separated values - are additionally tolerated) and must be a subset of the scopes previously - granted (authorization code or refresh token) or registered (client credentials). + **Scopes.** + `scope` is space-separated per RFC 6749 (comma-separated values are additionally tolerated). + Must be a subset of the scopes previously granted (authorization code or refresh token) or registered (client credentials). - Errors follow RFC 6749 Section 5.2: `400` (or `401` for `invalid_client`) - with the standard `error` codes such as `invalid_grant`, `invalid_client`, - `unauthorized_client`, `unsupported_grant_type`, and `invalid_scope`. + Errors follow RFC 6749 Section 5.2: `400` (or `401` for `invalid_client`) with the standard `error` codes such as `invalid_grant`, `invalid_client`, `unauthorized_client`, `unsupported_grant_type`, and `invalid_scope`. operationId: createAccessToken security: [] requestBody: @@ -47,28 +43,34 @@ post: description: The OAuth2 grant type. client_id: type: string - description: Client identifier. Omit when authenticating with HTTP Basic. + description: Client identifier. + Omit when authenticating with HTTP Basic. client_secret: type: string - description: Client secret. Omit when authenticating with HTTP Basic. + description: Client secret. + Omit when authenticating with HTTP Basic. code: type: string description: The authorization code (required for `authorization_code`). redirect_uri: type: string format: uri - description: Must match the redirect URI used in the authorization request (required for `authorization_code`). + description: Must match the redirect URI used in the authorization request. + Required for `authorization_code`). code_verifier: type: string minLength: 43 maxLength: 128 - description: PKCE code verifier (RFC 7636); required when the authorization request included a `code_challenge`. + description: PKCE code verifier (RFC 7636) + Required when the authorization request included a `code_challenge`. refresh_token: type: string - description: The refresh token (required for `refresh_token`). + description: The refresh token. + Required for `refresh_token`. scope: type: string - description: Space-separated scopes to request; defaults to the previously granted or registered scopes. + description: Space-separated scopes to request. + Defaults to the previously granted or registered scopes. required: - grant_type examples: @@ -97,7 +99,8 @@ post: refresh_token: YOUR_REFRESH_TOKEN responses: '200': - description: Token issued successfully. The response is not cacheable (it is served with `Cache-Control` set to `no-store`). + description: Token issued successfully. + The response is not cacheable (it is served with `Cache-Control` set to `no-store`). content: application/json: schema: diff --git a/openapi/paths/well-known_oauth-authorization-server.yaml b/openapi/paths/well-known_oauth-authorization-server.yaml index d9d8e02..28412d3 100644 --- a/openapi/paths/well-known_oauth-authorization-server.yaml +++ b/openapi/paths/well-known_oauth-authorization-server.yaml @@ -3,11 +3,9 @@ get: - Authorization summary: Retrieve authorization server metadata description: | - OAuth2 authorization server metadata (RFC 8414). Lists the authorization, - token, registration, and revocation endpoints along with the supported - scopes, grant types, client authentication methods, and PKCE code challenge - methods, so clients can discover the server's capabilities instead of - hard-coding them. + OAuth2 authorization server metadata (RFC 8414). + Lists the authorization, token, registration, and revocation endpoints along with the supported scopes, grant types, client authentication methods, and PKCE code challenge methods. + Clients can discover the server's capabilities instead of hard-coding them. operationId: getAuthorizationServerMetadata security: [] responses: From 958a6652e4cd89c7e03e22cadc48eb99ecaacbbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacek=20=C5=81=C4=99kawa?= <164185257+JLekawa@users.noreply.github.com> Date: Thu, 27 Aug 2026 18:00:10 +0200 Subject: [PATCH 14/19] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jacek Łękawa <164185257+JLekawa@users.noreply.github.com> --- openapi/components/responses/OAuth2Unauthorized.yaml | 2 +- openapi/components/schemas/OAuth2Client.yaml | 4 ++-- openapi/components/schemas/RegisterClientObject.yaml | 4 +++- openapi/paths/oauth2_revoke.yaml | 7 +++---- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/openapi/components/responses/OAuth2Unauthorized.yaml b/openapi/components/responses/OAuth2Unauthorized.yaml index 2f18635..cc846c2 100644 --- a/openapi/components/responses/OAuth2Unauthorized.yaml +++ b/openapi/components/responses/OAuth2Unauthorized.yaml @@ -3,7 +3,7 @@ description: | The response carries a `WWW-Authenticate` challenge naming the expected authentication scheme. headers: WWW-Authenticate: - description: Authentication challenge, e.g. `Basic realm="redocly-cafe"` or `Bearer realm="redocly-cafe", error="invalid_token"`. + description: Authentication challenge, for example: `Basic realm="redocly-cafe"` or `Bearer realm="redocly-cafe", error="invalid_token"`. schema: type: string content: diff --git a/openapi/components/schemas/OAuth2Client.yaml b/openapi/components/schemas/OAuth2Client.yaml index 9724880..120f088 100644 --- a/openapi/components/schemas/OAuth2Client.yaml +++ b/openapi/components/schemas/OAuth2Client.yaml @@ -18,7 +18,7 @@ properties: type: integer format: int64 description: Time at which the client_secret expires, represented as seconds since epoch. - 0 indicates the secret does not expire (RFC 7591). + `0` indicates the secret does not expire (RFC 7591). registration_client_uri: type: string format: uri @@ -56,7 +56,7 @@ properties: - client_secret_post description: | Registered token endpoint authentication method. Defaults to `client_secret_basic` when not requested at registration (RFC 7591 Section 2). - Informational: the token and revocation endpoints accept both methods for every client regardless. + The token and revocation endpoints accept both methods for every client regardless. # client_name is the only conditional field: it is omitted for clients # registered without a name. Everything else is always returned. required: diff --git a/openapi/components/schemas/RegisterClientObject.yaml b/openapi/components/schemas/RegisterClientObject.yaml index e8d084b..29fb637 100644 --- a/openapi/components/schemas/RegisterClientObject.yaml +++ b/openapi/components/schemas/RegisterClientObject.yaml @@ -3,7 +3,9 @@ description: | Client registration metadata per RFC 7591 Section 2. Standard snake_case metadata names (`client_name`, `redirect_uris`, `grant_types`, `scope`, `token_endpoint_auth_method`) and this API's camelCase aliases (`name`, `redirectUris`, `grantTypes`, `scopes`, `tokenEndpointAuthMethod`) are both accepted. When both field name variants are present, the standard snake_case one takes precedence. - All fields are optional, with one conditional exception enforced by the schema below: `redirect_uris` is required whenever the effective grant types include `authorization_code`, including when `grant_types` is omitted, since authorization_code is part of the default (RFC 7591 Section 2). + All fields are optional, with one conditional exception enforced by the schema below. + `redirect_uris` is required whenever the effective grant types include `authorization_code`. + This requirement still applies when `grant_types` is omitted: `authorization_code` is part of the default (RFC 7591 Section 2). properties: client_name: type: string diff --git a/openapi/paths/oauth2_revoke.yaml b/openapi/paths/oauth2_revoke.yaml index adca78c..0283fc1 100644 --- a/openapi/paths/oauth2_revoke.yaml +++ b/openapi/paths/oauth2_revoke.yaml @@ -10,10 +10,9 @@ post: Authenticate with HTTP Basic or with `client_id` and `client_secret` in the request body, the same as the token endpoint. - `token_type_hint` is an optional lookup optimization; both token types are - searched regardless. Per RFC 7009 Section 2.2 the endpoint returns `200` - even when the token is unknown or already revoked, so it cannot be used to - probe for valid tokens. + `token_type_hint` is an optional lookup optimization; both token types are searched regardless. + Per RFC 7009 Section 2.2 the endpoint returns `200` even when the token is unknown or already revoked. + It cannot be used to probe for valid tokens. operationId: revokeToken security: [] requestBody: From fa8bf42a752e161792de6cdacaac88fb663a7137 Mon Sep 17 00:00:00 2001 From: DmitryAnansky Date: Fri, 28 Aug 2026 11:15:51 +0300 Subject: [PATCH 15/19] fix: indentation errors --- openapi/components/responses/OAuth2Unauthorized.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openapi/components/responses/OAuth2Unauthorized.yaml b/openapi/components/responses/OAuth2Unauthorized.yaml index cc846c2..091f071 100644 --- a/openapi/components/responses/OAuth2Unauthorized.yaml +++ b/openapi/components/responses/OAuth2Unauthorized.yaml @@ -3,7 +3,7 @@ description: | The response carries a `WWW-Authenticate` challenge naming the expected authentication scheme. headers: WWW-Authenticate: - description: Authentication challenge, for example: `Basic realm="redocly-cafe"` or `Bearer realm="redocly-cafe", error="invalid_token"`. + description: 'Authentication challenge, for example: `Basic realm="redocly-cafe"` or `Bearer realm="redocly-cafe", error="invalid_token"`.' schema: type: string content: From 4fef6eb39183f11589634463daae33c7a29c7309 Mon Sep 17 00:00:00 2001 From: DmitryAnansky Date: Wed, 9 Sep 2026 18:40:12 +0300 Subject: [PATCH 16/19] chore(other): rewrite if-then with allOf-anyOf-not --- .redocly.lint-ignore.yaml | 16 +- .../GrantTypesIncludeAuthorizationCode.yaml | 29 +++ .../schemas/RedirectUrisMissing.yaml | 27 +++ .../schemas/RegisterClientMetadata.yaml | 74 +++++++ .../schemas/RegisterClientObject.yaml | 183 ++++-------------- 5 files changed, 182 insertions(+), 147 deletions(-) create mode 100644 openapi/components/schemas/GrantTypesIncludeAuthorizationCode.yaml create mode 100644 openapi/components/schemas/RedirectUrisMissing.yaml create mode 100644 openapi/components/schemas/RegisterClientMetadata.yaml diff --git a/.redocly.lint-ignore.yaml b/.redocly.lint-ignore.yaml index 570447d..50fe32a 100644 --- a/.redocly.lint-ignore.yaml +++ b/.redocly.lint-ignore.yaml @@ -1,6 +1,14 @@ # This file instructs Redocly's linter to ignore the rules contained for specific parts of your API. # See https://redocly.com/docs/cli/ for more information. -openapi/paths/oauth2_register_{clientId}.yaml: - no-invalid-media-type-examples: - - >- - #/put/requestBody/content/application~1json/examples/UpdateClient/dataValue/client_id +# The entries below are presence checks (`required` inside `not`) in constraint +# schemas; they intentionally carry no sibling `properties` declarations. +openapi/components/schemas/GrantTypesIncludeAuthorizationCode.yaml: + no-required-schema-properties-undefined: + - '#/anyOf/1/not/required/0' + - '#/anyOf/2/not/anyOf/0/required/0' + - '#/anyOf/2/not/anyOf/1/required/0' +openapi/components/schemas/RedirectUrisMissing.yaml: + no-required-schema-properties-undefined: + - '#/anyOf/1/not/required/0' + - '#/anyOf/2/not/anyOf/0/required/0' + - '#/anyOf/2/not/anyOf/1/required/0' diff --git a/openapi/components/schemas/GrantTypesIncludeAuthorizationCode.yaml b/openapi/components/schemas/GrantTypesIncludeAuthorizationCode.yaml new file mode 100644 index 0000000..cb9d585 --- /dev/null +++ b/openapi/components/schemas/GrantTypesIncludeAuthorizationCode.yaml @@ -0,0 +1,29 @@ +description: | + Matches a registration whose effective grant types include `authorization_code`: + `grant_types` contains it, the `grantTypes` alias contains it while `grant_types` is absent, + or both fields are omitted and the default set (which includes it) applies. +anyOf: + # grant_types contains authorization_code. + - required: + - grant_types + properties: + grant_types: + contains: + const: authorization_code + # grant_types is absent and the grantTypes alias contains authorization_code. + - required: + - grantTypes + properties: + grantTypes: + contains: + const: authorization_code + not: + required: + - grant_types + # Both fields are absent, so the default set applies. + - not: + anyOf: + - required: + - grant_types + - required: + - grantTypes diff --git a/openapi/components/schemas/RedirectUrisMissing.yaml b/openapi/components/schemas/RedirectUrisMissing.yaml new file mode 100644 index 0000000..90086e7 --- /dev/null +++ b/openapi/components/schemas/RedirectUrisMissing.yaml @@ -0,0 +1,27 @@ +description: | + Matches a registration whose effective redirect URI list is missing or empty: + `redirect_uris` is present but empty, or `redirect_uris` is absent while the + `redirectUris` alias is also absent or empty. +anyOf: + # redirect_uris is present but empty. + - required: + - redirect_uris + properties: + redirect_uris: + maxItems: 0 + # redirect_uris is absent and the redirectUris alias is present but empty. + - required: + - redirectUris + properties: + redirectUris: + maxItems: 0 + not: + required: + - redirect_uris + # Both fields are absent. + - not: + anyOf: + - required: + - redirect_uris + - required: + - redirectUris diff --git a/openapi/components/schemas/RegisterClientMetadata.yaml b/openapi/components/schemas/RegisterClientMetadata.yaml new file mode 100644 index 0000000..f3f683b --- /dev/null +++ b/openapi/components/schemas/RegisterClientMetadata.yaml @@ -0,0 +1,74 @@ +type: object +description: | + Client registration metadata fields per RFC 7591 Section 2, shared by both + registration variants. +properties: + client_name: + type: string + description: Human-readable client name, displayed on the authorization consent screen. + scope: + type: string + description: Space-separated list of scopes. + Defaults to all available scopes. + default: menu:read menu:write orders:read orders:write revenue:read + pattern: '^(menu:read|menu:write|orders:read|orders:write|revenue:read)( (menu:read|menu:write|orders:read|orders:write|revenue:read))*$' + example: menu:read orders:read orders:write + grant_types: + type: array + items: + type: string + enum: + - authorization_code + - client_credentials + - refresh_token + default: + - authorization_code + - client_credentials + - refresh_token + description: | + Grant types the client may use. + A refresh token is issued with `authorization_code` grant responses only when `refresh_token` is included here. + The `client_credentials` grant never returns one (RFC 6749 Section 4.4.3). + token_endpoint_auth_method: + type: string + enum: + - client_secret_basic + - client_secret_post + default: client_secret_basic + description: | + Requested client authentication method for the token endpoint, registered as client metadata and echoed in registration responses. + Defaults to `client_secret_basic` (RFC 7591 Section 2). + The token and revocation endpoints accept both methods for every client regardless. + name: + type: string + description: Alias for `client_name`. + Used when `client_name` is absent. + scopes: + type: array + items: + type: string + enum: + - menu:read + - menu:write + - orders:read + - orders:write + - revenue:read + description: Alias for `scope`, as an array instead of a space-separated string. + Used when `scope` is absent. + grantTypes: + type: array + items: + type: string + enum: + - authorization_code + - client_credentials + - refresh_token + description: Alias for `grant_types` + Used when `grant_types` is absent. + tokenEndpointAuthMethod: + type: string + enum: + - client_secret_basic + - client_secret_post + description: Alias for `token_endpoint_auth_method`. + Used when `token_endpoint_auth_method` is absent. diff --git a/openapi/components/schemas/RegisterClientObject.yaml b/openapi/components/schemas/RegisterClientObject.yaml index 29fb637..a6ee3ed 100644 --- a/openapi/components/schemas/RegisterClientObject.yaml +++ b/openapi/components/schemas/RegisterClientObject.yaml @@ -1,149 +1,46 @@ -type: object description: | Client registration metadata per RFC 7591 Section 2. Standard snake_case metadata names (`client_name`, `redirect_uris`, `grant_types`, `scope`, `token_endpoint_auth_method`) and this API's camelCase aliases (`name`, `redirectUris`, `grantTypes`, `scopes`, `tokenEndpointAuthMethod`) are both accepted. When both field name variants are present, the standard snake_case one takes precedence. - All fields are optional, with one conditional exception enforced by the schema below. + All fields are optional, with one conditional exception. `redirect_uris` is required whenever the effective grant types include `authorization_code`. This requirement still applies when `grant_types` is omitted: `authorization_code` is part of the default (RFC 7591 Section 2). -properties: - client_name: - type: string - description: Human-readable client name, displayed on the authorization consent screen. - redirect_uris: - type: array - items: - type: string - format: uri - description: | - Redirect URIs for the `authorization_code` grant. - Compared with exact string matching at authorization time (RFC 6749 Section 3.1.2.3). - Required when the effective grant types include `authorization_code`. - scope: - type: string - description: Space-separated list of scopes. - Defaults to all available scopes. - default: menu:read menu:write orders:read orders:write revenue:read - pattern: '^(menu:read|menu:write|orders:read|orders:write|revenue:read)( (menu:read|menu:write|orders:read|orders:write|revenue:read))*$' - example: menu:read orders:read orders:write - grant_types: - type: array - items: - type: string - enum: - - authorization_code - - client_credentials - - refresh_token - default: - - authorization_code - - client_credentials - - refresh_token - description: | - Grant types the client may use. - A refresh token is issued with `authorization_code` grant responses only when `refresh_token` is included here. - The `client_credentials` grant never returns one (RFC 6749 Section 4.4.3). - token_endpoint_auth_method: - type: string - enum: - - client_secret_basic - - client_secret_post - default: client_secret_basic - description: | - Requested client authentication method for the token endpoint, registered as client metadata and echoed in registration responses. - Defaults to `client_secret_basic` (RFC 7591 Section 2). - The token and revocation endpoints accept both methods for every client regardless. - name: - type: string - description: Alias for `client_name`. - Used when `client_name` is absent. - redirectUris: - type: array - items: - type: string - format: uri - description: Alias for `redirect_uris`. - Used when `redirect_uris` is absent. - scopes: - type: array - items: - type: string - enum: - - menu:read - - menu:write - - orders:read - - orders:write - - revenue:read - description: Alias for `scope`, as an array instead of a space-separated string. - Used when `scope` is absent. - grantTypes: - type: array - items: - type: string - enum: - - authorization_code - - client_credentials - - refresh_token - description: Alias for `grant_types` - Used when `grant_types` is absent. - tokenEndpointAuthMethod: - type: string - enum: - - client_secret_basic - - client_secret_post - description: Alias for `token_endpoint_auth_method`. - Used when `token_endpoint_auth_method` is absent. -# RFC 7591 Section 2: redirect-based grants require redirect URIs. The condition -# mirrors the server's alias handling — the standard grant_types wins over the -# grantTypes alias when both are present, and omitting both applies the default, -# which includes authorization_code. -if: - anyOf: - - required: - - grant_types - properties: - grant_types: - contains: - const: authorization_code - - allOf: - - not: - required: - - grant_types - properties: - grant_types: {} - - required: - - grantTypes - properties: - grantTypes: - contains: - const: authorization_code - - not: - anyOf: - - required: - - grant_types - properties: - grant_types: {} - - required: - - grantTypes - properties: - grantTypes: {} -# The effective redirect list follows the same precedence: redirect_uris when -# present, otherwise the redirectUris alias — and the effective one must be -# non-empty. -then: - anyOf: - - required: - - redirect_uris - properties: - redirect_uris: - minItems: 1 - - allOf: - - not: - required: - - redirect_uris - properties: - redirect_uris: {} - - required: - - redirectUris - properties: - redirectUris: - minItems: 1 +anyOf: + - title: authorization_code + description: >- + Effective grant types include `authorization_code` — explicitly, via + the `grantTypes` alias, or by default when both grant fields are omitted. + A non-empty `redirect_uris` (or its `redirectUris` alias) is required. + Other grant types (`client_credentials`, `refresh_token`) can be + registered alongside. + allOf: + - type: object + properties: + redirect_uris: + type: array + items: + type: string + format: uri + description: | + Redirect URIs for the `authorization_code` grant. + Compared with exact string matching at authorization time (RFC 6749 Section 3.1.2.3). + Required for this variant, with at least one entry. + redirectUris: + type: array + items: + type: string + format: uri + description: Alias for `redirect_uris`. + Used when `redirect_uris` is absent. + - $ref: ./RegisterClientMetadata.yaml + - not: + $ref: ./RedirectUrisMissing.yaml + - title: client_credentials + description: >- + `grant_types` (or the `grantTypes` alias) is provided and does not + include `authorization_code` — typically machine-to-machine clients + using `client_credentials`. No redirect URI is needed. + allOf: + - $ref: ./RegisterClientMetadata.yaml + - not: + $ref: ./GrantTypesIncludeAuthorizationCode.yaml From 6c744d916a7ed1b4a3f39e1c62c2518aae2c6609 Mon Sep 17 00:00:00 2001 From: DmitryAnansky Date: Wed, 9 Sep 2026 18:59:22 +0300 Subject: [PATCH 17/19] chore(other): sidebar config --- sidebars.yaml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 sidebars.yaml diff --git a/sidebars.yaml b/sidebars.yaml new file mode 100644 index 0000000..9676783 --- /dev/null +++ b/sidebars.yaml @@ -0,0 +1,2 @@ +- page: openapi/cafe.yaml + label: Redocly Cafe From 97752a9b7c7485a0a5dd48f6151500c33412a52e Mon Sep 17 00:00:00 2001 From: DmitryAnansky Date: Thu, 10 Sep 2026 16:01:11 +0300 Subject: [PATCH 18/19] chore(other): changes after review --- .redocly.lint-ignore.yaml | 14 ------- .../GrantTypesIncludeAuthorizationCode.yaml | 29 ------------- .../schemas/RedirectUrisMissing.yaml | 27 ------------ .../schemas/RegisterClientMetadata.yaml | 33 --------------- .../schemas/RegisterClientObject.yaml | 42 +++++++++---------- .../schemas/UpdateClientObject.yaml | 17 ++------ openapi/paths/oauth2_register.yaml | 16 +++---- openapi/paths/oauth2_register_{clientId}.yaml | 2 +- 8 files changed, 28 insertions(+), 152 deletions(-) delete mode 100644 .redocly.lint-ignore.yaml delete mode 100644 openapi/components/schemas/GrantTypesIncludeAuthorizationCode.yaml delete mode 100644 openapi/components/schemas/RedirectUrisMissing.yaml diff --git a/.redocly.lint-ignore.yaml b/.redocly.lint-ignore.yaml deleted file mode 100644 index 50fe32a..0000000 --- a/.redocly.lint-ignore.yaml +++ /dev/null @@ -1,14 +0,0 @@ -# This file instructs Redocly's linter to ignore the rules contained for specific parts of your API. -# See https://redocly.com/docs/cli/ for more information. -# The entries below are presence checks (`required` inside `not`) in constraint -# schemas; they intentionally carry no sibling `properties` declarations. -openapi/components/schemas/GrantTypesIncludeAuthorizationCode.yaml: - no-required-schema-properties-undefined: - - '#/anyOf/1/not/required/0' - - '#/anyOf/2/not/anyOf/0/required/0' - - '#/anyOf/2/not/anyOf/1/required/0' -openapi/components/schemas/RedirectUrisMissing.yaml: - no-required-schema-properties-undefined: - - '#/anyOf/1/not/required/0' - - '#/anyOf/2/not/anyOf/0/required/0' - - '#/anyOf/2/not/anyOf/1/required/0' diff --git a/openapi/components/schemas/GrantTypesIncludeAuthorizationCode.yaml b/openapi/components/schemas/GrantTypesIncludeAuthorizationCode.yaml deleted file mode 100644 index cb9d585..0000000 --- a/openapi/components/schemas/GrantTypesIncludeAuthorizationCode.yaml +++ /dev/null @@ -1,29 +0,0 @@ -description: | - Matches a registration whose effective grant types include `authorization_code`: - `grant_types` contains it, the `grantTypes` alias contains it while `grant_types` is absent, - or both fields are omitted and the default set (which includes it) applies. -anyOf: - # grant_types contains authorization_code. - - required: - - grant_types - properties: - grant_types: - contains: - const: authorization_code - # grant_types is absent and the grantTypes alias contains authorization_code. - - required: - - grantTypes - properties: - grantTypes: - contains: - const: authorization_code - not: - required: - - grant_types - # Both fields are absent, so the default set applies. - - not: - anyOf: - - required: - - grant_types - - required: - - grantTypes diff --git a/openapi/components/schemas/RedirectUrisMissing.yaml b/openapi/components/schemas/RedirectUrisMissing.yaml deleted file mode 100644 index 90086e7..0000000 --- a/openapi/components/schemas/RedirectUrisMissing.yaml +++ /dev/null @@ -1,27 +0,0 @@ -description: | - Matches a registration whose effective redirect URI list is missing or empty: - `redirect_uris` is present but empty, or `redirect_uris` is absent while the - `redirectUris` alias is also absent or empty. -anyOf: - # redirect_uris is present but empty. - - required: - - redirect_uris - properties: - redirect_uris: - maxItems: 0 - # redirect_uris is absent and the redirectUris alias is present but empty. - - required: - - redirectUris - properties: - redirectUris: - maxItems: 0 - not: - required: - - redirect_uris - # Both fields are absent. - - not: - anyOf: - - required: - - redirect_uris - - required: - - redirectUris diff --git a/openapi/components/schemas/RegisterClientMetadata.yaml b/openapi/components/schemas/RegisterClientMetadata.yaml index f3f683b..7f8eecc 100644 --- a/openapi/components/schemas/RegisterClientMetadata.yaml +++ b/openapi/components/schemas/RegisterClientMetadata.yaml @@ -39,36 +39,3 @@ properties: Requested client authentication method for the token endpoint, registered as client metadata and echoed in registration responses. Defaults to `client_secret_basic` (RFC 7591 Section 2). The token and revocation endpoints accept both methods for every client regardless. - name: - type: string - description: Alias for `client_name`. - Used when `client_name` is absent. - scopes: - type: array - items: - type: string - enum: - - menu:read - - menu:write - - orders:read - - orders:write - - revenue:read - description: Alias for `scope`, as an array instead of a space-separated string. - Used when `scope` is absent. - grantTypes: - type: array - items: - type: string - enum: - - authorization_code - - client_credentials - - refresh_token - description: Alias for `grant_types` - Used when `grant_types` is absent. - tokenEndpointAuthMethod: - type: string - enum: - - client_secret_basic - - client_secret_post - description: Alias for `token_endpoint_auth_method`. - Used when `token_endpoint_auth_method` is absent. diff --git a/openapi/components/schemas/RegisterClientObject.yaml b/openapi/components/schemas/RegisterClientObject.yaml index a6ee3ed..a34751b 100644 --- a/openapi/components/schemas/RegisterClientObject.yaml +++ b/openapi/components/schemas/RegisterClientObject.yaml @@ -1,46 +1,42 @@ description: | - Client registration metadata per RFC 7591 Section 2. - Standard snake_case metadata names (`client_name`, `redirect_uris`, `grant_types`, `scope`, `token_endpoint_auth_method`) and this API's camelCase aliases (`name`, `redirectUris`, `grantTypes`, `scopes`, `tokenEndpointAuthMethod`) are both accepted. - When both field name variants are present, the standard snake_case one takes precedence. + Client registration metadata per RFC 7591 Section 2, using the standard snake_case field names. All fields are optional, with one conditional exception. `redirect_uris` is required whenever the effective grant types include `authorization_code`. This requirement still applies when `grant_types` is omitted: `authorization_code` is part of the default (RFC 7591 Section 2). anyOf: - title: authorization_code description: >- - Effective grant types include `authorization_code` — explicitly, via - the `grantTypes` alias, or by default when both grant fields are omitted. - A non-empty `redirect_uris` (or its `redirectUris` alias) is required. - Other grant types (`client_credentials`, `refresh_token`) can be - registered alongside. + Effective grant types include `authorization_code` — explicitly, or by + default when `grant_types` is omitted. A non-empty `redirect_uris` is + required. Other grant types (`client_credentials`, `refresh_token`) can + be registered alongside. allOf: - type: object + required: + - redirect_uris properties: redirect_uris: type: array + minItems: 1 items: type: string format: uri description: | Redirect URIs for the `authorization_code` grant. Compared with exact string matching at authorization time (RFC 6749 Section 3.1.2.3). - Required for this variant, with at least one entry. - redirectUris: - type: array - items: - type: string - format: uri - description: Alias for `redirect_uris`. - Used when `redirect_uris` is absent. - $ref: ./RegisterClientMetadata.yaml - - not: - $ref: ./RedirectUrisMissing.yaml - title: client_credentials description: >- - `grant_types` (or the `grantTypes` alias) is provided and does not - include `authorization_code` — typically machine-to-machine clients - using `client_credentials`. No redirect URI is needed. + `grant_types` is provided and does not include `authorization_code` — + typically machine-to-machine clients using `client_credentials`. + No redirect URI is needed. allOf: - $ref: ./RegisterClientMetadata.yaml - - not: - $ref: ./GrantTypesIncludeAuthorizationCode.yaml + - type: object + required: + - grant_types + properties: + grant_types: + not: + contains: + const: authorization_code diff --git a/openapi/components/schemas/UpdateClientObject.yaml b/openapi/components/schemas/UpdateClientObject.yaml index db1dce5..745abb2 100644 --- a/openapi/components/schemas/UpdateClientObject.yaml +++ b/openapi/components/schemas/UpdateClientObject.yaml @@ -1,9 +1,11 @@ description: | - Client registration update request per RFC 7592 Section 2.2: the full set of registration metadata, plus the `client_id` of the client being updated, which is required (either as `client_id` or its `clientId` alias). + Client registration update request per RFC 7592 Section 2.2: the full set of registration metadata, plus the required `client_id` of the client being updated. Omitted metadata fields are reset to their registration defaults. allOf: - $ref: ./RegisterClientObject.yaml - type: object + required: + - client_id properties: client_id: type: string @@ -14,16 +16,3 @@ allOf: type: string description: Optional. If present, must match the client's current secret. - clientId: - type: string - description: Alias for `client_id` - Used when `client_id` is absent. - clientSecret: - type: string - description: Alias for `client_secret`. - Used when `client_secret` is absent. -anyOf: - - required: - - client_id - - required: - - clientId diff --git a/openapi/paths/oauth2_register.yaml b/openapi/paths/oauth2_register.yaml index 7175035..a036ad5 100644 --- a/openapi/paths/oauth2_register.yaml +++ b/openapi/paths/oauth2_register.yaml @@ -3,8 +3,7 @@ post: - Authorization summary: Create OAuth2 client description: | - Register a new OAuth2 client, implementing the Dynamic Client Registration Protocol (RFC 7591). - The standard snake_case metadata names (`client_name`, `redirect_uris`, `grant_types`, `scope`, `token_endpoint_auth_method`) are accepted, as are this API's camelCase aliases (`name`, `redirectUris`, `grantTypes`, `scopes`, `tokenEndpointAuthMethod`), so off-the-shelf DCR clients work without modification. + Register a new OAuth2 client, implementing the Dynamic Client Registration Protocol (RFC 7591) with the standard snake_case metadata names, so off-the-shelf DCR clients work without modification. Responses use only the standard snake_case names (RFC 7591 Section 3.2.1). All fields are optional. @@ -41,16 +40,11 @@ post: $ref: ../components/schemas/RegisterClientObject.yaml examples: RegisterClientObject: - summary: Client credentials only (camelCase aliases) + summary: Client credentials only dataValue: - name: auth - scopes: - - menu:read - - menu:write - - orders:read - - orders:write - - revenue:read - grantTypes: + client_name: auth + scope: menu:read menu:write orders:read orders:write revenue:read + grant_types: - client_credentials RegisterClientForAuthorizationCode: summary: Authorization code with refresh tokens (RFC 7591 names) diff --git a/openapi/paths/oauth2_register_{clientId}.yaml b/openapi/paths/oauth2_register_{clientId}.yaml index 31088ab..21474e3 100644 --- a/openapi/paths/oauth2_register_{clientId}.yaml +++ b/openapi/paths/oauth2_register_{clientId}.yaml @@ -31,7 +31,7 @@ put: Authenticate with the `registration_access_token` as a Bearer token. This is a full replacement, not a merge: omitted metadata fields are reset to their registration defaults. - The request must include `client_id` (or the `clientId` alias) matching the client being updated (RFC 7592 Section 2.2). + The request must include `client_id` matching the client being updated (RFC 7592 Section 2.2). If `client_secret` is included it must match the current secret. Credentials cannot be rotated through this endpoint. operationId: updateOAuth2Client From 9b708c1da4dd4df2c3f9ca88d58e12639c283399 Mon Sep 17 00:00:00 2001 From: DmitryAnansky Date: Thu, 10 Sep 2026 17:05:44 +0300 Subject: [PATCH 19/19] chore(other): changes after review --- .../RegisterClientAuthorizationCode.yaml | 21 ++++++++++ .../schemas/RegisterClientCredentials.yaml | 15 ++++++++ .../schemas/RegisterClientObject.yaml | 38 +------------------ 3 files changed, 38 insertions(+), 36 deletions(-) create mode 100644 openapi/components/schemas/RegisterClientAuthorizationCode.yaml create mode 100644 openapi/components/schemas/RegisterClientCredentials.yaml diff --git a/openapi/components/schemas/RegisterClientAuthorizationCode.yaml b/openapi/components/schemas/RegisterClientAuthorizationCode.yaml new file mode 100644 index 0000000..f7d6ebc --- /dev/null +++ b/openapi/components/schemas/RegisterClientAuthorizationCode.yaml @@ -0,0 +1,21 @@ +title: authorization_code +description: >- + Effective grant types include `authorization_code` — explicitly, or by + default when `grant_types` is omitted. A non-empty `redirect_uris` is + required. Other grant types (`client_credentials`, `refresh_token`) can + be registered alongside. +allOf: + - type: object + required: + - redirect_uris + properties: + redirect_uris: + type: array + minItems: 1 + items: + type: string + format: uri + description: | + Redirect URIs for the `authorization_code` grant. + Compared with exact string matching at authorization time (RFC 6749 Section 3.1.2.3). + - $ref: ./RegisterClientMetadata.yaml diff --git a/openapi/components/schemas/RegisterClientCredentials.yaml b/openapi/components/schemas/RegisterClientCredentials.yaml new file mode 100644 index 0000000..adce219 --- /dev/null +++ b/openapi/components/schemas/RegisterClientCredentials.yaml @@ -0,0 +1,15 @@ +title: client_credentials +description: >- + `grant_types` is provided and does not include `authorization_code` — + typically machine-to-machine clients using `client_credentials`. + No redirect URI is needed. +allOf: + - $ref: ./RegisterClientMetadata.yaml + - type: object + required: + - grant_types + properties: + grant_types: + not: + contains: + const: authorization_code diff --git a/openapi/components/schemas/RegisterClientObject.yaml b/openapi/components/schemas/RegisterClientObject.yaml index a34751b..68e81e9 100644 --- a/openapi/components/schemas/RegisterClientObject.yaml +++ b/openapi/components/schemas/RegisterClientObject.yaml @@ -4,39 +4,5 @@ description: | `redirect_uris` is required whenever the effective grant types include `authorization_code`. This requirement still applies when `grant_types` is omitted: `authorization_code` is part of the default (RFC 7591 Section 2). anyOf: - - title: authorization_code - description: >- - Effective grant types include `authorization_code` — explicitly, or by - default when `grant_types` is omitted. A non-empty `redirect_uris` is - required. Other grant types (`client_credentials`, `refresh_token`) can - be registered alongside. - allOf: - - type: object - required: - - redirect_uris - properties: - redirect_uris: - type: array - minItems: 1 - items: - type: string - format: uri - description: | - Redirect URIs for the `authorization_code` grant. - Compared with exact string matching at authorization time (RFC 6749 Section 3.1.2.3). - - $ref: ./RegisterClientMetadata.yaml - - title: client_credentials - description: >- - `grant_types` is provided and does not include `authorization_code` — - typically machine-to-machine clients using `client_credentials`. - No redirect URI is needed. - allOf: - - $ref: ./RegisterClientMetadata.yaml - - type: object - required: - - grant_types - properties: - grant_types: - not: - contains: - const: authorization_code + - $ref: ./RegisterClientAuthorizationCode.yaml + - $ref: ./RegisterClientCredentials.yaml