feat: public setup-status probe, bootstrap admin no longer throws on empty store
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterAll, describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("server-only", () => ({}));
|
||||
|
||||
const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "evosci-setup-status-"));
|
||||
const ORIGINAL_ENV = {
|
||||
EVOSCIENTIST_DATA_DIR: process.env.EVOSCIENTIST_DATA_DIR,
|
||||
WEBUI_AUTH_ENABLED: process.env.WEBUI_AUTH_ENABLED,
|
||||
WEBUI_AUTH_SECRET: process.env.WEBUI_AUTH_SECRET,
|
||||
WEBUI_AUTH_USERNAME: process.env.WEBUI_AUTH_USERNAME,
|
||||
WEBUI_AUTH_PASSWORD: process.env.WEBUI_AUTH_PASSWORD,
|
||||
};
|
||||
|
||||
process.env.EVOSCIENTIST_DATA_DIR = dataDir;
|
||||
process.env.WEBUI_AUTH_ENABLED = "true";
|
||||
process.env.WEBUI_AUTH_SECRET = "test-secret-with-at-least-32-characters";
|
||||
delete process.env.WEBUI_AUTH_USERNAME;
|
||||
delete process.env.WEBUI_AUTH_PASSWORD;
|
||||
|
||||
const { GET } = await import("./route");
|
||||
const { closeUserStoreForTests, countUsers } = await import(
|
||||
"@/lib/server/userStore"
|
||||
);
|
||||
const { verifyCredentials } = await import("@/lib/server/auth");
|
||||
|
||||
afterAll(() => {
|
||||
closeUserStoreForTests();
|
||||
for (const [key, value] of Object.entries(ORIGINAL_ENV)) {
|
||||
if (value === undefined) delete process.env[key];
|
||||
else process.env[key] = value;
|
||||
}
|
||||
fs.rmSync(dataDir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
describe("GET /api/auth/setup-status", () => {
|
||||
it("reports needsSetup when the store is empty and no env seeding is set", async () => {
|
||||
const res = await GET();
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get("cache-control")).toBe("no-store");
|
||||
expect(await res.json()).toEqual({ needsSetup: true });
|
||||
});
|
||||
|
||||
it("no longer throws on the auth path with an empty store and no env", () => {
|
||||
expect(verifyCredentials("nobody", "wrong-password")).toBeNull();
|
||||
});
|
||||
|
||||
it("seeds from env on the first probe and then reports needsSetup false", async () => {
|
||||
process.env.WEBUI_AUTH_USERNAME = "env-admin";
|
||||
process.env.WEBUI_AUTH_PASSWORD = "env-password-123";
|
||||
const res = await GET();
|
||||
expect(await res.json()).toEqual({ needsSetup: false });
|
||||
expect(countUsers()).toBe(1);
|
||||
expect(verifyCredentials("env-admin", "env-password-123")).not.toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { countUsers, ensureBootstrapAdmin } from "@/lib/server/userStore";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
const NO_STORE = { "Cache-Control": "no-store" };
|
||||
|
||||
export async function GET() {
|
||||
ensureBootstrapAdmin();
|
||||
return NextResponse.json(
|
||||
{ needsSetup: countUsers() === 0 },
|
||||
{ headers: NO_STORE }
|
||||
);
|
||||
}
|
||||
@@ -210,17 +210,13 @@ export function verifyUserPassword(
|
||||
|
||||
/**
|
||||
* Seed the first admin from the deployment environment when the store is
|
||||
* empty (design doc 7.2: the first admin comes from env or an init command;
|
||||
* later users are managed by admins through the API).
|
||||
* empty. Without env seeding the store stays empty and the first-start
|
||||
* setup flow (/api/auth/setup-status, /api/auth/setup) creates the admin.
|
||||
*/
|
||||
export function ensureBootstrapAdmin(): void {
|
||||
if (countUsers() > 0) return;
|
||||
const username = process.env.WEBUI_AUTH_USERNAME?.trim();
|
||||
const password = process.env.WEBUI_AUTH_PASSWORD;
|
||||
if (!username || !password) {
|
||||
throw new UserStoreError(
|
||||
"No WebUI users exist yet. Set WEBUI_AUTH_USERNAME and WEBUI_AUTH_PASSWORD to create the first admin."
|
||||
);
|
||||
}
|
||||
if (!username || !password) return;
|
||||
createUser(username, password, "admin");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user