turnstile

Drupal paywall plugin
Log | Files | Refs | README | LICENSE

commit 633a036ca2450d664bc621ac7fcc3288772b23ed
parent cc3ed8cc3c3adaa4164faa6e99192ec2311676d5
Author: Christian Grothoff <christian@grothoff.org>
Date:   Wed, 20 May 2026 18:59:46 +0200

fix cache control, cookie path

Diffstat:
Msrc/PaivanaCookie.php | 14+++++++++++++-
Mtaler_turnstile.module | 8++++++++
2 files changed, 21 insertions(+), 1 deletion(-)

diff --git a/src/PaivanaCookie.php b/src/PaivanaCookie.php @@ -56,17 +56,29 @@ class PaivanaCookie { * now + $lifetime_seconds. Cookie is bound to the request's * client address. * + * The cookie's Path attribute is scoped to the website's URL path, + * so it is only sent back when the browser visits exactly that + * resource. This mirrors paivana-httpd's default (per-URL cookie): + * other paywalled pages on the same host get their own cookie and + * cannot piggy-back on this one. As a side effect, it also keeps + * the cookie out of unrelated requests, so the page cache for + * other URLs stays warm. + * * @return \Symfony\Component\HttpFoundation\Cookie */ public function mint(Request $request, string $website, int $lifetime_seconds): Cookie { $expires = time() + $lifetime_seconds; $client_addr = $request->getClientIp() ?? ''; $value = $expires . '-' . $this->hash($expires, $website, $client_addr); + $path = parse_url($website, PHP_URL_PATH); + if (! is_string($path) || $path === '') { + $path = '/'; + } return Cookie::create( self::COOKIE_NAME, $value, $expires, - '/', + $path, NULL, $request->isSecure(), TRUE, diff --git a/taler_turnstile.module b/taler_turnstile.module @@ -118,6 +118,14 @@ function taler_turnstile_entity_view_alter(array &$build, EntityInterface $entit if ($cookies->verify($request, $fulfillment_url)) { \Drupal::logger('taler_turnstile')->debug('Valid Paivana cookie, granting access to @url', ['@url' => $fulfillment_url]); $build['#cache']['contexts'][] = 'cookies:' . TALER_TURNSTILE_COOKIE; + // Vary on Cookie so that when the access cookie expires (or is + // cleared) the next navigation to this URL misses the browser + // cache and re-reaches the origin to re-evaluate the paywall. + // 'private' keeps shared caches from handing one paying + // visitor's full-content response to another visitor whose + // request happens to lack the cookie. + $build['#attached']['http_header'][] = ['Vary', 'Cookie', FALSE]; + $build['#attached']['http_header'][] = ['Cache-Control', 'private', TRUE]; return; }