v4: an instance that takes no users of others still blocks instances its own users may not sign in to — with GERBEUR_FEDERATION_INCOMING=0, /manage hid its list of instances, though a blocked one still kept the instance's users from signing in there, and no admin could block another. The list now stays while federated sign-in goes either way: without incoming sign-ins it shows and adds only blocked instances, the trusted ones kept for when they're back, and says what blocking does there. Where both ways are on, its note now says that users here can't sign in to a blocked instance either.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
khannurien
2026-10-09 16:33:08 +00:00
parent 83dc44050e
commit 52a23db504
6 changed files with 195 additions and 65 deletions

View File

@@ -237,10 +237,11 @@ export const FEDERATION_ALLOW_PRIVATE: boolean =
* *
* - `incoming` (`GERBEUR_FEDERATION_INCOMING`): users of other instances sign * - `incoming` (`GERBEUR_FEDERATION_INCOMING`): users of other instances sign
* in here. Off, the login and register pages offer no other instance, * in here. Off, the login and register pages offer no other instance,
* `/manage` no list of instances to trust or block, * `/manage` lists only the blocked ones, which this instance's users can't
* `federation-service.ts` refuses their sign-ins, and the accounts that came * sign in to (and none with `outgoing` off too), `federation-service.ts`
* from one are signed out (`getSessionState`) until it is on again. Their * refuses their sign-ins, and the accounts that came from one are signed
* homes' events are still applied, so that none is lost meanwhile. * out (`getSessionState`) until it is on again. Their homes' events are
* still applied, so that none is lost meanwhile.
* - `outgoing` (`GERBEUR_FEDERATION_OUTGOING`): this instance's users sign in * - `outgoing` (`GERBEUR_FEDERATION_OUTGOING`): this instance's users sign in
* to others. Off, `identity-provider-service.ts` lets no instance sign them * to others. Off, `identity-provider-service.ts` lets no instance sign them
* in, and its metadata says so, which the others check before sending * in, and its metadata says so, which the others check before sending

View File

@@ -354,7 +354,7 @@ msgstr "Already dumped by {0} {1}"
msgid "Already have an account? <0>Log in</0>" msgid "Already have an account? <0>Log in</0>"
msgstr "Already have an account? <0>Log in</0>" msgstr "Already have an account? <0>Log in</0>"
#: src/components/federation/PeerManager.tsx:149 #: src/components/federation/PeerManager.tsx:167
msgid "An instance's address, like music.example" msgid "An instance's address, like music.example"
msgstr "An instance's address, like music.example" msgstr "An instance's address, like music.example"
@@ -395,11 +395,12 @@ msgstr "Back to login"
msgid "Back to the login page" msgid "Back to the login page"
msgstr "Back to the login page" msgstr "Back to the login page"
#: src/components/federation/PeerManager.tsx:163 #: src/components/federation/PeerManager.tsx:184
#: src/components/federation/PeerManager.tsx:190
msgid "Block" msgid "Block"
msgstr "Block" msgstr "Block"
#: src/components/federation/PeerManager.tsx:33 #: src/components/federation/PeerManager.tsx:39
msgid "Blocked" msgid "Blocked"
msgstr "Blocked" msgstr "Blocked"
@@ -453,7 +454,7 @@ msgid "Cancel search"
msgstr "Cancel search" msgstr "Cancel search"
#: src/components/category/CategorySelect.tsx:33 #: src/components/category/CategorySelect.tsx:33
#: src/pages/Manage.tsx:38 #: src/pages/Manage.tsx:42
msgid "Categories" msgid "Categories"
msgstr "Categories" msgstr "Categories"
@@ -502,7 +503,8 @@ msgstr "Chat ({unreadChatCount} unread)"
msgid "Checking invite…" msgid "Checking invite…"
msgstr "Checking invite…" msgstr "Checking invite…"
#: src/components/federation/PeerManager.tsx:154 #: src/components/federation/PeerManager.tsx:175
#: src/components/federation/PeerManager.tsx:189
msgid "Checking…" msgid "Checking…"
msgstr "Checking…" msgstr "Checking…"
@@ -590,7 +592,7 @@ msgstr "Could not rename"
msgid "Could not save" msgid "Could not save"
msgstr "Could not save" msgstr "Could not save"
#: src/components/federation/PeerManager.tsx:146 #: src/components/federation/PeerManager.tsx:164
msgid "Couldn't add that instance" msgid "Couldn't add that instance"
msgstr "Couldn't add that instance" msgstr "Couldn't add that instance"
@@ -885,7 +887,7 @@ msgid "Failed to post reply"
msgstr "Failed to post reply" msgstr "Failed to post reply"
#: src/components/federation/GrantList.tsx:98 #: src/components/federation/GrantList.tsx:98
#: src/components/federation/PeerManager.tsx:143 #: src/components/federation/PeerManager.tsx:158
#: src/components/playlist/PlaylistEditForm.tsx:185 #: src/components/playlist/PlaylistEditForm.tsx:185
#: src/components/profile/DescriptionEditor.tsx:99 #: src/components/profile/DescriptionEditor.tsx:99
#: src/components/profile/EmailEditor.tsx:86 #: src/components/profile/EmailEditor.tsx:86
@@ -1029,7 +1031,7 @@ msgstr "In the chat"
msgid "Insert emoji" msgid "Insert emoji"
msgstr "Insert emoji" msgstr "Insert emoji"
#: src/pages/Manage.tsx:45 #: src/pages/Manage.tsx:49
msgid "Instances" msgid "Instances"
msgstr "Instances" msgstr "Instances"
@@ -1145,7 +1147,7 @@ msgstr "Loading profile…"
#: src/components/category/CategoryManager.tsx:60 #: src/components/category/CategoryManager.tsx:60
#: src/components/chat/ChatModal.tsx:763 #: src/components/chat/ChatModal.tsx:763
#: src/components/editor/EmojiPickerPopup.tsx:125 #: src/components/editor/EmojiPickerPopup.tsx:125
#: src/components/federation/PeerManager.tsx:108 #: src/components/federation/PeerManager.tsx:120
#: src/components/playlist/PlaylistMembershipPanel.tsx:45 #: src/components/playlist/PlaylistMembershipPanel.tsx:45
#: src/components/shared/ListState.tsx:38 #: src/components/shared/ListState.tsx:38
#: src/components/shared/LoadMoreButton.tsx:32 #: src/components/shared/LoadMoreButton.tsx:32
@@ -1194,8 +1196,8 @@ msgid "Lowercase letters, numbers and single hyphens only"
msgstr "Lowercase letters, numbers and single hyphens only" msgstr "Lowercase letters, numbers and single hyphens only"
#: src/components/layout/UserMenu.tsx:114 #: src/components/layout/UserMenu.tsx:114
#: src/pages/Manage.tsx:29 #: src/pages/Manage.tsx:32
#: src/pages/Manage.tsx:36 #: src/pages/Manage.tsx:40
#: src/pages/UserPublicProfile.tsx:164 #: src/pages/UserPublicProfile.tsx:164
msgid "Manage" msgid "Manage"
msgstr "Manage" msgstr "Manage"
@@ -1538,8 +1540,8 @@ msgid "Related"
msgstr "Related" msgstr "Related"
#. placeholder {0}: peer.host #. placeholder {0}: peer.host
#: src/components/federation/PeerManager.tsx:133 #: src/components/federation/PeerManager.tsx:148
#: src/components/federation/PeerManager.tsx:134 #: src/components/federation/PeerManager.tsx:149
msgid "Remove {0}" msgid "Remove {0}"
msgstr "Remove {0}" msgstr "Remove {0}"
@@ -1893,11 +1895,11 @@ msgstr "Top this week"
msgid "Track {0}" msgid "Track {0}"
msgstr "Track {0}" msgstr "Track {0}"
#: src/components/federation/PeerManager.tsx:155 #: src/components/federation/PeerManager.tsx:176
msgid "Trust" msgid "Trust"
msgstr "Trust" msgstr "Trust"
#: src/components/federation/PeerManager.tsx:32 #: src/components/federation/PeerManager.tsx:38
msgid "Trusted" msgid "Trusted"
msgstr "Trusted" msgstr "Trusted"
@@ -2032,7 +2034,15 @@ msgstr "Username"
msgid "Users" msgid "Users"
msgstr "Users" msgstr "Users"
#: src/components/federation/PeerManager.tsx:169 #: src/components/federation/PeerManager.tsx:198
msgid "Users here can't sign in to a blocked instance."
msgstr "Users here can't sign in to a blocked instance."
#: src/components/federation/PeerManager.tsx:201
msgid "Users of a trusted instance sign in here with their account there. Those of a blocked one can't, and users here can't sign in to it. Users of any other instance need an invite."
msgstr "Users of a trusted instance sign in here with their account there. Those of a blocked one can't, and users here can't sign in to it. Users of any other instance need an invite."
#: src/components/federation/PeerManager.tsx:208
msgid "Users of a trusted instance sign in here with their account there. Those of a blocked one can't. Users of any other instance need an invite." msgid "Users of a trusted instance sign in here with their account there. Those of a blocked one can't. Users of any other instance need an invite."
msgstr "Users of a trusted instance sign in here with their account there. Those of a blocked one can't. Users of any other instance need an invite." msgstr "Users of a trusted instance sign in here with their account there. Those of a blocked one can't. Users of any other instance need an invite."

View File

@@ -354,7 +354,7 @@ msgstr "Déjà recommandé par {0} {1}"
msgid "Already have an account? <0>Log in</0>" msgid "Already have an account? <0>Log in</0>"
msgstr "Vous avez déjà un compte ? <0>Se connecter</0>" msgstr "Vous avez déjà un compte ? <0>Se connecter</0>"
#: src/components/federation/PeerManager.tsx:149 #: src/components/federation/PeerManager.tsx:167
msgid "An instance's address, like music.example" msgid "An instance's address, like music.example"
msgstr "L'adresse d'une instance, comme music.example" msgstr "L'adresse d'une instance, comme music.example"
@@ -395,11 +395,12 @@ msgstr "Retour à la connexion"
msgid "Back to the login page" msgid "Back to the login page"
msgstr "Retour à la page de connexion" msgstr "Retour à la page de connexion"
#: src/components/federation/PeerManager.tsx:163 #: src/components/federation/PeerManager.tsx:184
#: src/components/federation/PeerManager.tsx:190
msgid "Block" msgid "Block"
msgstr "Bloquer" msgstr "Bloquer"
#: src/components/federation/PeerManager.tsx:33 #: src/components/federation/PeerManager.tsx:39
msgid "Blocked" msgid "Blocked"
msgstr "Bloquée" msgstr "Bloquée"
@@ -453,7 +454,7 @@ msgid "Cancel search"
msgstr "Annuler la recherche" msgstr "Annuler la recherche"
#: src/components/category/CategorySelect.tsx:33 #: src/components/category/CategorySelect.tsx:33
#: src/pages/Manage.tsx:38 #: src/pages/Manage.tsx:42
msgid "Categories" msgid "Categories"
msgstr "Catégories" msgstr "Catégories"
@@ -502,7 +503,8 @@ msgstr "{unreadChatCount} messages non lus"
msgid "Checking invite…" msgid "Checking invite…"
msgstr "Vérification de l'invitation…" msgstr "Vérification de l'invitation…"
#: src/components/federation/PeerManager.tsx:154 #: src/components/federation/PeerManager.tsx:175
#: src/components/federation/PeerManager.tsx:189
msgid "Checking…" msgid "Checking…"
msgstr "Vérification…" msgstr "Vérification…"
@@ -590,7 +592,7 @@ msgstr "Renommage impossible"
msgid "Could not save" msgid "Could not save"
msgstr "Sauvegarde impossible" msgstr "Sauvegarde impossible"
#: src/components/federation/PeerManager.tsx:146 #: src/components/federation/PeerManager.tsx:164
msgid "Couldn't add that instance" msgid "Couldn't add that instance"
msgstr "Impossible d'ajouter cette instance" msgstr "Impossible d'ajouter cette instance"
@@ -885,7 +887,7 @@ msgid "Failed to post reply"
msgstr "Impossible de publier la réponse" msgstr "Impossible de publier la réponse"
#: src/components/federation/GrantList.tsx:98 #: src/components/federation/GrantList.tsx:98
#: src/components/federation/PeerManager.tsx:143 #: src/components/federation/PeerManager.tsx:158
#: src/components/playlist/PlaylistEditForm.tsx:185 #: src/components/playlist/PlaylistEditForm.tsx:185
#: src/components/profile/DescriptionEditor.tsx:99 #: src/components/profile/DescriptionEditor.tsx:99
#: src/components/profile/EmailEditor.tsx:86 #: src/components/profile/EmailEditor.tsx:86
@@ -1029,7 +1031,7 @@ msgstr "Dans la tribune"
msgid "Insert emoji" msgid "Insert emoji"
msgstr "Insérer un emoji" msgstr "Insérer un emoji"
#: src/pages/Manage.tsx:45 #: src/pages/Manage.tsx:49
msgid "Instances" msgid "Instances"
msgstr "Instances" msgstr "Instances"
@@ -1145,7 +1147,7 @@ msgstr "Chargement du profil…"
#: src/components/category/CategoryManager.tsx:60 #: src/components/category/CategoryManager.tsx:60
#: src/components/chat/ChatModal.tsx:763 #: src/components/chat/ChatModal.tsx:763
#: src/components/editor/EmojiPickerPopup.tsx:125 #: src/components/editor/EmojiPickerPopup.tsx:125
#: src/components/federation/PeerManager.tsx:108 #: src/components/federation/PeerManager.tsx:120
#: src/components/playlist/PlaylistMembershipPanel.tsx:45 #: src/components/playlist/PlaylistMembershipPanel.tsx:45
#: src/components/shared/ListState.tsx:38 #: src/components/shared/ListState.tsx:38
#: src/components/shared/LoadMoreButton.tsx:32 #: src/components/shared/LoadMoreButton.tsx:32
@@ -1194,8 +1196,8 @@ msgid "Lowercase letters, numbers and single hyphens only"
msgstr "Minuscules, chiffres et tirets simples seulement" msgstr "Minuscules, chiffres et tirets simples seulement"
#: src/components/layout/UserMenu.tsx:114 #: src/components/layout/UserMenu.tsx:114
#: src/pages/Manage.tsx:29 #: src/pages/Manage.tsx:32
#: src/pages/Manage.tsx:36 #: src/pages/Manage.tsx:40
#: src/pages/UserPublicProfile.tsx:164 #: src/pages/UserPublicProfile.tsx:164
msgid "Manage" msgid "Manage"
msgstr "Administration" msgstr "Administration"
@@ -1538,8 +1540,8 @@ msgid "Related"
msgstr "Connexe" msgstr "Connexe"
#. placeholder {0}: peer.host #. placeholder {0}: peer.host
#: src/components/federation/PeerManager.tsx:133 #: src/components/federation/PeerManager.tsx:148
#: src/components/federation/PeerManager.tsx:134 #: src/components/federation/PeerManager.tsx:149
msgid "Remove {0}" msgid "Remove {0}"
msgstr "Retirer {0}" msgstr "Retirer {0}"
@@ -1893,11 +1895,11 @@ msgstr "Les plus votées de la semaine"
msgid "Track {0}" msgid "Track {0}"
msgstr "Piste {0}" msgstr "Piste {0}"
#: src/components/federation/PeerManager.tsx:155 #: src/components/federation/PeerManager.tsx:176
msgid "Trust" msgid "Trust"
msgstr "Approuver" msgstr "Approuver"
#: src/components/federation/PeerManager.tsx:32 #: src/components/federation/PeerManager.tsx:38
msgid "Trusted" msgid "Trusted"
msgstr "Approuvée" msgstr "Approuvée"
@@ -2032,7 +2034,15 @@ msgstr "Nom d'utilisateur"
msgid "Users" msgid "Users"
msgstr "Utilisateurs" msgstr "Utilisateurs"
#: src/components/federation/PeerManager.tsx:169 #: src/components/federation/PeerManager.tsx:198
msgid "Users here can't sign in to a blocked instance."
msgstr "Les utilisateurs d'ici ne peuvent pas se connecter à une instance bloquée."
#: src/components/federation/PeerManager.tsx:201
msgid "Users of a trusted instance sign in here with their account there. Those of a blocked one can't, and users here can't sign in to it. Users of any other instance need an invite."
msgstr "Les utilisateurs d'une instance approuvée se connectent ici avec leur compte de là-bas. Ceux d'une instance bloquée ne le peuvent pas, et les utilisateurs d'ici ne peuvent pas s'y connecter. Ceux de toute autre instance ont besoin d'une invitation."
#: src/components/federation/PeerManager.tsx:208
msgid "Users of a trusted instance sign in here with their account there. Those of a blocked one can't. Users of any other instance need an invite." msgid "Users of a trusted instance sign in here with their account there. Those of a blocked one can't. Users of any other instance need an invite."
msgstr "Les utilisateurs d'une instance approuvée se connectent ici avec leur compte de là-bas. Ceux d'une instance bloquée ne le peuvent pas. Ceux de toute autre instance ont besoin d'une invitation." msgstr "Les utilisateurs d'une instance approuvée se connectent ici avec leur compte de là-bas. Ceux d'une instance bloquée ne le peuvent pas. Ceux de toute autre instance ont besoin d'une invitation."

View File

@@ -1,6 +1,8 @@
/** /**
* Renders the list of the other gerbeur instances an admin trusts or blocks, * Renders the list of the other gerbeur instances an admin trusts or blocks,
* on `/manage`, for those who hold `federation:manage`. * on `/manage`, for those who hold `federation:manage`. Where users of other
* instances don't sign in, only blocking is left: it keeps this instance's
* users from signing in to one.
* *
* @module * @module
*/ */
@@ -11,6 +13,10 @@ import { Trans } from "@lingui/react/macro";
import { FormProvider } from "react-hook-form"; import { FormProvider } from "react-hook-form";
import { API_URL } from "../../config/api.ts"; import { API_URL } from "../../config/api.ts";
import {
FEDERATION_INCOMING,
FEDERATION_OUTGOING,
} from "../../config/instance.ts";
import type { Wire } from "../../../shared/api.ts"; import type { Wire } from "../../../shared/api.ts";
import type { import type {
FederationPeer, FederationPeer,
@@ -42,6 +48,8 @@ interface Values {
* (`PUT /api/federation/peers`), and a button that takes it off the lists * (`PUT /api/federation/peers`), and a button that takes it off the lists
* (`DELETE /api/federation/peers/:host`), then a field that adds one. The * (`DELETE /api/federation/peers/:host`), then a field that adds one. The
* server checks that it answers as a gerbeur, and the permission again. * server checks that it answers as a gerbeur, and the permission again.
* Without {@link FEDERATION_INCOMING} it lists and adds only blocked
* instances; the trusted ones are kept for when it is back.
*/ */
export function PeerManager(): React.JSX.Element { export function PeerManager(): React.JSX.Element {
const { authFetch } = useAuth(); const { authFetch } = useAuth();
@@ -101,6 +109,10 @@ export function PeerManager(): React.JSX.Element {
form.reset(); form.reset();
}); });
const shown = FEDERATION_INCOMING
? peers
: peers?.filter((peer) => peer.status === "blocked") ?? null;
return ( return (
<div className="peer-manager"> <div className="peer-manager">
{peers === null && !error && ( {peers === null && !error && (
@@ -108,9 +120,9 @@ export function PeerManager(): React.JSX.Element {
<Trans>Loading…</Trans> <Trans>Loading…</Trans>
</p> </p>
)} )}
{peers && peers.length > 0 && ( {shown && shown.length > 0 && (
<div className="profile-appearance-grid"> <div className="profile-appearance-grid">
{peers.map((peer) => ( {shown.map((peer) => (
<SettingRow <SettingRow
key={peer.issuer} key={peer.issuer}
label={ label={
@@ -121,11 +133,14 @@ export function PeerManager(): React.JSX.Element {
} }
> >
<span className="peer-controls"> <span className="peer-controls">
{FEDERATION_INCOMING && (
<Segmented <Segmented
options={STATUS_OPTIONS} options={STATUS_OPTIONS}
value={peer.status} value={peer.status}
onChange={(status) => change(() => save(peer.issuer, status))} onChange={(status) =>
change(() => save(peer.issuer, status))}
/> />
)}
<button <button
type="button" type="button"
className="btn--ghost peer-remove" className="btn--ghost peer-remove"
@@ -142,7 +157,10 @@ export function PeerManager(): React.JSX.Element {
)} )}
{error && <ErrorCard title={t`Failed to save`} message={error} />} {error && <ErrorCard title={t`Failed to save`} message={error} />}
<FormProvider {...form}> <FormProvider {...form}>
<form className="form peer-add" onSubmit={add("trusted")}> <form
className="form peer-add"
onSubmit={add(FEDERATION_INCOMING ? "trusted" : "blocked")}
>
<FormError title={t`Couldn't add that instance`} /> <FormError title={t`Couldn't add that instance`} />
<TextField<Values> <TextField<Values>
name="instance" name="instance"
@@ -151,6 +169,9 @@ export function PeerManager(): React.JSX.Element {
rules={{ required: true }} rules={{ required: true }}
/> />
<div className="peer-add-actions"> <div className="peer-add-actions">
{FEDERATION_INCOMING
? (
<>
<SubmitButton pendingLabel={<Trans>Checking…</Trans>}> <SubmitButton pendingLabel={<Trans>Checking…</Trans>}>
<Trans>Trust</Trans> <Trans>Trust</Trans>
</SubmitButton> </SubmitButton>
@@ -162,15 +183,34 @@ export function PeerManager(): React.JSX.Element {
> >
<Trans>Block</Trans> <Trans>Block</Trans>
</button> </button>
</>
)
: (
<SubmitButton pendingLabel={<Trans>Checking…</Trans>}>
<Trans>Block</Trans>
</SubmitButton>
)}
</div> </div>
</form> </form>
</FormProvider> </FormProvider>
<p className="form-hint"> <p className="form-hint">
{!FEDERATION_INCOMING
? <Trans>Users here can't sign in to a blocked instance.</Trans>
: FEDERATION_OUTGOING
? (
<Trans>
Users of a trusted instance sign in here with their account there.
Those of a blocked one can't, and users here can't sign in to it.
Users of any other instance need an invite.
</Trans>
)
: (
<Trans> <Trans>
Users of a trusted instance sign in here with their account there. Users of a trusted instance sign in here with their account there.
Those of a blocked one can't. Users of any other instance need an Those of a blocked one can't. Users of any other instance need an
invite. invite.
</Trans> </Trans>
)}
</p> </p>
</div> </div>
); );

View File

@@ -16,20 +16,24 @@ import { PageHeader } from "../components/layout/PageHeader.tsx";
import { PageGroup } from "../components/layout/PageGroup.tsx"; import { PageGroup } from "../components/layout/PageGroup.tsx";
import { CategoryManager } from "../components/category/CategoryManager.tsx"; import { CategoryManager } from "../components/category/CategoryManager.tsx";
import { PeerManager } from "../components/federation/PeerManager.tsx"; import { PeerManager } from "../components/federation/PeerManager.tsx";
import { FEDERATION_INCOMING } from "../config/instance.ts"; import {
FEDERATION_INCOMING,
FEDERATION_OUTGOING,
} from "../config/instance.ts";
import { NotFound } from "./NotFound.tsx"; import { NotFound } from "./NotFound.tsx";
/** /**
* Renders the category manager and the instances the site trusts or blocks, * Renders the category manager and the instances the site trusts or blocks,
* each to those who hold its permission, which the API checks again, the * each to those who hold its permission, which the API checks again, the
* instances only while users of other instances may sign in here; without * instances only while federated sign-in goes at least one way; without
* either, the page isn't found. * either, the page isn't found.
*/ */
export function Manage(): React.JSX.Element { export function Manage(): React.JSX.Element {
useDocumentTitle(t`Manage`); useDocumentTitle(t`Manage`);
const { user } = useRequiredAuth(); const { user } = useRequiredAuth();
const categories = can(user, "category:manage"); const categories = can(user, "category:manage");
const instances = FEDERATION_INCOMING && can(user, "federation:manage"); const instances = (FEDERATION_INCOMING || FEDERATION_OUTGOING) &&
can(user, "federation:manage");
if (!categories && !instances) return <NotFound />; if (!categories && !instances) return <NotFound />;
return ( return (
<PageShell narrow> <PageShell narrow>

View File

@@ -1,4 +1,4 @@
import { describe, expect, test } from "vitest"; import { afterEach, describe, expect, test, vi } from "vitest";
import { screen, waitFor, within } from "@testing-library/react"; import { screen, waitFor, within } from "@testing-library/react";
import { userEvent } from "@testing-library/user-event"; import { userEvent } from "@testing-library/user-event";
import { Manage } from "../../pages/Manage.tsx"; import { Manage } from "../../pages/Manage.tsx";
@@ -6,6 +6,20 @@ import { calls, fail, route } from "../fetch.ts";
import { makeCategory, makeSession, wire } from "../fixtures.ts"; import { makeCategory, makeSession, wire } from "../fixtures.ts";
import { renderApp } from "../render.tsx"; import { renderApp } from "../render.tsx";
const instance = vi.hoisted(() => ({
federationIncoming: true,
federationOutgoing: true,
}));
vi.mock("../../config/instance.ts", async (importOriginal) => ({
...await importOriginal<typeof import("../../config/instance.ts")>(),
get FEDERATION_INCOMING() {
return instance.federationIncoming;
},
get FEDERATION_OUTGOING() {
return instance.federationOutgoing;
},
}));
const admin = makeSession({ id: "ann", username: "ann", role: "admin" }); const admin = makeSession({ id: "ann", username: "ann", role: "admin" });
function open(session = admin) { function open(session = admin) {
@@ -16,6 +30,11 @@ function open(session = admin) {
} }
describe("Manage", () => { describe("Manage", () => {
afterEach(() => {
instance.federationIncoming = true;
instance.federationOutgoing = true;
});
test("an admin manages categories", async () => { test("an admin manages categories", async () => {
const music = makeCategory({ id: "c1", slug: "music", name: "Music" }); const music = makeCategory({ id: "c1", slug: "music", name: "Music" });
let list = [music]; let list = [music];
@@ -128,6 +147,52 @@ describe("Manage", () => {
); );
}); });
test("where users of other instances don't sign in, an admin only blocks instances", async () => {
instance.federationIncoming = false;
route("GET /api/categories", () => []);
const peer = (name: string, host: string, status: string) => ({
issuer: `https://${host}`,
host,
name,
status,
createdAt: new Date().toISOString(),
});
route("GET /api/federation/peers", () => [
peer("Films", "films.example", "trusted"),
peer("Spam", "spam.example", "blocked"),
]);
route(
"PUT /api/federation/peers",
() => peer("Music", "music.example", "blocked"),
);
const user = open();
expect(await screen.findByText("Spam")).toBeInTheDocument();
expect(screen.queryByText("Films")).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Trust" })).not
.toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Blocked" })).not
.toBeInTheDocument();
await user.type(
screen.getByPlaceholderText("An instance's address, like music.example"),
"music.example{Enter}",
);
await waitFor(() =>
expect(calls("PUT /api/federation/peers")[0]?.body).toEqual({
instance: "music.example",
status: "blocked",
})
);
});
test("without federated sign-in either way, there are no instances to manage", async () => {
instance.federationIncoming = false;
instance.federationOutgoing = false;
route("GET /api/categories", () => []);
open();
expect(await screen.findByText("Categories")).toBeInTheDocument();
expect(screen.queryByText("Instances")).not.toBeInTheDocument();
});
test("without category:manage, the page isn't found", async () => { test("without category:manage, the page isn't found", async () => {
open(makeSession({ id: "mod", role: "moderator" })); open(makeSession({ id: "mod", role: "moderator" }));
expect(await screen.findByText("This page does not exist.")) expect(await screen.findByText("This page does not exist."))