source: Klonkt/test/paid-post-lekt-niet.test.js@ 1019a90

main
Last change on this file since 1019a90 was 1019a90, checked in by Robin <roboburr@…>, 3 weeks ago

Een betaalde post verloor zijn waarschuwing, en een test die dat vastlegt

Barts melding (15-8): https://boiert.eu/we-should-explore-at-least-something was
bij een volger opeens volledig te lezen toen de outbox werd opgehaald.

NAGEMETEN, en het ligt anders dan het lijkt. Die post staat met 4555 tekens
volledig in de PUBLIEKE outbox -- ik haalde hem onbetekend op, dus niet alleen
volgers zien hem. Maar hij draagt sensitive: true en
summary: "Smut Lewd, and PAID..", en juist DAT bewijst dat de paid-tak niet
genomen is: die tak liet allebei die velden vallen. De post is dus niet als
paid gemarkeerd -- "PAID" staat in de waarschuwingstekst, en dat is tekst,
geen poort.

De redactie zelf werkt wel. Vier van de vijf nieuwe tests slaagden meteen: een
betaalde post lekt zijn inhoud niet via buildNote, niet via de outbox, en ook
niet voor een VRIEND. Dat laatste is het antwoord op Barts vraag: fan_only laat
posts mee voor iedere volger (outboxAudience geeft 'friend' aan elke rij in
ap_followers), maar de paid-redactie kent geen publiek -- het object zelf is een
teaser, voor iedereen.

WAT ER WEL STUK WAS: de vroege return zette sensitive en summary niet, want
die worden pas na de gewone tak toegevoegd. Een betaalde post met een
waarschuwing ging dus ZONDER waarschuwing de deur uit -- en de teaser is
publiek, dus juist die had hem nodig. Een gevoelige teaser zonder vlag is erger
dan geen teaser.

Vijf tests, waaronder een tegenproef zodat ze niet slagen door alles dicht te
gooien. Gecontroleerd dat ze bijten: reparatie eruit -> 4 groen 1 rood.

WAT DIT NIET OPLOST, en dat hoort erbij: de bescherming hangt aan EEN vroege
return in buildNote. Elk pad dat post.content serveert zonder buildNote lekt
meteen en zonder foutmelding. De drie paden die een lezer heeft (outbox,
/ap/notes/:id, thread) gaan er nu langs; die tests staan er nu op. En het blijft
zo dat "PAID" in een waarschuwingstekst niets doet -- als dat vaker gebeurt is
dat een UI-vraag, niet een bug.

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

  • Property mode set to 100644
File size: 4.0 KB
Line 
1// Een betaalde post lekt zijn inhoud nergens (klonkt-demo-aki).
2//
3// Aanleiding (Bart, 15-8): https://boiert.eu/we-should-explore-at-least-something
4// stond met 4555 tekens VOLLEDIG in de publieke outbox -- onbetekend op te
5// halen. Die post bleek niet als `paid` gemarkeerd: "PAID" stond alleen in de
6// waarschuwingstekst, en dat is tekst, geen poort.
7//
8// Dat de redactie op EEN plek hangt (de vroege return in buildNote) maakt hem
9// kwetsbaar: elk pad dat post.content serveert zonder buildNote lekt meteen, en
10// zonder foutmelding. Deze tests leggen vast dat geen enkele weg dat doet.
11import { test } from 'node:test';
12import assert from 'node:assert/strict';
13
14process.env.DATABASE_PATH = ':memory:';
15process.env.PUBLIC_BASE_URL = 'https://ons.test';
16
17const dbMod = await import('../src/config/database.js');
18const db = dbMod.default;
19dbMod.initializeDatabase();
20const AP = await import('../src/services/ActivityPubService.js');
21
22db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)')
23 .run('u1', 'u1', 'u1@t', 'x', 'god');
24db.prepare('INSERT INTO sites (id, slug, title, owner_id, is_public) VALUES (?,?,?,?,1)')
25 .run('s1', 'dev', 'Dev', 'u1');
26const site = () => db.prepare("SELECT * FROM sites WHERE id = 's1'").get();
27
28const GEHEIM = 'DIT IS DE BETAALDE INHOUD DIE NERGENS MAG STAAN';
29
30function maak({ id, slug, paid = 0, fan = 0, nsfw = 0, cw = null }) {
31 db.prepare(`INSERT INTO posts (id, site_id, author_id, slug, title, excerpt, content,
32 status, published_at, paid, fan_only, nsfw, content_warning)
33 VALUES (?,?,?,?,?,?,?,'published',?,?,?,?,?)`)
34 .run(id, 's1', 'u1', slug, 'Titel', 'Een teaser.',
35 `<p>${GEHEIM}</p><p>en nog meer</p>`, '2026-08-15T12:00:00Z', paid, fan, nsfw, cw);
36 return db.prepare('SELECT * FROM posts WHERE id = ?').get(id);
37}
38
39const alleTekst = (o) => JSON.stringify(o);
40
41test('een betaalde post geeft een teaser, nooit de inhoud', () => {
42 const note = AP.buildNote('https://ons.test', site(), maak({ id: 'p1', slug: 'betaald', paid: 1 }));
43 assert.ok(!alleTekst(note).includes(GEHEIM), 'de inhoud staat nergens in het object');
44 assert.match(note.content, /Een teaser/);
45});
46
47test('ook niet via de OUTBOX, het pad waar het misging', () => {
48 // Bart zag hem juist daar: de outbox serveert Create-activiteiten en die
49 // dragen het Note-object. Als de redactie daar niet meekomt, lekt alles.
50 const p = maak({ id: 'p2', slug: 'betaald-2', paid: 1 });
51 const ob = AP.buildOutbox('https://ons.test', site(), [p], []);
52 assert.ok(!alleTekst(ob).includes(GEHEIM), 'ook de outbox draagt hem niet');
53});
54
55test('en niet voor een VRIEND, want de redactie kent geen publiek', () => {
56 // outboxAudience geeft 'friend' aan iedere volger; fan_only laat die posts dan
57 // mee. Een betaalde post mag dat NIET volgen -- betalen is iets anders dan
58 // volgen.
59 const p = maak({ id: 'p3', slug: 'betaald-vrienden', paid: 1, fan: 1 });
60 const ob = AP.buildOutbox('https://ons.test', site(), [p], []);
61 assert.ok(!alleTekst(ob).includes(GEHEIM));
62});
63
64test('een SENSITIVE betaalde post houdt zijn waarschuwing', () => {
65 // Hier zat de tweede fout: de vroege return liet `sensitive` en `summary`
66 // vallen, dus een betaalde post met een waarschuwing kwam ZONDER die
67 // waarschuwing de deur uit. De teaser is publiek; dan hoort de waarschuwing
68 // dat ook te zijn.
69 const p = maak({ id: 'p4', slug: 'betaald-nsfw', paid: 1, nsfw: 1, cw: 'Smut' });
70 const note = AP.buildNote('https://ons.test', site(), p);
71 assert.ok(!alleTekst(note).includes(GEHEIM), 'inhoud nog steeds weg');
72 assert.equal(note.sensitive, true, 'de vlag reist mee');
73 assert.equal(note.summary, 'Smut', 'en de waarschuwingstekst ook');
74});
75
76test('een NIET-betaalde post lekt niets extra: die hoort gewoon leesbaar', () => {
77 // De tegenproef, zodat de test niet slaagt door alles dicht te gooien.
78 const note = AP.buildNote('https://ons.test', site(), maak({ id: 'p5', slug: 'gewoon' }));
79 assert.ok(alleTekst(note).includes(GEHEIM), 'een gewone post draagt zijn inhoud wel');
80});
Note: See TracBrowser for help on using the repository browser.