taler-docs

Documentation for GNU Taler components, APIs and protocols
Log | Files | Refs | README | LICENSE

commit 645e8db76727cc918d063a7c82cbd0a4a9f2f9b9
parent f1fd027c473738f4a443ef9c5c9f2d066244f45f
Author: Christian Grothoff <christian@grothoff.org>
Date:   Thu,  6 Aug 2026 23:03:53 +0200

make challenger API more RFC-compatible

Diffstat:
Mcore/api-challenger.rst | 2+-
Mcore/challenger/get-config.rst | 5+++--
Mcore/challenger/get-info.rst | 38++++++++++++++++++++++++++------------
Mcore/challenger/post-authorize-NONCE.rst | 13++++++++++---
Mcore/challenger/post-challenge-NONCE.rst | 13+++++++++----
Mcore/challenger/post-setup-CLIENT_ID.rst | 7++++++-
Mcore/challenger/post-solve-NONCE.rst | 31+++++++++++++++++++++----------
Mcore/challenger/post-token.rst | 27++++++++++++++++-----------
8 files changed, 92 insertions(+), 44 deletions(-)

diff --git a/core/api-challenger.rst b/core/api-challenger.rst @@ -97,7 +97,7 @@ The current protocol version is **v8**. * ``v6``: add the ``address_type`` field to :http:get:`/config </config>` * ``v7``: adds ``build_version`` to :http:get:`/config </config>` -* ``v8``: HTTP status code and error code corrections. +* ``v8``: HTTP status code and error code corrections. **Upcoming versions:** diff --git a/core/challenger/get-config.rst b/core/challenger/get-config.rst @@ -16,10 +16,11 @@ computed once at startup from the configuration, so no error code is ever returned. :http:statuscode:`405 Method Not Allowed`: - The request used a method other than ``GET`` or ``OPTIONS``. + The request used a method other than ``GET``, ``HEAD`` or ``OPTIONS``. Returned by the request router with an ``Allow`` header and an **empty body**; in particular there is no Taler error code. - Note that ``HEAD`` is currently *not* accepted either. + ``HEAD`` is handled exactly like ``GET`` but without a response body, + as required by RFC 9110 section 9.3.2; since protocol **v8**. .. ts:def:: ChallengerConfigurationResponse diff --git a/core/challenger/get-info.rst b/core/challenger/get-info.rst @@ -7,14 +7,20 @@ Must include the token returned to the client from the :http:post:`/token </token>` endpoint as a ``Bearer`` token in an ``Authorization`` header. The scheme token is - matched case-insensitively, but exactly one space must separate it from the - access token. No query parameters or request body are read. + matched case-insensitively and may be followed by any amount of + whitespace. No query parameters or request body are read. **Response:** - Error responses at the ``401`` status carry an RFC 6749 section 5.2 - ``error`` field in the JSON body and a corresponding RFC 6750 section 3 - ``WWW-Authenticate: Bearer error="..."`` header. + Every error response carries an RFC 6749 section 5.2 ``error`` field in + the JSON body, and every ``401`` additionally carries the RFC 6750 + section 3 challenge:: + + WWW-Authenticate: Bearer realm="challenger", error="...", error_description="..." + + As required by RFC 6750 section 3, the challenge answering a request that + carried *no* authentication credentials at all names only the realm and + omits both ``error`` and ``error_description``. :http:statuscode:`200 OK`: The body contains the address as a `ChallengerInfoResponse`. @@ -23,8 +29,9 @@ Error codes used are: * ``TALER_EC_GENERIC_PARAMETER_MISSING`` --- there is no - ``Authorization`` header (``error`` is ``invalid_request``, - ``detail`` is ``"Authorization"``). + ``Authorization`` header (``detail`` is ``"Authorization"``). Since + protocol **v8** the JSON body and the ``WWW-Authenticate`` challenge + omit ``error`` in this case, per RFC 6750 section 3. * ``TALER_EC_GENERIC_PARAMETER_MALFORMED`` --- the ``Authorization`` header does not use the ``Bearer`` scheme (``error`` is ``invalid_request``, ``detail`` is ``"Authorization"``). @@ -47,15 +54,22 @@ Removed in **v8** (to better match RFC 6750); the condition is now reported as ``401``. :http:statuscode:`405 Method Not Allowed`: - The request used a method other than ``GET`` or ``OPTIONS``. + The request used a method other than ``GET``, ``HEAD`` or ``OPTIONS``. Returned by the request router with an ``Allow`` header and an **empty body**; in particular there is no Taler error code. + ``HEAD`` is handled exactly like ``GET`` but without a response body, + as required by RFC 9110 section 9.3.2; since protocol **v8**. :http:statuscode:`500 Internal Server Error`: The challenger service encountered an internal error, - typically a database failure. Always returned with - ``TALER_EC_GENERIC_DB_FETCH_FAILED`` and ``detail`` set to - ``"get_token"``. Unlike the other endpoints, this covers both a hard - database error and a serialization failure that survived all retries. + typically a database failure. ``detail`` is ``"get_token"`` and + ``error`` is ``server_error``. Error codes used are: + + * ``TALER_EC_GENERIC_DB_FETCH_FAILED`` --- a hard database error. + * ``TALER_EC_GENERIC_DB_SOFT_FAILURE`` --- a serialization failure that + survived all retries; retrying the request may succeed. + Since protocol **v8**, matching :http:post:`/token </token>`; + previously this case also used + ``TALER_EC_GENERIC_DB_FETCH_FAILED``. **Details::** diff --git a/core/challenger/post-authorize-NONCE.rst b/core/challenger/post-authorize-NONCE.rst @@ -49,9 +49,12 @@ ``"nonce"``; since protocol **v8**, previously reported as ``404``), ``response_type`` is not ``code``, ``client_id`` is not a number, ``code_challenge_method`` is - neither ``plain`` nor ``S256``, or a non-web ``redirect_uri`` was + neither ``plain`` nor ``S256``, a non-web ``redirect_uri`` was combined with a ``plain``/absent ``code_challenge_method`` (the PKCE - downgrade guard); ``detail`` names the argument. + downgrade guard), or one of ``redirect_uri``, ``state``, ``scope`` + and ``code_challenge`` is not valid UTF-8 (since protocol **v8**; + previously such a value reached the database and produced a + ``500``); ``detail`` names the argument. :http:statuscode:`404 Not found`: The service is unaware of a matching challenge. Since protocol **v1**. Returned with ``TALER_EC_CHALLENGER_GENERIC_VALIDATION_UNKNOWN`` when @@ -62,9 +65,13 @@ the one registered for the client. Distinguishing them would let an unauthenticated caller enumerate validations. :http:statuscode:`405 Method Not Allowed`: - The request used a method other than ``GET``, ``POST`` or ``OPTIONS``. + The request used a method other than ``GET``, ``HEAD``, ``POST`` or + ``OPTIONS``. Returned by the request router with an ``Allow`` header and an **empty body**; in particular there is no Taler error code. + ``HEAD`` is accepted on every endpoint that accepts ``GET``, and is + handled identically but without a response body (RFC 9110 section + 9.3.2); since protocol **v8**. :http:statuscode:`500 Internal Server Error`: Server is not able to respond due to internal problems. Since protocol **v1**. Returned with diff --git a/core/challenger/post-challenge-NONCE.rst b/core/challenger/post-challenge-NONCE.rst @@ -35,10 +35,10 @@ * ``TALER_EC_GENERIC_PARAMETER_MALFORMED`` --- the ``$NONCE`` in the URL is not a valid 52-character Crockford-base32 value (``detail`` is - ``"nonce"``), the ``Content-Type`` is missing or unusable (``detail`` - is ``"Content-Type"``), the ``Content-Length`` header is not a number - (``detail`` is ``"Content-Length"``), or a submitted field name or - value is not valid UTF-8. + ``"nonce"``), the ``Content-Length`` header is not a number + (``detail`` is ``"Content-Length"``), an ``application/json`` body is + valid JSON but not an object (``detail`` is ``"address"``), or a + submitted field name or value is not valid UTF-8. * ``TALER_EC_CHALLENGER_ADDRESS_RESTRICTION_VIOLATED`` --- a field is absent or violates the regular expression configured for it in ``restrictions``, see :ref:`config <challenger-config>`; ``detail`` @@ -66,6 +66,11 @@ :http:statuscode:`413 Request entity too large`: The request body exceeds the 1024 byte limit. Returned with ``TALER_EC_GENERIC_UPLOAD_EXCEEDS_LIMIT``. + :http:statuscode:`415 Unsupported Media Type`: + The ``Content-Type`` is missing or is not one the service can parse. + Returned with ``TALER_EC_GENERIC_PARAMETER_MALFORMED`` and ``detail`` + set to ``"Content-Type"``. + Since protocol **v8**; previously reported as ``400``. :http:statuscode:`429 Too Many Requests`: There have been too many attempts to request challenge transmissions for this $NONCE. The user-agent should diff --git a/core/challenger/post-setup-CLIENT_ID.rst b/core/challenger/post-setup-CLIENT_ID.rst @@ -22,6 +22,10 @@ ``read_only`` bit that has a special meaning independent of the address type: it informs Challenger that the address should not be editable. + The body is optional: a request with no body at all, or with a body of + length zero, is accepted and leaves the address unset. If a body is + present it must be a JSON **object**; any other JSON value is rejected. + Passing an address in the ``/setup`` body is supported @since protocol **v4**. **Response:** @@ -32,7 +36,8 @@ The request is malformed. Error codes used are: * ``TALER_EC_GENERIC_PARAMETER_MALFORMED`` --- the ``$CLIENT_ID`` in the - URL is not a number (``detail`` is ``"client_id"``), or ``read_only`` + URL is not a number (``detail`` is ``"client_id"``), the body is valid + JSON but not an object (``detail`` is ``"address"``), or ``read_only`` is present in the body but is not a boolean (``detail`` is ``"read_only"``). * ``TALER_EC_GENERIC_JSON_INVALID`` --- the body is not well-formed JSON. diff --git a/core/challenger/post-solve-NONCE.rst b/core/challenger/post-solve-NONCE.rst @@ -33,8 +33,7 @@ * ``TALER_EC_GENERIC_PARAMETER_MALFORMED`` --- the ``$NONCE`` in the URL is not a valid 52-character Crockford-base32 value (``detail`` is - ``"nonce"``), the ``Content-Type`` is missing or unusable (``detail`` - is ``"Content-Type"``), the ``Content-Length`` header is not a number + ``"nonce"``), the ``Content-Length`` header is not a number (``detail`` is ``"Content-Length"``), or the ``pin`` field is not a decimal number (``detail`` is ``"pin"``). * ``TALER_EC_GENERIC_PARAMETER_MISSING`` --- there is no ``pin`` field in @@ -60,22 +59,32 @@ :http:statuscode:`413 Request entity too large`: The request body exceeds the 1024 byte limit. Returned with ``TALER_EC_GENERIC_UPLOAD_EXCEEDS_LIMIT``. + :http:statuscode:`415 Unsupported Media Type`: + The ``Content-Type`` is missing or is not one the service can parse. + Returned with ``TALER_EC_GENERIC_PARAMETER_MALFORMED`` and ``detail`` + set to ``"Content-Type"``. + Since protocol **v8**; previously reported as ``400``. :http:statuscode:`429 Too Many Requests`: There have been too many attempts to solve the challenge for this address (and $NONCE). The user-agent should either try a different address (or wait and (eventually) request a fresh nonce to be set up by the client). - Since protocol **v2**. Two cases are distinguished, and they differ - both in error code and in body shape: + Since protocol **v2**. The body is an `InvalidPinResponse` in both of + the cases below, which are told apart by the error code: * ``TALER_EC_CHALLENGER_NO_PIN_ATTEMPTS_LEFT`` --- the user has run out of PIN guesses but may still request a retransmission or change the - address. The body is an `InvalidPinResponse` with ``exhausted`` set - to true. Since protocol **v8**. + address. ``exhausted`` is true. Since protocol **v8**. * ``TALER_EC_CHALLENGER_TOO_MANY_ATTEMPTS`` --- the user has exhausted - address changes, PIN guesses *and* retransmissions. The situation is - terminal and the body is a plain error object with only ``code``, - ``hint`` and ``detail``. + address changes, PIN guesses *and* retransmissions, so the situation + is terminal and all three counters are zero. Since protocol **v8** + this returns an `InvalidPinResponse` as well; it previously returned + a plain error object with only ``code``, ``hint`` and ``detail``. + Note that the response *consuming* the very last guess already + reports this, rather than + ``TALER_EC_CHALLENGER_INVALID_PIN`` with a ``403``: at that point + nothing is left to try, which is the more useful thing to tell the + user. :http:statuscode:`500 Internal Server Error`: Server is not able to respond due to internal problems. Since protocol **v1**. Returned with @@ -98,7 +107,9 @@ .. ts:def:: ChallengeSolveResponse // Only the "completed" variant occurs with a 200 status; the - // "pending" variant is returned with a 403, 409 or 429 status. + // "pending" variant (`InvalidPinResponse`) is returned with a 403, + // 409 or 429 status. Since **v8** every unsuccessful /solve uses + // that one shape, so a client need only parse `InvalidPinResponse`. type ChallengeSolveResponse = ChallengeRedirect; .. ts:def:: InvalidPinResponse diff --git a/core/challenger/post-token.rst b/core/challenger/post-token.rst @@ -28,12 +28,8 @@ as well as also returning GNU Taler style error messages. The ``error`` values used are ``invalid_request``, ``unsupported_grant_type``, ``invalid_client``, ``invalid_grant`` and ``server_error``. - - .. note:: - - The ``error`` field is currently omitted on some internal-error - responses, namely those reported with the generic database error codes, - and on ``413``. Clients must therefore treat ``error`` as optional. + Since protocol **v8** every error response of this endpoint carries the + ``error`` field, including ``415``, ``413`` and ``500``. :http:statuscode:`200 OK`: The body will be a `ChallengerAuthResponse`. @@ -50,7 +46,7 @@ * ``TALER_EC_GENERIC_PARAMETER_MALFORMED`` (``error`` is ``invalid_request``, or ``unsupported_grant_type`` when the ``grant_type`` is not ``authorization_code``) --- the - ``Content-Type`` or ``Content-Length`` header is unusable, + ``Content-Length`` header is not a number, ``client_id`` is not a number, or the ``code_verifier`` violates the length (43--128) or character-set rules of RFC 7636. * ``TALER_EC_CHALLENGER_CLIENT_FORBIDDEN_BAD_CODE`` (``error`` is @@ -101,7 +97,13 @@ ``TALER_EC_CHALLENGER_CLIENT_FORBIDDEN_BAD_CODE``. :http:statuscode:`413 Request entity too large`: The request body exceeds the 2048 byte limit. - Returned with ``TALER_EC_GENERIC_UPLOAD_EXCEEDS_LIMIT``. + Returned with ``TALER_EC_GENERIC_UPLOAD_EXCEEDS_LIMIT`` and ``error`` + set to ``invalid_request``. + :http:statuscode:`415 Unsupported Media Type`: + The ``Content-Type`` is missing or is not one the service can parse. + Returned with ``TALER_EC_GENERIC_PARAMETER_MALFORMED``, ``detail`` set + to ``"Content-Type"`` and ``error`` set to ``invalid_request``. + Since protocol **v8**; previously reported as ``400``. :http:statuscode:`500 Internal Server Error`: The challenger service encountered an internal error, for example a database failure or a failure of the SHA-256 @@ -120,9 +122,12 @@ * ``TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE`` (``error`` is ``server_error``) --- the SHA-256 or Base64 helper used for PKCE verification failed. - * ``TALER_EC_GENERIC_PARAMETER_MALFORMED`` --- the stored - ``code_challenge_method`` is not a known value. (Despite the name, - this indicates corrupt server state, not a bad request.) + + All of these carry ``error`` set to ``server_error``. Note that the + stored ``code_challenge_method`` being unknown is reported with + ``TALER_EC_GENERIC_DB_INVARIANT_FAILURE`` since protocol **v8**; + it previously used ``TALER_EC_GENERIC_PARAMETER_MALFORMED``, which + wrongly suggested the client's request was at fault. **Details::**