Changeset 072a242 in Klonkt for src/routes/posts.js


Ignore:
Timestamp:
07/21/2026 03:26:24 AM (7 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
b9beb16
Parents:
dede82e
git-author:
Robin <roboburr@…> (07/21/2026 03:26:22 AM)
git-committer:
Robin <roboburr@…> (07/21/2026 03:26:24 AM)
Message:

Fix: unlocked post renders via its own page; no duplicate supporter button

Two things the tester hit once the passkey flow worked:

  1. Duplicate "Word supporter". The gate's no-WebAuthn fallback relabelled the unlock button to "Word supporter", which sat next to the real "Word supporter op Patreon" button. In the two-button layout that fallback now hides the (unusable) unlock button instead; the single-button layout keeps the relabel since it's the only button.
  1. The unlocked content sat in a bare <div>, so it lost the post page's layout and scoped typography ("div niet in een goeie div"). Instead of injecting HTML into the gate, /paid/unlock now returns a short-lived (120s) signed unlock capability and the client reloads the real post URL with it (?u=). GET /:slug renders the FULL post through its normal template when the capability is valid: correct wrapper, scoped styles, and working audio players (which bind on load and couldn't init in injected HTML). Not a cookie and not stored: the token lives only in that one URL and expires.

Changed files:
src/routes/paid.js

  • /unlock returns { ok, redirect } with a 120s unlock blob; drop the renderPostBodyHtml import (no longer injected here)

src/routes/posts.js

  • a valid ?u= capability bypasses the paid gate and renders the post

src/views/pages/paid-gate.ejs

  • no-WebAuthn fallback hides the unlock button when a Patreon page exists; success reloads the real post via the redirect

-robo
Co-Authored-By: Claude Opus 4.8 <noreply@…>

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/routes/posts.js

    rdede82e r072a242  
    2222import { premiumUnlocked } from '../services/PatreonService.js';
    2323import { defaultMinCents as paidDefaultMinCents, patreonUrl as paidPatronUrl } from '../services/PaidPatreonService.js';
     24import { verifyBlob } from '../services/CryptoBox.js';
    2425import MusicMeta from '../services/MusicMeta.js';
    2526
     
    12381239  // never see the unlock button).
    12391240  const canEditThis = req.session?.user && PermissionsService.canEditPost(req.session.user, post, site);
    1240   if (post.paid && !canEditThis) {
     1241  // A fresh unlock capability (?u=) from /paid/unlock lets a just-verified
     1242  // supporter render the FULL post through this normal template (correct layout,
     1243  // scoped styles, working audio). Short-lived signed blob, single post, not a
     1244  // cookie and not stored.
     1245  const _u = req.query.u ? verifyBlob(String(req.query.u)) : null;
     1246  const _unlocked = _u && _u.purpose === 'unlocked' && _u.siteId === site.id && String(_u.post) === String(post.slug);
     1247  if (post.paid && !canEditThis && !_unlocked) {
    12411248    const { newerPost, olderPost } = postNeighbors(site, post, res.locals.tenancy === 'hub');
    12421249    return renderPage(req, res, 'pages/paid-gate', {
Note: See TracChangeset for help on using the changeset viewer.