Releases: jandrescodes/Encriptacion_PHP
Releases · jandrescodes/Encriptacion_PHP
Release list
v1.14.1
Fixed
- Weak password bypass via password reset —
AuthController::resetPassword()only checked thatnew_passwordmatchedconfirm_password, never enforcing the 8-character minimum already required byProfileController::changePassword()andUserController::validateUser(). A valid reset token could be used to set an empty or trivially short password. Now enforcesstrlen($newPassword) >= 8before callingAuth::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 aboveREADME.md— restructured for a public-facing audience:- Added Table of Contents and a Screenshots section (title + description + image per feature, no tables)
Featuresreorganized into three grouped categories instead of a 25-bullet list that duplicated theSecuritysection's implementation detail; cross-linked toSecurityfor specificsInstallationno longer pastes the full.envcontents — points to.env.exampleinsteadProject Structurereduced from a fully-annotated recursive tree to a 2-level overview, with a pointer toCLAUDE.mdfor the file-by-file breakdown- Fixed a stale/vague comment in the
Testingsetup instructions referencing a non-existent ".env.testingsection in docs"
Full Changelog
https://github.com/Jandres25/Encriptacion_PHP/blob/master/CHANGELOG.md
v1.14.0
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_hashis 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()), viaUserSession::create()/UserSession::createTo() - New
AuthMiddleware::session(\mysqli)guard — validates the current session's hash is still active (existsActive()againstSESSION_TIMEOUT) and toucheslast_activity; if the row was deleted (revoked elsewhere), it destroys the session and redirects to/loginwith a warning flash - New
GET /sessions— lists all of the user's active sessions (device/browser, IP, created, last activity, current-session badge) viaApp\Controller\SessionController::index() - New
POST /sessions/revoke— revokes a single session by id, scoped to the authenticateduser_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(defaulttrue) — disables the revocation check inAuthMiddleware::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_rememberflag,existsActive()TTL boundary,getForUser()ordering andis_currentflag,revoke()ownership scoping,revokeAllExcept()count and exclusion,deleteByTokenHash(),purgeExpired(); 71 tests in total
- New table
Changed
app/Core/Auth.php— issuessession_tokenand creates theuser_sessionsrow on remember-me auto-login (restoreFromCookie())app/Controller/AuthController.php— issuessession_tokenand creates theuser_sessionsrow on password login; deletes the row by token hash on logoutapp/Middleware/AuthMiddleware.php— new staticsession(\mysqli $connection): voidroutes/web.php— new routesGET /sessions,POST /sessions/revoke,POST /sessions/revoke-othersdatabase/schema.sql/database/schema_test.sql— newuser_sessionstableapp/Model/ActivityLog.php— new event constantEVENT_SESSION_REVOKED
Full Changelog
https://github.com/Jandres25/Encriptacion_PHP/blob/master/CHANGELOG.md
v1.13.0
Added
- Filtros server-side en Audit Log —
/activity-logsahora usa DataTables server-side processing con filtros por evento, usuario y rango de fechas:- Nuevo endpoint
GET /activity-logs/dataque devuelve JSON para DataTables (protocolodraw/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: falsedesactiva 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:
eventvalidado con allow-list de constantesEVENT_*; fechas validadas estrictamente conDateTime::createFromFormat;usernamecontrim()+ cap de 100 caracteres;lengthrestringido a[10, 25, 50, 100]; XSS en JSON mitigado conhtmlspecialchars()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
- Nuevo endpoint
Changed
app/Model/ActivityLog.php—getAll()reescrito con prepared statements y WHERE dinámico; nueva firmagetAll(array $filters = [], ?int $limit = null, ?int $offset = null): array; nuevo métodogetTotalCount(array $filters = []): int; método privadobuildWhere(array $filters): arrayapp/Controller/ActivityLogController.php— nuevo métododata(): void(endpoint JSON); método privadosanitizeFilters(array $input): array;index()sin cambios de lógicaroutes/web.php— nueva rutaGET /activity-logs/dataregistrada antes de/activity-logsviews/activity-log/index.php—<tbody>vacío (DataTables llena vía AJAX); formulario de filtros colapsable añadido;$hasActiveFiltersbadge en togglepublic/js/activity-logs-table.js— migrado aserverSide: true;ajax.datacallback pasa los valores del formulario; botón "Filtrar" llamaajax.reload()
Full Changelog
https://github.com/Jandres25/Encriptacion_PHP/blob/master/CHANGELOG.md
v1.12.0
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-logsvisible 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ónActivityLog::getCountTodayByEvent(string $event): int— conteo diario por tipo de eventoActivityLog::getRecentEvents(int $limit = 5): array— últimos N eventos con JOIN a usuariosLoginAttempt::getLockedCount(): int— cuentas conlocked_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
Added
- Botones de exportación y ColVis en tablas de administración — disponibles en
/usersy/activity-logs:- Assets DataTables Buttons 2.4.2 self-hosted:
pdfmake,vfs_fonts,buttons.html5,buttons.print,buttons.colVis,dataTables.buttons(JS + CSS) enpublic/js/ypublic/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— claseno-exporten columna Actions para excluirla de exportaciones/activity-logs— todas las columnas exportadas- Assets cargados dentro del bloque
$useDataTablesenfooter.php— nunca por$pageScripts
- Assets DataTables Buttons 2.4.2 self-hosted:
Full Changelog
https://github.com/Jandres25/Encriptacion_PHP/blob/master/CHANGELOG.md
v1.10.0
Added
- Audit log — registro completo de eventos de seguridad y administración:
- Nueva tabla
activity_logs(id,user_idnullable con FKON DELETE SET NULL,event,description,ip_address,created_at) con índices encreated_atyuser_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áticolog()(usa singleton DB) y helperlogTo(\mysqli)para inyección en tests;getAll()con LEFT JOIN ausersyCOALESCEpara mostrar "Anónimo" cuandouser_ides NULL; ordenado porcreated_at DESC; sin cachéActivityLogController::index()— guarda conAuthMiddleware::timeout()+AuthMiddleware::admin(); solo GET- Ruta
GET /activity-logsregistrada enroutes/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 conorderpor fecha descendente ypageLength: 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 emailProfileController: cambio de contraseña exitosoUserController: crear, editar y eliminar usuario (con username del objetivo en la descripción)
log()envuelto en try/catch conerror_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— cubrelogTo()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
- Nueva tabla
Full Changelog
https://github.com/Jandres25/Encriptacion_PHP/blob/master/CHANGELOG.md
v1.9.0
Added
- Perfil de usuario — nueva sección
/profileaccesible para cualquier usuario autenticado (sin requisito de admin):- Vista unificada
views/profile/index.phpcon dos formularios independientes, cada uno con su propio token CSRF - Form 1 (
POST /profile): editafirst_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 conpassword_verify(); valida coincidencia y mínimo 8 caracteres User::updateProfile()— UPDATE limitado a info básica; sin acceso apasswordniis_admin(previene escalada de privilegios vía IDOR)User::getPasswordById()— SELECT solo del hash para verificar la contraseña actualUser::updatePasswordProfile()— UPDATE de contraseña porid, independiente deupdatePassword()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
- Vista unificada
Full Changelog
https://github.com/Jandres25/Encriptacion_PHP/blob/master/CHANGELOG.md
v1.8.0
Security
- HTTP Security Headers —
X-Frame-Options: DENY,X-Content-Type-Options: nosniff,Referrer-Policy,X-XSS-Protection,Permissions-PolicyyContent-Security-Policyenpublic/.htaccessviamod_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 enautoload.phpaplicahttponly,samesite=Strictysecurecondicional en todos los puntos donde se inicia sesión - Logout cambiado de GET a POST con CSRF — ruta
/logoutahora 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()yedit() - Error de conexión DB no expone detalles internos —
error_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.phpcon 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 homeRouter::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-naven lugar de inline styles storage/.htaccessconRequire all deniedpara proteger archivos de caché
Full Changelog
https://github.com/Jandres25/Encriptacion_PHP/blob/master/CHANGELOG.md
v1.7.0
Security
- Account lockout — bloqueo automático de cuenta tras 5 intentos de login fallidos consecutivos (configurable):
- Nueva tabla
login_attemptsconidentifiercomoPRIMARY 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 UPDATEen 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)
- Nueva tabla
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
Security
- CSRF protection — nueva clase
App\Core\Csrfcon métodos estáticostoken()/verify(); todos los formularios POST incluyen un campo oculto_csrfvalidado en cada controlador viaController::verifyCsrf(); token comparado conhash_equals()para prevenir timing attacks - XSS en mensajes flash —
$icony$messageenviews/layouts/messages.phpahora usanjson_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.jscrea y envía un form dinámicamente con el token CSRF al confirmar - Session fixation en login —
session_regenerate_id(true)se llama inmediatamente después depassword_verify()exitoso - Tokens de reset de contraseña hasheados en DB —
Auth::createPasswordResetToken()almacenahash('sha256', $token)enpassword_resets; el token raw solo viaja en la URL del email
Fixed
- Null dereference en
User::update()— retornafalsetemprano sigetById($id)esnull, evitando fatal TypeError en PHP 8.x User::update()éxito falso — cambiado deaffected_rows >= 0aaffected_rows !== -1para distinguir error de DB de actualización idempotenteFileCache::remember()no cacheaba null — ahora verificais_file()antes de llamarget()para distinguir un miss real de un valor null cacheado$faviconsin escapar en header —views/layouts/header.phpahora pasa$faviconporhtmlspecialchars()
Full Changelog
https://github.com/Jandres25/Encriptacion_PHP/blob/master/CHANGELOG.md