Allow portal contacts to reset their passwords via email

This commit is contained in:
Marcus Hill 2022-10-01 21:32:19 +01:00
parent 851ca7fae5
commit 6529ff8bbf
6 changed files with 349 additions and 23 deletions

View File

@ -200,6 +200,9 @@ while($row = mysqli_fetch_array($sql_companies)){
// Clean-up shared items that have expired
mysqli_query($mysqli, "DELETE FROM shared_items WHERE item_expire_at < NOW()");
// Invalidate any password reset links
mysqli_query($mysqli, "UPDATE contacts SET contact_password_reset_token = NULL WHERE contact_archived_at IS NULL");
// PAST DUE INVOICE Notifications
//$invoiceAlertArray = [$config_invoice_overdue_reminders];
$invoiceAlertArray = [30,60,90,120,150,180,210,240,270,300,330,360,390,420,450,480,510,540,570,590,620];

View File

@ -180,7 +180,7 @@ if(LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION){
mysqli_query($mysqli, "ALTER TABLE contacts ADD contact_department VARCHAR(200) NULL AFTER contact_title");
mysqli_query($mysqli, "DROP TABLE departments");
mysqli_query($mysqli, "ALTER TABLE contacts DROP contact_department_id");
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '0.0.7'");
}
@ -248,11 +248,11 @@ if(LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION){
mysqli_query($mysqli, "CREATE TABLE `asset_documents` (`asset_id` int(11) NOT NULL,`document_id` int(11) NOT NULL, PRIMARY KEY (`asset_id`,`document_id`))");
mysqli_query($mysqli, "CREATE TABLE `asset_logins` (`asset_id` int(11) NOT NULL,`login_id` int(11) NOT NULL, PRIMARY KEY (`asset_id`,`login_id`))");
mysqli_query($mysqli, "CREATE TABLE `asset_files` (`asset_id` int(11) NOT NULL,`file_id` int(11) NOT NULL, PRIMARY KEY (`asset_id`,`file_id`))");
mysqli_query($mysqli, "CREATE TABLE `contact_documents` (`contact_id` int(11) NOT NULL,`document_id` int(11) NOT NULL, PRIMARY KEY (`contact_id`,`document_id`))");
mysqli_query($mysqli, "CREATE TABLE `contact_logins` (`contact_id` int(11) NOT NULL,`login_id` int(11) NOT NULL, PRIMARY KEY (`contact_id`,`login_id`))");
mysqli_query($mysqli, "CREATE TABLE `contact_files` (`contact_id` int(11) NOT NULL,`file_id` int(11) NOT NULL, PRIMARY KEY (`contact_id`,`file_id`))");
mysqli_query($mysqli, "CREATE TABLE `software_documents` (`software_id` int(11) NOT NULL,`document_id` int(11) NOT NULL, PRIMARY KEY (`software_id`,`document_id`))");
mysqli_query($mysqli, "CREATE TABLE `software_logins` (`software_id` int(11) NOT NULL,`login_id` int(11) NOT NULL, PRIMARY KEY (`software_id`,`login_id`))");
mysqli_query($mysqli, "CREATE TABLE `software_files` (`software_id` int(11) NOT NULL,`file_id` int(11) NOT NULL, PRIMARY KEY (`software_id`,`file_id`))");
@ -290,12 +290,12 @@ if(LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION){
}
if(CURRENT_DATABASE_VERSION == '0.1.5'){
// Insert queries here required to update to DB version 0.1.6
// Insert queries here required to update to DB version 0.1.6
// Remove Mailing List Tables
mysqli_query($mysqli, "DROP TABLE campaigns");
mysqli_query($mysqli, "DROP TABLE campaign_messages");
// Then, update the database to the next sequential version
// Then, update the database to the next sequential version
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '0.1.6'");
}
@ -308,29 +308,38 @@ if(LATEST_DATABASE_VERSION > CURRENT_DATABASE_VERSION){
}
if(CURRENT_DATABASE_VERSION == '0.1.7'){
// Insert queries here required to update to DB version 0.1.8
// Insert queries here required to update to DB version 0.1.8
mysqli_query($mysqli, "ALTER TABLE `settings` DROP `config_backup_enable`");
mysqli_query($mysqli, "ALTER TABLE `settings` DROP `config_backup_path`");
// Then, update the database to the next sequential version
// Then, update the database to the next sequential version
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '0.1.8'");
}
//if(CURRENT_DATABASE_VERSION == '0.1.8'){
// Insert queries here required to update to DB version 0.1.9
if(CURRENT_DATABASE_VERSION == '0.1.8'){
// Insert queries here required to update to DB version 0.1.9
mysqli_query($mysqli, "ALTER TABLE `settings` DROP `config_base_url`");
// Then, update the database to the next sequential version
// Then, update the database to the next sequential version
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '0.1.9'");
//}
}
//if(CURRENT_DATABASE_VERSION == '0.1.9'){
// Insert queries here required to update to DB version 0.2.0
if(CURRENT_DATABASE_VERSION == '0.1.9'){
// Insert queries here required to update to DB version 0.2.0
// Allow contacts to reset their portal password
mysqli_query($mysqli, "ALTER TABLE contacts ADD contact_password_reset_token VARCHAR(200) NULL DEFAULT NULL AFTER contact_password_hash");
// Then, update the database to the next sequential version
// mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '0.2.0'");
//}
// Then, update the database to the next sequential version
mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '0.2.0'");
}
}
else{
// Up-to-date
}
//if(CURRENT_DATABASE_VERSION == '0.2.0'){
// Insert queries here required to update to DB version 0.2.1
// Then, update the database to the next sequential version
// mysqli_query($mysqli, "UPDATE `settings` SET `config_current_database_version` = '0.2.1'");
//}
}
else{
// Up-to-date
}

View File

@ -5,4 +5,4 @@
* It is used in conjunction with database_updates.php
*/
DEFINE("LATEST_DATABASE_VERSION", "0.1.9");
DEFINE("LATEST_DATABASE_VERSION", "0.2.0");

1
db.sql
View File

@ -327,6 +327,7 @@ CREATE TABLE `contacts` (
`contact_notes` text DEFAULT NULL,
`contact_auth_method` varchar(200) DEFAULT NULL,
`contact_password_hash` varchar(200) DEFAULT NULL,
`contact_password_reset_token` varchar(200) DEFAULT NULL,
`contact_important` tinyint(1) NOT NULL DEFAULT 0,
`contact_created_at` datetime NOT NULL DEFAULT current_timestamp(),
`contact_updated_at` datetime DEFAULT NULL ON UPDATE current_timestamp(),

View File

@ -4,8 +4,10 @@
* Landing / Home page for the client portal
*/
include('../config.php');
include('../functions.php');
$session_company_id = 1;
require_once('../config.php');
require_once('../functions.php');
require_once ('../get_settings.php');
if(!isset($_SESSION)){
// HTTP Only cookies
@ -121,6 +123,11 @@ if($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['login'])){
<button type="submit" class="btn btn-success btn-block mb-3" name="login">Login</button>
<?php
if (!empty($config_smtp_host)) { ?>
<a href="login_reset.php">Forgotten password?</a>
<?php } ?>
</form>
<?php

View File

@ -1 +1,307 @@
<?php
/*
* Client Portal
* Password reset page
*/
// Initiate PHPMailer
require_once("../plugins/PHPMailer/src/PHPMailer.php");
require_once("../plugins/PHPMailer/src/SMTP.php");
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
$session_company_id = 1;
require_once('../config.php');
require_once('../functions.php');
require_once ('../get_settings.php');
if (empty($config_smtp_host)) {
header("Location: login.php");
exit();
}
if(!isset($_SESSION)){
// HTTP Only cookies
ini_set("session.cookie_httponly", True);
if($config_https_only){
// Tell client to only send cookie(s) over HTTPS
ini_set("session.cookie_secure", True);
}
session_start();
}
$ip = strip_tags(mysqli_real_escape_string($mysqli,get_ip()));
$user_agent = strip_tags(mysqli_real_escape_string($mysqli,$_SERVER['HTTP_USER_AGENT']));
$company_sql = mysqli_query($mysqli, "SELECT company_name FROM companies WHERE company_id = '1'");
$company_results = mysqli_fetch_array($company_sql);
$company_name = $company_results['company_name'];
DEFINE("WORDING_ERROR", "Something went wrong! Your link may have expired. Please request a new password reset e-mail.");
if ($_SERVER['REQUEST_METHOD'] == "POST") {
/*
* Send password reset email
*/
if(isset($_POST['password_reset_email_request'])){
$email = strip_tags(mysqli_real_escape_string($mysqli, $_POST['email']));
$sql = mysqli_query($mysqli, "SELECT contact_id, contact_name, contact_email, contact_client_id, company_id FROM contacts WHERE contact_email = '$email' AND contact_auth_method = 'local' LIMIT 1");
$row = mysqli_fetch_assoc($sql);
$id = $row['contact_id'];
$name = $row['contact_name'];
$client = $row['contact_client_id'];
$company = $row['company_id'];
if ($row['contact_email'] == $email) {
$token = key32gen();
$url = "https://$config_base_url/portal/login_reset.php?email=$email&token=$token&client=$client";
mysqli_query($mysqli, "UPDATE contacts SET contact_password_reset_token = '$token' WHERE contact_id = $id LIMIT 1");
mysqli_query($mysqli,"INSERT INTO logs SET log_type = 'Contact', log_action = 'Modify', log_description = 'Sent a portal password reset e-mail for $email.', log_ip = '$ip', log_user_agent = '$user_agent', log_created_at = NOW(), log_client_id = $client, company_id = $company");
// Send email
$mail = new PHPMailer(true);
try{
//Mail Server Settings
$mail->SMTPDebug = false; // No debug output as client facing
$mail->isSMTP(); // Set mailer to use SMTP
$mail->Host = $config_smtp_host; // Specify main and backup SMTP servers
$mail->SMTPAuth = true; // Enable SMTP authentication
$mail->Username = $config_smtp_username; // SMTP username
$mail->Password = $config_smtp_password; // SMTP password
$mail->SMTPSecure = $config_smtp_encryption; // Enable TLS encryption, `ssl` also accepted
$mail->Port = $config_smtp_port; // TCP port to connect to
//Recipients
$mail->setFrom($config_mail_from_email, $config_mail_from_name);
$mail->addAddress("$email", "$name"); // Add user as recipient
// Content
$mail->isHTML(true); // Set email format to HTML
$mail->Subject = "Password reset for $company_name ITFlow Portal";
$mail->Body = "Hello, $name<br><br>Someone (probably you) has requested a new password for your account on $company_name's ITFlow Client Portal. <br><br><b>Please <a href='$url'>click here</a> to reset your password.</b> <br><br>Alternatively, copy and paste this URL into your browser: $url<br><br><i>If you didn't request this change, you can safely ignore this email.</i><br><br>~<br>$company_name<br>Support Department<br>$config_mail_from_email";
$mail->send();
}
catch(Exception $e){
echo "Message could not be sent. Please contact $company_name.";
mysqli_query($mysqli,"INSERT INTO logs SET log_type = 'Contact', log_action = 'Modify', log_description = 'FAILED to send a portal password reset e-mail for $email due to PHP Mailer error.', log_ip = '$ip', log_user_agent = '$user_agent', log_created_at = NOW(), log_client_id = $client, company_id = $company");
exit();
}
//End Mail IF Try-Catch
} else {
sleep(rand(2, 4)); // Mimic the e-mail send delay even if email is invalid to help prevent user enumeration
}
$_SESSION['login_message'] = "If your account exists, a reset link is on it's way!";
/*
* Do password reset
*/
}
elseif(isset($_POST['password_reset_set_password'])){
if(!isset($_POST['new_password']) || !isset($_POST['email']) || !isset($_POST['token']) || !isset($_POST['client'])) {
$_SESSION['login_message'] = WORDING_ERROR;
}
$token = strip_tags(mysqli_real_escape_string($mysqli, $_POST['token']));
$email = strip_tags(mysqli_real_escape_string($mysqli, $_POST['email']));
$client = intval(strip_tags(mysqli_real_escape_string($mysqli, $_POST['client'])));
// Query user
$sql = mysqli_query($mysqli, "SELECT * FROM contacts WHERE contact_email = '$email' AND contact_password_reset_token = '$token' AND contact_client_id = $client AND contact_auth_method = 'local' LIMIT 1");
$contact_row = mysqli_fetch_array($sql);
$contact_id = $contact_row['contact_id'];
$name = $contact_row['contact_name'];
$company = $contact_row['company_id'];
// Ensure the token is correct
if (sha1($contact_row['contact_password_reset_token']) == sha1($token)) {
// Set password, invalidate token, logging
$password = mysqli_real_escape_string($mysqli, password_hash($_POST['new_password'], PASSWORD_DEFAULT));
mysqli_query($mysqli, "UPDATE contacts SET contact_password_hash = '$password', contact_password_reset_token = NULL WHERE contact_id = $contact_id LIMIT 1");
mysqli_query($mysqli,"INSERT INTO logs SET log_type = 'Contact', log_action = 'Modify', log_description = 'Reset portal password for $email.', log_ip = '$ip', log_user_agent = '$user_agent', log_created_at = NOW(), log_client_id = $client, company_id = $company");
// Send confirmation email
$mail = new PHPMailer(true);
try{
//Mail Server Settings
$mail->SMTPDebug = false; // No debug output as client facing
$mail->isSMTP(); // Set mailer to use SMTP
$mail->Host = $config_smtp_host; // Specify main and backup SMTP servers
$mail->SMTPAuth = true; // Enable SMTP authentication
$mail->Username = $config_smtp_username; // SMTP username
$mail->Password = $config_smtp_password; // SMTP password
$mail->SMTPSecure = $config_smtp_encryption; // Enable TLS encryption, `ssl` also accepted
$mail->Port = $config_smtp_port; // TCP port to connect to
//Recipients
$mail->setFrom($config_mail_from_email, $config_mail_from_name);
$mail->addAddress("$email", "$name"); // Add user as recipient
// Content
$mail->isHTML(true); // Set email format to HTML
$mail->Subject = "Password reset confirmation for $company_name ITFlow Portal";
$mail->Body = "Hello, $name<br><br>Your password for your account on $company_name's ITFlow Client Portal was successfully reset. You should be all set! <br><br><b>If you didn't reset your password, please get in touch ASAP.</b><br><br>~<br>$company_name<br>Support Department<br>$config_mail_from_email";
$mail->send();
}
catch(Exception $e){
echo "Message could not be sent. Please contact $company_name.";
mysqli_query($mysqli,"INSERT INTO logs SET log_type = 'Contact', log_action = 'Modify', log_description = 'FAILED to send a password reset e-mail for $email due to PHP Mailer error.', log_ip = '$ip', log_user_agent = '$user_agent', log_created_at = NOW(), log_client_id = $client, company_id = $company");
exit();
}
//End Mail IF Try-Catch
// Redirect to login page
$_SESSION['login_message'] = "Password reset successfully!";
header("Location: login.php");
exit();
} else {
$_SESSION['login_message'] = WORDING_ERROR;
}
}
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title><?php echo $company_name; ?> | Password Reset</title>
<!-- Tell the browser to be responsive to screen width -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex">
<!-- Font Awesome -->
<link rel="stylesheet" href="../plugins/fontawesome-free/css/all.min.css">
<!-- Theme style -->
<link rel="stylesheet" href="../dist/css/adminlte.min.css">
<!-- Google Font: Source Sans Pro -->
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700" rel="stylesheet">
</head>
<body class="hold-transition login-page">
<div class="login-box">
<div class="login-logo"><b><?=$company_name?></b> <br>Password Reset</h2></div>
<div class="card">
<div class="card-body login-card-body">
<form method="post">
<?php
/*
* Password reset form
*/
if (isset($_GET['token']) && isset($_GET['email']) && isset($_GET['client'])) {
$token = strip_tags(mysqli_real_escape_string($mysqli, $_GET['token']));
$email = strip_tags(mysqli_real_escape_string($mysqli, $_GET['email']));
$client = intval(strip_tags(mysqli_real_escape_string($mysqli, $_GET['client'])));
$sql = mysqli_query($mysqli, "SELECT * FROM contacts WHERE contact_email = '$email' AND contact_password_reset_token = '$token' AND contact_client_id = $client LIMIT 1");
$contact_row = mysqli_fetch_array($sql);
// Sanity check
if (sha1($contact_row['contact_password_reset_token']) == sha1($token)) { ?>
<div class="input-group mb-3">
<input type="password" class="form-control" placeholder="New Password" name="new_password" required minlength="8">
<div class="input-group-append">
<div class="input-group-text">
<span class="fas fa-lock"></span>
</div>
</div>
</div>
<input type="hidden" name="token" value="<?=$token?>">
<input type="hidden" name="email" value="<?=$email?>">
<input type="hidden" name="client" value="<?=$client?>">
<button type="submit" class="btn btn-success btn-block mb-3" name="password_reset_set_password">Reset password</button>
<?php } else {
$_SESSION['login_message'] = WORDING_ERROR;
}
/*
* Else: Just show the form to request a reset token email
*/
} else { ?>
<div class="input-group mb-3">
<input type="text" class="form-control" placeholder="Registered Client Email" name="email" required autofocus>
<div class="input-group-append">
<div class="input-group-text">
<span class="fas fa-envelope"></span>
</div>
</div>
</div>
<button type="submit" class="btn btn-success btn-block mb-3" name="password_reset_email_request">Reset my password</button>
<?php }
?>
</form>
<p class="login-box-msg text-danger">
<?php
// Show feedback from session
if(!empty($_SESSION['login_message'])){
echo $_SESSION['login_message'];
unset($_SESSION['login_message']);
}
?>
</p>
<a href="login.php">Back to login</a>
</div>
<!-- /.login-card-body -->
</div>
<!-- /.div.card -->
</div>
<!-- /.login-box -->
<!-- jQuery -->
<script src="../plugins/jquery/jquery.min.js"></script>
<!-- Bootstrap 4 -->
<script src="../plugins/bootstrap/js/bootstrap.bundle.min.js"></script>
<!-- AdminLTE App -->
<script src="../dist/js/adminlte.min.js"></script>
<script src="../plugins/Show-Hide-Passwords-Bootstrap-4/bootstrap-show-password.min.js"></script>
<!-- Prevents resubmit on refresh or back -->
<script>
if(window.history.replaceState){
window.history.replaceState(null,null,window.location.href);
}
</script>
</body>
</html>