commit 44e4d30aaa7d3326beb812c934440aa699d00dcd
parent 15e28394170f90ea178271d604d01e65b0964519
Author: Christian Grothoff <christian@grothoff.org>
Date: Wed, 23 Sep 2026 14:49:58 +0200
challenger: document v10 reporting of permanently failed validations
Issue: https://bugs.taler.net/n/11740
Signed-off-by: Christian Grothoff <christian@grothoff.org>
Diffstat:
4 files changed, 79 insertions(+), 25 deletions(-)
diff --git a/core/api-challenger.rst b/core/api-challenger.rst
@@ -89,7 +89,7 @@ verified address of the user.
Version History
---------------
-The current protocol version is **v9**.
+The current protocol version is **v10**.
* The Challenger SPA is currently targeting **v6**.
@@ -100,6 +100,10 @@ The current protocol version is **v9**.
* ``v8``: HTTP status code and error code corrections.
* ``v9``: adds ``expires`` to the :http:post:`/setup/$CLIENT_ID
</setup/$CLIENT_ID>` response.
+* ``v10``: a validation the user can no longer pass is reported to the
+ client with an ``access_denied`` error redirect;
+ :http:post:`/solve/$NONCE`, :http:post:`/challenge/$NONCE` and
+ :http:post:`/authorize/$NONCE` answer it with ``410 Gone``.
**Upcoming versions:**
diff --git a/core/challenger/post-authorize-NONCE.rst b/core/challenger/post-authorize-NONCE.rst
@@ -37,6 +37,8 @@
string of the request with a ``nonce=$NONCE`` argument appended.
Note that a request without any ``Accept`` header, or with
``Accept: */*``, is answered with ``200 OK`` and JSON instead.
+ Since protocol **v10**, a validation that failed permanently is
+ instead redirected to the client, see ``410 Gone`` below.
:http:statuscode:`400 Bad Request`:
The request does not follow the spec. Since protocol **v1**.
Error codes used are:
@@ -72,6 +74,18 @@
``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:`410 Gone`:
+ The validation failed permanently: the user exhausted every address
+ change, TAN transmission and TAN attempt (an address the client set
+ as ``read_only`` counts as one that cannot be changed), so it can never
+ succeed. Returned with ``TALER_EC_CHALLENGER_VALIDATION_FAILED``; the
+ response is a `ValidationFailedResponse`. The user-agent must send the
+ user to its ``redirect_url`` so that the client learns about the
+ failure. If the request asked for ``text/html``, the service instead
+ redirects to that URL with a ``302 Found`` right away.
+ Since protocol **v10**.
+ This lets a user who returns to the validation get back to the
+ client.
: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
@@ -63,6 +63,16 @@
The request used a method other than ``POST`` or ``OPTIONS``.
Returned by the request router with an ``Allow`` header and an
**empty body**; in particular there is no Taler error code.
+ :http:statuscode:`410 Gone`:
+ The validation failed permanently: the user exhausted every address
+ change, TAN transmission and TAN attempt (an address the client set
+ as ``read_only`` counts as one that cannot be changed), so it can never
+ succeed. Returned with ``TALER_EC_CHALLENGER_VALIDATION_FAILED``; the
+ response is a `ValidationFailedResponse`. The user-agent must send the
+ user to its ``redirect_url`` so that the client learns about the
+ failure. If the request asked for ``text/html``, the service instead
+ redirects to that URL with a ``302 Found`` right away.
+ Since protocol **v10**.
:http:statuscode:`413 Request entity too large`:
The request body exceeds the 1024 byte limit.
Returned with ``TALER_EC_GENERIC_UPLOAD_EXCEEDS_LIMIT``.
diff --git a/core/challenger/post-solve-NONCE.rst b/core/challenger/post-solve-NONCE.rst
@@ -17,9 +17,12 @@
If the request ask for application/json the response is
a `ChallengeSolveResponse`. Since protocol **v2**.
Note that this status is only used for the *successful* outcome; an
- incorrect or unusable TAN is reported with 403, 409 or 429 (see below).
+ incorrect or unusable TAN is reported with 403, 409, 410 or 429 (see
+ below).
:http:statuscode:`302 Found`:
Only possible if request didn't ask for application/json. Since protocol **v2**.
+ Since protocol **v10**, also returned for a validation that failed
+ permanently, see ``410 Gone`` below.
The user is redirected to the redirect URI of the client to pass the
grant to the client. The target will be the redirect URI specified
by the client (during registration and again upon ``/authorize``),
@@ -56,6 +59,22 @@
The response is an `InvalidPinResponse` with ``no_challenge`` set to
true. Returned with
``TALER_EC_CHALLENGER_NO_CHALLENGE_TRANSMITTED``.
+ :http:statuscode:`410 Gone`:
+ The validation failed permanently: the user exhausted every address
+ change, TAN transmission and TAN attempt (an address the client set
+ as ``read_only`` counts as one that cannot be changed), so it can never
+ succeed. Returned with ``TALER_EC_CHALLENGER_VALIDATION_FAILED``; the
+ response is a `ValidationFailedResponse`. The user-agent must send the
+ user to its ``redirect_url`` so that the client learns about the
+ failure. If the request asked for ``text/html``, the service instead
+ redirects to that URL with a ``302 Found`` right away.
+ Since protocol **v10**.
+ 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.
+ Before protocol **v10**, this situation was reported as ``429`` with
+ ``TALER_EC_CHALLENGER_TOO_MANY_ATTEMPTS`` and the client was never
+ told.
:http:statuscode:`413 Request entity too large`:
The request body exceeds the 1024 byte limit.
Returned with ``TALER_EC_GENERIC_UPLOAD_EXCEEDS_LIMIT``.
@@ -65,26 +84,13 @@
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**. 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 TAN guesses but may still request a retransmission or change the
- address. ``exhausted`` is true. Since protocol **v8**.
- * ``TALER_EC_CHALLENGER_TOO_MANY_ATTEMPTS`` --- the user has exhausted
- address changes, TAN 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.
+ The user has run out of TAN guesses for the current TAN, but may
+ still request a retransmission or change the address.
+ Since protocol **v2**. The body is an `InvalidPinResponse` with
+ ``exhausted`` set to true, returned with
+ ``TALER_EC_CHALLENGER_NO_PIN_ATTEMPTS_LEFT`` (since protocol **v8**).
+ Once nothing is left to try, the service answers ``410`` instead (since
+ protocol **v10**).
:http:statuscode:`500 Internal Server Error`:
Server is not able to respond due to internal problems.
Since protocol **v1**. Returned with
@@ -95,8 +101,8 @@
.. note::
Error responses are always JSON, even when the request asked for
- ``text/html``; only the success case honours the ``Accept`` header by
- returning a 302 redirect.
+ ``text/html``; only the success case and a permanently failed
+ validation honour the ``Accept`` header by returning a 302 redirect.
.. note::
@@ -108,10 +114,30 @@
// Only the "completed" variant occurs with a 200 status; the
// "pending" variant (`InvalidPinResponse`) is returned with a 403,
- // 409 or 429 status. Since **v8** every unsuccessful /solve uses
+ // 409 or 429 status, and a failed validation with a 410
+ // (`ValidationFailedResponse`). Since **v8** every unsuccessful /solve uses
// that one shape, so a client need only parse `InvalidPinResponse`.
type ChallengeSolveResponse = ChallengeRedirect;
+ .. ts:def:: ValidationFailedResponse
+
+ // Since protocol **v10**.
+ interface ValidationFailedResponse {
+ // TALER_EC_CHALLENGER_VALIDATION_FAILED
+ code: Integer;
+
+ // human-readable description of the error
+ hint: string;
+
+ // Where the user-agent must send the user: the client's redirect
+ // URI with the RFC 6749 section 4.1.2.1 error response arguments
+ // ``error=access_denied``, an ``error_description`` and the
+ // ``state`` given to ``/authorize`` (omitted if there was none).
+ // Absent if the service does not know a redirect URI for the
+ // validation, which cannot happen once ``/authorize`` succeeded.
+ redirect_url?: string;
+ }
+
.. ts:def:: InvalidPinResponse
interface InvalidPinResponse {