From 4aca82fb1a7b14ff575f4b89e58c1c83e4fcbd8e Mon Sep 17 00:00:00 2001 From: Pouzor Date: Sat, 28 Mar 2026 18:27:54 +0100 Subject: [PATCH] fix: remove hardcoded CORS_ORIGINS from docker-compose, improve login errors CORS_ORIGINS was hardcoded in docker-compose.yml, silently overriding .env and breaking login for users who change the frontend port. It now comes from .env exclusively, with a clear comment in .env.example. Login page now distinguishes network errors (CORS/offline) from wrong credentials, and footer correctly references .env instead of config.yml. --- .env.example | 1 + docker-compose.yml | 3 +-- frontend/src/components/LoginPage.tsx | 7 ++++--- .../src/components/__tests__/LoginPage.test.tsx | 13 ++++++++++++- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/.env.example b/.env.example index f9affad..de4d90c 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,7 @@ # Backend - server-side only (NEVER commit .env) SECRET_KEY=change_me_in_production SQLITE_PATH=./data/homelab.db +# Set this to the URL(s) you use to access Homelable in your browser. CORS_ORIGINS=["http://localhost:5173","http://localhost:3000"] # Auth — default credentials: admin / admin diff --git a/docker-compose.yml b/docker-compose.yml index 613d572..16c9b2a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,9 +7,8 @@ services: env_file: - .env environment: - # Override env_file values that differ in Docker + # Override env_file: SQLite path must point inside the container volume SQLITE_PATH: /app/data/homelab.db - CORS_ORIGINS: '["http://localhost:3000"]' volumes: - backend_data:/app/data networks: diff --git a/frontend/src/components/LoginPage.tsx b/frontend/src/components/LoginPage.tsx index 7fbbb0e..3272798 100644 --- a/frontend/src/components/LoginPage.tsx +++ b/frontend/src/components/LoginPage.tsx @@ -20,8 +20,9 @@ export function LoginPage() { try { const res = await authApi.login(username, password) login(res.data.access_token) - } catch { - setError('Invalid username or password') + } catch (err: unknown) { + const hasResponse = err && typeof err === 'object' && 'response' in err + setError(hasResponse ? 'Invalid username or password' : 'Could not reach the server — check your CORS_ORIGINS setting') } finally { setLoading(false) } @@ -95,7 +96,7 @@ export function LoginPage() {

- Credentials configured in config.yml + Credentials configured in .env

diff --git a/frontend/src/components/__tests__/LoginPage.test.tsx b/frontend/src/components/__tests__/LoginPage.test.tsx index 9b617a6..416ea74 100644 --- a/frontend/src/components/__tests__/LoginPage.test.tsx +++ b/frontend/src/components/__tests__/LoginPage.test.tsx @@ -51,7 +51,7 @@ describe('LoginPage', () => { }) it('shows a generic error message — no credential enumeration', async () => { - vi.mocked(authApi.login).mockRejectedValue(new Error('401')) + vi.mocked(authApi.login).mockRejectedValue({ response: { status: 401 } }) render() fireEvent.change(screen.getByLabelText('Username'), { target: { value: 'admin' } }) fireEvent.change(screen.getByLabelText('Password'), { target: { value: 'wrongpass' } }) @@ -65,6 +65,17 @@ describe('LoginPage', () => { expect(errors[0].textContent).toBe('Invalid username or password') }) + it('shows a network error message when no response (e.g. CORS misconfiguration)', async () => { + vi.mocked(authApi.login).mockRejectedValue(new Error('Network Error')) + render() + fireEvent.change(screen.getByLabelText('Username'), { target: { value: 'admin' } }) + fireEvent.change(screen.getByLabelText('Password'), { target: { value: 'admin' } }) + fireEvent.submit(screen.getByRole('button', { name: /sign in/i }).closest('form')!) + await waitFor(() => { + expect(screen.getByText(/Could not reach the server/)).toBeDefined() + }) + }) + it('clears previous error before each new attempt', async () => { vi.mocked(authApi.login) .mockRejectedValueOnce(new Error('401'))