From d572a6de3b9a24687f7b6ebb88f0228b5044c679 Mon Sep 17 00:00:00 2001 From: James Richie Date: Fri, 8 May 2026 14:24:42 -0400 Subject: [PATCH] added forgot/reset password feature --- classes/Authentication.php | 393 ++++++++++++++++++++++++++++++++++++ classes/Base.php | 29 +++ js/signIn.js | 30 +++ xsl/resetPasswordNotice.xsl | 43 ++++ xsl/signIn.xsl | 18 +- 5 files changed, 511 insertions(+), 2 deletions(-) create mode 100644 classes/Authentication.php create mode 100644 xsl/resetPasswordNotice.xsl diff --git a/classes/Authentication.php b/classes/Authentication.php new file mode 100644 index 0000000..eae3973 --- /dev/null +++ b/classes/Authentication.php @@ -0,0 +1,393 @@ +settings = new SimpleXMLElement((file_exists("xml/settings.xml") ? file_get_contents("xml/settings.xml") : "")); + + if ($this->settings->count() === 0) { + + error_log("GHR Authentication Class : Unable to initialize settings"); + exit(); + } + + // Open Database Connections + $this->ghrconnection = $this->connect(); + } + + // ====================================== + // Return a GHR MySQL Database Connection + // ====================================== + + public function connect() { + + if ($this->settings->Environment == 'Production') { + $host = 'localhost'; + } else { + $host = '192.168.1.190'; + $host = '100.67.215.67'; + } + + try { + + $database = (string) ($this->settings->DatabaseName ?? ""); + $user = (string) ($this->settings->DatabaseUser ?? ""); + $password = (string) ($this->settings->DatabasePassword ?? ""); + + $connection = new PDO("mysql:host={$host};dbname={$database};charset=utf8", $user, $password); + + $connection->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + } catch (PDOException | Exception $e) { + + $this->debug($e->getMessage()); + exit(); + } + + return $connection; + } + + // ======================== + // Generate Random Password + // ======================== + + private function generateRandomPassword($length = 12) { + + $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()-_+='; + $password = ''; + + for ($i = 0; $i < $length; $i++) { + $index = rand(0, strlen($characters) - 1); + $password .= $characters[$index]; + } + + return $password; + } + + // =============== + // Debug an Object + // =============== + + private function debug($object = "") { + + echo "
";
+        print_r($object);
+        exit();
+    }
+
+    // ==============
+    // Reset Password
+    // ==============
+
+    public function resetPassword() {
+
+        $username = filter_input(INPUT_POST, "user_name") ?: "";
+
+        if (empty($username)) {
+            return false;
+        }
+
+        $exists = $this->userExists($username);
+
+        if (!$exists) {
+            return false;
+        }
+
+        $user = $this->getUser($username);
+
+        $password = $this->resetUsersPassword($user->record->user_serial);
+
+        $this->emailUserPasswordResetLink($user, $password);
+        $this->journalEntry("{$username} reset their password.");
+
+        return true;
+    }
+
+    // ===========
+    // User Exists
+    // ===========
+
+    private function userExists($username) {
+
+        $SQL = 'select user_name from view_users where user_name = :username';
+
+        $statement = $this->ghrconnection->prepare($SQL);
+        $statement->bindValue(':username', $username, PDO::PARAM_STR);
+        $statement->execute();
+
+        $user = $statement->fetch(PDO::FETCH_ASSOC);
+
+        return ($user !== false);
+    }
+
+    // =================
+    // Get a User Object
+    // =================
+
+    private function getUser($username) {
+
+        $SQL = 'select user_serial, user_name, user_email from view_users where user_name = :username';
+
+        $statement = $this->ghrconnection->prepare($SQL);
+        $statement->bindValue(':username', $username, PDO::PARAM_STR);
+        $statement->execute();
+
+        $user = $statement->fetch(PDO::FETCH_ASSOC);
+
+        $xml = new SimpleXMLElement('');
+        $record = $xml->addChild('record');
+
+        $this->arrayToXml($user, $record);
+
+        return $xml;
+    }
+
+    private function arrayToXml(array $data, SimpleXMLElement $xml): void {
+        foreach ($data as $key => $value) {
+            $key = is_numeric($key) ? 'item' : $key;
+
+            if (is_array($value)) {
+                $child = $xml->addChild($key);
+                $this->arrayToXml($value, $child);
+            } else {
+                $xml->addChild($key, htmlspecialchars((string) $value));
+            }
+        }
+    }
+
+    // ========================
+    // Reset the Users Password
+    // ========================
+
+    private function resetUsersPassword($user_serial) {
+
+        $user_random_password = $this->generateRandomPassword();
+        $user_temp_password = password_hash($user_random_password, PASSWORD_DEFAULT);
+
+        $SQL = "update users 
+                   set user_password        = :user_password,
+                       user_change_password = :user_change_password
+                 where user_serial          = :user_serial";
+
+        $statement = $this->ghrconnection->prepare($SQL);
+
+        $statement->bindValue(":user_serial", $user_serial);
+        $statement->bindValue(":user_password", $user_temp_password);
+        $statement->bindValue(":user_change_password", 1);
+
+        $statement->execute();
+
+        return $user_random_password;
+    }
+
+    // =================================
+    // Email a users Password Reset Link
+    // =================================
+
+    public function emailUserPasswordResetLink($user, $password) {
+
+        if (!$user instanceof SimpleXMLElement) {
+            return;
+        }
+
+        // Prepare the email.
+
+        $XSLParms['USER-TEMP-PASSWORD'] = $password;
+
+        $to = $user->record->user_email;
+        $subject = "DEC International DMS  - Password Reset Request";
+        $message = $this->applyXSL($user, $this->getXSL("emailPasswordResetLink"), $XSLParms);
+
+        // Send the Email
+        $this->sendEmail($to, $subject, $message);
+    }
+
+    // =======================================
+    // Send an email with optional attachments
+    // =======================================
+
+    public function sendEmail($to = "", $subject = "", $message = "", $attachments = "", $attachment_names = "unknown") {
+
+        if (empty($to) or empty($subject) or empty($message)) {
+
+            return false;
+        }
+
+        $SmtpHost = $this->settings->SmtpServer;
+        $SmtpUsername = $this->settings->SmtpUserName;
+        $SmtpPassword = $this->settings->SmtpPassword;
+        $SmtpPort = $this->settings->SmtpPort;
+
+        $email = new PHPMailer(true);
+
+        // Only for development
+
+        $message = "" . $message . "";
+
+        try {
+
+            //Server settings
+            $email->isSMTP();
+            $email->Host = $SmtpHost;
+            $email->SMTPAuth = true;
+            $email->Username = $SmtpUsername; // Your Gmail address
+            $email->Password = $SmtpPassword;    // App password from Google
+//            $email->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; // or 'tls'
+            $email->Port = $SmtpPort;
+
+            $email->isHTML(true);
+            $email->SetFrom("info@dec-international.com", "DEC-International");
+
+            if (is_array($to)) {
+                foreach ($to as $address) {
+                    $address = trim(str_replace(" ", "", $address));
+                    if (empty($address)) {
+                        continue;
+                    }
+                    $email->addAddress($address);
+                }
+            } else {
+                $email->addAddress($to);
+            }
+
+            $email->Subject = $subject;
+            $email->Body = $message;
+
+            if (is_array($attachments)) {
+                foreach ($attachments as $index => $item) {
+                    if (!empty($item)) {
+                        $email->addStringAttachment($item, ($attachment_names[$index] ?? "unknown"));
+                    }
+                }
+            } else {
+                if (!empty($attachments)) {
+                    $email->addStringAttachment($attachments, $attachment_names);
+                }
+            }
+
+            $success = $email->Send();
+        } catch (phpMailerException $e) {
+
+            $this->journalEntry($e->errorMessage());
+            $success = false;
+        } catch (Exception $e) {
+
+            $this->journalEntry($e->getMessage());
+            $success = false;
+        }
+
+        return $success;
+    }
+
+    // ======================
+    // Create a Journal Entry
+    // ======================
+
+    public function journalEntry($journal_entry = "") {
+
+        if (empty($journal_entry)) {
+            return;
+        }
+
+        $SQL = "insert into journal
+      
+                      ( journal_origin,
+                        journal_ip,
+                        journal_entry )
+
+              VALUES ( :journal_origin,
+                       :journal_ip,
+                       :journal_entry )";
+
+        $statement = $this->ghrconnection->prepare($SQL);
+
+        $statement->bindValue(":journal_origin", "Authentication Class");
+        $statement->bindValue(":journal_ip", $_SERVER["REMOTE_ADDR"] ?? "");
+        $statement->bindValue(":journal_entry", $journal_entry);
+
+        $statement->execute();
+    }
+
+    // ==========================================
+    // Apply an XSL Stylesheet to an XML Document
+    // ==========================================
+
+    public function applyXSL($XMLString = "", $XSLString = "", $XSLParms = "") {
+
+        if ($XMLString instanceof SimpleXMLElement) {
+            $XMLString = $XMLString->asXML();
+        }
+
+        if (empty($XSLString)) {
+            trigger_error("No XSL Stylesheet provided.");
+        }
+
+        if (!is_array($XSLParms)) {
+            $XSLParms = array();
+        }
+
+        $XSLParms["RANDOM_NUMBER"] = rand();
+        $XSLParms["TODAY_YYYYMMDD"] = date("Y-m-d");
+        $XSLParms["TODAY_MMDDYYYY"] = date("m/d/Y");
+        $XSLParms["TODAY_HHMM"] = date("Hi");
+        $XSLParms["COPYRIGHT"] = date("Y");
+
+        $XML = new DOMDocument();
+        $XSL = new DOMDocument();
+        $XSLT = new XSLTProcessor();
+
+        if ($XMLString) {
+            $XML->loadXML($XMLString);
+        }
+
+        if ($XSLString) {
+            $XSL->loadXML($XSLString);
+        }
+
+        $XSLT->importStylesheet($XSL);
+
+        if ($XSLParms) {
+            $XSLT->setParameter("", $XSLParms);
+        }
+
+        $document = $XSLT->transformToXml($XML);
+
+        return $document;
+    }
+
+    // ========================
+    // Return an XSL Stylesheet
+    // ========================
+
+    public function getXSL($document = "") {
+
+        if (trim($document) == "") {
+            return "";
+        }
+
+        $document = "xsl/" . trim($document) . ".xsl";
+        $XSL = (file_exists($document) ? file_get_contents($document) : "");
+
+        return $XSL;
+    }
+}
diff --git a/classes/Base.php b/classes/Base.php
index 15b902c..c3d6c96 100644
--- a/classes/Base.php
+++ b/classes/Base.php
@@ -266,6 +266,18 @@ class Base {
 
         $_SESSION[self::TODAY] = date("Y-m-d");
 
+        // Get the requested Action
+
+        $action = filter_input(INPUT_POST, "action") ?: "";
+        $Xaction = filter_input(INPUT_GET, "Xaction") ?: "";
+
+        $action = (empty($Xaction) ? $action : $Xaction);
+
+        if ($Xaction == 'resetpassword') {
+            $this->resetpassword();
+        }
+
+
         // Login
 
         if (($_SESSION[self::USER_AUTHENTICATED] ?? false) === false) {
@@ -784,6 +796,23 @@ class Base {
 
         exit();
     }
+
+    // =====================
+    // Forgot/Reset Password
+    // =====================
+
+    public function resetpassword() {
+
+        include_once 'classes/Authentication.php';
+
+        $reset = (new Authentication())->resetPassword() ? true : false;
+
+        $XSLParms["BTN_SUBMIT"] = "main.php?action=Users.signIn&step=authenticate";
+        $XSLParms["RESET_PASSWORD_SUCCESSFUL"] = "TRUE";
+
+        echo $this->applyXSL("", $this->getXSL("signIn"), $XSLParms);
+        die();
+    }
 }
 
 ?>
diff --git a/js/signIn.js b/js/signIn.js
index d681c14..4d47e7f 100644
--- a/js/signIn.js
+++ b/js/signIn.js
@@ -53,6 +53,36 @@ $(document).ready(function () {
 
     });
 
+    $(".btnForgot").on("click", function (e) {
+
+        e.preventDefault();
+        $("#reset-password").submit();
+
+    });
+
+    // ---------------
+    // Validate Inputs
+    // ---------------
+
+    $("#reset-password").submit(function (e) {
+
+        clearInputErrors();
+
+        var user_name = $("#user_name");
+
+        user_name.val(user_name.val().replace(/\s+/g, " ").trim());
+
+        if (isBlank(user_name)) {
+            createInputError(user_name, "User Name/Email is required to reset password");
+            return false;
+        }
+
+        $("#reset_user_name").val(user_name.val());
+
+        return true;
+
+    });
+
 });
 
 // --------------------------------
diff --git a/xsl/resetPasswordNotice.xsl b/xsl/resetPasswordNotice.xsl
new file mode 100644
index 0000000..a304989
--- /dev/null
+++ b/xsl/resetPasswordNotice.xsl
@@ -0,0 +1,43 @@
+
+
+
+    
+
+    
+
+    2.0
+    
+    notice
+
+    
+    
+        
+ +
+ +
+