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.
This commit is contained in:
+11
-4
@@ -16,10 +16,17 @@ services:
|
|||||||
DATABASE_PREFIX: "${DATABASE_PREFIX:-}"
|
DATABASE_PREFIX: "${DATABASE_PREFIX:-}"
|
||||||
DATABASE_CHARSET: "${DATABASE_CHARSET:-utf8}"
|
DATABASE_CHARSET: "${DATABASE_CHARSET:-utf8}"
|
||||||
ALLOW_SELF_REGISTRATION: "${ALLOW_SELF_REGISTRATION:-false}"
|
ALLOW_SELF_REGISTRATION: "${ALLOW_SELF_REGISTRATION:-false}"
|
||||||
MAIL_HOST: "mailhog"
|
# This file is also what vm420 runs in production (not docker-compose.prod.yml -
|
||||||
MAIL_PORT: "1025"
|
# see the "prod" naming is misleading, this is the actually-deployed one). Defaults
|
||||||
MAIL_ENCRYPTION: ""
|
# here are dev-convenience only (mailhog, no auth) - a real deployment's .env must
|
||||||
MAIL_SMTP_AUTH: "false"
|
# 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_ADDRESS: "${MAIL_FROM_ADDRESS:-noreply@example.com}"
|
||||||
MAIL_FROM_NAME: "${MAIL_FROM_NAME:-QRForge}"
|
MAIL_FROM_NAME: "${MAIL_FROM_NAME:-QRForge}"
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
@@ -12,6 +12,20 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="email">Email</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<span class="input-group-text"><i class="fa fa-envelope"></i></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="email" name="email" placeholder="Email" class="form-control" value="<?php echo ($edit) ? htmlspecialchars($user['email'] ?? '', ENT_QUOTES, 'UTF-8') : ''; ?>" autocomplete="off">
|
||||||
|
</div>
|
||||||
|
<small class="form-text text-muted">Used to log in once set. Leave blank to prompt for it on next login.</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="password">Password *</label>
|
<label for="password">Password *</label>
|
||||||
|
|||||||
@@ -6,8 +6,9 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th width="5%">ID</th>
|
<th width="5%">ID</th>
|
||||||
<th width="45%">Username</th>
|
<th width="25%">Username</th>
|
||||||
<th width="40%">Type</th>
|
<th width="30%">Email</th>
|
||||||
|
<th width="30%">Type</th>
|
||||||
<th width="10%">Actions</th>
|
<th width="10%">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -16,6 +17,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td><?php echo $row['id']; ?></td>
|
<td><?php echo $row['id']; ?></td>
|
||||||
<td><?php echo htmlspecialchars($row['username']); ?></td>
|
<td><?php echo htmlspecialchars($row['username']); ?></td>
|
||||||
|
<td><?php echo htmlspecialchars($row['email'] ?? ''); ?></td>
|
||||||
<td><?php echo htmlspecialchars($row['type']); ?></td>
|
<td><?php echo htmlspecialchars($row['type']); ?></td>
|
||||||
<td>
|
<td>
|
||||||
<!-- EDIT -->
|
<!-- EDIT -->
|
||||||
|
|||||||
@@ -32,6 +32,23 @@ class Users
|
|||||||
return null;
|
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');
|
$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) {
|
if (!isset($input_data['password']) || strlen($input_data['password']) < 10) {
|
||||||
$this->failure('Password must be at least 10 characters long.', 'Location: user.php');
|
$this->failure('Password must be at least 10 characters long.', 'Location: user.php');
|
||||||
}
|
}
|
||||||
|
|
||||||
$data_to_db["username"] = $input_data["username"];
|
$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['password'] = password_hash($input_data['password'], PASSWORD_DEFAULT);
|
||||||
$data_to_db["type"] = $requested_type;
|
$data_to_db["type"] = $requested_type;
|
||||||
$data_to_db['owner_admin_id'] = $owner_admin_id;
|
$data_to_db['owner_admin_id'] = $owner_admin_id;
|
||||||
@@ -131,6 +156,16 @@ class Users
|
|||||||
if ($db->count >= 1)
|
if ($db->count >= 1)
|
||||||
$this->failure('Username already exists');
|
$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);
|
$last_id = $db->insert('users', $data_to_db);
|
||||||
|
|
||||||
if ($last_id) {
|
if ($last_id) {
|
||||||
@@ -258,6 +293,12 @@ class Users
|
|||||||
$this->failure($validation_error, 'Location: user.php?'.$query_string);
|
$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) {
|
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);
|
$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);
|
$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["username"] = $input_data["username"];
|
||||||
$data_to_db["type"] = $requested_type;
|
$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_static'] = !empty($input_data['can_view_static']) ? 1 : 0;
|
||||||
$data_to_db['can_view_dynamic'] = !empty($input_data['can_view_dynamic']) ? 1 : 0;
|
$data_to_db['can_view_dynamic'] = !empty($input_data['can_view_dynamic']) ? 1 : 0;
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@ $users = new Users();
|
|||||||
if (!in_array($_SESSION['type'], ['super', 'admin'], true))
|
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');
|
$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');
|
$search_fields = array('username');
|
||||||
require_once BASE_PATH . '/includes/search_order.php';
|
require_once BASE_PATH . '/includes/search_order.php';
|
||||||
$page = filter_input(INPUT_GET, 'page', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? 1;
|
$page = filter_input(INPUT_GET, 'page', FILTER_SANITIZE_FULL_SPECIAL_CHARS) ?? 1;
|
||||||
|
|||||||
Reference in New Issue
Block a user