commit 20aba703085bee6083254eb9cfca94e904d19719
parent bff7981e354ff85aa587f59a8ee2af450b7fe472
Author: Christian Grothoff <christian@grothoff.org>
Date: Thu, 6 Aug 2026 15:10:58 +0200
report number of in-flight validations the client deletion discarded
Diffstat:
5 files changed, 145 insertions(+), 17 deletions(-)
diff --git a/src/challenger/challenger-admin.c b/src/challenger/challenger-admin.c
@@ -106,6 +106,7 @@ run (void *cls,
if (del_flag)
{
enum GNUNET_DB_QueryStatus qs;
+ uint64_t validations_deleted;
if (NULL != client_id)
{
@@ -122,7 +123,8 @@ run (void *cls,
goto cleanup;
}
qs = CHALLENGERDB_delete_client (db,
- redirect_uri);
+ redirect_uri,
+ &validations_deleted);
switch (qs)
{
case GNUNET_DB_STATUS_SOFT_ERROR:
@@ -137,8 +139,13 @@ run (void *cls,
goto cleanup;
case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
if (! be_quiet)
+ {
+ fprintf (stdout,
+ "Client deleted, %llu validation(s) in progress discarded.\n",
+ (unsigned long long) validations_deleted);
fprintf (stdout,
- "Client deleted\n");
+ "Note: access tokens already issued to this client remain valid until they expire.\n");
+ }
break;
}
goto cleanup;
diff --git a/src/challengerdb/challenger-dbinit.c b/src/challengerdb/challenger-dbinit.c
@@ -120,9 +120,12 @@ main (int argc,
"reset",
"reset database (DANGEROUS: all existing data is lost!)",
&reset_db),
+ /* Note: challenger-httpd never runs the garbage collection itself, so
+ expired validations and tokens accumulate until an operator runs this
+ (typically from cron or a systemd timer). */
GNUNET_GETOPT_option_flag ('g',
"garbagecollect",
- "remove state data from database",
+ "remove expired validations and access tokens from the database; challenger-httpd does not do this on its own, so run this periodically",
&gc_db),
GNUNET_GETOPT_OPTION_END
};
diff --git a/src/challengerdb/delete_client.c b/src/challengerdb/delete_client.c
@@ -28,18 +28,55 @@
enum GNUNET_DB_QueryStatus
CHALLENGERDB_delete_client (struct CHALLENGERDB_PostgresContext *ctx,
- const char *client_redirect_uri)
+ const char *client_redirect_uri,
+ uint64_t *validations_deleted)
{
struct GNUNET_PQ_QueryParam params[] = {
GNUNET_PQ_query_param_string (client_redirect_uri),
GNUNET_PQ_query_param_end
};
+ bool deleted;
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("validations_deleted",
+ validations_deleted),
+ GNUNET_PQ_result_spec_bool ("deleted",
+ &deleted),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+ *validations_deleted = 0;
+ /* validations_client_serial_id_fkey is ON DELETE CASCADE, so deleting a
+ client silently discards all of its validations -- KYC processes that
+ are in flight right now. Delete them here explicitly instead, so that
+ we can tell the operator how many were lost. Note that access tokens
+ already issued are *not* affected: 'tokens' has no client reference at
+ all and thus cannot be revoked per client. */
PREPARE (ctx,
"delete_client",
- "DELETE FROM clients"
- " WHERE uri=$1;");
- return GNUNET_PQ_eval_prepared_non_select (ctx->conn,
- "delete_client",
- params);
+ "WITH victim AS ("
+ " SELECT client_serial_id"
+ " FROM clients"
+ " WHERE uri=$1"
+ "), dv AS ("
+ " DELETE FROM validations"
+ " WHERE client_serial_id IN"
+ " (SELECT client_serial_id FROM victim)"
+ " RETURNING validation_serial_id"
+ "), dc AS ("
+ " DELETE FROM clients"
+ " WHERE uri=$1"
+ " RETURNING client_serial_id"
+ ") SELECT"
+ " (SELECT COUNT(*) FROM dv) AS validations_deleted"
+ " ,EXISTS (SELECT 1 FROM dc) AS deleted;");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (ctx->conn,
+ "delete_client",
+ params,
+ rs);
+ if (0 >= qs)
+ return qs;
+ return deleted
+ ? GNUNET_DB_STATUS_SUCCESS_ONE_RESULT
+ : GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
}
diff --git a/src/challengerdb/test_challenger_db.c b/src/challengerdb/test_challenger_db.c
@@ -36,6 +36,8 @@
#include "challenger-database/get_validation_pkce.h"
#include "challenger_util.h"
#include "pg_helper.h"
+#include "challenger-database/delete_client.h"
+#include "challenger-database/do_insert_validation.h"
#define FAILIF(cond) \
@@ -537,14 +539,14 @@ exec_sql (const char *sql)
static enum GNUNET_GenericReturnValue
test_insert_client (void)
{
- uint64_t client_id;
+ uint64_t nclient_id;
bool uri_taken;
if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
CHALLENGERDB_insert_client (pg,
"https://example.com/a",
"secret-token:a",
- &client_id,
+ &nclient_id,
&uri_taken))
{
GNUNET_break (0);
@@ -560,7 +562,7 @@ test_insert_client (void)
CHALLENGERDB_insert_client (pg,
"https://example.com/a",
"secret-token:b",
- &client_id,
+ &nclient_id,
&uri_taken)) ||
(! uri_taken) )
{
@@ -586,7 +588,7 @@ test_insert_client (void)
CHALLENGERDB_insert_client (pg,
"https://example.com/b",
"secret-token:b",
- &client_id,
+ &nclient_id,
&uri_taken))
{
/* The PK collision must not go unnoticed. */
@@ -617,7 +619,7 @@ test_insert_client (void)
CHALLENGERDB_insert_client (pg,
"https://example.com/b",
"secret-token:b",
- &client_id,
+ &nclient_id,
&uri_taken)) ||
(uri_taken) )
{
@@ -629,6 +631,75 @@ test_insert_client (void)
/**
+ * Test that deleting a client reports how many validations it took with it:
+ * validations_client_serial_id_fkey cascades, so an operator would otherwise
+ * abort in-flight KYC processes without being told.
+ *
+ * @return #GNUNET_OK on success
+ */
+static enum GNUNET_GenericReturnValue
+test_delete_client (void)
+{
+ const char *uri = "https://example.com/del";
+ const char *secret = "secret-token:del";
+ struct GNUNET_TIME_Absolute expiration
+ = GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_HOURS);
+ uint64_t nclient_id;
+ uint64_t validations_deleted;
+ bool uri_taken;
+
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ CHALLENGERDB_insert_client (pg,
+ uri,
+ secret,
+ &nclient_id,
+ &uri_taken))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ for (unsigned int i = 0; i < 2; i++)
+ {
+ struct CHALLENGER_ValidationNonceP nonce;
+
+ GNUNET_CRYPTO_random_block (&nonce,
+ sizeof (nonce));
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ CHALLENGERDB_do_insert_validation (pg,
+ nclient_id,
+ secret,
+ &nonce,
+ expiration,
+ NULL))
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ }
+ if ( (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ CHALLENGERDB_delete_client (pg,
+ uri,
+ &validations_deleted)) ||
+ (2 != validations_deleted) )
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ /* Deleting it again finds nothing and discards nothing. */
+ if ( (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ CHALLENGERDB_delete_client (pg,
+ uri,
+ &validations_deleted)) ||
+ (0 != validations_deleted) )
+ {
+ GNUNET_break (0);
+ return GNUNET_SYSERR;
+ }
+ return GNUNET_OK;
+}
+
+
+/**
* Main function that will be run by the scheduler.
*
* @param cls closure with config
@@ -683,6 +754,8 @@ run (void *cls)
test_foreign_client_not_redeemable ());
FAILIF (GNUNET_OK !=
test_insert_client ());
+ FAILIF (GNUNET_OK !=
+ test_delete_client ());
result = 0;
drop:
GNUNET_break (GNUNET_OK ==
diff --git a/src/include/challenger-database/delete_client.h b/src/include/challenger-database/delete_client.h
@@ -26,14 +26,22 @@
struct CHALLENGERDB_PostgresContext;
/**
- * Delete client from the list of authorized clients.
+ * Delete client from the list of authorized clients. All validations of the
+ * client are deleted with it; access tokens that were already issued are
+ * *not* revoked, as the 'tokens' table has no client reference.
*
* @param cls
* @param client_url URL of the client
- * @return transaction status
+ * @param[out] validations_deleted set to the number of (in-flight)
+ * validations that were discarded along with the client
+ * @return transaction status:
+ * #GNUNET_DB_STATUS_SUCCESS_ONE_RESULT if the client was deleted
+ * #GNUNET_DB_STATUS_SUCCESS_NO_RESULTS if there is no such client
+ * #GNUNET_DB_STATUS_HARD_ERROR on failure
*/
enum GNUNET_DB_QueryStatus
CHALLENGERDB_delete_client (struct CHALLENGERDB_PostgresContext *ctx,
- const char *client_url);
+ const char *client_url,
+ uint64_t *validations_deleted);
#endif