You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CHANGELOG.md
+16Lines changed: 16 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -6,6 +6,22 @@ Format based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6
6
7
7
> Note: Entries before `1.3.1` may reference legacy paths (`config/`, `controllers/`, `model/`) that were moved to `app/Config/`, `app/Controller/`, and `app/Model/`.
8
8
9
+
## [1.14.1] — 2026-07-15
10
+
11
+
### Fixed
12
+
13
+
-**Weak password bypass via password reset** — `AuthController::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()`.
14
+
15
+
### Changed
16
+
17
+
-`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
18
+
-`README.md` — restructured for a public-facing audience:
19
+
- Added Table of Contents and a Screenshots section (title + description + image per feature, no tables)
20
+
-`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
21
+
-`Installation` no longer pastes the full `.env` contents — points to `.env.example` instead
22
+
-`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
23
+
- Fixed a stale/vague comment in the `Testing` setup instructions referencing a non-existent "`.env.testing` section in docs"
Copy file name to clipboardExpand all lines: CLAUDE.md
+2-3Lines changed: 2 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -47,7 +47,7 @@ SMTP_PORT=587
47
47
48
48
APP_URL=http://localhost/Encriptacion_PHP/public
49
49
APP_TIMEZONE=America/Bogota
50
-
APP_VERSION=1.14.0
50
+
APP_VERSION=1.14.1
51
51
52
52
CACHE_ENABLED=true
53
53
CACHE_TTL_USERS=60
@@ -246,6 +246,7 @@ Loaded **only** on pages that set `$useDataTables = true`. The flag loads the fu
246
246
-**CSRF**: `App\Core\Csrf::token()` generates a `bin2hex(random_bytes(32))` token stored in `$_SESSION['csrf_token']`; all POST forms include `<input type="hidden" name="_csrf">` with this value; controllers call `$this->verifyCsrf($redirectPath)` which uses `hash_equals()` to compare — prevents timing attacks; token is **rotated** after each successful verification (`unset($_SESSION['csrf_token'])` in `Csrf::verify()`)
247
247
-**Logout is POST-only** — `/logout` route only accepts POST; `header.php` renders a `<form>` with CSRF token; `AuthController::logout()` calls `verifyCsrf()` before processing — prevents logout CSRF via `<img>` or link
248
248
- Reset tokens: `bin2hex(random_bytes(32))` raw token sent in email URL; SHA-256 hash stored in `password_resets.token` — 1-hour expiry, single-use (`used = 1` after consumption)
249
+
-**Password reset minimum length**: `AuthController::resetPassword()` enforces the same 8-character minimum as `ProfileController::changePassword()` and `UserController::validateUser()` before calling `consumeResetToken()` — previously only checked `new_password === confirm_password`, allowing arbitrarily short/empty passwords via the reset flow (fixed 2026-07-15)
249
250
-**User enumeration prevention**: `AuthController::forgotPassword()` always returns the same generic message regardless of whether the email is registered — email is sent silently if the token was created
250
251
- All DB queries in `app/Model/User.php` use MySQLi prepared statements
251
252
- Email sanitized with `filter_var($email, FILTER_SANITIZE_EMAIL)` before DB queries
@@ -318,5 +319,3 @@ composer test:integration # Auth class only
318
319
- Error/success messages use unified session flash: `$_SESSION['message']` and `$_SESSION['icon']`. Rendered via `views/layouts/messages.php`. Never pass them via URL query params
319
320
- Auth views use `<button type="submit">` (not `<input type="submit">`); POST detection uses `isset($_POST['btnXXX'])` — not `!empty()` — since `<button>` without a `value` attribute submits an empty string
320
321
- User delete flow uses `.js-delete-user` buttons with `data-delete-url`, `data-name`, `data-username`; confirmation handled in `public/js/users-delete.js`
321
-
-`session_start_secure()` is called in `app/Config/autoload.php` — always use this helper instead of bare `session_start()` to ensure `httponly`/`samesite`/`secure` options are applied; `Auth::restoreFromCookie()` runs immediately after on every request
322
-
-`AuthMiddleware::session()` is currently only wired into `SessionController::guard()` (after `timeout()`, before `auth()`) — other protected controllers do not yet check session revocation; if that changes, keep `timeout() → session() → auth()` order since `session()` needs `$_SESSION['session_token']` to still be present
0 commit comments