diff --git a/src/__tests__/pages/auth/signin.test.jsx b/src/__tests__/pages/auth/signin.test.jsx
index 7d016e8a0..2f699b5f9 100644
--- a/src/__tests__/pages/auth/signin.test.jsx
+++ b/src/__tests__/pages/auth/signin.test.jsx
@@ -3,10 +3,11 @@
import { render, screen, waitFor } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
-const { getSettingsMock, authOptionsMock, signInMock, routerQuery } = vi.hoisted(() => ({
+const { getSettingsMock, authOptionsMock, signInMock, replaceMock, routerQuery } = vi.hoisted(() => ({
getSettingsMock: vi.fn(),
authOptionsMock: vi.fn(),
signInMock: vi.fn(),
+ replaceMock: vi.fn(),
routerQuery: {},
}));
@@ -23,6 +24,7 @@ vi.mock("pages/api/auth/[...nextauth]", () => ({
vi.mock("next/router", () => ({
useRouter: () => ({
query: routerQuery,
+ replace: replaceMock,
}),
}));
@@ -37,6 +39,7 @@ describe("pages/auth/signin", () => {
beforeEach(() => {
vi.clearAllMocks();
Object.keys(routerQuery).forEach((key) => delete routerQuery[key]);
+ window.sessionStorage.clear();
});
it("renders an error state when no providers are configured", async () => {
@@ -106,11 +109,14 @@ describe("pages/auth/signin", () => {
expect(screen.getByRole("button", { name: /login via homepage oidc/i })).toBeInTheDocument();
});
- it("renders the button when the server disabled auto-login", () => {
- render();
+ it("stops auto-login and hands back the page when the session never sticks", () => {
+ routerQuery.callbackUrl = "/some/page";
- expect(signInMock).not.toHaveBeenCalled();
- expect(screen.getByRole("button", { name: /login via homepage oidc/i })).toBeInTheDocument();
+ render();
+ render();
+
+ expect(signInMock).toHaveBeenCalledTimes(1);
+ expect(replaceMock).toHaveBeenCalledWith("/auth/signin?autologin=0&callbackUrl=%2Fsome%2Fpage");
});
it("does not auto-login the password provider", () => {
@@ -173,17 +179,6 @@ describe("pages/auth/signin", () => {
vi.unstubAllEnvs();
});
- it("getServerSideProps disables auto-login while an attempt is pending", async () => {
- authOptionsMock.mockReturnValueOnce({ providers: [] });
- getSettingsMock.mockReturnValueOnce({ theme: "dark" });
- vi.stubEnv("HOMEPAGE_OIDC_AUTO_LOGIN", "true");
-
- const res = await getServerSideProps({ req: { cookies: { "homepage-autologin-attempt": "1" } } });
-
- expect(res.props.autoLogin).toBe(false);
- vi.unstubAllEnvs();
- });
-
it("getServerSideProps falls back to no providers when auth options fail to load", async () => {
const consoleError = vi.spyOn(console, "error").mockImplementation(() => {});
authOptionsMock.mockImplementationOnce(() => {
diff --git a/src/pages/auth/signin.jsx b/src/pages/auth/signin.jsx
index d3125e38d..0fd4afcbd 100644
--- a/src/pages/auth/signin.jsx
+++ b/src/pages/auth/signin.jsx
@@ -7,8 +7,8 @@ import { BiShieldQuarter } from "react-icons/bi";
import { getSettings } from "utils/config/config";
const PUBLIC_SIGN_IN_SETTINGS = ["theme", "color", "title", "background", "backgroundOpacity"];
-const AUTO_LOGIN_COOKIE = "homepage-autologin-attempt";
-const AUTO_LOGIN_RETRY_SECONDS = 10;
+const AUTO_LOGIN_KEY = "homepage-autologin-attempt";
+const AUTO_LOGIN_RETRY_MS = 10000;
export default function SignIn({ providers, settings, autoLogin }) {
const router = useRouter();
@@ -33,12 +33,21 @@ export default function SignIn({ providers, settings, autoLogin }) {
useEffect(() => {
if (!redirecting) return;
- // getServerSideProps drops autoLogin while this is set, so a bounce loop falls back to the button
- const secure = window.location.protocol === "https:" ? "; secure" : "";
- document.cookie = `${AUTO_LOGIN_COOKIE}=1; path=/auth; max-age=${AUTO_LOGIN_RETRY_SECONDS}; samesite=lax${secure}`;
+ let lastAttempt = 0;
+ try {
+ lastAttempt = Number(window.sessionStorage.getItem(AUTO_LOGIN_KEY)) || 0;
+ window.sessionStorage.setItem(AUTO_LOGIN_KEY, String(Date.now()));
+ } catch {
+ // sessionStorage throws when site data is blocked, fall through and redirect anyway
+ }
+ // Getting here quickly means the session never stuck, hand it back to the user
+ if (Date.now() - lastAttempt < AUTO_LOGIN_RETRY_MS) {
+ router.replace(`/auth/signin?autologin=0&callbackUrl=${encodeURIComponent(callbackUrl)}`);
+ return;
+ }
signIn(oidcProvider.id, { callbackUrl });
- }, [redirecting, oidcProvider, callbackUrl]);
+ }, [redirecting, oidcProvider, callbackUrl, router]);
let backgroundImage = "";
let opacity = settings?.backgroundOpacity ?? 0;
@@ -243,14 +252,7 @@ export async function getServerSideProps(context) {
homepageSettings[key],
]),
);
- // A pending attempt means the previous redirect never established a session
- const autoLoginAttempted = context.req?.cookies?.[AUTO_LOGIN_COOKIE] === "1";
-
return {
- props: {
- providers,
- settings,
- autoLogin: process.env.HOMEPAGE_OIDC_AUTO_LOGIN === "true" && !autoLoginAttempted,
- },
+ props: { providers, settings, autoLogin: process.env.HOMEPAGE_OIDC_AUTO_LOGIN === "true" },
};
}