merchant

Merchant backend to process payments, run by merchants
Log | Files | Refs | Submodules | README | LICENSE

taler-merchant-httpd_post-management-instances.c (25005B)


      1 /*
      2   This file is part of TALER
      3   (C) 2020-2025 Taler Systems SA
      4 
      5   TALER is free software; you can redistribute it and/or modify
      6   it under the terms of the GNU Affero General Public License as
      7   published by the Free Software Foundation; either version 3,
      8   or (at your option) any later version.
      9 
     10   TALER is distributed in the hope that it will be useful, but
     11   WITHOUT ANY WARRANTY; without even the implied warranty of
     12   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13   GNU General Public License for more details.
     14 
     15   You should have received a copy of the GNU General Public
     16   License along with TALER; see the file COPYING.  If not,
     17   see <http://www.gnu.org/licenses/>
     18 */
     19 
     20 /**
     21  * @file taler-merchant-httpd_post-management-instances.c
     22  * @brief implementing POST /instances request handling
     23  * @author Christian Grothoff
     24  */
     25 #include "taler/platform.h"
     26 #include "taler-merchant-httpd_post-management-instances.h"
     27 #include "taler-merchant-httpd_helper.h"
     28 #include "taler-merchant-httpd.h"
     29 #include "taler-merchant-httpd_auth.h"
     30 #include "taler-merchant-httpd_mfa.h"
     31 #include "taler/taler_merchant_bank_lib.h"
     32 #include <taler/taler_dbevents.h>
     33 #include <taler/taler_json_lib.h>
     34 #include <regex.h>
     35 
     36 /**
     37  * How often do we retry the simple INSERT database transaction?
     38  */
     39 #define MAX_RETRIES 3
     40 
     41 
     42 /**
     43  * Generate an instance, given its configuration.
     44  *
     45  * @param rh context of the handler
     46  * @param connection the MHD connection to handle
     47  * @param[in,out] hc context with further information about the request
     48  * @param login_token_expiration set to how long a login token validity
     49  *   should be, use zero if no login token should be created
     50  * @param validation_needed true if self-provisioned and
     51  *   email/phone registration is required before the
     52  *   instance can become fully active
     53  * @return MHD result code
     54  */
     55 static MHD_RESULT
     56 post_instances (const struct TMH_RequestHandler *rh,
     57                 struct MHD_Connection *connection,
     58                 struct TMH_HandlerContext *hc,
     59                 struct GNUNET_TIME_Relative login_token_expiration,
     60                 bool validation_needed)
     61 {
     62   struct TALER_MERCHANTDB_InstanceSettings is = { 0 };
     63   struct TALER_MERCHANTDB_InstanceAuthSettings ias;
     64   const char *auth_password = NULL;
     65   struct TMH_WireMethod *wm_head = NULL;
     66   struct TMH_WireMethod *wm_tail = NULL;
     67   const json_t *jauth;
     68   const char *iphone = NULL;
     69   bool no_pay_delay;
     70   bool no_refund_delay;
     71   bool no_transfer_delay;
     72   bool no_rounding_interval;
     73   struct GNUNET_JSON_Specification spec[] = {
     74     GNUNET_JSON_spec_string ("id",
     75                              (const char **) &is.id),
     76     GNUNET_JSON_spec_string ("name",
     77                              (const char **) &is.name),
     78     GNUNET_JSON_spec_mark_optional (
     79       GNUNET_JSON_spec_string ("email",
     80                                (const char **) &is.email),
     81       NULL),
     82     GNUNET_JSON_spec_mark_optional (
     83       GNUNET_JSON_spec_string ("phone_number",
     84                                &iphone),
     85       NULL),
     86     GNUNET_JSON_spec_mark_optional (
     87       GNUNET_JSON_spec_string ("website",
     88                                (const char **) &is.website),
     89       NULL),
     90     GNUNET_JSON_spec_mark_optional (
     91       GNUNET_JSON_spec_string ("logo",
     92                                (const char **) &is.logo),
     93       NULL),
     94     GNUNET_JSON_spec_object_const ("auth",
     95                                    &jauth),
     96     GNUNET_JSON_spec_json ("address",
     97                            &is.address),
     98     GNUNET_JSON_spec_json ("jurisdiction",
     99                            &is.jurisdiction),
    100     GNUNET_JSON_spec_bool ("use_stefan",
    101                            &is.use_stefan),
    102     GNUNET_JSON_spec_mark_optional (
    103       GNUNET_JSON_spec_relative_time ("default_pay_delay",
    104                                       &is.default_pay_delay),
    105       &no_pay_delay),
    106     GNUNET_JSON_spec_mark_optional (
    107       GNUNET_JSON_spec_relative_time ("default_refund_delay",
    108                                       &is.default_refund_delay),
    109       &no_refund_delay),
    110     GNUNET_JSON_spec_mark_optional (
    111       GNUNET_JSON_spec_relative_time ("default_wire_transfer_delay",
    112                                       &is.default_wire_transfer_delay),
    113       &no_transfer_delay),
    114     GNUNET_JSON_spec_mark_optional (
    115       GNUNET_JSON_spec_time_rounder_interval (
    116         "default_wire_transfer_rounding_interval",
    117         &is.default_wire_transfer_rounding_interval),
    118       &no_rounding_interval),
    119     GNUNET_JSON_spec_end ()
    120   };
    121 
    122   {
    123     enum GNUNET_GenericReturnValue res;
    124 
    125     res = TALER_MHD_parse_json_data (connection,
    126                                      hc->request_body,
    127                                      spec);
    128     if (GNUNET_OK != res)
    129       return (GNUNET_NO == res)
    130              ? MHD_YES
    131              : MHD_NO;
    132   }
    133   if (no_pay_delay)
    134     is.default_pay_delay = TMH_default_pay_delay;
    135   if (no_refund_delay)
    136     is.default_refund_delay = TMH_default_refund_delay;
    137   if (no_transfer_delay)
    138     is.default_wire_transfer_delay = TMH_default_wire_transfer_delay;
    139   if (no_rounding_interval)
    140     is.default_wire_transfer_rounding_interval
    141       = TMH_default_wire_transfer_rounding_interval;
    142   if (GNUNET_TIME_relative_is_forever (is.default_pay_delay))
    143   {
    144     GNUNET_break_op (0);
    145     GNUNET_JSON_parse_free (spec);
    146     return TALER_MHD_reply_with_error (connection,
    147                                        MHD_HTTP_BAD_REQUEST,
    148                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    149                                        "default_pay_delay");
    150   }
    151   if (GNUNET_TIME_relative_is_forever (is.default_refund_delay))
    152   {
    153     GNUNET_break_op (0);
    154     GNUNET_JSON_parse_free (spec);
    155     return TALER_MHD_reply_with_error (connection,
    156                                        MHD_HTTP_BAD_REQUEST,
    157                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    158                                        "default_refund_delay");
    159   }
    160   if (GNUNET_TIME_relative_is_forever (is.default_wire_transfer_delay))
    161   {
    162     GNUNET_break_op (0);
    163     GNUNET_JSON_parse_free (spec);
    164     return TALER_MHD_reply_with_error (connection,
    165                                        MHD_HTTP_BAD_REQUEST,
    166                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    167                                        "default_wire_transfer_delay");
    168   }
    169   if (NULL != iphone)
    170   {
    171     is.phone = TALER_MERCHANT_phone_validate_normalize (iphone,
    172                                                         false);
    173     if (NULL == is.phone)
    174     {
    175       GNUNET_break_op (0);
    176       GNUNET_JSON_parse_free (spec);
    177       return TALER_MHD_reply_with_error (connection,
    178                                          MHD_HTTP_BAD_REQUEST,
    179                                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
    180                                          "phone_number");
    181     }
    182     if ( (NULL != TMH_phone_regex) &&
    183          (0 !=
    184           regexec (&TMH_phone_rx,
    185                    is.phone,
    186                    0,
    187                    NULL,
    188                    0)) )
    189     {
    190       GNUNET_break_op (0);
    191       GNUNET_JSON_parse_free (spec);
    192       return TALER_MHD_reply_with_error (connection,
    193                                          MHD_HTTP_BAD_REQUEST,
    194                                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
    195                                          "phone_number");
    196     }
    197   }
    198   if ( (NULL != is.email) &&
    199        (! TALER_MERCHANT_email_valid (is.email)) )
    200   {
    201     GNUNET_break_op (0);
    202     GNUNET_JSON_parse_free (spec);
    203     GNUNET_free (is.phone);
    204     return TALER_MHD_reply_with_error (connection,
    205                                        MHD_HTTP_BAD_REQUEST,
    206                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    207                                        "email");
    208   }
    209 
    210   {
    211     enum GNUNET_GenericReturnValue ret;
    212 
    213     ret = TMH_check_auth_config (connection,
    214                                  jauth,
    215                                  &auth_password);
    216     if (GNUNET_OK != ret)
    217     {
    218       GNUNET_free (is.phone);
    219       GNUNET_JSON_parse_free (spec);
    220       return (GNUNET_NO == ret) ? MHD_YES : MHD_NO;
    221     }
    222   }
    223 
    224   /* check 'id' well-formed */
    225   {
    226     static bool once;
    227     static regex_t reg;
    228     bool id_wellformed = true;
    229 
    230     if (! once)
    231     {
    232       once = true;
    233       GNUNET_assert (0 ==
    234                      regcomp (&reg,
    235                               "^[A-Za-z0-9][A-Za-z0-9_.@-]+$",
    236                               REG_EXTENDED));
    237     }
    238 
    239     if (0 != regexec (&reg,
    240                       is.id,
    241                       0, NULL, 0))
    242       id_wellformed = false;
    243     if (! id_wellformed)
    244     {
    245       GNUNET_JSON_parse_free (spec);
    246       GNUNET_free (is.phone);
    247       return TALER_MHD_reply_with_error (connection,
    248                                          MHD_HTTP_BAD_REQUEST,
    249                                          TALER_EC_GENERIC_PARAMETER_MALFORMED,
    250                                          "id");
    251     }
    252   }
    253 
    254   if (! TMH_location_object_valid (is.address))
    255   {
    256     GNUNET_break_op (0);
    257     GNUNET_JSON_parse_free (spec);
    258     GNUNET_free (is.phone);
    259     return TALER_MHD_reply_with_error (connection,
    260                                        MHD_HTTP_BAD_REQUEST,
    261                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    262                                        "address");
    263   }
    264 
    265   if (! TMH_location_object_valid (is.jurisdiction))
    266   {
    267     GNUNET_break_op (0);
    268     GNUNET_JSON_parse_free (spec);
    269     GNUNET_free (is.phone);
    270     return TALER_MHD_reply_with_error (connection,
    271                                        MHD_HTTP_BAD_REQUEST,
    272                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    273                                        "jurisdiction");
    274   }
    275 
    276   if ( (NULL != is.logo) &&
    277        (! TALER_MERCHANT_image_data_url_valid (is.logo)) )
    278   {
    279     GNUNET_break_op (0);
    280     GNUNET_JSON_parse_free (spec);
    281     GNUNET_free (is.phone);
    282     return TALER_MHD_reply_with_error (connection,
    283                                        MHD_HTTP_BAD_REQUEST,
    284                                        TALER_EC_GENERIC_PARAMETER_MALFORMED,
    285                                        "logo");
    286   }
    287 
    288   {
    289     /* Test if an instance of this id is known */
    290     struct TMH_MerchantInstance *mi;
    291 
    292     mi = TMH_lookup_instance (is.id);
    293     if (NULL != mi)
    294     {
    295       if (mi->deleted)
    296       {
    297         GNUNET_JSON_parse_free (spec);
    298         GNUNET_free (is.phone);
    299         return TALER_MHD_reply_with_error (connection,
    300                                            MHD_HTTP_CONFLICT,
    301                                            TALER_EC_MERCHANT_PRIVATE_POST_INSTANCES_PURGE_REQUIRED,
    302                                            is.id);
    303       }
    304       /* Check for idempotency */
    305       if ( (0 == strcmp (mi->settings.id,
    306                          is.id)) &&
    307            (0 == strcmp (mi->settings.name,
    308                          is.name)) &&
    309            ((mi->settings.email == is.email) ||
    310             (NULL != is.email && NULL != mi->settings.email &&
    311              0 == strcmp (mi->settings.email,
    312                           is.email))) &&
    313            ((mi->settings.website == is.website) ||
    314             (NULL != is.website && NULL != mi->settings.website &&
    315              0 == strcmp (mi->settings.website,
    316                           is.website))) &&
    317            ((mi->settings.logo == is.logo) ||
    318             (NULL != is.logo && NULL != mi->settings.logo &&
    319              0 == strcmp (mi->settings.logo,
    320                           is.logo))) &&
    321            ( ( (NULL != auth_password) &&
    322                (GNUNET_OK ==
    323                 TMH_check_auth (auth_password,
    324                                 &mi->auth.auth_salt,
    325                                 &mi->auth.auth_hash)) ) ||
    326              ( (NULL == auth_password) &&
    327                (GNUNET_YES ==
    328                 GNUNET_is_zero (&mi->auth.auth_hash))) ) &&
    329            (1 == json_equal (mi->settings.address,
    330                              is.address)) &&
    331            (1 == json_equal (mi->settings.jurisdiction,
    332                              is.jurisdiction)) &&
    333            (mi->settings.use_stefan == is.use_stefan) &&
    334            (GNUNET_TIME_relative_cmp (mi->settings.default_wire_transfer_delay,
    335                                       ==,
    336                                       is.default_wire_transfer_delay)) &&
    337            (GNUNET_TIME_relative_cmp (mi->settings.default_pay_delay,
    338                                       ==,
    339                                       is.default_pay_delay)) &&
    340            (GNUNET_TIME_relative_cmp (mi->settings.default_refund_delay,
    341                                       ==,
    342                                       is.default_refund_delay)) )
    343       {
    344         GNUNET_JSON_parse_free (spec);
    345         GNUNET_free (is.phone);
    346         return TALER_MHD_reply_static (connection,
    347                                        MHD_HTTP_NO_CONTENT,
    348                                        NULL,
    349                                        NULL,
    350                                        0);
    351       }
    352       GNUNET_JSON_parse_free (spec);
    353       GNUNET_free (is.phone);
    354       return TALER_MHD_reply_with_error (connection,
    355                                          MHD_HTTP_CONFLICT,
    356                                          TALER_EC_MERCHANT_PRIVATE_POST_INSTANCES_ALREADY_EXISTS,
    357                                          is.id);
    358     }
    359   }
    360 
    361   /* Check MFA is satisfied */
    362   if (validation_needed)
    363   {
    364     enum GNUNET_GenericReturnValue ret = GNUNET_SYSERR;
    365 
    366     if ( (0 != (TMH_TCS_SMS & TEH_mandatory_tan_channels)) &&
    367          (NULL == is.phone) )
    368     {
    369       GNUNET_break_op (0);
    370       GNUNET_JSON_parse_free (spec);
    371       GNUNET_free (is.phone); /* does nothing... */
    372       return TALER_MHD_reply_with_error (connection,
    373                                          MHD_HTTP_BAD_REQUEST,
    374                                          TALER_EC_GENERIC_PARAMETER_MISSING,
    375                                          "phone_number");
    376 
    377     }
    378     if ( (0 != (TMH_TCS_EMAIL & TEH_mandatory_tan_channels)) &&
    379          (NULL == is.email) )
    380     {
    381       GNUNET_break_op (0);
    382       GNUNET_JSON_parse_free (spec);
    383       GNUNET_free (is.phone);
    384       return TALER_MHD_reply_with_error (connection,
    385                                          MHD_HTTP_BAD_REQUEST,
    386                                          TALER_EC_GENERIC_PARAMETER_MISSING,
    387                                          "email");
    388     }
    389     switch (TEH_mandatory_tan_channels)
    390     {
    391     case TMH_TCS_NONE:
    392       GNUNET_assert (0);
    393       ret = GNUNET_OK;
    394       break;
    395     case TMH_TCS_SMS:
    396       is.phone_validated = true;
    397       ret = TMH_mfa_challenges_do (hc,
    398                                    TALER_MERCHANT_MFA_CO_INSTANCE_PROVISION,
    399                                    true,
    400                                    TALER_MERCHANT_MFA_CHANNEL_SMS,
    401                                    is.phone,
    402                                    TALER_MERCHANT_MFA_CHANNEL_NONE);
    403       break;
    404     case TMH_TCS_EMAIL:
    405       is.email_validated = true;
    406       ret = TMH_mfa_challenges_do (hc,
    407                                    TALER_MERCHANT_MFA_CO_INSTANCE_PROVISION,
    408                                    true,
    409                                    TALER_MERCHANT_MFA_CHANNEL_EMAIL,
    410                                    is.email,
    411                                    TALER_MERCHANT_MFA_CHANNEL_NONE);
    412       break;
    413     case TMH_TCS_EMAIL_AND_SMS:
    414       is.phone_validated = true;
    415       is.email_validated = true;
    416       ret = TMH_mfa_challenges_do (hc,
    417                                    TALER_MERCHANT_MFA_CO_INSTANCE_PROVISION,
    418                                    true,
    419                                    TALER_MERCHANT_MFA_CHANNEL_SMS,
    420                                    is.phone,
    421                                    TALER_MERCHANT_MFA_CHANNEL_EMAIL,
    422                                    is.email,
    423                                    TALER_MERCHANT_MFA_CHANNEL_NONE);
    424       break;
    425     }
    426     if (GNUNET_OK != ret)
    427     {
    428       GNUNET_JSON_parse_free (spec);
    429       GNUNET_free (is.phone);
    430       return (GNUNET_NO == ret)
    431         ? MHD_YES
    432         : MHD_NO;
    433     }
    434   }
    435 
    436   /* handle authentication token setup */
    437   if (NULL == auth_password)
    438   {
    439     memset (&ias.auth_salt,
    440             0,
    441             sizeof (ias.auth_salt));
    442     memset (&ias.auth_hash,
    443             0,
    444             sizeof (ias.auth_hash));
    445   }
    446   else
    447   {
    448     /* Sets 'auth_salt' and 'auth_hash' */
    449     TMH_compute_auth (auth_password,
    450                       &ias.auth_salt,
    451                       &ias.auth_hash);
    452   }
    453 
    454   /* create in-memory data structure */
    455   {
    456     struct TMH_MerchantInstance *mi;
    457     enum GNUNET_DB_QueryStatus qs;
    458 
    459     mi = GNUNET_new (struct TMH_MerchantInstance);
    460     mi->wm_head = wm_head;
    461     mi->wm_tail = wm_tail;
    462     mi->settings = is;
    463     mi->settings.address = json_incref (mi->settings.address);
    464     mi->settings.jurisdiction = json_incref (mi->settings.jurisdiction);
    465     mi->settings.id = GNUNET_strdup (is.id);
    466     mi->settings.name = GNUNET_strdup (is.name);
    467     if (NULL != is.email)
    468       mi->settings.email = GNUNET_strdup (is.email);
    469     mi->settings.phone = is.phone;
    470     is.phone = NULL;
    471     if (NULL != is.website)
    472       mi->settings.website = GNUNET_strdup (is.website);
    473     if (NULL != is.logo)
    474       mi->settings.logo = GNUNET_strdup (is.logo);
    475     mi->auth = ias;
    476     mi->validation_needed = validation_needed;
    477     GNUNET_CRYPTO_eddsa_key_create (&mi->merchant_priv.eddsa_priv);
    478     GNUNET_CRYPTO_eddsa_key_get_public (&mi->merchant_priv.eddsa_priv,
    479                                         &mi->merchant_pub.eddsa_pub);
    480 
    481     for (unsigned int i = 0; i<MAX_RETRIES; i++)
    482     {
    483       if (GNUNET_OK !=
    484           TMH_db->start (TMH_db->cls,
    485                          "post /instances"))
    486       {
    487         mi->rc = 1;
    488         TMH_instance_decref (mi);
    489         GNUNET_JSON_parse_free (spec);
    490         return TALER_MHD_reply_with_error (connection,
    491                                            MHD_HTTP_INTERNAL_SERVER_ERROR,
    492                                            TALER_EC_GENERIC_DB_START_FAILED,
    493                                            NULL);
    494       }
    495       qs = TMH_db->insert_instance (TMH_db->cls,
    496                                     &mi->merchant_pub,
    497                                     &mi->merchant_priv,
    498                                     &mi->settings,
    499                                     &mi->auth,
    500                                     validation_needed);
    501       switch (qs)
    502       {
    503       case GNUNET_DB_STATUS_HARD_ERROR:
    504         {
    505           MHD_RESULT ret;
    506 
    507           TMH_db->rollback (TMH_db->cls);
    508           GNUNET_break (0);
    509           ret = TALER_MHD_reply_with_error (connection,
    510                                             MHD_HTTP_INTERNAL_SERVER_ERROR,
    511                                             TALER_EC_GENERIC_DB_STORE_FAILED,
    512                                             is.id);
    513           mi->rc = 1;
    514           TMH_instance_decref (mi);
    515           GNUNET_JSON_parse_free (spec);
    516           return ret;
    517         }
    518       case GNUNET_DB_STATUS_SOFT_ERROR:
    519         goto retry;
    520       case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    521         {
    522           MHD_RESULT ret;
    523 
    524           TMH_db->rollback (TMH_db->cls);
    525           GNUNET_break (0);
    526           ret = TALER_MHD_reply_with_error (connection,
    527                                             MHD_HTTP_CONFLICT,
    528                                             TALER_EC_MERCHANT_PRIVATE_POST_INSTANCES_ALREADY_EXISTS,
    529                                             is.id);
    530           mi->rc = 1;
    531           TMH_instance_decref (mi);
    532           GNUNET_JSON_parse_free (spec);
    533           return ret;
    534         }
    535       case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    536         /* handled below */
    537         break;
    538       }
    539       qs = TMH_db->commit (TMH_db->cls);
    540       if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    541         qs = GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
    542 retry:
    543       if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
    544         break; /* success! -- or hard failure */
    545     } /* for .. MAX_RETRIES */
    546     if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != qs)
    547     {
    548       mi->rc = 1;
    549       TMH_instance_decref (mi);
    550       GNUNET_JSON_parse_free (spec);
    551       return TALER_MHD_reply_with_error (connection,
    552                                          MHD_HTTP_INTERNAL_SERVER_ERROR,
    553                                          TALER_EC_GENERIC_DB_COMMIT_FAILED,
    554                                          NULL);
    555     }
    556     /* Finally, also update our running process */
    557     GNUNET_assert (GNUNET_OK ==
    558                    TMH_add_instance (mi));
    559     TMH_reload_instances (mi->settings.id);
    560   }
    561   GNUNET_JSON_parse_free (spec);
    562   if (GNUNET_TIME_relative_is_zero (login_token_expiration))
    563   {
    564     return TALER_MHD_reply_static (connection,
    565                                    MHD_HTTP_NO_CONTENT,
    566                                    NULL,
    567                                    NULL,
    568                                    0);
    569   }
    570 
    571   {
    572     struct TALER_MERCHANTDB_LoginTokenP btoken;
    573     enum TMH_AuthScope iscope = TMH_AS_REFRESHABLE | TMH_AS_SPA;
    574     enum GNUNET_DB_QueryStatus qs;
    575     struct GNUNET_TIME_Timestamp expiration_time;
    576     bool refreshable = true;
    577 
    578     GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
    579                                 &btoken,
    580                                 sizeof (btoken));
    581     expiration_time
    582       = GNUNET_TIME_relative_to_timestamp (login_token_expiration);
    583     qs = TMH_db->insert_login_token (TMH_db->cls,
    584                                      is.id,
    585                                      &btoken,
    586                                      GNUNET_TIME_timestamp_get (),
    587                                      expiration_time,
    588                                      iscope,
    589                                      "login token from instance creation");
    590     switch (qs)
    591     {
    592     case GNUNET_DB_STATUS_HARD_ERROR:
    593     case GNUNET_DB_STATUS_SOFT_ERROR:
    594     case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    595       GNUNET_break (0);
    596       return TALER_MHD_reply_with_ec (connection,
    597                                       TALER_EC_GENERIC_DB_STORE_FAILED,
    598                                       "insert_login_token");
    599     case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
    600       break;
    601     }
    602 
    603     {
    604       char *tok;
    605       MHD_RESULT ret;
    606       char *val;
    607 
    608       val = GNUNET_STRINGS_data_to_string_alloc (&btoken,
    609                                                  sizeof (btoken));
    610       GNUNET_asprintf (&tok,
    611                        RFC_8959_PREFIX "%s",
    612                        val);
    613       GNUNET_free (val);
    614       ret = TALER_MHD_REPLY_JSON_PACK (
    615         connection,
    616         MHD_HTTP_OK,
    617         GNUNET_JSON_pack_string ("access_token",
    618                                  tok),
    619         GNUNET_JSON_pack_string ("token",
    620                                  tok),
    621         GNUNET_JSON_pack_string ("scope",
    622                                  TMH_get_name_by_scope (iscope,
    623                                                         &refreshable)),
    624         GNUNET_JSON_pack_bool ("refreshable",
    625                                refreshable),
    626         GNUNET_JSON_pack_timestamp ("expiration",
    627                                     expiration_time));
    628       GNUNET_free (tok);
    629       return ret;
    630     }
    631   }
    632 }
    633 
    634 
    635 /**
    636  * Generate an instance, given its configuration.
    637  *
    638  * @param rh context of the handler
    639  * @param connection the MHD connection to handle
    640  * @param[in,out] hc context with further information about the request
    641  * @return MHD result code
    642  */
    643 MHD_RESULT
    644 TMH_private_post_instances (const struct TMH_RequestHandler *rh,
    645                             struct MHD_Connection *connection,
    646                             struct TMH_HandlerContext *hc)
    647 {
    648   return post_instances (rh,
    649                          connection,
    650                          hc,
    651                          GNUNET_TIME_UNIT_ZERO,
    652                          false);
    653 }
    654 
    655 
    656 /**
    657  * Generate an instance, given its configuration.
    658  * Public handler to be used when self-provisioning.
    659  *
    660  * @param rh context of the handler
    661  * @param connection the MHD connection to handle
    662  * @param[in,out] hc context with further information about the request
    663  * @return MHD result code
    664  */
    665 MHD_RESULT
    666 TMH_public_post_instances (const struct TMH_RequestHandler *rh,
    667                            struct MHD_Connection *connection,
    668                            struct TMH_HandlerContext *hc)
    669 {
    670   struct GNUNET_TIME_Relative expiration;
    671 
    672   TALER_MHD_parse_request_rel_time (connection,
    673                                     "token_validity_ms",
    674                                     &expiration);
    675   if (GNUNET_YES !=
    676       TMH_have_self_provisioning)
    677   {
    678     GNUNET_break_op (0);
    679     return TALER_MHD_reply_with_error (connection,
    680                                        MHD_HTTP_FORBIDDEN,
    681                                        TALER_EC_MERCHANT_GENERIC_UNAUTHORIZED,
    682                                        "Self-provisioning is not enabled");
    683   }
    684 
    685   return post_instances (rh,
    686                          connection,
    687                          hc,
    688                          expiration,
    689                          TMH_TCS_NONE !=
    690                          TEH_mandatory_tan_channels);
    691 }
    692 
    693 
    694 /* end of taler-merchant-httpd_post-management-instances.c */