summaryrefslogtreecommitdiff
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.tar.bz2
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.
// For how many years from now would the client like us to
// store the truth?
- storage_duration_years: Integer;
+ storage_duration_years: number;
}
@@ -482,6 +482,10 @@ charge per truth operation using GNU Taler.
The decrypted ``truth`` does not match the expectations of the authentication
backend, i.e. a phone number for sending an SMS is not a number, or
an e-mail address for sending an E-mail is not a valid e-mail address.
+ :http:statuscode:`429 Too Many Requests`:
+ The client exceeded the number of allowed attempts at providing
+ a valid response for the given time interval.
+ The response format is given by `RateLimitedMessage`_.
:http:statuscode:`503 Service Unavailable`:
Server is out of Service.
@@ -543,9 +547,29 @@ charge per truth operation using GNU Taler.
business_name: string;
// What is the expected wire transfer subject?
- wire_transfer_subject: Integer;
+ wire_transfer_subject: number;
// Hint about the origin account that must be used.
debit_account_hint: string;
}
+
+
+ .. _RateLimitedMessage:
+ .. ts:def:: RateLimitedMessage
+
+ interface RateLimitedMessage {
+
+ // Taler error code, TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED.
+ code: number;
+
+ // How many attempts are allowed per challenge?
+ request_limit: number;
+
+ // At what frequency are new challenges issued?
+ request_frequency: RelativeTime;
+
+ // The error message.
+ hint: string;
+
+ }
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;
/**
+ * Generate a response telling the client that answering this
+ * challenge failed because the rate limit has been exceeded.
+ *
+ * @param gc request to answer for
+ * @return MHD status code
+ */
+static MHD_RESULT
+reply_rate_limited (const struct GetContext *gc)
+{
+ return TALER_MHD_REPLY_JSON_PACK (
+ gc->connection,
+ MHD_HTTP_TOO_MANY_REQUESTS,
+ TALER_MHD_PACK_EC (TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED),
+ GNUNET_JSON_pack_uint64 ("request_limit",
+ gc->authorization->retry_counter),
+ GNUNET_JSON_pack_time_rel ("request_frequency",
+ gc->authorization->code_rotation_period));
+}
+
+
+/**
* Timeout requests that are past their due date.
*
* @param cls NULL
@@ -991,9 +1012,8 @@ run_authorization_process (struct MHD_Connection *connection,
/**
- * Use the database to rate-limit queries to the
- * authentication procedure, but without actually
- * storing 'real' challenge codes.
+ * Use the database to rate-limit queries to the authentication
+ * procedure, but without actually storing 'real' challenge codes.
*
* @param[in,out] gc context to rate limit requests for
* @return #GNUNET_OK if rate-limiting passes,
@@ -1034,10 +1054,7 @@ rate_limit (struct GetContext *gc)
if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
{
return (MHD_YES ==
- TALER_MHD_reply_with_error (gc->connection,
- MHD_HTTP_TOO_MANY_REQUESTS,
- TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED,
- NULL))
+ reply_rate_limited (gc))
? GNUNET_NO
: GNUNET_SYSERR;
}
@@ -1066,10 +1083,7 @@ rate_limit (struct GetContext *gc)
: GNUNET_SYSERR;
case ANASTASIS_DB_CODE_STATUS_NO_RESULTS:
return (MHD_YES ==
- TALER_MHD_reply_with_error (gc->connection,
- MHD_HTTP_TOO_MANY_REQUESTS,
- TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED,
- NULL))
+ reply_rate_limited (gc))
? GNUNET_NO
: GNUNET_SYSERR;
case ANASTASIS_DB_CODE_STATUS_VALID_CODE_STORED:
@@ -1640,10 +1654,7 @@ AH_handler_truth_get (
case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
/* 0 == retry_counter of existing challenge => rate limit exceeded */
GNUNET_free (decrypted_truth);
- return TALER_MHD_reply_with_error (connection,
- MHD_HTTP_TOO_MANY_REQUESTS,
- TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED,
- NULL);
+ return reply_rate_limited (gc);
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
/* challenge code was stored successfully*/
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
unsigned int http_status;
} open_challenge;
+ /**
+ * Details for #ANASTASIS_CHALLENGE_STATUS_RATE_LIMIT_EXCEEDED.
+ */
+ struct
+ {
+
+ /**
+ * How many requests are allowed at most per @e request_frequency?
+ */
+ uint32_t request_limit;
+
+ /**
+ * Frequency at which requests are allowed / new challenges are
+ * created.
+ */
+ struct GNUNET_TIME_Relative request_frequency;
+
+ } rate_limit_exceeded;
/**
* 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
} payment_required;
+ struct
+ {
+
+ /**
+ * How many requests are allowed at most per @e request_frequency?
+ */
+ uint32_t request_limit;
+
+ /**
+ * Frequency at which requests are allowed / new challenges are
+ * created.
+ */
+ struct GNUNET_TIME_Relative request_frequency;
+
+ } rate_limit_exceeded;
+
+
/**
* Response with details about a server-side failure, if
* @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,
{
struct ANASTASIS_ChallengeStartResponse csr = {
.cs = ANASTASIS_CHALLENGE_STATUS_RATE_LIMIT_EXCEEDED,
- .challenge = c
+ .challenge = c,
+ .details.rate_limit_exceeded.request_limit
+ = dd->details.rate_limit_exceeded.request_limit,
+ .details.rate_limit_exceeded.request_frequency
+ = dd->details.rate_limit_exceeded.request_frequency
};
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 (
json_t *err;
err = GNUNET_JSON_PACK (
- GNUNET_JSON_pack_string ("state",
- "rate-limit-exceeded"),
- GNUNET_JSON_pack_uint64 ("error_code",
- TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED));
+ GNUNET_JSON_pack_string (
+ "state",
+ "rate-limit-exceeded"),
+ GNUNET_JSON_pack_uint64 (
+ "request_limit",
+ csr->details.rate_limit_exceeded.request_limit),
+ GNUNET_JSON_pack_time_rel (
+ "request_frequency",
+ csr->details.rate_limit_exceeded.request_frequency),
+ GNUNET_JSON_pack_uint64 (
+ "error_code",
+ TALER_EC_ANASTASIS_TRUTH_RATE_LIMITED));
GNUNET_assert (0 ==
json_object_set_new (feedback,
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,
break;
case MHD_HTTP_TOO_MANY_REQUESTS:
kdd.status = ANASTASIS_KSD_RATE_LIMIT_EXCEEDED;
+ {
+ struct GNUNET_JSON_Specification spec[] = {
+ GNUNET_JSON_spec_uint32 (
+ "request_limit",
+ &kdd.details.rate_limit_exceeded.request_limit),
+ GNUNET_JSON_spec_relative_time (
+ "request_frequency",
+ &kdd.details.rate_limit_exceeded.request_frequency),
+ GNUNET_JSON_spec_end ()
+ };
+ json_t *reply;
+
+ reply = json_loadb (data,
+ data_size,
+ JSON_REJECT_DUPLICATES,
+ NULL);
+ if (NULL == reply)
+ {
+ GNUNET_break_op (0);
+ kdd.status = ANASTASIS_KSD_SERVER_ERROR;
+ kdd.details.server_failure.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
+ kdd.details.server_failure.http_status = response_code;
+ break;
+ }
+ if (GNUNET_OK !=
+ GNUNET_JSON_parse (reply,
+ spec,
+ NULL, NULL))
+ {
+ GNUNET_break_op (0);
+ kdd.status = ANASTASIS_KSD_SERVER_ERROR;
+ kdd.details.server_failure.ec = TALER_JSON_get_error_code (reply);
+ kdd.details.server_failure.http_status = response_code;
+ json_decref (reply);
+ break;
+ }
+ json_decref (reply);
+ }
break;
case MHD_HTTP_INTERNAL_SERVER_ERROR:
/* Server had an internal issue; we should retry, but this API