diff options
Diffstat (limited to 'core/api-challenger.rst')
-rw-r--r-- | core/api-challenger.rst | 548 |
1 files changed, 548 insertions, 0 deletions
diff --git a/core/api-challenger.rst b/core/api-challenger.rst new file mode 100644 index 00000000..afb3096d --- /dev/null +++ b/core/api-challenger.rst @@ -0,0 +1,548 @@ +.. + This file is part of GNU TALER. + Copyright (C) 2023, 2024 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU Affero General Public License as published by the Free Software + Foundation; either version 2.1, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + + @author Christian Grothoff + +.. _challenger-api: + +====================== +Challenger Service API +====================== + +The challenger service validates that a user is able to receive challenges at +an address (such as e-mail or SMS) and allows an OAuth 2.0 client to obtain +access to these validated addresses. + +The high-level flow is that an OAuth 2.0 client is first registered with the +challenger service (via command-line). Using the command-line tool will print +the resulting client ID to the console. + +.. note:: + + The current service mandates that redirection URIs + start with "http://" or "https://". See issue #7838 + for what should be done to lift this restriction. + +.. note:: + + Right now, registration of a unique redirection URI is *mandatory* for + each client. If multiple redirection URIs are needed, it is suggested to + just register additional clients. (While OAuth 2.0 would support not + registering fixed redirection URIs with a client, this is not recommended + as it would create an open redirector.) + +Once a client is registered, that client can use the challenger service when +it needs a user to prove that the user is able to receive messages at a +particular address. However, asking a user to prove access to a particular +address can be expensive as it may involve sending an SMS or even postal mail +depending on the type of address. Thus, challenger does not allow a user +agent to begin an address validation process without prior approval by a +registered client. Thus, the process begins with a ``/setup/$CLIENT_ID`` request where a +client requests challenger to begin an address validation request. The +``/setup/$CLIENT_ID`` response contains a ``nonce`` which is then used to construct the +URL of the endpoint to which the client must redirect the user-agent to begin +the address validation and authorization process. + +The client then redirects the user-agent to the ``/authorize/$NONCE`` endpoint +of the challenger service, adding its ``state``, ``client_id`` and +``redirect_uri`` as query parameters. The ``redirect_uri`` must match the +redirect URI registered with the client. From this endpoint, the challenger +service will return a Web page asking the user to provide its address. + +.. note:: + + Challenger is a bit unusual in that the ``$NONCE`` in the endpoint URL + makes the authorization endpoint URL (deliberately) unpredictable, while + for many other OAuth 2.0 APIs this endpoint is static. However, this is + compliant with OAuth 2.0 as determining the authorization URL is left out + of the scope of the standard. + +When the user has filled in the form with their address, it will be submitted +to the ``/challenge/$NONCE`` endpoint and the challenger service will send a +challenge to the user's address and generate an HTML form asking the user to +enter the received challenge value. + +The user can then enter the answer to the challenge which is then submitted to +the ``/solve/$NONCE`` endpoint. If the answer is correct, the user agent will +be redirected to the client redirect URI that was specified by the OAuth 2.0 +client upon ``/authorize``, together with an authorization grant encoded in +the redirection URI. + +Given this authorization grant, the OAuth 2.0 client can then use the +``/token`` endpoint to obtain an access token which will grant it access to +the resource. + +Using the ``/info`` endpoint the client can then finally obtain the (now) +verified address of the user. + +.. contents:: Table of Contents + :local: + +.. include:: tos.rst + +----------------------- +Receiving Configuration +----------------------- + +.. http:get:: /config + + Obtain the key configuration settings of the storage service. + This specification corresponds to ``current`` protocol being version **1**. + + **Response:** + + Returns a `ChallengerTermsOfServiceResponse`. + + .. ts:def:: ChallengerTermsOfServiceResponse + + interface ChallengerTermsOfServiceResponse { + // Name of the service + name: "challenger"; + + // libtool-style representation of the Challenger protocol version, see + // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning + // The format is "current:revision:age". + version: string; + + // URN of the implementation (needed to interpret 'revision' in version). + // @since v0, may become mandatory in the future. + implementation?: string; + + // @since v2. + // Object; map of keys (names of the fields of the address + // to be entered by the user) to objects with a "regex" (string) + // containing an extended Posix regular expression for allowed + // address field values, and a "hint"/"hint_i18n" giving a + // human-readable explanation to display if the value entered + // by the user does not match the regex. Keys that are not mapped + // to such an object have no restriction on the value provided by + // the user. See "ADDRESS_RESTRICTIONS" in the challenger configuration. + restrictions: Object; + + // @since v2. + address_type: "email" | "phone"; + } + +.. _challenger-setup: + +----- +Setup +----- + +.. http:post:: /setup/$CLIENT_ID + + This endpoint is used by the client to authorize the execution of an address + validation on its behalf. An ``Authorization`` header (for now always using + a ``Bearer`` token) should be included to provide the client's credentials + to authorize access to the challenger service. This token must match the + ``client_secret`` from the registration of the client with the challenger + service (which will also be used in the later ``/token`` request). + + **Response:** + + :http:statuscode:`200 OK`: + Response is a `ChallengeSetupResponse`. + :http:statuscode:`404 Not found`: + The backup service is unaware of a matching client. + or the credentials of the client are invalid. + + **Details::** + + .. ts:def:: ChallengeSetupResponse + + interface ChallengeSetupResponse { + // Nonce to use when constructing ``/authorize`` endpoint. + nonce: string; + } + + +.. _challenger-login: + +----- +Login +----- + +.. http:get:: /authorize/$NONCE +.. http:post:: /authorize/$NONCE + + This is the "authorization" endpoint of the OAuth 2.0 protocol. This + endpoint is used by the user-agent. It will return a form to enter the + address. + + The NONCE is a unique value identifying the challenge, should be shown to + the user so that they can recognize it when they receive the TAN code. + + This endpoint typically also supports requests with the "Accept" header + requesting "text/html". In this case, an HTML response using the template + :ref:`enter-$ADDRESS_TYPE-form <challenger_enter-address_type-form>` is + returned. If the backend installation does not include the required HTML + templates, a 406 status code is returned. + + + **Request:** + + :query response_type: Must be ``code`` + :query client_id: Identifier of the client. + :query redirect_uri: URI-encoded redirection URI to use upon authorization. + :query state: Arbitrary client state to associate with the request. + :query scope: Not supported, any value is accepted. + + **Response:** + + :http:statuscode:`200 OK`: + If the request ask for application/json then the response is + a `ChallengeStatus`. Since protocol **v1**. + Otherwise, the body contains a form to be submitted by the user-agent + using the template :ref:`enter-$ADDRESS_TYPE-form <challenger_enter-address_type-form>`. + The form will ask the user to specify their address. + :http:statuscode:`400 Bad Request`: + The request does not follow the spec. + If the request ask for application/json the response will include error + code, hint and detail. Since protocol **v1**. + Otherwise, the body contains information using the template :ref:`invalid-request <challenger_invalid-request>`. + :http:statuscode:`404 Not found`: + The service is unaware of a matching challenge. + If the request ask for application/json the response will include error + code, hint and detail. Since protocol **v1**. + Otherwise, the body contains information using the template :ref:`validation-unknown <challenger_validation-unknown>`. + :http:statuscode:`406 Not Acceptable`: + The client ask for "text/html" and the backend installation does + not include the required HTML templates. + :http:statuscode:`500 Internal Server Error`: + Server is not able to respond due to internal problems. + If the request ask for application/json the response will include error + code, hint and detail. Since protocol **v1**. + Otherwise, the body contains information using the template :ref:`internal-error <challenger_internal-error>`. + + .. ts:def:: ChallengeStatus + + interface ChallengeStatus { + // @deprecated since v2, use /config + restrictions?: Object; + + // indicates if the given address cannot be changed anymore, the + // form should be read-only if set to true. + fix_address: boolean; + + // form values from the previous submission if available, details depend + // on the ``ADDRESS_TYPE``, should be used to pre-populate the form + last_address?: Object; + + // number of times the address can still be changed, may or may not be + // shown to the user + changes_left: Integer; + + // when we would re-transmit the challenge the next + // time (at the earliest) if requested by the user + // only present if challenge already created + // @since v2 + retransmission_time: Timestamp; + + // how many times might the PIN still be retransmitted + + // how many times might the PIN still be retransmitted + // only present if challenge already created + // @since v2 + pin_transmissions_left?: Integer; + + // how many times might the user still try entering the PIN code + // only present if challenge already created + // @since v2 + auth_attempts_left?: Integer; + } + + +.. _challenger-challenge: + +--------- +Challenge +--------- + +.. http:post:: /challenge/$NONCE + + This endpoint is used by the user-agent to submit the address to which a + challenge should be sent by the challenger service. + + **Request:** + + Body should use the mime-type "application/x-www-form-urlencoded". + The posted form data must contain an "address". + + **Response:** + + :http:statuscode:`200 OK`: + If the request ask for application/json the response is `ChallengeResponse`. Since protocol **v2**. + Otherwise, the body contains a form asking for the answer to + the challenge to be entered by the user using the + template :ref:`enter-tan-form <challenger_enter-tan-form>`. + :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. + :http:statuscode:`400 Bad Request`: + The request does not follow the spec. + If the request ask for application/json the response will include error + code, hint and detail. Since protocol **v1**. + Otherwise, the body contains information using the template :ref:`invalid-request <challenger_invalid-request>`. + :http:statuscode:`404 Not Found`: + The service is unaware of a matching challenge. + If the request ask for application/json the response will include error + code, hint and detail. Since protocol **v1**. + Otherwise, the body contains information using the template :ref:`validation-unknown <challenger_validation-unknown>`. + :http:statuscode:`406 Not Acceptable`: + The client ask for "text/html" and the backend installation does + not include the required HTML templates. + :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. + :http:statuscode:`500 Internal Server Error`: + Server is not able to respond due to internal problems. + If the request ask for application/json the response will include error + code, hint and detail. Since protocol **v1**. + Otherwise, the body contains information using the template :ref:`internal-error <challenger_internal-error>`. + + .. ts:def:: ChallengeResponse + + interface ChallengeResponse = ChallengeRedirect | ChallengeCreateResponse + + .. ts:def:: ChallengeRedirect + + // @since v2 + interface ChallengeRedirect { + // challenge is completed, use should redirect here + redirect_url: string; + } + + .. ts:def:: ChallengeCreateResponse + + interface ChallengeCreateResponse { + + // how many more attempts are allowed, might be shown to the user, + // highlighting might be appropriate for low values such as 1 or 2 (the + // form will never be used if the value is zero) + attempts_left: Integer; + + // the address that is being validated, might be shown or not + address: Object; + + // true if we just retransmitted the challenge, false if we sent a + // challenge recently and thus refused to transmit it again this time; + // might make a useful hint to the user + transmitted: boolean; + + // @deprecated in v2, use retransmission_time + next_tx_time?: string; + + // when we would re-transmit the challenge the next + // time (at the earliest) if requested by the user + // @since v2 + retransmission_time: Timestamp; + } + + + +.. _challenger-solve: + +----- +Solve +----- + +.. http:post:: /solve/$NONCE + + Used by the user-agent to submit an answer to the challenge. If the answer + is correct, the user will be redirected to the client's redirect URI, + otherwise the user may be given another chance to complete the process. + + **Request:** + + Depends on the form from ``/challenge``. TBD. + + **Response:** + + :http:statuscode:`200 OK`: + If the request ask for application/json the response is `ChallengeSolveResponse`. Since protocol **v2**. + :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. + :http:statuscode:`400 Bad Request`: + The request does not follow the spec. + If the request ask for application/json the response will include error + code, hint and detail. Since protocol **v1**. + Otherwise, the body contains information using the template :ref:`invalid-request <challenger_invalid-request>`. + :http:statuscode:`403 Forbidden`: + If the request ask for application/json the response is `InvalidPinResponse`. Since protocol **v1**. + Otherwise, the body contains information using the template :ref:`invalid-pin <challenger_invalid-pin>`. + :http:statuscode:`404 Not found`: + The service is unaware of a matching challenge. + If the request ask for application/json the response will include error + code, hint and detail. Since protocol **v1**. + Otherwise, the body contains information using the template :ref:`validation-unknown <challenger_validation-unknown>`. + :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). + :http:statuscode:`500 Internal Server Error`: + Server is not able to respond due to internal problems. + If the request ask for application/json the response will include error + code, hint and detail. Since protocol **v1**. + Otherwise, the body contains information using the template :ref:`internal-error <challenger_internal-error>`. + + .. ts:def:: ChallengeSolveResponse + + type ChallengeSolveResponse = ChallengeRedirect | InvalidPinResponse; + + .. ts:def:: InvalidPinResponse + + interface InvalidPinResponse { + // numeric Taler error code, should be shown to indicate the error + // compactly for reporting to developers + ec: Integer; + + // human-readable Taler error code, should be shown for the user to + // understand the error + hint: string; + + // how many times is the user still allowed to change the address; + // if 0, the user should not be shown a link to jump to the + // address entry form + addresses_left: Integer; + + // how many times might the PIN still be retransmitted + pin_transmissions_left: Integer; + + // how many times might the user still try entering the PIN code + auth_attempts_left: Integer; + + // if true, the PIN was not even evaluated as the user previously + // exhausted the number of attempts + exhausted: boolean; + + // if true, the PIN was not even evaluated as no challenge was ever + // issued (the user must have skipped the step of providing their + // address first!) + no_challenge: boolean; + } + +.. _challenger-auth: + +---- +Auth +---- + +.. http:post:: /token + + This is the token endpoint of the OAuth 2.0 specification. + This endpoint is used by the client to provide its authorization code, + demonstrating that it has the right to learn a particular user's validated + address. In return, the challenger service returns the access token. + Renewal is not supported. + + **Request:** + + The request must include an ``application/www-form-urlencoded`` body + 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 + ``/authorize``. The ``code`` must be the authorization code that ``/solve`` + returned to the user. The ``client_id`` and ``client_secret`` must match + the usual client credentials. + + **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. + + :http:statuscode:`200 OK`: + The body will be a `ChallengerAuthResponse` + :http:statuscode:`403 Forbidden`: + The credentials of the client are invalid. + :http:statuscode:`404 Not found`: + The service is unaware of a matching login process. + + **Details::** + + .. ts:def:: ChallengerAuthResponse + + interface ChallengerAuthResponse { + // Token used to authenticate access in ``/info``. + access_token: string; + + // Type of the access token. + token_type: "Bearer"; + + // Amount of time that an access token is valid (in seconds). + expires_in: Integer; + + } + + +.. _challenger-info: + +---- +Info +---- + +.. http:get:: /info + + This userinfo endpoint of the OAuth 2.0 specification. + This endpoint is used by the client to obtain the user's validated address. + + **Request:** + + Must include the token returned to the client from the ``/token`` endpoint + as a ``Bearer`` token in an ``Authorization`` header. + + **Response:** + + :http:statuscode:`200 OK`: + The body contains the address as a `ChallengerInfoResponse`. + :http:statuscode:`403 Forbidden`: + The bearer token is missing or invalid (malformed). + :http:statuscode:`404 Not found`: + The bearer token is invalid (includes unknown or expired). + + **Details::** + + .. ts:def:: ChallengerInfoResponse + + interface ChallengerInfoResponse { + + // Unique ID of the record within Challenger + // (identifies the rowid of the token). + id: Integer; + + // Address that was validated. + // Key-value pairs, details depend on the + // address_type. + address: Object; + + // Type of the address. + address_type: string; + + // How long do we consider the address to be + // valid for this user. + expires: Timestamp; + + } |