Changeset b1d5f3f in Klonkt
- Timestamp:
- 10/24/2026 11:48:41 AM (23 hours ago)
- Branches:
- main
- Children:
- 5d0d3f35
- Parents:
- 61d52ed
- Files:
-
- 2 added
- 4 edited
-
src/config/database.js (modified) (1 diff)
-
src/routes/activitypub.js (modified) (2 diffs)
-
src/services/ActivityPubService.js (modified) (2 diffs)
-
src/services/IdentityProof.js (added)
-
test/activitypub-as2.test.js (modified) (1 diff)
-
test/identity-proofs.test.js (added)
Legend:
- Unmodified
- Added
- Removed
-
src/config/database.js
r61d52ed rb1d5f3f 245 245 // kort bericht een halve pagina leegte gaf; nu is het een keuze. 246 246 ensureColumn('sites', 'reader_full_page', 'INTEGER DEFAULT 0'); 247 // Identiteitsbewijzen (FEP-c390, shaer-jpxj): de ondertekende verklaringen, 248 // als JSON-lijst, precies zoals ze binnenkwamen -- een handtekening hangt aan 249 // elke byte van de canonieke vorm, dus herschrijven zou hem breken. 250 ensureColumn('sites', 'identity_proofs', 'TEXT'); 247 251 ensureColumn('sites', 'show_search', 'INTEGER DEFAULT 1'); 248 252 ensureColumn('sites', 'show_archive_link', 'INTEGER DEFAULT 1'); -
src/routes/activitypub.js
r61d52ed rb1d5f3f 21 21 import * as Guardianship from '../services/guardianship/index.js'; 22 22 import * as ConversationKey from '../services/conversation-key.js'; 23 import * as IdentityProof from '../services/IdentityProof.js'; 23 24 import * as Migration from '../services/MigrationService.js'; 24 25 import { getPrimarySite } from '../middleware/site.js'; … … 1029 1030 }, 1030 1031 }); 1032 // ── Identiteitsbewijzen (FEP-c390, shaer-jpxj) ──────────────────── 1033 // 1034 // De eigenaar biedt een verklaring aan die met een sleutel van ELDERS is 1035 // ondertekend: "did:key:... is ook deze actor". Deze server ondertekent niets; 1036 // hij controleert (IdentityProof.checkStatement) en publiceert onder 1037 // `attachment` op de actor, waar iedereen hem zelf kan nalopen. 1038 // 1039 // Eén per did:key: een nieuwe verklaring met dezelfde sleutel vervangt de 1040 // vorige, want een verhuizing of een vernieuwde datum is dezelfde bewering. 1041 // Een handvol is genoeg; meer is geen identiteit meer maar een verzameling. 1042 const MAX_IDENTITY_PROOFS = 3; 1043 function readProofs(site) { 1044 try { const a = JSON.parse(site.identity_proofs || '[]'); return Array.isArray(a) ? a : []; } catch { return []; } 1045 } 1046 function writeProofs(site, list) { 1047 db.prepare('UPDATE sites SET identity_proofs = ? WHERE id = ?').run(list.length ? JSON.stringify(list) : null, site.id); 1048 // De volgers horen dat de actor veranderde; anders blijft hun kopie de oude. 1049 const vers = db.prepare('SELECT * FROM sites WHERE id = ?').get(site.id); 1050 if (vers) AP.deliverActorUpdate(vers).catch((e) => console.warn('[c390] actor-update:', e && e.message)); 1051 } 1052 router.post('/ap/users/:slug/identityProofs', express.json({ limit: '16kb', type: ['application/json', 'application/activity+json', 'application/ld+json'] }), (req, res) => { 1053 const auth = OAuth.verifyBearer(req.headers.authorization); 1054 if (!auth || auth.site.slug !== req.params.slug) return res.status(403).end(); 1055 const base = baseUrl(req); 1056 const me = AP.actorId(base, auth.site.slug); 1057 const stmt = req.body; 1058 const uitslag = IdentityProof.checkStatement(stmt, me); 1059 if (!uitslag.ok) return res.status(422).json({ error: uitslag.error }); 1060 const site = db.prepare('SELECT * FROM sites WHERE id = ?').get(auth.site.id); 1061 const rest = readProofs(site).filter((b) => b && b.subject !== uitslag.subject); 1062 if (rest.length >= MAX_IDENTITY_PROOFS) return res.status(409).json({ error: 'too_many_identity_proofs', max: MAX_IDENTITY_PROOFS }); 1063 writeProofs(site, [...rest, stmt]); 1064 res.status(201).json({ subject: uitslag.subject, alsoKnownAs: me }); 1065 }); 1066 router.delete('/ap/users/:slug/identityProofs', (req, res) => { 1067 const auth = OAuth.verifyBearer(req.headers.authorization); 1068 if (!auth || auth.site.slug !== req.params.slug) return res.status(403).end(); 1069 const subject = String(req.query.subject || ''); 1070 if (!IdentityProof.publicKeyFromDidKey(subject)) return res.status(400).json({ error: 'subject must be an Ed25519 did:key' }); 1071 const site = db.prepare('SELECT * FROM sites WHERE id = ?').get(auth.site.id); 1072 const voor = readProofs(site); 1073 const na = voor.filter((b) => b && b.subject !== subject); 1074 if (na.length === voor.length) return res.status(404).json({ error: 'no_such_identity_proof' }); 1075 writeProofs(site, na); 1076 res.status(204).end(); 1077 }); 1078 1031 1079 router.post('/ap/users/:slug/uploadMedia', (req, res) => { 1032 1080 const auth = OAuth.verifyBearer(req.headers.authorization); -
src/services/ActivityPubService.js
r61d52ed rb1d5f3f 393 393 oauthTokenEndpoint: `${base}/oauth/token`, 394 394 uploadMedia: `${id}/uploadMedia`, 395 // FEP-c390 (shaer-jpxj): waar de eigenaar een identiteitsbewijs aanbiedt. 396 // Alleen met zijn token; Shaer vindt het hier in plaats van het te raden. 397 'shaer:identityProofs': `${id}/identityProofs`, 395 398 }, 396 399 publicKey: { … … 449 452 } 450 453 } catch { /* skip malformed profile_links */ } 454 // FEP-c390: de identiteitsbewijzen, ACHTER de profiellinks. Ongewijzigd uit de 455 // database: ze werden bij binnenkomst gecontroleerd, en een handtekening hangt 456 // aan elke byte. Elke verklaring draagt haar eigen @context, dus de actor zelf 457 // hoeft er geen bij. 458 try { 459 const bewijzen = JSON.parse(site.identity_proofs || '[]'); 460 if (Array.isArray(bewijzen) && bewijzen.length) { 461 actor.attachment = [...(actor.attachment || []), ...bewijzen.filter((b) => b && b.type === 'VerifiableIdentityStatement')]; 462 } 463 } catch { /* skip malformed identity_proofs */ } 451 464 return actor; 452 465 } -
test/activitypub-as2.test.js
r61d52ed rb1d5f3f 58 58 // ActivityPub §4.1 `endpoints` vocabulary (same category as sharedInbox), used for C2S. 59 59 'oauthAuthorizationEndpoint', 'oauthTokenEndpoint', 'uploadMedia', 60 // FEP-c390 (shaer-jpxj): waar de eigenaar een identiteitsbewijs aanbiedt. De 61 // verklaringen zelf staan onder `attachment` en dragen hun eigen @context 62 // (did/v1, data-integrity/v1, fep/c390), dus hun termen horen niet hier. 63 'shaer:identityProofs', 60 64 'publicKey', 'owner', 'publicKeyPem', 61 65 'Note', 'Person', 'Create', 'Update', 'Delete', 'Tombstone', 'Announce', 'Like', 'Follow',
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)