Changeset 61e3daf in Klonkt for docs/paid-posts-design.md


Ignore:
Timestamp:
07/21/2026 12:38:08 AM (7 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
928d1c7
Parents:
4a08bfc
git-author:
Robin <roboburr@…> (07/21/2026 12:38:07 AM)
git-committer:
Robin <roboburr@…> (07/21/2026 12:38:08 AM)
Message:

Feature: paid posts slice 1, owner Patreon config (encrypted)

The site owner can connect their OWN Patreon campaign for paid posts
(klonkt-demo-aki), premium-gated in Beheer. Client id/secret, campaign
id and the creator access/refresh token are stored ENCRYPTED at rest
(new CryptoBox AES-256-GCM helper, key from PAID_SECRET), so a database
dump leaks nothing usable; the token auto-refreshes. Separate from
Klonkt Premium's license flow, which is untouched. Degrades gracefully:
without PAID_SECRET the admin page refuses to save rather than storing
plaintext. Nothing patron-facing yet (posts.paid + unlock come in
slices 2 to 4), so no changelog entry.

CryptoBox also carries the cookie-less signed-blob helper (signBlob/
verifyBlob) that slices 3 and 4 reuse for the OAuth state and the
WebAuthn challenge.

Changed files:
src/config/database.js

  • paid_patreon table (site_id PK, secrets encrypted)

src/server.js

  • mount /admin/paid

src/views/pages/admin.ejs

  • "Betaalde posts" button in Beheer

New file:
src/services/CryptoBox.js

  • aes-256-gcm encrypt/decrypt + HMAC signBlob/verifyBlob

src/services/PaidPatreonService.js

  • owner config CRUD (encrypted), token refresh, creatorAccessToken

src/routes/admin-paid.js

  • premium-gated config form (GET/POST/disconnect)

src/views/pages/admin-paid.ejs

  • the form + status

test/paid-patreon.test.js

  • crypto roundtrip, no-plaintext-in-DB, refresh, blob signing

docs/paid-posts-design.md, docs/privacy-betaalde-posts.md

  • concurrency property documented

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

File:
1 edited

Legend:

Unmodified
Added
Removed
  • docs/paid-posts-design.md

    r4a08bfc r61e3daf  
    7070
    7171`PAID_SECRET` (32 random bytes) lives in env, like the other secrets.
     72
     73## Concurrency (a property of the cookie-less model)
     74
     75Because there is no session and no "current user", the model is inherently
     76multi-user. Two people unlock side by side with no shared state to collide:
     77each request carries its own assertion, verified against that credential's own
     78public key, and the content goes back in that one response. `paid_entitlements`
     79is keyed per credential, so N passkeys are N independent rows. The challenge is
     80stateless (the signed blob), so there is no single "pending challenge" slot a
     81second visitor could overwrite. Unlike a cookie session, "two people in the same
     82browser" cannot clobber each other. The only caveat is a shared browser profile:
     83the passkey picker would then list both passkeys (a small visibility hint, not
     84access).
    7285
    7386## Data model (additive)
Note: See TracChangeset for help on using the changeset viewer.