Files

160 lines
5.7 KiB
Bash
Executable File

#!/usr/bin/env bash
set -Eeuo pipefail
dhv2_base_url="${DHV2_BASE_URL:-https://dhv2.korexlabs.com}"
dhv2_base_url="${dhv2_base_url%/}"
dhv2_tmp="$(mktemp -d)"
umask 077
cleanup() {
rm -rf "$dhv2_tmp"
unset dhv2_username dhv2_password dhv2_csrf
}
trap cleanup EXIT
fail() {
echo "ERROR: $*" >&2
exit 1
}
command -v curl >/dev/null || fail "curl no está instalado"
command -v python3 >/dev/null || fail "python3 no está instalado"
echo "========== A7 · PRUEBAS SIN SESIÓN =========="
for dhv2_path in dashboard/summary users roles audit; do
dhv2_code="$(curl -sS --connect-timeout 10 --max-time 30 \
-o "$dhv2_tmp/public.json" -w '%{http_code}' \
"$dhv2_base_url/api/v3/$dhv2_path")"
test "$dhv2_code" = "401" || fail "/api/v3/$dhv2_path devolvió HTTP $dhv2_code; se esperaba 401"
echo "/api/v3/$dhv2_path sin sesión: 401 OK"
done
echo
echo "========== A7 · LOGIN INVÁLIDO GENÉRICO =========="
DHV2_PAYLOAD_PATH="$dhv2_tmp/invalid-payload.json" python3 - <<'PY'
import json
import os
from pathlib import Path
Path(os.environ['DHV2_PAYLOAD_PATH']).write_text(json.dumps({
'identifier': 'a7_usuario_inexistente',
'password': 'A7-invalida-2026',
'deviceLabel': 'Prueba A7',
}), encoding='utf-8')
PY
dhv2_invalid_code="$(curl -sS --connect-timeout 10 --max-time 30 \
-o "$dhv2_tmp/invalid.json" -w '%{http_code}' \
-H 'Content-Type: application/json' \
--data-binary "@$dhv2_tmp/invalid-payload.json" \
"$dhv2_base_url/api/v3/auth/login")"
test "$dhv2_invalid_code" = "401" || fail "El login inválido devolvió HTTP $dhv2_invalid_code; se esperaba 401"
grep -q '"code":"INVALID_CREDENTIALS"' "$dhv2_tmp/invalid.json" || fail "El login inválido no devolvió el error genérico esperado"
echo "Login inválido genérico: 401 OK"
echo
echo "========== A7 · LOGIN ADMINISTRATIVO =========="
dhv2_username="${DHV2_TEST_USERNAME:-}"
dhv2_password="${DHV2_TEST_PASSWORD:-}"
if [ -z "$dhv2_username" ]; then
read -r -p "Usuario administrador: " dhv2_username
fi
if [ -z "$dhv2_password" ]; then
read -r -s -p "Contraseña: " dhv2_password
echo
fi
test -n "$dhv2_username" || fail "El usuario es obligatorio"
test -n "$dhv2_password" || fail "La contraseña es obligatoria"
DHV2_USERNAME="$dhv2_username" \
DHV2_PASSWORD="$dhv2_password" \
DHV2_PAYLOAD_PATH="$dhv2_tmp/login-payload.json" \
python3 - <<'PY'
import json
import os
from pathlib import Path
Path(os.environ['DHV2_PAYLOAD_PATH']).write_text(json.dumps({
'identifier': os.environ['DHV2_USERNAME'],
'password': os.environ['DHV2_PASSWORD'],
'deviceLabel': 'Aceptación Fase A7',
}), encoding='utf-8')
PY
dhv2_login_code="$(curl -sS --connect-timeout 10 --max-time 30 \
-D "$dhv2_tmp/login.headers" \
-c "$dhv2_tmp/cookies.txt" \
-o "$dhv2_tmp/login.json" \
-w '%{http_code}' \
-H 'Content-Type: application/json' \
--data-binary "@$dhv2_tmp/login-payload.json" \
"$dhv2_base_url/api/v3/auth/login")"
rm -f "$dhv2_tmp/login-payload.json"
unset dhv2_password
test "$dhv2_login_code" = "200" || fail "El login administrativo devolvió HTTP $dhv2_login_code"
grep -q '"mustChangePassword":false' "$dhv2_tmp/login.json" || fail "La cuenta todavía exige cambiar la contraseña desde el panel"
dhv2_csrf="$(DHV2_RESPONSE_PATH="$dhv2_tmp/login.json" python3 - <<'PY'
import json
import os
from pathlib import Path
data = json.loads(Path(os.environ['DHV2_RESPONSE_PATH']).read_text(encoding='utf-8'))
print(data.get('csrfToken', ''))
PY
)"
test -n "$dhv2_csrf" || fail "El login no devolvió un token CSRF"
dhv2_secure_cookie_count="$(grep -Ei '^set-cookie:' "$dhv2_tmp/login.headers" | grep -Eic 'Secure.*SameSite=Strict' || true)"
dhv2_http_only_count="$(grep -Ei '^set-cookie:' "$dhv2_tmp/login.headers" | grep -Eic 'HttpOnly.*Secure.*SameSite=Strict' || true)"
test "$dhv2_secure_cookie_count" -ge 3 || fail "No se detectaron las tres cookies Secure + SameSite=Strict"
test "$dhv2_http_only_count" -ge 2 || fail "Las cookies de acceso y refresh no tienen todos los atributos requeridos"
echo "Login y atributos de cookies: OK"
echo
echo "========== A7 · ENDPOINTS AUTENTICADOS =========="
for dhv2_path in auth/me dashboard/summary 'users?page=1&pageSize=1' roles 'audit?page=1&pageSize=1'; do
dhv2_safe_name="${dhv2_path//[^a-zA-Z0-9]/_}"
dhv2_code="$(curl -sS --connect-timeout 10 --max-time 30 \
-b "$dhv2_tmp/cookies.txt" \
-o "$dhv2_tmp/$dhv2_safe_name.json" \
-w '%{http_code}' \
"$dhv2_base_url/api/v3/$dhv2_path")"
test "$dhv2_code" = "200" || fail "/api/v3/$dhv2_path devolvió HTTP $dhv2_code; se esperaba 200"
echo "/api/v3/$dhv2_path autenticado: 200 OK"
done
echo
echo "========== A7 · CSRF Y LOGOUT =========="
dhv2_csrf_failure="$(curl -sS --connect-timeout 10 --max-time 30 \
-b "$dhv2_tmp/cookies.txt" \
-o "$dhv2_tmp/csrf-failure.json" \
-w '%{http_code}' \
-X POST "$dhv2_base_url/api/v3/auth/logout")"
test "$dhv2_csrf_failure" = "403" || fail "Una escritura sin CSRF devolvió HTTP $dhv2_csrf_failure; se esperaba 403"
grep -q '"code":"CSRF_INVALID"' "$dhv2_tmp/csrf-failure.json" || fail "No se devolvió CSRF_INVALID"
echo "Escritura sin CSRF: 403 OK"
dhv2_logout_code="$(curl -sS --connect-timeout 10 --max-time 30 \
-b "$dhv2_tmp/cookies.txt" \
-c "$dhv2_tmp/cookies.txt" \
-o "$dhv2_tmp/logout.json" \
-w '%{http_code}' \
-H "x-csrf-token: $dhv2_csrf" \
-X POST "$dhv2_base_url/api/v3/auth/logout")"
test "$dhv2_logout_code" = "200" || fail "Logout devolvió HTTP $dhv2_logout_code"
dhv2_after_logout="$(curl -sS --connect-timeout 10 --max-time 30 \
-b "$dhv2_tmp/cookies.txt" \
-o "$dhv2_tmp/after-logout.json" \
-w '%{http_code}' \
"$dhv2_base_url/api/v3/auth/me")"
test "$dhv2_after_logout" = "401" || fail "La sesión siguió activa después del logout"
echo "Logout y revocación de sesión: OK"
echo
echo "ACEPTACIÓN AUTENTICADA DE FASE A: OK"