/*
  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 Affero 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 Affero General Public License for more details.

  You should have received a copy of the GNU Affero General Public License along with
  Challenger; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
*/
/**
 * @file challenger-httpd_info.c
 * @brief functions to handle incoming requests for infos
 * @author Christian Grothoff
 */
#include "platform.h"
#include "challenger-httpd.h"
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_db_lib.h>
#include "challenger-httpd_common.h"
#include "challenger-httpd_info.h"
#include "challenger-database/get_token.h"


/**
 * Prefix of a 'Bearer' token in an 'Authorization' HTTP header.
 */
#define BEARER_PREFIX "Bearer "


/**
 * Maximum number of retries for the database interaction.
 */
#define MAX_RETRIES 3


/**
 * Reply to @a connection that the bearer token is not (or no longer)
 * valid, as per RFC 6750 Section 3.1.  Used both for tokens that do not
 * even decode and for tokens that are simply unknown, so that the reply
 * does not first reveal whether a guessed token is well-formed and only
 * then whether it exists.
 *
 * @param connection connection to reply on
 * @return MHD result code
 */
static enum MHD_Result
reply_invalid_token (struct MHD_Connection *connection)
{
  return CH_reply_with_oauth_error (
    connection,
    MHD_HTTP_UNAUTHORIZED,
    "invalid_token",
    TALER_EC_CHALLENGER_TOKEN_UNKNOWN,
    NULL);
}


enum MHD_Result
CH_handler_info (struct CH_HandlerContext *hc,
                 const char *upload_data,
                 size_t *upload_data_size)
{
  const char *auth;
  const char *token;
  struct CHALLENGER_AccessTokenP grant;

  (void) upload_data;
  (void) upload_data_size;
  auth = MHD_lookup_connection_value (hc->connection,
                                      MHD_HEADER_KIND,
                                      MHD_HTTP_HEADER_AUTHORIZATION);
  if (NULL == auth)
  {
    GNUNET_break_op (0);
    /* RFC 6750 section 3: a challenge answering a request that carried no
       authentication credentials must not include an error code. */
    return CH_reply_with_oauth_error (
      hc->connection,
      MHD_HTTP_UNAUTHORIZED,
      NULL,
      TALER_EC_GENERIC_PARAMETER_MISSING,
      MHD_HTTP_HEADER_AUTHORIZATION);
  }
  /* RFC 7235: the auth-scheme token ("Bearer") is case-insensitive. */
  if (0 != strncasecmp (auth,
                        BEARER_PREFIX,
                        strlen (BEARER_PREFIX)))
  {
    GNUNET_break_op (0);
    return CH_reply_with_oauth_error (
      hc->connection,
      MHD_HTTP_UNAUTHORIZED,
      "invalid_request",
      TALER_EC_GENERIC_PARAMETER_MALFORMED,
      MHD_HTTP_HEADER_AUTHORIZATION);
  }
  token = auth + strlen (BEARER_PREFIX);
  /* RFC 7235: the scheme and the credentials are separated by 1*SP;
     note that the single space was already in our BEARER_PREFIX */
  if (GNUNET_OK !=
      GNUNET_STRINGS_string_to_data (token,
                                     strlen (token),
                                     &grant,
                                     sizeof (grant)))
  {
    GNUNET_break_op (0);
    return reply_invalid_token (hc->connection);
  }

  /* Check token is valid */
  for (unsigned int r = 0; r<MAX_RETRIES; r++)
  {
    uint64_t id;
    json_t *address;
    enum GNUNET_DB_QueryStatus qs;
    struct GNUNET_TIME_Timestamp address_expiration;

    qs = CHALLENGERDB_get_token (CH_context,
                                 &grant,
                                 &id,
                                 &address,
                                 &address_expiration);
    switch (qs)
    {
    case GNUNET_DB_STATUS_HARD_ERROR:
      GNUNET_break (0);
      return CH_reply_with_oauth_error (hc->connection,
                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
                                        "server_error",
                                        TALER_EC_GENERIC_DB_FETCH_FAILED,
                                        "get_token");
    case GNUNET_DB_STATUS_SOFT_ERROR:
      if (r < MAX_RETRIES - 1)
        continue;
      /* Retries exhausted on serialization failures; unlike a hard error
         this is worth retrying from the client side. */
      GNUNET_break (0);
      return CH_reply_with_oauth_error (hc->connection,
                                        MHD_HTTP_INTERNAL_SERVER_ERROR,
                                        "server_error",
                                        TALER_EC_GENERIC_DB_SOFT_FAILURE,
                                        "get_token");
    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
      return reply_invalid_token (hc->connection);
    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
      break;
    }
    return TALER_MHD_REPLY_JSON_PACK (
      hc->connection,
      MHD_HTTP_OK,
      GNUNET_JSON_pack_uint64 ("id",
                               id),
      GNUNET_JSON_pack_object_steal ("address",
                                     address),
      GNUNET_JSON_pack_string ("address_type",
                               CH_address_type),
      GNUNET_JSON_pack_timestamp ("expires",
                                  address_expiration));
  }
  GNUNET_assert (0);
  return MHD_NO;
}
