diff --git a/docs/buzz-help.md b/docs/buzz-help.md index 964380f44c..56e3c3700c 100644 --- a/docs/buzz-help.md +++ b/docs/buzz-help.md @@ -72,6 +72,9 @@ the interface unless an available tool actually supports that action. unavailable until you sign in. Sign-in, sign-out and when the current sign-in ends also live under **Settings → Buzz → Identity → Organization sign-in**. +- When a community requires organization sign-in, the sidebar or join dialog + explains why and offers **Sign in with** the organization’s name or server + address. Complete sign-in to reconnect; in the join dialog, choose **Continue**. - Signed in to more than one organization server? **Organization sign-in** lists each one you're signed in to, with the communities it's used by, and each **Sign out** ends only that server's sign-in. Your organization's own diff --git a/src/app/shell/PrimarySidebar.sign-in.test.tsx b/src/app/shell/PrimarySidebar.sign-in.test.tsx index 4334c97142..b60d433e54 100644 --- a/src/app/shell/PrimarySidebar.sign-in.test.tsx +++ b/src/app/shell/PrimarySidebar.sign-in.test.tsx @@ -60,11 +60,18 @@ it("offers explicit sign-in when the live socket refuses a protected community", const h = await refusedLive("Federated sign-in is required"); // The cached channel list stays; nothing signs in on its own. expect(screen.getByRole("button", { name: "Chats" })).toBeVisible(); + expect( + await screen.findByText( + "This community requires builderlab.example sign-in.", + ), + ).toBeVisible(); expect(h.identity.service.signIn).not.toHaveBeenCalled(); expect(h.connects()).toBe(1); - await userEvent - .setup() - .click(await screen.findByRole("button", { name: "Sign in" })); + await userEvent.setup().click( + await screen.findByRole("button", { + name: "Sign in with builderlab.example", + }), + ); expect(h.identity.service.signIn).toHaveBeenCalledOnce(); // A completed sign-in reconnects the live socket with the new session. await waitFor(() => expect(h.connects()).toBe(2)); @@ -76,7 +83,9 @@ it.each([ ])("shows %s recovery from the live socket", async (refusal, text) => { await refusedLive(refusal); expect(await screen.findByRole("alert")).toHaveTextContent(text); - expect(screen.queryByRole("button", { name: "Sign in" })).toBeNull(); + expect( + screen.queryByRole("button", { name: "Sign in with builderlab.example" }), + ).toBeNull(); }); it.each(["adapter-refused", "grant-refused", "invalid-response"])( @@ -87,7 +96,9 @@ it.each(["adapter-refused", "grant-refused", "invalid-response"])( "Restart Buzz to try again", ); expect(screen.getByRole("button", { name: "Chats" })).toBeVisible(); - expect(screen.queryByRole("button", { name: "Sign in" })).toBeNull(); + expect( + screen.queryByRole("button", { name: "Sign in with builderlab.example" }), + ).toBeNull(); expect(h.connects()).toBe(1); }, ); diff --git a/src/features/communities/CommunityDialog.test.tsx b/src/features/communities/CommunityDialog.test.tsx index abbc575532..649a9a5627 100644 --- a/src/features/communities/CommunityDialog.test.tsx +++ b/src/features/communities/CommunityDialog.test.tsx @@ -680,13 +680,22 @@ it("offers federated sign-in when joining refuses for it, then lets Continue run "wss://relay.example", ); await user.click(screen.getByRole("button", { name: "Continue" })); - const signIn = await screen.findByRole("button", { name: "Sign in" }); + expect( + await screen.findByText( + "This community requires builderlab.example sign-in.", + ), + ).toBeVisible(); + const signIn = await screen.findByRole("button", { + name: "Sign in with builderlab.example", + }); refuse = false; const requests = vi.mocked(fetch).mock.calls.length; await user.click(signIn); expect(h.service.signIn).toHaveBeenCalledOnce(); await waitFor(() => - expect(screen.queryByRole("button", { name: "Sign in" })).toBeNull(), + expect( + screen.queryByRole("button", { name: "Sign in with builderlab.example" }), + ).toBeNull(), ); // Completing login replays nothing captured before it. expect(vi.mocked(fetch).mock.calls.length).toBe(requests); @@ -748,7 +757,11 @@ it.each(["edit", "close"])( const field = screen.getByLabelText("Community URL"); await user.type(field, "wss://old.example"); await user.click(screen.getByRole("button", { name: "Continue" })); - await user.click(await screen.findByRole("button", { name: "Sign in" })); + await user.click( + await screen.findByRole("button", { + name: "Sign in with builderlab.example", + }), + ); if (change === "close") view.unmount(); else { await user.clear(field); diff --git a/src/features/federated-identity/CommunitySignIn.test.tsx b/src/features/federated-identity/CommunitySignIn.test.tsx index bf27211aa7..de3d423b44 100644 --- a/src/features/federated-identity/CommunitySignIn.test.tsx +++ b/src/features/federated-identity/CommunitySignIn.test.tsx @@ -27,22 +27,32 @@ it.each(["storage-unavailable", "storage-denied"])( expect(h.service.signIn).not.toHaveBeenCalled(); }, ); -it("requires explicit sign-in before retrying protected community admission", async () => { - const h = federatedFixture(); - const retry = vi.fn(); - render( - , - ); - expect(h.service.signIn).not.toHaveBeenCalled(); - await userEvent - .setup() - .click(screen.getByRole("button", { name: "Sign in" })); - expect(retry).toHaveBeenCalledOnce(); -}); +it.each([null, "Builderlab"])( + "names server %s and requires explicit sign-in before retrying admission", + async (organization) => { + const h = federatedFixture(); + const authority = h.service.snapshot().authority; + if (!authority) throw new Error("Fixture authority missing"); + h.publish({ authority: { ...authority, organization } }); + const server = organization ?? "builderlab.example"; + const retry = vi.fn(); + render( + , + ); + expect( + screen.getByText(`This community requires ${server} sign-in.`), + ).toBeVisible(); + expect(h.service.signIn).not.toHaveBeenCalled(); + await userEvent + .setup() + .click(screen.getByRole("button", { name: `Sign in with ${server}` })); + expect(retry).toHaveBeenCalledOnce(); + }, +); it.each(["binding-mismatch", "authorization-denied"])( "explains %s without a session to switch", (failure) => { diff --git a/src/features/federated-identity/CommunitySignIn.tsx b/src/features/federated-identity/CommunitySignIn.tsx index 12274bb6ab..4474794351 100644 --- a/src/features/federated-identity/CommunitySignIn.tsx +++ b/src/features/federated-identity/CommunitySignIn.tsx @@ -129,8 +129,10 @@ function Action({ ) return null; if (state.status === "signed-in") return null; + const server = serverName(state.authority); return ( <> +

This community requires {server} sign-in.

{(failure || state.failure) && (