taler-docs

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

commit 0b8ba06aa18ebb97ca52e80b3f84e82eb04d8471
parent 77e72cd258a642c08648c8e666cc7c918abccc6a
Author: Christian Grothoff <christian@grothoff.org>
Date:   Thu,  6 Aug 2026 18:11:42 +0200

update challenger API spec to implementation

Diffstat:
Mcore/api-challenger.rst | 3++-
Mcore/challenger/get-config.rst | 14++++++++++++++
Mcore/challenger/get-info.rst | 42+++++++++++++++++++++++++++++++++++++-----
Mcore/challenger/post-authorize-NONCE.rst | 53+++++++++++++++++++++++++++++++++++++++++++++--------
Mcore/challenger/post-challenge-NONCE.rst | 86+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Mcore/challenger/post-setup-CLIENT_ID.rst | 48++++++++++++++++++++++++++++++++++++++----------
Mcore/challenger/post-solve-NONCE.rst | 80++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Mcore/challenger/post-token.rst | 104+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
8 files changed, 357 insertions(+), 73 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 **v7**. +The current protocol version is **v8**. * The Challenger SPA is currently targeting **v6**. @@ -97,6 +97,7 @@ The current protocol version is **v7**. * ``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. **Upcoming versions:** diff --git a/core/challenger/get-config.rst b/core/challenger/get-config.rst @@ -2,10 +2,24 @@ Obtain the key configuration settings of the storage service. + **Request:** + + The request takes no query parameters, headers or body. The response + is always ``application/json``; unlike ``/authorize`` and ``/solve``, + this endpoint does not perform content negotiation on ``Accept``. + **Response:** :http:statuscode:`200 OK`: Response is a `ChallengerConfigurationResponse`. + This endpoint has no failure mode of its own: the response is + 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``. + 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. .. ts:def:: ChallengerConfigurationResponse diff --git a/core/challenger/get-info.rst b/core/challenger/get-info.rst @@ -6,22 +6,52 @@ **Request:** Must include the token returned to the client from the :http:post:`/token </token>` endpoint - as a ``Bearer`` token in an ``Authorization`` header. + 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. **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. + :http:statuscode:`200 OK`: The body contains the address as a `ChallengerInfoResponse`. :http:statuscode:`401 Unauthorized`: The bearer token is missing, invalid (malformed) or unknown. + Error codes used are: + + * ``TALER_EC_GENERIC_PARAMETER_MISSING`` --- there is no + ``Authorization`` header (``error`` is ``invalid_request``, + ``detail`` is ``"Authorization"``). + * ``TALER_EC_GENERIC_PARAMETER_MALFORMED`` --- the ``Authorization`` + header does not use the ``Bearer`` scheme (``error`` is + ``invalid_request``, ``detail`` is ``"Authorization"``). + * ``TALER_EC_CHALLENGER_GRANT_UNKNOWN`` --- the token does not decode, + is unknown, or has expired (``error`` is ``invalid_token``). + + .. note:: + + A token that does not decode and a token that is simply unknown are + answered with a byte-identical response on purpose, so that a caller + cannot first learn whether a guessed token is well-formed and only + then whether it exists. :http:statuscode:`404 Not found`: The bearer token is invalid (includes unknown or expired). Returned with ``TALER_EC_CHALLENGER_GRANT_UNKNOWN``. - Removed in **v8** (to better match RFC 6750) + 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``. + Returned by the request router with an ``Allow`` header and an + **empty body**; in particular there is no Taler error code. :http:statuscode:`500 Internal Server Error`: The challenger service encountered an internal error, - typically a database failure. Usually returned with - ``TALER_EC_GENERIC_DB_FETCH_FAILED``. + 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. **Details::** @@ -38,7 +68,9 @@ // address_type. address: Object; - // Type of the address. + // Type of the address. This is the ``address_type`` of the + // challenger service as a whole (see ``/config``), not a + // per-token value. address_type: string; // How long do we consider the address to be diff --git a/core/challenger/post-authorize-NONCE.rst b/core/challenger/post-authorize-NONCE.rst @@ -28,22 +28,56 @@ :http:statuscode:`200 OK`: The the response is a `ChallengeStatusResponse`. Since protocol **v1**. + The response carries ``Cache-Control: no-store,no-cache``. :http:statuscode:`302 Found`: Returned when the client explicitly accepts ``text/html`` returning a redirection to the WebUI. Since protocol **v1**. + The ``Location`` is the relative URL ``/webui/`` followed by the query + 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. :http:statuscode:`400 Bad Request`: - The request does not follow the spec. - The response will include error - code, hint and detail. Since protocol **v1**. + The request does not follow the spec. Since protocol **v1**. + Error codes used are: + + * ``TALER_EC_GENERIC_PARAMETER_MISSING`` --- ``response_type``, + ``client_id`` or (when a ``code_challenge_method`` was given) + ``code_challenge`` is absent; ``detail`` names the argument. + * ``TALER_EC_GENERIC_PARAMETER_MALFORMED`` --- the ``$NONCE`` in the URL + is not a valid 52-character Crockford-base32 value (``detail`` is + ``"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 + combined with a ``plain``/absent ``code_challenge_method`` (the PKCE + downgrade guard); ``detail`` names the argument. :http:statuscode:`404 Not found`: - The service is unaware of a matching challenge. - The response will include error - code, hint and detail. Since protocol **v1**. + The service is unaware of a matching challenge. Since protocol **v1**. + Returned with ``TALER_EC_CHALLENGER_GENERIC_VALIDATION_UNKNOWN`` when + the nonce is + well-formed but no matching validation was updated. This deliberately + conflates four causes: the nonce is unknown, it has expired, the + ``client_id`` does not own it, or the ``redirect_uri`` does not match + 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``. + Returned by the request router with an ``Allow`` header and an + **empty body**; in particular there is no Taler error code. :http:statuscode:`500 Internal Server Error`: Server is not able to respond due to internal problems. - The response will include error - code, hint and detail. Since protocol **v1**. + Since protocol **v1**. Returned with + ``TALER_EC_GENERIC_DB_STORE_FAILED`` (``detail`` is + ``"update_validation"``), both for a hard database error and for a + serialization failure that survived all retries. + + .. note:: + + Unlike RFC 6749 section 4.1.2.1, errors are never reported by redirecting + the user-agent back to the ``redirect_uri`` with an ``error`` argument; + all failures above are returned as a JSON body, even when the request + asked for ``text/html``. .. ts:def:: ChallengeStatusResponse @@ -58,6 +92,9 @@ // May contain a boolean field ``read_only`` indicating if // the client is not allowed to change the address when posting // it to the ``/challenge`` endpoint. + // If ``read_only`` is present and true, the service forces + // ``fix_address`` to true and ``changes_left`` to 0. + // Omitted entirely (not null) if no address was submitted yet. last_address?: Object; // is the challenge already solved? diff --git a/core/challenger/post-challenge-NONCE.rst b/core/challenger/post-challenge-NONCE.rst @@ -5,7 +5,14 @@ **Request:** - Body should use the mime-type "application/x-www-form-urlencoded". + Body should use the mime-type "application/x-www-form-urlencoded"; + ``multipart/form-data`` is accepted as well. Alternatively, the address + may be uploaded directly as a JSON object using the mime-type + ``application/json``. In the form encodings, each field name/value pair + becomes one string-valued member of the address object, and each field is + limited to 1024 bytes. The total request body is limited to 1024 bytes + for the form encodings. + The posted form data must contain an address JSON object that follow the restrictions defined in :ref:`config <challenger-config>`. @@ -13,17 +20,33 @@ of ``/setup`` was set to be ``read_only`` and that was subsequently returned in the `ChallengeStatusResponse`, then the body - must not change that address, including preservation of the - ``read_only`` field. + must not change that address. The ``read_only`` field itself is + ignored when comparing the addresses and is re-inserted by the service, + so the client need not (but may) preserve it. **Response:** :http:statuscode:`200 OK`: The response is `ChallengeResponse`. Since protocol **v2**. + The ``created`` variant carries ``Cache-Control: no-store,no-cache``. :http:statuscode:`400 Bad Request`: - The request does not follow the spec. - The response will include error - code, hint and detail. Since protocol **v1**. + The request does not follow the spec. Since protocol **v1**. + Error codes used are: + + * ``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. + * ``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`` + names the offending field, so the user agent can highlight it and + show the corresponding ``hint``/``hint_i18n``. Since protocol + **v8**. + * ``TALER_EC_GENERIC_JSON_INVALID`` --- an ``application/json`` body is + not well-formed JSON. :http:statuscode:`403 Forbidden`: The address being submitted differs from the previously submitted address but the validation process was set up @@ -32,25 +55,62 @@ ``TALER_EC_CHALLENGER_CLIENT_FORBIDDEN_READ_ONLY``. Since protocol **v4**. :http:statuscode:`404 Not Found`: - The service is unaware of a matching challenge. - The response will include error - code, hint and detail. Since protocol **v1**. + The service is unaware of a matching challenge. Since protocol **v1**. + Returned with ``TALER_EC_CHALLENGER_GENERIC_VALIDATION_UNKNOWN`` when + the nonce is well-formed but unknown, or the validation has expired. + These two cases are not distinguished. + :http:statuscode:`405 Method Not Allowed`: + 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:`413 Request entity too large`: + The request body exceeds the 1024 byte limit. + Returned with ``TALER_EC_GENERIC_UPLOAD_EXCEEDS_LIMIT``. :http:statuscode:`429 Too Many Requests`: There have been too many attempts to request challenge transmissions for this $NONCE. The user-agent should wait and (eventually) request a fresh nonce to be set up by the client. - Returned with ``TALER_EC_CHALLENGER_TOO_MANY_ATTEMPTS``. - Since protocol **v2**. + Since protocol **v2**. Two distinct situations are distinguished, + since the appropriate recovery differs. Since protocol **v8**: + + * ``TALER_EC_CHALLENGER_TOO_MANY_ADDRESS_CHANGES`` --- the number of + permitted *address changes* was exhausted. The user must obtain a + fresh nonce from the client. + * ``TALER_EC_CHALLENGER_TOO_MANY_PIN_TRANSMISSIONS`` --- the number of + permitted *PIN transmissions* for the current address was exhausted. + The user may still try a different address if address changes remain. + + Note that merely being within the retransmission cooldown is + **not** an error: it is reported as ``200 OK`` with ``transmitted`` + set to false. :http:statuscode:`500 Internal Server Error`: Server is not able to respond due to internal problems. - The response will include error - code, hint and detail. Since protocol **v1**. + Since protocol **v1**. Error codes used are: + + * ``TALER_EC_GENERIC_DB_FETCH_FAILED`` --- reading the previously stored + address failed (``detail`` is ``"get_validation_address"``). + * ``TALER_EC_GENERIC_DB_STORE_FAILED`` --- storing the address or + confirming the transmitted PIN failed (``detail`` is + ``"do_challenge_address"`` or ``"do_challenge_address_confirm_pin"``). + * ``TALER_EC_GENERIC_FAILED_TO_EXPAND_TEMPLATE`` --- expanding + ``MESSAGE_TEMPLATE_FILE`` for the challenge message failed. + * ``TALER_EC_GENERIC_PARSER_OUT_OF_MEMORY`` --- the service ran out of + memory while buffering an ``application/json`` body. + * ``TALER_EC_CHALLENGER_ADDRESS_RESTRICTION_MALFORMED`` --- the + ``ADDRESS_RESTRICTIONS`` configuration for the field named in + ``detail`` has no regular expression, or one that failed to compile. + This is an operator error, not a client error; the request is + refused because a restriction that cannot be evaluated must not be + treated as "no restriction". Since protocol **v8**. :http:statuscode:`502 Bad Gateway`: The challenger service failed to launch or communicate with its helper process for delivering the challenge (SMS, e-mail, postal mail). Returned with ``TALER_EC_CHALLENGER_HELPER_EXEC_FAILED``. + The ``detail`` distinguishes the failure: ``"pipe"``, ``"exec"``, + ``"write"``, or ``"$EXIT_CODE/$PROCESS_STATUS"`` when the helper + terminated abnormally or with a non-zero exit code. .. ts:def:: ChallengeResponse diff --git a/core/challenger/post-setup-CLIENT_ID.rst b/core/challenger/post-setup-CLIENT_ID.rst @@ -9,6 +9,12 @@ **Request:** + The ``Authorization`` header is mandatory and must be of the form + ``Bearer secret-token:$CLIENT_SECRET``: the value is expected to carry the + RFC 8959 ``secret-token:`` prefix, and the prefix is part of the secret as + stored by ``challenger-admin``. The ``Bearer`` scheme token is matched + case-insensitively. + The body can be an address in JSON encoding to pre-initialize the address to be used by challenger for this process. If the body is absent, the user will have to enter the full address details. The specific address format depends @@ -23,20 +29,42 @@ :http:statuscode:`200 OK`: Response is a `ChallengeSetupResponse`. :http:statuscode:`400 Bad request`: - The request is malformed. Usually returned with an - error code of ``TALER_EC_GENERIC_PARAMETER_MISSING`` or - ``TALER_EC_GENERIC_PARAMETER_MALFORMED``. + 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`` + 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. + :http:statuscode:`403 Forbidden`: + The client did not present usable credentials. Since protocol **v8**; + previously reported as ``400``. In both cases ``detail`` is + ``"Authorization"``. Error codes used are: + + * ``TALER_EC_GENERIC_PARAMETER_MISSING`` --- there is no + ``Authorization`` header at all. + * ``TALER_EC_GENERIC_PARAMETER_MALFORMED`` --- the header is present but + does not use the ``Bearer`` scheme, or its value lacks the RFC 8959 + ``secret-token:`` prefix. :http:statuscode:`404 Not found`: - The challenger service is unaware of a matching client. - or the credentials of the client are invalid. - Usually returned with + The challenger service is unaware of a matching client, + or the credentials of the client are invalid. These two cases are + deliberately not distinguished. Always returned with ``TALER_EC_CHALLENGER_GENERIC_CLIENT_UNKNOWN``. + :http:statuscode:`405 Method Not Allowed`: + 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:`500 Internal server error`: The challenger service encountered an internal error. - Usually returned with - ``TALER_EC_GENERIC_DB_FETCH_FAILED`` or - ``TALER_EC_GENERIC_DB_STORE_FAILED`` or - ``TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE``. + Error codes used are: + + * ``TALER_EC_GENERIC_DB_STORE_FAILED`` --- the database transaction that + authenticates the client and inserts the validation failed, either + hard or by exhausting the retries for serialization failures + (``detail`` is ``"do_insert_validation"``). + * ``TALER_EC_GENERIC_PARSER_OUT_OF_MEMORY`` --- the service ran out of + memory while buffering the request body. **Details::** diff --git a/core/challenger/post-solve-NONCE.rst b/core/challenger/post-solve-NONCE.rst @@ -6,50 +6,100 @@ **Request:** - Body should use the mime-type "application/x-www-form-urlencoded". - The posted form data must contain a "pin" field. + Body should use the mime-type "application/x-www-form-urlencoded"; + ``multipart/form-data`` is accepted as well. + The posted form data must contain a "pin" field, whose value must be a + decimal unsigned integer. The request body is limited to 1024 bytes. **Response:** :http:statuscode:`200 OK`: 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 PIN is reported with 403, 409 or 429 (see below). :http:statuscode:`302 Found`: Only possible if request didn't ask for application/json. Since protocol **v2**. 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``), plus a ``code`` argument with the authorization code, and the - ``state`` argument from the ``/authorize`` endpoint. + ``state`` argument from the ``/authorize`` endpoint. The ``state`` + argument is omitted entirely if the client did not supply one. The + response body is the plain text ``Ok!``. :http:statuscode:`400 Bad Request`: - The request does not follow the spec. - The response will include error - code, hint and detail. Since protocol **v1**. + The request does not follow the spec. Since protocol **v1**. + Error codes used are: + + * ``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 the ``pin`` field is not a + decimal number (``detail`` is ``"pin"``). + * ``TALER_EC_GENERIC_PARAMETER_MISSING`` --- there is no ``pin`` field in + the body (``detail`` is ``"pin"``). :http:statuscode:`403 Forbidden`: + The PIN was checked and did not match. The response is `InvalidPinResponse`. Since protocol **v1**. + Returned with ``TALER_EC_CHALLENGER_INVALID_PIN``. :http:statuscode:`404 Not found`: - The service is unaware of a matching challenge. - The response will include error - code, hint and detail. Since protocol **v1**. + The service is unaware of a matching challenge, or the validation + has expired. Since protocol **v1**. Returned with + ``TALER_EC_CHALLENGER_GENERIC_VALIDATION_UNKNOWN``. + :http:statuscode:`405 Method Not Allowed`: + 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:`409 Conflict`: The service had never actually transmitted a TAN, so solving is naturally impossible. Since protocol **v8**. + The response is an `InvalidPinResponse` with ``no_challenge`` set to + true. Returned with + ``TALER_EC_CHALLENGER_NO_CHALLENGE_TRANSMITTED``. + :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:`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). - The response will include error - code, hint and detail. Since protocol **v2**. + Since protocol **v2**. Two cases are distinguished, and they differ + both in error code and in body shape: + + * ``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**. + * ``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``. :http:statuscode:`500 Internal Server Error`: Server is not able to respond due to internal problems. - The response will include error - code, hint and detail. Since protocol **v1**. + Since protocol **v1**. Returned with + ``TALER_EC_GENERIC_DB_FETCH_FAILED``; ``detail`` is + ``"do_solve_challenge"`` when solving failed and ``"get_validation"`` + when the subsequent construction of the redirect URL failed. + + .. 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. + + .. note:: + + Once a challenge has been solved, repeating the request for the same + (unexpired) ``$NONCE`` succeeds again regardless of the ``pin`` + submitted, re-issuing the redirect and authorization code. .. ts:def:: ChallengeSolveResponse - // Union discriminated by the "type" field. - type ChallengeSolveResponse = ChallengeRedirect | InvalidPinResponse; + // Only the "completed" variant occurs with a 200 status; the + // "pending" variant is returned with a 403, 409 or 429 status. + type ChallengeSolveResponse = ChallengeRedirect; .. ts:def:: InvalidPinResponse diff --git a/core/challenger/post-token.rst b/core/challenger/post-token.rst @@ -8,7 +8,8 @@ **Request:** - The request must include an ``application/www-form-urlencoded`` body + The request must include an ``application/x-www-form-urlencoded`` body + (``multipart/form-data`` is accepted as well) specifying the ``client_id``, ``redirect_uri``, ``client_secret``, ``code`` and ``grant_type``. The ``grant_type`` must be set to ``authorization_code``. The ``redirect_uri`` must match the URI from @@ -16,46 +17,107 @@ returned to the user. The ``client_id`` and ``client_secret`` must match the usual client credentials. Since protocol **v3**, ``code_verifier`` can also be included. + The request body is limited to 2048 bytes, as is each individual field. + Note that the client credentials must be passed in the body + (``client_secret_post``); HTTP Basic authentication + (``client_secret_basic``) is not supported. + **Response:** Error responses follow RFC 6749, section 5.2 with an "error" field in JSON, - as well as also returning GNU Taler style error messages. + 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. :http:statuscode:`200 OK`: The body will be a `ChallengerAuthResponse`. :http:statuscode:`400 Bad Request`: A required POST field (``grant_type``, ``client_id``, ``client_secret``, ``code`` or ``redirect_uri``) is missing - or malformed, or ``grant_type`` is not ``authorization_code``. - Usually returned with ``TALER_EC_GENERIC_PARAMETER_MISSING`` - ``TALER_EC_CHALLENGER_CLIENT_FORBIDDEN_BAD_CODE``, - ``TALER_EC_CHALLENGER_GENERIC_VALIDATION_UNKNOWN``, - ``TALER_EC_CHALLENGER_GRANT_UNKNOWN`` - or ``TALER_EC_GENERIC_PARAMETER_MALFORMED``. + or malformed, ``grant_type`` is not ``authorization_code``, + or the authorization code was not accepted. + Error codes used are: + + * ``TALER_EC_GENERIC_PARAMETER_MISSING`` (``error`` is + ``invalid_request``, or ``invalid_grant`` for a missing + ``code_verifier``) --- a required field is absent; ``detail`` names it. + * ``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, + ``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 + ``invalid_grant``) --- the ``code`` is not well-formed, does not + correspond to a solved validation of this client, does not match its + recomputed authentication tag, or the ``code_verifier`` does not + match the stored ``code_challenge``. + * ``TALER_EC_CHALLENGER_GRANT_UNKNOWN`` (``error`` is + ``invalid_grant``) --- the authorization code was already redeemed, + or the validation expired between the two database transactions. + + .. note:: + + Several of these conditions are answered with a byte-identical + response on purpose, so that a caller cannot use ``/token`` as an + oracle to distinguish "this nonce does not exist" from "this + validation has no address" from "your authentication tag is wrong". :http:statuscode:`401 Unauthorized`: - Authentication of the client failed. Returned (per - RFC 6749, section 5.2) when the client credentials are - invalid, when the supplied ``code`` is malformed or does - not match the validation, when the ``redirect_uri`` does - not match the one registered with the client, or when the - ``code_verifier`` does not match the saved - ``code_challenge``. Returned with - ``TALER_EC_CHALLENGER_GENERIC_CLIENT_FORBIDDEN_BAD_REDIRECT_URI``, - or ``TALER_EC_CHALLENGER_GENERIC_CLIENT_UNKNOWN``. - PKCE-related rejections are since protocol **v3**. + Authentication of the *client* failed (per RFC 6749, section 5.2): + either the ``client_id``/``client_secret`` pair is invalid, or the + ``redirect_uri`` does not match the one registered with the client. + Error codes used are: + + * ``TALER_EC_CHALLENGER_GENERIC_CLIENT_UNKNOWN`` + * ``TALER_EC_CHALLENGER_GENERIC_CLIENT_FORBIDDEN_BAD_REDIRECT_URI`` + + Both are returned with ``error`` set to ``invalid_client`` and a + ``WWW-Authenticate: Bearer error="invalid_client"`` header. + Note that failures concerning the ``code`` itself --- including + PKCE ``code_verifier`` mismatches, which exist since protocol **v3** + --- are reported with ``400``, not ``401``. + :http:statuscode:`405 Method Not Allowed`: + 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:`409 Conflict`: A ``code`` was presented for a validation process for which the user has not (yet) submitted any address, so the token cannot be issued. Returned with ``TALER_EC_CHALLENGER_MISSING_ADDRESS``. - Removed in **v8** (to better match RFC 6749, section 5.2) + Removed in **v8** (to better match RFC 6749, section 5.2); + the condition is now reported as ``400`` with + ``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``. :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 or Base64 helpers used for PKCE verification. Error codes used are: - * ``TALER_EC_CHALLENGER_GENERIC_DB_FETCH_FAILED`` - * ``TALER_EC_CHALLENGER_GENERIC_DB_STORE_FAILED`` + + * ``TALER_EC_GENERIC_DB_FETCH_FAILED`` --- ``detail`` is + ``"get_client"`` or ``"get_validation_pkce"``. + * ``TALER_EC_GENERIC_DB_STORE_FAILED`` --- ``detail`` is + ``"do_insert_token"``. + * ``TALER_EC_GENERIC_DB_SOFT_FAILURE`` --- a serialization failure that + survived all retries. + * ``TALER_EC_GENERIC_DB_INVARIANT_FAILURE`` (``error`` is + ``server_error``) --- the stored validation has a ``code_challenge`` + but no challenge method. + * ``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.) **Details::**