aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2021-12-31 11:37:42 +0100
committerChristian Grothoff <christian@grothoff.org>2021-12-31 11:37:42 +0100
commitce443bb4d4815ac79170b81cae74fc8b8030ea54 (patch)
tree5ad2e67ea4179a4f766c3180bf44b06cc8577f33
parent9f7a6d50b4c6a79ab16dfabe2c57510565bc4cf2 (diff)
downloadanastasis-ce443bb4d4815ac79170b81cae74fc8b8030ea54.tar.gz
anastasis-ce443bb4d4815ac79170b81cae74fc8b8030ea54.zip
fix #7054: propagate more information on the rate-limiting
-rw-r--r--doc/sphinx/rest.rst28
-rw-r--r--src/backend/anastasis-httpd_truth.c41
-rw-r--r--src/include/anastasis.h18
-rw-r--r--src/include/anastasis_service.h17
-rw-r--r--src/lib/anastasis_recovery.c6
-rw-r--r--src/reducer/anastasis_api_recovery_redux.c16
-rw-r--r--src/restclient/anastasis_api_keyshare_lookup.c38
7 files changed, 142 insertions, 22 deletions
diff --git a/doc/sphinx/rest.rst b/doc/sphinx/rest.rst
index a1c5810..9127354 100644
--- a/doc/sphinx/rest.rst
+++ b/doc/sphinx/rest.rst
@@ -425,7 +425,7 @@ charge per truth operation using GNU Taler.
425 425
426 // For how many years from now would the client like us to 426 // For how many years from now would the client like us to
427 // store the truth? 427 // store the truth?
428 storage_duration_years: Integer; 428 storage_duration_years: number;
429 429
430 } 430 }
431 431
@@ -482,6 +482,10 @@ charge per truth operation using GNU Taler.
482 The decrypted ``truth`` does not match the expectations of the authentication 482 The decrypted ``truth`` does not match the expectations of the authentication
483 backend, i.e. a phone number for sending an SMS is not a number, or 483 backend, i.e. a phone number for sending an SMS is not a number, or
484 an e-mail address for sending an E-mail is not a valid e-mail address. 484 an e-mail address for sending an E-mail is not a valid e-mail address.
485 :http:statuscode:`429 Too Many Requests`:
486 The client exceeded the number of allowed attempts at providing
487 a valid response for the given time interval.
488 The response format is given by `RateLimitedMessage`_.
485 :http:statuscode:`503 Service Unavailable`: 489 :http:statuscode:`503 Service Unavailable`:
486 Server is out of Service. 490 Server is out of Service.
487 491
@@ -543,9 +547,29 @@ charge per truth operation using GNU Taler.
543 business_name: string; 547 business_name: string;
544 548
545 // What is the expected wire transfer subject? 549 // What is the expected wire transfer subject?
546 wire_transfer_subject: Integer; 550 wire_transfer_subject: number;
547 551
548 // Hint about the origin account that must be used. 552 // Hint about the origin account that must be used.
549 debit_account_hint: string; 553 debit_account_hint: string;
550 554
551 } 555 }
556
557
558 .. _RateLimitedMessage:
559 .. ts:def:: RateLimitedMessage
560
561 interface RateLimitedMessage {
562
563 // Taler error code, TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED.
564 code: number;
565
566 // How many attempts are allowed per challenge?
567 request_limit: number;
568
569 // At what frequency are new challenges issued?
570 request_frequency: RelativeTime;
571
572 // The error message.
573 hint: string;
574
575 }
diff --git a/src/backend/anastasis-httpd_truth.c b/src/backend/anastasis-httpd_truth.c
index 6c05ef8..54969bf 100644
--- a/src/backend/anastasis-httpd_truth.c
+++ b/src/backend/anastasis-httpd_truth.c
@@ -237,6 +237,27 @@ static struct GNUNET_SCHEDULER_Task *to_task;
237 237
238 238
239/** 239/**
240 * Generate a response telling the client that answering this
241 * challenge failed because the rate limit has been exceeded.
242 *
243 * @param gc request to answer for
244 * @return MHD status code
245 */
246static MHD_RESULT
247reply_rate_limited (const struct GetContext *gc)
248{
249 return TALER_MHD_REPLY_JSON_PACK (
250 gc->connection,
251 MHD_HTTP_TOO_MANY_REQUESTS,
252 TALER_MHD_PACK_EC (TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED),
253 GNUNET_JSON_pack_uint64 ("request_limit",
254 gc->authorization->retry_counter),
255 GNUNET_JSON_pack_time_rel ("request_frequency",
256 gc->authorization->code_rotation_period));
257}
258
259
260/**
240 * Timeout requests that are past their due date. 261 * Timeout requests that are past their due date.
241 * 262 *
242 * @param cls NULL 263 * @param cls NULL
@@ -991,9 +1012,8 @@ run_authorization_process (struct MHD_Connection *connection,
991 1012
992 1013
993/** 1014/**
994 * Use the database to rate-limit queries to the 1015 * Use the database to rate-limit queries to the authentication
995 * authentication procedure, but without actually 1016 * procedure, but without actually storing 'real' challenge codes.
996 * storing 'real' challenge codes.
997 * 1017 *
998 * @param[in,out] gc context to rate limit requests for 1018 * @param[in,out] gc context to rate limit requests for
999 * @return #GNUNET_OK if rate-limiting passes, 1019 * @return #GNUNET_OK if rate-limiting passes,
@@ -1034,10 +1054,7 @@ rate_limit (struct GetContext *gc)
1034 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs) 1054 if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
1035 { 1055 {
1036 return (MHD_YES == 1056 return (MHD_YES ==
1037 TALER_MHD_reply_with_error (gc->connection, 1057 reply_rate_limited (gc))
1038 MHD_HTTP_TOO_MANY_REQUESTS,
1039 TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED,
1040 NULL))
1041 ? GNUNET_NO 1058 ? GNUNET_NO
1042 : GNUNET_SYSERR; 1059 : GNUNET_SYSERR;
1043 } 1060 }
@@ -1066,10 +1083,7 @@ rate_limit (struct GetContext *gc)
1066 : GNUNET_SYSERR; 1083 : GNUNET_SYSERR;
1067 case ANASTASIS_DB_CODE_STATUS_NO_RESULTS: 1084 case ANASTASIS_DB_CODE_STATUS_NO_RESULTS:
1068 return (MHD_YES == 1085 return (MHD_YES ==
1069 TALER_MHD_reply_with_error (gc->connection, 1086 reply_rate_limited (gc))
1070 MHD_HTTP_TOO_MANY_REQUESTS,
1071 TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED,
1072 NULL))
1073 ? GNUNET_NO 1087 ? GNUNET_NO
1074 : GNUNET_SYSERR; 1088 : GNUNET_SYSERR;
1075 case ANASTASIS_DB_CODE_STATUS_VALID_CODE_STORED: 1089 case ANASTASIS_DB_CODE_STATUS_VALID_CODE_STORED:
@@ -1640,10 +1654,7 @@ AH_handler_truth_get (
1640 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS: 1654 case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
1641 /* 0 == retry_counter of existing challenge => rate limit exceeded */ 1655 /* 0 == retry_counter of existing challenge => rate limit exceeded */
1642 GNUNET_free (decrypted_truth); 1656 GNUNET_free (decrypted_truth);
1643 return TALER_MHD_reply_with_error (connection, 1657 return reply_rate_limited (gc);
1644 MHD_HTTP_TOO_MANY_REQUESTS,
1645 TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED,
1646 NULL);
1647 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT: 1658 case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
1648 /* challenge code was stored successfully*/ 1659 /* challenge code was stored successfully*/
1649 GNUNET_log (GNUNET_ERROR_TYPE_INFO, 1660 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
diff --git a/src/include/anastasis.h b/src/include/anastasis.h
index fd7ed40..b957f18 100644
--- a/src/include/anastasis.h
+++ b/src/include/anastasis.h
@@ -207,6 +207,24 @@ struct ANASTASIS_ChallengeStartResponse
207 unsigned int http_status; 207 unsigned int http_status;
208 } open_challenge; 208 } open_challenge;
209 209
210 /**
211 * Details for #ANASTASIS_CHALLENGE_STATUS_RATE_LIMIT_EXCEEDED.
212 */
213 struct
214 {
215
216 /**
217 * How many requests are allowed at most per @e request_frequency?
218 */
219 uint32_t request_limit;
220
221 /**
222 * Frequency at which requests are allowed / new challenges are
223 * created.
224 */
225 struct GNUNET_TIME_Relative request_frequency;
226
227 } rate_limit_exceeded;
210 228
211 /** 229 /**
212 * Response with details if 230 * Response with details if
diff --git a/src/include/anastasis_service.h b/src/include/anastasis_service.h
index b6b3d76..0ef31d6 100644
--- a/src/include/anastasis_service.h
+++ b/src/include/anastasis_service.h
@@ -570,6 +570,23 @@ struct ANASTASIS_KeyShareDownloadDetails
570 } payment_required; 570 } payment_required;
571 571
572 572
573 struct
574 {
575
576 /**
577 * How many requests are allowed at most per @e request_frequency?
578 */
579 uint32_t request_limit;
580
581 /**
582 * Frequency at which requests are allowed / new challenges are
583 * created.
584 */
585 struct GNUNET_TIME_Relative request_frequency;
586
587 } rate_limit_exceeded;
588
589
573 /** 590 /**
574 * Response with details about a server-side failure, if 591 * Response with details about a server-side failure, if
575 * @e status is #ANASTASIS_KSD_SERVER_ERROR, 592 * @e status is #ANASTASIS_KSD_SERVER_ERROR,
diff --git a/src/lib/anastasis_recovery.c b/src/lib/anastasis_recovery.c
index b85b0f6..3a7943e 100644
--- a/src/lib/anastasis_recovery.c
+++ b/src/lib/anastasis_recovery.c
@@ -319,7 +319,11 @@ keyshare_lookup_cb (void *cls,
319 { 319 {
320 struct ANASTASIS_ChallengeStartResponse csr = { 320 struct ANASTASIS_ChallengeStartResponse csr = {
321 .cs = ANASTASIS_CHALLENGE_STATUS_RATE_LIMIT_EXCEEDED, 321 .cs = ANASTASIS_CHALLENGE_STATUS_RATE_LIMIT_EXCEEDED,
322 .challenge = c 322 .challenge = c,
323 .details.rate_limit_exceeded.request_limit
324 = dd->details.rate_limit_exceeded.request_limit,
325 .details.rate_limit_exceeded.request_frequency
326 = dd->details.rate_limit_exceeded.request_frequency
323 }; 327 };
324 328
325 c->af (c->af_cls, 329 c->af (c->af_cls,
diff --git a/src/reducer/anastasis_api_recovery_redux.c b/src/reducer/anastasis_api_recovery_redux.c
index a26b6ad..088ff7e 100644
--- a/src/reducer/anastasis_api_recovery_redux.c
+++ b/src/reducer/anastasis_api_recovery_redux.c
@@ -765,10 +765,18 @@ answer_feedback_cb (
765 json_t *err; 765 json_t *err;
766 766
767 err = GNUNET_JSON_PACK ( 767 err = GNUNET_JSON_PACK (
768 GNUNET_JSON_pack_string ("state", 768 GNUNET_JSON_pack_string (
769 "rate-limit-exceeded"), 769 "state",
770 GNUNET_JSON_pack_uint64 ("error_code", 770 "rate-limit-exceeded"),
771 TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED)); 771 GNUNET_JSON_pack_uint64 (
772 "request_limit",
773 csr->details.rate_limit_exceeded.request_limit),
774 GNUNET_JSON_pack_time_rel (
775 "request_frequency",
776 csr->details.rate_limit_exceeded.request_frequency),
777 GNUNET_JSON_pack_uint64 (
778 "error_code",
779 TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED));
772 GNUNET_assert (0 == 780 GNUNET_assert (0 ==
773 json_object_set_new (feedback, 781 json_object_set_new (feedback,
774 uuid, 782 uuid,
diff --git a/src/restclient/anastasis_api_keyshare_lookup.c b/src/restclient/anastasis_api_keyshare_lookup.c
index 13390c9..99924d1 100644
--- a/src/restclient/anastasis_api_keyshare_lookup.c
+++ b/src/restclient/anastasis_api_keyshare_lookup.c
@@ -258,6 +258,44 @@ handle_keyshare_lookup_finished (void *cls,
258 break; 258 break;
259 case MHD_HTTP_TOO_MANY_REQUESTS: 259 case MHD_HTTP_TOO_MANY_REQUESTS:
260 kdd.status = ANASTASIS_KSD_RATE_LIMIT_EXCEEDED; 260 kdd.status = ANASTASIS_KSD_RATE_LIMIT_EXCEEDED;
261 {
262 struct GNUNET_JSON_Specification spec[] = {
263 GNUNET_JSON_spec_uint32 (
264 "request_limit",
265 &kdd.details.rate_limit_exceeded.request_limit),
266 GNUNET_JSON_spec_relative_time (
267 "request_frequency",
268 &kdd.details.rate_limit_exceeded.request_frequency),
269 GNUNET_JSON_spec_end ()
270 };
271 json_t *reply;
272
273 reply = json_loadb (data,
274 data_size,
275 JSON_REJECT_DUPLICATES,
276 NULL);
277 if (NULL == reply)
278 {
279 GNUNET_break_op (0);
280 kdd.status = ANASTASIS_KSD_SERVER_ERROR;
281 kdd.details.server_failure.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
282 kdd.details.server_failure.http_status = response_code;
283 break;
284 }
285 if (GNUNET_OK !=
286 GNUNET_JSON_parse (reply,
287 spec,
288 NULL, NULL))
289 {
290 GNUNET_break_op (0);
291 kdd.status = ANASTASIS_KSD_SERVER_ERROR;
292 kdd.details.server_failure.ec = TALER_JSON_get_error_code (reply);
293 kdd.details.server_failure.http_status = response_code;
294 json_decref (reply);
295 break;
296 }
297 json_decref (reply);
298 }
261 break; 299 break;
262 case MHD_HTTP_INTERNAL_SERVER_ERROR: 300 case MHD_HTTP_INTERNAL_SERVER_ERROR:
263 /* Server had an internal issue; we should retry, but this API 301 /* Server had an internal issue; we should retry, but this API