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:
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::**