commit b40237ea89dc17e952ff067f95679ddf3205d687
parent 7c8cc2e03068f420a425f4756cda78f4294199ed
Author: Florian Dold <dold@taler.net>
Date: Mon, 24 Aug 2026 22:51:33 +0200
exchange KYC: restart expired provider processes
Diffstat:
17 files changed, 1001 insertions(+), 86 deletions(-)
diff --git a/src/exchange/taler-exchange-httpd_post-kyc-start-ID.c b/src/exchange/taler-exchange-httpd_post-kyc-start-ID.c
@@ -29,9 +29,8 @@
#include "taler-exchange-httpd_get-keys.h"
#include "taler-exchange-httpd_post-kyc-start-ID.h"
#include "taler-exchange-httpd_responses.h"
-#include "exchange-database/update_legitimization_process_by_row.h"
-#include "exchange-database/get_pending_legitimization_process.h"
-#include "exchange-database/insert_legitimization_process.h"
+#include "exchange-database/complete_legitimization_process_start.h"
+#include "exchange-database/do_reserve_legitimization_process.h"
#include "exchange-database/get_pending_legitimization.h"
@@ -83,6 +82,26 @@ struct KycPoller
struct TALER_KYCLOGIC_InitiateHandle *ih;
/**
+ * Provider configuration for @e ih_logic.
+ */
+ struct TALER_KYCLOGIC_ProviderDetails *pd;
+
+ /**
+ * Provider selected for this process.
+ */
+ const struct TALER_KYCLOGIC_KycProvider *provider;
+
+ /**
+ * Context passed to the provider initiation.
+ */
+ const json_t *context;
+
+ /**
+ * Task used while another request owns the initiation lease.
+ */
+ struct GNUNET_SCHEDULER_Task *retry_task;
+
+ /**
* Set of applicable KYC measures.
*/
json_t *jmeasures;
@@ -113,6 +132,11 @@ struct KycPoller
uint64_t process_row;
/**
+ * Generation of the provider initiation stored in @e process_row.
+ */
+ struct GNUNET_TIME_Absolute process_start_time;
+
+ /**
* Index of the measure this upload is for.
*/
unsigned int measure_index;
@@ -128,6 +152,11 @@ struct KycPoller
bool suspended;
/**
+ * True while waiting to retry the atomic process reservation.
+ */
+ bool waiting_for_process;
+
+ /**
* True if @e h_payto is for a wallet
*/
bool is_wallet;
@@ -161,6 +190,11 @@ TEH_kyc_start_cleanup ()
kyp->ih_logic->initiate_cancel (kyp->ih);
kyp->ih = NULL;
}
+ if (NULL != kyp->retry_task)
+ {
+ GNUNET_SCHEDULER_cancel (kyp->retry_task);
+ kyp->retry_task = NULL;
+ }
if (kyp->suspended)
{
kyp->suspended = false;
@@ -187,6 +221,11 @@ kyp_cleanup (struct TEH_RequestContext *rc)
kyp->ih_logic->initiate_cancel (kyp->ih);
kyp->ih = NULL;
}
+ if (NULL != kyp->retry_task)
+ {
+ GNUNET_SCHEDULER_cancel (kyp->retry_task);
+ kyp->retry_task = NULL;
+ }
GNUNET_free (kyp->redirect_url);
GNUNET_free (kyp->hint);
json_decref (kyp->jmeasures);
@@ -216,6 +255,44 @@ initiate_cb (
{
struct KycPoller *kyp = cls;
enum GNUNET_DB_QueryStatus qs;
+ struct GNUNET_TIME_Absolute process_expiration
+ = GNUNET_TIME_UNIT_ZERO_ABS;
+
+ if ( (TALER_EC_NONE == ec) &&
+ (NULL == redirect_url) )
+ {
+ GNUNET_break_op (0);
+ ec = TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY;
+ error_msg_hint = "KYC provider returned no redirect URL";
+ }
+ if ( (TALER_EC_NONE == ec) &&
+ (NULL != kyp->ih_logic->initiate_get_expiration) )
+ {
+ struct GNUNET_TIME_Timestamp provider_expiration;
+
+ provider_expiration
+ = kyp->ih_logic->initiate_get_expiration (kyp->ih);
+ process_expiration = provider_expiration.abs_time;
+ }
+ if ( (TALER_EC_NONE == ec) &&
+ GNUNET_TIME_absolute_is_zero (process_expiration) )
+ {
+ process_expiration = GNUNET_TIME_absolute_add (
+ kyp->process_start_time,
+ TALER_KYCLOGIC_provider_get_process_timeout (kyp->provider));
+ }
+ if ( (TALER_EC_NONE == ec) &&
+ ( (GNUNET_TIME_UNIT_FOREVER_ABS.abs_value_us ==
+ process_expiration.abs_value_us) ||
+ GNUNET_TIME_absolute_cmp (process_expiration,
+ <=,
+ GNUNET_TIME_absolute_get ()) ) )
+ {
+ GNUNET_break_op (0);
+ ec = TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_UNEXPECTED_REPLY;
+ redirect_url = NULL;
+ error_msg_hint = "KYC provider returned an invalid process expiration";
+ }
kyp->ih = NULL;
GNUNET_log (GNUNET_ERROR_TYPE_INFO,
@@ -225,34 +302,42 @@ initiate_cb (
(TALER_EC_NONE == ec)
? redirect_url
: error_msg_hint);
- kyp->ec = ec;
- if (TALER_EC_NONE == ec)
- {
- kyp->redirect_url = GNUNET_strdup (redirect_url);
- }
- else
- {
- kyp->hint = GNUNET_strdup (error_msg_hint);
- }
- qs = TALER_EXCHANGEDB_update_legitimization_process_by_row (
+ qs = TALER_EXCHANGEDB_complete_legitimization_process_start (
TEH_pg,
kyp->process_row,
+ kyp->process_start_time,
kyp->provider_name,
&kyp->h_payto,
provider_user_id,
provider_legitimization_id,
redirect_url,
- GNUNET_TIME_UNIT_ZERO_ABS,
+ process_expiration,
ec,
error_msg_hint,
TALER_EC_NONE != ec);
- if (qs <= 0)
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+ {
GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
- "KYC requirement update failed for %s with status %d at %s:%u\n",
+ "KYC process start update failed or was superseded for %s with status %d at %s:%u\n",
TALER_B2S (&kyp->h_payto),
qs,
__FILE__,
__LINE__);
+ kyp->ec = (qs < 0)
+ ? TALER_EC_GENERIC_DB_STORE_FAILED
+ : TALER_EC_EXCHANGE_KYC_GENERIC_PROVIDER_TIMEOUT;
+ kyp->hint = GNUNET_strdup ((qs < 0)
+ ? "complete_legitimization_process_start"
+ : "KYC process initiation was superseded");
+ }
+ else
+ {
+ kyp->ec = ec;
+ if (TALER_EC_NONE == ec)
+ kyp->redirect_url = GNUNET_strdup (redirect_url);
+ else
+ kyp->hint = GNUNET_strdup (error_msg_hint);
+ }
GNUNET_assert (kyp->suspended);
kyp->suspended = false;
GNUNET_CONTAINER_DLL_remove (kyp_head,
@@ -263,6 +348,139 @@ initiate_cb (
}
+/**
+ * Resume a request that was waiting for another process initiation.
+ *
+ * @param cls a `struct KycPoller *`
+ */
+static void
+retry_process_start (void *cls)
+{
+ struct KycPoller *kyp = cls;
+
+ kyp->retry_task = NULL;
+ GNUNET_assert (kyp->suspended);
+ kyp->suspended = false;
+ GNUNET_CONTAINER_DLL_remove (kyp_head,
+ kyp_tail,
+ kyp);
+ MHD_resume_connection (kyp->connection);
+ TALER_MHD_daemon_trigger ();
+}
+
+
+/**
+ * Atomically reserve, reuse or wait for a provider process.
+ *
+ * @param kyp request state
+ * @return MHD result
+ */
+static enum MHD_Result
+reserve_and_start_process (struct KycPoller *kyp)
+{
+ enum GNUNET_DB_QueryStatus qs;
+ enum TALER_EXCHANGEDB_KycProcessReservationState state;
+ struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+ struct GNUNET_TIME_Absolute lease_expiration
+ = GNUNET_TIME_absolute_add (now,
+ GNUNET_TIME_UNIT_MINUTES);
+
+ GNUNET_free (kyp->redirect_url);
+ kyp->redirect_url = NULL;
+ qs = TALER_EXCHANGEDB_do_reserve_legitimization_process (
+ TEH_pg,
+ &kyp->h_payto,
+ kyp->measure_index,
+ kyp->legitimization_measure_serial_id,
+ kyp->provider_name,
+ now,
+ lease_expiration,
+ &kyp->process_row,
+ &kyp->process_start_time,
+ &kyp->redirect_url,
+ &state);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+ {
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ kyp->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "do_reserve_legitimization_process");
+ }
+ switch (state)
+ {
+ case TALER_EXCHANGEDB_KPRS_READY:
+ GNUNET_assert (NULL != kyp->redirect_url);
+ return MHD_YES;
+ case TALER_EXCHANGEDB_KPRS_BUSY:
+ kyp->waiting_for_process = true;
+ kyp->retry_task = GNUNET_SCHEDULER_add_delayed (
+ GNUNET_TIME_UNIT_SECONDS,
+ &retry_process_start,
+ kyp);
+ kyp->suspended = true;
+ GNUNET_CONTAINER_DLL_insert (kyp_head,
+ kyp_tail,
+ kyp);
+ MHD_suspend_connection (kyp->connection);
+ return MHD_YES;
+ case TALER_EXCHANGEDB_KPRS_OWNER:
+ break;
+ default:
+ GNUNET_break (0);
+ return TALER_MHD_reply_with_error (
+ kyp->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_FETCH_FAILED,
+ "invalid KYC process reservation state");
+ }
+
+ kyp->ih = kyp->ih_logic->initiate (
+ kyp->ih_logic->cls,
+ kyp->pd,
+ &kyp->h_payto,
+ kyp->process_row,
+ kyp->context,
+ &initiate_cb,
+ kyp);
+ if (NULL == kyp->ih)
+ {
+ qs = TALER_EXCHANGEDB_complete_legitimization_process_start (
+ TEH_pg,
+ kyp->process_row,
+ kyp->process_start_time,
+ kyp->provider_name,
+ &kyp->h_payto,
+ NULL,
+ NULL,
+ NULL,
+ GNUNET_TIME_UNIT_ZERO_ABS,
+ TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_BUG,
+ "initiate KYC process",
+ true);
+ GNUNET_break (0);
+ if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
+ return TALER_MHD_reply_with_error (
+ kyp->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_GENERIC_DB_STORE_FAILED,
+ "complete_legitimization_process_start");
+ return TALER_MHD_reply_with_error (
+ kyp->connection,
+ MHD_HTTP_INTERNAL_SERVER_ERROR,
+ TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_BUG,
+ "initiate KYC process");
+ }
+ kyp->suspended = true;
+ GNUNET_CONTAINER_DLL_insert (kyp_head,
+ kyp_tail,
+ kyp);
+ MHD_suspend_connection (kyp->connection);
+ return MHD_YES;
+}
+
+
enum MHD_Result
TEH_handler_kyc_start (
struct TEH_RequestContext *rc,
@@ -277,7 +495,6 @@ TEH_handler_kyc_start (
const char *id = args[0];
enum GNUNET_DB_QueryStatus qs;
const struct TALER_KYCLOGIC_KycProvider *provider;
- struct TALER_KYCLOGIC_ProviderDetails *pd;
bool is_finished;
const json_t *context;
@@ -440,75 +657,17 @@ TEH_handler_kyc_start (
TALER_KYCLOGIC_provider_to_logic (
provider,
&kyp->ih_logic,
- &pd,
+ &kyp->pd,
&kyp->provider_name);
+ kyp->provider = provider;
+ kyp->context = context;
+ return reserve_and_start_process (kyp);
+ }
- /* FIXME-#9419: the next two DB interactions should be ONE
- transaction */
- /* Check if we already initiated this process */
- qs = TALER_EXCHANGEDB_get_pending_legitimization_process (
- TEH_pg,
- &kyp->h_payto,
- kyp->provider_name,
- &kyp->redirect_url);
- if (qs < 0)
- {
- GNUNET_break (0);
- /* Simple query, never should be a soft error. */
- GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR != qs);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_FETCH_FAILED,
- "get_pending_kyc_requirement_process");
- }
- if (NULL != kyp->redirect_url)
- return MHD_YES; /* success, return the redirect URL
- (in next call to this function) */
-
- /* set up new requirement process */
- qs = TALER_EXCHANGEDB_insert_legitimization_process (
- TEH_pg,
- &kyp->h_payto,
- kyp->measure_index,
- kyp->legitimization_measure_serial_id,
- kyp->provider_name,
- NULL, /* provider_account_id */
- NULL, /* provider_legitimziation_id */
- &kyp->process_row);
- if (qs < 0)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_GENERIC_DB_STORE_FAILED,
- "insert_kyc_requirement_process");
- }
-
- kyp->ih = kyp->ih_logic->initiate (
- kyp->ih_logic->cls,
- pd,
- &kyp->h_payto,
- kyp->process_row,
- context,
- &initiate_cb,
- kyp);
- if (NULL == kyp->ih)
- {
- GNUNET_break (0);
- return TALER_MHD_reply_with_error (
- rc->connection,
- MHD_HTTP_INTERNAL_SERVER_ERROR,
- TALER_EC_EXCHANGE_KYC_GENERIC_LOGIC_BUG,
- "initiate KYC process");
- }
- kyp->suspended = true;
- GNUNET_CONTAINER_DLL_insert (kyp_head,
- kyp_tail,
- kyp);
- MHD_suspend_connection (kyp->connection);
- return MHD_YES;
+ if (kyp->waiting_for_process)
+ {
+ kyp->waiting_for_process = false;
+ return reserve_and_start_process (kyp);
}
if ( (TALER_EC_NONE != kyp->ec) ||
diff --git a/src/exchangedb/complete_legitimization_process_start.c b/src/exchangedb/complete_legitimization_process_start.c
@@ -0,0 +1,82 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2026 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file exchangedb/complete_legitimization_process_start.c
+ * @brief conditionally complete external KYC process initiation
+ */
+#include "taler/taler_pq_lib.h"
+#include "exchange-database/complete_legitimization_process_start.h"
+#include "helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TALER_EXCHANGEDB_complete_legitimization_process_start (
+ struct TALER_EXCHANGEDB_PostgresContext *pg,
+ uint64_t process_row,
+ struct GNUNET_TIME_Absolute expected_start_time,
+ const char *provider_name,
+ const struct TALER_NormalizedPaytoHashP *h_payto,
+ const char *provider_account_id,
+ const char *provider_legitimization_id,
+ const char *redirect_url,
+ struct GNUNET_TIME_Absolute process_expiration,
+ enum TALER_ErrorCode ec,
+ const char *error_message_hint,
+ bool finished)
+{
+ uint32_t ec32 = (uint32_t) ec;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_uint64 (&process_row),
+ GNUNET_PQ_query_param_absolute_time (&expected_start_time),
+ GNUNET_PQ_query_param_string (provider_name),
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ (NULL != provider_account_id)
+ ? GNUNET_PQ_query_param_string (provider_account_id)
+ : GNUNET_PQ_query_param_null (),
+ (NULL != provider_legitimization_id)
+ ? GNUNET_PQ_query_param_string (provider_legitimization_id)
+ : GNUNET_PQ_query_param_null (),
+ (NULL != redirect_url)
+ ? GNUNET_PQ_query_param_string (redirect_url)
+ : GNUNET_PQ_query_param_null (),
+ GNUNET_PQ_query_param_absolute_time (&process_expiration),
+ GNUNET_PQ_query_param_uint32 (&ec32),
+ (NULL != error_message_hint)
+ ? GNUNET_PQ_query_param_string (error_message_hint)
+ : GNUNET_PQ_query_param_null (),
+ GNUNET_PQ_query_param_bool (finished),
+ GNUNET_PQ_query_param_end
+ };
+
+ PREPARE (pg,
+ "complete_legitimization_process_start",
+ "UPDATE legitimization_processes"
+ " SET provider_user_id=$5"
+ " ,provider_legitimization_id=$6"
+ " ,redirect_url=$7"
+ " ,process_expiration_time=$8"
+ " ,error_code=$9"
+ " ,error_message=$10"
+ " ,finished=$11"
+ " WHERE legitimization_process_serial_id=$1"
+ " AND start_time=$2"
+ " AND provider_name=$3"
+ " AND h_payto=$4;");
+ return GNUNET_PQ_eval_prepared_non_select (
+ pg->conn,
+ "complete_legitimization_process_start",
+ params);
+}
diff --git a/src/exchangedb/do_reserve_legitimization_process.c b/src/exchangedb/do_reserve_legitimization_process.c
@@ -0,0 +1,82 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2026 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file exchangedb/do_reserve_legitimization_process.c
+ * @brief atomically reserve or reuse an external KYC process
+ */
+#include "taler/taler_pq_lib.h"
+#include "exchange-database/do_reserve_legitimization_process.h"
+#include "helper.h"
+
+
+enum GNUNET_DB_QueryStatus
+TALER_EXCHANGEDB_do_reserve_legitimization_process (
+ struct TALER_EXCHANGEDB_PostgresContext *pg,
+ const struct TALER_NormalizedPaytoHashP *h_payto,
+ uint32_t measure_index,
+ uint64_t legitimization_measure_serial_id,
+ const char *provider_name,
+ struct GNUNET_TIME_Absolute now,
+ struct GNUNET_TIME_Absolute lease_expiration,
+ uint64_t *process_row,
+ struct GNUNET_TIME_Absolute *start_time,
+ char **redirect_url,
+ enum TALER_EXCHANGEDB_KycProcessReservationState *state)
+{
+ uint32_t state32;
+ struct GNUNET_PQ_QueryParam params[] = {
+ GNUNET_PQ_query_param_auto_from_type (h_payto),
+ GNUNET_PQ_query_param_uint32 (&measure_index),
+ GNUNET_PQ_query_param_uint64 (&legitimization_measure_serial_id),
+ GNUNET_PQ_query_param_string (provider_name),
+ GNUNET_PQ_query_param_absolute_time (&now),
+ GNUNET_PQ_query_param_absolute_time (&lease_expiration),
+ GNUNET_PQ_query_param_end
+ };
+ struct GNUNET_PQ_ResultSpec rs[] = {
+ GNUNET_PQ_result_spec_uint64 ("process_row",
+ process_row),
+ GNUNET_PQ_result_spec_absolute_time ("start_time",
+ start_time),
+ GNUNET_PQ_result_spec_allow_null (
+ GNUNET_PQ_result_spec_string ("redirect_url",
+ redirect_url),
+ NULL),
+ GNUNET_PQ_result_spec_uint32 ("state",
+ &state32),
+ GNUNET_PQ_result_spec_end
+ };
+ enum GNUNET_DB_QueryStatus qs;
+
+ *redirect_url = NULL;
+ PREPARE (pg,
+ "do_reserve_legitimization_process",
+ "SELECT"
+ " out_process_row AS process_row"
+ ",out_start_time AS start_time"
+ ",out_redirect_url AS redirect_url"
+ ",out_state AS state"
+ " FROM exchange_do_reserve_legitimization_process"
+ " ($1,$2,$3,$4,$5,$6);");
+ qs = GNUNET_PQ_eval_prepared_singleton_select (
+ pg->conn,
+ "do_reserve_legitimization_process",
+ params,
+ rs);
+ if (qs > 0)
+ *state = (enum TALER_EXCHANGEDB_KycProcessReservationState) state32;
+ return qs;
+}
diff --git a/src/exchangedb/do_reserve_legitimization_process.sql b/src/exchangedb/do_reserve_legitimization_process.sql
@@ -0,0 +1,121 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2026 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+DROP FUNCTION IF EXISTS exchange_do_reserve_legitimization_process;
+CREATE FUNCTION exchange_do_reserve_legitimization_process(
+ IN in_h_payto BYTEA,
+ IN in_measure_index INT4,
+ IN in_legitimization_measure_serial_id INT8,
+ IN in_provider_name TEXT,
+ IN in_now INT8,
+ IN in_lease_expiration INT8,
+ OUT out_process_row INT8,
+ OUT out_start_time INT8,
+ OUT out_redirect_url TEXT,
+ OUT out_state INT4)
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ my_rec RECORD;
+BEGIN
+ -- Keep these INT4 values synchronized with
+ -- enum TALER_EXCHANGEDB_KycProcessReservationState:
+ -- 0 = KPRS_READY, 1 = KPRS_OWNER, 2 = KPRS_BUSY.
+ PERFORM FROM kyc_targets
+ WHERE h_normalized_payto=in_h_payto
+ FOR UPDATE;
+
+ SELECT legitimization_process_serial_id,
+ start_time,
+ redirect_url
+ INTO my_rec
+ FROM legitimization_processes
+ WHERE h_payto=in_h_payto
+ AND provider_name=in_provider_name
+ AND NOT finished
+ AND process_expiration_time > in_now
+ ORDER BY start_time DESC
+ LIMIT 1
+ FOR UPDATE;
+
+ IF FOUND
+ THEN
+ out_process_row=my_rec.legitimization_process_serial_id;
+ out_start_time=my_rec.start_time;
+ out_redirect_url=my_rec.redirect_url;
+ IF out_redirect_url IS NULL
+ THEN
+ out_state=2;
+ ELSE
+ out_state=0;
+ END IF;
+ RETURN;
+ END IF;
+
+ UPDATE legitimization_processes
+ SET finished=TRUE
+ WHERE h_payto=in_h_payto
+ AND provider_name=in_provider_name
+ AND NOT finished;
+
+ INSERT INTO legitimization_processes
+ (h_payto,
+ start_time,
+ process_expiration_time,
+ expiration_time,
+ provider_name,
+ provider_user_id,
+ provider_legitimization_id,
+ redirect_url,
+ finished,
+ legitimization_measure_serial_id,
+ measure_index,
+ error_code,
+ error_message)
+ VALUES
+ (in_h_payto,
+ in_now,
+ in_lease_expiration,
+ 0,
+ in_provider_name,
+ NULL,
+ NULL,
+ NULL,
+ FALSE,
+ in_legitimization_measure_serial_id,
+ in_measure_index,
+ 0,
+ NULL)
+ ON CONFLICT (legitimization_measure_serial_id,measure_index)
+ DO UPDATE SET
+ h_payto=in_h_payto,
+ start_time=in_now,
+ process_expiration_time=in_lease_expiration,
+ expiration_time=0,
+ provider_name=in_provider_name,
+ provider_user_id=NULL,
+ provider_legitimization_id=NULL,
+ redirect_url=NULL,
+ finished=FALSE,
+ error_code=0,
+ error_message=NULL
+ RETURNING legitimization_process_serial_id,
+ start_time
+ INTO out_process_row,
+ out_start_time;
+ out_redirect_url=NULL;
+ out_state=1;
+END $$;
diff --git a/src/exchangedb/meson.build b/src/exchangedb/meson.build
@@ -251,6 +251,8 @@ libtalerexchangedb = library(
'template.c',
'get_exists_aml_officer.c',
'do_trigger_kyc_rule_for_account.c',
+ 'do_reserve_legitimization_process.c',
+ 'complete_legitimization_process_start.c',
'update_aggregation_transient.c',
'update_auditor.c',
'update_legitimization_process_by_row.c',
diff --git a/src/exchangedb/sql-schema/exchange-0015.sql b/src/exchangedb/sql-schema/exchange-0015.sql
@@ -0,0 +1,54 @@
+--
+-- This file is part of TALER
+-- Copyright (C) 2026 Taler Systems SA
+--
+-- TALER is free software; you can redistribute it and/or modify it under the
+-- terms of the GNU General Public License as published by the Free Software
+-- Foundation; either version 3, or (at your option) any later version.
+--
+-- TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+-- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+-- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+--
+-- You should have received a copy of the GNU General Public License along with
+-- TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+--
+
+BEGIN;
+
+SELECT _v.register_patch('exchange-0015', NULL, NULL);
+
+SET search_path TO exchange;
+
+CREATE FUNCTION alter_table_legitimization_processes15()
+RETURNS void
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ ALTER TABLE legitimization_processes
+ ADD COLUMN process_expiration_time INT8 NOT NULL DEFAULT (0);
+ COMMENT ON COLUMN legitimization_processes.process_expiration_time
+ IS 'time until which an unfinished external provider process may be reused; zero means expired';
+ CREATE INDEX legitimization_processes_active_by_account_provider
+ ON legitimization_processes
+ (h_payto, provider_name, start_time DESC)
+ WHERE NOT finished;
+ COMMENT ON INDEX legitimization_processes_active_by_account_provider
+ IS 'finds the newest reusable or currently initiating KYC process for an account and provider';
+END
+$$;
+
+INSERT INTO exchange_tables
+ (name
+ ,version
+ ,action
+ ,partitioned
+ ,by_range)
+ VALUES
+ ('legitimization_processes15'
+ ,'exchange-0015'
+ ,'alter'
+ ,TRUE
+ ,FALSE);
+
+COMMIT;
diff --git a/src/exchangedb/sql-schema/meson.build b/src/exchangedb/sql-schema/meson.build
@@ -35,6 +35,7 @@ procedures_sql = [
# semi-dead...
'exchange_do_get_link_data.sql',
'../do_trigger_kyc_rule_for_account.sql',
+ '../do_reserve_legitimization_process.sql',
'../get_legitimization_requirement_by_row.sql',
'../insert_active_legitimization_measure.sql',
'../iterate_aggregations_above_serial_id.sql',
@@ -188,6 +189,7 @@ generated_sql = [
['exchange-0012.sql', exchange_0012_sql],
['exchange-0013.sql', exchange_0013_sql],
['exchange-0014.sql', ['exchange-0014.sql']],
+ ['exchange-0015.sql', ['exchange-0015.sql']],
['tops-0001.sql', ['tops-0001.sql']],
]
diff --git a/src/exchangedb/test_legitimization_processes.c b/src/exchangedb/test_legitimization_processes.c
@@ -21,6 +21,8 @@
*
* Covers #TALER_EXCHANGEDB_insert_legitimization_process(),
* #TALER_EXCHANGEDB_update_legitimization_process_by_row(),
+ * #TALER_EXCHANGEDB_do_reserve_legitimization_process(),
+ * #TALER_EXCHANGEDB_complete_legitimization_process_start(),
* #TALER_EXCHANGEDB_insert_kyc_failure(),
* #TALER_EXCHANGEDB_insert_aml_program_failure(),
* #TALER_EXCHANGEDB_get_legitimization_process_by_account(),
@@ -35,6 +37,8 @@
* "redirect known" to "finished".
*/
#include "test_common.h"
+#include "exchange-database/complete_legitimization_process_start.h"
+#include "exchange-database/do_reserve_legitimization_process.h"
#include "exchange-database/do_trigger_kyc_rule_for_account.h"
#include "exchange-database/get_kyc_provider_account.h"
#include "exchange-database/get_legitimization_process_by_account.h"
@@ -574,6 +578,153 @@ check_references (struct TALER_EXCHANGEDB_PostgresContext *pg)
/**
+ * Process initiation is reserved atomically, completed only by the owner,
+ * and restarted after its provider-side expiration.
+ *
+ * @param pg the database context
+ * @return 0 on success
+ */
+static int
+check_reservation (struct TALER_EXCHANGEDB_PostgresContext *pg)
+{
+ const char *provider = "reservation-provider";
+ struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
+ struct GNUNET_TIME_Absolute lease_expiration
+ = GNUNET_TIME_absolute_add (now,
+ GNUNET_TIME_UNIT_MINUTES);
+ struct GNUNET_TIME_Absolute process_expiration
+ = GNUNET_TIME_absolute_add (now,
+ GNUNET_TIME_UNIT_HOURS);
+ struct GNUNET_TIME_Absolute start_time;
+ struct GNUNET_TIME_Absolute other_start_time;
+ struct GNUNET_TIME_Absolute restart_time;
+ struct GNUNET_TIME_Absolute restart_lease;
+ enum TALER_EXCHANGEDB_KycProcessReservationState state;
+ uint64_t row;
+ uint64_t other_row;
+ char *redirect_url = NULL;
+
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ TALER_EXCHANGEDB_do_reserve_legitimization_process (
+ pg,
+ &account.h_normalized,
+ 3,
+ measure_row,
+ provider,
+ now,
+ lease_expiration,
+ &row,
+ &start_time,
+ &redirect_url,
+ &state));
+ FAILIF (TALER_EXCHANGEDB_KPRS_OWNER != state);
+ FAILIF (NULL != redirect_url);
+
+ /* A concurrent request must wait for the reservation owner. */
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ TALER_EXCHANGEDB_do_reserve_legitimization_process (
+ pg,
+ &account.h_normalized,
+ 3,
+ measure_row,
+ provider,
+ now,
+ lease_expiration,
+ &other_row,
+ &other_start_time,
+ &redirect_url,
+ &state));
+ FAILIF (TALER_EXCHANGEDB_KPRS_BUSY != state);
+ FAILIF (row != other_row);
+ FAILIF (start_time.abs_value_us != other_start_time.abs_value_us);
+ FAILIF (NULL != redirect_url);
+
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ TALER_EXCHANGEDB_complete_legitimization_process_start (
+ pg,
+ row,
+ start_time,
+ provider,
+ &account.h_normalized,
+ "provider-account-reservation",
+ "provider-process-reservation",
+ "https://kyc.example/reserved",
+ process_expiration,
+ TALER_EC_NONE,
+ NULL,
+ false));
+
+ /* Once initiation completed, all requests reuse its redirect. */
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ TALER_EXCHANGEDB_do_reserve_legitimization_process (
+ pg,
+ &account.h_normalized,
+ 3,
+ measure_row,
+ provider,
+ now,
+ lease_expiration,
+ &other_row,
+ &other_start_time,
+ &redirect_url,
+ &state));
+ FAILIF (TALER_EXCHANGEDB_KPRS_READY != state);
+ FAILIF (row != other_row);
+ FAILIF (NULL == redirect_url);
+ FAILIF_C (0 != strcmp (redirect_url,
+ "https://kyc.example/reserved"),
+ GNUNET_free (redirect_url));
+ GNUNET_free (redirect_url);
+ redirect_url = NULL;
+
+ /* A callback from a superseded owner cannot overwrite the process. */
+ other_start_time = GNUNET_TIME_absolute_add (
+ start_time,
+ GNUNET_TIME_UNIT_SECONDS);
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS !=
+ TALER_EXCHANGEDB_complete_legitimization_process_start (
+ pg,
+ row,
+ other_start_time,
+ provider,
+ &account.h_normalized,
+ NULL,
+ NULL,
+ "https://kyc.example/late",
+ process_expiration,
+ TALER_EC_NONE,
+ NULL,
+ false));
+
+ /* Expiration makes the next request the owner of a fresh start. */
+ restart_time = GNUNET_TIME_absolute_add (
+ process_expiration,
+ GNUNET_TIME_UNIT_SECONDS);
+ restart_lease = GNUNET_TIME_absolute_add (
+ restart_time,
+ GNUNET_TIME_UNIT_MINUTES);
+ FAILIF (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT !=
+ TALER_EXCHANGEDB_do_reserve_legitimization_process (
+ pg,
+ &account.h_normalized,
+ 3,
+ measure_row,
+ provider,
+ restart_time,
+ restart_lease,
+ &other_row,
+ &other_start_time,
+ &redirect_url,
+ &state));
+ FAILIF (TALER_EXCHANGEDB_KPRS_OWNER != state);
+ FAILIF (row != other_row);
+ FAILIF (start_time.abs_value_us == other_start_time.abs_value_us);
+ FAILIF (NULL != redirect_url);
+ return 0;
+}
+
+
+/**
* The checks to run, in order.
*/
static const struct TDB_Test tests[] = {
@@ -589,6 +740,8 @@ static const struct TDB_Test tests[] = {
&check_aml_program_failure },
{ "legitimization-processes-references",
&check_references },
+ { "legitimization-processes-reservation",
+ &check_reservation },
{ NULL, NULL }
};
diff --git a/src/include/exchange-database/complete_legitimization_process_start.h b/src/include/exchange-database/complete_legitimization_process_start.h
@@ -0,0 +1,65 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2026 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file src/include/exchange-database/complete_legitimization_process_start.h
+ * @brief conditionally complete external KYC process initiation
+ */
+#ifndef EXCHANGE_DATABASE_COMPLETE_LEGITIMIZATION_PROCESS_START_H
+#define EXCHANGE_DATABASE_COMPLETE_LEGITIMIZATION_PROCESS_START_H
+
+#include "taler/taler_error_codes.h"
+#include "taler/taler_util.h"
+#include "exchangedb_lib.h"
+
+
+/**
+ * Store the result of provider initiation if the caller still owns the
+ * process generation. Matching @a expected_start_time prevents a late
+ * callback from overwriting a newer initiation in the same row.
+ *
+ * Primary test table: `legitimization_processes` (see
+ * test_legitimization_processes.c).
+ *
+ * @param pg database context
+ * @param process_row process row to update
+ * @param expected_start_time generation the caller reserved
+ * @param provider_name expected provider configuration name
+ * @param h_payto expected account
+ * @param provider_account_id account identifier returned by provider, or NULL
+ * @param provider_legitimization_id process identifier returned by provider, or NULL
+ * @param redirect_url provider redirect URL, or NULL on failure
+ * @param process_expiration time until the unfinished process may be reused
+ * @param ec initiation result
+ * @param error_message_hint optional human-readable error details
+ * @param finished true if initiation failed and the process is finished
+ * @return one result if updated, no results if superseded, negative on error
+ */
+enum GNUNET_DB_QueryStatus
+TALER_EXCHANGEDB_complete_legitimization_process_start (
+ struct TALER_EXCHANGEDB_PostgresContext *pg,
+ uint64_t process_row,
+ struct GNUNET_TIME_Absolute expected_start_time,
+ const char *provider_name,
+ const struct TALER_NormalizedPaytoHashP *h_payto,
+ const char *provider_account_id,
+ const char *provider_legitimization_id,
+ const char *redirect_url,
+ struct GNUNET_TIME_Absolute process_expiration,
+ enum TALER_ErrorCode ec,
+ const char *error_message_hint,
+ bool finished);
+
+#endif
diff --git a/src/include/exchange-database/do_reserve_legitimization_process.h b/src/include/exchange-database/do_reserve_legitimization_process.h
@@ -0,0 +1,72 @@
+/*
+ This file is part of TALER
+ Copyright (C) 2026 Taler Systems SA
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
+*/
+/**
+ * @file src/include/exchange-database/do_reserve_legitimization_process.h
+ * @brief atomically reserve or reuse an external KYC process
+ */
+#ifndef EXCHANGE_DATABASE_DO_RESERVE_LEGITIMIZATION_PROCESS_H
+#define EXCHANGE_DATABASE_DO_RESERVE_LEGITIMIZATION_PROCESS_H
+
+#include "taler/taler_util.h"
+#include "exchangedb_lib.h"
+
+enum TALER_EXCHANGEDB_KycProcessReservationState
+{
+ /** A reusable redirect URL is available. */
+ TALER_EXCHANGEDB_KPRS_READY = 0,
+
+ /** The caller owns the reservation and must initiate the provider. */
+ TALER_EXCHANGEDB_KPRS_OWNER = 1,
+
+ /** Another caller is currently initiating the provider. */
+ TALER_EXCHANGEDB_KPRS_BUSY = 2
+};
+
+
+/**
+ * Atomically reuse, wait for, or reserve an external KYC process.
+ *
+ * Primary test table: `legitimization_processes` (see
+ * test_legitimization_processes.c).
+ *
+ * @param pg database context
+ * @param h_payto account to validate
+ * @param measure_index selected measure index
+ * @param legitimization_measure_serial_id selected measure row
+ * @param provider_name provider configuration name
+ * @param now current time
+ * @param lease_expiration expiration of a newly reserved initiation lease
+ * @param[out] process_row process row that was selected or reserved
+ * @param[out] start_time generation of @a process_row
+ * @param[out] redirect_url reusable redirect for READY, otherwise NULL
+ * @param[out] state result of the reservation attempt
+ * @return database query status
+ */
+enum GNUNET_DB_QueryStatus
+TALER_EXCHANGEDB_do_reserve_legitimization_process (
+ struct TALER_EXCHANGEDB_PostgresContext *pg,
+ const struct TALER_NormalizedPaytoHashP *h_payto,
+ uint32_t measure_index,
+ uint64_t legitimization_measure_serial_id,
+ const char *provider_name,
+ struct GNUNET_TIME_Absolute now,
+ struct GNUNET_TIME_Absolute lease_expiration,
+ uint64_t *process_row,
+ struct GNUNET_TIME_Absolute *start_time,
+ char **redirect_url,
+ enum TALER_EXCHANGEDB_KycProcessReservationState *state);
+
+#endif
diff --git a/src/include/taler/taler_kyclogic_lib.h b/src/include/taler/taler_kyclogic_lib.h
@@ -603,6 +603,18 @@ TALER_KYCLOGIC_provider_to_logic (
/**
+ * Return the fallback lifetime for a provider process. This is used only
+ * when the provider does not report an authoritative expiration.
+ *
+ * @param provider provider to inspect
+ * @return finite fallback lifetime
+ */
+struct GNUNET_TIME_Relative
+TALER_KYCLOGIC_provider_get_process_timeout (
+ const struct TALER_KYCLOGIC_KycProvider *provider);
+
+
+/**
* Find default measure @a measure_name.
*
* @param measure_name name of measure to find
diff --git a/src/include/taler/taler_kyclogic_plugin.h b/src/include/taler/taler_kyclogic_plugin.h
@@ -416,6 +416,20 @@ struct TALER_KYCLOGIC_Plugin
/**
+ * Obtain the finite provider-reported expiration for an initiation result.
+ * Called from the initiation callback, before the plugin releases @a ih.
+ * A zero timestamp means that the provider did not report an expiration.
+ * Optional; NULL is equivalent to returning zero.
+ *
+ * @param ih active initiation handle
+ * @return provider-reported process expiration or zero
+ */
+ struct GNUNET_TIME_Timestamp
+ (*initiate_get_expiration)(
+ const struct TALER_KYCLOGIC_InitiateHandle *ih);
+
+
+ /**
* Cancel KYC check initiation.
*
* @param[in] ih handle of operation to cancel
diff --git a/src/kyclogic/kyclogic-kycaid.conf b/src/kyclogic/kyclogic-kycaid.conf
@@ -6,6 +6,10 @@
LOGIC = kycaid
+# Fallback lifetime for a provider process when the provider does not report
+# its own expiration. Optional, defaults to one day.
+KYC_PROCESS_TIMEOUT = 1 day
+
# How long is the KYC check valid?
KYC_KYCAID_VALIDITY = forever
diff --git a/src/kyclogic/kyclogic-oauth2.conf b/src/kyclogic/kyclogic-oauth2.conf
@@ -9,6 +9,10 @@ LOGIC = oauth2
USER_TYPE = INDIVIDUAL
PROVIDED_CHECKS = EXAMPLE_DO_NOT_USE
+# Fallback lifetime for a provider process when the provider does not report
+# its own expiration. Optional, defaults to one day.
+KYC_PROCESS_TIMEOUT = 1 day
+
# Converter that converts OAuth2.0 data about the user
# into GNU Taler standardized attribute data.
#
diff --git a/src/kyclogic/kyclogic-persona.conf b/src/kyclogic/kyclogic-persona.conf
@@ -17,6 +17,10 @@
LOGIC = persona
+# Fallback lifetime for a provider process when the provider does not report
+# its own expiration. Optional, defaults to one day.
+KYC_PROCESS_TIMEOUT = 1 day
+
# Generic converter that does nothing.
KYC_PERSONA_CONVERTER_HELPER = cat
@@ -42,4 +46,4 @@ KYC_PERSONA_POST_URL = https://taler.net/
# Salt to give to requests for idempotency.
# Optional.
-# KYC_PERSONA_SALT = salt
-\ No newline at end of file
+# KYC_PERSONA_SALT = salt
diff --git a/src/kyclogic/kyclogic_api.c b/src/kyclogic/kyclogic_api.c
@@ -54,6 +54,12 @@ struct TALER_KYCLOGIC_KycProvider
*/
struct TALER_KYCLOGIC_ProviderDetails *pd;
+ /**
+ * Maximum time to reuse a process when the provider does not report its
+ * authoritative expiration.
+ */
+ struct GNUNET_TIME_Relative process_timeout;
+
};
@@ -2168,6 +2174,41 @@ add_provider (const struct GNUNET_CONFIGURATION_Handle *cfg,
= GNUNET_strdup (§ion[strlen ("kyc-provider-")]);
kp->logic = lp;
kp->pd = pd;
+ kp->process_timeout = GNUNET_TIME_UNIT_DAYS;
+ if (GNUNET_YES ==
+ GNUNET_CONFIGURATION_have_value (cfg,
+ section,
+ "KYC_PROCESS_TIMEOUT"))
+ {
+ if (GNUNET_OK !=
+ GNUNET_CONFIGURATION_get_value_time (cfg,
+ section,
+ "KYC_PROCESS_TIMEOUT",
+ &kp->process_timeout))
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "KYC_PROCESS_TIMEOUT",
+ "finite positive duration required");
+ GNUNET_free (kp->provider_name);
+ GNUNET_free (kp);
+ lp->unload_configuration (pd);
+ return GNUNET_SYSERR;
+ }
+ }
+ if ( (0 == kp->process_timeout.rel_value_us) ||
+ (GNUNET_TIME_UNIT_FOREVER_REL.rel_value_us ==
+ kp->process_timeout.rel_value_us) )
+ {
+ GNUNET_log_config_invalid (GNUNET_ERROR_TYPE_ERROR,
+ section,
+ "KYC_PROCESS_TIMEOUT",
+ "finite positive duration required");
+ GNUNET_free (kp->provider_name);
+ GNUNET_free (kp);
+ lp->unload_configuration (pd);
+ return GNUNET_SYSERR;
+ }
GNUNET_array_append (kyc_providers,
num_kyc_providers,
kp);
@@ -2176,6 +2217,14 @@ add_provider (const struct GNUNET_CONFIGURATION_Handle *cfg,
}
+struct GNUNET_TIME_Relative
+TALER_KYCLOGIC_provider_get_process_timeout (
+ const struct TALER_KYCLOGIC_KycProvider *provider)
+{
+ return provider->process_timeout;
+}
+
+
/**
* Tokenize @a input along @a token
* and build an array of the tokens.
diff --git a/src/kyclogic/plugin_kyclogic_oauth2.c b/src/kyclogic/plugin_kyclogic_oauth2.c
@@ -190,6 +190,11 @@ struct TALER_KYCLOGIC_InitiateHandle
json_t *initial_address;
/**
+ * Expiration reported by a dynamic /setup endpoint.
+ */
+ struct GNUNET_TIME_Timestamp process_expiration;
+
+ /**
* Context for #TEH_curl_easy_post(). Keeps the data that must
* persist for Curl to make the upload.
*/
@@ -689,9 +694,14 @@ handle_curl_setup_finished (void *cls,
case MHD_HTTP_OK:
{
const char *nonce;
+ bool no_expiration;
struct GNUNET_JSON_Specification spec[] = {
GNUNET_JSON_spec_string ("nonce",
&nonce),
+ GNUNET_JSON_spec_mark_optional (
+ GNUNET_JSON_spec_timestamp ("expires",
+ &ih->process_expiration),
+ &no_expiration),
GNUNET_JSON_spec_end ()
};
enum GNUNET_GenericReturnValue res;
@@ -718,6 +728,23 @@ handle_curl_setup_finished (void *cls,
oauth2_initiate_cancel (ih);
return;
}
+ if ( (! no_expiration) &&
+ GNUNET_TIME_timestamp_cmp (
+ ih->process_expiration,
+ <=,
+ GNUNET_TIME_timestamp_get ()) )
+ {
+ GNUNET_break_op (0);
+ ih->cb (ih->cb_cls,
+ TALER_EC_EXCHANGE_KYC_PROOF_BACKEND_INVALID_RESPONSE,
+ NULL,
+ NULL,
+ NULL,
+ "KYC gateway returned an expired setup process");
+ GNUNET_JSON_parse_free (spec);
+ oauth2_initiate_cancel (ih);
+ return;
+ }
{
char *nonce_encoded;
@@ -750,6 +777,14 @@ handle_curl_setup_finished (void *cls,
}
+static struct GNUNET_TIME_Timestamp
+oauth2_initiate_get_expiration (
+ const struct TALER_KYCLOGIC_InitiateHandle *ih)
+{
+ return ih->process_expiration;
+}
+
+
/**
* Logic to asynchronously return the response for how to begin the OAuth2.0
* checking process to the client. May first request a dynamic URL via
@@ -1942,6 +1977,8 @@ libtaler_plugin_kyclogic_oauth2_init (void *cls)
= &oauth2_unload_configuration;
plugin->initiate
= &oauth2_initiate;
+ plugin->initiate_get_expiration
+ = &oauth2_initiate_get_expiration;
plugin->initiate_cancel
= &oauth2_initiate_cancel;
plugin->proof