merchant

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

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


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