challenger

OAuth 2.0-based authentication service that validates user can receive messages at a certain address
Log | Files | Refs | Submodules | README | LICENSE

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:
Msrc/challenger/challenger-admin.c | 11+++++++++--
Msrc/challengerdb/challenger-dbinit.c | 5++++-
Msrc/challengerdb/delete_client.c | 49+++++++++++++++++++++++++++++++++++++++++++------
Msrc/challengerdb/test_challenger_db.c | 83++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Msrc/include/challenger-database/delete_client.h | 14+++++++++++---
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