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:
@@ -156,6 +156,57 @@ test("federation: a remote user's home link leads to its pages, not its API", as
|
||||
);
|
||||
});
|
||||
|
||||
test("federation: an instance is reached by its pages' address too, which its API must name back", async () => {
|
||||
const home = await fakeInstance(HOME, {
|
||||
authorization_endpoint: "https://www.home.test/authorize",
|
||||
});
|
||||
// Its pages pass `/.well-known` on to its API, as a proxy would; so does an
|
||||
// impostor's, which the home doesn't name.
|
||||
const proxy = (origin: string): FakeInstance => ({
|
||||
...home,
|
||||
origin,
|
||||
handle: (url, init) =>
|
||||
url.origin === origin
|
||||
? home.handle(new URL(url.pathname + url.search, HOME), init)
|
||||
: null,
|
||||
});
|
||||
using _net = network(
|
||||
home,
|
||||
proxy("https://www.home.test"),
|
||||
proxy("https://evil.test"),
|
||||
);
|
||||
|
||||
const peer = await trust("www.home.test");
|
||||
assertEquals(peer.data.host, "home.test");
|
||||
const start = await post<FederationLoginResponse>("/api/federation/logins", {
|
||||
json: { instance: "https://www.home.test" },
|
||||
});
|
||||
assertEquals(start.status, 200);
|
||||
const authorizeUrl = start.data.authorizeUrl;
|
||||
const session = await post<AuthResponse>("/api/federation/sessions", {
|
||||
json: {
|
||||
code: home.issue(authorizeUrl, ALICE),
|
||||
state: new URL(authorizeUrl).searchParams.get("state"),
|
||||
},
|
||||
});
|
||||
assertEquals(session.data.user.username, "alice@home.test");
|
||||
|
||||
assertEquals(
|
||||
(await post("/api/federation/logins", { json: { instance: "evil.test" } }))
|
||||
.status,
|
||||
502,
|
||||
);
|
||||
assertEquals((await trust("evil.test")).status, 502);
|
||||
// Nor is this instance another, by its API's address or its pages'.
|
||||
assertEquals(
|
||||
(await post("/api/federation/logins", {
|
||||
json: { instance: "gerbeur.test" },
|
||||
}))
|
||||
.status,
|
||||
400,
|
||||
);
|
||||
});
|
||||
|
||||
test("federation: a login works once, with the code the home made for it", async () => {
|
||||
const home = await fakeInstance(HOME);
|
||||
using _net = network(home);
|
||||
|
||||
Reference in New Issue
Block a user