diff --git a/.env.example b/.env.example index b8d37bb..85723bb 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,9 @@ APP_NAME="Arcos" APP_ENV=local APP_KEY=base64:2qBv3agj3nPac2/LlDEAiQBE3vV7ycffh4lhc0ksfGM= +# Clave dedicada para tokens de arcos RFID (NO CAMBIAR DESPUÉS DE GENERARLA) +# Generar con: php -r "echo 'base64:' . base64_encode(random_bytes(32)) . PHP_EOL;" +ARCO_TOKEN_ENCRYPTION_KEY= APP_DEBUG=true APP_TIMEZONE=America/Mexico_City APP_URL=http://localhost:8080 @@ -55,7 +58,7 @@ CACHE_PREFIX= MEMCACHED_HOST=127.0.0.1 REDIS_CLIENT=phpredis -REDIS_HOST=127.0.0.1 +REDIS_HOST=redis REDIS_PASSWORD=null REDIS_PORT=6379 diff --git a/app/Helpers/EncryptionHelper.php b/app/Helpers/EncryptionHelper.php index 7c7b704..272071f 100644 --- a/app/Helpers/EncryptionHelper.php +++ b/app/Helpers/EncryptionHelper.php @@ -1,68 +1,10 @@ getMessage()); - } - } - - /** - * Decrypt the given data (arrays/objects). - */ - public static function decryptData($encryptedData) - { - try { - $decrypted = Crypt::decryptString($encryptedData); - return json_decode($decrypted, true); - } catch (DecryptException $e) { - Log::error('Error al desencriptar los datos: ' . $e->getMessage()); - return null; - } catch (\Exception $e) { - Log::error('Error inesperado al desencriptar los datos: ' . $e->getMessage()); - return null; - } - } - - /** - * Encrypt a simple string (for tokens, passwords, etc.) - */ - public static function encryptString(string $string): string - { - try { - return Crypt::encryptString($string); - } catch (\Exception $e) { - throw new \RuntimeException("Error al encriptar el string: " . $e->getMessage()); - } - } - - /** - * Decrypt a simple string - */ - public static function decryptString(string $encryptedString): ?string - { - try { - return Crypt::decryptString($encryptedString); - } catch (DecryptException $e) { - Log::error('Error al desencriptar el string: ' . $e->getMessage()); - return null; - } catch (\Exception $e) { - Log::error('Error inesperado al desencriptar el string: ' . $e->getMessage()); - return null; - } - } /** * Encrypt using a custom key (independent of APP_KEY) @@ -133,50 +75,4 @@ public static function verifyWithCustomKey(string $plainValue, string $encrypted } } - /** - * Generate a hash for searchable encrypted data - */ - public static function hash(string $data, string $algorithm = 'sha256'): string - { - return hash($algorithm, $data); - } - - /** - * Encrypt multiple fields in an array - */ - public static function encryptFields(array $data, array $fields): array - { - foreach ($fields as $field) { - if (isset($data[$field])) { - $data[$field] = self::encryptData($data[$field]); - } - } - return $data; - } - - /** - * Decrypt multiple fields in an array - */ - public static function decryptFields(array $data, array $fields): array - { - foreach ($fields as $field) { - if (isset($data[$field])) { - $data[$field] = self::decryptData($data[$field]); - } - } - return $data; - } - - /** - * Verify if a plain value matches an encrypted value - */ - public static function verifyEncrypted(string $plainValue, string $encryptedValue): bool - { - try { - $decrypted = self::decryptString($encryptedValue); - return $decrypted === $plainValue; - } catch (\Exception $e) { - return false; - } - } } diff --git a/app/Helpers/arco.php b/app/Helpers/arco.php new file mode 100644 index 0000000..e69de29 diff --git a/app/Http/Middleware/ArcoTokenMiddleware.php b/app/Http/Middleware/ArcoTokenMiddleware.php index 23ad22a..020cb55 100644 --- a/app/Http/Middleware/ArcoTokenMiddleware.php +++ b/app/Http/Middleware/ArcoTokenMiddleware.php @@ -23,7 +23,7 @@ public function handle(Request $request, Closure $next): Response ]); } - // Buscar arco por token plano (el método desencripta internamente) + // Buscar arco por token plano $arco = Arco::buscarPorToken($tokenPlano); if (!$arco) { @@ -39,18 +39,23 @@ public function handle(Request $request, Closure $next): Response } // Validación de IP deshabilitada para desarrollo - // TODO: Habilitar en producción o configurar nginx para enviar X-Forwarded-For - // $requestIp = $request->ip(); - // if ($arco->ip_address !== $requestIp) { - // return ApiResponse::FORBIDDEN->response([ - // 'message' => 'Token no autorizado para esta IP', - // 'detail' => [ - // 'ip_registrada_arco' => $arco->ip_address, - // 'ip_del_request' => $requestIp, - // 'arco_nombre' => $arco->nombre - // ] - // ]); - // } + // PROBLEMA: En Docker, $request->ip() retorna la IP del gateway (172.x.x.x), no la IP real del cliente + // SOLUCIÓN PRODUCCIÓN: Usar un proxy inverso externo (nginx/traefik) que pase X-Forwarded-For correctamente + // Para desarrollo, la validación de token es suficiente + /* + $requestIp = $request->ip(); + if ($arco->ip_address !== $requestIp) { + return ApiResponse::FORBIDDEN->response([ + 'message' => 'Token no autorizado para esta IP', + 'detail' => [ + 'ip_registrada_arco' => $arco->ip_address, + 'ip_del_request' => $requestIp, + 'arco_nombre' => $arco->nombre + ] + ]); + } + */ + // Agregar el arco al request para uso posterior $request->merge(['arco_autenticado' => $arco]); diff --git a/app/Models/Arco.php b/app/Models/Arco.php index 7b4377e..a3921f5 100644 --- a/app/Models/Arco.php +++ b/app/Models/Arco.php @@ -49,11 +49,33 @@ protected static function boot() static::creating(function ($arco) { if (!$arco->api_token) { $plainToken = Str::random(64); - $arco->api_token = EncryptionHelper::encryptString($plainToken); + $arco->api_token = EncryptionHelper::encryptWithCustomKey( + $plainToken, + self::getEncryptionKey() + ); } }); } + /** + * Obtener la clave de encriptación personalizada para tokens de arcos + */ + private static function getEncryptionKey(): string + { + $key = config('app.arco_token_encryption_key'); + + if (empty($key)) { + throw new \RuntimeException('ARCO_TOKEN_ENCRYPTION_KEY no está configurada en .env'); + } + + // Si la clave está en formato base64:xxxx, decodificarla + if (str_starts_with($key, 'base64:')) { + return base64_decode(substr($key, 7)); + } + + return $key; + } + /** * Relación con detecciones */ @@ -87,7 +109,7 @@ public static function buscarPorToken(string $tokenPlano): ?self foreach ($arcos as $arco) { // Desencriptar el token de la BD y comparar con el token plano recibido - if (EncryptionHelper::verifyEncrypted($tokenPlano, $arco->api_token)) { + if (EncryptionHelper::verifyWithCustomKey($tokenPlano, $arco->api_token, self::getEncryptionKey())) { return $arco; } } @@ -101,7 +123,10 @@ public static function buscarPorToken(string $tokenPlano): ?self public function regenerarToken(): string { $plainToken = Str::random(64); - $this->api_token = EncryptionHelper::encryptString($plainToken); + $this->api_token = EncryptionHelper::encryptWithCustomKey( + $plainToken, + self::getEncryptionKey() + ); $this->save(); return $plainToken; @@ -112,6 +137,6 @@ public function regenerarToken(): string */ public function obtenerTokenDesencriptado(): ?string { - return EncryptionHelper::decryptString($this->api_token); + return EncryptionHelper::decryptWithCustomKey($this->api_token, self::getEncryptionKey()); } } diff --git a/config/app.php b/config/app.php index 004d7e0..ba02ac4 100644 --- a/config/app.php +++ b/config/app.php @@ -104,6 +104,19 @@ 'key' => env('APP_KEY'), + /* + |-------------------------------------------------------------------------- + | Arco Token Encryption Key + |-------------------------------------------------------------------------- + | + | This key is used to encrypt the API tokens for RFID arcos. Unlike APP_KEY, + | this key should NEVER change once set, as changing it will invalidate all + | existing arco tokens. Store this key securely and back it up. + | + */ + + 'arco_token_encryption_key' => env('ARCO_TOKEN_ENCRYPTION_KEY'), + 'previous_keys' => [ ...array_filter( explode(',', env('APP_PREVIOUS_KEYS', '')) diff --git a/docker-compose.yml b/docker-compose.yml index 8034c8c..4f09e94 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,6 +3,9 @@ services: build: context: . dockerfile: dockerfile + args: + USER_ID: 1000 + GROUP_ID: 1000 working_dir: /var/www/arcos-backend environment: - DB_HOST=mysql diff --git a/dockerfile b/dockerfile index 49c206a..76f1112 100644 --- a/dockerfile +++ b/dockerfile @@ -15,6 +15,7 @@ RUN apk add --no-cache \ openssl \ bash \ mysql-client \ + su-exec \ && docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd zip # Instalar extensión de Redis @@ -33,10 +34,11 @@ COPY . . COPY entrypoint-dev.sh /usr/local/bin/entrypoint-dev.sh RUN chmod +x /usr/local/bin/entrypoint-dev.sh -RUN mkdir -p storage/app/keys storage/logs bootstrap/cache - -RUN chown -R www-data:www-data /var/www/arcos-backend/storage /var/www/arcos-backend/bootstrap/cache -RUN chmod -R 775 /var/www/arcos-backend/storage /var/www/arcos-backend/bootstrap/cache +ARG USER_ID=1000 +ARG GROUP_ID=1000 +RUN apk add --no-cache shadow && \ + usermod -u ${USER_ID} www-data && \ + groupmod -g ${GROUP_ID} www-data EXPOSE 9000 diff --git a/entrypoint-dev.sh b/entrypoint-dev.sh index 932cb96..803cc65 100644 --- a/entrypoint-dev.sh +++ b/entrypoint-dev.sh @@ -1,10 +1,16 @@ #!/bin/bash set -e -git config --global --add safe.directory /var/www/repuve-v1 +# Configurar Git sin necesidad de permisos globales +export GIT_CONFIG_GLOBAL=/tmp/.gitconfig +git config --global --add safe.directory /var/www/pdv.backend echo "=== Iniciando entrypoint DESARROLLO ===" +# Asegurar permisos correctos para directorios críticos +echo "Verificando permisos de directorios..." +chown -R www-data:www-data /var/www/arcos-backend/vendor /var/www/arcos-backend/storage /var/www/arcos-backend/bootstrap/cache 2>/dev/null || true + # Variables desde Docker environment DB_HOST=${DB_HOST:-mysql} DB_USERNAME=${DB_USERNAME:-root} @@ -79,7 +85,6 @@ fi # Establecer permisos correctos para las claves chmod 600 storage/app/keys/oauth-private.key chmod 644 storage/app/keys/oauth-public.key -chown www-data:www-data storage/app/keys/oauth-*.key echo "✓ Claves de Passport verificadas" @@ -111,5 +116,5 @@ echo "✓ Configuración de desarrollo completada" echo "=== Iniciando PHP-FPM DESARROLLO ===" -# Iniciar PHP-FPM -exec "$@" +# Iniciar PHP-FPM como usuario www-data +exec su-exec www-data "$@"