exchange

Base system with REST service to issue digital coins, run by the payment service provider
Log | Files | Refs | Submodules | README | LICENSE

commit 758afecc1bf42cbfa498f10a9a9735e99758b605
parent 0d8f1fa2b4998d6d44164512c8aeb1e861ab0a98
Author: Christian Grothoff <christian@grothoff.org>
Date:   Thu, 16 Nov 2023 15:24:24 +0100

add logic to serve AML SPA

Diffstat:
Mbootstrap | 13+++++++++++++
Dcontrib/Makefile.am | 63---------------------------------------------------------------
Acontrib/Makefile.am.in | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/exchange/Makefile.am | 3++-
Msrc/exchange/taler-exchange-httpd.c | 74+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Asrc/exchange/taler-exchange-httpd_spa.c | 362+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/exchange/taler-exchange-httpd_spa.h | 49+++++++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 564 insertions(+), 71 deletions(-)

diff --git a/bootstrap b/bootstrap @@ -39,4 +39,17 @@ else echo "Uncrustify not detected, hook not installed. Please install uncrustify if you plan on doing development" fi + +# Generate Makefile.am in contrib/ +cd contrib +rm -f Makefile.am +find wallet-core/aml-backoffice/ -type f -printf ' %p \\\n' | sort > Makefile.am.ext +# Remove extra '\' at the end of the file +truncate -s -2 Makefile.am.ext +cat Makefile.am.in Makefile.am.ext >> Makefile.am +# Prevent accidental editing of the generated Makefile.am +chmod -w Makefile.am +cd .. + +echo "$0: Running autoreconf" autoreconf -fi diff --git a/contrib/Makefile.am b/contrib/Makefile.am @@ -1,63 +0,0 @@ -# This file is in the public domain. - -SUBDIRS = . - -tmplpkgdatadir = $(datadir)/taler/exchange/templates/ -dist_tmplpkgdata_DATA = \ - persona-exchange-unauthorized.en.must \ - persona-load-failure.en.must \ - persona-exchange-unpaid.en.must \ - persona-logic-failure.en.must \ - persona-invalid-response.en.must \ - persona-network-timeout.en.must \ - persona-kyc-failed.en.must \ - persona-provider-failure.en.must - -termsdir=$(datadir)/taler/terms/ -terms_DATA = \ - exchange-tos-v0.rst \ - exchange-tos-bfh-v0.rst \ - exchange-pp-v0.rst - -install-exec-local: - find locale/ -name "*.po" - mkdir -p $(DESTDIR)$(datadir) - cp --parents -r $$(find locale/ -name "*.po") $(DESTDIR)$(datadir) - -rdatadir=$(datadir)/taler/exchange -rdata_DATA = \ - auditor-report.tex.j2 - -bin_SCRIPTS = \ - taler-auditor-dbconfig \ - taler-exchange-dbconfig \ - taler-terms-generator \ - taler-bank-manage-testing \ - taler-nexus-prepare - -edit_script = $(SED) -e 's,%termsdir%,$(termsdir),'g -e 's,%localedir%,$(localedir),'g $(NULL) -taler-terms-generator: taler-terms-generator.in - rm -f $@ $@.tmp && \ - $(edit_script) $< >$@.tmp && \ - chmod a-w+x $@.tmp && \ - mv $@.tmp $@ - -CLEANFILES = \ - taler-terms-generator - -EXTRA_DIST = \ - locale/de/LC_MESSAGES/exchange-tos-v0.po \ - taler-bank-manage-testing \ - taler-nexus-prepare \ - taler-terms-generator.in \ - taler-auditor-dbconfig \ - taler-exchange-dbconfig \ - gana-generate.sh \ - gana/gnu-taler-error-codes/registry.rec \ - gana/gnu-taler-error-codes/Makefile \ - $(terms_DATA) \ - $(rdata_DATA) \ - coverage.sh \ - gnunet.tag \ - microhttpd.tag \ - packages diff --git a/contrib/Makefile.am.in b/contrib/Makefile.am.in @@ -0,0 +1,71 @@ +# This file is in the public domain. + +SUBDIRS = . + +tmplpkgdatadir = $(datadir)/taler/exchange/templates/ +dist_tmplpkgdata_DATA = \ + persona-exchange-unauthorized.en.must \ + persona-load-failure.en.must \ + persona-exchange-unpaid.en.must \ + persona-logic-failure.en.must \ + persona-invalid-response.en.must \ + persona-network-timeout.en.must \ + persona-kyc-failed.en.must \ + persona-provider-failure.en.must + +termsdir=$(datadir)/taler/terms/ +terms_DATA = \ + exchange-tos-v0.rst \ + exchange-tos-bfh-v0.rst \ + exchange-pp-v0.rst + +install-exec-local: + find locale/ -name "*.po" + mkdir -p $(DESTDIR)$(datadir) + cp --parents -r $$(find locale/ -name "*.po") $(DESTDIR)$(datadir) + +rdatadir=$(datadir)/taler/exchange +rdata_DATA = \ + auditor-report.tex.j2 + +bin_SCRIPTS = \ + taler-auditor-dbconfig \ + taler-exchange-dbconfig \ + taler-terms-generator \ + taler-bank-manage-testing \ + taler-nexus-prepare + +edit_script = $(SED) -e 's,%termsdir%,$(termsdir),'g -e 's,%localedir%,$(localedir),'g $(NULL) +taler-terms-generator: taler-terms-generator.in + rm -f $@ $@.tmp && \ + $(edit_script) $< >$@.tmp && \ + chmod a-w+x $@.tmp && \ + mv $@.tmp $@ + +CLEANFILES = \ + taler-terms-generator + +EXTRA_DIST = \ + locale/de/LC_MESSAGES/exchange-tos-v0.po \ + taler-bank-manage-testing \ + taler-nexus-prepare \ + taler-terms-generator.in \ + taler-auditor-dbconfig \ + taler-exchange-dbconfig \ + gana-generate.sh \ + gana/gnu-taler-error-codes/registry.rec \ + gana/gnu-taler-error-codes/Makefile \ + $(terms_DATA) \ + $(rdata_DATA) \ + coverage.sh \ + gnunet.tag \ + microhttpd.tag \ + packages + +spapkgdatadir = $(prefix)/share/taler/exchange/spa/ + +# This is for the single-page-app imported from the wallet-core.git +# prebuilt branch. This MUST be the last line in the +# Makefile.am.in, as it will be combined with the +# actual SPA data by 'bootstrap'! +dist_spapkgdata_DATA = \ diff --git a/src/exchange/Makefile.am b/src/exchange/Makefile.am @@ -181,8 +181,9 @@ taler_exchange_httpd_SOURCES = \ taler-exchange-httpd_reserves_open.c taler-exchange-httpd_reserves_open.h \ taler-exchange-httpd_reserves_purse.c taler-exchange-httpd_reserves_purse.h \ taler-exchange-httpd_responses.c taler-exchange-httpd_responses.h \ + taler-exchange-httpd_spa.c taler-exchange-httpd_spa.h \ taler-exchange-httpd_terms.c taler-exchange-httpd_terms.h \ - taler-exchange-httpd_transfers_get.c taler-exchange-httpd_transfers_get.h + taler-exchange-httpd_transfers_get.c taler-exchange-httpd_transfers_get.h taler_exchange_httpd_LDADD = \ $(LIBGCRYPT_LIBS) \ diff --git a/src/exchange/taler-exchange-httpd.c b/src/exchange/taler-exchange-httpd.c @@ -68,6 +68,7 @@ #include "taler-exchange-httpd_reserves_history.h" #include "taler-exchange-httpd_reserves_open.h" #include "taler-exchange-httpd_reserves_purse.h" +#include "taler-exchange-httpd_spa.h" #include "taler-exchange-httpd_terms.h" #include "taler-exchange-httpd_transfers_get.h" #include "taler_exchangedb_lib.h" @@ -1508,6 +1509,56 @@ handle_post_auditors (struct TEH_RequestContext *rc, /** + * Generates the response for "/", redirecting the + * client to the "/webui/" from where we serve the SPA. + * + * @param rc request context + * @param args remaining arguments (should be empty) + * @return MHD result code + */ +static MHD_RESULT +spa_redirect (struct TEH_RequestContext *rc, + const char *const args[]) +{ + const char *text = "Redirecting to /webui/"; + struct MHD_Response *response; + + response = MHD_create_response_from_buffer (strlen (text), + (void *) text, + MHD_RESPMEM_PERSISTENT); + if (NULL == response) + { + GNUNET_break (0); + return MHD_NO; + } + TALER_MHD_add_global_headers (response); + GNUNET_break (MHD_YES == + MHD_add_response_header (response, + MHD_HTTP_HEADER_CONTENT_TYPE, + "text/plain")); + if (MHD_NO == + MHD_add_response_header (response, + MHD_HTTP_HEADER_LOCATION, + "/webui/")) + { + GNUNET_break (0); + MHD_destroy_response (response); + return MHD_NO; + } + + { + MHD_RESULT ret; + + ret = MHD_queue_response (rc->connection, + MHD_HTTP_FOUND, + response); + MHD_destroy_response (response); + return ret; + } +} + + +/** * Handle incoming HTTP request. * * @param cls closure for MHD daemon (unused) @@ -1540,15 +1591,11 @@ handle_mhd_request (void *cls, .data = "User-agent: *\nDisallow: /\n", .response_code = MHD_HTTP_OK }, - /* Landing page, tell humans to go away. */ + /* Landing page, redirect to SPA */ { .url = "", .method = MHD_HTTP_METHOD_GET, - .handler.get = TEH_handler_static_response, - .mime_type = "text/plain", - .data = - "Hello, I'm the Taler exchange. This HTTP server is not for humans.\n", - .response_code = MHD_HTTP_OK + .handler.get = &spa_redirect }, /* AGPL licensing page, redirect to source. As per the AGPL-license, every deployment is required to offer the user a download of the source of @@ -1778,7 +1825,13 @@ handle_mhd_request (void *cls, .handler.post = &handle_post_aml, .nargs = 2 }, - + { + .url = "webui", + .method = MHD_HTTP_METHOD_GET, + .handler.get = &TEH_handler_spa, + .nargs = 1, + .nargs_is_upper_bound = true + }, /* mark end of list */ { @@ -2543,6 +2596,13 @@ run (void *cls, return; } if (GNUNET_OK != + TEH_spa_init ()) + { + global_ret = EXIT_NOTCONFIGURED; + GNUNET_SCHEDULER_shutdown (); + return; + } + if (GNUNET_OK != TALER_TEMPLATING_init ("exchange")) { global_ret = EXIT_FAILURE; diff --git a/src/exchange/taler-exchange-httpd_spa.c b/src/exchange/taler-exchange-httpd_spa.c @@ -0,0 +1,362 @@ +/* + This file is part of TALER + Copyright (C) 2020, 2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of EXCHANGEABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-exchange-httpd_spa.c + * @brief logic to load the single page app (/) + * @author Christian Grothoff + */ +#include "platform.h" +#include <gnunet/gnunet_util_lib.h> +#include <taler/taler_util.h> +#include <taler/taler_mhd_lib.h> +#include <gnunet/gnunet_mhd_compat.h> +#include "taler-exchange-httpd.h" + + +/** + * Resource from the WebUi. + */ +struct WebuiFile +{ + /** + * Kept in a DLL. + */ + struct WebuiFile *next; + + /** + * Kept in a DLL. + */ + struct WebuiFile *prev; + + /** + * Path this resource matches. + */ + char *path; + + /** + * SPA resource, compressed. + */ + struct MHD_Response *zspa; + + /** + * SPA resource, vanilla. + */ + struct MHD_Response *spa; + +}; + + +/** + * Resources of the WebuUI, kept in a DLL. + */ +static struct WebuiFile *webui_head; + +/** + * Resources of the WebuUI, kept in a DLL. + */ +static struct WebuiFile *webui_tail; + + +MHD_RESULT +TEH_handler_spa (struct TEH_RequestContext *rc, + const char *const args[]) +{ + struct WebuiFile *w = NULL; + const char *infix = args[0]; + + if ( (NULL == infix) || + (0 == strcmp (infix, + "")) ) + infix = "index.html"; + for (struct WebuiFile *pos = webui_head; + NULL != pos; + pos = pos->next) + if (0 == strcmp (infix, + pos->path)) + { + w = pos; + break; + } + if (NULL == w) + return TALER_MHD_reply_with_error (rc->connection, + MHD_HTTP_NOT_FOUND, + TALER_EC_GENERIC_ENDPOINT_UNKNOWN, + rc->url); + if ( (MHD_YES == + TALER_MHD_can_compress (rc->connection)) && + (NULL != w->zspa) ) + return MHD_queue_response (rc->connection, + MHD_HTTP_OK, + w->zspa); + return MHD_queue_response (rc->connection, + MHD_HTTP_OK, + w->spa); +} + + +/** + * Function called on each file to load for the WebUI. + * + * @param cls NULL + * @param dn name of the file to load + */ +static enum GNUNET_GenericReturnValue +build_webui (void *cls, + const char *dn) +{ + static struct + { + const char *ext; + const char *mime; + } mime_map[] = { + { + .ext = "css", + .mime = "text/css" + }, + { + .ext = "html", + .mime = "text/html" + }, + { + .ext = "js", + .mime = "text/javascript" + }, + { + .ext = "jpg", + .mime = "image/jpeg" + }, + { + .ext = "jpeg", + .mime = "image/jpeg" + }, + { + .ext = "png", + .mime = "image/png" + }, + { + .ext = "svg", + .mime = "image/svg+xml" + }, + { + .ext = NULL, + .mime = NULL + }, + }; + int fd; + struct stat sb; + struct MHD_Response *zspa = NULL; + struct MHD_Response *spa; + const char *ext; + const char *mime; + + (void) cls; + /* finally open template */ + fd = open (dn, + O_RDONLY); + if (-1 == fd) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "open", + dn); + return GNUNET_SYSERR; + } + if (0 != + fstat (fd, + &sb)) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "open", + dn); + GNUNET_break (0 == close (fd)); + return GNUNET_SYSERR; + } + + mime = NULL; + ext = strrchr (dn, '.'); + if (NULL == ext) + { + GNUNET_break (0 == close (fd)); + return GNUNET_OK; + } + ext++; + for (unsigned int i = 0; NULL != mime_map[i].ext; i++) + if (0 == strcasecmp (ext, + mime_map[i].ext)) + { + mime = mime_map[i].mime; + break; + } + + { + void *in; + ssize_t r; + size_t csize; + + in = GNUNET_malloc_large (sb.st_size); + if (NULL == in) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "malloc"); + GNUNET_break (0 == close (fd)); + return GNUNET_SYSERR; + } + r = read (fd, + in, + sb.st_size); + if ( (-1 == r) || + (sb.st_size != (size_t) r) ) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "read", + dn); + GNUNET_free (in); + GNUNET_break (0 == close (fd)); + return GNUNET_SYSERR; + } + csize = (size_t) r; + if (MHD_YES == + TALER_MHD_body_compress (&in, + &csize)) + { + zspa = MHD_create_response_from_buffer (csize, + in, + MHD_RESPMEM_MUST_FREE); + if (NULL != zspa) + { + if (MHD_NO == + MHD_add_response_header (zspa, + MHD_HTTP_HEADER_CONTENT_ENCODING, + "deflate")) + { + GNUNET_break (0); + MHD_destroy_response (zspa); + zspa = NULL; + } + if (NULL != mime) + GNUNET_break (MHD_YES == + MHD_add_response_header (zspa, + MHD_HTTP_HEADER_CONTENT_TYPE, + mime)); + } + } + else + { + GNUNET_free (in); + } + } + + spa = MHD_create_response_from_fd (sb.st_size, + fd); + if (NULL == spa) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, + "open", + dn); + GNUNET_break (0 == close (fd)); + if (NULL != zspa) + { + MHD_destroy_response (zspa); + zspa = NULL; + } + return GNUNET_SYSERR; + } + if (NULL != mime) + GNUNET_break (MHD_YES == + MHD_add_response_header (spa, + MHD_HTTP_HEADER_CONTENT_TYPE, + mime)); + + { + struct WebuiFile *w; + const char *fn; + + fn = strrchr (dn, '/'); + GNUNET_assert (NULL != fn); + w = GNUNET_new (struct WebuiFile); + w->path = GNUNET_strdup (fn + 1); + w->spa = spa; + w->zspa = zspa; + GNUNET_CONTAINER_DLL_insert (webui_head, + webui_tail, + w); + } + return GNUNET_OK; +} + + +enum GNUNET_GenericReturnValue +TEH_spa_init () +{ + char *dn; + + { + char *path; + + path = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR); + if (NULL == path) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + GNUNET_asprintf (&dn, + "%sexchange/spa/", + path); + GNUNET_free (path); + } + + if (-1 == + GNUNET_DISK_directory_scan (dn, + &build_webui, + NULL)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to load WebUI from `%s'\n", + dn); + GNUNET_free (dn); + return GNUNET_SYSERR; + } + GNUNET_free (dn); + return GNUNET_OK; +} + + +/** + * Nicely shut down. + */ +void __attribute__ ((destructor)) +get_spa_fini () +{ + struct WebuiFile *w; + + while (NULL != (w = webui_head)) + { + GNUNET_CONTAINER_DLL_remove (webui_head, + webui_tail, + w); + if (NULL != w->spa) + { + MHD_destroy_response (w->spa); + w->spa = NULL; + } + if (NULL != w->zspa) + { + MHD_destroy_response (w->zspa); + w->zspa = NULL; + } + GNUNET_free (w->path); + GNUNET_free (w); + } +} diff --git a/src/exchange/taler-exchange-httpd_spa.h b/src/exchange/taler-exchange-httpd_spa.h @@ -0,0 +1,49 @@ +/* + This file is part of TALER + Copyright (C) 2023 Taler Systems SA + + TALER is free software; you can redistribute it and/or modify it under the + terms of the GNU General Public License as published by the Free Software + Foundation; either version 3, or (at your option) any later version. + + TALER is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of EXCHANGEABILITY or FITNESS FOR + A PARTICULAR PURPOSE. See the GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along with + TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> +*/ +/** + * @file taler-exchange-httpd_spa.h + * @brief logic to preload and serve static files + * @author Christian Grothoff + */ +#ifndef TALER_EXCHANGE_HTTPD_SPA_H +#define TALER_EXCHANGE_HTTPD_SPA_H + +#include <microhttpd.h> +#include "taler-exchange-httpd.h" + + +/** + * Return our single-page-app user interface (see contrib/wallet-core/). + * + * @param rc context of the handler + * @param[in,out] args remaining arguments (ignored) + * @return #MHD_YES on success (reply queued), #MHD_NO on error (close connection) + */ +MHD_RESULT +TEH_handler_spa (struct TEH_RequestContext *rc, + const char *const args[]); + + +/** + * Preload and compress SPA files. + * + * @return #GNUNET_OK on success + */ +enum GNUNET_GenericReturnValue +TEH_spa_init (void); + + +#endif