merchant

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

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


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