diff --git a/backend/routers/users.py b/backend/routers/users.py index 54d127c3..2d8dd608 100644 --- a/backend/routers/users.py +++ b/backend/routers/users.py @@ -73,6 +73,11 @@ def authenticate_ldap(username, password): return None real_user_dn = conn.entries[0].entry_dn + user_groups = [] + if hasattr(conn.entries[0], 'memberOf'): + user_groups = [str(g).lower() for g in conn.entries[0].memberOf.values] + log.debug(f"LDAP: Found memberOf groups on user: {user_groups}") + log.debug(f"LDAP: Canonical DN found: {real_user_dn}") # Check roles based on group membership @@ -87,27 +92,39 @@ def authenticate_ldap(username, password): groups_dn = config.get("groups_dn", "ou=groups") # Iterate through mappings to find the highest role - # Priority: admin > user potential_roles = [] for mapping in role_mappings: group_name = mapping["group"] target_role = mapping["role"] - # Construct group DN if it's just a common name, else use as is + # Construct group DN if it's just a common name if "=" not in group_name: full_group_dn = f"cn={group_name},{groups_dn},{base_dn}" else: full_group_dn = group_name - + + full_group_dn_lower = full_group_dn.lower() + log.debug(f"LDAP: Checking membership in group: {full_group_dn}") - conn.search(full_group_dn, '(objectClass=*)', attributes=['member']) + + # Method 1: Check memberOf if available (AD/LLDAP) + if full_group_dn_lower in user_groups: + log.debug(f"LDAP: Match found via memberOf for {target_role}") + potential_roles.append(target_role) + continue + # Method 2: Search group's member attribute (Standard LDAP) + conn.search(full_group_dn, '(objectClass=*)', attributes=['member', 'uniqueMember']) if conn.entries: - members = conn.entries[0].member.values - if real_user_dn in members or user_dn in members or \ - any(m.lower().replace(" ", "") == real_user_dn.lower().replace(" ", "") for m in members): - log.debug(f"LDAP: User is in group {group_name}, assigning role: {target_role}") + members = [] + if hasattr(conn.entries[0], 'member'): + members = [str(m).lower() for m in conn.entries[0].member.values] + elif hasattr(conn.entries[0], 'uniqueMember'): + members = [str(m).lower() for m in conn.entries[0].uniqueMember.values] + + if real_user_dn.lower() in members or user_dn.lower() in members: + log.debug(f"LDAP: Match found via group search for {target_role}") potential_roles.append(target_role) if "admin" in potential_roles: diff --git a/config/Caddyfile b/config/Caddyfile index 28f82987..cdefa387 100644 --- a/config/Caddyfile +++ b/config/Caddyfile @@ -1,21 +1,35 @@ -# TFM aInventory - Caddy Explicit IP Configuration -# Version 1.9.10 - Focused on resolving Protocol Errors on private IPs. +# TFM aInventory - Caddy Patched IP Configuration +# Version 1.9.12 - Secure Seal & IP Stability { - # Global options admin off - # Disable automatic HTTP->HTTPS redirects to prevent port conflicts on non-standard ports auto_https disable_redirects + + # Trust local CA for internal certificate issuance + local_certs } -# Frontend SSL Proxy - Explicit address to improve handshake success +# Frontend SSL Proxy (8919 -> 443) https://{$SERVER_IP:192.168.84.113}:443 { - tls internal + tls internal { + on_demand + } reverse_proxy frontend:3000 + + # Security Headers for PWA + header { + Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" + X-XSS-Protection "1; mode=block" + X-Content-Type-Options "nosniff" + X-Frame-Options "SAMEORIGIN" + Referrer-Policy "strict-origin-when-cross-origin" + } } -# Backend SSL Proxy +# Backend SSL Proxy (8918 -> 444) https://{$SERVER_IP:192.168.84.113}:444 { - tls internal + tls internal { + on_demand + } reverse_proxy backend:8000 } diff --git a/config/proxy/Dockerfile b/config/proxy/Dockerfile new file mode 100644 index 00000000..1ada1851 --- /dev/null +++ b/config/proxy/Dockerfile @@ -0,0 +1,8 @@ +FROM caddy:2-alpine + +# Install nss-tools to allow Caddy to manage its internal trust store (fixes certutil warning) +# Install ca-certificates to ensure Caddy can trust external sites if needed +RUN apk add --no-cache nss-tools ca-certificates + +# Expose the internal proxy ports +EXPOSE 80 443 444 diff --git a/deploy.sh b/deploy.sh index ea041e71..792d08c9 100755 --- a/deploy.sh +++ b/deploy.sh @@ -1,6 +1,6 @@ #!/bin/bash # ============================================================================= -# TFM aInventory - Bulletproof Deployment Script (v1.9.10) +# TFM aInventory - Bulletproof Deployment Script (v1.9.12) # ============================================================================= set -e @@ -30,7 +30,7 @@ for arg in "$@"; do --help) echo "Usage: ./deploy.sh [options]" echo "Options:" - echo " --reset-ssl Clear Caddy storage and reset certificates" + echo " --reset-ssl Clear Caddy storage and reset certificates (Aggressive)" echo " --reset-admin Force reset Admin password to 'Admin123!'" echo " --help Show this help message" exit 0 @@ -39,19 +39,25 @@ for arg in "$@"; do done if [ "$RESET_SSL" = true ]; then - echo "๐Ÿงน Resetting SSL certificates..." + echo "๐Ÿงน Aggressive SSL Reset in progress..." docker compose down + # Clear internal docker volumes docker volume rm -f inventory_caddy_data 2>/dev/null || true - echo "โœ… SSL data cleared." + docker volume rm -f inventory_caddy_config 2>/dev/null || true + # Clear persistent host volumes if they exist + rm -rf ./data/caddy_data/* 2>/dev/null || true + rm -rf ./data/caddy_config/* 2>/dev/null || true + echo "โœ… SSL storage completely cleared." fi echo "๐Ÿš€ Starting TFM aInventory Services..." +# Use --build to ensure the custom Caddy image is built docker compose --env-file inventory.env up -d --build --remove-orphans if [ "$RESET_ADMIN" = true ]; then echo "๐Ÿ” Resetting Admin credentials..." # Wait for container to be ready - sleep 2 + sleep 3 docker compose exec backend python3 -m backend.scripts.reset_admin fi @@ -60,17 +66,18 @@ echo "๐Ÿ” Verifying port mapping..." docker compose ps --format "table {{.Name}}\t{{.Status}}\t{{.Ports}}" echo "" -echo "๐Ÿš€ DIAGNOSTIC LOGS (Proxy SSL Handshake):" +echo "๐Ÿš€ DIAGNOSTIC LOGS (Proxy Status):" docker compose logs proxy --tail 20 echo "" -echo "โœ… Deployment complete." +echo "โœ… Deployment complete (v1.9.12)." echo " ------------------------------------------------------------" -echo " DEFAULT CREDENTIALS:" +echo " ACCESS COORDINATES:" +echo " 1. SECURE: https://${SERVER_IP:-localhost}:${FRONTEND_SSL_PORT:-8919}" +echo " 2. DIRECT: http://${SERVER_IP:-localhost}:${FRONTEND_PORT:-8917}" +echo " ------------------------------------------------------------" +echo " CREDENTIALS (if first run or reset):" echo " User: Admin" echo " Pass: Admin123!" echo " ------------------------------------------------------------" -echo " 1. SECURE ACCESS: https://${SERVER_IP:-localhost}:${FRONTEND_SSL_PORT:-8919}" -echo " 2. DIRECT ACCESS: http://${SERVER_IP:-localhost}:${FRONTEND_PORT:-8917}" -echo " ------------------------------------------------------------" echo "" diff --git a/docker-compose.yml b/docker-compose.yml index 7ddcd86f..51d5ea5d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,6 +12,7 @@ services: volumes: - ./data:/app/data - ./logs:/app/logs + - ./config:/app/config - ./scripts:/app/scripts:ro environment: - DATA_DIR=/app/data @@ -36,7 +37,9 @@ services: restart: unless-stopped proxy: - image: caddy:alpine + build: + context: . + dockerfile: config/proxy/Dockerfile networks: - inventory_net ports: diff --git a/frontend/VERSION.json b/frontend/VERSION.json index b2e4784a..7a96fb62 100644 --- a/frontend/VERSION.json +++ b/frontend/VERSION.json @@ -1 +1 @@ -{"version": "1.9.10", "last_build": "2026-04-13-2207", "codename": "AccessRecovery", "commit": "bdf6d605"} +{"version": "1.9.11", "last_build": "2026-04-13-2215", "codename": "Convergence", "commit": "9d7e4f0c"}