Changeset 834bcc3 in Klonkt for src/services/PatreonService.js
- Timestamp:
- 06/23/2026 06:14:27 PM (3 months ago)
- Branches:
- main
- Children:
- d774679
- Parents:
- bb42dfb
- File:
-
- 1 edited
-
src/services/PatreonService.js (modified) (6 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/services/PatreonService.js
rbb42dfb r834bcc3 1 // Patreon -entitlement (premium-laag).1 // Patreon entitlement (premium layer). 2 2 // 3 // Model (Klonkt, 2026-06): de app + alle updates zijn gratis. Een paar premium-4 // modules (Hub -modus, Statistieken, Fan-login) zitten achter een $10-lifetime5 // Patreon -supporter-status. De centrale license-server (license.klonkt.com)6 // check t Patreon en tekent een Ed25519-JWT "entitlement-token". DEZEinstance7 // verifie ert dat token OFFLINE met de publieke sleutel van de server — een8 // gekraakte/geforkte self-host kan dus geen geldig token verzinnen (alleen de9 // license-server kan tekenen). Dat is het echte slot; de feature-flags zelf zijn10 // op self-host wel te patchen (bewust geaccepteerd: $10 < moeite om te kraken).3 // Model (Klonkt, 2026-06): the app + all updates are free. A few premium 4 // modules (Hub mode, Statistics, Fan login) are gated behind a $10 lifetime 5 // Patreon supporter status. The central license server (license.klonkt.com) 6 // checks Patreon and signs an Ed25519 JWT "entitlement token". THIS instance 7 // verifies that token OFFLINE using the server's public key — a cracked/forked 8 // self-host cannot forge a valid token (only the license server can sign). 9 // That is the real lock; feature flags themselves can be patched on self-host 10 // (deliberately accepted: $10 < effort to crack). 11 11 // 12 // Premium staat STANDAARD UIT (KLONKT_PREMIUM_ENABLED != 'on'): dan is er geen 13 // premium-UI en wordt er niets gegate. De self-hoster zet 'm aan zodra Patreon 14 // geregeld is. 12 // Premium is OFF by default (KLONKT_PREMIUM_ENABLED != 'on'): no premium UI 13 // is shown and nothing is gated. Self-hosters enable it once Patreon is set up. 15 14 16 15 import crypto from 'node:crypto'; … … 25 24 export function licenseBase() { return LICENSE_URL; } 26 25 27 // --- Publieke sleutel van de license-server cachen (voor offline verificatie) ---26 // --- Cache the license-server public key (for offline verification) --- 28 27 let _pubKey = null; 29 28 async function licensePublicKey() { 30 29 if (_pubKey) return _pubKey; 31 30 const res = await fetch(`${LICENSE_URL}/pubkey`); 32 if (!res.ok) throw new Error('pubkey fetch fa alde: ' + res.status);31 if (!res.ok) throw new Error('pubkey fetch failed: ' + res.status); 33 32 const pem = await res.text(); 34 33 _pubKey = crypto.createPublicKey(pem); // SPKI-PEM -> Ed25519 public key … … 40 39 } 41 40 42 // Verif ieer een entitlement-token (EdDSA-JWT van de license-server). Gooit bij43 // ongeldige handtekening/issuer/verlooptijd. Geeft de claims terug.41 // Verify an entitlement token (EdDSA JWT from the license server). Throws on 42 // invalid signature, issuer, or expiry. Returns the claims on success. 44 43 export async function verifyEntitlementToken(token) { 45 44 const parts = String(token || '').split('.'); … … 47 46 const [h, p, s] = parts; 48 47 const header = JSON.parse(b64urlToBuf(h).toString('utf8')); 49 if (header.alg !== 'EdDSA') throw new Error(' onverwachtalg');48 if (header.alg !== 'EdDSA') throw new Error('unexpected alg'); 50 49 const key = await licensePublicKey(); 51 50 const ok = crypto.verify(null, Buffer.from(`${h}.${p}`), key, b64urlToBuf(s)); 52 if (!ok) throw new Error(' ongeldige handtekening');51 if (!ok) throw new Error('invalid signature'); 53 52 const payload = JSON.parse(b64urlToBuf(p).toString('utf8')); 54 if (payload.iss !== ISSUER) throw new Error(' onverwachteissuer');55 if (payload.exp && payload.exp * 1000 < Date.now()) throw new Error(' verlopentoken');53 if (payload.iss !== ISSUER) throw new Error('unexpected issuer'); 54 if (payload.exp && payload.exp * 1000 < Date.now()) throw new Error('expired token'); 56 55 return payload; // { sub, entitled, plan, lifetime_support_cents, exp, ... } 57 56 } … … 71 70 } 72 71 73 // Is deze instance premium? Premium-laag aan + een geldig, niet-verlopen,74 // entitled opgeslagen token. Patreon-lifetime daalt nooit, dus opnieuw koppelen75 // na verloop slaagt altijd.72 // Is this instance premium? Premium layer enabled + a valid, non-expired, 73 // entitled stored token. Patreon lifetime never decreases, so re-linking 74 // after expiry always succeeds. 76 75 export function isPremium() { 77 76 if (!premiumEnabled()) return false; … … 82 81 } 83 82 84 // Is een premium-feature beschikbaar? True als de premium-laag UIT staat (danis85 // niets gegate — huidige gedrag), of AAN én deze instance is entitled. False alleen86 // als premium aan staat maar er geen geldige Patreon-koppeling is (= betaalmuur).83 // Is a premium feature available? True if the premium layer is OFF (nothing is 84 // gated — current behavior), or ON and this instance is entitled. False only 85 // if premium is on but there is no valid Patreon connection (= paywall). 87 86 export function premiumUnlocked() { 88 87 return !premiumEnabled() || isPremium();
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)