From 9f10c8ffb5f7463c40f5ada59e7e21ee2887280e Mon Sep 17 00:00:00 2001 From: Dillard Blom Date: Tue, 14 Jul 2026 12:47:28 +0200 Subject: [PATCH] Fix mail config not reaching vm420 + let admins view/set a user's email docker-compose.yml (the file vm420 actually runs, not docker-compose.prod.yml) hardcoded MAIL_HOST=mailhog and MAIL_SMTP_AUTH=false with no way to override from .env, and never passed MAIL_USERNAME/MAIL_PASSWORD through at all - so setting real values in .env silently had no effect on the running container. Registrations succeeded and created accounts, but the password email was never actually sent (PHPMailer tried to reach a nonexistent "mailhog" host inside vm420's network). Switched to ${MAIL_HOST:-mailhog}-style fallbacks, same pattern already used for DATABASE_PASSWORD, so local dev still defaults to mailhog for free while a real .env override now actually takes effect. Separately: the admin-panel user form never had an email field at all (only register.php could set one) - a superadmin had no way to view or correct a self-registered user's email address. Added the field (optional, uniqueness-checked, defaults to forcing must_set_email on next login if left blank) to form_users.php, Users::addUser()/editUser(), and the users list/table. --- docker-compose.yml | 15 +++++++--- src/forms/form_users.php | 14 ++++++++++ src/forms/table_users.php | 6 ++-- src/lib/Users/Users.php | 58 +++++++++++++++++++++++++++++++++++++++ src/users.php | 2 +- 5 files changed, 88 insertions(+), 7 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 8cb6a2d..aa7961b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,10 +16,17 @@ services: DATABASE_PREFIX: "${DATABASE_PREFIX:-}" DATABASE_CHARSET: "${DATABASE_CHARSET:-utf8}" ALLOW_SELF_REGISTRATION: "${ALLOW_SELF_REGISTRATION:-false}" - MAIL_HOST: "mailhog" - MAIL_PORT: "1025" - MAIL_ENCRYPTION: "" - MAIL_SMTP_AUTH: "false" + # This file is also what vm420 runs in production (not docker-compose.prod.yml - + # see the "prod" naming is misleading, this is the actually-deployed one). Defaults + # here are dev-convenience only (mailhog, no auth) - a real deployment's .env must + # override MAIL_HOST/MAIL_SMTP_AUTH/MAIL_USERNAME/MAIL_PASSWORD explicitly, same as + # DATABASE_PASSWORD above already requires. + MAIL_HOST: "${MAIL_HOST:-mailhog}" + MAIL_PORT: "${MAIL_PORT:-1025}" + MAIL_ENCRYPTION: "${MAIL_ENCRYPTION:-}" + MAIL_SMTP_AUTH: "${MAIL_SMTP_AUTH:-false}" + MAIL_USERNAME: "${MAIL_USERNAME:-}" + MAIL_PASSWORD: "${MAIL_PASSWORD:-}" MAIL_FROM_ADDRESS: "${MAIL_FROM_ADDRESS:-noreply@example.com}" MAIL_FROM_NAME: "${MAIL_FROM_NAME:-QRForge}" ports: diff --git a/src/forms/form_users.php b/src/forms/form_users.php index f5db84a..ea1880f 100644 --- a/src/forms/form_users.php +++ b/src/forms/form_users.php @@ -12,6 +12,20 @@ +
+
+ +
+
+ +
+ + +
+ Used to log in once set. Leave blank to prompt for it on next login. +
+
+
diff --git a/src/forms/table_users.php b/src/forms/table_users.php index 6b2eeeb..91f98b3 100644 --- a/src/forms/table_users.php +++ b/src/forms/table_users.php @@ -6,8 +6,9 @@ ID - Username - Type + Username + Email + Type Actions @@ -16,6 +17,7 @@ + diff --git a/src/lib/Users/Users.php b/src/lib/Users/Users.php index b70dddb..9a02893 100644 --- a/src/lib/Users/Users.php +++ b/src/lib/Users/Users.php @@ -32,6 +32,23 @@ class Users return null; } + /** + * Server-side validation of the (optional, for admin-created accounts) email address. + * Empty is allowed here - an account without one gets must_set_email=1, same as a + * pre-migration legacy account (see the callers below). + */ + private function validateEmail($email) { + if ($email === '' || $email === null) { + return null; + } + + if (!is_string($email) || strlen($email) > 255 || !filter_var($email, FILTER_VALIDATE_EMAIL)) { + return 'Please enter a valid email address.'; + } + + return null; + } + /** * */ @@ -114,11 +131,19 @@ class Users $this->failure($validation_error, 'Location: user.php'); } + $email = trim($input_data['email'] ?? ''); + $email_error = $this->validateEmail($email); + if ($email_error !== null) { + $this->failure($email_error, 'Location: user.php'); + } + if (!isset($input_data['password']) || strlen($input_data['password']) < 10) { $this->failure('Password must be at least 10 characters long.', 'Location: user.php'); } $data_to_db["username"] = $input_data["username"]; + $data_to_db["email"] = $email !== '' ? $email : null; + $data_to_db['must_set_email'] = $email === '' ? 1 : 0; $data_to_db['password'] = password_hash($input_data['password'], PASSWORD_DEFAULT); $data_to_db["type"] = $requested_type; $data_to_db['owner_admin_id'] = $owner_admin_id; @@ -131,6 +156,16 @@ class Users if ($db->count >= 1) $this->failure('Username already exists'); + if ($email !== '') { + $db = getDbInstance(); + $db->where('email', $email); + $db->get('users'); + + if ($db->count >= 1) + $this->failure('An account with this email already exists', 'Location: user.php'); + } + + $db = getDbInstance(); $last_id = $db->insert('users', $data_to_db); if ($last_id) { @@ -258,6 +293,12 @@ class Users $this->failure($validation_error, 'Location: user.php?'.$query_string); } + $email = trim($input_data['email'] ?? ''); + $email_error = $this->validateEmail($email); + if ($email_error !== null) { + $this->failure($email_error, 'Location: user.php?'.$query_string); + } + if (isset($input_data['password']) && strlen($input_data['password']) > 0 && strlen($input_data['password']) < 10) { $this->failure('Password must be at least 10 characters long.', 'Location: user.php?'.$query_string); } @@ -271,8 +312,25 @@ class Users $this->failure('Username already exists', 'Location: user.php?'.$query_string); } + if ($email !== '') { + $db = getDbInstance(); + $db->where('email', $email); + $db->where('id', $input_data["id"], '!='); + $row = $db->getOne('users'); + + if (!empty($row['email'])) { + $this->failure('An account with this email already exists', 'Location: user.php?'.$query_string); + } + } + $data_to_db["username"] = $input_data["username"]; $data_to_db["type"] = $requested_type; + // Only touch email/must_set_email if an email was actually submitted - an admin + // leaving the field blank on an already-set account shouldn't wipe it back out. + if ($email !== '') { + $data_to_db['email'] = $email; + $data_to_db['must_set_email'] = 0; + } $data_to_db['can_view_static'] = !empty($input_data['can_view_static']) ? 1 : 0; $data_to_db['can_view_dynamic'] = !empty($input_data['can_view_dynamic']) ? 1 : 0; diff --git a/src/users.php b/src/users.php index d762f9f..dcdc73b 100644 --- a/src/users.php +++ b/src/users.php @@ -9,7 +9,7 @@ $users = new Users(); if (!in_array($_SESSION['type'], ['super', 'admin'], true)) $users->failure('Only "super admin" and "admin" accounts can access the user management page', 'Location: index.php'); -$select = array('id', 'username', 'type'); +$select = array('id', 'username', 'email', 'type'); $search_fields = array('username'); require_once BASE_PATH . '/includes/search_order.php'; $page = filter_input(INPUT_GET, 'page', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? 1;