From 0ec2d4ff75ea01689252d32b7239366e504afe05 Mon Sep 17 00:00:00 2001 From: DmitryAnansky Date: Thu, 20 Aug 2026 15:59:18 +0300 Subject: [PATCH 01/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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/12] 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