fix(frontend): resolve production API URL fallback to localhost
CI/CD Pipeline / Test (push) Successful in 20m59s
CI/CD Pipeline / Security Scan (push) Successful in 10m38s
CI/CD Pipeline / Build and Push Images (push) Failing after 13s

Problem:
The unified Docker image builds the frontend at build time without
VITE_API_URL. Vite inlined import.meta.env.VITE_API_URL as undefined,
so every API call fell back to the hardcoded 'http://localhost:8080'.
This broke Casa deployments where the frontend loaded from the public
 domain but tried to reach the backend at localhost.

Solution:
1. Centralize API URL resolution in lib/api-url.ts via getApiOrigin().
   It checks runtime window.ENV first (injected by docker-entrypoint.sh
   at container startup), then build-time import.meta.env, then dev
   fallback. In production unified deployments it returns '' so API
   calls use same-origin relative URLs (/api/v1/...) that nginx proxies
   to the backend.
2. Replace all 50+ inline import.meta.env.VITE_API_URL || 'localhost'
   usages across 14 source files with getApiOrigin() / getApiV1BaseUrl().
3. Add build args and runtime sed substitution to Dockerfile and
   docker-entrypoint.sh so the same image works for any deployment.
4. Pass VITE_API_URL through docker-compose.yml and CI/CD build-args.

Verified:
- Production bundle contains 0 occurrences of localhost:8080.
- Container health check and /api/v1/auth/check-users both return 200.
- Runtime injection correctly sets VITE_API_URL='' for same-origin
  and VITE_API_URL='https://domain' for external backend.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomas Dvorak
2026-05-22 12:34:39 +02:00
parent b539aa1b91
commit 4dfdd500b4
19 changed files with 129 additions and 56 deletions
+8 -7
View File
@@ -1,5 +1,6 @@
import { createSignal, onMount, Show, For } from 'solid-js';
import { Button } from './ui/Button';
import { getApiOrigin } from '@/lib/api-url';
interface TOTPSetupResponse {
secret: string;
@@ -42,7 +43,7 @@ export function TwoFactorAuth() {
const fetchTOTPStatus = async () => {
try {
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/2fa/status`, {
const response = await fetch(`${getApiOrigin()}/api/v1/auth/2fa/status`, {
headers: getAuthHeaders(),
});
@@ -66,7 +67,7 @@ export function TwoFactorAuth() {
setSuccess(null);
try {
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/2fa/setup`, {
const response = await fetch(`${getApiOrigin()}/api/v1/auth/2fa/setup`, {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify({
@@ -102,7 +103,7 @@ export function TwoFactorAuth() {
setError(null);
try {
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/2fa/verify`, {
const response = await fetch(`${getApiOrigin()}/api/v1/auth/2fa/verify`, {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify({
@@ -135,7 +136,7 @@ export function TwoFactorAuth() {
setSuccess(null);
try {
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/2fa/enable`, {
const response = await fetch(`${getApiOrigin()}/api/v1/auth/2fa/enable`, {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify({
@@ -171,7 +172,7 @@ export function TwoFactorAuth() {
setSuccess(null);
try {
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/2fa/disable`, {
const response = await fetch(`${getApiOrigin()}/api/v1/auth/2fa/disable`, {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify({
@@ -206,7 +207,7 @@ export function TwoFactorAuth() {
setError(null);
try {
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/2fa/backup-codes/verify`, {
const response = await fetch(`${getApiOrigin()}/api/v1/auth/2fa/backup-codes/verify`, {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify({
@@ -240,7 +241,7 @@ export function TwoFactorAuth() {
setSuccess(null);
try {
const response = await fetch(`${import.meta.env.VITE_API_URL || 'http://localhost:8080'}/api/v1/auth/2fa/backup-codes/regenerate`, {
const response = await fetch(`${getApiOrigin()}/api/v1/auth/2fa/backup-codes/regenerate`, {
method: 'POST',
headers: getAuthHeaders(),
body: JSON.stringify({