v4: an instance can be named by the address of its pages — sign-in and the trust list took only an instance's issuer, its API's origin, so where the pages live elsewhere the address people see in their browser named no instance: in development, localhost:3000 answered its /.well-known with index.html, and the sign-in failed as "doesn't answer as a gerbeur". resolveIssuer (remote/gerbeur-instance.ts) now reads the metadata at the address given and, when it names another issuer, takes it only if that issuer's own metadata names those pages back as where its /authorize is, so an impostor can't claim one; sign-in, the trust list and the check against this instance go through it. The Vite dev server passes /.well-known on to its API, as whatever serves the pages must in production (README). A handle still carries the API's host.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
khannurien
2026-10-09 22:14:21 +00:00
parent 9608520675
commit f1bc23a65e
7 changed files with 161 additions and 27 deletions

View File

@@ -18,7 +18,8 @@ import {
parseInstance,
type PeerStatus,
} from "../../shared/federation.ts";
import { fetchMetadata } from "../remote/gerbeur-instance.ts";
import { fetchMetadata, resolveIssuer } from "../remote/gerbeur-instance.ts";
import { isSelfHost } from "../lib/self-url.ts";
import { closeUserSockets } from "./ws-service.ts";
interface PeerRow {
@@ -64,8 +65,8 @@ export function listPeers(trustedOnly: boolean): FederationPeer[] {
}
/**
* Puts the instance `instance` names on the list of `status`, for `/manage`,
* and returns it. A trusted one must answer as a gerbeur instance, whose name
* Puts the instance `instance` names, by its API's address or its pages'
* (`resolveIssuer`), on the list of `status`, for `/manage`, and returns it. A trusted one must answer as a gerbeur instance, whose name
* is kept; a blocked one is blocked whether it answers or not, keeping the
* name it had. Blocking one ends the sessions of its users here at once:
* their tokens stop counting by `getSessionState`, and this closes their
@@ -78,20 +79,27 @@ export async function setPeer(
instance: string,
status: PeerStatus,
): Promise<FederationPeer> {
const issuer = parseInstance(instance);
if (!issuer) throw new APIException(400, "Not an instance's address");
if (issuer === PUBLIC_URL) {
const origin = parseInstance(instance);
if (!origin) throw new APIException(400, "Not an instance's address");
if (isSelfHost(new URL(origin).host)) {
throw new APIException(400, "That is this instance");
}
// An instance is listed by its issuer, which its pages' address leads to;
// one that doesn't answer may still be blocked, by the address given.
let issuer = origin;
let name: string | null = null;
try {
issuer = await resolveIssuer(origin);
name = (await fetchMetadata(issuer)).gerbeur_site_name;
} catch (err) {
console.error(`[federation] ${issuer} doesn't answer:`, err);
console.error(`[federation] ${origin} doesn't answer:`, err);
if (status === "trusted") {
throw new APIException(502, "That address doesn't answer as a gerbeur");
}
}
if (issuer === PUBLIC_URL) {
throw new APIException(400, "That is this instance");
}
db.prepare(
`INSERT INTO federation_peers (issuer, status, name, created_at)
VALUES (?, ?, ?, ?)