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 }