| [5d0d3f35] | 1 | // WebFinger op een did:key (FEP-c390, shaer-jpxj): wie een papieren sleutel
|
|---|
| 2 | // vasthoudt, vraagt welk account zegt dat het die sleutel is.
|
|---|
| 3 | //
|
|---|
| 4 | // Wat hier bewaakt wordt: alleen wat NU klopt (een verklaring die niet meer
|
|---|
| 5 | // natelopen is telt niet), alleen openbare sites (net als acct:), twee accounts
|
|---|
| 6 | // met dezelfde sleutel staan er allebei (oud en nieuw na een verhuizing), en
|
|---|
| 7 | // een acct:-vraag gaat precies als vroeger.
|
|---|
| 8 | import { test } from 'node:test';
|
|---|
| 9 | import assert from 'node:assert/strict';
|
|---|
| 10 |
|
|---|
| 11 | process.env.DATABASE_PATH = ':memory:';
|
|---|
| 12 | process.env.PUBLIC_BASE_URL = 'https://test.example';
|
|---|
| 13 |
|
|---|
| 14 | const dbMod = await import('../src/config/database.js');
|
|---|
| 15 | const db = dbMod.default;
|
|---|
| 16 | { const stil = console.log; console.log = () => {}; try { dbMod.initializeDatabase(); } finally { console.log = stil; } }
|
|---|
| 17 | const I = await import('../src/services/IdentityProof.js');
|
|---|
| 18 |
|
|---|
| 19 | const seedA = Buffer.alloc(32, 7);
|
|---|
| 20 | const seedB = Buffer.alloc(32, 9);
|
|---|
| 21 | const didA = I.didKeyFromPublicKey(I.publicKeyFromSeed(seedA));
|
|---|
| 22 | const didB = I.didKeyFromPublicKey(I.publicKeyFromSeed(seedB));
|
|---|
| 23 | const actorOf = (slug) => `https://test.example/ap/users/${slug}`;
|
|---|
| 24 | function verklaring(seed, slug) {
|
|---|
| 25 | const did = I.didKeyFromPublicKey(I.publicKeyFromSeed(seed));
|
|---|
| 26 | const doc = { '@context': I.STATEMENT_CONTEXT, type: 'VerifiableIdentityStatement', subject: did, alsoKnownAs: actorOf(slug) };
|
|---|
| 27 | return I.signEddsaJcs2022(doc, seed, { created: '2026-10-24T10:00:00Z', verificationMethod: did, proofPurpose: 'assertionMethod' });
|
|---|
| 28 | }
|
|---|
| 29 |
|
|---|
| 30 | db.prepare('INSERT INTO users (id, username, email, password_hash, role) VALUES (?,?,?,?,?)').run('u1', 'u1', 'u1@t', 'x', 'god');
|
|---|
| 31 | const site = (id, slug, proofs, isPublic = 1) => db.prepare('INSERT INTO sites (id, slug, title, owner_id, is_public, is_primary, identity_proofs) VALUES (?,?,?,?,?,?,?)')
|
|---|
| 32 | .run(id, slug, slug, 'u1', isPublic, id === 's1' ? 1 : 0, proofs ? JSON.stringify(proofs) : null);
|
|---|
| 33 | site('s1', 'oud', [verklaring(seedA, 'oud')]);
|
|---|
| 34 | site('s2', 'nieuw', [verklaring(seedA, 'nieuw')]); // dezelfde sleutel, na een verhuizing
|
|---|
| 35 | site('s3', 'besloten', [verklaring(seedB, 'besloten')], 0); // niet openbaar
|
|---|
| 36 | // Een verklaring die op de verkeerde actor staat: geldig ondertekend, maar voor 'oud'.
|
|---|
| 37 | site('s4', 'leent', [verklaring(seedA, 'oud')]);
|
|---|
| 38 |
|
|---|
| 39 | const express = (await import('express')).default;
|
|---|
| 40 | const routes = (await import('../src/routes/activitypub.js')).default;
|
|---|
| 41 | const app = express(); app.use(routes);
|
|---|
| 42 | const server = app.listen(0); server.unref();
|
|---|
| 43 | const base = `http://127.0.0.1:${server.address().port}`;
|
|---|
| 44 | test.after(() => server.close());
|
|---|
| 45 | const wf = (resource) => fetch(`${base}/.well-known/webfinger?resource=${encodeURIComponent(resource)}`);
|
|---|
| 46 |
|
|---|
| 47 | test('een gekoppelde sleutel vindt zijn accounts, oud en nieuw allebei', async () => {
|
|---|
| 48 | const r = await wf(didA);
|
|---|
| 49 | assert.equal(r.status, 200);
|
|---|
| 50 | assert.match(r.headers.get('content-type'), /application\/jrd\+json/);
|
|---|
| 51 | const jrd = await r.json();
|
|---|
| 52 | assert.equal(jrd.subject, didA);
|
|---|
| 53 | assert.deepEqual(jrd.links.map((l) => l.href), [actorOf('nieuw'), actorOf('oud')]);
|
|---|
| 54 | assert.ok(jrd.links.every((l) => l.rel === 'self' && l.type === 'application/activity+json'));
|
|---|
| 55 | assert.ok(!jrd.links.some((l) => l.href === actorOf('leent')), 'een verklaring voor een ander account telt niet voor deze');
|
|---|
| 56 | });
|
|---|
| 57 |
|
|---|
| 58 | test('een sleutel die nergens aan hangt, of alleen aan een besloten site, is er niet', async () => {
|
|---|
| 59 | assert.equal((await wf(I.didKeyFromPublicKey(I.publicKeyFromSeed(Buffer.alloc(32, 1))))).status, 404);
|
|---|
| 60 | assert.equal((await wf(didB)).status, 404, 'besloten sites tellen niet mee, net als bij acct:');
|
|---|
| 61 | });
|
|---|
| 62 |
|
|---|
| 63 | test('een verklaring die niet meer klopt, telt niet', async () => {
|
|---|
| 64 | const geknoeid = verklaring(seedA, 'oud'); geknoeid.proof.created = '2030-01-01T00:00:00Z';
|
|---|
| 65 | db.prepare("UPDATE sites SET identity_proofs = ? WHERE id = 's1'").run(JSON.stringify([geknoeid]));
|
|---|
| 66 | try {
|
|---|
| 67 | const jrd = await (await wf(didA)).json();
|
|---|
| 68 | assert.deepEqual(jrd.links.map((l) => l.href), [actorOf('nieuw')]);
|
|---|
| 69 | } finally {
|
|---|
| 70 | db.prepare("UPDATE sites SET identity_proofs = ? WHERE id = 's1'").run(JSON.stringify([verklaring(seedA, 'oud')]));
|
|---|
| 71 | }
|
|---|
| 72 | });
|
|---|
| 73 |
|
|---|
| 74 | test('geen did:key is geen did-vraag, en acct: gaat als vroeger', async () => {
|
|---|
| 75 | assert.equal((await wf('did:key:zNietBase58!!')).status, 400);
|
|---|
| 76 | const r = await wf('acct:oud@test.example');
|
|---|
| 77 | assert.equal(r.status, 200);
|
|---|
| 78 | assert.equal((await r.json()).subject, 'acct:oud@test.example');
|
|---|
| 79 | });
|
|---|