fix(auth): harden OIDC browser transactions
This commit is contained in:
@@ -84,6 +84,43 @@ describe("LoginPage", () => {
|
||||
expect(screen.getByRole("button", { name: /single sign-on/i })).toBeEnabled();
|
||||
});
|
||||
|
||||
test("routes every rendered SSO affordance through the held logout barrier", async () => {
|
||||
let releaseLogout!: () => void;
|
||||
let logoutStarted!: () => void;
|
||||
const logoutGate = new Promise<void>((resolve) => { releaseLogout = resolve; });
|
||||
const logoutRequest = new Promise<void>((resolve) => { logoutStarted = resolve; });
|
||||
server.use(http.post("/api/auth/logout", async () => {
|
||||
logoutStarted();
|
||||
await logoutGate;
|
||||
return new HttpResponse(null, { status: 204 });
|
||||
}));
|
||||
setAuthState({ ...authenticated, subject: "user-a" });
|
||||
const logoutPromise = authApi.logout();
|
||||
await logoutRequest;
|
||||
|
||||
const navigate = vi.fn();
|
||||
const beginOidcLogin = authApi.beginOidcLogin;
|
||||
const begin = vi.spyOn(authApi, "beginOidcLogin")
|
||||
.mockImplementation(() => beginOidcLogin(navigate));
|
||||
try {
|
||||
render(<LoginPage config={oidcConfig} onAuthenticated={vi.fn()} />);
|
||||
const buttons = screen.getAllByRole("button", { name: /single sign-on/i });
|
||||
expect(screen.queryAllByRole("link", { name: /single sign-on/i })).toHaveLength(0);
|
||||
expect(buttons).toHaveLength(1);
|
||||
|
||||
await userEvent.click(buttons[0]);
|
||||
await Promise.resolve();
|
||||
expect(begin).toHaveBeenCalledOnce();
|
||||
expect(navigate).not.toHaveBeenCalled();
|
||||
|
||||
releaseLogout();
|
||||
await Promise.all([logoutPromise, vi.waitFor(() => expect(navigate).toHaveBeenCalledWith("/api/auth/oidc/login"))]);
|
||||
} finally {
|
||||
releaseLogout();
|
||||
begin.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
test("does not dispatch local login until an in-flight logout response settles", async () => {
|
||||
let releaseLogout!: () => void;
|
||||
let logoutStarted!: () => void;
|
||||
|
||||
@@ -150,16 +150,6 @@ export function LoginPage({ config, onAuthenticated, onRetry }: LoginPageProps)
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{config.oidcLogin && (
|
||||
<a
|
||||
href="/api/auth/oidc/login"
|
||||
className="mt-4 inline-flex h-10 w-full items-center justify-center gap-2 rounded-md border border-border bg-card px-4 text-sm font-semibold shadow-xs outline-none transition-colors hover:bg-muted focus-visible:ring-3 focus-visible:ring-ring/25"
|
||||
>
|
||||
Continue with single sign-on
|
||||
<ArrowRight aria-hidden="true" className="size-4" />
|
||||
</a>
|
||||
)}
|
||||
|
||||
{!localLogin && !config.oidcLogin && (
|
||||
<p role="status" className="rounded-md border border-border bg-muted/40 p-3 text-sm text-muted-foreground">
|
||||
No browser sign-in method is enabled for this installation.
|
||||
|
||||
Reference in New Issue
Block a user