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 @@
+
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;
|