diff --git a/src/app/api/settings/require-login/route.ts b/src/app/api/settings/require-login/route.ts index 29dfa6b128..547ac96859 100644 --- a/src/app/api/settings/require-login/route.ts +++ b/src/app/api/settings/require-login/route.ts @@ -8,9 +8,15 @@ export async function GET() { try { const settings = await getSettings(); const requireLogin = settings.requireLogin !== false; - return NextResponse.json({ requireLogin }); + const hasPassword = !!settings.password || !!process.env.INITIAL_PASSWORD; + const setupComplete = !!settings.setupComplete; + return NextResponse.json({ requireLogin, hasPassword, setupComplete }); } catch (error) { - return NextResponse.json({ requireLogin: true }, { status: 200 }); + console.error("[API] Error fetching require-login settings:", error); + return NextResponse.json( + { requireLogin: true, hasPassword: true, setupComplete: true }, + { status: 200 } + ); } } diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index cac7cc8c82..f5cb661c31 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -24,7 +24,7 @@ export default function LoginPage() { const baseUrl = typeof window !== "undefined" ? window.location.origin : ""; try { - const res = await fetch(`${baseUrl}/api/settings`, { + const res = await fetch(`${baseUrl}/api/settings/require-login`, { signal: controller.signal, }); clearTimeout(timeoutId); diff --git a/tests/unit/login-bootstrap-route.test.mjs b/tests/unit/login-bootstrap-route.test.mjs new file mode 100644 index 0000000000..fc56cbd2ab --- /dev/null +++ b/tests/unit/login-bootstrap-route.test.mjs @@ -0,0 +1,83 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-login-bootstrap-")); +process.env.DATA_DIR = TEST_DATA_DIR; + +const core = await import("../../src/lib/db/core.ts"); +const settingsDb = await import("../../src/lib/db/settings.ts"); +const route = await import("../../src/app/api/settings/require-login/route.ts"); + +async function resetStorage() { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); + fs.mkdirSync(TEST_DATA_DIR, { recursive: true }); +} + +test.beforeEach(async () => { + delete process.env.INITIAL_PASSWORD; + await resetStorage(); +}); + +test.after(() => { + delete process.env.INITIAL_PASSWORD; + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); +}); + +test("public login bootstrap route exposes the metadata the login page consumes", async () => { + await settingsDb.updateSettings({ + requireLogin: true, + setupComplete: true, + }); + + const response = await route.GET(); + const body = await response.json(); + + assert.equal(response.status, 200); + assert.deepEqual(body, { + requireLogin: true, + hasPassword: false, + setupComplete: true, + }); +}); + +test("public login bootstrap route reports env-provided bootstrap password metadata", async () => { + process.env.INITIAL_PASSWORD = "bootstrap-secret"; + + await settingsDb.updateSettings({ + requireLogin: true, + setupComplete: true, + }); + + const response = await route.GET(); + const body = await response.json(); + + assert.equal(response.status, 200); + assert.deepEqual(body, { + requireLogin: true, + hasPassword: true, + setupComplete: true, + }); +}); + +test("public login bootstrap route reports stored password metadata and disabled auth state", async () => { + await settingsDb.updateSettings({ + requireLogin: false, + password: "hashed-password", + setupComplete: true, + }); + + const response = await route.GET(); + const body = await response.json(); + + assert.equal(response.status, 200); + assert.deepEqual(body, { + requireLogin: false, + hasPassword: true, + setupComplete: true, + }); +});