diff --git a/backend/app/api/v1/auth.py b/backend/app/api/v1/auth.py index e34ee71..e5784a1 100644 --- a/backend/app/api/v1/auth.py +++ b/backend/app/api/v1/auth.py @@ -16,7 +16,8 @@ router = APIRouter(prefix="/auth", tags=["auth"]) @router.post("/login", response_model=TokenPair) def login(payload: LoginRequest, db: Session = Depends(get_db)) -> TokenPair: - user = db.scalar(select(User).where(User.email == payload.email)) + email = payload.email.strip().lower() + user = db.scalar(select(User).where(User.email == email)) if not user or not verify_password(payload.password, user.password_hash): if user: user.failed_login_attempts += 1 @@ -41,4 +42,3 @@ def me(user: CurrentUser) -> User: def logout(user: CurrentUser, db: Session = Depends(get_db)) -> dict[str, str]: write_audit(db, action="logout", object_type="user", object_id=user.id, user_id=user.id) return {"status": "ok", "at": datetime.utcnow().isoformat()} - diff --git a/backend/app/schemas/domain.py b/backend/app/schemas/domain.py index e5762a1..f47ac2e 100644 --- a/backend/app/schemas/domain.py +++ b/backend/app/schemas/domain.py @@ -1,7 +1,7 @@ from datetime import datetime from typing import Any -from pydantic import BaseModel, ConfigDict, EmailStr, Field +from pydantic import BaseModel, ConfigDict, Field class OrmModel(BaseModel): @@ -15,13 +15,13 @@ class TokenPair(BaseModel): class LoginRequest(BaseModel): - email: EmailStr + email: str password: str class UserRead(OrmModel): id: str - email: EmailStr + email: str display_name: str is_active: bool diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 6cb5464..c883352 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,4 +1,5 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { useEffect } from "react"; import { BrowserRouter, Route, Routes } from "react-router-dom"; import { Layout } from "./components/Layout"; @@ -7,10 +8,17 @@ import { FirewallPreview } from "./pages/FirewallPreview"; import { ListPage } from "./pages/ListPage"; import { Login } from "./pages/Login"; import { PolicyDesigner } from "./pages/PolicyDesigner"; +import { useTheme } from "./stores/theme"; const queryClient = new QueryClient(); export function App() { + const dark = useTheme((state) => state.dark); + + useEffect(() => { + document.documentElement.classList.toggle("dark", dark); + }, [dark]); + return ( @@ -38,4 +46,3 @@ export function App() { ); } - diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx index f1f038f..3472ebc 100644 --- a/frontend/src/components/Layout.tsx +++ b/frontend/src/components/Layout.tsx @@ -45,10 +45,6 @@ export function Layout() { const navigate = useNavigate(); const { dark, toggle } = useTheme(); - useEffect(() => { - document.documentElement.classList.toggle("dark", dark); - }, [dark]); - useEffect(() => { if (!token()) navigate("/login"); }, [navigate]); @@ -96,4 +92,3 @@ export function Layout() { ); } - diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx index 31e6029..d2ba941 100644 --- a/frontend/src/pages/Login.tsx +++ b/frontend/src/pages/Login.tsx @@ -1,14 +1,16 @@ import { FormEvent, useState } from "react"; import { useNavigate } from "react-router-dom"; -import { ShieldCheck } from "lucide-react"; +import { Moon, ShieldCheck, Sun } from "lucide-react"; import { login } from "../api/client"; +import { useTheme } from "../stores/theme"; export function Login() { const navigate = useNavigate(); const [email, setEmail] = useState("admin@nexafabric.local"); const [password, setPassword] = useState("ChangeMe_UseEnvInstead"); const [error, setError] = useState(""); + const { dark, toggle } = useTheme(); async function submit(event: FormEvent) { event.preventDefault(); @@ -23,6 +25,14 @@ export function Login() { return (
+
@@ -47,4 +57,3 @@ export function Login() {
); } - diff --git a/frontend/src/stores/theme.ts b/frontend/src/stores/theme.ts index 31e3db0..e1ee999 100644 --- a/frontend/src/stores/theme.ts +++ b/frontend/src/stores/theme.ts @@ -5,8 +5,10 @@ type ThemeState = { toggle: () => void; }; +const storedTheme = localStorage.getItem("nexafabric.theme"); + export const useTheme = create((set) => ({ - dark: localStorage.getItem("nexafabric.theme") === "dark", + dark: storedTheme ? storedTheme === "dark" : true, toggle: () => set((state) => { const dark = !state.dark; @@ -14,4 +16,3 @@ export const useTheme = create((set) => ({ return { dark }; }), })); -