diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index 73491883..89bace9a 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -36,8 +36,10 @@ import { userFileRoutes } from "./routes/user-files.js"; runMigrations(); console.log("Database initialized"); -// Create default admin user if no users exist -await ensureDefaultAdmin(); +// Create default admin user if no users exist and auth is enabled +if (env.AUTH_ENABLED) { + await ensureDefaultAdmin(); +} function ensureInstanceId() { const existing = db diff --git a/apps/api/src/plugins/auth.ts b/apps/api/src/plugins/auth.ts index 1bebb587..dbaaa751 100644 --- a/apps/api/src/plugins/auth.ts +++ b/apps/api/src/plugins/auth.ts @@ -160,6 +160,10 @@ export async function authRoutes(app: FastifyInstance): Promise { "/api/auth/login", { config: { rateLimit: { max: getLoginAttemptLimit, timeWindow: "1 minute" } } }, async (request: FastifyRequest, reply: FastifyReply) => { + if (!env.AUTH_ENABLED) { + return reply.status(403).send({ error: "Authentication is disabled" }); + } + const body = request.body as { username?: string; password?: string } | null; if (!body?.username || !body?.password) { @@ -230,6 +234,22 @@ export async function authRoutes(app: FastifyInstance): Promise { // GET /api/auth/session app.get("/api/auth/session", async (request: FastifyRequest, reply: FastifyReply) => { + if (!env.AUTH_ENABLED) { + return reply.send({ + user: { + id: "anonymous", + username: "anonymous", + role: "user", + mustChangePassword: false, + permissions: getPermissions("user"), + analyticsEnabled: null, + analyticsConsentShownAt: null, + analyticsConsentRemindAt: null, + }, + expiresAt: null, + }); + } + const token = extractToken(request); if (!token) { return reply.status(401).send({ error: "No session token provided" }); @@ -238,7 +258,6 @@ export async function authRoutes(app: FastifyInstance): Promise { const session = db.select().from(schema.sessions).where(eq(schema.sessions.id, token)).get(); if (!session || session.expiresAt < new Date()) { - // Clean up expired session if it exists if (session) { db.delete(schema.sessions).where(eq(schema.sessions.id, token)).run(); } diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index e295a2de..5bf5fedc 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -87,9 +87,7 @@ function AuthGuard({ children }: { children: React.ReactNode }) { const setStoreConsent = useAnalyticsStore((s) => s.setConsent); const location = useLocation(); - // Hydrate the analytics store from session data on initial load. - // Only hydrate if the store is still in its initial state (user hasn't taken - // an explicit action like accepting/declining on the consent page). + // biome-ignore lint/correctness/useExhaustiveDependencies: only hydrate on session load, not on store changes useEffect(() => { if ( !loading && @@ -103,9 +101,17 @@ function AuthGuard({ children }: { children: React.ReactNode }) { analyticsConsentRemindAt: null, }); } - // eslint-disable-next-line -- only hydrate on session load, not on store changes }, [loading, analyticsEnabled, analyticsConsentShownAt, setStoreConsent]); + // When auth is disabled, redirect away from login/change-password to prevent escalation + if ( + !loading && + !authEnabled && + (location.pathname === "/login" || location.pathname === "/change-password") + ) { + return ; + } + // Don't guard the login or change-password pages if ( location.pathname === "/login" || diff --git a/apps/web/src/components/settings/settings-dialog.tsx b/apps/web/src/components/settings/settings-dialog.tsx index 4eb17fde..6f12fffa 100644 --- a/apps/web/src/components/settings/settings-dialog.tsx +++ b/apps/web/src/components/settings/settings-dialog.tsx @@ -202,6 +202,7 @@ interface TeamEntry { /* ────────────────────── General ────────────────────── */ function GeneralSection() { + const { authEnabled } = useAuth(); const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const [defaultToolView, setDefaultToolView] = useState("sidebar"); @@ -277,14 +278,16 @@ function GeneralSection() {

{role}

- + {authEnabled && ( + + )} {/* Default view */}