/*
   This file is part of Challenger
   Copyright (C) 2023 Taler Systems SA

   Challenger 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.

   Challenger 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
   Challenger; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */
/**
 * @file src/challengerdb/do_insert_token.c
 * @brief Implementation of the do_insert_token function for Postgres
 * @author Christian Grothoff
 */
#include "platform.h"
#include <taler/taler_error_codes.h>
#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "do_insert_token.h"
#include "pg_helper.h"


enum GNUNET_DB_QueryStatus
CHALLENGERDB_do_insert_token (struct CHALLENGERDB_PostgresContext *ctx,
                              const struct CHALLENGER_ValidationNonceP *nonce,
                              uint64_t client_id,
                              const struct CHALLENGER_AccessTokenP *token,
                              struct GNUNET_TIME_Relative token_expiration,
                              struct GNUNET_TIME_Relative address_expiration)
{
  struct GNUNET_TIME_Absolute now
    = GNUNET_TIME_absolute_get ();
  struct GNUNET_TIME_Absolute ge
    = GNUNET_TIME_relative_to_absolute (token_expiration);
  struct GNUNET_TIME_Absolute ae
    = GNUNET_TIME_relative_to_absolute (address_expiration);
  struct GNUNET_PQ_QueryParam params[] = {
    GNUNET_PQ_query_param_auto_from_type (nonce),
    GNUNET_PQ_query_param_auto_from_type (token),
    GNUNET_PQ_query_param_absolute_time (&ge),
    GNUNET_PQ_query_param_absolute_time (&ae),
    GNUNET_PQ_query_param_absolute_time (&now),
    GNUNET_PQ_query_param_uint64 (&client_id),
    GNUNET_PQ_query_param_end
  };

  /* Redemption must be atomic and one-shot (RFC 6749 4.1.2): consume the
     validation by deleting it as we mint the token, so the same authorization
     code cannot be replayed to mint additional tokens.  A replay finds no
     matching validations row and thus inserts no token (NO_RESULTS), which the
     caller maps to 'invalid_grant'.  The 'auth_attempts_left < 0' guard is what
     restricts us to validations that were actually *solved*: -1 is the sentinel
     challenger_do_validate_and_solve_pin() writes once the user entered the
     correct PIN.  The 'address IS NOT NULL' guard additionally avoids violating
     the tokens.address NOT NULL constraint.  Expiration and client are checked
     here as well and not merely by the get_validation_pkce() that preceded us:
     the two run in separate transactions, so anything we do not repeat is a
     TOCTOU window. */
  PREPARE (ctx,
           "do_insert_token",
           "WITH consumed AS ("
           "  DELETE FROM validations"
           "   WHERE nonce=$1"
           "     AND address IS NOT NULL"
           "     AND auth_attempts_left < 0"
           "     AND expiration_time > $5"
           "     AND client_serial_id=$6"
           "  RETURNING address"
           ") INSERT INTO tokens"
           " (access_token"
           " ,address"
           " ,token_expiration_time"
           " ,address_expiration_time"
           ") SELECT"
           " $2"
           " ,address"
           " ,$3"
           " ,$4"
           " FROM consumed;");
  return GNUNET_PQ_eval_prepared_non_select (ctx->conn,
                                             "do_insert_token",
                                             params);
}
