fix: tokens de arcos

This commit is contained in:
Juan Felipe Zapata Moreno 2026-01-07 20:31:23 -06:00
parent 50e028827f
commit 9bd301b55d
9 changed files with 82 additions and 130 deletions

View File

@ -1,6 +1,9 @@
APP_NAME="Arcos" APP_NAME="Arcos"
APP_ENV=local APP_ENV=local
APP_KEY=base64:2qBv3agj3nPac2/LlDEAiQBE3vV7ycffh4lhc0ksfGM= 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_DEBUG=true
APP_TIMEZONE=America/Mexico_City APP_TIMEZONE=America/Mexico_City
APP_URL=http://localhost:8080 APP_URL=http://localhost:8080
@ -55,7 +58,7 @@ CACHE_PREFIX=
MEMCACHED_HOST=127.0.0.1 MEMCACHED_HOST=127.0.0.1
REDIS_CLIENT=phpredis REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1 REDIS_HOST=redis
REDIS_PASSWORD=null REDIS_PASSWORD=null
REDIS_PORT=6379 REDIS_PORT=6379

View File

@ -1,68 +1,10 @@
<?php <?php
namespace App\Helpers; namespace App\Helpers;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
class EncryptionHelper class EncryptionHelper
{ {
/**
* Encrypt the given data (arrays/objects).
*/
public static function encryptData($data)
{
try {
return Crypt::encryptString(json_encode($data));
} catch (\Exception $e) {
throw new \RuntimeException("Error al encriptar los datos: " . $e->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) * 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;
}
}
} }

0
app/Helpers/arco.php Normal file
View File

View File

@ -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); $arco = Arco::buscarPorToken($tokenPlano);
if (!$arco) { if (!$arco) {
@ -39,18 +39,23 @@ public function handle(Request $request, Closure $next): Response
} }
// Validación de IP deshabilitada para desarrollo // Validación de IP deshabilitada para desarrollo
// TODO: Habilitar en producción o configurar nginx para enviar X-Forwarded-For // PROBLEMA: En Docker, $request->ip() retorna la IP del gateway (172.x.x.x), no la IP real del cliente
// $requestIp = $request->ip(); // SOLUCIÓN PRODUCCIÓN: Usar un proxy inverso externo (nginx/traefik) que pase X-Forwarded-For correctamente
// if ($arco->ip_address !== $requestIp) { // Para desarrollo, la validación de token es suficiente
// return ApiResponse::FORBIDDEN->response([ /*
// 'message' => 'Token no autorizado para esta IP', $requestIp = $request->ip();
// 'detail' => [ if ($arco->ip_address !== $requestIp) {
// 'ip_registrada_arco' => $arco->ip_address, return ApiResponse::FORBIDDEN->response([
// 'ip_del_request' => $requestIp, 'message' => 'Token no autorizado para esta IP',
// 'arco_nombre' => $arco->nombre 'detail' => [
// ] 'ip_registrada_arco' => $arco->ip_address,
// ]); 'ip_del_request' => $requestIp,
// } 'arco_nombre' => $arco->nombre
]
]);
}
*/
// Agregar el arco al request para uso posterior // Agregar el arco al request para uso posterior
$request->merge(['arco_autenticado' => $arco]); $request->merge(['arco_autenticado' => $arco]);

View File

@ -49,11 +49,33 @@ protected static function boot()
static::creating(function ($arco) { static::creating(function ($arco) {
if (!$arco->api_token) { if (!$arco->api_token) {
$plainToken = Str::random(64); $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 * Relación con detecciones
*/ */
@ -87,7 +109,7 @@ public static function buscarPorToken(string $tokenPlano): ?self
foreach ($arcos as $arco) { foreach ($arcos as $arco) {
// Desencriptar el token de la BD y comparar con el token plano recibido // 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; return $arco;
} }
} }
@ -101,7 +123,10 @@ public static function buscarPorToken(string $tokenPlano): ?self
public function regenerarToken(): string public function regenerarToken(): string
{ {
$plainToken = Str::random(64); $plainToken = Str::random(64);
$this->api_token = EncryptionHelper::encryptString($plainToken); $this->api_token = EncryptionHelper::encryptWithCustomKey(
$plainToken,
self::getEncryptionKey()
);
$this->save(); $this->save();
return $plainToken; return $plainToken;
@ -112,6 +137,6 @@ public function regenerarToken(): string
*/ */
public function obtenerTokenDesencriptado(): ?string public function obtenerTokenDesencriptado(): ?string
{ {
return EncryptionHelper::decryptString($this->api_token); return EncryptionHelper::decryptWithCustomKey($this->api_token, self::getEncryptionKey());
} }
} }

View File

@ -104,6 +104,19 @@
'key' => env('APP_KEY'), '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' => [ 'previous_keys' => [
...array_filter( ...array_filter(
explode(',', env('APP_PREVIOUS_KEYS', '')) explode(',', env('APP_PREVIOUS_KEYS', ''))

View File

@ -3,6 +3,9 @@ services:
build: build:
context: . context: .
dockerfile: dockerfile dockerfile: dockerfile
args:
USER_ID: 1000
GROUP_ID: 1000
working_dir: /var/www/arcos-backend working_dir: /var/www/arcos-backend
environment: environment:
- DB_HOST=mysql - DB_HOST=mysql

View File

@ -15,6 +15,7 @@ RUN apk add --no-cache \
openssl \ openssl \
bash \ bash \
mysql-client \ mysql-client \
su-exec \
&& docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd zip && docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath gd zip
# Instalar extensión de Redis # Instalar extensión de Redis
@ -33,10 +34,11 @@ COPY . .
COPY entrypoint-dev.sh /usr/local/bin/entrypoint-dev.sh COPY entrypoint-dev.sh /usr/local/bin/entrypoint-dev.sh
RUN chmod +x /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 ARG USER_ID=1000
ARG GROUP_ID=1000
RUN chown -R www-data:www-data /var/www/arcos-backend/storage /var/www/arcos-backend/bootstrap/cache RUN apk add --no-cache shadow && \
RUN chmod -R 775 /var/www/arcos-backend/storage /var/www/arcos-backend/bootstrap/cache usermod -u ${USER_ID} www-data && \
groupmod -g ${GROUP_ID} www-data
EXPOSE 9000 EXPOSE 9000

View File

@ -1,10 +1,16 @@
#!/bin/bash #!/bin/bash
set -e 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 ===" 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 # Variables desde Docker environment
DB_HOST=${DB_HOST:-mysql} DB_HOST=${DB_HOST:-mysql}
DB_USERNAME=${DB_USERNAME:-root} DB_USERNAME=${DB_USERNAME:-root}
@ -79,7 +85,6 @@ fi
# Establecer permisos correctos para las claves # Establecer permisos correctos para las claves
chmod 600 storage/app/keys/oauth-private.key chmod 600 storage/app/keys/oauth-private.key
chmod 644 storage/app/keys/oauth-public.key chmod 644 storage/app/keys/oauth-public.key
chown www-data:www-data storage/app/keys/oauth-*.key
echo "✓ Claves de Passport verificadas" echo "✓ Claves de Passport verificadas"
@ -111,5 +116,5 @@ echo "✓ Configuración de desarrollo completada"
echo "=== Iniciando PHP-FPM DESARROLLO ===" echo "=== Iniciando PHP-FPM DESARROLLO ==="
# Iniciar PHP-FPM # Iniciar PHP-FPM como usuario www-data
exec "$@" exec su-exec www-data "$@"