taler-docs

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

api-challenger.rst (21447B)


      1 ..
      2   This file is part of GNU TALER.
      3   Copyright (C) 2023, 2024 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify it under the
      6   terms of the GNU Affero General Public License as published by the Free Software
      7   Foundation; either version 2.1, or (at your option) any later version.
      8 
      9   TALER is distributed in the hope that it will be useful, but WITHOUT ANY
     10   WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11   A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more details.
     12 
     13   You should have received a copy of the GNU Affero General Public License along with
     14   TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15 
     16   @author Christian Grothoff
     17 
     18 .. _challenger-api:
     19 
     20 ==============================
     21 Challenger Service RESTful API
     22 ==============================
     23 
     24 The challenger service validates that a user is able to receive challenges at
     25 an address (such as e-mail or SMS) and allows an OAuth 2.0 client to obtain
     26 access to these validated addresses.
     27 
     28 The high-level flow is that an OAuth 2.0 client is first registered with the
     29 challenger service (via command-line). Using the command-line tool will print
     30 the resulting client ID to the console.
     31 
     32 .. note::
     33 
     34   Right now, registration of a unique redirection URI is *mandatory* for
     35   each client. If multiple redirection URIs are needed, it is suggested to
     36   just register additional clients.  (While OAuth 2.0 would support not
     37   registering fixed redirection URIs with a client, this is not recommended
     38   as it would create an open redirector.)
     39 
     40 Once a client is registered, that client can use the challenger service when
     41 it needs a user to prove that the user is able to receive messages at a
     42 particular address.  However, asking a user to prove access to a particular
     43 address can be expensive as it may involve sending an SMS or even postal mail
     44 depending on the type of address.  Thus, challenger does not allow a user
     45 agent to begin an address validation process without prior approval by a
     46 registered client.  Thus, the process begins with a ``/setup/$CLIENT_ID`` request where a
     47 client requests challenger to begin an address validation request.  The
     48 ``/setup/$CLIENT_ID`` response contains a ``nonce`` which is then used to construct the
     49 URL of the endpoint to which the client must redirect the user-agent to begin
     50 the address validation and authorization process.
     51 
     52 The client then redirects the user-agent to the ``/authorize/$NONCE`` endpoint
     53 of the challenger service, adding its ``state``, ``client_id`` and
     54 ``redirect_uri`` as query parameters.  The ``redirect_uri`` must match the
     55 redirect URI registered with the client. From this endpoint, the challenger
     56 service will return a Web page asking the user to provide its address.
     57 
     58 .. note::
     59 
     60   Challenger is a bit unusual in that the ``$NONCE`` in the endpoint URL
     61   makes the authorization endpoint URL (deliberately) unpredictable, while
     62   for many other OAuth 2.0 APIs this endpoint is static. However, this is
     63   compliant with OAuth 2.0 as determining the authorization URL is left out
     64   of the scope of the standard.
     65 
     66 When the user has filled in the form with their address, it will be submitted
     67 to the ``/challenge/$NONCE`` endpoint and the challenger service will send a
     68 challenge to the user's address and generate an HTML form asking the user to
     69 enter the received challenge value.
     70 
     71 The user can then enter the answer to the challenge which is then submitted to
     72 the ``/solve/$NONCE`` endpoint.  If the answer is correct, the user agent will
     73 be redirected to the client redirect URI that was specified by the OAuth 2.0
     74 client upon ``/authorize``, together with an authorization grant encoded in
     75 the redirection URI.
     76 
     77 Given this authorization grant, the OAuth 2.0 client can then use the
     78 ``/token`` endpoint to obtain an access token which will grant it access to
     79 the resource.
     80 
     81 Using the ``/info`` endpoint the client can then finally obtain the (now)
     82 verified address of the user.
     83 
     84 .. contents:: Table of Contents
     85   :local:
     86 
     87 
     88 ---------------
     89 Version History
     90 ---------------
     91 
     92 The current protocol version is **v6**.
     93 
     94 * The Challenger SPA is currently targeting **v6**.
     95 
     96 **Version history:**
     97 
     98 * ``v6``: add the ``address_type`` field to ``/config``
     99 
    100 **Upcoming versions:**
    101 
    102 * None anticipated.
    103 
    104 **Ideas for future version:**
    105 
    106 * ``vXXX``: marker for features not yet targeted for release
    107 
    108 
    109 .. include:: tos.rst
    110 
    111 .. _challenger-config:
    112 
    113 -----------------------
    114 Receiving Configuration
    115 -----------------------
    116 
    117 .. http:get:: /config
    118 
    119   Obtain the key configuration settings of the storage service.
    120 
    121   **Response:**
    122 
    123   Returns a `ChallengerTermsOfServiceResponse`.
    124 
    125   .. ts:def:: ChallengerTermsOfServiceResponse
    126 
    127     interface ChallengerTermsOfServiceResponse {
    128       // Name of the service
    129       name: "challenger";
    130 
    131       // libtool-style representation of the Challenger protocol version, see
    132       // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning
    133       // The format is "current:revision:age".
    134       version: string;
    135 
    136       // URN of the implementation (needed to interpret 'revision' in version).
    137       // @since v0, may become mandatory in the future.
    138       implementation?: string;
    139 
    140       // @since **v2**.
    141       // Object; map of keys (names of the fields of the address
    142       // to be entered by the user) to objects with a "regex" (string)
    143       // containing an extended Posix regular expression for allowed
    144       // address field values, and a "hint"/"hint_i18n" giving a
    145       // human-readable explanation to display if the value entered
    146       // by the user does not match the regex. Keys that are not mapped
    147       // to such an object have no restriction on the value provided by
    148       // the user.  See "ADDRESS_RESTRICTIONS" in the challenger configuration.
    149       restrictions: Object;
    150 
    151       // @since **v6**
    152       // Defines the set of fields asked to the user.
    153       // The field names are registered via GANA at
    154       // https://git.taler.net/gana.git/tree/gnu-taler-form-attributes
    155       // email: CONTACT_EMAIL
    156       // phone: CONTACT_PHONE
    157       // postal: CONTACT_NAME, ADDRESS_LINES, ADDRESS_COUNTRY
    158       // postal-ch: CONTACT_NAME, ADDRESS_LINES
    159       address_type: "email" | "phone" | "postal" | "postal-ch";
    160 
    161       // Hint to show in the address bar for the user as an example for
    162       // the format of the address.
    163       address_hint: string;
    164     }
    165 
    166 .. _challenger-setup:
    167 
    168 -----
    169 Setup
    170 -----
    171 
    172 .. http:post:: /setup/$CLIENT_ID
    173 
    174   This endpoint is used by the client to authorize the execution of an address
    175   validation on its behalf.  An ``Authorization`` header (for now always using
    176   a ``Bearer`` token) should be included to provide the client's credentials
    177   to authorize access to the challenger service.  This token must match the
    178   ``client_secret`` from the registration of the client with the challenger
    179   service (which will also be used in the later ``/token`` request).
    180 
    181   **Request:**
    182 
    183   The body can be an address in JSON encoding to pre-initialize the address to
    184   be used by challenger for this process. If the body is absent, the user will
    185   have to enter the full address details.  The specific address format depends
    186   on the address type.  However, `ChallengeSetupRequest` defines the shared
    187   ``read_only`` bit that has a special meaning independent of the address type:
    188   it informs Challenger that the address should not be editable.
    189 
    190   Passing an address in the ``/setup`` body is supported @since protocol **v4**.
    191 
    192   **Response:**
    193 
    194   :http:statuscode:`200 OK`:
    195     Response is a `ChallengeSetupResponse`.
    196   :http:statuscode:`400 Bad request`:
    197     The request is malformed. Usually returned with an
    198     error code of ``TALER_EC_GENERIC_PARAMETER_MISSING`` or
    199     ``TALER_EC_GENERIC_PARAMETER_MALFORMED``.
    200   :http:statuscode:`404 Not found`:
    201     The challenger service is unaware of a matching client.
    202     or the credentials of the client are invalid.
    203     Usually returned with
    204     ``TALER_EC_CHALLENGER_GENERIC_CLIENT_UNKNOWN``.
    205   :http:statuscode:`500 Internal server error`:
    206     The challenger service encountered an internal error.
    207     Usually returned with
    208     ``TALER_EC_GENERIC_DB_FETCH_FAILED`` or
    209     ``TALER_EC_GENERIC_DB_STORE_FAILED`` or
    210     ``TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE``.
    211 
    212   **Details::**
    213 
    214   .. ts:def:: ChallengeSetupRequest
    215 
    216     interface ChallengeSetupRequest {
    217       // If true, the given address should not be edited.
    218       // Defaults to 'false' if not specified.
    219       read_only?: boolean;
    220 
    221       // Optional, additional fields to pre-populate
    222       // the address to be validated.
    223       // The fields depend on the challenger type.
    224       [x: string]: any;
    225     }
    226 
    227 
    228   .. ts:def:: ChallengeSetupResponse
    229 
    230     interface ChallengeSetupResponse {
    231       // Nonce to use when constructing ``/authorize`` endpoint.
    232       nonce: string;
    233     }
    234 
    235 
    236 .. _challenger-login:
    237 
    238 -----
    239 Login
    240 -----
    241 
    242 .. http:get:: /authorize/$NONCE
    243 .. http:post:: /authorize/$NONCE
    244 
    245   This is the "authorization" endpoint of the OAuth 2.0 protocol.  This
    246   endpoint is used by the user-agent. It will return a form to enter the
    247   address.
    248 
    249   The NONCE is a unique value identifying the challenge, should be shown to
    250   the user so that they can recognize it when they receive the TAN code.
    251 
    252   Note that both for GET and POST requests the request arguments must
    253   be given in the URL and the body should be empty. We currently do NOT
    254   support using x-www-form-urlencoded arguments in the body, even for
    255   a POST.
    256 
    257   **Request:**
    258 
    259   :query response_type: Must be ``code``
    260   :query client_id: Identifier of the client.
    261   :query redirect_uri: URI-encoded redirection URI to use upon authorization.
    262   :query state: Arbitrary client state to associate with the request.
    263   :query scope: Not supported, any value is accepted.
    264   :query code_challenge: A string to enhance security using PKCE (available since **v3**).
    265   :query code_challenge_method: The method used for the code_challenge. Options are S256 (SHA-256) or plain (available since **v3**).
    266 
    267   **Response:**
    268 
    269   :http:statuscode:`200 OK`:
    270     The the response is
    271     a `ChallengeStatus`. Since protocol **v1**.
    272   :http:statuscode:`302 Found`:
    273     Returned when the client explicitly accepts ``text/html``
    274     returning a redirection to the WebUI.
    275     Since protocol **v1**.
    276   :http:statuscode:`400 Bad Request`:
    277     The request does not follow the spec.
    278     The response will include error
    279     code, hint and detail. Since protocol **v1**.
    280   :http:statuscode:`404 Not found`:
    281     The service is unaware of a matching challenge.
    282     The response will include error
    283     code, hint and detail. Since protocol **v1**.
    284   :http:statuscode:`406 Not Acceptable`:
    285     The client ask for "text/html" and the backend installation does
    286     not include the required HTML templates.
    287   :http:statuscode:`500 Internal Server Error`:
    288     Server is not able to respond due to internal problems.
    289     The response will include error
    290     code, hint and detail. Since protocol **v1**.
    291 
    292   .. ts:def:: ChallengeStatus
    293 
    294     interface ChallengeStatus {
    295 
    296       // indicates if the given address cannot be changed anymore, the
    297       // form should be read-only if set to true.
    298       fix_address: boolean;
    299 
    300       // form values from the previous submission if available, details depend
    301       // on the ``ADDRESS_TYPE``, should be used to pre-populate the form
    302       last_address?: Object;
    303 
    304       // is the challenge already solved?
    305       solved: boolean;
    306 
    307       // number of times the address can still be changed, may or may not be
    308       // shown to the user
    309       changes_left: Integer;
    310 
    311       // when we would re-transmit the challenge the next
    312       // time (at the earliest) if requested by the user
    313       // only present if challenge already created
    314       // @since **v2**
    315       retransmission_time: Timestamp;
    316 
    317       // how many times might the PIN still be retransmitted
    318       // only present if challenge already created
    319       // @since **v2**
    320       pin_transmissions_left?: Integer;
    321 
    322       // how many times might the user still try entering the PIN code
    323       // only present if challenge already created
    324       // @since **v2**
    325       auth_attempts_left?: Integer;
    326     }
    327 
    328 
    329 .. _challenger-challenge:
    330 
    331 ---------
    332 Challenge
    333 ---------
    334 
    335 .. http:post:: /challenge/$NONCE
    336 
    337   This endpoint is used by the user-agent to submit the address to which a
    338   challenge should be sent by the challenger service.
    339 
    340   **Request:**
    341 
    342   Body should use the mime-type "application/x-www-form-urlencoded".
    343   The posted form data must contain an object that follow the restrictions
    344   defined in :ref:`config <challenger-config>`.
    345 
    346   **Response:**
    347 
    348   :http:statuscode:`200 OK`:
    349     The response is `ChallengeResponse`. Since protocol **v2**.
    350   :http:statuscode:`400 Bad Request`:
    351     The request does not follow the spec.
    352     The response will include error
    353     code, hint and detail. Since protocol **v1**.
    354   :http:statuscode:`404 Not Found`:
    355     The service is unaware of a matching challenge.
    356     The response will include error
    357     code, hint and detail. Since protocol **v1**.
    358   :http:statuscode:`406 Not Acceptable`:
    359     The client ask for "text/html" and the backend installation does
    360     not include the required HTML templates.
    361   :http:statuscode:`429 Too Many Requests`:
    362     There have been too many attempts to request challenge
    363     transmissions for this $NONCE. The user-agent should
    364     wait and (eventually) request a fresh nonce to be set
    365     up by the client.
    366     The response will include error
    367     code, hint and detail. Since protocol **v2**.
    368   :http:statuscode:`500 Internal Server Error`:
    369     Server is not able to respond due to internal problems.
    370     The response will include error
    371     code, hint and detail. Since protocol **v1**.
    372 
    373   .. ts:def:: ChallengeResponse
    374 
    375     // Union discriminated by the "type" field.
    376     type ChallengeResponse = ChallengeRedirect | ChallengeCreateResponse
    377 
    378   .. ts:def:: ChallengeRedirect
    379 
    380     // @since **v2**
    381     interface ChallengeRedirect {
    382       // Union discriminator field.
    383       type: "completed";
    384 
    385       // challenge is completed, use should redirect here
    386       redirect_url: string;
    387     }
    388 
    389   .. ts:def:: ChallengeCreateResponse
    390 
    391    interface ChallengeCreateResponse {
    392       // Union discriminator field.
    393       type: "created"
    394 
    395       // how many more attempts are allowed, might be shown to the user,
    396       // highlighting might be appropriate for low values such as 1 or 2 (the
    397       // form will never be used if the value is zero)
    398       attempts_left: Integer;
    399 
    400       // the address that is being validated, might be shown or not
    401       address: Object;
    402 
    403       // true if we just retransmitted the challenge, false if we sent a
    404       // challenge recently and thus refused to transmit it again this time;
    405       // might make a useful hint to the user
    406       transmitted: boolean;
    407 
    408       // @deprecated in **v2**, use retransmission_time
    409       next_tx_time?: string;
    410 
    411       // when we would re-transmit the challenge the next
    412       // time (at the earliest) if requested by the user
    413       // @since **v2**
    414       retransmission_time: Timestamp;
    415     }
    416 
    417 
    418 .. _challenger-solve:
    419 
    420 -----
    421 Solve
    422 -----
    423 
    424 .. http:post:: /solve/$NONCE
    425 
    426   Used by the user-agent to submit an answer to the challenge.  If the answer
    427   is correct, the user will be redirected to the client's redirect URI,
    428   otherwise the user may be given another chance to complete the process.
    429 
    430   **Request:**
    431 
    432   Body should use the mime-type "application/x-www-form-urlencoded".
    433   The posted form data must contain a "pin" field.
    434 
    435   **Response:**
    436 
    437   :http:statuscode:`200 OK`:
    438     If the request ask for application/json the response is
    439     a `ChallengeSolveResponse`. Since protocol **v2**.
    440   :http:statuscode:`302 Found`:
    441     Only possible if request didn't ask for application/json. Since protocol **v2**.
    442     The user is redirected to the redirect URI of the client to pass the
    443     grant to the client.  The target will be the redirect URI specified
    444     by the client (during registration and again upon ``/authorize``),
    445     plus a ``code`` argument with the authorization code, and the
    446     ``state`` argument from the ``/authorize`` endpoint.
    447   :http:statuscode:`400 Bad Request`:
    448     The request does not follow the spec.
    449     The response will include error
    450     code, hint and detail. Since protocol **v1**.
    451   :http:statuscode:`403 Forbidden`:
    452     The response is `InvalidPinResponse`. Since protocol **v1**.
    453   :http:statuscode:`404 Not found`:
    454     The service is unaware of a matching challenge.
    455     The response will include error
    456     code, hint and detail. Since protocol **v1**.
    457   :http:statuscode:`429 Too Many Requests`:
    458     There have been too many attempts to solve the challenge
    459     for this address (and $NONCE). The user-agent should
    460     either try a different address (or wait and (eventually)
    461     request a fresh nonce to be set up by the client).
    462     The response will include error
    463     code, hint and detail. Since protocol **v2**.
    464   :http:statuscode:`500 Internal Server Error`:
    465     Server is not able to respond due to internal problems.
    466     The response will include error
    467     code, hint and detail. Since protocol **v1**.
    468 
    469   .. ts:def:: ChallengeSolveResponse
    470 
    471     // Union discriminated by the "type" field.
    472     type ChallengeSolveResponse = ChallengeRedirect | InvalidPinResponse;
    473 
    474   .. ts:def:: InvalidPinResponse
    475 
    476     interface InvalidPinResponse {
    477       // Union discriminator field.
    478       type: "pending";
    479 
    480       // numeric Taler error code, should be shown to indicate the error
    481       // compactly for reporting to developers
    482       code: Integer;
    483 
    484       // human-readable Taler error code, should be shown for the user to
    485       // understand the error
    486       hint: string;
    487 
    488       // how many times is the user still allowed to change the address;
    489       // if 0, the user should not be shown a link to jump to the
    490       // address entry form
    491       addresses_left: Integer;
    492 
    493       // how many times might the PIN still be retransmitted
    494       pin_transmissions_left: Integer;
    495 
    496       // how many times might the user still try entering the PIN code
    497       auth_attempts_left: Integer;
    498 
    499       // if true, the PIN was not even evaluated as the user previously
    500       // exhausted the number of attempts
    501       exhausted: boolean;
    502 
    503       // if true, the PIN was not even evaluated as no challenge was ever
    504       // issued (the user must have skipped the step of providing their
    505       // address first!)
    506       no_challenge: boolean;
    507     }
    508 
    509 .. _challenger-auth:
    510 
    511 ----
    512 Auth
    513 ----
    514 
    515 .. http:post:: /token
    516 
    517   This is the token endpoint of the OAuth 2.0 specification.
    518   This endpoint is used by the client to provide its authorization code,
    519   demonstrating that it has the right to learn a particular user's validated
    520   address.  In return, the challenger service returns the access token.
    521   Renewal is not supported.
    522 
    523   **Request:**
    524 
    525   The request must include an ``application/www-form-urlencoded`` body
    526   specifying the ``client_id``, ``redirect_uri``, ``client_secret``, ``code``
    527   and ``grant_type``.  The ``grant_type`` must be set to
    528   ``authorization_code``.  The ``redirect_uri`` must match the URI from
    529   ``/authorize``. The ``code`` must be the authorization code that ``/solve``
    530   returned to the user.  The ``client_id`` and ``client_secret`` must match
    531   the usual client credentials. Since protocol **v3**, ``code_verifier`` can also be included.
    532 
    533   **Response:**
    534 
    535   Error responses follow RFC 6749, section 5.2 with an "error" field in JSON,
    536   as well as also returning GNU Taler style error messages.
    537 
    538   :http:statuscode:`200 OK`:
    539     The body will be a `ChallengerAuthResponse`
    540   :http:statuscode:`401 Unauthorized`:
    541     The ``code_verifier`` is not matching the saved ones. (Since **v3**)
    542   :http:statuscode:`403 Forbidden`:
    543     The credentials of the client are invalid.
    544   :http:statuscode:`404 Not found`:
    545     The service is unaware of a matching login process.
    546 
    547   **Details::**
    548 
    549   .. ts:def:: ChallengerAuthResponse
    550 
    551     interface ChallengerAuthResponse {
    552       // Token used to authenticate access in ``/info``.
    553       access_token: string;
    554 
    555       // Type of the access token.
    556       token_type: "Bearer";
    557 
    558       // Amount of time that an access token is valid (in seconds).
    559       expires_in: Integer;
    560 
    561     }
    562 
    563 
    564 .. _challenger-info:
    565 
    566 ----
    567 Info
    568 ----
    569 
    570 .. http:get:: /info
    571 
    572   This userinfo endpoint of the OAuth 2.0 specification.
    573   This endpoint is used by the client to obtain the user's validated address.
    574 
    575   **Request:**
    576 
    577   Must include the token returned to the client from the ``/token`` endpoint
    578   as a ``Bearer`` token in an ``Authorization`` header.
    579 
    580   **Response:**
    581 
    582   :http:statuscode:`200 OK`:
    583     The body contains the address as a `ChallengerInfoResponse`.
    584   :http:statuscode:`403 Forbidden`:
    585     The bearer token is missing or invalid (malformed).
    586   :http:statuscode:`404 Not found`:
    587     The bearer token is invalid (includes unknown or expired).
    588 
    589   **Details::**
    590 
    591   .. ts:def:: ChallengerInfoResponse
    592 
    593     interface ChallengerInfoResponse {
    594 
    595       // Unique ID of the record within Challenger
    596       // (identifies the rowid of the token).
    597       id: Integer;
    598 
    599       // Address that was validated.
    600       // Key-value pairs, details depend on the
    601       // address_type.
    602       address: Object;
    603 
    604      // Type of the address.
    605       address_type: string;
    606 
    607       // How long do we consider the address to be
    608       // valid for this user.
    609       expires: Timestamp;
    610 
    611     }