source: Klonkt/src/routes/federation.js@ 459acd9

main
Last change on this file since 459acd9 was f63cbc2, checked in by roboburr <roboburr@…>, 3 months ago

Circles: protocol version enforced via signature binding (update enforcement)

KLONKT_PROTO is now part of the signed basis (signingInput:
"klonkt/proto/N\n"+body). An instance on a different proto cannot verify
the signed outbox and vice versa → staying up to date is cryptographically
enforced, not a patchable check. Both sides verify the proto
(actor.klonkt.proto + Klonkt-Proto header → 426); too-old/too-new sources
are excluded with status 'outdated' + a clear message. Couple security to
every proto bump. Proto starts at 2; roll out in lockstep.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@…>

  • Property mode set to 100644
File size: 2.5 KB
RevLine 
[b300682]1// routes/federation.js — publieke Cirkels-endpoints (v1, publicatie-kant).
2//
3// GET /.klonkt/actor.json — ActivityStreams-actor + Ed25519-pubkey
4// GET /.klonkt/outbox.json — publieke posts als AS Create-objecten,
5// getekend via de Klonkt-Signature-header
6//
7// Site-agnostisch en zonder auth — alleen lezen. Zie docs/cirkels-v1-spec.md.
8
9import express from 'express';
[f63cbc2]10import { buildActor, buildOutbox, signBody, KLONKT_PROTO, MIN_PROTO } from '../services/CircleFederation.js';
[efdde37]11import { getTenancy } from '../services/SettingsService.js';
[b300682]12
13const router = express.Router();
14
15function baseUrl(req) {
16 const b = process.env.PUBLIC_BASE_URL || `${req.protocol}://${req.get('host')}`;
17 return b.replace(/\/+$/, '');
18}
19
[f63cbc2]20// De proto die de consument zegt te draaien (uit z'n request-header), of 0.
21function consumerProto(req) {
22 return parseInt(req.get('Klonkt-Proto') || '0', 10) || 0;
23}
24
[b300682]25router.get('/.klonkt/actor.json', (req, res) => {
[efdde37]26 // Cirkels = solo-naar-solo; hubs publiceren geen federatie-actor.
27 if (getTenancy() === 'hub') return res.status(404).type('text/plain').send('Niet beschikbaar in hub-modus');
[f63cbc2]28 // De actor serveren we ALTIJD (ook aan oudere consumenten) zodat zij onze proto
29 // kunnen lezen en een nette "update vereist"-melding kunnen tonen.
[b300682]30 const body = JSON.stringify(buildActor(baseUrl(req)), null, 2);
31 res.type('application/activity+json; charset=utf-8');
[f63cbc2]32 res.set('Klonkt-Proto', String(KLONKT_PROTO));
[b300682]33 res.set('Cache-Control', 'public, max-age=300');
34 res.send(body);
35});
36
37router.get('/.klonkt/outbox.json', (req, res) => {
[efdde37]38 if (getTenancy() === 'hub') return res.status(404).type('text/plain').send('Niet beschikbaar in hub-modus');
[f63cbc2]39 res.set('Klonkt-Proto', String(KLONKT_PROTO));
40 // Te-oude consument? Weiger met 426 Upgrade Required (de crypto-binding sluit 'm
41 // sowieso al uit; dit geeft een expliciet, leesbaar signaal). proto 0 = geen
42 // header (bv. een browser/curl) → toestaan, die verifieert toch niet.
43 const cp = consumerProto(req);
44 if (cp && cp < MIN_PROTO) {
45 return res.status(426).type('text/plain')
46 .send(`Upgrade Required: deze cirkel draait proto ${KLONKT_PROTO}; jouw Klonkt (proto ${cp}) is te oud.`);
47 }
[b300682]48 const body = JSON.stringify(buildOutbox(baseUrl(req)), null, 2);
49 res.type('application/activity+json; charset=utf-8');
50 res.set('Cache-Control', 'public, max-age=300');
51 res.set('Klonkt-Signature', `ed25519=${signBody(body)}`);
52 res.send(body);
53});
54
55export default router;
Note: See TracBrowser for help on using the repository browser.