paivana-httpd-manual.rst (52618B)
1 .. 2 This file is part of GNU TALER. 3 4 Copyright (C) 2026 Taler Systems SA 5 6 TALER is free software; you can redistribute it and/or modify it under the 7 terms of the GNU Affero General Public License as published by the Free Software 8 Foundation; either version 3.0, or (at your option) any later version. 9 10 TALER is distributed in the hope that it will be useful, but WITHOUT ANY 11 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 12 A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. 13 14 You should have received a copy of the GNU Affero General Public License along with 15 TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/> 16 17 @author Christian Grothoff 18 19 .. _Paivana-httpd: 20 21 Paivana-httpd 22 ============= 23 24 This chapter documents the installation and operation of the Paivana 25 reverse proxy ``paivana-httpd``. The reverse proxy sits between the 26 public Internet and an upstream Web service, intercepting requests 27 that have not yet been paid for and presenting the client with a 28 GNU Taler paywall. Once a payment has been confirmed by the 29 configured GNU Taler merchant backend, ``paivana-httpd`` forwards 30 subsequent requests of that client to the upstream service. 31 32 The full list of command-line options is documented in 33 :manpage:`paivana-httpd(1)`; the configuration file is 34 documented in :manpage:`paivana.conf(5)`. 35 36 37 .. _Paivana-Motivation: 38 39 Motivation and use cases 40 ------------------------ 41 42 ``paivana-httpd`` answers a single question for every incoming 43 request — *"has the client paid for this?"* — but operators deploy it 44 for two rather different reasons. Which one applies to you mostly 45 changes how you *price* the templates (see :ref:`Paivana-Templates`), 46 not how you install or run the daemon. 47 48 Selling access: a paywall with revenue 49 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 50 51 The classic use case is to charge money for access to digital content 52 or services: a news article, a research dataset, an API call, a file 53 download. Because Paivana builds on GNU Taler, this comes with 54 properties that distinguish it from traditional paywalls: 55 56 - **No accounts, no sign-up.** A visitor pays directly from a Taler 57 wallet; there is no registration, no login, and no credit-card form. 58 This removes the friction that kills conversion on per-article 59 purchases. 60 - **Micropayments are practical.** Taler's costs per transaction are 61 low enough that charging fractions of a cent for a single page is 62 economically meaningful, enabling true pay-per-article or 63 pay-per-request business models. 64 - **Privacy for the buyer.** Taler is privacy-preserving by design: 65 the seller (and Paivana) learns that *an* order was paid, not *who* 66 paid it. No tracking profile is required to gate the content. 67 - **Subscriptions and discounts.** A template's ``choices`` can offer 68 a one-off payment *or* the purchase/redemption of a subscription 69 token, so returning subscribers pass the paywall automatically 70 (see :ref:`Paivana-Templates`). 71 - **Agentic payments.** Every paywall response also carries a 72 machine-readable ``Paivana:`` HTTP header (see 73 :ref:`Paivana-Customizing`). An automated client — for example an 74 AI agent provisioned with a Taler wallet — can therefore pay and 75 fetch the resource without rendering the HTML page, while the 76 operator caps financial exposure to whatever was loaded into that 77 agent's wallet. 78 79 Rate-limiting bots and abuse: DDoS and scraper protection 80 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 81 82 The second use case treats the payment not as a source of revenue but 83 as a *cost imposed on the client* — economic rate limiting. Here you 84 set the price very low (a fraction of a cent, effectively a "CAPTCHA 85 token"): negligible for a human visitor reading a handful of pages, 86 but ruinous for a botnet or scraper that wants to issue millions of 87 requests. An attacker sending a million requests must spend a million 88 micropayments; a legitimate user spends almost nothing. 89 90 This is a deliberate alternative to two older defenses whose 91 cost-asymmetry has eroded: 92 93 - **Proof-of-work puzzles** (as used by tools such as Anubis) ask the 94 client to burn CPU before being served. But CPU is cheap and 95 abundant for a well-resourced attacker or botnet, and the puzzle 96 burdens low-powered legitimate devices (phones, assistive tech) the 97 most. 98 - **CAPTCHAs** ask the client to solve a perception task. Modern 99 LLMs and automated solvers now clear text, image and even 100 "behavioral" CAPTCHAs cheaply and at scale, so they increasingly 101 inconvenience humans while barely slowing down the bots they were 102 meant to stop. 103 104 A Taler payment sidesteps both problems: it does not rely on a human 105 being slow, or on a machine being computationally weak. It relies on 106 the one resource a flooding attacker genuinely cannot conjure for free 107 — money. Bots *can* solve puzzles and CAPTCHAs; they cannot mint 108 funds. For this use case you typically combine a low per-request 109 price with a subscription-token ``choice`` so that a paying or 110 authenticated user buys a token once and then sails through, while 111 anonymous floods keep paying per request. 112 113 .. note:: 114 115 The two use cases are not mutually exclusive: the same deployment 116 can earn revenue on premium URLs and simultaneously throttle abuse 117 on cheap ones, simply by defining several templates with different 118 prices and ``website_regex`` scopes. 119 120 121 Architecture overview 122 --------------------- 123 124 ``paivana-httpd`` does not implement any payment logic of its own. 125 Instead, every Paivana deployment combines three components: 126 127 1. **The upstream web service.** This is the existing HTTP service 128 whose content should be sold (a static website, a cgit service, 129 a REST API, …). It does not need to be modified to 130 work with Paivana. 131 2. **A GNU Taler merchant backend** (``taler-merchant-httpd``). The 132 merchant backend manages templates, creates orders, talks to one 133 or more Taler exchanges, and ultimately reports back whether a 134 given order has been paid. See the 135 :ref:`Taler Merchant Backend Operator Manual 136 <taler-merchant-backend-operator-manual>` for full details. 137 3. **``paivana-httpd`` itself.** This is the reverse proxy that 138 gates the upstream service. It reads a single 139 :ref:`paivana.conf <Paivana-Configuration>` configuration file 140 that points at both the merchant backend and the upstream 141 service. 142 143 Typically a TLS-terminating reverse proxy (Nginx or Apache) is 144 deployed in front of ``paivana-httpd`` to handle HTTPS and to route 145 multiple virtual hosts; see :ref:`Paivana-ReverseProxy` below. 146 147 In normal operation the request flow is: 148 149 :: 150 151 client ──▶ Nginx/Apache (TLS) ──▶ paivana-httpd ──▶ upstream 152 │ 153 ▼ 154 taler-merchant-httpd 155 │ 156 ▼ 157 Taler exchange 158 159 160 Installation 161 ------------ 162 163 Installing from source 164 ^^^^^^^^^^^^^^^^^^^^^^ 165 166 The package sources can be found in our 167 `download directory <http://ftpmirror.gnu.org/taler/>`__. 168 169 GNU Taler components follow the ``MAJOR.MINOR.MICRO`` version 170 scheme. The general rule for compatibility is that ``MAJOR`` and 171 ``MINOR`` must match across components; exceptions are noted in the 172 release notes. For example, ``paivana-httpd`` 1.6.x is expected to 173 work with ``taler-merchant-httpd`` 1.6.x. A ``MAJOR`` version of 0 174 indicates experimental development; in that case you should always 175 run the *latest* releases of every component together. 176 177 The following packages must be installed before compiling 178 ``paivana-httpd``: 179 180 - GNUnet (``libgnunetutil``) matching the Taler release 181 - GNU Taler exchange libraries (``libtalerexchange``, 182 ``libtalerutil``) 183 - GNU Taler merchant client library (``libtalermerchant``) 184 - GNU Taler HTTP daemon helpers (``libtalermhd``, 185 ``libtalertemplating``) 186 - libmicrohttpd, libcurl, libjansson, libgcrypt, zlib 187 188 Build and install with: 189 190 .. code-block:: shell-session 191 192 $ ./bootstrap 193 $ ./configure --prefix=$PREFIX 194 $ make 195 $ sudo make install 196 197 198 Installing the binary packages on Debian 199 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 200 201 .. include:: frags/installing-debian.rst 202 203 To install ``paivana-httpd`` you can now simply run: 204 205 .. code-block:: shell-session 206 207 # apt install paivana-httpd 208 209 The package does not perform any deployment-specific configuration 210 work; it only sets up the ``paivana-httpd`` system user, the systemd 211 service and socket units, and installs example configuration 212 snippets for Nginx and Apache under ``/etc/nginx/sites-available/`` 213 and ``/etc/apache2/sites-available/``. You still must configure the 214 HTTP request routing and the Paivana templates as described below. 215 216 217 Installing the binary packages on Ubuntu 218 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 219 220 .. include:: frags/installing-ubuntu.rst 221 222 To install ``paivana-httpd``, run: 223 224 .. code-block:: shell-session 225 226 # apt install paivana-httpd 227 228 As on Debian, the package does not perform any deployment-specific 229 configuration work. 230 231 232 .. _Paivana-Configuration: 233 234 Configuring paivana-httpd 235 ------------------------- 236 237 The main configuration file is ``/etc/paivana/paivana.conf``. Its 238 syntax follows the standard GNUnet configuration file format and is 239 documented in full in :manpage:`paivana.conf(5)`. Files under 240 ``$PREFIX/share/paivana/config.d/`` are read first and overridden by 241 it, but Paivana installs none: every option is either required or has 242 the default stated with it in :manpage:`paivana.conf(5)`. 243 244 All Paivana-specific keys live in the ``[paivana]`` section. At a 245 minimum, the file must specify three things: 246 247 - where ``paivana-httpd`` should listen for incoming requests 248 (``SERVE``, ``UNIXPATH`` / ``PORT``); 249 - where it should forward paid requests to 250 (``DESTINATION_BASE_URL``); 251 - how it should reach the merchant backend 252 (``MERCHANT_BACKEND_URL`` and ``MERCHANT_ACCESS_TOKEN``). 253 254 A typical configuration that listens on a UNIX domain socket 255 managed by systemd and forwards to a local upstream server looks 256 like this: 257 258 .. code-block:: ini 259 260 [paivana] 261 # Listen on the socket provided by paivana-httpd.socket. 262 SERVE = unix 263 UNIXPATH = /run/paivana/httpd/paivana-http.sock 264 UNIXPATH_MODE = 660 265 266 # Public base URL of this paywall as seen by clients. 267 # Used when the Host/X-Forwarded-Host headers are unavailable. 268 BASE_URL = https://paywall.example.com/ 269 270 # Upstream service that gets proxied after payment. 271 DESTINATION_BASE_URL = http://127.0.0.1:8080/ 272 273 # Merchant backend used to create and verify orders. 274 MERCHANT_BACKEND_URL = http://localhost:9966/ 275 MERCHANT_ACCESS_TOKEN = secret-token:CHANGE-ME 276 277 # Stable key material used to MAC the access cookie. Must be 278 # generated from a random source, not invented; see below. If 279 # unset, a random value is generated at every startup, 280 # invalidating all previously issued cookies. 281 SECRET = REPLACE-ME-WITH-GENERATED-RANDOMNESS 282 283 # Resources that should never trigger the paywall, e.g. 284 # logos, stylesheets or favicons. 285 WHITELIST = ^/(favicon\.ico|assets/.*|robots\.txt)$ 286 287 The exhaustive list of supported keys (``SERVE``, ``PORT``, 288 ``BIND_TO``, ``UNIXPATH``, ``UNIXPATH_MODE``, ``CONNECTION_LIMIT``, 289 ``PER_IP_CONNECTION_LIMIT``, ``MAX_REQUEST_SIZE``, 290 ``REQUEST_BUFFER_MAX``, ``RESPONSE_BUFFER_MAX``, ``UPSTREAM_TIMEOUT``, 291 ``UPSTREAM_STALL_TIMEOUT``, ``BASE_URL``, 292 ``DESTINATION_BASE_URL``, ``DESTINATION_UNIXPATH``, 293 ``MERCHANT_BACKEND_URL``, ``MERCHANT_BACKEND_UNIX_PATH``, ``MERCHANT_ACCESS_TOKEN``, 294 ``SECRET``, ``WHITELIST``, ``TRUSTED_PROXIES``, 295 ``TRUSTED_PROXIES6``) is documented in :manpage:`paivana.conf(5)`. 296 297 If you reach the merchant backend over a UNIX domain socket on the 298 same host (recommended for a single-machine deployment), replace 299 the ``MERCHANT_BACKEND_URL`` block with: 300 301 .. code-block:: ini 302 303 MERCHANT_BACKEND_URL = http://localhost/ 304 MERCHANT_BACKEND_UNIX_PATH = /run/taler-merchant/merchant.sock 305 306 .. note:: 307 308 ``MERCHANT_ACCESS_TOKEN`` and ``SECRET`` are sensitive values. 309 Make sure ``paivana.conf`` is only readable by the 310 ``paivana-httpd`` user. The Debian package installs the file 311 accordingly. 312 313 When ``paivana-httpd`` runs behind a trusted reverse proxy 314 (Nginx/Apache), pass ``-f`` / ``--respect-forwarded-headers`` in the 315 systemd unit's ``ExecStart=`` so the real client address is taken from 316 the ``Forwarded`` and ``X-Forwarded-For`` headers. ``-f`` is a 317 statement that the server in front writes those headers itself, so 318 configure that server accordingly before you pass it; set 319 ``TRUSTED_PROXIES`` / ``TRUSTED_PROXIES6`` as well if there is more 320 than one proxy in the path. :ref:`Paivana-ReverseProxy` has the 321 details and working configurations. See :manpage:`paivana-httpd(1)` for the 322 remaining command-line flags (in particular ``-g`` to require only 323 a single payment per site and ``-n`` to disable the paywall for 324 debugging). 325 326 327 Body sizes, buffering and upstream timeouts 328 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 329 330 ``paivana-httpd`` relays bodies as they arrive rather than assembling 331 them first. A request or response of any size therefore passes 332 through, and the client starts receiving the response while the 333 upstream is still producing it — neither of which was true when each 334 body had to fit in memory, where anything over 40 MiB failed outright 335 and nothing reached the client until everything had. 336 337 Two consequences are worth knowing before tuning anything. 338 339 **Memory is bounded by the buffers, not by the content.** An 340 in-flight request can hold at most ``REQUEST_BUFFER_MAX`` plus 341 ``RESPONSE_BUFFER_MAX`` (256 KiB each by default), so the ceiling for 342 the process is that sum times ``CONNECTION_LIMIT`` — about 256 MiB at 343 the shipped defaults. That is a ceiling and not a typical cost: the 344 buffers are allocated on demand and grown only as far as a request 345 actually needs, so a page that arrives in one read never occupies more 346 than that read. Raise the buffers only to trade memory for throughput 347 on fast links, and lower ``CONNECTION_LIMIT`` if you raise them a 348 lot. When the client cannot keep up, Paivana stops reading 349 from the upstream; when the upstream cannot keep up, Paivana stops 350 reading from the client. Neither ever accumulates. 351 352 **A response can now fail after its status has been sent.** Once the 353 upstream's headers have gone out they cannot be retracted, so an 354 upstream that dies mid-body can no longer be reported as ``502``. 355 Paivana signals it by breaking the framing instead: a declared 356 ``Content-Length`` is left unmet, or a chunked response is closed 357 without its terminating chunk. Both are failures a conforming client 358 must detect (RFC 9112, section 8.1.2) — ``curl`` reports exit code 359 18, for instance — and the event is logged at ``WARNING`` naming the 360 URL and the byte count reached. 361 362 .. note:: 363 364 There is one case where it cannot be signalled at all: an HTTP/1.0 365 client receiving a response whose length the upstream never 366 declared. There the close of the connection *is* the framing, so a 367 truncated body is indistinguishable from a complete one. Nothing 368 at this layer can fix that; it is a property of HTTP/1.0. It does 369 not arise for HTTP/1.1 clients, which get chunked framing, nor for 370 any client when the upstream declares a length. 371 372 The two upstream timeouts bound different things and neither bounds 373 the request as a whole: 374 375 - ``UPSTREAM_TIMEOUT`` (60 s) is the time to the response *headers*. 376 Expiry here yields ``504``, and this is the only one that can still 377 produce a status code. 378 - ``UPSTREAM_STALL_TIMEOUT`` (60 s) is how long the upstream may move 379 no bytes in either direction. Expiry here truncates the response as 380 described above, if it had started, and yields ``504`` if it had 381 not. The clock does not run while Paivana is holding the upstream 382 back because the client has not caught up, so a slow client is never 383 mistaken for a stalled upstream. 384 385 Raise ``UPSTREAM_STALL_TIMEOUT`` if your upstream legitimately pauses 386 for long stretches mid-response — a report generator that computes for 387 minutes between writes, say. Do not raise it to accommodate slow 388 *clients*; that is already handled. 389 390 391 Starting and stopping the service 392 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 393 394 The Debian/Ubuntu package ships a socket-activated systemd unit. 395 After editing ``/etc/paivana/paivana.conf`` enable and start it: 396 397 .. code-block:: shell-session 398 399 # systemctl enable --now paivana-httpd.socket 400 # systemctl status paivana-httpd 401 402 The socket listens on ``/run/paivana/httpd/paivana-http.sock`` with 403 group ``www-data``, which lets a co-located Nginx or Apache talk to 404 the daemon without granting it broader filesystem access. Logs are 405 sent to the journal: 406 407 .. code-block:: shell-session 408 409 # journalctl -u paivana-httpd -f 410 411 412 .. _Paivana-Whitelisting: 413 414 Whitelisting unpaid resources 415 ----------------------------- 416 417 Most sites contain resources that must remain freely accessible even 418 to clients that have not paid: the stylesheets, fonts, logos and 419 scripts referenced by the paywall page itself, a ``favicon.ico``, a 420 ``robots.txt``, or a health-check endpoint polled by a load balancer. 421 If these were paywalled, the paywall page could not even render. 422 423 The ``WHITELIST`` configuration key holds a single POSIX *extended* 424 regular expression that is matched against the request path (which 425 always begins with ``/``). When it matches, the request bypasses both 426 the paywall and the access-cookie check and is proxied straight to the 427 upstream: 428 429 .. code-block:: ini 430 431 [paivana] 432 WHITELIST = ^/(favicon\.ico|robots\.txt|assets/.*|.*\.css|.*\.js)$ 433 434 A few details worth knowing: 435 436 - The expression is matched against the URL path only, not against the 437 host or the request method. 438 - The expression must match the path in its **entirety**. 439 ``paivana-httpd`` anchors it for you — it compiles ``^(`` *your 440 expression* ``)$``, so writing your own ``^`` and ``$`` is harmless 441 but redundant. What this rules out is a substring match: 442 ``/assets/`` on its own whitelists nothing, because no path is 443 exactly that; ``/assets/.*`` is what whitelists that subtree. The 444 enclosing group matters for alternations: ``a|b`` behaves as 445 "exactly ``a`` or exactly ``b``", not as "starts with ``a`` or ends 446 with ``b``". 447 - Anchoring is a safety property, not a convenience. An unanchored 448 whitelist grants free access to every path merely *containing* the 449 expression, which a client can arrange deliberately: with a 450 substring rule, a request for ``/premium/article?x=/robots.txt`` 451 would sail past the paywall. 452 - Paths longer than 16384 bytes are rejected outright with HTTP ``414 453 URI Too Long``, before any expression is evaluated. The cap bounds the 454 cost of a pathological expression on a path an unpaying client 455 controls; no legitimate URL comes close to it. 456 - An invalid regular expression is a fatal configuration error: 457 ``paivana-httpd`` logs the problem and refuses to start. 458 - The internal endpoints ``POST /.well-known/paivana`` (payment 459 callback) and ``GET /.well-known/paivana/templates/...`` (paywall 460 pages) are always handled by ``paivana-httpd`` itself and are not 461 affected by the whitelist. 462 - Running with ``-n`` / ``--no-payment`` whitelists *everything*; the 463 daemon then behaves as a transparent reverse proxy and never 464 consults the merchant backend. 465 466 .. note:: 467 468 Whitelisting is coarse — it is keyed purely on the URL. Per-URL 469 *pricing* (charging different amounts for different paths) is done 470 with merchant templates and their ``website_regex``, described in 471 :ref:`Paivana-Templates`, not with ``WHITELIST``. 472 473 474 .. _Paivana-AccessControl: 475 476 Access control: per-page vs. site-wide payment 477 ---------------------------------------------- 478 479 Once a client has paid, ``paivana-httpd`` issues a cryptographically 480 protected access cookie (``Paivana-Cookie``) instead of asking the 481 merchant backend again on every subsequent request. The cookie is a keyed hash 482 over *(expiration time, website, client address)*; it carries no 483 server-side state, so any of the (possibly several) ``paivana-httpd`` 484 processes can validate it. 485 486 Two aspects of this are operator-visible: 487 488 Scope of a payment 489 ^^^^^^^^^^^^^^^^^^^ 490 491 By default a payment grants access to **the specific URL that was 492 paid for**: the cookie's ``Path`` is set to that path and the website 493 is bound into the hash. This is what you want when each page is sold 494 individually. 495 496 Passing ``-g`` / ``--global-payment`` changes this so that a single 497 payment unlocks the **entire site**: the website component is dropped 498 from the hash and the cookie is issued with ``Path=/``. Use this when 499 one purchase (or one subscription) should cover everything behind the 500 proxy, including for the abuse-mitigation use case where you simply 501 want a paying client to stop hitting the paywall. 502 503 Binding to the client address 504 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 505 506 The cookie is tied to the client's network address so that it cannot 507 simply be copied to another machine. When ``paivana-httpd`` runs 508 behind a TLS-terminating reverse proxy, the socket peer is always that 509 proxy, so **every** client would appear to share one address. Run the 510 daemon with ``-f`` / ``--respect-forwarded-headers`` in that setup so 511 the real client address is taken from the ``Forwarded`` and 512 ``X-Forwarded-For`` headers instead. Only do this behind a proxy that 513 writes *both* of those headers itself; a header it merely passes 514 through is the client's own, and then the client chooses the address 515 its cookie is bound to. :ref:`Paivana-ReverseProxy` has the 516 configurations that get this right. 517 518 Cookie lifetime is derived from the order's ``max_pickup_delay``; 519 configure it on the template (``max_pickup_duration``) to control how 520 long a paid client may keep accessing the resource before paying 521 again. Because the cookie is keyed by ``SECRET``, leaving ``SECRET`` 522 unset means a new random key is chosen at every startup and all 523 outstanding cookies are invalidated on restart. 524 525 ``SECRET`` is hashed once to derive that key — no salt, no stretching 526 — so its own entropy is the only thing protecting it. An attacker who 527 has seen a single ``Paivana-Cookie`` can test candidate secrets 528 offline as fast as it can compute SHA-512, which on ordinary hardware 529 is billions of guesses per second. Give ``SECRET`` **at least 128 530 bits of entropy from a random source**; do not invent a passphrase, 531 and do not ship the placeholder from the sample configuration above. 532 Generate one with: 533 534 .. code-block:: shell-session 535 536 # head -c 32 /dev/urandom | base64 537 538 Whoever recovers a weak ``SECRET`` can mint access cookies for any URL 539 and any client address, and you will not see it happen: the forged 540 cookies never touch the merchant backend, so nothing distinguishes 541 them in your logs from paid traffic. 542 543 544 .. _Paivana-Redemption: 545 546 Redeeming a payment 547 ------------------- 548 549 The cookie above is handed out at the end of one round-trip that is 550 worth understanding, because it is the only point at which 551 ``paivana-httpd`` talks to the merchant backend on behalf of a client, 552 and because its failure modes are what you will see in the logs. 553 554 When the paywall page has been told by the merchant backend that its 555 order was paid, it POSTs the order ID (together with the ``nonce``, 556 ``expiration`` and ``website`` it used to construct the payment) to 557 ``/.well-known/paivana``. ``paivana-httpd`` re-derives the session ID 558 from those values and asks its backend whether that order was really 559 paid under it, over its own authenticated connection. Nothing the 560 client says is taken on trust: the order must be paid and not refunded, 561 its fulfillment URL must be the ``website`` being claimed (or, where the 562 order has none, that ``website`` must lie under the configured 563 ``BASE_URL``), and the requested expiration must not exceed the order's 564 ``max_pickup_time``. Only then is the cookie issued. 565 566 This query is a **long poll bounded at five seconds**, and both parts 567 are deliberate. A client can be a moment ahead of the backend — it may 568 even be talking to a different backend process than ``paivana-httpd`` 569 is — so waiting briefly admits an honest client that would otherwise be 570 turned away just after paying. Conversely, the endpoint is 571 unauthenticated and can be posted to before any payment exists, so that 572 wait is also the time an attacker can pin a connection for, which is 573 why it is short rather than generous. The same bound applies to the 574 outgoing request itself, so a merchant backend that stops responding 575 cannot hold connections open either. 576 577 The status returned to the client tells you which of these happened: 578 579 ======== ============================================================== 580 Status Meaning 581 ======== ============================================================== 582 303 Payment confirmed; the ``Paivana-Cookie`` is set and the 583 client is redirected to the content. 584 409 The backend says the order was not paid, or it was refunded, 585 or its contract does not cover the claimed website. This is 586 the client's problem, not yours — a stale replay, or a page 587 that gave up too early. 588 410 The client asked for access past the order's 589 ``max_pickup_time``. Raise ``max_pickup_duration`` on the 590 template if legitimate clients hit this. 591 404 The backend does not know this order at all. 592 504 The backend did not answer within the bound, or could not be 593 reached. Check that ``MERCHANT_BACKEND_URL`` is reachable 594 and that the backend is not itself stalled; the log line 595 naming the backend and order ID is emitted at ``WARNING``. 596 502 The backend answered, but with something ``paivana-httpd`` 597 could not use. This normally means a version mismatch 598 between the two and is worth reporting. 599 500 The backend rejected our credentials. Check 600 ``MERCHANT_ACCESS_TOKEN``; no client can fix this. 601 ======== ============================================================== 602 603 A burst of 504s or 500s is therefore an infrastructure problem and a 604 burst of 409s is not, which is the distinction to key any alerting on. 605 606 607 .. _Paivana-Templates: 608 609 Configuring Paivana templates 610 ----------------------------- 611 612 ``paivana-httpd`` does not store any per-site pricing or URL-matching rules 613 itself. Instead, all rules are expressed as :ref:`merchant templates 614 <template>` of type ``paivana`` in the merchant backend. When 615 ``paivana-httpd`` starts up it asks the merchant backend for every template 616 configured for the instance identified by ``MERCHANT_BACKEND_URL`` and uses 617 the ``website_regex`` field of each template to decide which template (and 618 therefore which payment options) applies to an incoming request URL. 619 620 The corresponding REST API is documented in detail in the 621 :ref:`Merchant Backend HTTP API <merchant-api>`; see in particular 622 the 623 `POST /private/templates 624 <https://docs.taler.net/core/api-merchant.html#post--private-templates>`__ 625 endpoint and the 626 :ts:type:`TemplateContractPaivana` definition. 627 628 Prerequisites 629 ^^^^^^^^^^^^^ 630 631 Before creating a template you need: 632 633 - a running ``taler-merchant-httpd`` (see the 634 :ref:`Launching-the-backend` section of the merchant manual); 635 - a merchant :ref:`instance <Instance-setup>` with at least one 636 configured :ref:`bank account <instance-bank-account>`; 637 - the access token of that instance (used as 638 ``MERCHANT_ACCESS_TOKEN`` in ``paivana.conf``). 639 640 In the examples below we assume the merchant backend is reachable 641 at ``http://localhost:9966/``, the default instance is ``default``, 642 its access token is ``secret-token:sandbox`` and the currency is 643 ``KUDOS``. Adjust the URLs, tokens and amounts to match your 644 deployment. The 645 `src/backend/test.sh 646 <https://git.taler.net/paivana.git/tree/src/backend/test.sh>`__ 647 script that ships with Paivana sets up exactly this minimal 648 configuration and is a good starting point for experimentation. 649 650 Creating a single global template 651 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 652 653 The simplest Paivana setup uses one template that matches every 654 URL on the site and charges a fixed price. This is the 655 configuration created by ``src/backend/test.sh``: 656 657 .. code-block:: bash 658 659 $ curl -X POST http://localhost:9966/private/templates \ 660 -H 'Authorization: Bearer secret-token:sandbox' \ 661 -H 'Content-Type: application/json' \ 662 -d '{ 663 "template_id": "paivana", 664 "template_description": "A Paivana template", 665 "template_contract": { 666 "template_type": "paivana", 667 "summary": "Access to example.com", 668 "website_regex": ".*", 669 "choices": [ { "amount": "KUDOS:1" } ] 670 } 671 }' 672 673 The ``template_type`` must be ``"paivana"``: this allows 674 ``paivana-httpd`` to pick the template up at startup and 675 also enables some required logic in the merchant backend. The 676 ``website_regex`` is a POSIX extended regular expression that is 677 matched against the request URL; ``.*`` covers everything. Each 678 entry in ``choices`` describes one way the client may pay and is an 679 :ts:type:`OrderChoice` object (so the paywall can also support 680 the use of subscription tokens, discount coupons, etc.). The 681 contract as a whole is the :ts:type:`TemplateContractPaivana` object 682 of the :ref:`merchant backend API <merchant-api>`, which stores 683 ``website_regex`` but leaves its interpretation to ``paivana-httpd`` 684 — that is, to the rules below. 685 686 Two properties of the matching are easy to get wrong, and both make 687 the difference between a template that applies and one that silently 688 never does: 689 690 - The subject is the **absolute URL**, not the path. It is the 691 configured ``BASE_URL`` (or the scheme and ``Host`` of the request, 692 when ``BASE_URL`` is not set) followed by the path — for example 693 ``https://example.com/premium/article``. An expression written 694 against the path alone, such as ``/premium/.*``, therefore matches 695 nothing. 696 - The expression must match that URL in its **entirety**. 697 ``paivana-httpd`` anchors it for you, compiling ``^(`` *your 698 expression* ``)$``, so a substring rule does not apply to the URLs 699 containing it. Writing your own ``^`` and ``$`` remains harmless. 700 The enclosing group means an alternation such as ``a|b`` is read as 701 "exactly ``a`` or exactly ``b``". 702 703 So a template meant for one subtree is written either against the 704 whole URL, ``https://example\.com/premium/.*``, or with a leading 705 wildcard, ``.*/premium/.*``, if the same template should apply 706 regardless of the host the site is served under. 707 708 A URL longer than 16384 bytes is answered with HTTP ``414 URI Too Long`` 709 and no template is evaluated for it. The cap bounds the cost of a 710 pathological expression on a URL an unpaying client controls. 711 712 A successful create returns HTTP ``204 No Content``. After 713 creating the template, (re)start ``paivana-httpd`` so that it 714 re-reads the template list: 715 716 .. code-block:: shell-session 717 718 # systemctl restart paivana-httpd 719 720 Multiple templates with URL-specific pricing 721 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 722 723 When a single site contains content with different prices, define one template 724 per price bucket and use ``website_regex`` to scope each template to the 725 matching URLs. When several templates match the same URL ``paivana-httpd`` 726 picks the first one if finds that matches. Be careful: if multiple templates 727 match a URL, the result is non-deterministic! 728 729 For example, a news site might charge 2 KUDOS for premium articles 730 and 50 cents (``KUDOS:0.5``) for standard articles: 731 732 .. code-block:: bash 733 734 $ curl -X POST http://localhost:9966/private/templates \ 735 -H 'Authorization: Bearer secret-token:sandbox' \ 736 -H 'Content-Type: application/json' \ 737 -d '{ 738 "template_id": "premium", 739 "template_description": "Premium long-form articles", 740 "template_contract": { 741 "template_type": "paivana", 742 "summary": "Premium article on example.com", 743 "website_regex": ".*/premium/.*", 744 "choices": [ { "amount": "KUDOS:2" } ] 745 } 746 }' 747 748 $ curl -X POST http://localhost:9966/private/templates \ 749 -H 'Authorization: Bearer secret-token:sandbox' \ 750 -H 'Content-Type: application/json' \ 751 -d '{ 752 "template_id": "default", 753 "template_description": "Standard articles", 754 "template_contract": { 755 "template_type": "paivana", 756 "summary": "Standard article on example.com", 757 "website_regex": ".*/standard/.*", 758 "choices": [ { "amount": "KUDOS:0.5" } ] 759 } 760 }' 761 762 Offering multiple payment options 763 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 764 765 The ``choices`` array lets a single template offer several mutually exclusive 766 ways to pay. A common pattern is to accept either a cash payment or to sell a 767 subscription; the wallet shows both options and the customer picks one. The 768 third option, where the customer already has a subscription, will be used 769 automatically by the wallet for subscribers and the customer will not even 770 have to click to bypass the paywall as a subscriber. See the merchant manual 771 for the details of :ref:`OrderChoice <template-choice>` objects. 772 773 .. code-block:: bash 774 775 $ curl -X POST http://localhost:9966/private/templates \ 776 -H 'Authorization: Bearer secret-token:sandbox' \ 777 -H 'Content-Type: application/json' \ 778 -d '{ 779 "template_id": "article", 780 "template_description": "Single article, paid or via subscription", 781 "template_contract": { 782 "template_type": "paivana", 783 "summary": "Article on example.com", 784 "website_regex": ".*", 785 "choices": [ 786 { "amount": "KUDOS:1", 787 "description": "Pay per article" }, 788 { "amount": "KUDOS:100", 789 "description": "Buy subscription", 790 "outputs": [ { "token": "monthly-subscription" } ] }, 791 { "amount": "KUDOS:0", 792 "description": "Use my subscription", 793 "inputs": [ { "token": "monthly-subscription" } ], 794 "outputs": [ { "token": "monthly-subscription" } ] } 795 ] 796 } 797 }' 798 799 Managing templates 800 ^^^^^^^^^^^^^^^^^^ 801 802 Templates can be listed, updated and deleted through the merchant 803 backend's REST API or through the merchant backend SPA at 804 ``$MERCHANT_BACKEND_URL/``. See the merchant manual section on 805 :ref:`templates <template>` for details, and the API reference for 806 the relevant endpoints: 807 808 - `GET /private/templates 809 <https://docs.taler.net/core/api-merchant.html#get--private-templates>`__ — 810 list all templates of the instance; 811 - `PATCH /private/templates/$TEMPLATE_ID 812 <https://docs.taler.net/core/api-merchant.html#patch--private-templates-$TEMPLATE_ID>`__ — 813 update a template; 814 - `DELETE /private/templates/$TEMPLATE_ID 815 <https://docs.taler.net/core/api-merchant.html#delete--private-templates-$TEMPLATE_ID>`__ — 816 remove a template. 817 818 After any change, restart ``paivana-httpd`` so the new template 819 list takes effect. 820 821 822 .. _Paivana-Customizing: 823 824 Customizing the paywall page 825 ---------------------------- 826 827 The merchant templates of the previous section decide *which* URLs 828 cost *how much*. This section is about the *look* of the page an 829 unpaid visitor sees — the HTML the daemon returns with the 830 ``402 Payment Required`` status. 831 832 How the page is rendered 833 ^^^^^^^^^^^^^^^^^^^^^^^^^^ 834 835 The paywall body is produced from a `Mustache 836 <https://mustache.github.io/>`__ template named ``paywall`` that 837 ``paivana-httpd`` loads, via ``libtalertemplating``, from 838 839 :: 840 841 $PREFIX/share/paivana/templates/ 842 843 Templates are language-specific: the file is named 844 ``paywall.$LANG.must`` and the daemon picks a variant based on the 845 client's ``Accept-Language`` header. The package ships an English 846 default, ``paywall.en.must``. To offer the paywall in additional 847 languages, drop further ``paywall.<lang>.must`` files into the same 848 directory. Rendered responses are cached per language and per 849 content-encoding (and carry ``Cache-Control: public`` and a matching 850 ``Vary`` header), which is what lets the paywall be served as a 851 scalable, mostly-static page even under load. 852 853 When rendering, ``paivana-httpd`` passes the following values into the 854 template: 855 856 ======================== ==================================================== 857 Variable Meaning 858 ======================== ==================================================== 859 ``merchant_backend`` Base URL of the merchant backend (the page 860 long-polls it for payment completion). 861 ``template_id`` ID of the matched merchant template. 862 ``summary`` Human-readable summary from the template, 863 if any. 864 ``choices`` Array of payment options (:ts:type:`OrderChoice`). 865 ``has_choices`` True if more than one payment option exists. 866 ``default_choice`` The first entry of ``choices``. 867 ``max_pickup_delay`` Seconds the order may be picked up within. 868 ======================== ==================================================== 869 870 The shipped page uses these to draw a ``taler://`` QR code and a 871 "Pay now" link, long-poll the backend, and reload the original URL 872 once the wallet confirms. The non-visual integration point — useful 873 for automated and agentic clients — is the ``Paivana:`` HTTP response 874 header, which carries the ``taler://pay-template/...`` URI directly, so 875 a client need not parse the HTML to find out how to pay. 876 877 Editing the appearance 878 ^^^^^^^^^^^^^^^^^^^^^^^^ 879 880 For small tweaks — colours, wording, your logo, a link back to your 881 site — you can edit the installed ``paywall.en.must`` directly; it is 882 ordinary HTML/CSS with Mustache ``{{ ... }}`` placeholders for the 883 variables above. Keep the ``<script>`` block intact: it is what 884 performs the long-poll and the post-payment reload. 885 886 The shipped ``paywall.en.must`` is in fact *generated* at build time 887 from a Jinja2 source, ``src/frontend/paywall.en.must.j2``, which inlines 888 the client-side logic from ``src/frontend/paywall.js`` and a bundled QR 889 library. (To avoid clashing with Mustache's ``{{ }}``, the Jinja2 890 source uses ``@@ ... @@`` and ``@< ... >@`` delimiters.) If you want to 891 change the JavaScript behaviour rather than just the styling, edit the 892 ``.j2`` source and rebuild — the Meson ``custom_target`` regenerates the 893 ``.must`` file and installs it into the templates directory shown 894 above. 895 896 897 .. _Paivana-ReverseProxy: 898 899 Reverse proxy configuration 900 --------------------------- 901 902 ``paivana-httpd`` itself speaks plain HTTP on a UNIX socket (or a 903 local TCP port). In production it is often run behind an Internet-facing 904 reverse proxy that terminates TLS and forwards requests to the 905 Paivana socket. This section gives minimal working examples for 906 both Nginx and Apache. The same approach is used for the merchant 907 backend; see the merchant manual's 908 :ref:`reverse-proxy-configuration` section for additional 909 discussion. 910 911 The examples assume the public domain is ``example.com``, 912 that ``paivana-httpd`` is socket-activated by the shipped 913 ``paivana-httpd.socket`` unit (so its listening socket lives at 914 ``/run/paivana/httpd/paivana-http.sock``) and that TLS termination 915 happens at the reverse proxy. 916 917 In this setup ``paivana-httpd`` has to run with ``-f`` / 918 ``--respect-forwarded-headers``: a UNIX-domain peer has no address of 919 its own, so without ``-f`` there is no client address at all, the 920 access cookie cannot be bound to one, and 921 ``POST /.well-known/paivana`` fails. What ``-f`` means, though, is 922 that the client address becomes whatever the forwarding headers say it 923 is — and that address is what the cookie is bound to and what every 924 address-based decision uses. So it is worth being precise about which 925 part of those headers is believed. 926 927 ``paivana-httpd`` reads the chain from the **right**. The rightmost 928 element was written by the peer it accepted the connection from, which 929 ``-f`` asserts is a proxy of yours; every step further leftwards is 930 hearsay written by the node to its right, and is permitted only if 931 that node is listed in ``TRUSTED_PROXIES`` / ``TRUSTED_PROXIES6``. 932 With no such list configured the walk stops on its first step: trust 933 extends exactly one hop, and the element the front server itself wrote 934 is the client. Anything a client prepends to the header sits to the 935 left of that and can never be promoted. 936 937 The failure mode is therefore not a client that prepends, but a header 938 the front server never writes at all — because then the rightmost 939 element is the client's own, and it is believed. Both Nginx and 940 Apache forward a request header they were not told to touch, and 941 neither generates an RFC 7239 ``Forwarded`` header on its own. 942 ``paivana-httpd`` *prefers* ``Forwarded`` over ``X-Forwarded-For`` 943 where both are present, so a front server that carefully sets 944 ``X-Forwarded-For`` and ignores ``Forwarded`` is still handing every 945 client its choice of address: the client sends one ``Forwarded`` 946 element, that element is the whole chain, it is the rightmost, and it 947 wins over everything the proxy set. A paid cookie is then worth 948 nothing, since anyone can present the address it was issued to. 949 950 Deal with **both** headers, on every request. Whether you overwrite 951 or append matters less than that you write: appending — Nginx's 952 ``$proxy_add_x_forwarded_for``, Apache's ``ProxyAddHeaders`` — also 953 puts the real peer rightmost and is what you want when your own proxy 954 sits behind another one. At the outermost hop there is nothing to 955 preserve, so the configurations below simply set. 956 957 They are the ones shipped with the Debian package, with TLS 958 termination added; their comments explain each choice. 959 960 .. tab-set:: 961 962 .. tab-item:: Nginx 963 964 The package installs this at 965 ``/etc/nginx/sites-available/paivana``. Adapt ``server_name`` 966 and the certificate paths, then enable it via ``ln -s 967 ../sites-available/paivana /etc/nginx/sites-enabled/`` and 968 reload Nginx (``systemctl reload nginx``). 969 970 .. code-block:: nginx 971 972 # RFC 7239 node identifier for the peer we accepted from. nginx has 973 # no built-in variable for this: an IPv6 address has to be bracketed 974 # and therefore quoted (RFC 7239 §6), and a peer with no address is 975 # "unknown" (§6.3). 976 map $remote_addr $paivana_forwarded_elem { 977 ~^[0-9.]+$ "for=$remote_addr"; 978 ~^[0-9A-Fa-f:.]+$ "for=\"[$remote_addr]\""; 979 default "for=unknown"; 980 } 981 982 server { 983 listen 443 ssl; 984 listen [::]:443 ssl; 985 server_name example.com; 986 987 ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; 988 ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; 989 990 location / { 991 proxy_pass http://unix:/run/paivana/httpd/paivana-http.sock; 992 proxy_redirect off; 993 proxy_set_header Host $host; 994 995 # paivana-httpd is started with -f (see paivana-httpd.service), so 996 # it takes the client address for the access cookie from the 997 # headers set here. What makes that sound is that we set every 998 # one of them: paivana-httpd reads the chain from the right, so 999 # the element that counts is the last one written -- ours. A 1000 # header we do not set is forwarded as the client sent it, and 1001 # then the element that counts is the client's own. 1002 # 1003 # $remote_addr rather than $proxy_add_x_forwarded_for because 1004 # this is the outermost hop: the peer we accepted from IS the 1005 # client, and there is no earlier chain worth preserving. If 1006 # this nginx is itself behind another proxy, switch to 1007 # $proxy_add_x_forwarded_for -- appending is equally sound, the 1008 # peer still ends up rightmost -- and list that hop in 1009 # TRUSTED_PROXIES so paivana-httpd may step over it. 1010 proxy_set_header X-Forwarded-For $remote_addr; 1011 proxy_set_header X-Forwarded-Proto $scheme; 1012 proxy_set_header X-Forwarded-Host $host; 1013 proxy_set_header X-Forwarded-Port $server_port; 1014 1015 # RFC 7239. paivana-httpd PREFERS this over the X-Forwarded-* 1016 # headers above, which are kept for origins that only speak those. 1017 # Setting it is not optional: nginx neither generates a Forwarded 1018 # header nor strips the client's, so without this line a client 1019 # can send one element of its own, have it be the whole chain, 1020 # and outrank everything set above. Behind another proxy, 1021 # replace this with the appending form from nginx.org's "Using 1022 # the Forwarded header", which validates $http_forwarded before 1023 # extending it. 1024 proxy_set_header Forwarded "$paivana_forwarded_elem;proto=$scheme;host=$host"; 1025 } 1026 } 1027 1028 server { 1029 listen 80; 1030 listen [::]:80; 1031 server_name example.com; 1032 return 301 https://$host$request_uri; 1033 } 1034 1035 .. tab-item:: Apache 1036 1037 Enable the required modules once: 1038 1039 .. code-block:: shell-session 1040 1041 # a2enmod proxy proxy_http headers ssl 1042 # systemctl reload apache2 1043 1044 The package installs the ``<Location>`` block below at 1045 ``/etc/apache2/sites-available/paivana.conf``; it is shown here 1046 inside a TLS-terminating virtual host. Enable it with 1047 ``a2ensite paivana`` and reload Apache. 1048 1049 .. code-block:: apacheconf 1050 1051 <VirtualHost *:80> 1052 ServerName example.com 1053 Redirect permanent / https://example.com/ 1054 </VirtualHost> 1055 1056 <VirtualHost *:443> 1057 ServerName example.com 1058 1059 SSLEngine on 1060 SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem 1061 SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem 1062 1063 <Location "/"> 1064 # paivana-httpd is started with -f (see paivana-httpd.service), so it 1065 # takes the client address for the access cookie from the forwarding 1066 # headers, reading the chain from the right. mod_proxy's 1067 # ProxyAddHeaders (on by default) appends the real client to 1068 # X-Forwarded-For, which puts it rightmost -- but mod_proxy does not 1069 # touch Forwarded, and paivana-httpd prefers Forwarded. A 1070 # client-supplied one would arrive whole and be believed. Drop the 1071 # client's copies of all of them first, so that only what Apache and 1072 # the line below produce reaches paivana-httpd. 1073 # 1074 # If this Apache is itself behind another proxy, remove these and 1075 # configure mod_remoteip (RemoteIPHeader / RemoteIPInternalProxy) for 1076 # that hop instead, listing it in TRUSTED_PROXIES. 1077 RequestHeader unset X-Forwarded-For 1078 RequestHeader unset X-Forwarded-Proto 1079 RequestHeader unset X-Forwarded-Host 1080 RequestHeader unset X-Forwarded-Port 1081 RequestHeader unset Forwarded 1082 1083 # RFC 7239, which paivana-httpd prefers over the X-Forwarded-* headers 1084 # mod_proxy adds. Apache emits no Forwarded of its own, so build the 1085 # element here. It must be the "expr=" form: mod_headers' %{NAME}e 1086 # reads subprocess_env, which is still empty when RequestHeader runs, 1087 # so the %{REMOTE_ADDR}e spelling silently yields "for=(null)". That 1088 # is not a weaker paywall but a dead one -- an element naming no 1089 # address sends paivana-httpd back to the socket peer, which over a 1090 # UNIX socket is no address at all, and no cookie can be issued. 1091 # Note that %{REMOTE_ADDR} yields an unbracketed IPv6 address where 1092 # RFC 7239 §6 asks for for="[...]"; paivana-httpd accepts both. 1093 RequestHeader set Forwarded "expr=for=%{REMOTE_ADDR};proto=%{REQUEST_SCHEME};host=%{HTTP_HOST}" 1094 1095 ProxyPass "unix:/run/paivana/httpd/paivana-http.sock|http://example.com/" 1096 ProxyPassReverse "unix:/run/paivana/httpd/paivana-http.sock|http://example.com/" 1097 </Location> 1098 </VirtualHost> 1099 1100 There is deliberately no ``RequestHeader set X-Forwarded-Proto`` 1101 here. ``mod_proxy`` does not add that header itself, and the 1102 scheme ``paivana-httpd`` uses comes from the ``proto=`` 1103 parameter of the ``Forwarded`` element built above, which it 1104 prefers anyway. 1105 1106 Neither configuration needs ``TRUSTED_PROXIES`` to be safe — with one 1107 proxy in front, the one hop of trust that ``-f`` grants is exactly the 1108 hop there is. ``TRUSTED_PROXIES`` and ``TRUSTED_PROXIES6`` are what 1109 you need when the chain is **longer** than that: a CDN or a load 1110 balancer in front of your Nginx, whose element your Nginx appends to. 1111 Listing those hops is what permits the walk to keep stepping leftwards 1112 past them and reach the client that is genuinely at the far end: 1113 1114 .. code-block:: ini 1115 1116 [paivana] 1117 TRUSTED_PROXIES = 10.0.0.0/8;192.168.0.0/16; 1118 TRUSTED_PROXIES6 = 2001:db8::/32; 1119 1120 The walk still stops at the first element written by a node you did 1121 not list, so a client that prepends entries of its own gains nothing 1122 by it however long the list gets. Listing too much is the real risk: 1123 every network you add is a network whose members may name any client 1124 address they like. Keep the list to the proxies you operate, and note 1125 that a UNIX-domain peer needs no entry at all — it has no address to 1126 match, is by construction on this host, and access to it is governed 1127 by ``UNIXPATH_MODE``. The syntax has sharp edges (entries are 1128 terminated as well as separated by ``;``, and ``TRUSTED_PROXIES6`` 1129 tolerates no spaces); :manpage:`paivana.conf(5)` documents them. 1130 1131 One thing the walk does not cover, and the reason the front server's 1132 configuration still matters after you have set all this: ``-f`` also 1133 lets the forwarding headers supply the scheme and authority that 1134 ``paivana-httpd`` builds its own URLs from, when ``BASE_URL`` is not 1135 set. Those are read as the **leftmost** ``proto=`` / ``host=`` (or 1136 ``X-Forwarded-Proto`` / ``-Host`` / ``-Port``) value, which is not the 1137 element the trusted-proxy walk selects and is not vetted against it. 1138 Setting ``BASE_URL`` to your public HTTPS URL closes that off entirely 1139 and is worth doing whether or not you also list your proxies. 1140 1141 Conversely, do not pass ``-f`` to a ``paivana-httpd`` that clients can 1142 reach directly. There it is the client, not a proxy, that is setting 1143 those headers, and ``-f`` is the assertion that this is not so. 1144 1145 If you operate both Paivana and the merchant backend on the same 1146 host, you typically expose them under two different hostnames (e.g. 1147 ``example.com`` and ``backend.example.com``); the merchant 1148 backend must *never* be proxied through ``paivana-httpd``, only 1149 the upstream content service should be. 1150 1151 1152 Verifying the setup 1153 ------------------- 1154 1155 After completing the steps above, a quick smoke test is to request 1156 a paywalled URL with ``curl``: 1157 1158 .. code-block:: shell-session 1159 1160 $ curl -i https://example.com/some-article 1161 1162 An unpaid request will first redirect (302 Found) to the static 1163 paywall page at ``/.well-known/paivana/templates/{id}#{base64_website}`` 1164 which will then return a ``HTTP/1.1 402 Payment Required`` together 1165 with a Taler-formatted paywall body containing the ``taler://pay/...`` URI of 1166 the freshly created order. Paying that order with any GNU Taler wallet (see 1167 the `Wallet documentation <https://docs.taler.net/wallet/>`__) and 1168 re-requesting the URL from the same client should then yield the upstream 1169 content unchanged. If the page is run in a browser, the client-side 1170 JavaScript should automatically trigger the required reload of the page after 1171 the wallet made the payment. 1172 1173 For interactive debugging, ``paivana-httpd -n`` disables the 1174 paywall and turns the daemon into a transparent reverse proxy; 1175 this is useful to confirm that the network plumbing to the 1176 upstream service works before involving the merchant backend. 1177 See :manpage:`paivana-httpd(1)` for the other runtime flags.