From 9654ba2c13e97aadd14af5af2bb662fd885fae2a Mon Sep 17 00:00:00 2001
From: DanielWalnut <45447813+hetaoBackend@users.noreply.github.com>
Date: Sun, 28 Jun 2026 16:10:44 +0800
Subject: [PATCH] fix auth setup redirects (#3844)
---
backend/app/gateway/routers/auth.py | 7 +-
backend/tests/test_initialize_admin.py | 17 +++-
frontend/src/app/(auth)/login/page.tsx | 88 +++++++++++++++------
frontend/src/app/(auth)/setup/page.tsx | 17 ++--
frontend/src/core/auth/setup.ts | 34 ++++++++
frontend/src/core/i18n/locales/en-US.ts | 4 +
frontend/src/core/i18n/locales/types.ts | 3 +
frontend/src/core/i18n/locales/zh-CN.ts | 4 +
frontend/tests/unit/core/auth/setup.test.ts | 78 ++++++++++++++++++
9 files changed, 223 insertions(+), 29 deletions(-)
create mode 100644 frontend/src/core/auth/setup.ts
create mode 100644 frontend/tests/unit/core/auth/setup.test.ts
diff --git a/backend/app/gateway/routers/auth.py b/backend/app/gateway/routers/auth.py
index 9c5fdf18c..a456fd7e7 100644
--- a/backend/app/gateway/routers/auth.py
+++ b/backend/app/gateway/routers/auth.py
@@ -513,7 +513,12 @@ async def initialize_admin(request: Request, response: Response, body: Initializ
try:
user = await get_local_provider().create_user(email=body.email, password=body.password, system_role="admin", needs_setup=False)
except ValueError:
- # DB unique-constraint race: another concurrent request beat us.
+ admin_count = await get_local_provider().count_admin_users()
+ if admin_count == 0:
+ raise HTTPException(
+ status_code=status.HTTP_400_BAD_REQUEST,
+ detail=AuthErrorResponse(code=AuthErrorCode.EMAIL_ALREADY_EXISTS, message="Email already registered").model_dump(),
+ )
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail=AuthErrorResponse(code=AuthErrorCode.SYSTEM_ALREADY_INITIALIZED, message="System already initialized").model_dump(),
diff --git a/backend/tests/test_initialize_admin.py b/backend/tests/test_initialize_admin.py
index 514ee6df3..841dfc88c 100644
--- a/backend/tests/test_initialize_admin.py
+++ b/backend/tests/test_initialize_admin.py
@@ -110,6 +110,21 @@ def test_initialize_register_does_not_block_initialization(client):
assert resp.json()["system_role"] == "admin"
+def test_initialize_existing_regular_user_email_reports_email_conflict(client):
+ """With no admin, reusing a regular user's email is an email conflict, not initialized."""
+ client.post("/api/v1/auth/register", json={"email": "regular@example.com", "password": "Tr0ub4dor3a"})
+
+ resp = client.post(
+ "/api/v1/auth/initialize",
+ json={**_init_payload(), "email": "regular@example.com"},
+ )
+
+ assert resp.status_code == 400
+ body = resp.json()
+ assert body["detail"]["code"] == "email_already_exists"
+ assert client.get("/api/v1/auth/setup-status").json()["needs_setup"] is True
+
+
# ── Endpoint is public (no cookie required) ───────────────────────────────
@@ -162,7 +177,7 @@ def test_setup_status_after_initialization(client):
assert resp.json()["needs_setup"] is False
-def test_setup_status_false_when_only_regular_user_exists(client):
+def test_setup_status_true_when_only_regular_user_exists(client):
"""setup-status returns needs_setup=True even when regular users exist (no admin)."""
client.post("/api/v1/auth/register", json={"email": "regular@example.com", "password": "Tr0ub4dor3a"})
resp = client.get("/api/v1/auth/setup-status")
diff --git a/frontend/src/app/(auth)/login/page.tsx b/frontend/src/app/(auth)/login/page.tsx
index 7971331b4..3d6c63e55 100644
--- a/frontend/src/app/(auth)/login/page.tsx
+++ b/frontend/src/app/(auth)/login/page.tsx
@@ -9,6 +9,11 @@ import { Button } from "@/components/ui/button";
import { FlickeringGrid } from "@/components/ui/flickering-grid";
import { Input } from "@/components/ui/input";
import { useAuth } from "@/core/auth/AuthProvider";
+import {
+ canCreateRegularAccount,
+ fetchSetupStatus,
+ type SetupStatusResponse,
+} from "@/core/auth/setup";
import { parseAuthError } from "@/core/auth/types";
import { useI18n } from "@/core/i18n/hooks";
@@ -58,6 +63,10 @@ export default function LoginPage() {
const [ssoProviders, setSsoProviders] = useState<
{ id: string; display_name: string; type: string }[]
>([]);
+ const [setupStatus, setSetupStatus] = useState
{t.login.adminSetupRequiredTitle}
++ {t.login.adminSetupRequiredDescription} +
+ + {t.login.createAdminAccount} + +