From a8cc0c40dfaa7eecdd415b90bc237a57d9b1f1ba Mon Sep 17 00:00:00 2001 From: Daniel Date: Sun, 13 Sep 2026 15:31:50 +0200 Subject: [PATCH] fix: Sign in goes to the provider, not to a box containing one button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The landing page's Sign in opened a modal whose entire content was a single "Sign in with PedsHub SSO" link. That is a step that exists to be clicked through. Every sign-in control on the page — the header, the hero, the closing call to action — now goes straight to /api/auth/sso/login when the site is SSO-only. The modal is still built and still opens on a site that has a password door, which is the only thing it was ever for. /login is deliberately left as it is. It renders the one button rather than redirecting, because it is also where the provider sends somebody back when sign-in fails — ?error=sso_failed — and a page that redirected on sight would bounce them into the provider again, forever. Verified live: one click from the landing page lands on sso.pedshub.com's flow with the client id, callback, scope, state and nonce, and no modal is rendered on the way. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01TqXevQJhxFrM7jJg82cgZN --- frontend/src/pages/LandingPage.jsx | 26 +++++++++++++++++++++---- frontend/src/pages/LandingPage.test.jsx | 26 +++++++++++++++++-------- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/frontend/src/pages/LandingPage.jsx b/frontend/src/pages/LandingPage.jsx index b5ebb96..a7070b3 100644 --- a/frontend/src/pages/LandingPage.jsx +++ b/frontend/src/pages/LandingPage.jsx @@ -596,13 +596,31 @@ export default function LandingPage() { //: One door. Accounts are made at the identity provider, so there is no //: second mode for this to be in. const [authOpen, setAuthOpen] = useState(false) + //: Where "Sign in" goes. On a site whose only way in is the provider there + //: is nothing to ask first, so the button is the provider's door rather than + //: a box containing one button — a modal whose entire content is a link is a + //: step that exists to be clicked through. + const [ssoOnly, setSsoOnly] = useState(false) + useEffect(() => { + let live = true + api.get('/auth/sso/config') + .then(res => { + if (live) setSsoOnly(res.data?.sso_enabled === true && res.data?.sso_only === true) + }) + .catch(() => {}) + return () => { live = false } + }, []) + const signIn = () => { + if (ssoOnly) window.location.href = '/api/auth/sso/login' + else setAuthOpen(true) + } return (
- {authOpen && setAuthOpen(false)} />} + {authOpen && !ssoOnly && setAuthOpen(false)} />} - setAuthOpen(true)} /> + {/* ── Hero ───────────────────────────────────────────────────────────── */}
@@ -627,7 +645,7 @@ export default function LandingPage() { {/* One button. "Create an account" led to a form that no longer exists — an account is made by following an invitation from the identity provider, not from here. */} - + }
@@ -716,7 +734,7 @@ export default function LandingPage() { 🏥 PedsHub© {new Date().getFullYear()}
- + Clinical Tools
diff --git a/frontend/src/pages/LandingPage.test.jsx b/frontend/src/pages/LandingPage.test.jsx index a16b0cc..ad724ac 100644 --- a/frontend/src/pages/LandingPage.test.jsx +++ b/frontend/src/pages/LandingPage.test.jsx @@ -141,9 +141,16 @@ describe('the auth modal', () => { expect(screen.queryByRole('form', { name: 'Sign in' })).not.toBeInTheDocument() }) - it('shows the provider and nothing else on an SSO-only site', async () => { - // And nothing at all before the answer arrives: drawing the email form - // and then replacing it is a flash of a way in that does not exist. + it('goes straight to the provider on an SSO-only site, with no modal', async () => { + // A box whose entire content is one link is a step that exists to be + // clicked through. There is nothing to ask first when the provider is the + // only way in. + const go = vi.fn() + const real = Object.getOwnPropertyDescriptor(window, 'location') + Object.defineProperty(window, 'location', { + configurable: true, + value: { get href() { return '' }, set href(v) { go(v) } }, + }) api.get.mockImplementation(url => Promise.resolve({ data: url === '/public/stats' ? STATS : url === '/auth/sso/config' @@ -151,10 +158,13 @@ describe('the auth modal', () => { : {}, })) mount() - await userEvent.click(screen.getByRole('button', { name: 'Open login' })) - expect(await screen.findByRole('link', { name: /Sign in with PedsHub SSO/ })) - .toHaveAttribute('href', '/api/auth/sso/login') - expect(screen.queryByRole('form', { name: 'Sign in' })).toBeNull() - expect(screen.queryByText(/signs in through/)).toBeNull() + const open = await screen.findByRole('button', { name: 'Open login' }) + // Given a tick for the config to arrive before the click is judged. + await waitFor(() => expect(api.get).toHaveBeenCalledWith('/auth/sso/config')) + await userEvent.click(open) + await waitFor(() => expect(go).toHaveBeenCalledWith('/api/auth/sso/login')) + expect(screen.queryByRole('button', { name: 'Close' })).toBeNull() + if (real) Object.defineProperty(window, 'location', real) }) + })