diff --git a/PROJECT_ARCHITECTURE.md b/PROJECT_ARCHITECTURE.md index 21629153..df5bfa04 100644 --- a/PROJECT_ARCHITECTURE.md +++ b/PROJECT_ARCHITECTURE.md @@ -24,8 +24,9 @@ A unified system to maintain an inventory of "items" and their quantities, inclu ### 2.3 Operations & Tooling - **PWA Deployment:** `next-pwa` (Service Workers + Manifest.json) -- **HTTPS Proxy:** `local-ssl-proxy` (Required for mobile camera access, Port 3003) -- **Servers:** Frontend (`npm run dev` on 3000), Backend (`./start_server.sh` on 8000) +- **HTTPS Proxy:** `caddy` or `local-ssl-proxy` (Port 8909) +- **Servers:** Frontend (Port 8907), Backend (Port 8906) +- **Configuration:** Centrally managed via root `config/` directory (includes `network_config.env`, `ldap_config.json`, `Caddyfile`). ## 3. Data Models & Entities - **Item:** Name, Category Group (Structured), Item Type (Specific), Quantity, Barcode, Part Number, Box Label (Association). @@ -88,7 +89,7 @@ To ensure enterprise-grade protection, the following policies are enforced: - **Cryptographic Credential Caching:** To support offline operations, the system caches a **PBKDF2-HMAC-SHA256 hash** of the user's Enterprise credentials upon successful online login. Plain text passwords are NEVER stored. ### 7.4 PWA Trust & Security -- **HTTPS Enforcement:** The system requires TLS (Port 3003) for camera access and secure token transmission. +- **HTTPS Enforcement:** The system requires TLS (Port 8909) for camera access and secure token transmission. - **Manifest Integrity:** A comprehensive `manifest.json` ensures the app is recognized as a trusted PWA on mobile platforms (iOS/Android). 7.5 Git Infrastructure Hardening (v1.7.0) To ensure deployment stability on macOS environments with potentially broken developer tool links (`xcode-select` errors): diff --git a/README.md b/README.md index b82e435c..1aaa1c6a 100644 --- a/README.md +++ b/README.md @@ -12,21 +12,21 @@ This project supports three distinct operational modes: Ideal for local development on macOS/Linux. * **Command:** `./start_server.sh` * **Details:** Runs FastAPI (backend) and Next.js (frontend) in development mode. Uses `local-ssl-proxy` for HTTPS. -* **Backend:** http://localhost:8000 -* **Frontend:** https://localhost:3003 +* **Backend:** http://localhost:8906 +* **Frontend:** https://localhost:8909 ### 2. 🐳 Docker Mode (Recommended for Production) Isolated and portable container stack. * **Command:** `docker-compose up -d --build` * **Details:** Uses Caddy as a reverse proxy for HTTPS. Persistent data and logs are mapped to `./data` and `./logs`. -* **Access:** https://localhost:3003 +* **Access:** https://localhost:8909 ### 3. 🐧 Standalone Linux Mode (Systemd) Native Linux installation (Alma/Debian/Ubuntu) without Docker dependencies. * **Installation:** `sudo ./install_service.sh` * **Execution:** `sudo systemctl start inventory` * **Details:** Compiles the frontend for production and manages the entire stack as a system service. -* **Access:** https://:3003 +* **Access:** https://:8909 --- @@ -77,6 +77,12 @@ export ALLOWED_ORIGINS="https://your-domain.com" docker-compose up -d --build ``` +### 🌐 Network & Port Customization +The application uses a central configuration file for all network settings: +- **Location:** `config/network_config.env` +- **Purpose:** Change the `SERVER_IP` (default: `192.168.84.113`) and reserved ports (`8906-8909`). +- **Mechanism:** Startup scripts automatically sync these settings to the frontend and Docker environment. + For detailed security audit report, see [dev_docs/SECURITY_REPORT.md](dev_docs/SECURITY_REPORT.md). --- diff --git a/USER_GUIDE.md b/USER_GUIDE.md index 34cbeed3..03fd910f 100644 --- a/USER_GUIDE.md +++ b/USER_GUIDE.md @@ -100,6 +100,11 @@ If your organization uses LDAP/Active Directory, administrators can: ### Settings Access application settings from the **Admin** panel. +### 🌐 Network & Configuration (NEW v1.8.0) +The application now uses a centralized configuration folder in the project root: +- **`config/`**: Contains all network settings (`network_config.env`), LDAP profiles (`ldap_config.json`), and security proxy rules (`Caddyfile`). +- **Dynamic Port Mapping**: Changes to the server IP or ports in the configuration are automatically detected by both the frontend and backend after a restart. + --- ## 🚨 Security Notices @@ -141,5 +146,5 @@ For detailed technical documentation, see the [Project Architecture](../PROJECT_ --- -**Version:** v1.7.0 -**Last Updated:** 2026-04-12 +**Version:** v1.8.0 +**Last Updated:** 2026-04-13 diff --git a/VERSION.json b/VERSION.json index 62d17b28..9975f1af 100644 --- a/VERSION.json +++ b/VERSION.json @@ -1,5 +1,5 @@ { - "version": "1.7.0", - "last_build": "2026-04-12-2224", - "codename": "GitGuard" + "version": "1.8.0", + "last_build": "2026-04-13-1925", + "codename": "ConfigSync" } \ No newline at end of file diff --git a/backend/main.py b/backend/main.py index 2cf79ee1..024245de 100644 --- a/backend/main.py +++ b/backend/main.py @@ -24,7 +24,7 @@ log.info("TFM aInventory API process started.") # Secure fallback: localhost only for development. _raw_origins = os.environ.get( "ALLOWED_ORIGINS", - "http://localhost:3000,http://localhost:3002" + "http://localhost:8907,https://localhost:8909" ) ALLOWED_ORIGINS = [o.strip() for o in _raw_origins.split(",") if o.strip()] log.info(f"CORS allowed origins: {ALLOWED_ORIGINS}") diff --git a/backend/routers/users.py b/backend/routers/users.py index e655344a..5b898e08 100644 --- a/backend/routers/users.py +++ b/backend/routers/users.py @@ -20,8 +20,9 @@ limiter = Limiter(key_func=get_remote_address) pwd_context = CryptContext(schemes=["pbkdf2_sha256"], deprecated="auto") def get_ldap_config(): - # Read from backend/config/ directory - config_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "config") + # Read from root /config directory + root_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + config_dir = os.path.join(root_dir, "config") config_path = os.path.join(config_dir, "ldap_config.json") if os.path.exists(config_path): with open(config_path, "r") as f: @@ -314,7 +315,8 @@ def update_ldap_settings( current_user: auth.TokenData = Depends(auth.get_current_admin) ): """[C-01] Update LDAP config — admin only.""" - config_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "config") + root_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + config_dir = os.path.join(root_dir, "config") os.makedirs(config_dir, exist_ok=True) config_path = os.path.join(config_dir, "ldap_config.json") with open(config_path, "w") as f: diff --git a/Caddyfile b/config/Caddyfile similarity index 81% rename from Caddyfile rename to config/Caddyfile index 478f2dca..6f4c1b5b 100644 --- a/Caddyfile +++ b/config/Caddyfile @@ -1,12 +1,12 @@ # TFM aInventory - Caddy Self-Signed Internal Proxy # This replaces the need for `local-ssl-proxy` in Node. -:3003 { +:{$FRONTEND_SSL_PORT} { tls internal reverse_proxy frontend:3000 } -:3002 { +:{$BACKEND_SSL_PORT} { tls internal reverse_proxy backend:8000 } diff --git a/backend/.env.example b/config/backend.env.example similarity index 87% rename from backend/.env.example rename to config/backend.env.example index e734ffd7..d10b1247 100644 --- a/backend/.env.example +++ b/config/backend.env.example @@ -17,8 +17,8 @@ JWT_SECRET_KEY=change-me-generate-a-secure-random-value # --- CORS --- # Comma-separated list of allowed frontend origins # Example for LAN deployment: -# ALLOWED_ORIGINS=http://192.168.1.100:3000,https://192.168.1.100:3003 -ALLOWED_ORIGINS=http://localhost:3000,https://localhost:3003 +# ALLOWED_ORIGINS=http://192.168.84.113:8907,https://192.168.84.113:8909 +ALLOWED_ORIGINS=http://localhost:8907,https://localhost:8909 # --- Data Paths (overridden by start_server.sh / docker-compose) --- # DATA_DIR=/absolute/path/to/data diff --git a/config/ldap_config.json b/config/ldap_config.json new file mode 100644 index 00000000..2a0c202d --- /dev/null +++ b/config/ldap_config.json @@ -0,0 +1,19 @@ +{ + "ldap_enabled": true, + "server_uri": "ldaps://192.168.84.107:6360", + "base_dn": "dc=ldap,dc=lan", + "user_template": "uid={username},ou=people,dc=ldap,dc=lan", + "groups_dn": "ou=groups", + "use_tls": true, + "role_mappings": [ + { + "group": "inventory_admins", + "role": "admin" + }, + { + "group": "inventory_users", + "role": "user" + } + ], + "ignore_cert": true +} \ No newline at end of file diff --git a/backend/config/ldap_config.json.example b/config/ldap_config.json.example similarity index 100% rename from backend/config/ldap_config.json.example rename to config/ldap_config.json.example diff --git a/config/network_config.env b/config/network_config.env new file mode 100644 index 00000000..d5220b5f --- /dev/null +++ b/config/network_config.env @@ -0,0 +1,28 @@ +# ============================================================================= +# TFM aInventory - Central Network Configuration +# ============================================================================= +# Use this file to customize the ports and IP address used by the application +# without needing to modify the source code. +# +# IMPORTANT: After modifying this file, restart the application for changes +# to take effect. +# ============================================================================= + +# The primary IP address where the application will be accessed. +# Used for CORS settings and documentation links. +SERVER_IP=192.168.84.113 + +# --- BACKEND PORTS --- +# Internal port for the FastAPI server (Backend) +BACKEND_PORT=8906 + +# External port for the Backend HTTPS Proxy (Caddy/local-ssl-proxy) +BACKEND_SSL_PORT=8908 + +# --- FRONTEND PORTS --- +# Internal port for the Next.js dev/prod server +FRONTEND_PORT=8907 + +# External port for the Frontend HTTPS Proxy (Caddy/local-ssl-proxy) +# This is the port you will use in your browser (e.g. https://192.168.84.113:8909) +FRONTEND_SSL_PORT=8909 diff --git a/dev_docs/SESSION_STATE.md b/dev_docs/SESSION_STATE.md index b9f758bf..7e7b72ba 100644 --- a/dev_docs/SESSION_STATE.md +++ b/dev_docs/SESSION_STATE.md @@ -56,7 +56,9 @@ ## SYSTEM STATE **Active database:** `/data/inventory.db` -**LDAP config:** `backend/config/ldap_config.json` +**LDAP config:** `config/ldap_config.json` +**Network config:** `config/network_config.env` +**Proxy config:** `config/Caddyfile` **Production Bundle:** `aInventory-PROD-v1.6.0.zip` (BoxMaster Final) > [!IMPORTANT] @@ -66,8 +68,8 @@ ```bash ./start_server.sh ``` -- Frontend: `https://:3003` -- Backend: `https://:3002` +- Frontend: `https://192.168.84.113:8909` +- Backend: `https://192.168.84.113:8908` **Environment variables set by start_server.sh:** - `ALLOWED_ORIGINS` — auto-detected diff --git a/docker-compose.yml b/docker-compose.yml index 5f3480ba..bc20394f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,7 +8,9 @@ services: networks: - inventory_net ports: - - "8000:8000" + - "${BACKEND_PORT:-8000}:8000" + env_file: + - ./config/network_config.env volumes: - ./data:/app/data - ./logs:/app/logs @@ -16,8 +18,8 @@ services: environment: - DATA_DIR=/app/data - LOGS_DIR=/app/logs - # [M-01] CORS allowed origins — customize for production - - ALLOWED_ORIGINS=http://localhost:3000,http://localhost:3002 + # [M-01] CORS allowed origins — dynamically constructed from config + - ALLOWED_ORIGINS=http://localhost:${FRONTEND_PORT:-3001},https://localhost:${FRONTEND_SSL_PORT:-3003},http://${SERVER_IP:-localhost}:${FRONTEND_PORT:-3001},https://${SERVER_IP:-localhost}:${FRONTEND_SSL_PORT:-3003},https://localhost:${BACKEND_SSL_PORT:-3002},https://${SERVER_IP:-localhost}:${BACKEND_SSL_PORT:-3002} # [C-01] JWT secret key — GENERATE A SECURE VALUE FOR PRODUCTION! - JWT_SECRET_KEY=${JWT_SECRET_KEY:-change-me-in-production} restart: unless-stopped @@ -29,7 +31,9 @@ services: networks: - inventory_net ports: - - "3000:3000" + - "${FRONTEND_PORT:-3000}:3000" + env_file: + - ./config/network_config.env volumes: - ./logs:/app/logs # Write Next.js logs to both stdout (docker logs) and file (mapped volume) @@ -41,10 +45,12 @@ services: networks: - inventory_net ports: - - "3002:3002" - - "3003:3003" + - "${BACKEND_SSL_PORT:-3002}:${BACKEND_SSL_PORT:-3002}" + - "${FRONTEND_SSL_PORT:-3003}:${FRONTEND_SSL_PORT:-3003}" + env_file: + - ./config/network_config.env volumes: - - ./Caddyfile:/etc/caddy/Caddyfile + - ./config/Caddyfile:/etc/caddy/Caddyfile # Persist the internal Caddy certificates so users don't get new certificate warnings constantly - ./data/caddy_data:/data - ./data/caddy_config:/config diff --git a/export_prod.sh b/export_prod.sh index f3391a41..32651493 100755 --- a/export_prod.sh +++ b/export_prod.sh @@ -18,9 +18,9 @@ echo "📂 Copying application components (excluding dev artifacts)..." rsync -a --exclude 'node_modules' --exclude '.next' frontend/ "$PROD_DIR/frontend/" rsync -a --exclude '__pycache__' --exclude '.pytest_cache' --exclude '.venv' --exclude 'tests' backend/ "$PROD_DIR/backend/" -# Orchestration & Scripts +# Orchestration, Config & Scripts cp docker-compose.yml "$PROD_DIR/" -cp Caddyfile "$PROD_DIR/" +rsync -a config/ "$PROD_DIR/config/" cp start_server.sh "$PROD_DIR/" cp run_standalone.sh "$PROD_DIR/" cp install_service.sh "$PROD_DIR/" @@ -44,7 +44,7 @@ TO RUN VIA DOCKER (Recommended): 1. Install Docker Desktop or Docker Engine. 2. Run: docker-compose build 3. Run: docker-compose up -d -4. Access via https://:3003 (Accept the internal security warning). +4. Access via https://:8909 (Accept the internal security warning). TO INSTALL AS A LINUX SYSTEM SERVICE (Optional): 1. sudo ./install_service.sh @@ -54,7 +54,7 @@ TO RUN BARE-METAL (No Docker): 1. Install Python 3.12+ and Node.js 20+. 2. Ensure you have network access for npm installs. 3. Run: ./start_server.sh -4. Access via https://:3003 +4. Access via https://:8909 Note: Database and Logs will persist in the /data and /logs directories. EOF diff --git a/frontend/lib/api.ts b/frontend/lib/api.ts index 53c7d01e..e33d66d3 100644 --- a/frontend/lib/api.ts +++ b/frontend/lib/api.ts @@ -1,20 +1,48 @@ import axios from 'axios'; import { getToken, clearAuth } from './auth'; -export const getBackendUrl = () => { - if (typeof window === 'undefined') return 'http://localhost:8000'; +// Cached config to avoid repeated fetches +let cachedConfig: any = null; + +/** + * Fetches the network configuration from the public/network.json file. + * This file is generated at startup by start_server.sh or docker-compose. + */ +export const getNetworkConfig = async () => { + if (cachedConfig) return cachedConfig; + + if (typeof window === 'undefined') { + return { SERVER_IP: 'localhost', BACKEND_PORT: 8000, BACKEND_SSL_PORT: 8908 }; + } + + try { + const response = await fetch('/network.json'); + if (!response.ok) throw new Error("Config not found"); + cachedConfig = await response.json(); + return cachedConfig; + } catch (e) { + console.warn("Network config not found, using compiled defaults."); + // Defaults matching the initial reserve ports in case network.json is missing + return { SERVER_IP: 'localhost', BACKEND_PORT: 8906, BACKEND_SSL_PORT: 8908 }; + } +}; + +export const getBackendUrl = async () => { + const config = await getNetworkConfig(); + + if (typeof window === 'undefined') return `http://localhost:${config.BACKEND_PORT}`; const host = window.location.hostname; - // If we are on HTTPS (Proxy/Mobile mode), we use port 3002 for the backend + // If we are on HTTPS (Proxy/Mobile mode), we use the SSL port for the backend if (window.location.protocol === 'https:') { if (host.includes('.loca.lt')) { return 'https://inventory-ai-api.loca.lt'; } - return `https://${host}:3002`; + return `https://${host}:${config.BACKEND_SSL_PORT}`; } - return `http://${host}:8000`; + return `http://${host}:${config.BACKEND_PORT}`; }; /** @@ -23,9 +51,9 @@ export const getBackendUrl = () => { */ const axiosInstance = axios.create({}); -axiosInstance.interceptors.request.use((config) => { +axiosInstance.interceptors.request.use(async (config) => { if (!config.baseURL) { - config.baseURL = getBackendUrl(); // called at request time — always correct + config.baseURL = await getBackendUrl(); // called at request time — always correct } const token = getToken(); if (token) { @@ -110,7 +138,8 @@ export const inventoryApi = { // Users getUsers: async () => { // [C-01] Public endpoint — use plain axios to avoid JWT interceptor - const res = await axios.get(`${getBackendUrl()}/users/`); + const baseUrl = await getBackendUrl(); + const res = await axios.get(`${baseUrl}/users/`); return res.data; }, @@ -121,7 +150,8 @@ export const inventoryApi = { login: async (credentials: any) => { // [C-01] Login endpoint — NU adaug token header (login e public) - const res = await axios.post(`${getBackendUrl()}/users/login`, credentials); + const baseUrl = await getBackendUrl(); + const res = await axios.post(`${baseUrl}/users/login`, credentials); return res.data; }, diff --git a/run_standalone.sh b/run_standalone.sh index e7b52b3b..72f1dcb7 100755 --- a/run_standalone.sh +++ b/run_standalone.sh @@ -7,17 +7,38 @@ echo "🚀 Starting TFM aInventory in Standalone Mode..." # Trapping termination signals to clean up child processes trap "echo 'Stopping all processes...'; kill 0" SIGINT SIGTERM EXIT -# --- CONFIGURATION (Match start_server.sh) --- +# --- CONFIGURATION (Default values, overridden by network_config.env) --- BACKEND_PORT=8000 FRONTEND_PORT=3001 BACKEND_SSL_PORT=3002 FRONTEND_SSL_PORT=3003 +SERVER_IP="localhost" + +# Load Configuration from file if it exists +CONFIG_PATH="$(cd "$(dirname "$0")" && pwd)/config/network_config.env" +if [ -f "$CONFIG_PATH" ]; then + echo "⚙️ Loading network configuration from $CONFIG_PATH..." + export $(grep -v '^#' "$CONFIG_PATH" | xargs) +fi # 1. Activate Environment if [ -d ".venv" ]; then source .venv/bin/activate fi +# 1.5 Sync Network Config to Frontend +echo "🔌 Syncing network configuration to frontend..." +mkdir -p frontend/public +cat < frontend/public/network.json +{ + "SERVER_IP": "$SERVER_IP", + "BACKEND_PORT": $BACKEND_PORT, + "BACKEND_SSL_PORT": $BACKEND_SSL_PORT, + "FRONTEND_PORT": $FRONTEND_PORT, + "FRONTEND_SSL_PORT": $FRONTEND_SSL_PORT +} +EOF + # 2. Start Backend (No Reload for Prod) echo "🔥 Starting Backend (Uvicorn)..." python3 -m uvicorn backend.main:app --host 0.0.0.0 --port $BACKEND_PORT & diff --git a/scripts/init_data.sh b/scripts/init_data.sh index 09d93ad3..120d8157 100755 --- a/scripts/init_data.sh +++ b/scripts/init_data.sh @@ -32,10 +32,10 @@ mkdir -p "$DATA_DIR" mkdir -p "$LOGS_DIR" # 2. Copy LDAP config from example template if no active config exists yet -# NOTE: The application reads LDAP config from backend/config/ldap_config.json +# NOTE: The application reads LDAP config from config/ldap_config.json # (see backend/routers/users.py → get_ldap_config()) -LDAP_CONFIG="$PROJECT_ROOT/backend/config/ldap_config.json" -LDAP_EXAMPLE="$PROJECT_ROOT/backend/config/ldap_config.json.example" +LDAP_CONFIG="$PROJECT_ROOT/config/ldap_config.json" +LDAP_EXAMPLE="$PROJECT_ROOT/config/ldap_config.json.example" if [ ! -f "$LDAP_CONFIG" ]; then if [ -f "$LDAP_EXAMPLE" ]; then diff --git a/start_server.sh b/start_server.sh index ca6e6de9..c0bf6f53 100755 --- a/start_server.sh +++ b/start_server.sh @@ -1,10 +1,19 @@ #!/bin/bash -# --- CONFIGURATION --- +# --- CONFIGURATION (Default values, will be overridden by network_config.env) --- BACKEND_PORT=8000 FRONTEND_PORT=3001 BACKEND_SSL_PORT=3002 FRONTEND_SSL_PORT=3003 +SERVER_IP="localhost" + +# Load Configuration from file if it exists +CONFIG_PATH="$(cd "$(dirname "$0")" && pwd)/config/network_config.env" +if [ -f "$CONFIG_PATH" ]; then + echo "⚙️ Loading network configuration from $CONFIG_PATH..." + # Export variables from .env file (ignoring comments and empty lines) + export $(grep -v '^#' "$CONFIG_PATH" | xargs) +fi # Add common Mac paths for npm/node export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH" @@ -39,6 +48,18 @@ export LOGS_DIR="$(cd "$(dirname "$0")" && pwd)/logs" echo "🔧 Initializing runtime data directories..." bash "$(cd "$(dirname "$0")" && pwd)/scripts/init_data.sh" +# 4.1 Sync Network Config to Frontend for runtime discovery +echo "🔌 Syncing network configuration to frontend..." +cat < frontend/public/network.json +{ + "SERVER_IP": "$SERVER_IP", + "BACKEND_PORT": $BACKEND_PORT, + "BACKEND_SSL_PORT": $BACKEND_SSL_PORT, + "FRONTEND_PORT": $FRONTEND_PORT, + "FRONTEND_SSL_PORT": $FRONTEND_SSL_PORT +} +EOF + echo "🔥 Starting Backend on port $BACKEND_PORT..." echo " CORS origins: $ALLOWED_ORIGINS" python3 -m uvicorn backend.main:app --host 0.0.0.0 --port $BACKEND_PORT --reload & @@ -67,8 +88,8 @@ echo -e "${GREEN}${BOLD} 🚀 TFM aInventory UNIFIED ACCESS${NC}" echo -e "${GREEN}=======================================================${NC}" echo "" echo -e " USE THIS URL ON BOTH DESKTOP & MOBILE:" -echo -e " 👉 ${GREEN}${BOLD}https://$LOCAL_IP:3003${NC}" -echo -e " (Or ${GREEN}https://localhost:3003${NC} on this Mac)" +echo -e " 👉 ${GREEN}${BOLD}https://$LOCAL_IP:$FRONTEND_SSL_PORT${NC}" +echo -e " (Or ${GREEN}https://localhost:$FRONTEND_SSL_PORT${NC} on this Mac)" echo "" echo -e " ${YELLOW}${BOLD}NOTE:${NC} If you see a 'Not Private' warning," echo -e " Click 'Advanced' -> 'Proceed' to continue."