New public register.php flow: email + a GD-rendered math CAPTCHA (no
third-party service), a mailed temporary password doubling as email
verification, forced password change on first login. Gated behind a
new ALLOW_SELF_REGISTRATION toggle (default off).
Login moves from username to email (falls back to username for
pre-migration accounts without one yet, mirroring qr-vip's existing
migration 006 pattern) - self-registration needs email as the
identifier. New set_email.php interstitial for legacy accounts.
Adds a small PHPMailer-based Mailer class (SMTP, with an
unauthenticated-relay option via MAIL_SMTP_AUTH=false) since no mail
infrastructure existed in this app before.
docker compose down -v silently deletes named volumes with no
confirmation - not acceptable now that this holds real user/customer
data. Bind mounts under ./data/ are never touched by down -v, and give
a fixed host path that's simple to include in a backup routine later.
Cosmetic-only rename, no functional/data-layer change: service names
(php-dynamic-qrcode -> qrforge-app, php-dynamic-qrcode-db -> qrforge-db),
the network (php-dynamic-qrcode-network -> qrforge-network), and the two
named volumes (php_dynamic_qrcode_db_data -> qrforge_db_data,
php_dynamic_qrcode_saved_qrcode_data -> qrforge_qrcode_storage), plus
matching references in nginx.conf's fastcgi_pass and .env.example's
DATABASE_HOST default. Database name itself ('qrcode') intentionally
left unchanged per user request.
Volume rename requires an explicit data migration on already-deployed
hosts (a bare name change would otherwise attach a fresh empty volume) -
handled separately as part of this same deploy, not by this commit.
Fase 1 hardening (2026-07-08) moved saved QR code storage from
src/saved_qrcode/ to /var/www/qrcode-storage/ (outside the webroot,
SAVED_QRCODE_DIRECTORY in config.php), but docker-compose.yml's volume
mount was never updated and still pointed at the old path
(/var/www/html/saved_qrcode). The named volume was therefore mounted
somewhere the app never wrote to - every actual qr code image the app
generates at /var/www/qrcode-storage/ lived only in the container's
ephemeral filesystem and was silently lost on every container
recreation, while the qrcode_storage volume itself stayed permanently
empty. DB rows (filenames/content) were never affected, only the
generated image files.
Confirmed as the cause of qr.ensembia.com's "old and new QR codes not
showing in the list" report: qrcode_image.php's is_file() check failed
because the file genuinely wasn't there anymore. docker-compose.prod.yml
already had the correct path - only the dev compose file (what
qr.ensembia.com actually runs) had this bug.
Fixes critical pre-existing issues found during review: bulk_action.php had no
auth check at all (unauthenticated download/delete of any qrcode) and built a
table name from unwhitelisted user input (SQL injection); the QR generator
classes wrote files from unvalidated filename/format, allowing path traversal
and arbitrary file writes. Also pins chillerlan/php-qrcode to 5.0.5 since
master now requires PHP 8.4, breaking the PHP 8.3 build.
- CSRF tokens on all POST forms and the bulk_action.php JSON endpoint
- Login rate limiting (5 attempts / 15 min) via new login_attempts table
- Hardened sessions: httponly/samesite cookies, 30 min idle timeout,
session regeneration on login
- Forced password change for the default superadmin/superadmin account
- Server-side validation in Users/DynamicQrcode/Qrcode classes
- Audit log table for auth, user, and qrcode actions
- Checked-in db schema (db/init.sql, migrations/) instead of relying on an
opaque prebuilt db image
- Production docker-compose with Nginx + php-fpm instead of the PHP dev server