From 31eb71b354287acfcdafcfcdde67906442f3cab2 Mon Sep 17 00:00:00 2001 From: Leander Furumo Date: Thu, 5 Nov 2026 03:44:07 +0100 Subject: [PATCH] fix(dev): keep logins on the app's own address The admin and login links pointed at VITE_API_URL (http://localhost:3000) while the app runs at http://127.0.0.1:5173. Browsers keep cookies for the two apart, so logging in there left the app logged out. In development these links now go through Vite's proxy, as GraphQL requests already did. Logging in through the proxy also failed Django's CSRF origin check, because the proxy rewrote the Host header. Keep the app's Host for the local backend; a remote VITE_DEV_API_PROXY still gets its own. Co-Authored-By: Claude Opus 5.5 --- frontend/src/client.ts | 4 +++- frontend/src/components/Header.tsx | 3 ++- frontend/src/features/inventory/InventoryAdd.tsx | 3 ++- frontend/src/utils/api.ts | 6 ++++++ frontend/vite.config.ts | 12 +++++++++--- 5 files changed, 22 insertions(+), 6 deletions(-) create mode 100644 frontend/src/utils/api.ts diff --git a/frontend/src/client.ts b/frontend/src/client.ts index 55705c0..97ca081 100644 --- a/frontend/src/client.ts +++ b/frontend/src/client.ts @@ -1,7 +1,9 @@ import { ApolloClient, HttpLink, InMemoryCache } from "@apollo/client"; +import { apiUrl } from "./utils/api"; + const client = new ApolloClient({ - link: new HttpLink({ uri: import.meta.env.DEV ? "/graphql/" : `${import.meta.env.VITE_API_URL}/graphql/` }), + link: new HttpLink({ uri: apiUrl("/graphql/") }), cache: new InMemoryCache(), }); diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index 5cb3147..a812671 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -3,8 +3,9 @@ import React from "react"; import { NavLink } from "react-router-dom"; import styles from "./Header.module.css"; +import { apiUrl } from "../utils/api"; -const ADMIN_URL = `${import.meta.env.VITE_API_URL}/admin/`; +const ADMIN_URL = apiUrl("/admin/"); const navLinkStyles = ({ isActive }: { isActive: boolean }) => clsx(styles.navLink, { [styles.active]: isActive }); diff --git a/frontend/src/features/inventory/InventoryAdd.tsx b/frontend/src/features/inventory/InventoryAdd.tsx index 9c85b03..3af6f29 100644 --- a/frontend/src/features/inventory/InventoryAdd.tsx +++ b/frontend/src/features/inventory/InventoryAdd.tsx @@ -6,8 +6,9 @@ import styles from "./Inventory.module.css"; import { GQLError } from "../../components/GQLError"; import { useToast } from "../../hooks/useToast"; import { CreateInventoryTransactionDocument, InventoryProductsDocument } from "../../types"; +import { loginUrl } from "../../utils/api"; -const LOGIN_URL = `${import.meta.env.VITE_API_URL}/accounts/login/?next=/inventory/add`; +const LOGIN_URL = loginUrl("/inventory/add"); const InventoryAdd = () => { const navigate = useNavigate(); diff --git a/frontend/src/utils/api.ts b/frontend/src/utils/api.ts new file mode 100644 index 0000000..f238e95 --- /dev/null +++ b/frontend/src/utils/api.ts @@ -0,0 +1,6 @@ +// URL of a backend path such as /graphql/. In development this goes through Vite's proxy on the app's +// own address (see vite.config.ts), so the login cookie Django sets belongs to the host the app runs on; +// browsers keep cookies for localhost and 127.0.0.1 apart. Builds use VITE_API_URL. +export const apiUrl = (path: string) => (import.meta.env.DEV ? path : `${import.meta.env.VITE_API_URL}${path}`); + +export const loginUrl = (next: string) => apiUrl(`/accounts/login/?next=${encodeURIComponent(next)}`); diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 74e782c..ca4ea98 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -13,9 +13,15 @@ export default defineConfig({ server: { host: "127.0.0.1", proxy: Object.fromEntries( - ["/admin", "/accounts", "/static", "/media", "/graphql"].map( - (path) => [path, process.env.VITE_DEV_API_PROXY || "http://127.0.0.1:3000"], - ), + ["/admin", "/accounts", "/static", "/media", "/graphql"].map((path) => [ + path, + { + target: process.env.VITE_DEV_API_PROXY || "http://127.0.0.1:3000", + // Django only accepts a login form posted from the Host it was sent to (CSRF), so keep the + // app's own Host for the local backend. A remote backend needs its own Host to be reached. + changeOrigin: !!process.env.VITE_DEV_API_PROXY, + }, + ]), ), }, build: { outDir: "../static", emptyOutDir: false },