api-challenger.rst (23852B)
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 :http:statuscode:`200 OK`: 124 Response is a `ChallengerConfigurationResponse`. 125 126 .. ts:def:: ChallengerConfigurationResponse 127 128 interface ChallengerConfigurationResponse { 129 // Name of the service 130 name: "challenger"; 131 132 // libtool-style representation of the Challenger protocol version, see 133 // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning 134 // The format is "current:revision:age". 135 version: string; 136 137 // URN of the implementation (needed to interpret 'revision' in version). 138 // @since v0, may become mandatory in the future. 139 implementation?: string; 140 141 // @since **v2**. 142 // Object; map of keys (names of the fields of the address 143 // to be entered by the user) to objects with a "regex" (string) 144 // containing an extended Posix regular expression for allowed 145 // address field values, and a "hint"/"hint_i18n" giving a 146 // human-readable explanation to display if the value entered 147 // by the user does not match the regex. Keys that are not mapped 148 // to such an object have no restriction on the value provided by 149 // the user. See "ADDRESS_RESTRICTIONS" in the challenger configuration. 150 restrictions: Object; 151 152 // @since **v6** 153 // Defines the set of fields asked to the user. 154 // The field names are registered via GANA at 155 // https://git.taler.net/gana.git/tree/gnu-taler-form-attributes 156 // email: CONTACT_EMAIL 157 // phone: CONTACT_PHONE 158 // postal: CONTACT_NAME, ADDRESS_LINES, ADDRESS_COUNTRY 159 // postal-ch: CONTACT_NAME, ADDRESS_LINES 160 address_type: "email" | "phone" | "postal" | "postal-ch"; 161 162 // Hint to show in the address bar for the user as an example for 163 // the format of the address. 164 address_hint: string; 165 } 166 167 .. _challenger-setup: 168 169 ----- 170 Setup 171 ----- 172 173 .. http:post:: /setup/$CLIENT_ID 174 175 This endpoint is used by the client to authorize the execution of an address 176 validation on its behalf. An ``Authorization`` header (for now always using 177 a ``Bearer`` token) should be included to provide the client's credentials 178 to authorize access to the challenger service. This token must match the 179 ``client_secret`` from the registration of the client with the challenger 180 service (which will also be used in the later ``/token`` request). 181 182 **Request:** 183 184 The body can be an address in JSON encoding to pre-initialize the address to 185 be used by challenger for this process. If the body is absent, the user will 186 have to enter the full address details. The specific address format depends 187 on the address type. However, `ChallengeSetupRequest` defines the shared 188 ``read_only`` bit that has a special meaning independent of the address type: 189 it informs Challenger that the address should not be editable. 190 191 Passing an address in the ``/setup`` body is supported @since protocol **v4**. 192 193 **Response:** 194 195 :http:statuscode:`200 OK`: 196 Response is a `ChallengeSetupResponse`. 197 :http:statuscode:`400 Bad request`: 198 The request is malformed. Usually returned with an 199 error code of ``TALER_EC_GENERIC_PARAMETER_MISSING`` or 200 ``TALER_EC_GENERIC_PARAMETER_MALFORMED``. 201 :http:statuscode:`404 Not found`: 202 The challenger service is unaware of a matching client. 203 or the credentials of the client are invalid. 204 Usually returned with 205 ``TALER_EC_CHALLENGER_GENERIC_CLIENT_UNKNOWN``. 206 :http:statuscode:`500 Internal server error`: 207 The challenger service encountered an internal error. 208 Usually returned with 209 ``TALER_EC_GENERIC_DB_FETCH_FAILED`` or 210 ``TALER_EC_GENERIC_DB_STORE_FAILED`` or 211 ``TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE``. 212 213 **Details::** 214 215 .. ts:def:: ChallengeSetupRequest 216 217 interface ChallengeSetupRequest { 218 // If true, the given address should not be edited. 219 // Defaults to 'false' if not specified. 220 read_only?: boolean; 221 222 // Optional, additional fields to pre-populate 223 // the address to be validated. 224 // The fields depend on the challenger type. 225 [x: string]: any; 226 } 227 228 229 .. ts:def:: ChallengeSetupResponse 230 231 interface ChallengeSetupResponse { 232 // Nonce to use when constructing ``/authorize`` endpoint. 233 nonce: string; 234 } 235 236 237 .. _challenger-login: 238 239 ----- 240 Login 241 ----- 242 243 .. http:get:: /authorize/$NONCE 244 .. http:post:: /authorize/$NONCE 245 246 This is the "authorization" endpoint of the OAuth 2.0 protocol. This 247 endpoint is used by the user-agent. It will return a form to enter the 248 address. 249 250 The NONCE is a unique value identifying the challenge, should be shown to 251 the user so that they can recognize it when they receive the TAN code. 252 253 Note that both for GET and POST requests the request arguments must 254 be given in the URL and the body should be empty. We currently do NOT 255 support using x-www-form-urlencoded arguments in the body, even for 256 a POST. 257 258 **Request:** 259 260 :query response_type: Must be ``code`` 261 :query client_id: Identifier of the client. 262 :query redirect_uri: URI-encoded redirection URI to use upon authorization. 263 :query state: Arbitrary client state to associate with the request. 264 :query scope: Not supported, any value is accepted. 265 :query code_challenge: A string to enhance security using PKCE (available since **v3**). 266 :query code_challenge_method: The method used for the code_challenge. Options are S256 (SHA-256) or plain (available since **v3**). 267 268 **Response:** 269 270 :http:statuscode:`200 OK`: 271 The the response is 272 a `ChallengeStatus`. Since protocol **v1**. 273 :http:statuscode:`302 Found`: 274 Returned when the client explicitly accepts ``text/html`` 275 returning a redirection to the WebUI. 276 Since protocol **v1**. 277 :http:statuscode:`400 Bad Request`: 278 The request does not follow the spec. 279 The response will include error 280 code, hint and detail. Since protocol **v1**. 281 :http:statuscode:`404 Not found`: 282 The service is unaware of a matching challenge. 283 The response will include error 284 code, hint and detail. Since protocol **v1**. 285 :http:statuscode:`406 Not Acceptable`: 286 The client ask for "text/html" and the backend installation does 287 not include the required HTML templates. 288 :http:statuscode:`500 Internal Server Error`: 289 Server is not able to respond due to internal problems. 290 The response will include error 291 code, hint and detail. Since protocol **v1**. 292 293 .. ts:def:: ChallengeStatus 294 295 interface ChallengeStatus { 296 297 // indicates if the given address cannot be changed anymore, the 298 // form should be read-only if set to true. 299 fix_address: boolean; 300 301 // form values from the previous submission if available, details depend 302 // on the ``ADDRESS_TYPE``, should be used to pre-populate the form 303 last_address?: Object; 304 305 // is the challenge already solved? 306 solved: boolean; 307 308 // number of times the address can still be changed, may or may not be 309 // shown to the user 310 changes_left: Integer; 311 312 // when we would re-transmit the challenge the next 313 // time (at the earliest) if requested by the user 314 // only present if challenge already created 315 // @since **v2** 316 retransmission_time: Timestamp; 317 318 // how many times might the PIN still be retransmitted 319 // only present if challenge already created 320 // @since **v2** 321 pin_transmissions_left?: Integer; 322 323 // how many times might the user still try entering the PIN code 324 // only present if challenge already created 325 // @since **v2** 326 auth_attempts_left?: Integer; 327 } 328 329 330 .. _challenger-challenge: 331 332 --------- 333 Challenge 334 --------- 335 336 .. http:post:: /challenge/$NONCE 337 338 This endpoint is used by the user-agent to submit the address to which a 339 challenge should be sent by the challenger service. 340 341 **Request:** 342 343 Body should use the mime-type "application/x-www-form-urlencoded". 344 The posted form data must contain an object that follow the restrictions 345 defined in :ref:`config <challenger-config>`. 346 347 **Response:** 348 349 :http:statuscode:`200 OK`: 350 The response is `ChallengeResponse`. Since protocol **v2**. 351 :http:statuscode:`400 Bad Request`: 352 The request does not follow the spec. 353 The response will include error 354 code, hint and detail. Since protocol **v1**. 355 :http:statuscode:`403 Forbidden`: 356 The address being submitted differs from the previously 357 submitted address but the validation process was set up 358 as ``read_only`` and thus the address cannot be changed. 359 Returned with 360 ``TALER_EC_CHALLENGER_CLIENT_FORBIDDEN_READ_ONLY``. 361 Since protocol **v4**. 362 :http:statuscode:`404 Not Found`: 363 The service is unaware of a matching challenge. 364 The response will include error 365 code, hint and detail. Since protocol **v1**. 366 :http:statuscode:`406 Not Acceptable`: 367 The client ask for "text/html" and the backend installation does 368 not include the required HTML templates. 369 :http:statuscode:`429 Too Many Requests`: 370 There have been too many attempts to request challenge 371 transmissions for this $NONCE. The user-agent should 372 wait and (eventually) request a fresh nonce to be set 373 up by the client. 374 Returned with ``TALER_EC_CHALLENGER_TOO_MANY_ATTEMPTS``. 375 Since protocol **v2**. 376 :http:statuscode:`500 Internal Server Error`: 377 Server is not able to respond due to internal problems. 378 The response will include error 379 code, hint and detail. Since protocol **v1**. 380 :http:statuscode:`502 Bad Gateway`: 381 The challenger service failed to launch or communicate with 382 its helper process for delivering the challenge (SMS, e-mail, 383 postal mail). Returned with 384 ``TALER_EC_CHALLENGER_HELPER_EXEC_FAILED``. 385 386 .. ts:def:: ChallengeResponse 387 388 // Union discriminated by the "type" field. 389 type ChallengeResponse = ChallengeRedirect | ChallengeCreateResponse 390 391 .. ts:def:: ChallengeRedirect 392 393 // @since **v2** 394 interface ChallengeRedirect { 395 // Union discriminator field. 396 type: "completed"; 397 398 // challenge is completed, use should redirect here 399 redirect_url: string; 400 } 401 402 .. ts:def:: ChallengeCreateResponse 403 404 interface ChallengeCreateResponse { 405 // Union discriminator field. 406 type: "created" 407 408 // how many more attempts are allowed, might be shown to the user, 409 // highlighting might be appropriate for low values such as 1 or 2 (the 410 // form will never be used if the value is zero) 411 attempts_left: Integer; 412 413 // the address that is being validated, might be shown or not 414 address: Object; 415 416 // true if we just retransmitted the challenge, false if we sent a 417 // challenge recently and thus refused to transmit it again this time; 418 // might make a useful hint to the user 419 transmitted: boolean; 420 421 // @deprecated in **v2**, use retransmission_time 422 next_tx_time?: string; 423 424 // when we would re-transmit the challenge the next 425 // time (at the earliest) if requested by the user 426 // @since **v2** 427 retransmission_time: Timestamp; 428 } 429 430 431 .. _challenger-solve: 432 433 ----- 434 Solve 435 ----- 436 437 .. http:post:: /solve/$NONCE 438 439 Used by the user-agent to submit an answer to the challenge. If the answer 440 is correct, the user will be redirected to the client's redirect URI, 441 otherwise the user may be given another chance to complete the process. 442 443 **Request:** 444 445 Body should use the mime-type "application/x-www-form-urlencoded". 446 The posted form data must contain a "pin" field. 447 448 **Response:** 449 450 :http:statuscode:`200 OK`: 451 If the request ask for application/json the response is 452 a `ChallengeSolveResponse`. Since protocol **v2**. 453 :http:statuscode:`302 Found`: 454 Only possible if request didn't ask for application/json. Since protocol **v2**. 455 The user is redirected to the redirect URI of the client to pass the 456 grant to the client. The target will be the redirect URI specified 457 by the client (during registration and again upon ``/authorize``), 458 plus a ``code`` argument with the authorization code, and the 459 ``state`` argument from the ``/authorize`` endpoint. 460 :http:statuscode:`400 Bad Request`: 461 The request does not follow the spec. 462 The response will include error 463 code, hint and detail. Since protocol **v1**. 464 :http:statuscode:`403 Forbidden`: 465 The response is `InvalidPinResponse`. Since protocol **v1**. 466 :http:statuscode:`404 Not found`: 467 The service is unaware of a matching challenge. 468 The response will include error 469 code, hint and detail. Since protocol **v1**. 470 :http:statuscode:`429 Too Many Requests`: 471 There have been too many attempts to solve the challenge 472 for this address (and $NONCE). The user-agent should 473 either try a different address (or wait and (eventually) 474 request a fresh nonce to be set up by the client). 475 The response will include error 476 code, hint and detail. Since protocol **v2**. 477 :http:statuscode:`500 Internal Server Error`: 478 Server is not able to respond due to internal problems. 479 The response will include error 480 code, hint and detail. Since protocol **v1**. 481 482 .. ts:def:: ChallengeSolveResponse 483 484 // Union discriminated by the "type" field. 485 type ChallengeSolveResponse = ChallengeRedirect | InvalidPinResponse; 486 487 .. ts:def:: InvalidPinResponse 488 489 interface InvalidPinResponse { 490 // Union discriminator field. 491 type: "pending"; 492 493 // numeric Taler error code, should be shown to indicate the error 494 // compactly for reporting to developers 495 code: Integer; 496 497 // human-readable Taler error code, should be shown for the user to 498 // understand the error 499 hint: string; 500 501 // how many times is the user still allowed to change the address; 502 // if 0, the user should not be shown a link to jump to the 503 // address entry form 504 addresses_left: Integer; 505 506 // how many times might the PIN still be retransmitted 507 pin_transmissions_left: Integer; 508 509 // how many times might the user still try entering the PIN code 510 auth_attempts_left: Integer; 511 512 // if true, the PIN was not even evaluated as the user previously 513 // exhausted the number of attempts 514 exhausted: boolean; 515 516 // if true, the PIN was not even evaluated as no challenge was ever 517 // issued (the user must have skipped the step of providing their 518 // address first!) 519 no_challenge: boolean; 520 } 521 522 .. _challenger-auth: 523 524 ---- 525 Auth 526 ---- 527 528 .. http:post:: /token 529 530 This is the token endpoint of the OAuth 2.0 specification. 531 This endpoint is used by the client to provide its authorization code, 532 demonstrating that it has the right to learn a particular user's validated 533 address. In return, the challenger service returns the access token. 534 Renewal is not supported. 535 536 **Request:** 537 538 The request must include an ``application/www-form-urlencoded`` body 539 specifying the ``client_id``, ``redirect_uri``, ``client_secret``, ``code`` 540 and ``grant_type``. The ``grant_type`` must be set to 541 ``authorization_code``. The ``redirect_uri`` must match the URI from 542 ``/authorize``. The ``code`` must be the authorization code that ``/solve`` 543 returned to the user. The ``client_id`` and ``client_secret`` must match 544 the usual client credentials. Since protocol **v3**, ``code_verifier`` can also be included. 545 546 **Response:** 547 548 Error responses follow RFC 6749, section 5.2 with an "error" field in JSON, 549 as well as also returning GNU Taler style error messages. 550 551 :http:statuscode:`200 OK`: 552 The body will be a `ChallengerAuthResponse`. 553 :http:statuscode:`400 Bad Request`: 554 A required POST field (``grant_type``, ``client_id``, 555 ``client_secret``, ``code`` or ``redirect_uri``) is missing 556 or malformed, or ``grant_type`` is not ``authorization_code``. 557 Usually returned with ``TALER_EC_GENERIC_PARAMETER_MISSING`` 558 or ``TALER_EC_GENERIC_PARAMETER_MALFORMED``. 559 :http:statuscode:`401 Unauthorized`: 560 Authentication of the client failed. Returned (per 561 RFC 6749, section 5.2) when the client credentials are 562 invalid, when the supplied ``code`` is malformed or does 563 not match the validation, when the ``redirect_uri`` does 564 not match the one registered with the client, or when the 565 ``code_verifier`` does not match the saved 566 ``code_challenge``. Returned with 567 ``TALER_EC_CHALLENGER_GENERIC_CLIENT_FORBIDDEN_BAD_REDIRECT_URI``, 568 ``TALER_EC_CHALLENGER_CLIENT_FORBIDDEN_BAD_CODE``, 569 ``TALER_EC_CHALLENGER_GENERIC_VALIDATION_UNKNOWN`` or 570 ``TALER_EC_CHALLENGER_GRANT_UNKNOWN``. 571 PKCE-related rejections are since protocol **v3**. 572 :http:statuscode:`404 Not found`: 573 The service is unaware of a matching login process or client. 574 Returned with error codes of 575 ``TALER_EC_CHALLENGER_GENERIC_CLIENT_UNKOWN`` 576 :http:statuscode:`409 Conflict`: 577 A ``code`` was presented for a validation process for which 578 the user has not (yet) submitted any address, so the token 579 cannot be issued. Returned with 580 ``TALER_EC_CHALLENGER_MISSING_ADDRESS``. 581 :http:statuscode:`500 Internal Server Error`: 582 The challenger service encountered an internal error, 583 for example a database failure or a failure of the SHA-256 584 or Base64 helpers used for PKCE verification. 585 Error codes used are: 586 * ``TALER_EC_CHALLENGER_GENERIC_DB_FETCH_FAILED`` 587 * ``TALER_EC_CHALLENGER_GENERIC_DB_STORE_FAILED`` 588 589 **Details::** 590 591 .. ts:def:: ChallengerAuthResponse 592 593 interface ChallengerAuthResponse { 594 // Token used to authenticate access in ``/info``. 595 access_token: string; 596 597 // Type of the access token. 598 token_type: "Bearer"; 599 600 // Amount of time that an access token is valid (in seconds). 601 expires_in: Integer; 602 603 } 604 605 606 .. _challenger-info: 607 608 ---- 609 Info 610 ---- 611 612 .. http:get:: /info 613 614 This userinfo endpoint of the OAuth 2.0 specification. 615 This endpoint is used by the client to obtain the user's validated address. 616 617 **Request:** 618 619 Must include the token returned to the client from the ``/token`` endpoint 620 as a ``Bearer`` token in an ``Authorization`` header. 621 622 **Response:** 623 624 :http:statuscode:`200 OK`: 625 The body contains the address as a `ChallengerInfoResponse`. 626 :http:statuscode:`403 Forbidden`: 627 The bearer token is missing or invalid (malformed). 628 :http:statuscode:`404 Not found`: 629 The bearer token is invalid (includes unknown or expired). 630 Returned with ``TALER_EC_CHALLENGER_GRANT_UNKNOWN``. 631 :http:statuscode:`500 Internal Server Error`: 632 The challenger service encountered an internal error, 633 typically a database failure. Usually returned with 634 ``TALER_EC_GENERIC_DB_FETCH_FAILED``. 635 636 **Details::** 637 638 .. ts:def:: ChallengerInfoResponse 639 640 interface ChallengerInfoResponse { 641 642 // Unique ID of the record within Challenger 643 // (identifies the rowid of the token). 644 id: Integer; 645 646 // Address that was validated. 647 // Key-value pairs, details depend on the 648 // address_type. 649 address: Object; 650 651 // Type of the address. 652 address_type: string; 653 654 // How long do we consider the address to be 655 // valid for this user. 656 expires: Timestamp; 657 658 }