Skip to content

Releases: jandrescodes/Encriptacion_PHP

v1.14.1

Choose a tag to compare

@jandrescodes jandrescodes released this 15 Jul 14:56

Fixed

  • Weak password bypass via password resetAuthController::resetPassword() only checked that new_password matched confirm_password, never enforcing the 8-character minimum already required by ProfileController::changePassword() and UserController::validateUser(). A valid reset token could be used to set an empty or trivially short password. Now enforces strlen($newPassword) >= 8 before calling Auth::consumeResetToken().

Changed

  • CLAUDE.md — removed two Notes bullets duplicating facts already covered in the Key Files table and Security Patterns section (session_start_secure() usage, AuthMiddleware::session() wiring); documented the resetPassword fix above
  • README.md — restructured for a public-facing audience:
    • Added Table of Contents and a Screenshots section (title + description + image per feature, no tables)
    • Features reorganized into three grouped categories instead of a 25-bullet list that duplicated the Security section's implementation detail; cross-linked to Security for specifics
    • Installation no longer pastes the full .env contents — points to .env.example instead
    • Project Structure reduced from a fully-annotated recursive tree to a 2-level overview, with a pointer to CLAUDE.md for the file-by-file breakdown
    • Fixed a stale/vague comment in the Testing setup instructions referencing a non-existent ".env.testing section in docs"

Full Changelog

https://github.com/Jandres25/Encriptacion_PHP/blob/master/CHANGELOG.md

v1.14.0

Choose a tag to compare

@jandrescodes jandrescodes released this 07 Jul 18:39

Added

  • Active sessions management — users can view and revoke their own logged-in sessions across devices:
    • New table user_sessions (id, user_id, token_hash, ip_address, user_agent, via_remember, created_at, last_activity); token_hash is a UNIQUE SHA-256 hash of a random session token — the raw token is stored only in $_SESSION['session_token'], never in the DB
    • A row is created on every successful login, both password login and remember-me auto-login (Auth::restoreFromCookie() / AuthController::login()), via UserSession::create() / UserSession::createTo()
    • New AuthMiddleware::session(\mysqli) guard — validates the current session's hash is still active (existsActive() against SESSION_TIMEOUT) and touches last_activity; if the row was deleted (revoked elsewhere), it destroys the session and redirects to /login with a warning flash
    • New GET /sessions — lists all of the user's active sessions (device/browser, IP, created, last activity, current-session badge) via App\Controller\SessionController::index()
    • New POST /sessions/revoke — revokes a single session by id, scoped to the authenticated user_id (cannot revoke another user's session even by guessing an id)
    • New POST /sessions/revoke-others — revokes every session except the current one (revokeAllExcept())
    • Session row deleted on logout (AuthController::logout()) and cascades on user deletion (ON DELETE CASCADE)
    • New env var ACTIVE_SESSIONS_ENABLED (default true) — disables the revocation check in AuthMiddleware::session() without removing session tracking
    • Both revoke actions log ActivityLog::EVENT_SESSION_REVOKED
    • 13 new tests in tests/Unit/UserSessionTest.php — hash storage (never raw token), via_remember flag, existsActive() TTL boundary, getForUser() ordering and is_current flag, revoke() ownership scoping, revokeAllExcept() count and exclusion, deleteByTokenHash(), purgeExpired(); 71 tests in total

Changed

  • app/Core/Auth.php — issues session_token and creates the user_sessions row on remember-me auto-login (restoreFromCookie())
  • app/Controller/AuthController.php — issues session_token and creates the user_sessions row on password login; deletes the row by token hash on logout
  • app/Middleware/AuthMiddleware.php — new static session(\mysqli $connection): void
  • routes/web.php — new routes GET /sessions, POST /sessions/revoke, POST /sessions/revoke-others
  • database/schema.sql / database/schema_test.sql — new user_sessions table
  • app/Model/ActivityLog.php — new event constant EVENT_SESSION_REVOKED

Full Changelog

https://github.com/Jandres25/Encriptacion_PHP/blob/master/CHANGELOG.md

v1.13.0

Choose a tag to compare

@jandrescodes jandrescodes released this 24 Jun 14:59

Added

  • Filtros server-side en Audit Log/activity-logs ahora usa DataTables server-side processing con filtros por evento, usuario y rango de fechas:
    • Nuevo endpoint GET /activity-logs/data que devuelve JSON para DataTables (protocolo draw / recordsTotal / recordsFiltered / data)
    • Formulario de filtros colapsable (Bootstrap collapse) sobre la tabla: select de evento, input de username (match parcial), inputs date_from / date_to; badge warning en el toggle cuando hay filtros activos
    • DataTables reconfigured to serverSide: true — la tabla empieza vacía y carga datos vía AJAX; searching: false desactiva el input nativo de DT (reemplazado por el formulario propio)
    • Botones de export (Copy, PDF, Excel, CSV, Print) y ColVis conservados; exportan la página visible (comportamiento esperado con server-side processing)
    • Seguridad: event validado con allow-list de constantes EVENT_*; fechas validadas estrictamente con DateTime::createFromFormat; username con trim() + cap de 100 caracteres; length restringido a [10, 25, 50, 100]; XSS en JSON mitigado con htmlspecialchars() por celda
    • 8 nuevos tests en tests/Unit/ActivityLogTest.php — filtro por evento, match parcial de username, rango de fechas, LIMIT/OFFSET, getTotalCount() con y sin filtros, combinación AND; 58 tests en total

Changed

  • app/Model/ActivityLog.phpgetAll() reescrito con prepared statements y WHERE dinámico; nueva firma getAll(array $filters = [], ?int $limit = null, ?int $offset = null): array; nuevo método getTotalCount(array $filters = []): int; método privado buildWhere(array $filters): array
  • app/Controller/ActivityLogController.php — nuevo método data(): void (endpoint JSON); método privado sanitizeFilters(array $input): array; index() sin cambios de lógica
  • routes/web.php — nueva ruta GET /activity-logs/data registrada antes de /activity-logs
  • views/activity-log/index.php<tbody> vacío (DataTables llena vía AJAX); formulario de filtros colapsable añadido; $hasActiveFilters badge en toggle
  • public/js/activity-logs-table.js — migrado a serverSide: true; ajax.data callback pasa los valores del formulario; botón "Filtrar" llama ajax.reload()

Full Changelog

https://github.com/Jandres25/Encriptacion_PHP/blob/master/CHANGELOG.md

v1.12.0

Choose a tag to compare

@jandrescodes jandrescodes released this 23 Jun 16:25

Added

  • Dashboard con métricas reales — la home reemplaza las feature cards estáticas con datos en vivo:
    • 4 stat-cards: usuarios totales, logins exitosos hoy, intentos fallidos hoy, cuentas bloqueadas ahora
    • Tabla Bootstrap de los últimos 5 eventos del audit log con LEFT JOIN a users; usuario sin registro muestra "Anónimo"; enlace "Ver todo" a /activity-logs visible solo para admins
    • Sin DataTables — tabla simple Bootstrap para mantener la home ligera
    • Todos los outputs de BD escapados con htmlspecialchars(); contadores emitidos como (int)
    • Fechas calculadas en MySQL (CURDATE(), NOW()) — sin drift PHP/MySQL

Changed

  • User::getTotalCount(): int — nuevo método de agregación
  • ActivityLog::getCountTodayByEvent(string $event): int — conteo diario por tipo de evento
  • ActivityLog::getRecentEvents(int $limit = 5): array — últimos N eventos con JOIN a usuarios
  • LoginAttempt::getLockedCount(): int — cuentas con locked_until > NOW()
  • HomeController::index() — instancia los tres modelos y pasa las métricas a la vista

Full Changelog

https://github.com/Jandres25/Encriptacion_PHP/blob/master/CHANGELOG.md

v1.11.0

Choose a tag to compare

@jandrescodes jandrescodes released this 20 Jun 18:19

Added

  • Botones de exportación y ColVis en tablas de administración — disponibles en /users y /activity-logs:
    • Assets DataTables Buttons 2.4.2 self-hosted: pdfmake, vfs_fonts, buttons.html5, buttons.print, buttons.colVis, dataTables.buttons (JS + CSS) en public/js/ y public/css/
    • Colección de botones: Copy, PDF, Excel, CSV, Print + ColVis (visibilidad de columnas)
    • Exportación PDF con customize: encabezado con color de paleta (#142e3d), título, subtítulo con fecha y pie de página por hoja
    • /users — clase no-export en columna Actions para excluirla de exportaciones
    • /activity-logs — todas las columnas exportadas
    • Assets cargados dentro del bloque $useDataTables en footer.php — nunca por $pageScripts

Full Changelog

https://github.com/Jandres25/Encriptacion_PHP/blob/master/CHANGELOG.md

v1.10.0

Choose a tag to compare

@jandrescodes jandrescodes released this 15 Jun 18:06

Added

  • Audit log — registro completo de eventos de seguridad y administración:
    • Nueva tabla activity_logs (id, user_id nullable con FK ON DELETE SET NULL, event, description, ip_address, created_at) con índices en created_at y user_id
    • App\Model\ActivityLog — constantes de evento (EVENT_LOGIN_SUCCESS, EVENT_LOGIN_FAILED, EVENT_LOGOUT, EVENT_PASSWORD_CHANGED, EVENT_PASSWORD_RESET, EVENT_USER_CREATED, EVENT_USER_UPDATED, EVENT_USER_DELETED); método estático log() (usa singleton DB) y helper logTo(\mysqli) para inyección en tests; getAll() con LEFT JOIN a users y COALESCE para mostrar "Anónimo" cuando user_id es NULL; ordenado por created_at DESC; sin caché
    • ActivityLogController::index() — guarda con AuthMiddleware::timeout() + AuthMiddleware::admin(); solo GET
    • Ruta GET /activity-logs registrada en routes/web.php
    • Vista views/activity-log/index.php — tabla Bootstrap con DataTables client-side; badges de color por tipo de evento; htmlspecialchars() en todas las celdas
    • public/js/activity-logs-table.js — inicialización DataTables con order por fecha descendente y pageLength: 25
    • Enlace "Activity Log" en nav solo visible para admins ($_SESSION['is_admin'])
    • Instrumentación en controllers:
      • AuthController: login exitoso, login fallido (user_id NULL), logout, reset de contraseña por email
      • ProfileController: cambio de contraseña exitoso
      • UserController: crear, editar y eliminar usuario (con username del objetivo en la descripción)
    • log() envuelto en try/catch con error_log() — un fallo de auditoría nunca aborta el flujo principal
    • IP registrada desde $_SERVER['REMOTE_ADDR']; nunca X-Forwarded-For
    • 10 nuevos tests en tests/Unit/ActivityLogTest.php — cubre logTo() con/sin user_id, IP presente/ausente, caracteres especiales, acumulación de filas, getAll() vacío, JOIN con nombre, COALESCE "Anónimo" y orden DESC — 50 tests en total

Full Changelog

https://github.com/Jandres25/Encriptacion_PHP/blob/master/CHANGELOG.md

v1.9.0

Choose a tag to compare

@jandrescodes jandrescodes released this 15 Jun 00:09

Added

  • Perfil de usuario — nueva sección /profile accesible para cualquier usuario autenticado (sin requisito de admin):
    • Vista unificada views/profile/index.php con dos formularios independientes, cada uno con su propio token CSRF
    • Form 1 (POST /profile): edita first_name, last_name, email, username; valida unicidad excluyendo el propio ID; actualiza $_SESSION['name'] si cambia el nombre
    • Form 2 (POST /profile/password): requiere contraseña actual con password_verify(); valida coincidencia y mínimo 8 caracteres
    • User::updateProfile() — UPDATE limitado a info básica; sin acceso a password ni is_admin (previene escalada de privilegios vía IDOR)
    • User::getPasswordById() — SELECT solo del hash para verificar la contraseña actual
    • User::updatePasswordProfile() — UPDATE de contraseña por id, independiente de updatePassword() que sigue siendo exclusivo del flujo de reset por email
    • Nombre de usuario en el nav convertido en enlace a /profile (visible para todos los usuarios autenticados)
    • Ambas operaciones invalidan la caché users.all

Full Changelog

https://github.com/Jandres25/Encriptacion_PHP/blob/master/CHANGELOG.md

v1.8.0

Choose a tag to compare

@jandrescodes jandrescodes released this 11 Jun 19:02

Security

  • HTTP Security HeadersX-Frame-Options: DENY, X-Content-Type-Options: nosniff, Referrer-Policy, X-XSS-Protection, Permissions-Policy y Content-Security-Policy en public/.htaccess via mod_headers; HSTS comentado listo para HTTPS
  • Eliminación de dependencias externas — jQuery y Google Fonts cargaban desde CDN externo sin SRI en las vistas de auth; reemplazados por assets self-hosted — elimina vector de supply-chain y fuga de token de reset via cabecera Referer
  • Cookie de sesión segura — helper session_start_secure() centralizado en autoload.php aplica httponly, samesite=Strict y secure condicional en todos los puntos donde se inicia sesión
  • Logout cambiado de GET a POST con CSRF — ruta /logout ahora solo acepta POST con token CSRF verificado; elimina logout CSRF via <img> o link
  • Eliminación de user enumeration en forgot-password — respuesta genérica siempre, independientemente de si el email existe
  • Rotación de token CSRF — token invalidado tras cada verificación exitosa, forzando regeneración en el siguiente request
  • Protección contra auto-eliminación y auto-degradación de admin — guards en UserController::delete() y edit()
  • Error de conexión DB no expone detalles internoserror_log() + vista de error 500 en lugar del mensaje crudo de MySQLi

Added

  • Páginas de error personalizadas — vistas standalone en views/errors/: 404.php, 403.php, 500.php con el gradiente y paleta del proyecto; sin dependencia de la app ni de la DB
  • AuthMiddleware::admin() devuelve HTTP 403 con página de error en lugar de redirigir al home
  • Router::dispatch() renderiza la vista 404 en lugar de imprimir el path interno

Fixed

  • Alineación del botón Logout en la navbar — clase CSS .btn-logout-nav en lugar de inline styles
  • storage/.htaccess con Require all denied para proteger archivos de caché

Full Changelog

https://github.com/Jandres25/Encriptacion_PHP/blob/master/CHANGELOG.md

v1.7.0

Choose a tag to compare

@jandrescodes jandrescodes released this 08 Jun 18:30

Security

  • Account lockout — bloqueo automático de cuenta tras 5 intentos de login fallidos consecutivos (configurable):
    • Nueva tabla login_attempts con identifier como PRIMARY KEY — sin enumeración de usuarios: solo se registran intentos para usernames que existen en DB
    • Operación atómica via INSERT ... ON DUPLICATE KEY UPDATE en SQL — sin race conditions
    • Lógica de bloqueo integrada en App\Core\Auth — lockout se limpia en login exitoso y en reset de contraseña
    • Check de bloqueo antes de verifyCredentials(): contraseña correcta no levanta el bloqueo durante la ventana
    • Mensaje con minutos restantes mostrado al usuario bloqueado
    • Controlado por LOGIN_LOCKOUT_ENABLED, LOGIN_MAX_ATTEMPTS (default 5), LOGIN_LOCKOUT_MINUTES (default 15)

Tests

  • 7 nuevos tests en tests/Unit/LoginAttemptTest.php — operaciones atómicas, normalización, expiración de bloqueo
  • 4 nuevos casos en tests/Integration/AuthTest.php — lockout en Auth, limpieza en reset de contraseña
  • Total: 40 tests

Full Changelog

https://github.com/Jandres25/Encriptacion_PHP/blob/master/CHANGELOG.md

v1.6.1

Choose a tag to compare

@jandrescodes jandrescodes released this 07 Jun 16:26

Security

  • CSRF protection — nueva clase App\Core\Csrf con métodos estáticos token() / verify(); todos los formularios POST incluyen un campo oculto _csrf validado en cada controlador via Controller::verifyCsrf(); token comparado con hash_equals() para prevenir timing attacks
  • XSS en mensajes flash$icon y $message en views/layouts/messages.php ahora usan json_encode() en lugar de interpolación directa en el string JavaScript
  • Delete de usuario cambiado de GET a POST — elimina explotación CSRF vía <img> o link prefetch; users-delete.js crea y envía un form dinámicamente con el token CSRF al confirmar
  • Session fixation en loginsession_regenerate_id(true) se llama inmediatamente después de password_verify() exitoso
  • Tokens de reset de contraseña hasheados en DBAuth::createPasswordResetToken() almacena hash('sha256', $token) en password_resets; el token raw solo viaja en la URL del email

Fixed

  • Null dereference en User::update() — retorna false temprano si getById($id) es null, evitando fatal TypeError en PHP 8.x
  • User::update() éxito falso — cambiado de affected_rows >= 0 a affected_rows !== -1 para distinguir error de DB de actualización idempotente
  • FileCache::remember() no cacheaba null — ahora verifica is_file() antes de llamar get() para distinguir un miss real de un valor null cacheado
  • $favicon sin escapar en headerviews/layouts/header.php ahora pasa $favicon por htmlspecialchars()

Full Changelog

https://github.com/Jandres25/Encriptacion_PHP/blob/master/CHANGELOG.md