taler-docs

Documentation for GNU Taler components, APIs and protocols
Log | Files | Refs | README | LICENSE

commit f5cd9e43a524132b8fc5717fd5401459c2732a65
parent aeaca65e39c0c55864ac77dd965fe8035304b4f8
Author: Christian Grothoff <christian@grothoff.org>
Date:   Sun,  4 Jan 2026 21:31:43 +0100

work on #10806

Diffstat:
Mcore/api-merchant.rst | 56+++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mdesign-documents/076-paywall-proxy.rst | 102++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------
2 files changed, 135 insertions(+), 23 deletions(-)

diff --git a/core/api-merchant.rst b/core/api-merchant.rst @@ -46,6 +46,7 @@ Android PoS app is currently targeting **v20**. **Upcoming versions:** * ``vTAXES``: adds features to manage taxes +* ``vPAIVANA``: adds features for templates to support session-based payments **Ideas for future version:** @@ -1015,6 +1016,59 @@ Querying payment status } +.. http:get:: [/instances/$INSTANCE]/sessions/$SESSION_ID + + Query the payment status for a session. This endpoint is for Web shops, + Paivana and other integrations (like JavaScript from inside the customer's + browser) that need an unauthenticated way to query a payment status where + they do not know the order ID because they *only* know the session ID they + passed into the template mechanism. Here, the "$SESSION_ID" is an arbitrary + string, the semantics of which are left to the application. However, it must + match the "$SESSION_ID" passed to the template mechanism. + + Since protocol **vPAIVANA**. + + **Request:** + + :query timeout_ms=NUMBER: *Optional.* If specified, the merchant backend will + wait up to ``timeout_ms`` milliseconds for completion of the payment before + sending the HTTP response. A client must never rely on this behavior, as the + merchant backend may return a response immediately. + :query claimed=BOOLEAN: *Optional*. If set to true, long-polling should + return as soon as a respective order was claimed. + + **Response:** + + :http:statuscode:`200 OK`: + The response is a `GetSessionStatusPaidResponse`. Returned if an + order that paid for the respective session exists and was paid. + :http:statuscode:`202 Accepted`: + The response is a `GetSessionStatusClaimedResponse`. Returned if an + order that paid for the respective session exists and was claimed, + but not yet paid. + :http:statuscode:`404 Not found`: + The merchant backend is unaware of an order matching the given session. + + **Details:** + + .. ts:def:: GetSessionStatusPaidResponse + + interface GetSessionStatusPaidResponse { + + // Order ID of the paid order. + order_id: String; + + } + + .. ts:def:: GetSessionStatusClaimedResponse + + interface GetSessionStatusClaimedResponse { + + // Order ID of the claimed order. + order_id: String; + + } + Demonstrating payment ^^^^^^^^^^^^^^^^^^^^^ @@ -1452,7 +1506,7 @@ Setting up instances The administrator must use the command-line tool if they forgot their password. - Since protocol **v21** + Since protocol **v21**. **Request** the request must be an `InstanceAuthConfigurationMessage`. diff --git a/design-documents/076-paywall-proxy.rst b/design-documents/076-paywall-proxy.rst @@ -20,11 +20,16 @@ Requirements * Must withstand high traffic from bots, requests before a payment happened must be *very* cheap, both in terms of response generation and database interaction. +* Should work not just for our paivana-httpd but also for Turnstile-style + paywalls that need to work with purely static paywall pages without + PHP sessions. + Proposed Solution ================= Architecture +------------ * paivana-httpd is a reverse proxy that sits between ingress HTTP(S) traffic and the protected upstream service. @@ -34,28 +39,69 @@ Architecture Steps: -* Browser visits git.taler.net -* paivana-httpd checks for a Paivana cookie - - * If cookie is set and valid, the request is reverse-proxied to upstream. *Stop.* - * Otherwise, a paywall page is rendered, continue. - -* The browser (rendering the paywall page) generates a random paivana ID via JS. -* Based on this paivana ID, a ``taler://pay-template/{paivana_backend}/.well-known/paivana/{template_id}?paivana_id={paivana_id}`` - URI is generated and rendered as a QR code and link. -* The browser long-polls on a ``{paivana_backend}/.well-known/pavivana/paivanas/{paivana_id}`` - endpoint that returns when an order with the given paivana ID has been paid +* Browser visits ``{website}`` + (for example, ``https://git.taler.net``) where + ``{domain}`` is the domain name of ``{website}``. +* paivana-httpd working as a reverse-proxy for + ``{website}``. Whenever called for a non-whitelisted + URL, it checks for a the presence of a Paivana cookie valid for + this client IP address and ``{website}`` at this time. + The *Paivana Cookie* is computed as: + + ``cur_time || '-' || H(website || client_ip || paivana_server_secret || cur_time)``. + + * If such a cookie is set and valid, the request is + reverse-proxied to upstream. *Stop.* + * Otherwise, a static non-cachable paywall page is returned, + including a machine-readable ``Paivana`` HTTP header with + the ``taler://pay-template/`` URL minus the client-computed + ``{paivana_id}`` and fullfillment URL (see below). + *Continue.* + +* The browser (rendering the paywall page) generates a random + *paivana ID* via JS using the current time (``cur_time``) in seconds + since the Epoch and the current URL (``{website}``) plus some + freshly generated entropy (``{nonce}``): + + ``paivana_id := cur_time || '-' || H(nonce || website || cur_time)``. + + The same computation could also easily be done by a non-JS client + that processes the ``Paivana`` HTTP header (or a GNU Taler wallet + running as a Web extension). + +* Based on this paivana ID, a + ``taler://pay-template/{merchant_backend}/{template_id}?session_id={paivana_id}&fulfillment_url={website}`` + URI is generated and rendered as a QR code and link, prompting + the user to pay for access to the ``{website}`` using GNU Taler. + +* The JavaScript in the paywall page running in the browser + (or the non-JS client) long-polls + on a new ``https://{merchant_backend}/sessions/{paivana_id}`` + endpoint that returns when an order with the given session ID has been paid for (regardless of the order ID, which is not known to the browser). -* A wallet now needs to instantiate the pay template and pay for the resulting order - by talking to the Paivana backend which proxies the requests to the merchant backend - and in the process learns the order ID and the payment status change. - paivana-httpd may also implement the required subset of the merchant backend itself in the future. +* A wallet now needs to instantiate the pay template, passing the + ``session_id`` and the ``website`` as an additional inputs + to the order creation (the session ID here will work just like + existing use of ``session_ids`` in session-bound payments). + Similarly, the ``website`` works as the fulfillment URL as usual. +* The wallet then must pay for the resulting order + by talking to the Merchant backend. * When the long-poller returns and the payment has succeeded, the - HTTP response sets the Paivana cookie. The browser reloads the page. + browser (still rendering the paywall page) also learns the order ID. +* The JavaScript of the paywall page (or the non-JS client + processing the ``Paivana`` HTTP header) then POSTs the order ID, + ``nonce``, ``cur_time`` + and ``website`` to ``{domain}/.well-known/pavivana``. +* paivana-httpd computes the paivana ID and checks if the given + order ID was indeed paid recently for the computed paivana ID. + If so, it generates an HTTP response which the Paivana cookie + and redirects to the fulfillment URL (which is the original {website}). +* The browser reloads the page with the correct + Paivana cookie (see first step). -The Paivana Cookie is computed as ``exp_timestamp || '-' || H(client_ip || paivana_server_secret || exp_timestamp)``. Problems: +--------- * A smart attacker might still create a lot of orders via the pay-template. @@ -63,10 +109,15 @@ Problems: * Solution B: Rate-limit template instantiation on a per-IP basis. Implementation: +--------------- -* Paivana needs to support extended template instantiation with a ``paivana_id``. -* Paivana component needs to be specified / implemented -* Wallet-core needs support for a ``paivana_id`` in pay templates. +* Merchant backend needs way to lookup order IDs under a ``session_id`` +* Merchant backend needs way to instantiate templates with + a given ``session_id`` and ``fulfillment_url``. This also + requires extending the allowed responses for templates in general. +* Paivana component needs to be implemented +* Wallet-core needs support for a ``session_id`` and + ``fulfillment_url`` in pay templates. Test Plan @@ -82,12 +133,19 @@ N/A Alternatives ============ +* Do not re-use the session ID mechanism but introduce some new concept. + This has the drawback of us needing additional tables and indicies, + and also the existing use of the session ID is very parallel to this one. + Drawbacks ========= -* Requires JavaScript +* This exposes an order ID to anyone who knows the session ID. This is + clearly not an issue in this context, and for the existing uses of + the session ID it also seems clear that knowledge of the session ID + requires an attacker to have access that would easily also already + give them any order ID, so this seems harmless. - * Could be made to work without JS by returning some ``Paivana: ...`` header. Discussion / Q&A ================