mirror of https://github.com/itflow-org/itflow
Merge pull request #1254 from itflow-org/develop
Develop to Master for 25.12.1 Maint Release
This commit is contained in:
commit
163aa3062e
17
CHANGELOG.md
17
CHANGELOG.md
|
|
@ -2,6 +2,21 @@
|
||||||
|
|
||||||
This file documents all notable changes made to ITFlow.
|
This file documents all notable changes made to ITFlow.
|
||||||
|
|
||||||
|
## [25.12.1] Maint Release
|
||||||
|
|
||||||
|
### Major Changes
|
||||||
|
- Unified the Client/Agent Login and process (Note only Client Users can Reset passwords from the login page, does not apply to agent users).
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
- Fix Payment Provider not adding an account.
|
||||||
|
- Fix New ticket button in contact details in the related tickets section.
|
||||||
|
|
||||||
|
### New Features & Updates
|
||||||
|
- You can now Set Payment Provider income/expense account, expense vendor and expense category upond creation or editing.
|
||||||
|
- Moved Saved Payment Provider Methods away from admin side nav to the count link within Payment Providers page.
|
||||||
|
- Moved AI Models from the admin side nav to the model count link within AI Providers.
|
||||||
|
- Add Favicon Reset.
|
||||||
|
|
||||||
## [25.12] Stable Release
|
## [25.12] Stable Release
|
||||||
|
|
||||||
### Breaking Changes ###
|
### Breaking Changes ###
|
||||||
|
|
@ -319,7 +334,7 @@ We will provide example code with directory structure for each custom directory
|
||||||
---
|
---
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Several security vulnerabilities patched.
|
- Several security vulnerabilities patched (with thanks to www.helx.io).
|
||||||
- Ticket status is no longer updated when scheduling.
|
- Ticket status is no longer updated when scheduling.
|
||||||
- Client Portal: Tech contacts can no longer edit their own details.
|
- Client Portal: Tech contacts can no longer edit their own details.
|
||||||
- Fixed overlapping logo issue in Invoice/Quote PDF exports.
|
- Fixed overlapping logo issue in Invoice/Quote PDF exports.
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ We operate a rolling release model. Any bug fixes will be released into latest v
|
||||||
|
|
||||||
| Version | Supported |
|
| Version | Supported |
|
||||||
|---------| ------------------ |
|
|---------| ------------------ |
|
||||||
| 25.05 | :white_check_mark: |
|
| 25.12 | :white_check_mark: |
|
||||||
|
|
||||||
## Reporting a Vulnerability via GitHub Security Advisories
|
## Reporting a Vulnerability via GitHub Security Advisories
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,16 @@ $num_rows = mysqli_num_rows($sql);
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|
||||||
|
<ol class="breadcrumb d-print-none">
|
||||||
|
<li class="breadcrumb-item">
|
||||||
|
<a href="/admin">Admin</a>
|
||||||
|
</li>
|
||||||
|
<li class="breadcrumb-item">
|
||||||
|
<a href="ai_provider.php">AI Providers</a>
|
||||||
|
</li>
|
||||||
|
<li class="breadcrumb-item active">AI Models</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
<div class="card card-dark">
|
<div class="card card-dark">
|
||||||
<div class="card-header py-2">
|
<div class="card-header py-2">
|
||||||
<h3 class="card-title mt-2"><i class="fas fa-fw fa-robot mr-2"></i>AI Models</h3>
|
<h3 class="card-title mt-2"><i class="fas fa-fw fa-robot mr-2"></i>AI Models</h3>
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ $num_rows = mysqli_num_rows($sql);
|
||||||
Key <?php if ($sort == 'ai_provider_api_key') { echo $order_icon; } ?>
|
Key <?php if ($sort == 'ai_provider_api_key') { echo $order_icon; } ?>
|
||||||
</a>
|
</a>
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th class="text-center">
|
||||||
<a class="text-dark">Models</a>
|
<a class="text-dark">Models</a>
|
||||||
</th>
|
</th>
|
||||||
<th class="text-center">Action</th>
|
<th class="text-center">Action</th>
|
||||||
|
|
@ -67,7 +67,8 @@ $num_rows = mysqli_num_rows($sql);
|
||||||
</td>
|
</td>
|
||||||
<td><?php echo $url; ?></td>
|
<td><?php echo $url; ?></td>
|
||||||
<td><?php echo $key; ?></td>
|
<td><?php echo $key; ?></td>
|
||||||
<td><?php echo $ai_model_count; ?></td>
|
<td class="text-center">
|
||||||
|
<a class="badge badge-dark badge-pill p-2" href="ai_model.php"><?= $ai_model_count ?></a>
|
||||||
<td>
|
<td>
|
||||||
<div class="dropdown dropleft text-center">
|
<div class="dropdown dropleft text-center">
|
||||||
<button class="btn btn-secondary btn-sm" type="button" data-toggle="dropdown">
|
<button class="btn btn-secondary btn-sm" type="button" data-toggle="dropdown">
|
||||||
|
|
|
||||||
|
|
@ -69,30 +69,20 @@
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="/admin/payment_provider.php" class="nav-link <?php echo (basename($_SERVER['PHP_SELF']) == 'payment_provider.php' ? 'active' : ''); ?>">
|
<a href="/admin/payment_provider.php"
|
||||||
|
class="nav-link <?php echo (in_array(basename($_SERVER['PHP_SELF']), ['payment_provider.php', 'saved_payment_method.php']) ? 'active' : ''); ?>">
|
||||||
<i class="nav-icon far fa-credit-card"></i>
|
<i class="nav-icon far fa-credit-card"></i>
|
||||||
<p>Payment Providers</p>
|
<p>Payment Providers</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
|
||||||
<a href="/admin/saved_payment_method.php" class="nav-link <?php echo (basename($_SERVER['PHP_SELF']) == 'saved_payment_method.php' ? 'active' : ''); ?>">
|
|
||||||
<i class="nav-icon far fa-credit-card"></i>
|
|
||||||
<p>Saved Payments</p>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<?php } ?>
|
<?php } ?>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a href="/admin/ai_provider.php" class="nav-link <?php echo (basename($_SERVER['PHP_SELF']) == 'ai_provider.php' ? 'active' : ''); ?>">
|
<a href="/admin/ai_provider.php"
|
||||||
|
class="nav-link <?php echo (in_array(basename($_SERVER['PHP_SELF']), ['ai_provider.php', 'ai_model.php']) ? 'active' : ''); ?>">
|
||||||
<i class="nav-icon fas fa-robot"></i>
|
<i class="nav-icon fas fa-robot"></i>
|
||||||
<p>AI Providers</p>
|
<p>AI Providers</p>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
|
||||||
<a href="/admin/ai_model.php" class="nav-link <?php echo (basename($_SERVER['PHP_SELF']) == 'ai_model.php' ? 'active' : ''); ?>">
|
|
||||||
<i class="nav-icon fas fa-robot"></i>
|
|
||||||
<p>AI Models</p>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<?php if ($config_module_enable_ticketing) { ?>
|
<?php if ($config_module_enable_ticketing) { ?>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
|
|
|
||||||
|
|
@ -16,85 +16,182 @@ ob_start();
|
||||||
|
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
|
||||||
<div class="alert alert-info">
|
<div class="alert alert-info text-center">
|
||||||
An income account named after the provider will always be created and used for income of paid invoices.<br>
|
<h6>Before Adding a Payment Provider!</h6>
|
||||||
If "Enable Expense" option is enabled, a matching vendor will also be automatically created for expense tracking. Additionally, an expense category named "Payment Processing" will be created.
|
We recommend you add an <strong>Account</strong> and <strong>Vendor</strong> based off the Provider name before continuing eg <strong>Stripe</strong>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<ul class="nav nav-pills nav-justified mb-3">
|
||||||
<label>Provider <strong class="text-danger">*</strong></label>
|
<li class="nav-item">
|
||||||
<div class="input-group">
|
<a class="nav-link active" data-toggle="pill" href="#pills-details">Details</a>
|
||||||
<div class="input-group-prepend">
|
</li>
|
||||||
<span class="input-group-text"><i class="fa fa-fw fa-credit-card"></i></span>
|
<li class="nav-item">
|
||||||
</div>
|
<a class="nav-link" data-toggle="pill" href="#pills-expense">Expense</a>
|
||||||
<select class="form-control select2" name="provider">
|
</li>
|
||||||
<option>Stripe</option>
|
</ul>
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Publishable key <strong class="text-danger">*</strong></label>
|
|
||||||
<div class="input-group">
|
|
||||||
<div class="input-group-prepend">
|
|
||||||
<span class="input-group-text"><i class="fa fa-fw fa-eye"></i></span>
|
|
||||||
</div>
|
|
||||||
<input type="text" class="form-control" name="public_key" placeholder="Publishable API Key (pk_...)">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Secret key <strong class="text-danger">*</strong></label>
|
|
||||||
<div class="input-group">
|
|
||||||
<div class="input-group-prepend">
|
|
||||||
<span class="input-group-text"><i class="fa fa-fw fa-key"></i></span>
|
|
||||||
</div>
|
|
||||||
<input type="text" class="form-control" name="private_key" placeholder="Secret API Key (sk_...)">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Threshold</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<div class="input-group-prepend">
|
|
||||||
<span class="input-group-text"><i class="fa fa-fw fa-shopping-cart"></i></span>
|
|
||||||
</div>
|
|
||||||
<input type="text" class="form-control" inputmode="decimal" pattern="[0-9]*\.?[0-9]{0,2}" name="threshold" placeholder="1000.00">
|
|
||||||
</div>
|
|
||||||
<small class="form-text text-muted">Will not show as an option at Checkout if invoice amount is above this number, 0 disables the threshold check.</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="tab-content">
|
||||||
<div class="custom-control custom-switch">
|
|
||||||
<input type="checkbox" class="custom-control-input" name="enable_expense" checked value="1" id="enableExpenseSwitch">
|
|
||||||
<label class="custom-control-label" for="enableExpenseSwitch">Enable Expense</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="tab-pane fade show active" id="pills-details">
|
||||||
<label>Percentage Fee to expense</label>
|
|
||||||
<div class="input-group">
|
<div class="form-group">
|
||||||
<div class="input-group-prepend">
|
<label>Provider <strong class="text-danger">*</strong></label>
|
||||||
<span class="input-group-text"><i class="fa fa-fw fa-percent"></i></span>
|
<div class="input-group">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<span class="input-group-text"><i class="fa fa-fw fa-credit-card"></i></span>
|
||||||
|
</div>
|
||||||
|
<select class="form-control select2" name="provider">
|
||||||
|
<option>Stripe</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" class="form-control" inputmode="decimal" pattern="[0-9]*\.?[0-9]{0,2}" name="percentage_fee" placeholder="Enter Percentage">
|
|
||||||
</div>
|
|
||||||
<small class="form-text text-muted">See <a href="https://stripe.com/pricing" target="_blank">here <i class="fas fa-fw fa-external-link-alt"></i></a> for the latest Stripe Fees.</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Flat Fee to expense</label>
|
<label>Publishable key <strong class="text-danger">*</strong></label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<div class="input-group-prepend">
|
<div class="input-group-prepend">
|
||||||
<span class="input-group-text"><i class="fa fa-fw fa-shopping-cart"></i></span>
|
<span class="input-group-text"><i class="fa fa-fw fa-eye"></i></span>
|
||||||
|
</div>
|
||||||
|
<input type="text" class="form-control" name="public_key" placeholder="Publishable API Key (pk_...)">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" class="form-control" inputmode="decimal" pattern="[0-9]*\.?[0-9]{0,3}" name="flat_fee" placeholder="0.030">
|
|
||||||
</div>
|
|
||||||
<small class="form-text text-muted">See <a href="https://stripe.com/pricing" target="_blank">here <i class="fas fa-fw fa-external-link-alt"></i></a> for the latest Stripe Fees.</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Secret key <strong class="text-danger">*</strong></label>
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<span class="input-group-text"><i class="fa fa-fw fa-key"></i></span>
|
||||||
|
</div>
|
||||||
|
<input type="text" class="form-control" name="private_key" placeholder="Secret API Key (sk_...)">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Income / Expense Account <strong class="text-danger">*</strong></label>
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<span class="input-group-text"><i class="fa fa-fw fa-piggy-bank"></i></span>
|
||||||
|
</div>
|
||||||
|
<select class="form-control select2" name="account" required>
|
||||||
|
<option value="">- Select an Account -</option>
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$sql = mysqli_query($mysqli, "SELECT account_id, account_name FROM accounts WHERE account_archived_at IS NULL ORDER BY account_name ASC");
|
||||||
|
while ($row = mysqli_fetch_array($sql)) {
|
||||||
|
$account_id = intval($row['account_id']);
|
||||||
|
$account_name = nullable_htmlentities($row['account_name']);
|
||||||
|
?>
|
||||||
|
<option <?php if ($account_name === 'Stripe') { echo "selected"; } ?> value="<?= $account_id ?>"><?= $account_name ?></option>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Threshold</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<span class="input-group-text"><i class="fa fa-fw fa-shopping-cart"></i></span>
|
||||||
|
</div>
|
||||||
|
<input type="text" class="form-control" inputmode="decimal" pattern="[0-9]*\.?[0-9]{0,2}" name="threshold" placeholder="1000.00">
|
||||||
|
</div>
|
||||||
|
<small class="form-text text-muted">Will not show as an option at Checkout if invoice amount is above this number, 0 disables the threshold check.</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-pane fade" id="pills-expense">
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="custom-control custom-switch">
|
||||||
|
<input type="checkbox" class="custom-control-input" name="enable_expense" checked value="1" id="enableExpenseSwitch">
|
||||||
|
<label class="custom-control-label" for="enableExpenseSwitch">Enable Expense</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Payment Provider Vendor <strong class="text-danger">*</strong></label>
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<span class="input-group-text"><i class="fa fa-fw fa-building"></i></span>
|
||||||
|
</div>
|
||||||
|
<select class="form-control select2" name="expense_vendor" required>
|
||||||
|
<option value="0">Expense Disabled</option>
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$sql = mysqli_query($mysqli, "SELECT vendor_id, vendor_name FROM vendors WHERE vendor_client_id = 0 AND vendor_archived_at IS NULL ORDER BY vendor_name ASC");
|
||||||
|
while ($row = mysqli_fetch_array($sql)) {
|
||||||
|
$vendor_id = intval($row['vendor_id']);
|
||||||
|
$vendor_name = nullable_htmlentities($row['vendor_name']);
|
||||||
|
?>
|
||||||
|
<option <?php if ($vendor_name === 'Stripe') { echo "selected"; } ?> value="<?= $vendor_id ?>"><?= $vendor_name ?></option>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Expense Category <strong class="text-danger">*</strong></label>
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<span class="input-group-text"><i class="fa fa-fw fa-list"></i></span>
|
||||||
|
</div>
|
||||||
|
<select class="form-control select2" name="expense_category" required>
|
||||||
|
<option value="">- Select a Category -</option>
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$sql = mysqli_query($mysqli, "SELECT category_id, category_name FROM categories WHERE category_type = 'Expense' AND category_archived_at IS NULL ORDER BY category_name ASC");
|
||||||
|
while ($row = mysqli_fetch_array($sql)) {
|
||||||
|
$category_id = intval($row['category_id']);
|
||||||
|
$category_name = nullable_htmlentities($row['category_name']);
|
||||||
|
?>
|
||||||
|
<option <?php if ($category_name === 'Processing Fee') { echo "selected"; } ?> value="<?= $category_id ?>"><?= $category_name ?></option>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</select>
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button class="btn btn-secondary ajax-modal" type="button"
|
||||||
|
data-modal-url="../admin/modals/category/category_add.php?category=Expense">
|
||||||
|
<i class="fas fa-plus"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Percentage Fee to expense</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<span class="input-group-text"><i class="fa fa-fw fa-percent"></i></span>
|
||||||
|
</div>
|
||||||
|
<input type="text" class="form-control" inputmode="decimal" pattern="[0-9]*\.?[0-9]{0,2}" name="percentage_fee" placeholder="Enter Percentage">
|
||||||
|
</div>
|
||||||
|
<small class="form-text text-muted">See <a href="https://stripe.com/pricing" target="_blank">here <i class="fas fa-fw fa-external-link-alt"></i></a> for the latest Stripe Fees.</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Flat Fee to expense</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<span class="input-group-text"><i class="fa fa-fw fa-shopping-cart"></i></span>
|
||||||
|
</div>
|
||||||
|
<input type="text" class="form-control" inputmode="decimal" pattern="[0-9]*\.?[0-9]{0,3}" name="flat_fee" placeholder="0.030">
|
||||||
|
</div>
|
||||||
|
<small class="form-text text-muted">See <a href="https://stripe.com/pricing" target="_blank">here <i class="fas fa-fw fa-external-link-alt"></i></a> for the latest Stripe Fees.</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="submit" name="add_payment_provider" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Add</button>
|
<button type="submit" name="add_payment_provider" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Add</button>
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,10 @@ $row = mysqli_fetch_array($sql);
|
||||||
$provider_name = nullable_htmlentities($row['payment_provider_name']);
|
$provider_name = nullable_htmlentities($row['payment_provider_name']);
|
||||||
$public_key = nullable_htmlentities($row['payment_provider_public_key']);
|
$public_key = nullable_htmlentities($row['payment_provider_public_key']);
|
||||||
$private_key = nullable_htmlentities($row['payment_provider_private_key']);
|
$private_key = nullable_htmlentities($row['payment_provider_private_key']);
|
||||||
$account_id = nullable_htmlentities($row['payment_provider_account']);
|
$account_id = intval($row['payment_provider_account']);
|
||||||
$threshold = floatval($row['payment_provider_threshold']);
|
$threshold = floatval($row['payment_provider_threshold']);
|
||||||
$vendor_id = nullable_htmlentities($row['payment_provider_expense_vendor']);
|
$vendor_id = intval($row['payment_provider_expense_vendor']);
|
||||||
$category_id = nullable_htmlentities($row['payment_provider_expense_category']);
|
$category_id = intval($row['payment_provider_expense_category']);
|
||||||
$percent_fee = floatval($row['payment_provider_expense_percentage_fee']) * 100;
|
$percent_fee = floatval($row['payment_provider_expense_percentage_fee']) * 100;
|
||||||
$flat_fee = floatval($row['payment_provider_expense_flat_fee']);
|
$flat_fee = floatval($row['payment_provider_expense_flat_fee']);
|
||||||
|
|
||||||
|
|
@ -21,80 +21,170 @@ $flat_fee = floatval($row['payment_provider_expense_flat_fee']);
|
||||||
ob_start();
|
ob_start();
|
||||||
?>
|
?>
|
||||||
<div class="modal-header bg-dark">
|
<div class="modal-header bg-dark">
|
||||||
<h5 class="modal-title"><i class="fa fa-fw fa-credit-card mr-2"></i>Editing: <strong><?php echo $provider_name; ?></strong></h5>
|
<h5 class="modal-title"><i class="fa fa-fw fa-credit-card mr-2"></i>Editing: <strong><?= $provider_name ?></strong></h5>
|
||||||
<button type="button" class="close text-white" data-dismiss="modal">
|
<button type="button" class="close text-white" data-dismiss="modal">
|
||||||
<span>×</span>
|
<span>×</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<form action="post.php" method="post" autocomplete="off">
|
<form action="post.php" method="post" autocomplete="off">
|
||||||
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token'] ?>">
|
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
|
||||||
<input type="hidden" name="provider_id" value="<?php echo $provider_id; ?>">
|
<input type="hidden" name="provider_id" value="<?= $provider_id ?>">
|
||||||
|
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
|
||||||
<div class="form-group">
|
<ul class="nav nav-pills nav-justified mb-3">
|
||||||
<label>Publishable key <strong class="text-danger">*</strong></label>
|
<li class="nav-item">
|
||||||
<div class="input-group">
|
<a class="nav-link active" data-toggle="pill" href="#pills-details">Details</a>
|
||||||
<div class="input-group-prepend">
|
</li>
|
||||||
<span class="input-group-text"><i class="fa fa-fw fa-eye"></i></span>
|
<li class="nav-item">
|
||||||
</div>
|
<a class="nav-link" data-toggle="pill" href="#pills-expense">Expense</a>
|
||||||
<input type="text" class="form-control" name="public_key" placeholder="Publishable API Key (pk_...)" value="<?php echo $public_key; ?>">
|
</li>
|
||||||
</div>
|
</ul>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Secret key <strong class="text-danger">*</strong></label>
|
|
||||||
<div class="input-group">
|
|
||||||
<div class="input-group-prepend">
|
|
||||||
<span class="input-group-text"><i class="fa fa-fw fa-key"></i></span>
|
|
||||||
</div>
|
|
||||||
<input type="text" class="form-control" name="private_key" placeholder="Secret API Key (sk_...)" value="<?php echo $private_key; ?>">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Threshold</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<div class="input-group-prepend">
|
|
||||||
<span class="input-group-text"><i class="fa fa-fw fa-shopping-cart"></i></span>
|
|
||||||
</div>
|
|
||||||
<input type="text" class="form-control" inputmode="decimal" pattern="[0-9]*\.?[0-9]{0,2}" name="threshold" placeholder="1000.00" value="<?php echo $threshold; ?>">
|
|
||||||
</div>
|
|
||||||
<small class="form-text text-muted">Will not show as an option at Checkout if above this number</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="tab-content">
|
||||||
<div class="custom-control custom-switch">
|
|
||||||
<input type="checkbox" class="custom-control-input" name="enable_expense" <?php if ($vendor_id) { echo "checked"; } ?> value="1" id="enableEditExpenseSwitch">
|
|
||||||
<label class="custom-control-label" for="enableEditExpenseSwitch">Enable Expense</label>
|
|
||||||
</div>
|
|
||||||
<small>(Category: Payment Processing -- Vendor: <?php echo $provider_name; ?></small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="tab-pane fade show active" id="pills-details">
|
||||||
<label>Percentage Fee to expense</label>
|
|
||||||
<div class="input-group">
|
<div class="form-group">
|
||||||
<div class="input-group-prepend">
|
<label>Publishable key <strong class="text-danger">*</strong></label>
|
||||||
<span class="input-group-text"><i class="fa fa-fw fa-percent"></i></span>
|
<div class="input-group">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<span class="input-group-text"><i class="fa fa-fw fa-eye"></i></span>
|
||||||
|
</div>
|
||||||
|
<input type="text" class="form-control" name="public_key" placeholder="Publishable API Key (pk_...)" value="<?= $public_key ?>">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" class="form-control" inputmode="decimal" pattern="[0-9]*\.?[0-9]{0,2}" name="percentage_fee" value="<?php echo $percent_fee; ?>" placeholder="Enter Percentage">
|
|
||||||
</div>
|
|
||||||
<small class="form-text text-muted">See <a href="https://stripe.com/pricing" target="_blank">here <i class="fas fa-fw fa-external-link-alt"></i></a> for the latest Stripe Fees.</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Flat Fee to expense</label>
|
<label>Secret key <strong class="text-danger">*</strong></label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<div class="input-group-prepend">
|
<div class="input-group-prepend">
|
||||||
<span class="input-group-text"><i class="fa fa-fw fa-shopping-cart"></i></span>
|
<span class="input-group-text"><i class="fa fa-fw fa-key"></i></span>
|
||||||
|
</div>
|
||||||
|
<input type="text" class="form-control" name="private_key" placeholder="Secret API Key (sk_...)" value="<?= $private_key ?>">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" class="form-control" inputmode="decimal" pattern="[0-9]*\.?[0-9]{0,3}" name="flat_fee" value="<?php echo $flat_fee; ?>" placeholder="0.030">
|
|
||||||
</div>
|
|
||||||
<small class="form-text text-muted">See <a href="https://stripe.com/pricing" target="_blank">here <i class="fas fa-fw fa-external-link-alt"></i></a> for the latest Stripe Fees.</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Income / Expense Account <strong class="text-danger">*</strong></label>
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<span class="input-group-text"><i class="fa fa-fw fa-piggy-bank"></i></span>
|
||||||
|
</div>
|
||||||
|
<select class="form-control select2" name="account" required>
|
||||||
|
<option value="">- Select an Account -</option>
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$sql = mysqli_query($mysqli, "SELECT account_id, account_name FROM accounts WHERE account_archived_at IS NULL ORDER BY account_name ASC");
|
||||||
|
while ($row = mysqli_fetch_array($sql)) {
|
||||||
|
$account_id_select = intval($row['account_id']);
|
||||||
|
$account_name = nullable_htmlentities($row['account_name']);
|
||||||
|
?>
|
||||||
|
<option <?php if ($account_id === $account_id_select) { echo "selected"; } ?> value="<?= $account_id_select ?>"><?= $account_name ?></option>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Threshold</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<span class="input-group-text"><i class="fa fa-fw fa-shopping-cart"></i></span>
|
||||||
|
</div>
|
||||||
|
<input type="text" class="form-control" inputmode="decimal" pattern="[0-9]*\.?[0-9]{0,2}" name="threshold" placeholder="1000.00" value="<?php echo $threshold; ?>">
|
||||||
|
</div>
|
||||||
|
<small class="form-text text-muted">Will not show as an option at Checkout if above this number</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-pane fade" id="pills-expense">
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Payment Provider Vendor <strong class="text-danger">*</strong></label>
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<span class="input-group-text"><i class="fa fa-fw fa-building"></i></span>
|
||||||
|
</div>
|
||||||
|
<select class="form-control select2" name="expense_vendor" required>
|
||||||
|
<option value="0">Expense Disabled</option>
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$sql = mysqli_query($mysqli, "SELECT vendor_id, vendor_name FROM vendors WHERE vendor_client_id = 0 AND vendor_archived_at IS NULL ORDER BY vendor_name ASC");
|
||||||
|
while ($row = mysqli_fetch_array($sql)) {
|
||||||
|
$vendor_id_select = intval($row['vendor_id']);
|
||||||
|
$vendor_name = nullable_htmlentities($row['vendor_name']);
|
||||||
|
?>
|
||||||
|
<option <?php if ($vendor_id === $vendor_id_select) { echo "selected"; } ?>
|
||||||
|
value="<?= $vendor_id_select ?>"><?= $vendor_name ?>
|
||||||
|
</option>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Expense Category <strong class="text-danger">*</strong></label>
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<span class="input-group-text"><i class="fa fa-fw fa-list"></i></span>
|
||||||
|
</div>
|
||||||
|
<select class="form-control select2" name="expense_category" required>
|
||||||
|
<option value="">- Select a Category -</option>
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$sql_category = mysqli_query($mysqli, "SELECT category_id, category_name FROM categories WHERE category_type = 'Expense' AND category_archived_at IS NULL ORDER BY category_name ASC");
|
||||||
|
while ($row = mysqli_fetch_array($sql_category)) {
|
||||||
|
$category_id_select = intval($row['category_id']);
|
||||||
|
$category_name = nullable_htmlentities($row['category_name']);
|
||||||
|
?>
|
||||||
|
<option <?php if ($category_id === $category_id_select) { echo "selected"; } ?> value="<?= $category_id_select ?>"><?= $category_name ?></option>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
</select>
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button class="btn btn-secondary ajax-modal" type="button"
|
||||||
|
data-modal-url="../admin/modals/category/category_add.php?category=Expense">
|
||||||
|
<i class="fas fa-plus"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Percentage Fee to expense</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<span class="input-group-text"><i class="fa fa-fw fa-percent"></i></span>
|
||||||
|
</div>
|
||||||
|
<input type="text" class="form-control" inputmode="decimal" pattern="[0-9]*\.?[0-9]{0,2}" name="percentage_fee" value="<?php echo $percent_fee; ?>" placeholder="Enter Percentage">
|
||||||
|
</div>
|
||||||
|
<small class="form-text text-muted">See <a href="https://stripe.com/pricing" target="_blank">here <i class="fas fa-fw fa-external-link-alt"></i></a> for the latest Stripe Fees.</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Flat Fee to expense</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<span class="input-group-text"><i class="fa fa-fw fa-shopping-cart"></i></span>
|
||||||
|
</div>
|
||||||
|
<input type="text" class="form-control" inputmode="decimal" pattern="[0-9]*\.?[0-9]{0,3}" name="flat_fee" value="<?php echo $flat_fee; ?>" placeholder="0.030">
|
||||||
|
</div>
|
||||||
|
<small class="form-text text-muted">See <a href="https://stripe.com/pricing" target="_blank">here <i class="fas fa-fw fa-external-link-alt"></i></a> for the latest Stripe Fees.</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="submit" name="edit_payment_provider" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Save</button>
|
<button type="submit" name="edit_payment_provider" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Save</button>
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,10 @@ $order = "ASC";
|
||||||
|
|
||||||
require_once "includes/inc_all_admin.php";
|
require_once "includes/inc_all_admin.php";
|
||||||
|
|
||||||
$sql = mysqli_query($mysqli, "SELECT * FROM payment_providers
|
$sql = mysqli_query($mysqli, "SELECT * FROM payment_providers
|
||||||
LEFT JOIN accounts ON payment_provider_account = account_id
|
LEFT JOIN accounts ON payment_provider_account = account_id
|
||||||
LEFT JOIN vendors ON payment_provider_expense_vendor = vendor_id
|
LEFT JOIN vendors ON payment_provider_expense_vendor = vendor_id
|
||||||
LEFT JOIN categories ON payment_provider_expense_category = category_id
|
LEFT JOIN categories ON payment_provider_expense_category = category_id
|
||||||
ORDER BY $sort $order"
|
ORDER BY $sort $order"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -57,7 +57,7 @@ $num_rows = mysqli_num_rows($sql);
|
||||||
<th>
|
<th>
|
||||||
<a class="text-dark">Expensed Fee</a>
|
<a class="text-dark">Expensed Fee</a>
|
||||||
</th>
|
</th>
|
||||||
<th>
|
<th class="text-center">
|
||||||
<a class="text-dark">Saved Payment Methods</a>
|
<a class="text-dark">Saved Payment Methods</a>
|
||||||
</th>
|
</th>
|
||||||
<th class="text-center">Action</th>
|
<th class="text-center">Action</th>
|
||||||
|
|
@ -72,7 +72,7 @@ $num_rows = mysqli_num_rows($sql);
|
||||||
$provider_description = nullable_htmlentities($row['payment_provider_description']);
|
$provider_description = nullable_htmlentities($row['payment_provider_description']);
|
||||||
$account_name = nullable_htmlentities($row['account_name']);
|
$account_name = nullable_htmlentities($row['account_name']);
|
||||||
$threshold = floatval($row['payment_provider_threshold']);
|
$threshold = floatval($row['payment_provider_threshold']);
|
||||||
$vendor_name = nullable_htmlentities($row['vendor_name']);
|
$vendor_name = nullable_htmlentities($row['vendor_name'] ?? "Expense Disabled");
|
||||||
$category = nullable_htmlentities($row['category_name']);
|
$category = nullable_htmlentities($row['category_name']);
|
||||||
$percent_fee = floatval($row['payment_provider_expense_percentage_fee']) * 100;
|
$percent_fee = floatval($row['payment_provider_expense_percentage_fee']) * 100;
|
||||||
$flat_fee = floatval($row['payment_provider_expense_flat_fee']);
|
$flat_fee = floatval($row['payment_provider_expense_flat_fee']);
|
||||||
|
|
@ -94,7 +94,9 @@ $num_rows = mysqli_num_rows($sql);
|
||||||
<td><?php echo $vendor_name; ?></td>
|
<td><?php echo $vendor_name; ?></td>
|
||||||
<td><?php echo $category; ?></td>
|
<td><?php echo $category; ?></td>
|
||||||
<td><?php echo $percent_fee; ?>% + <?php echo numfmt_format_currency($currency_format, $flat_fee, $session_company_currency); ?></td>
|
<td><?php echo $percent_fee; ?>% + <?php echo numfmt_format_currency($currency_format, $flat_fee, $session_company_currency); ?></td>
|
||||||
<td><?php echo $saved_payment_count; ?></td>
|
<td class="text-center">
|
||||||
|
<a class="badge badge-dark badge-pill p-2" href="saved_payment_method.php"><?= $saved_payment_count ?></a>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="dropdown dropleft text-center">
|
<div class="dropdown dropleft text-center">
|
||||||
<button class="btn btn-secondary btn-sm" type="button" data-toggle="dropdown">
|
<button class="btn btn-secondary btn-sm" type="button" data-toggle="dropdown">
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ if (isset($_POST['edit_ai_model'])) {
|
||||||
if (isset($_GET['delete_ai_model'])) {
|
if (isset($_GET['delete_ai_model'])) {
|
||||||
|
|
||||||
validateCSRFToken($_GET['csrf_token']);
|
validateCSRFToken($_GET['csrf_token']);
|
||||||
|
|
||||||
$model_id = intval($_GET['delete_ai_model']);
|
$model_id = intval($_GET['delete_ai_model']);
|
||||||
|
|
||||||
$model_name = sanitizeInput(getFieldById('ai_models', $model_id, 'ai_model_name'));
|
$model_name = sanitizeInput(getFieldById('ai_models', $model_id, 'ai_model_name'));
|
||||||
|
|
|
||||||
|
|
@ -14,53 +14,20 @@ if (isset($_POST['add_payment_provider'])) {
|
||||||
$public_key = sanitizeInput($_POST['public_key']);
|
$public_key = sanitizeInput($_POST['public_key']);
|
||||||
$private_key = sanitizeInput($_POST['private_key']);
|
$private_key = sanitizeInput($_POST['private_key']);
|
||||||
$threshold = floatval($_POST['threshold']);
|
$threshold = floatval($_POST['threshold']);
|
||||||
$enable_expense = intval($_POST['enable_expense'] ?? 0);
|
$account = intval($_POST['account']);
|
||||||
|
$expense_vendor = intval($_POST['expense_vendor']) ?? 0;
|
||||||
|
$expense_category = intval($_POST['expense_category']) ?? 0;
|
||||||
$percentage_fee = floatval($_POST['percentage_fee']) / 100 ?? 0;
|
$percentage_fee = floatval($_POST['percentage_fee']) / 100 ?? 0;
|
||||||
$flat_fee = floatval($_POST['flat_fee']) ?? 0;
|
$flat_fee = floatval($_POST['flat_fee']) ?? 0;
|
||||||
|
|
||||||
// Check to ensure provider isn't added twice
|
// Check to ensure provider isn't added twice
|
||||||
$sql = "SELECT 1 FROM payment_providers WHERE payment_provider_name = '$provider' LIMIT 1";
|
$sql = mysqli_query($mysqli, "SELECT 1 FROM payment_providers WHERE payment_provider_name = '$provider' LIMIT 1");
|
||||||
$result = mysqli_query($mysqli, $sql);
|
if (mysqli_num_rows($sql) > 0) {
|
||||||
if (mysqli_num_rows($result) > 0) {
|
|
||||||
flash_alert("Payment Provider <strong>$provider</strong> already exists", 'error');
|
flash_alert("Payment Provider <strong>$provider</strong> already exists", 'error');
|
||||||
redirect();
|
redirect();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for Stripe Account, if not create it
|
mysqli_query($mysqli,"INSERT INTO payment_providers SET payment_provider_name = '$provider', payment_provider_public_key = '$public_key', payment_provider_private_key = '$private_key', payment_provider_threshold = $threshold, payment_provider_account = $account, payment_provider_expense_vendor = $expense_vendor, payment_provider_expense_category = $expense_category, payment_provider_expense_percentage_fee = $percentage_fee, payment_provider_expense_flat_fee = $flat_fee");
|
||||||
$sql_account = mysqli_query($mysqli,"SELECT account_id FROM accounts WHERE account_name = '$provider' AND account_archived_at IS NULL LIMIT 1");
|
|
||||||
if (mysqli_num_rows($sql_account) == 0) {
|
|
||||||
$account_id = mysqli_insert_id($mysqli);
|
|
||||||
} else {
|
|
||||||
$row = mysqli_fetch_array($sql_account);
|
|
||||||
$account_id = intval($row['account_id']);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Expense defaults
|
|
||||||
$category_id = 0;
|
|
||||||
$vendor_id = 0;
|
|
||||||
|
|
||||||
if ($enable_expense) {
|
|
||||||
// Category
|
|
||||||
$sql_category = mysqli_query($mysqli,"SELECT category_id FROM categories WHERE category_name = 'Payment Processing' AND category_type = 'Expense' AND category_archived_at IS NULL LIMIT 1");
|
|
||||||
if (mysqli_num_rows($sql_category) == 0) {
|
|
||||||
mysqli_query($mysqli,"INSERT INTO categories SET category_name = 'Processing Fee', category_type = 'Payment Processing', category_color = 'gray'");
|
|
||||||
$category_id = mysqli_insert_id($mysqli);
|
|
||||||
} else {
|
|
||||||
$row = mysqli_fetch_array($sql_category);
|
|
||||||
$category_id = intval($row['category_id']);
|
|
||||||
}
|
|
||||||
// Vendor
|
|
||||||
$sql_vendor = mysqli_query($mysqli,"SELECT vendor_id FROM vendors WHERE vendor_name = '$provider' AND vendor_client_id = 0 AND vendor_archived_at IS NULL LIMIT 1");
|
|
||||||
if (mysqli_num_rows($sql_vendor) == 0) {
|
|
||||||
mysqli_query($mysqli,"INSERT INTO vendors SET vendor_name = '$provider', vendor_description = 'Payment Processor Provider', vendor_client_id = 0");
|
|
||||||
$vendor_id = mysqli_insert_id($mysqli);
|
|
||||||
} else {
|
|
||||||
$row = mysqli_fetch_array($sql_vendor);
|
|
||||||
$vendor_id = intval($row['vendor_id']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mysqli_query($mysqli,"INSERT INTO payment_providers SET payment_provider_name = '$provider', payment_provider_public_key = '$public_key', payment_provider_private_key = '$private_key', payment_provider_threshold = $threshold, payment_provider_account = $account_id, payment_provider_expense_vendor = $vendor_id, payment_provider_expense_category = $category_id, payment_provider_expense_percentage_fee = $percentage_fee, payment_provider_expense_flat_fee = $flat_fee");
|
|
||||||
|
|
||||||
$provider_id = mysqli_insert_id($mysqli);
|
$provider_id = mysqli_insert_id($mysqli);
|
||||||
|
|
||||||
|
|
@ -81,11 +48,13 @@ if (isset($_POST['edit_payment_provider'])) {
|
||||||
$public_key = sanitizeInput($_POST['public_key']);
|
$public_key = sanitizeInput($_POST['public_key']);
|
||||||
$private_key = sanitizeInput($_POST['private_key']);
|
$private_key = sanitizeInput($_POST['private_key']);
|
||||||
$threshold = floatval($_POST['threshold']);
|
$threshold = floatval($_POST['threshold']);
|
||||||
$enable_expense = intval($_POST['enable_expense'] ?? 0);
|
$account = intval($_POST['account']);
|
||||||
|
$expense_vendor = intval($_POST['expense_vendor']) ?? 0;
|
||||||
|
$expense_category = intval($_POST['expense_category']) ?? 0;
|
||||||
$percentage_fee = floatval($_POST['percentage_fee']) / 100;
|
$percentage_fee = floatval($_POST['percentage_fee']) / 100;
|
||||||
$flat_fee = floatval($_POST['flat_fee']);
|
$flat_fee = floatval($_POST['flat_fee']);
|
||||||
|
|
||||||
mysqli_query($mysqli,"UPDATE payment_providers SET payment_provider_public_key = '$public_key', payment_provider_private_key = '$private_key', payment_provider_threshold = $threshold, payment_provider_expense_percentage_fee = $percentage_fee, payment_provider_expense_flat_fee = $flat_fee WHERE payment_provider_id = $provider_id");
|
mysqli_query($mysqli,"UPDATE payment_providers SET payment_provider_public_key = '$public_key', payment_provider_private_key = '$private_key', payment_provider_threshold = $threshold, payment_provider_account = $account, payment_provider_expense_vendor = $expense_vendor, payment_provider_expense_category = $expense_category, payment_provider_expense_percentage_fee = $percentage_fee, payment_provider_expense_flat_fee = $flat_fee WHERE payment_provider_id = $provider_id");
|
||||||
|
|
||||||
logAction("Payment Provider", "Edit", "$session_name edited Payment Provider $provider");
|
logAction("Payment Provider", "Edit", "$session_name edited Payment Provider $provider");
|
||||||
|
|
||||||
|
|
@ -98,7 +67,7 @@ if (isset($_POST['edit_payment_provider'])) {
|
||||||
if (isset($_GET['delete_payment_provider'])) {
|
if (isset($_GET['delete_payment_provider'])) {
|
||||||
|
|
||||||
validateCSRFToken($_GET['csrf_token']);
|
validateCSRFToken($_GET['csrf_token']);
|
||||||
|
|
||||||
$provider_id = intval($_GET['delete_payment_provider']);
|
$provider_id = intval($_GET['delete_payment_provider']);
|
||||||
|
|
||||||
// When deleted it cascades deletes
|
// When deleted it cascades deletes
|
||||||
|
|
|
||||||
|
|
@ -49,3 +49,17 @@ if (isset($_POST['edit_favicon_settings'])) {
|
||||||
redirect();
|
redirect();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isset($_GET['reset_favicon'])) {
|
||||||
|
|
||||||
|
if (file_exists("../uploads/favicon.ico")) {
|
||||||
|
unlink("../uploads/favicon.ico");
|
||||||
|
}
|
||||||
|
|
||||||
|
logAction("Settings", "Edit", "$session_name reset Favicon");
|
||||||
|
|
||||||
|
flash_alert("Favicon reset", 'error');
|
||||||
|
|
||||||
|
redirect();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,18 +13,18 @@ $sql = mysqli_query($mysqli, "
|
||||||
clients.client_name,
|
clients.client_name,
|
||||||
client_payment_provider.payment_provider_client
|
client_payment_provider.payment_provider_client
|
||||||
FROM client_saved_payment_methods
|
FROM client_saved_payment_methods
|
||||||
LEFT JOIN payment_providers
|
LEFT JOIN payment_providers
|
||||||
ON client_saved_payment_methods.saved_payment_provider_id = payment_providers.payment_provider_id
|
ON client_saved_payment_methods.saved_payment_provider_id = payment_providers.payment_provider_id
|
||||||
LEFT JOIN clients
|
LEFT JOIN clients
|
||||||
ON client_saved_payment_methods.saved_payment_client_id = clients.client_id
|
ON client_saved_payment_methods.saved_payment_client_id = clients.client_id
|
||||||
LEFT JOIN client_payment_provider
|
LEFT JOIN client_payment_provider
|
||||||
ON client_payment_provider.client_id = client_saved_payment_methods.saved_payment_client_id
|
ON client_payment_provider.client_id = client_saved_payment_methods.saved_payment_client_id
|
||||||
AND client_payment_provider.payment_provider_id = client_saved_payment_methods.saved_payment_provider_id
|
AND client_payment_provider.payment_provider_id = client_saved_payment_methods.saved_payment_provider_id
|
||||||
WHERE
|
WHERE
|
||||||
client_name LIKE '%$q%'
|
client_name LIKE '%$q%'
|
||||||
OR payment_provider_name LIKE '%$q%'
|
OR payment_provider_name LIKE '%$q%'
|
||||||
OR saved_payment_description LIKE '%$q%'
|
OR saved_payment_description LIKE '%$q%'
|
||||||
OR payment_provider_client LIKE '%$q%'
|
OR payment_provider_client LIKE '%$q%'
|
||||||
OR saved_payment_provider_method LIKE '%$q%'
|
OR saved_payment_provider_method LIKE '%$q%'
|
||||||
ORDER BY $sort $order
|
ORDER BY $sort $order
|
||||||
");
|
");
|
||||||
|
|
@ -33,6 +33,16 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|
||||||
|
<ol class="breadcrumb d-print-none">
|
||||||
|
<li class="breadcrumb-item">
|
||||||
|
<a href="/admin">Admin</a>
|
||||||
|
</li>
|
||||||
|
<li class="breadcrumb-item">
|
||||||
|
<a href="payment_provider.php">Payment Providers</a>
|
||||||
|
</li>
|
||||||
|
<li class="breadcrumb-item active">Saved Payment Methods (Stripe)</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
<div class="card card-dark">
|
<div class="card card-dark">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3 class="card-title"><i class="fas fa-fw fa-credit-card mr-2"></i>Saved Payment Methods</h3>
|
<h3 class="card-title"><i class="fas fa-fw fa-credit-card mr-2"></i>Saved Payment Methods</h3>
|
||||||
|
|
@ -107,8 +117,16 @@ $num_rows = mysqli_fetch_row(mysqli_query($mysqli, "SELECT FOUND_ROWS()"));
|
||||||
|
|
||||||
?>
|
?>
|
||||||
<tr>
|
<tr>
|
||||||
<td><?php echo $client_name; ?> (<?php echo $client_id; ?>)</td>
|
<td>
|
||||||
<td><?php echo $provider_name; ?> (<?php echo $provider_id; ?>)</td>
|
<?= $client_name ?>
|
||||||
|
<br>
|
||||||
|
<small class="text-secondary">ID: <?= $client_id ?></small>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<?= $provider_name ?>
|
||||||
|
<br>
|
||||||
|
<small class="text-secondary">ID: <?= $provider_id ?></small>
|
||||||
|
</td>
|
||||||
<td><?php echo $saved_payment_description; ?></td>
|
<td><?php echo $saved_payment_description; ?></td>
|
||||||
<td><?php echo $provider_client; ?></td>
|
<td><?php echo $provider_client; ?></td>
|
||||||
<td><?php echo $provider_payment_method; ?></td>
|
<td><?php echo $provider_payment_method; ?></td>
|
||||||
|
|
|
||||||
|
|
@ -57,11 +57,12 @@ require_once "includes/inc_all_admin.php";
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<button type="submit" name="edit_favicon_settings" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Upload Icon</button>
|
<button type="submit" name="edit_favicon_settings" class="btn btn-primary text-bold"><i class="fa fa-check mr-2"></i>Upload Icon</button>
|
||||||
|
<?php if(file_exists("../uploads/favicon.ico")) { ?>
|
||||||
|
<a href="post.php?reset_favicon" class="btn btn-outline-danger"><i class="fas fa-redo-alt mr-2"></i>Reset Favicon</a>
|
||||||
|
<?php } ?>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
require_once "../includes/footer.php";
|
require_once "../includes/footer.php";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ if (isset($_GET['client_id'])) {
|
||||||
if (isset($_GET['contact_id'])) {
|
if (isset($_GET['contact_id'])) {
|
||||||
$contact_id = intval($_GET['contact_id']);
|
$contact_id = intval($_GET['contact_id']);
|
||||||
|
|
||||||
$sql = mysqli_query($mysqli, "SELECT * FROM contacts
|
$sql = mysqli_query($mysqli, "SELECT * FROM contacts
|
||||||
LEFT JOIN clients ON client_id = contact_client_id
|
LEFT JOIN clients ON client_id = contact_client_id
|
||||||
LEFT JOIN locations ON location_id = contact_location_id
|
LEFT JOIN locations ON location_id = contact_location_id
|
||||||
LEFT JOIN users ON user_id = contact_user_id
|
LEFT JOIN users ON user_id = contact_user_id
|
||||||
|
|
@ -76,7 +76,7 @@ if (isset($_GET['contact_id'])) {
|
||||||
|
|
||||||
// Linked Software Licenses
|
// Linked Software Licenses
|
||||||
$sql_linked_software = mysqli_query($mysqli, "SELECT * FROM software_contacts, software
|
$sql_linked_software = mysqli_query($mysqli, "SELECT * FROM software_contacts, software
|
||||||
WHERE software_contacts.contact_id = $contact_id
|
WHERE software_contacts.contact_id = $contact_id
|
||||||
AND software_contacts.software_id = software.software_id
|
AND software_contacts.software_id = software.software_id
|
||||||
AND software_archived_at IS NULL
|
AND software_archived_at IS NULL
|
||||||
ORDER BY software_name ASC"
|
ORDER BY software_name ASC"
|
||||||
|
|
@ -109,7 +109,7 @@ if (isset($_GET['contact_id'])) {
|
||||||
$ticket_count = mysqli_num_rows($sql_related_tickets);
|
$ticket_count = mysqli_num_rows($sql_related_tickets);
|
||||||
|
|
||||||
// Related Recurring Tickets Query
|
// Related Recurring Tickets Query
|
||||||
$sql_related_recurring_tickets = mysqli_query($mysqli, "SELECT * FROM recurring_tickets
|
$sql_related_recurring_tickets = mysqli_query($mysqli, "SELECT * FROM recurring_tickets
|
||||||
WHERE recurring_ticket_contact_id = $contact_id
|
WHERE recurring_ticket_contact_id = $contact_id
|
||||||
ORDER BY recurring_ticket_next_run DESC"
|
ORDER BY recurring_ticket_next_run DESC"
|
||||||
);
|
);
|
||||||
|
|
@ -144,7 +144,7 @@ if (isset($_GET['contact_id'])) {
|
||||||
|
|
||||||
// Linked Services
|
// Linked Services
|
||||||
$sql_linked_services = mysqli_query($mysqli, "SELECT * FROM service_contacts, services
|
$sql_linked_services = mysqli_query($mysqli, "SELECT * FROM service_contacts, services
|
||||||
WHERE service_contacts.contact_id = $contact_id
|
WHERE service_contacts.contact_id = $contact_id
|
||||||
AND service_contacts.service_id = services.service_id
|
AND service_contacts.service_id = services.service_id
|
||||||
ORDER BY service_name ASC"
|
ORDER BY service_name ASC"
|
||||||
);
|
);
|
||||||
|
|
@ -155,7 +155,7 @@ if (isset($_GET['contact_id'])) {
|
||||||
// Linked Documents
|
// Linked Documents
|
||||||
$sql_linked_documents = mysqli_query($mysqli, "SELECT * FROM contact_documents, documents
|
$sql_linked_documents = mysqli_query($mysqli, "SELECT * FROM contact_documents, documents
|
||||||
LEFT JOIN users ON document_created_by = user_id
|
LEFT JOIN users ON document_created_by = user_id
|
||||||
WHERE contact_documents.contact_id = $contact_id
|
WHERE contact_documents.contact_id = $contact_id
|
||||||
AND contact_documents.document_id = documents.document_id
|
AND contact_documents.document_id = documents.document_id
|
||||||
AND document_archived_at IS NULL
|
AND document_archived_at IS NULL
|
||||||
ORDER BY document_name ASC"
|
ORDER BY document_name ASC"
|
||||||
|
|
@ -166,7 +166,7 @@ if (isset($_GET['contact_id'])) {
|
||||||
|
|
||||||
// Linked Files
|
// Linked Files
|
||||||
$sql_linked_files = mysqli_query($mysqli, "SELECT * FROM contact_files, files
|
$sql_linked_files = mysqli_query($mysqli, "SELECT * FROM contact_files, files
|
||||||
WHERE contact_files.contact_id = $contact_id
|
WHERE contact_files.contact_id = $contact_id
|
||||||
AND contact_files.file_id = files.file_id
|
AND contact_files.file_id = files.file_id
|
||||||
AND file_archived_at IS NULL
|
AND file_archived_at IS NULL
|
||||||
ORDER BY file_name ASC"
|
ORDER BY file_name ASC"
|
||||||
|
|
@ -473,8 +473,8 @@ if (isset($_GET['contact_id'])) {
|
||||||
<i class="fas fa-fw fa-copy mr-2"></i>Copy
|
<i class="fas fa-fw fa-copy mr-2"></i>Copy
|
||||||
</a>
|
</a>
|
||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
<a class="dropdown-item"
|
<a class="dropdown-item"
|
||||||
href="post.php?unlink_asset_from_contact&contact_id=<?php echo $contact_id; ?>&asset_id=<?php echo $asset_id; ?>"
|
href="post.php?unlink_asset_from_contact&contact_id=<?php echo $contact_id; ?>&asset_id=<?php echo $asset_id; ?>"
|
||||||
class="btn btn-secondary btn-sm" title="Unlink">
|
class="btn btn-secondary btn-sm" title="Unlink">
|
||||||
<i class="fas fa-fw fa-unlink mr-2"></i>Unlink
|
<i class="fas fa-fw fa-unlink mr-2"></i>Unlink
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -613,8 +613,8 @@ if (isset($_GET['contact_id'])) {
|
||||||
<i class="fas fa-fw fa-share-alt mr-2"></i>Share
|
<i class="fas fa-fw fa-share-alt mr-2"></i>Share
|
||||||
</a>
|
</a>
|
||||||
<div class="dropdown-divider"></div>
|
<div class="dropdown-divider"></div>
|
||||||
<a class="dropdown-item"
|
<a class="dropdown-item"
|
||||||
href="post.php?unlink_credential_from_contact&contact_id=<?php echo $contact_id; ?>&credential_id=<?php echo $credential_id; ?>"
|
href="post.php?unlink_credential_from_contact&contact_id=<?php echo $contact_id; ?>&credential_id=<?php echo $credential_id; ?>"
|
||||||
class="btn btn-secondary btn-sm" title="Unlink">
|
class="btn btn-secondary btn-sm" title="Unlink">
|
||||||
<i class="fas fa-fw fa-unlink mr-2"></i>Unlink
|
<i class="fas fa-fw fa-unlink mr-2"></i>Unlink
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -797,7 +797,9 @@ if (isset($_GET['contact_id'])) {
|
||||||
<div class="card-header py-2">
|
<div class="card-header py-2">
|
||||||
<h3 class="card-title mt-2"><i class="fa fa-fw fa-life-ring mr-2"></i>Related Tickets</h3>
|
<h3 class="card-title mt-2"><i class="fa fa-fw fa-life-ring mr-2"></i>Related Tickets</h3>
|
||||||
<div class="card-tools">
|
<div class="card-tools">
|
||||||
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#addTicketModal">
|
<button type="button" class="btn btn-primary ajax-modal"
|
||||||
|
data-modal-url="modals/ticket/ticket_add.php?<?= $client_url ?>&contact_id=<?= $contact_id ?>"
|
||||||
|
data-modal-size="lg">
|
||||||
<i class="fas fa-plus mr-2"></i>New Ticket
|
<i class="fas fa-plus mr-2"></i>New Ticket
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1071,7 +1073,7 @@ if (isset($_GET['contact_id'])) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card card-dark <?php if ($note_count == 0) { echo "d-none"; } ?>">
|
<div class="card card-dark <?php if ($note_count == 0) { echo "d-none"; } ?>">
|
||||||
<div class="card-header py-2">
|
<div class="card-header py-2">
|
||||||
<h3 class="card-title mt-2"><i class="fa fa-fw fa-sticky-note mr-2"></i>Notes</h3>
|
<h3 class="card-title mt-2"><i class="fa fa-fw fa-sticky-note mr-2"></i>Notes</h3>
|
||||||
|
|
|
||||||
|
|
@ -152,8 +152,6 @@ ob_start();
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php if ($client_id) { ?>
|
<?php if ($client_id) { ?>
|
||||||
|
|
|
||||||
|
|
@ -16,13 +16,13 @@ if (!isset($_SESSION)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isset($_SESSION['client_logged_in']) || !$_SESSION['client_logged_in']) {
|
if (!isset($_SESSION['client_logged_in']) || !$_SESSION['client_logged_in']) {
|
||||||
header("Location: /client/login.php");
|
header("Location: /login.php");
|
||||||
die;
|
die;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check user type
|
// Check user type
|
||||||
if ($_SESSION['user_type'] !== 2) {
|
if ($_SESSION['user_type'] !== 2) {
|
||||||
header("Location: /client/login.php");
|
header("Location: /login.php");
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
234
client/login.php
234
client/login.php
|
|
@ -1,234 +0,0 @@
|
||||||
<?php
|
|
||||||
/*
|
|
||||||
* Client Portal
|
|
||||||
* Landing / Home page for the client portal
|
|
||||||
*/
|
|
||||||
|
|
||||||
header("Content-Security-Policy: default-src 'self'");
|
|
||||||
|
|
||||||
require_once '../config.php';
|
|
||||||
|
|
||||||
require_once '../functions.php';
|
|
||||||
|
|
||||||
require_once '../includes/load_global_settings.php';
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set Timezone after session_start
|
|
||||||
require_once "../includes/inc_set_timezone.php";
|
|
||||||
|
|
||||||
// Check to see if client portal is enabled
|
|
||||||
if($config_client_portal_enable == 0) {
|
|
||||||
echo "Client Portal is Disabled";
|
|
||||||
exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
$session_ip = sanitizeInput(getIP());
|
|
||||||
$session_user_agent = sanitizeInput($_SERVER['HTTP_USER_AGENT']);
|
|
||||||
|
|
||||||
$sql_settings = mysqli_query($mysqli, "SELECT config_azure_client_id, config_login_message FROM settings WHERE company_id = 1");
|
|
||||||
$settings = mysqli_fetch_array($sql_settings);
|
|
||||||
$azure_client_id = $settings['config_azure_client_id'];
|
|
||||||
$config_login_message = nullable_htmlentities($settings['config_login_message']);
|
|
||||||
|
|
||||||
$company_sql = mysqli_query($mysqli, "SELECT company_name, company_logo FROM companies WHERE company_id = 1");
|
|
||||||
$company_results = mysqli_fetch_array($company_sql);
|
|
||||||
$company_name = $company_results['company_name'];
|
|
||||||
$company_logo = $company_results['company_logo'];
|
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['login'])) {
|
|
||||||
|
|
||||||
$email = sanitizeInput($_POST['email']);
|
|
||||||
$password = $_POST['password'];
|
|
||||||
|
|
||||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
|
||||||
|
|
||||||
header("HTTP/1.1 401 Unauthorized");
|
|
||||||
|
|
||||||
$_SESSION['login_message'] = 'Invalid e-mail';
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
$sql = mysqli_query($mysqli, "SELECT * FROM users
|
|
||||||
LEFT JOIN contacts ON user_id = contact_user_id
|
|
||||||
LEFT JOIN clients ON contact_client_id = client_id
|
|
||||||
WHERE user_email = '$email'
|
|
||||||
AND client_archived_at IS NULL
|
|
||||||
AND user_archived_at IS NULL
|
|
||||||
AND user_type = 2
|
|
||||||
AND user_status = 1
|
|
||||||
LIMIT 1"
|
|
||||||
);
|
|
||||||
|
|
||||||
$row = mysqli_fetch_array($sql);
|
|
||||||
$client_id = intval($row['contact_client_id']);
|
|
||||||
$user_id = intval($row['user_id']);
|
|
||||||
$session_user_id = $user_id; // to pass the user_id to logAction function
|
|
||||||
$contact_id = intval($row['contact_id']);
|
|
||||||
$user_email = sanitizeInput($row['user_email']);
|
|
||||||
$user_auth_method = sanitizeInput($row['user_auth_method']);
|
|
||||||
|
|
||||||
if ($user_auth_method == 'local') {
|
|
||||||
if (password_verify($password, $row['user_password'])) {
|
|
||||||
|
|
||||||
$_SESSION['client_logged_in'] = true;
|
|
||||||
$_SESSION['client_id'] = $client_id;
|
|
||||||
$_SESSION['user_id'] = $user_id;
|
|
||||||
$_SESSION['user_type'] = 2;
|
|
||||||
$_SESSION['contact_id'] = $contact_id;
|
|
||||||
$_SESSION['login_method'] = "local";
|
|
||||||
|
|
||||||
header("Location: index.php");
|
|
||||||
|
|
||||||
// Logging
|
|
||||||
logAction("Client Login", "Success", "Client contact $user_email successfully logged in locally", $client_id, $user_id);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// Logging
|
|
||||||
logAction("Client Login", "Failed", "Failed client portal login attempt using $email (incorrect password for contact ID $contact_id)", $client_id, $user_id);
|
|
||||||
|
|
||||||
header("HTTP/1.1 401 Unauthorized");
|
|
||||||
$_SESSION['login_message'] = 'Incorrect username or password.';
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// Logging
|
|
||||||
logAction("Client Login", "Failed", "Failed client portal login attempt using $email (invalid email/not allowed local auth)");
|
|
||||||
|
|
||||||
header("HTTP/1.1 401 Unauthorized");
|
|
||||||
|
|
||||||
$_SESSION['login_message'] = 'Incorrect username or password.';
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
?>
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<title><?php echo $company_name; ?> | Client Portal Login</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">
|
|
||||||
|
|
||||||
<!-- Favicon - If Fav Icon exists else use the default one -->
|
|
||||||
<?php if(file_exists('../uploads/favicon.ico')) { ?>
|
|
||||||
<link rel="icon" type="image/x-icon" href="../uploads/favicon.ico">
|
|
||||||
<?php } ?>
|
|
||||||
|
|
||||||
<!-- Font Awesome -->
|
|
||||||
<link rel="stylesheet" href="../plugins/fontawesome-free/css/all.min.css">
|
|
||||||
|
|
||||||
<!-- Theme style -->
|
|
||||||
<link rel="stylesheet" href="../plugins/adminlte/css/adminlte.min.css">
|
|
||||||
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body class="hold-transition login-page">
|
|
||||||
<div class="login-box">
|
|
||||||
<div class="login-logo">
|
|
||||||
<?php if (!empty($company_logo)) { ?>
|
|
||||||
<img alt="<?=$company_name?> logo" height="110" width="380" class="img-fluid" src="<?php echo "../uploads/settings/$company_logo"; ?>">
|
|
||||||
<?php } else { ?>
|
|
||||||
<b><?=$company_name?></b> <br>Client Portal Login</h2>
|
|
||||||
<?php } ?>
|
|
||||||
</div>
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-body login-card-body">
|
|
||||||
<?php if(!empty($config_login_message)){ ?>
|
|
||||||
<p class="login-box-msg px-0"><?php echo nl2br($config_login_message); ?></p>
|
|
||||||
<?php } ?>
|
|
||||||
<?php
|
|
||||||
if (!empty($_SESSION['login_message'])) { ?>
|
|
||||||
<p class="login-box-msg text-danger">
|
|
||||||
<?php
|
|
||||||
echo $_SESSION['login_message'];
|
|
||||||
unset($_SESSION['login_message']);
|
|
||||||
?>
|
|
||||||
</p>
|
|
||||||
<?php
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
<form method="post">
|
|
||||||
<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>
|
|
||||||
<div class="input-group mb-3">
|
|
||||||
<input type="password" class="form-control" placeholder="Client Password" name="password" required>
|
|
||||||
<div class="input-group-append">
|
|
||||||
<div class="input-group-text">
|
|
||||||
<span class="fas fa-lock"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="submit" class="btn btn-success btn-block mb-3" name="login">Sign in</button>
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
|
|
||||||
<?php
|
|
||||||
if (!empty($config_smtp_host)) { ?>
|
|
||||||
<h5 class="text-center"><a href="login_reset.php">Forgot password?</a></h5>
|
|
||||||
<?php } ?>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<?php
|
|
||||||
if (!empty($azure_client_id)) { ?>
|
|
||||||
<hr>
|
|
||||||
<div class="col text-center">
|
|
||||||
<a href="login_microsoft.php">
|
|
||||||
<button type="button" class="btn btn-secondary">Login with Microsoft Entra</button>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<?php } ?>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<!-- /.login-card-body -->
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<!-- /.div.card -->
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<!-- /.login-box -->
|
|
||||||
|
|
||||||
<?php
|
|
||||||
if (!$config_whitelabel_enabled) {
|
|
||||||
echo '<small class="text-muted">Powered by ITFlow</small>';
|
|
||||||
}
|
|
||||||
?>
|
|
||||||
|
|
||||||
<!-- 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="../plugins/adminlte/js/adminlte.min.js"></script>
|
|
||||||
|
|
||||||
<!-- Prevents resubmit on refresh or back -->
|
|
||||||
<script src="../js/login_prevent_resubmit.js"></script>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -133,20 +133,20 @@ if (isset($_POST['code']) && $_POST['state'] == session_id()) {
|
||||||
header("Location: index.php");
|
header("Location: index.php");
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
$_SESSION['login_message'] = 'Something went wrong with logging you in: Your account is not configured for Entra SSO. Please ensure you are setup in ITFlow as a contact and have Entra SSO configured.';
|
$_SESSION['login_message'] = 'Something went wrong with logging you in: Your account is not configured for Entra SSO. Please ensure you are setup in ITFlow as a contact and have Entra SSO configured.';
|
||||||
|
|
||||||
header("Location: index.php");
|
header("Location: index.php");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
header('Location: index.php');
|
header('Location: index.php');
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
echo "Error getting access_token";
|
echo "Error getting access_token";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ require_once '../includes/load_global_settings.php';
|
||||||
|
|
||||||
|
|
||||||
if (empty($config_smtp_host)) {
|
if (empty($config_smtp_host)) {
|
||||||
header("Location: login.php");
|
header("Location: /login.php");
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -157,7 +157,7 @@ if ($_SERVER['REQUEST_METHOD'] == "POST") {
|
||||||
|
|
||||||
// Redirect to login page
|
// Redirect to login page
|
||||||
$_SESSION['login_message'] = "Password reset successfully!";
|
$_SESSION['login_message'] = "Password reset successfully!";
|
||||||
header("Location: login.php");
|
header("Location: /login.php");
|
||||||
exit();
|
exit();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -275,7 +275,7 @@ if ($_SERVER['REQUEST_METHOD'] == "POST") {
|
||||||
?>
|
?>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<a href="login.php">Back to login</a>
|
<a href="/login.php">Back to login</a>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -320,7 +320,7 @@ if (isset($_GET['logout'])) {
|
||||||
session_unset();
|
session_unset();
|
||||||
session_destroy();
|
session_destroy();
|
||||||
|
|
||||||
redirect('login.php');
|
redirect('/login.php');
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
217
functions.php
217
functions.php
|
|
@ -1784,3 +1784,220 @@ function cleanupUnusedImages(string $html, string $folderFsPath, string $folderW
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple mysqli helper functions
|
||||||
|
* - Prepared statements under the hood
|
||||||
|
* - "Old style" INSERT/UPDATE SET feeling
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Core executor: prepares, binds, executes.
|
||||||
|
*
|
||||||
|
* @throws Exception on error
|
||||||
|
*/
|
||||||
|
function dbExecute(mysqli $mysqli, string $sql, array $params = []): mysqli_stmt
|
||||||
|
{
|
||||||
|
$stmt = $mysqli->prepare($sql);
|
||||||
|
if (!$stmt) {
|
||||||
|
throw new Exception('MySQLi prepare error: ' . $mysqli->error . ' | SQL: ' . $sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($params)) {
|
||||||
|
$types = '';
|
||||||
|
$values = [];
|
||||||
|
|
||||||
|
foreach ($params as $param) {
|
||||||
|
if (is_int($param)) {
|
||||||
|
$types .= 'i';
|
||||||
|
} elseif (is_float($param)) {
|
||||||
|
$types .= 'd';
|
||||||
|
} elseif (is_bool($param)) {
|
||||||
|
$types .= 'i';
|
||||||
|
$param = $param ? 1 : 0;
|
||||||
|
} elseif (is_null($param)) {
|
||||||
|
$types .= 's';
|
||||||
|
$param = null;
|
||||||
|
} else {
|
||||||
|
$types .= 's';
|
||||||
|
}
|
||||||
|
$values[] = $param;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$stmt->bind_param($types, ...$values)) {
|
||||||
|
throw new Exception('MySQLi bind_param error: ' . $stmt->error . ' | SQL: ' . $sql);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$stmt->execute()) {
|
||||||
|
throw new Exception('MySQLi execute error: ' . $stmt->error . ' | SQL: ' . $sql);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $stmt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch all rows as associative arrays.
|
||||||
|
*/
|
||||||
|
function dbFetchAll(mysqli $mysqli, string $sql, array $params = []): array
|
||||||
|
{
|
||||||
|
$stmt = dbExecute($mysqli, $sql, $params);
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
if ($result === false) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return $result->fetch_all(MYSQLI_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch a single row (assoc) or null if none.
|
||||||
|
*/
|
||||||
|
function dbFetchOne(mysqli $mysqli, string $sql, array $params = []): ?array
|
||||||
|
{
|
||||||
|
$stmt = dbExecute($mysqli, $sql, $params);
|
||||||
|
$result = $stmt->get_result();
|
||||||
|
if ($result === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
$row = $result->fetch_assoc();
|
||||||
|
return $row !== null ? $row : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch a single scalar value (first column of first row) or null.
|
||||||
|
*/
|
||||||
|
function dbFetchValue(mysqli $mysqli, string $sql, array $params = [])
|
||||||
|
{
|
||||||
|
$row = dbFetchOne($mysqli, $sql, $params);
|
||||||
|
if ($row === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return reset($row);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* INSERT using "SET" style.
|
||||||
|
* Example:
|
||||||
|
* $id = dbInsert($mysqli, 'clients', [
|
||||||
|
* 'client_name' => $name,
|
||||||
|
* 'client_type' => $type,
|
||||||
|
* ]);
|
||||||
|
*
|
||||||
|
* @return int insert_id
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
function dbInsert(mysqli $mysqli, string $table, array $data): int
|
||||||
|
{
|
||||||
|
if (empty($data)) {
|
||||||
|
throw new InvalidArgumentException('dbInsert called with empty $data');
|
||||||
|
}
|
||||||
|
|
||||||
|
$setParts = [];
|
||||||
|
foreach ($data as $column => $_) {
|
||||||
|
$setParts[] = "$column = ?";
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "INSERT INTO $table SET " . implode(', ', $setParts);
|
||||||
|
$params = array_values($data);
|
||||||
|
|
||||||
|
dbExecute($mysqli, $sql, $params);
|
||||||
|
|
||||||
|
return $mysqli->insert_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function dbUpdate(
|
||||||
|
mysqli $mysqli,
|
||||||
|
string $table,
|
||||||
|
array $data,
|
||||||
|
$where,
|
||||||
|
array $whereParams = []
|
||||||
|
): int {
|
||||||
|
if (empty($data)) {
|
||||||
|
throw new InvalidArgumentException('dbUpdate called with empty $data');
|
||||||
|
}
|
||||||
|
if (empty($where)) {
|
||||||
|
throw new InvalidArgumentException('dbUpdate requires a WHERE clause');
|
||||||
|
}
|
||||||
|
|
||||||
|
$setParts = [];
|
||||||
|
foreach ($data as $column => $_) {
|
||||||
|
$setParts[] = "$column = ?";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_array($where)) {
|
||||||
|
$whereParts = [];
|
||||||
|
$whereParams = [];
|
||||||
|
foreach ($where as $column => $value) {
|
||||||
|
$whereParts[] = "$column = ?";
|
||||||
|
$whereParams[] = $value;
|
||||||
|
}
|
||||||
|
$whereSql = implode(' AND ', $whereParts);
|
||||||
|
} else {
|
||||||
|
$whereSql = $where;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "UPDATE $table SET " . implode(', ', $setParts) . " WHERE $whereSql";
|
||||||
|
$params = array_merge(array_values($data), $whereParams);
|
||||||
|
|
||||||
|
$stmt = dbExecute($mysqli, $sql, $params);
|
||||||
|
return $stmt->affected_rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DELETE helper.
|
||||||
|
*
|
||||||
|
* WHERE can be:
|
||||||
|
* - array: ['client_id' => $id] (auto "client_id = ?")
|
||||||
|
* - string: 'client_id = ?' (use with $whereParams)
|
||||||
|
*
|
||||||
|
* @return int affected_rows
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
function dbDelete(
|
||||||
|
mysqli $mysqli,
|
||||||
|
string $table,
|
||||||
|
$where,
|
||||||
|
array $whereParams = []
|
||||||
|
): int {
|
||||||
|
if (empty($where)) {
|
||||||
|
throw new InvalidArgumentException('dbDelete requires a WHERE clause');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_array($where)) {
|
||||||
|
$whereParts = [];
|
||||||
|
$whereParams = [];
|
||||||
|
foreach ($where as $column => $value) {
|
||||||
|
$whereParts[] = "$column = ?";
|
||||||
|
$whereParams[] = $value;
|
||||||
|
}
|
||||||
|
$whereSql = implode(' AND ', $whereParts);
|
||||||
|
} else {
|
||||||
|
$whereSql = $where;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "DELETE FROM $table WHERE $whereSql";
|
||||||
|
$stmt = dbExecute($mysqli, $sql, $whereParams);
|
||||||
|
return $stmt->affected_rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transaction helpers (optional sugar).
|
||||||
|
*/
|
||||||
|
function dbBegin(mysqli $mysqli): void
|
||||||
|
{
|
||||||
|
$mysqli->begin_transaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
function dbCommit(mysqli $mysqli): void
|
||||||
|
{
|
||||||
|
$mysqli->commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
function dbRollback(mysqli $mysqli): void
|
||||||
|
{
|
||||||
|
$mysqli->rollback();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,4 +5,4 @@
|
||||||
* Update this file each time we merge develop into master. Format is YY.MM (add a .v if there is more than one release a month.
|
* Update this file each time we merge develop into master. Format is YY.MM (add a .v if there is more than one release a month.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
DEFINE("APP_VERSION", "25.12");
|
DEFINE("APP_VERSION", "25.12.1");
|
||||||
|
|
|
||||||
698
login.php
698
login.php
|
|
@ -1,5 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
// Unified login (Agent + Client) using one email & password
|
||||||
|
|
||||||
// Enforce a Content Security Policy for security against cross-site scripting
|
// Enforce a Content Security Policy for security against cross-site scripting
|
||||||
header("Content-Security-Policy: default-src 'self'");
|
header("Content-Security-Policy: default-src 'self'");
|
||||||
|
|
||||||
|
|
@ -11,6 +13,21 @@ if (!file_exists('config.php')) {
|
||||||
}
|
}
|
||||||
|
|
||||||
require_once "config.php";
|
require_once "config.php";
|
||||||
|
require_once "functions.php";
|
||||||
|
require_once "plugins/totp/totp.php";
|
||||||
|
|
||||||
|
// Sessions & cookies
|
||||||
|
if (session_status() === PHP_SESSION_NONE) {
|
||||||
|
// HTTP-Only cookies
|
||||||
|
ini_set("session.cookie_httponly", true);
|
||||||
|
|
||||||
|
// Tell client to only send cookie(s) over HTTPS
|
||||||
|
if ($config_https_only || !isset($config_https_only)) {
|
||||||
|
ini_set("session.cookie_secure", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
session_start();
|
||||||
|
}
|
||||||
|
|
||||||
// Check if setup mode is enabled or the variable is missing
|
// Check if setup mode is enabled or the variable is missing
|
||||||
if (!isset($config_enable_setup) || $config_enable_setup == 1) {
|
if (!isset($config_enable_setup) || $config_enable_setup == 1) {
|
||||||
|
|
@ -19,28 +36,30 @@ if (!isset($config_enable_setup) || $config_enable_setup == 1) {
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set Timezone
|
|
||||||
require_once "includes/inc_set_timezone.php";
|
|
||||||
|
|
||||||
// Check if the application is configured for HTTPS-only access
|
// Check if the application is configured for HTTPS-only access
|
||||||
if ($config_https_only && (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] !== 'on') && (!isset($_SERVER['HTTP_X_FORWARDED_PROTO']) || $_SERVER['HTTP_X_FORWARDED_PROTO'] !== 'https')) {
|
if ($config_https_only && (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] !== 'on') && (!isset($_SERVER['HTTP_X_FORWARDED_PROTO']) || $_SERVER['HTTP_X_FORWARDED_PROTO'] !== 'https')) {
|
||||||
echo "Login is restricted as ITFlow defaults to HTTPS-only for enhanced security. To login using HTTP, modify the config.php file by setting config_https_only to false. However, this is strongly discouraged, especially when accessing from potentially unsafe networks like the internet.";
|
echo "Login is restricted as ITFlow defaults to HTTPS-only for enhanced security. To login using HTTP, modify the config.php file by setting config_https_only to false. However, this is strongly discouraged, especially when accessing from potentially unsafe networks like the internet.";
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
require_once "functions.php";
|
// Set Timezone after session_start
|
||||||
|
require_once "includes/inc_set_timezone.php";
|
||||||
require_once "plugins/totp/totp.php";
|
|
||||||
|
|
||||||
|
|
||||||
// IP & User Agent for logging
|
// IP & User Agent for logging
|
||||||
$session_ip = sanitizeInput(getIP());
|
$session_ip = sanitizeInput(getIP());
|
||||||
$session_user_agent = sanitizeInput($_SERVER['HTTP_USER_AGENT']);
|
$session_user_agent = sanitizeInput($_SERVER['HTTP_USER_AGENT'] ?? '');
|
||||||
|
|
||||||
// Block brute force password attacks - check recent failed login attempts for this IP
|
// Block brute force password attacks - check recent failed login attempts for this IP
|
||||||
// Block access if more than 15 failed login attempts have happened in the last 10 minutes
|
// Block access if more than 15 failed login attempts have happened in the last 10 minutes
|
||||||
$row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT COUNT(log_id) AS failed_login_count FROM logs WHERE log_ip = '$session_ip' AND log_type = 'Login' AND log_action = 'Failed' AND log_created_at > (NOW() - INTERVAL 10 MINUTE)"));
|
$row = mysqli_fetch_assoc(mysqli_query(
|
||||||
|
$mysqli,
|
||||||
|
"SELECT COUNT(log_id) AS failed_login_count
|
||||||
|
FROM logs
|
||||||
|
WHERE log_ip = '$session_ip'
|
||||||
|
AND log_type = 'Login'
|
||||||
|
AND log_action = 'Failed'
|
||||||
|
AND log_created_at > (NOW() - INTERVAL 10 MINUTE)"
|
||||||
|
));
|
||||||
$failed_login_count = intval($row['failed_login_count']);
|
$failed_login_count = intval($row['failed_login_count']);
|
||||||
|
|
||||||
if ($failed_login_count >= 15) {
|
if ($failed_login_count >= 15) {
|
||||||
|
|
@ -53,253 +72,402 @@ if ($failed_login_count >= 15) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query Settings for company
|
// Query Settings for company
|
||||||
$sql_settings = mysqli_query($mysqli, "SELECT * FROM settings LEFT JOIN companies ON settings.company_id = companies.company_id WHERE settings.company_id = 1");
|
$sql_settings = mysqli_query($mysqli, "
|
||||||
|
SELECT settings.*, companies.company_name, companies.company_logo
|
||||||
|
FROM settings
|
||||||
|
LEFT JOIN companies ON settings.company_id = companies.company_id
|
||||||
|
WHERE settings.company_id = 1
|
||||||
|
");
|
||||||
$row = mysqli_fetch_array($sql_settings);
|
$row = mysqli_fetch_array($sql_settings);
|
||||||
|
|
||||||
// Company info
|
// Company info
|
||||||
$company_name = $row['company_name'];
|
$company_name = $row['company_name'];
|
||||||
$company_logo = $row['company_logo'];
|
$company_logo = $row['company_logo'];
|
||||||
$config_start_page = nullable_htmlentities($row['config_start_page']);
|
$config_start_page = nullable_htmlentities($row['config_start_page']);
|
||||||
$config_login_message = nullable_htmlentities($row['config_login_message']);
|
$config_login_message = nullable_htmlentities($row['config_login_message']);
|
||||||
|
|
||||||
// Mail
|
// Mail
|
||||||
$config_smtp_host = $row['config_smtp_host'];
|
$config_smtp_host = $row['config_smtp_host'];
|
||||||
$config_smtp_port = intval($row['config_smtp_port']);
|
$config_smtp_port = intval($row['config_smtp_port']);
|
||||||
$config_smtp_encryption = $row['config_smtp_encryption'];
|
$config_smtp_encryption = $row['config_smtp_encryption'];
|
||||||
$config_smtp_username = $row['config_smtp_username'];
|
$config_smtp_username = $row['config_smtp_username'];
|
||||||
$config_smtp_password = $row['config_smtp_password'];
|
$config_smtp_password = $row['config_smtp_password'];
|
||||||
$config_mail_from_email = sanitizeInput($row['config_mail_from_email']);
|
$config_mail_from_email = sanitizeInput($row['config_mail_from_email']);
|
||||||
$config_mail_from_name = sanitizeInput($row['config_mail_from_name']);
|
$config_mail_from_name = sanitizeInput($row['config_mail_from_name']);
|
||||||
|
|
||||||
// Client Portal Enabled
|
// Client Portal Enabled
|
||||||
$config_client_portal_enable = intval($row['config_client_portal_enable']);
|
$config_client_portal_enable = intval($row['config_client_portal_enable']);
|
||||||
|
$config_login_remember_me_expire = intval($row['config_login_remember_me_expire']);
|
||||||
|
|
||||||
// Login key (if setup)
|
// Login key (if setup)
|
||||||
$config_login_key_required = $row['config_login_key_required'];
|
$config_login_key_required = $row['config_login_key_required'];
|
||||||
$config_login_key_secret = $row['config_login_key_secret'];
|
$config_login_key_secret = $row['config_login_key_secret'];
|
||||||
|
|
||||||
$config_login_remember_me_expire = intval($row['config_login_remember_me_expire']);
|
// Azure / Entra for client
|
||||||
|
$azure_client_id = $row['config_azure_client_id'] ?? null;
|
||||||
|
|
||||||
// Login key verification
|
$response = null;
|
||||||
// If no/incorrect 'key' is supplied, send to client portal instead
|
$token_field = null;
|
||||||
if ($config_login_key_required) {
|
$show_role_choice = false;
|
||||||
if (!isset($_GET['key']) || $_GET['key'] !== $config_login_key_secret) {
|
$email = '';
|
||||||
redirect("client");
|
$password = '';
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTTP-Only cookies
|
// Handle POST login request (normal login or role choice)
|
||||||
ini_set("session.cookie_httponly", true);
|
if ($_SERVER['REQUEST_METHOD'] === 'POST' && (isset($_POST['login']) || isset($_POST['role_choice']))) {
|
||||||
|
|
||||||
// Tell client to only send cookie(s) over HTTPS
|
$email = sanitizeInput($_POST['email'] ?? '');
|
||||||
if ($config_https_only || !isset($config_https_only)) {
|
$password = $_POST['password'] ?? '';
|
||||||
ini_set("session.cookie_secure", true);
|
$role_choice = $_POST['role_choice'] ?? null; // 'agent' or 'client'
|
||||||
}
|
|
||||||
|
|
||||||
// Handle POST login request
|
|
||||||
if (isset($_POST['login'])) {
|
|
||||||
|
|
||||||
// Sessions should start after the user has POSTed data
|
|
||||||
session_start();
|
|
||||||
|
|
||||||
// Passed login brute force check
|
|
||||||
$email = sanitizeInput($_POST['email']);
|
|
||||||
$password = $_POST['password'];
|
|
||||||
|
|
||||||
$current_code = 0; // Default value
|
|
||||||
if (isset($_POST['current_code'])) {
|
|
||||||
$current_code = intval($_POST['current_code']);
|
|
||||||
}
|
|
||||||
|
|
||||||
$row = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT * FROM users LEFT JOIN user_settings on users.user_id = user_settings.user_id WHERE user_email = '$email' AND user_archived_at IS NULL AND user_status = 1 AND user_type = 1"));
|
|
||||||
|
|
||||||
// Check password
|
|
||||||
if ($row && password_verify($password, $row['user_password'])) {
|
|
||||||
|
|
||||||
// User password correct (partial login)
|
|
||||||
|
|
||||||
// Set temporary user variables
|
|
||||||
$user_name = sanitizeInput($row['user_name']);
|
|
||||||
$user_id = intval($row['user_id']);
|
|
||||||
$session_user_id = $user_id; // to pass the user_id to logAction function
|
|
||||||
$user_email = sanitizeInput($row['user_email']);
|
|
||||||
$token = sanitizeInput($row['user_token']);
|
|
||||||
$force_mfa = intval($row['user_config_force_mfa']);
|
|
||||||
$user_role_id = intval($row['user_role_id']);
|
|
||||||
$user_encryption_ciphertext = $row['user_specific_encryption_ciphertext'];
|
|
||||||
$user_extension_key = $row['user_extension_key'];
|
|
||||||
|
|
||||||
$mfa_is_complete = false; // Default to requiring MFA
|
|
||||||
$extended_log = ''; // Default value
|
|
||||||
|
|
||||||
if (empty($token)) {
|
|
||||||
// MFA is not configured
|
|
||||||
$mfa_is_complete = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate MFA via a remember-me cookie
|
|
||||||
if (isset($_COOKIE['rememberme'])) {
|
|
||||||
// Get remember tokens less than $config_login_remember_me_days_expire days old
|
|
||||||
$remember_tokens = mysqli_query($mysqli, "SELECT remember_token_token FROM remember_tokens WHERE remember_token_user_id = $user_id AND remember_token_created_at > (NOW() - INTERVAL $config_login_remember_me_expire DAY)");
|
|
||||||
while ($row = mysqli_fetch_assoc($remember_tokens)) {
|
|
||||||
if (hash_equals($row['remember_token_token'], $_COOKIE['rememberme'])) {
|
|
||||||
$mfa_is_complete = true;
|
|
||||||
$extended_log = 'with 2FA remember-me cookie';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate MFA code
|
|
||||||
if (!empty($current_code) && TokenAuth6238::verify($token, $current_code)) {
|
|
||||||
$mfa_is_complete = true;
|
|
||||||
$extended_log = 'with MFA';
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($mfa_is_complete) {
|
|
||||||
// MFA Completed successfully
|
|
||||||
|
|
||||||
// FULL LOGIN SUCCESS
|
|
||||||
|
|
||||||
// Create a remember me token, if requested
|
|
||||||
if (isset($_POST['remember_me'])) {
|
|
||||||
// TODO: Record the UA and IP a token is generated from so that can be shown later on
|
|
||||||
$newRememberToken = bin2hex(random_bytes(64));
|
|
||||||
setcookie('rememberme', $newRememberToken, time() + 86400*$config_login_remember_me_expire, "/", null, true, true);
|
|
||||||
mysqli_query($mysqli, "INSERT INTO remember_tokens SET remember_token_user_id = $user_id, remember_token_token = '$newRememberToken'");
|
|
||||||
|
|
||||||
$extended_log .= ", generated a new remember-me token";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check this login isn't suspicious
|
|
||||||
$sql_ip_prev_logins = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT COUNT(log_id) AS ip_previous_logins FROM logs WHERE log_type = 'Login' AND log_action = 'Success' AND log_ip = '$session_ip' AND log_user_id = $user_id"));
|
|
||||||
$ip_previous_logins = sanitizeInput($sql_ip_prev_logins['ip_previous_logins']);
|
|
||||||
|
|
||||||
$sql_ua_prev_logins = mysqli_fetch_assoc(mysqli_query($mysqli, "SELECT COUNT(log_id) AS ua_previous_logins FROM logs WHERE log_type = 'Login' AND log_action = 'Success' AND log_user_agent = '$session_user_agent' AND log_user_id = $user_id"));
|
|
||||||
$ua_prev_logins = sanitizeInput($sql_ua_prev_logins['ua_previous_logins']);
|
|
||||||
|
|
||||||
// Notify if both the user agent and IP are different
|
|
||||||
if (!empty($config_smtp_host) && $ip_previous_logins == 0 && $ua_prev_logins == 0) {
|
|
||||||
$subject = "$config_app_name new login for $user_name";
|
|
||||||
$body = "Hi $user_name, <br><br>A recent successful login to your $config_app_name account was considered a little unusual. If this was you, you can safely ignore this email!<br><br>IP Address: $session_ip<br> User Agent: $session_user_agent <br><br>If you did not perform this login, your credentials may be compromised. <br><br>Thanks, <br>ITFlow";
|
|
||||||
|
|
||||||
$data = [
|
|
||||||
[
|
|
||||||
'from' => $config_mail_from_email,
|
|
||||||
'from_name' => $config_mail_from_name,
|
|
||||||
'recipient' => $user_email,
|
|
||||||
'recipient_name' => $user_name,
|
|
||||||
'subject' => $subject,
|
|
||||||
'body' => $body
|
|
||||||
]
|
|
||||||
];
|
|
||||||
addToMailQueue($data);
|
|
||||||
}
|
|
||||||
|
|
||||||
logAction("Login", "Success", "$user_name successfully logged in $extended_log", 0, $user_id);
|
|
||||||
|
|
||||||
// Session info
|
|
||||||
$_SESSION['user_id'] = $user_id;
|
|
||||||
$_SESSION['csrf_token'] = randomString(156);
|
|
||||||
$_SESSION['logged'] = true;
|
|
||||||
|
|
||||||
// Forcing MFA
|
|
||||||
if ($force_mfa == 1 && $token == NULL) {
|
|
||||||
$config_start_page = "user/mfa_enforcement.php";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup encryption session key
|
|
||||||
if (isset($user_encryption_ciphertext)) {
|
|
||||||
$site_encryption_master_key = decryptUserSpecificKey($user_encryption_ciphertext, $password);
|
|
||||||
generateUserSessionKey($site_encryption_master_key);
|
|
||||||
|
|
||||||
// Setup extension - currently unused
|
|
||||||
//if (is_null($user_extension_key)) {
|
|
||||||
// Extension cookie
|
|
||||||
// Note: Browsers don't accept cookies with SameSite None if they are not HTTPS.
|
|
||||||
//setcookie("user_extension_key", "$user_extension_key", ['path' => '/', 'secure' => true, 'httponly' => true, 'samesite' => 'None']);
|
|
||||||
|
|
||||||
// Set PHP session in DB, so we can access the session encryption data (above)
|
|
||||||
//$user_php_session = session_id();
|
|
||||||
//mysqli_query($mysqli, "UPDATE users SET user_php_session = '$user_php_session' WHERE user_id = $user_id");
|
|
||||||
//}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Redirect to last visited or config home
|
|
||||||
|
|
||||||
if (isset($_GET['last_visited']) && (str_starts_with(base64_decode($_GET['last_visited']), '/agent') || str_starts_with(base64_decode($_GET['last_visited']), '/admin'))) {
|
|
||||||
|
|
||||||
redirect($_SERVER["REQUEST_SCHEME"] . "://" . $config_base_url . base64_decode($_GET['last_visited']) );
|
|
||||||
|
|
||||||
} else {
|
|
||||||
redirect("agent/$config_start_page");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// MFA is configured and needs to be confirmed, or was unsuccessful
|
|
||||||
|
|
||||||
// HTML code for the token input field
|
|
||||||
$token_field = "
|
|
||||||
<div class='input-group mb-3'>
|
|
||||||
<input type='text' inputmode='numeric' pattern='[0-9]*' maxlength='6' class='form-control' placeholder='Enter your 2FA code' name='current_code' required autofocus>
|
|
||||||
<div class='input-group-append'>
|
|
||||||
<div class='input-group-text'>
|
|
||||||
<span class='fas fa-key'></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>";
|
|
||||||
|
|
||||||
// Log/notify if MFA was unsuccessful
|
|
||||||
if ($current_code !== 0) {
|
|
||||||
|
|
||||||
// Logging
|
|
||||||
logAction("Login", "MFA Failed", "$user_name failed MFA", 0, $user_id);
|
|
||||||
|
|
||||||
// Email the tech to advise their credentials may be compromised
|
|
||||||
if (!empty($config_smtp_host)) {
|
|
||||||
$subject = "Important: $config_app_name failed 2FA login attempt for $user_name";
|
|
||||||
$body = "Hi $user_name, <br><br>A recent login to your $config_app_name account was unsuccessful due to an incorrect 2FA code. If you did not attempt this login, your credentials may be compromised. <br><br>Thanks, <br>ITFlow";
|
|
||||||
$data = [
|
|
||||||
[
|
|
||||||
'from' => $config_mail_from_email,
|
|
||||||
'from_name' => $config_mail_from_name,
|
|
||||||
'recipient' => $user_email,
|
|
||||||
'recipient_name' => $user_name,
|
|
||||||
'subject' => $subject,
|
|
||||||
'body' => $body
|
|
||||||
]
|
|
||||||
];
|
|
||||||
$mail = addToMailQueue($data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// HTML feedback for incorrect 2FA code
|
|
||||||
$response = "
|
|
||||||
<div class='alert alert-warning'>
|
|
||||||
Please Enter 2FA Code!
|
|
||||||
<button class='close' data-dismiss='alert'>×</button>
|
|
||||||
</div>";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Basic validation
|
||||||
|
if (empty($email) || empty($password) || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||||
|
header("HTTP/1.1 401 Unauthorized");
|
||||||
|
$response = "
|
||||||
|
<div class='alert alert-danger'>
|
||||||
|
Incorrect username or password.
|
||||||
|
<button class='close' data-dismiss='alert'>×</button>
|
||||||
|
</div>";
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// Password incorrect or user doesn't exist - show generic error
|
/*
|
||||||
|
* Unified lookup:
|
||||||
|
* - user_type = 1 → Agent
|
||||||
|
* - user_type = 2 → Client (must not be archived, client not archived)
|
||||||
|
* We fetch all possible matches for this email, then verify password per row.
|
||||||
|
* If both an agent and a client match with the same password:
|
||||||
|
* - First, show choice buttons (Agent / Client).
|
||||||
|
* - When user clicks a choice, we honor role_choice.
|
||||||
|
*/
|
||||||
|
$sql = mysqli_query($mysqli, "
|
||||||
|
SELECT users.*,
|
||||||
|
user_settings.*,
|
||||||
|
contacts.*,
|
||||||
|
clients.*
|
||||||
|
FROM users
|
||||||
|
LEFT JOIN user_settings ON users.user_id = user_settings.user_id
|
||||||
|
LEFT JOIN contacts ON users.user_id = contacts.contact_user_id
|
||||||
|
LEFT JOIN clients ON contacts.contact_client_id = clients.client_id
|
||||||
|
WHERE user_email = '$email'
|
||||||
|
AND user_archived_at IS NULL
|
||||||
|
AND user_status = 1
|
||||||
|
AND (
|
||||||
|
user_type = 1
|
||||||
|
OR (user_type = 2 AND client_archived_at IS NULL)
|
||||||
|
)
|
||||||
|
");
|
||||||
|
|
||||||
header("HTTP/1.1 401 Unauthorized");
|
$agentRow = null;
|
||||||
|
$clientRow = null;
|
||||||
|
|
||||||
logAction("Login", "Failed", "Failed login attempt using $email");
|
while ($r = mysqli_fetch_assoc($sql)) {
|
||||||
|
if (!password_verify($password, $r['user_password'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (intval($r['user_type']) === 1 && $agentRow === null) {
|
||||||
|
$agentRow = $r;
|
||||||
|
}
|
||||||
|
if (intval($r['user_type']) === 2 && $clientRow === null) {
|
||||||
|
$clientRow = $r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$response = "
|
$selectedRow = null;
|
||||||
|
$selectedType = null; // 1 = agent, 2 = client
|
||||||
|
|
||||||
|
if ($agentRow === null && $clientRow === null) {
|
||||||
|
|
||||||
|
// No matching user/password combo
|
||||||
|
header("HTTP/1.1 401 Unauthorized");
|
||||||
|
logAction("Login", "Failed", "Failed login attempt using $email");
|
||||||
|
|
||||||
|
$response = "
|
||||||
<div class='alert alert-danger'>
|
<div class='alert alert-danger'>
|
||||||
Incorrect username or password.
|
Incorrect username or password.
|
||||||
<button class='close' data-dismiss='alert'>×</button>
|
<button class='close' data-dismiss='alert'>×</button>
|
||||||
</div>";
|
</div>";
|
||||||
|
|
||||||
|
} elseif ($agentRow !== null && $clientRow !== null) {
|
||||||
|
|
||||||
|
// Both agent and client accounts share same email + password
|
||||||
|
if ($role_choice === 'agent') {
|
||||||
|
$selectedRow = $agentRow;
|
||||||
|
$selectedType = 1;
|
||||||
|
} elseif ($role_choice === 'client') {
|
||||||
|
$selectedRow = $clientRow;
|
||||||
|
$selectedType = 2;
|
||||||
|
} else {
|
||||||
|
// First time we realise this is a dual-role account: ask user to pick
|
||||||
|
$show_role_choice = true;
|
||||||
|
$response = "
|
||||||
|
<div class='alert alert-info'>
|
||||||
|
This login can be used as either an Agent account or a Client Portal account.
|
||||||
|
Please choose how you want to continue.
|
||||||
|
<button class='close' data-dismiss='alert'>×</button>
|
||||||
|
</div>";
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Only one valid row (agent OR client)
|
||||||
|
if ($agentRow !== null) {
|
||||||
|
$selectedRow = $agentRow;
|
||||||
|
$selectedType = 1;
|
||||||
|
} else {
|
||||||
|
$selectedRow = $clientRow;
|
||||||
|
$selectedType = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have a specific user selected, proceed with actual login
|
||||||
|
if ($selectedRow !== null && $selectedType !== null) {
|
||||||
|
|
||||||
|
$user_id = intval($selectedRow['user_id']);
|
||||||
|
$user_email = sanitizeInput($selectedRow['user_email']);
|
||||||
|
$session_user_id = $user_id; // to pass the user_id to logAction function
|
||||||
|
|
||||||
|
// =========================
|
||||||
|
// AGENT LOGIN FLOW
|
||||||
|
// =========================
|
||||||
|
if ($selectedType === 1) {
|
||||||
|
// Login key verification
|
||||||
|
// If no/incorrect 'key' is supplied, send to client portal instead
|
||||||
|
if ($config_login_key_required) {
|
||||||
|
if (!isset($_GET['key']) || $_GET['key'] !== $config_login_key_secret) {
|
||||||
|
redirect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$user_name = sanitizeInput($selectedRow['user_name']);
|
||||||
|
$token = sanitizeInput($selectedRow['user_token']);
|
||||||
|
$force_mfa = intval($selectedRow['user_config_force_mfa']);
|
||||||
|
$user_role_id = intval($selectedRow['user_role_id']);
|
||||||
|
$user_encryption_ciphertext = $selectedRow['user_specific_encryption_ciphertext'];
|
||||||
|
$user_extension_key = $selectedRow['user_extension_key'];
|
||||||
|
|
||||||
|
$current_code = 0;
|
||||||
|
if (isset($_POST['current_code'])) {
|
||||||
|
$current_code = intval($_POST['current_code']);
|
||||||
|
}
|
||||||
|
|
||||||
|
$mfa_is_complete = false;
|
||||||
|
$extended_log = '';
|
||||||
|
|
||||||
|
if (empty($token)) {
|
||||||
|
// MFA is not configured
|
||||||
|
$mfa_is_complete = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate MFA via a remember-me cookie
|
||||||
|
if (isset($_COOKIE['rememberme'])) {
|
||||||
|
$remember_tokens = mysqli_query($mysqli, "
|
||||||
|
SELECT remember_token_token
|
||||||
|
FROM remember_tokens
|
||||||
|
WHERE remember_token_user_id = $user_id
|
||||||
|
AND remember_token_created_at > (NOW() - INTERVAL $config_login_remember_me_expire DAY)
|
||||||
|
");
|
||||||
|
while ($remember_row = mysqli_fetch_assoc($remember_tokens)) {
|
||||||
|
if (hash_equals($remember_row['remember_token_token'], $_COOKIE['rememberme'])) {
|
||||||
|
$mfa_is_complete = true;
|
||||||
|
$extended_log = 'with 2FA remember-me cookie';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate MFA code
|
||||||
|
if (!empty($current_code) && TokenAuth6238::verify($token, $current_code)) {
|
||||||
|
$mfa_is_complete = true;
|
||||||
|
$extended_log = 'with MFA';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($mfa_is_complete) {
|
||||||
|
// FULL AGENT LOGIN SUCCESS
|
||||||
|
|
||||||
|
// Create a remember me token, if requested
|
||||||
|
if (isset($_POST['remember_me'])) {
|
||||||
|
$newRememberToken = bin2hex(random_bytes(64));
|
||||||
|
setcookie(
|
||||||
|
'rememberme',
|
||||||
|
$newRememberToken,
|
||||||
|
time() + 86400 * $config_login_remember_me_expire,
|
||||||
|
"/",
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
mysqli_query($mysqli, "
|
||||||
|
INSERT INTO remember_tokens
|
||||||
|
SET remember_token_user_id = $user_id,
|
||||||
|
remember_token_token = '$newRememberToken'
|
||||||
|
");
|
||||||
|
|
||||||
|
$extended_log .= ", generated a new remember-me token";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check this login isn't suspicious
|
||||||
|
$sql_ip_prev_logins = mysqli_fetch_assoc(mysqli_query($mysqli, "
|
||||||
|
SELECT COUNT(log_id) AS ip_previous_logins
|
||||||
|
FROM logs
|
||||||
|
WHERE log_type = 'Login'
|
||||||
|
AND log_action = 'Success'
|
||||||
|
AND log_ip = '$session_ip'
|
||||||
|
AND log_user_id = $user_id
|
||||||
|
"));
|
||||||
|
$ip_previous_logins = sanitizeInput($sql_ip_prev_logins['ip_previous_logins']);
|
||||||
|
|
||||||
|
$sql_ua_prev_logins = mysqli_fetch_assoc(mysqli_query($mysqli, "
|
||||||
|
SELECT COUNT(log_id) AS ua_previous_logins
|
||||||
|
FROM logs
|
||||||
|
WHERE log_type = 'Login'
|
||||||
|
AND log_action = 'Success'
|
||||||
|
AND log_user_agent = '$session_user_agent'
|
||||||
|
AND log_user_id = $user_id
|
||||||
|
"));
|
||||||
|
$ua_prev_logins = sanitizeInput($sql_ua_prev_logins['ua_previous_logins']);
|
||||||
|
|
||||||
|
// Notify if both the user agent and IP are different
|
||||||
|
if (!empty($config_smtp_host) && $ip_previous_logins == 0 && $ua_prev_logins == 0) {
|
||||||
|
$subject = "$config_app_name new login for $user_name";
|
||||||
|
$body = "Hi $user_name, <br><br>A recent successful login to your $config_app_name account was considered a little unusual. If this was you, you can safely ignore this email!<br><br>IP Address: $session_ip<br> User Agent: $session_user_agent <br><br>If you did not perform this login, your credentials may be compromised. <br><br>Thanks, <br>ITFlow";
|
||||||
|
|
||||||
|
$data = [
|
||||||
|
[
|
||||||
|
'from' => $config_mail_from_email,
|
||||||
|
'from_name' => $config_mail_from_name,
|
||||||
|
'recipient' => $user_email,
|
||||||
|
'recipient_name' => $user_name,
|
||||||
|
'subject' => $subject,
|
||||||
|
'body' => $body
|
||||||
|
]
|
||||||
|
];
|
||||||
|
addToMailQueue($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
logAction("Login", "Success", "$user_name successfully logged in $extended_log", 0, $user_id);
|
||||||
|
|
||||||
|
// Session info
|
||||||
|
$_SESSION['user_id'] = $user_id;
|
||||||
|
$_SESSION['csrf_token'] = randomString(156);
|
||||||
|
$_SESSION['logged'] = true;
|
||||||
|
|
||||||
|
// Forcing MFA
|
||||||
|
if ($force_mfa == 1 && $token == NULL) {
|
||||||
|
$config_start_page = "user/mfa_enforcement.php";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Setup encryption session key
|
||||||
|
if (!empty($user_encryption_ciphertext)) {
|
||||||
|
$site_encryption_master_key = decryptUserSpecificKey($user_encryption_ciphertext, $password);
|
||||||
|
generateUserSessionKey($site_encryption_master_key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect to last visited or config home
|
||||||
|
if (isset($_GET['last_visited']) && (str_starts_with(base64_decode($_GET['last_visited']), '/agent') || str_starts_with(base64_decode($_GET['last_visited']), '/admin'))) {
|
||||||
|
|
||||||
|
redirect($_SERVER["REQUEST_SCHEME"] . "://" . $config_base_url . base64_decode($_GET['last_visited']));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
redirect("agent/$config_start_page");
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// MFA is configured and needs to be confirmed, or was unsuccessful
|
||||||
|
|
||||||
|
// HTML code for the token input field
|
||||||
|
$token_field = "
|
||||||
|
<div class='input-group mb-3'>
|
||||||
|
<input type='text' inputmode='numeric' pattern='[0-9]*' maxlength='6'
|
||||||
|
class='form-control' placeholder='Enter your 2FA code'
|
||||||
|
name='current_code' required autofocus>
|
||||||
|
<div class='input-group-append'>
|
||||||
|
<div class='input-group-text'>
|
||||||
|
<span class='fas fa-key'></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>";
|
||||||
|
|
||||||
|
if ($current_code !== 0) {
|
||||||
|
// Logging
|
||||||
|
logAction("Login", "MFA Failed", "$user_email failed MFA", 0, $user_id);
|
||||||
|
|
||||||
|
// Email the tech to advise their credentials may be compromised
|
||||||
|
if (!empty($config_smtp_host)) {
|
||||||
|
$subject = "Important: $config_app_name failed 2FA login attempt for $user_name";
|
||||||
|
$body = "Hi $user_name, <br><br>A recent login to your $config_app_name account was unsuccessful due to an incorrect 2FA code. If you did not attempt this login, your credentials may be compromised. <br><br>Thanks, <br>ITFlow";
|
||||||
|
$data = [
|
||||||
|
[
|
||||||
|
'from' => $config_mail_from_email,
|
||||||
|
'from_name' => $config_mail_from_name,
|
||||||
|
'recipient' => $user_email,
|
||||||
|
'recipient_name' => $user_name,
|
||||||
|
'subject' => $subject,
|
||||||
|
'body' => $body
|
||||||
|
]
|
||||||
|
];
|
||||||
|
addToMailQueue($data);
|
||||||
|
}
|
||||||
|
|
||||||
|
$response = "
|
||||||
|
<div class='alert alert-warning'>
|
||||||
|
Please Enter 2FA Code!
|
||||||
|
<button class='close' data-dismiss='alert'>×</button>
|
||||||
|
</div>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================
|
||||||
|
// CLIENT LOGIN FLOW
|
||||||
|
// =========================
|
||||||
|
} elseif ($selectedType === 2) {
|
||||||
|
|
||||||
|
if ($config_client_portal_enable != 1) {
|
||||||
|
// Client portal disabled
|
||||||
|
header("HTTP/1.1 401 Unauthorized");
|
||||||
|
logAction("Client Login", "Failed", "Client portal disabled; login attempt using $email");
|
||||||
|
$response = "
|
||||||
|
<div class='alert alert-danger'>
|
||||||
|
Incorrect username or password.
|
||||||
|
<button class='close' data-dismiss='alert'>×</button>
|
||||||
|
</div>";
|
||||||
|
} else {
|
||||||
|
|
||||||
|
$client_id = intval($selectedRow['contact_client_id']);
|
||||||
|
$contact_id = intval($selectedRow['contact_id']);
|
||||||
|
$user_auth_method = sanitizeInput($selectedRow['user_auth_method']);
|
||||||
|
|
||||||
|
if ($client_id && $contact_id && $user_auth_method === 'local') {
|
||||||
|
|
||||||
|
$_SESSION['client_logged_in'] = true;
|
||||||
|
$_SESSION['client_id'] = $client_id;
|
||||||
|
$_SESSION['user_id'] = $user_id;
|
||||||
|
$_SESSION['user_type'] = 2;
|
||||||
|
$_SESSION['contact_id'] = $contact_id;
|
||||||
|
$_SESSION['login_method'] = "local";
|
||||||
|
|
||||||
|
logAction("Client Login", "Success", "Client contact $user_email successfully logged in locally", $client_id, $user_id);
|
||||||
|
|
||||||
|
header("Location: client/index.php");
|
||||||
|
exit();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Not allowed or invalid
|
||||||
|
logAction("Client Login", "Failed", "Failed client portal login attempt using $email (invalid auth method or missing contact/client)", $client_id ?? 0, $user_id);
|
||||||
|
|
||||||
|
header("HTTP/1.1 401 Unauthorized");
|
||||||
|
$response = "
|
||||||
|
<div class='alert alert-danger'>
|
||||||
|
Incorrect username or password.
|
||||||
|
<button class='close' data-dismiss='alert'>×</button>
|
||||||
|
</div>";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
|
|
@ -313,10 +481,7 @@ if (isset($_POST['login'])) {
|
||||||
<!-- Font Awesome -->
|
<!-- Font Awesome -->
|
||||||
<link rel="stylesheet" href="plugins/fontawesome-free/css/all.min.css">
|
<link rel="stylesheet" href="plugins/fontawesome-free/css/all.min.css">
|
||||||
|
|
||||||
<!--
|
<!-- Favicon -->
|
||||||
Favicon
|
|
||||||
If Fav Icon exists else use the default one
|
|
||||||
-->
|
|
||||||
<?php if(file_exists('uploads/favicon.ico')) { ?>
|
<?php if(file_exists('uploads/favicon.ico')) { ?>
|
||||||
<link rel="icon" type="image/x-icon" href="/uploads/favicon.ico">
|
<link rel="icon" type="image/x-icon" href="/uploads/favicon.ico">
|
||||||
<?php } ?>
|
<?php } ?>
|
||||||
|
|
@ -336,7 +501,6 @@ if (isset($_POST['login'])) {
|
||||||
<?php } ?>
|
<?php } ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- /.login-logo -->
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body login-card-body">
|
<div class="card-body login-card-body">
|
||||||
|
|
||||||
|
|
@ -350,16 +514,24 @@ if (isset($_POST['login'])) {
|
||||||
|
|
||||||
<form method="post">
|
<form method="post">
|
||||||
|
|
||||||
<div class="input-group mb-3" <?php if (isset($token_field)) { echo "hidden"; } ?>>
|
<div class="input-group mb-3" <?php if (isset($token_field) && $token_field) { echo "hidden"; } ?>>
|
||||||
<input type="text" class="form-control" placeholder="Agent Email" name="email" value="<?php if (isset($token_field)) { echo $email; }?>" required <?php if (!isset($token_field)) { echo "autofocus"; } ?> >
|
<input type="text" class="form-control"
|
||||||
|
placeholder="<?php if ($config_login_key_required) { if (!isset($_GET['key']) || $_GET['key'] !== $config_login_key_secret) { echo "Client "; } } echo "Email"; ?>"
|
||||||
|
name="email"
|
||||||
|
value="<?php echo htmlspecialchars($email ?? '', ENT_QUOTES); ?>"
|
||||||
|
required <?php if (!isset($token_field) || !$token_field) { echo "autofocus"; } ?>
|
||||||
|
>
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
<div class="input-group-text">
|
<div class="input-group-text">
|
||||||
<span class="fas fa-envelope"></span>
|
<span class="fas fa-envelope"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-group mb-3" <?php if (isset($token_field)) { echo "hidden"; } ?>>
|
|
||||||
<input type="password" class="form-control" placeholder="Agent Password" name="password" value="<?php if (isset($token_field)) { echo $password; } ?>" required>
|
<div class="input-group mb-3" <?php if (isset($token_field) && $token_field) { echo "hidden"; } ?>>
|
||||||
|
<input type="password" class="form-control" placeholder="Password" name="password"
|
||||||
|
value="<?php echo isset($token_field) && $token_field ? htmlspecialchars($password ?? '', ENT_QUOTES) : ''; ?>"
|
||||||
|
required>
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
<div class="input-group-text">
|
<div class="input-group-text">
|
||||||
<span class="fas fa-lock"></span>
|
<span class="fas fa-lock"></span>
|
||||||
|
|
@ -368,38 +540,58 @@ if (isset($_POST['login'])) {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php
|
<?php
|
||||||
if (isset($token_field)) {
|
// If agent needs MFA, show 2FA field + remember me
|
||||||
|
if (isset($token_field) && $token_field) {
|
||||||
|
|
||||||
echo $token_field;
|
echo $token_field;
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="form-group mb-3">
|
<div class="form-group mb-3">
|
||||||
<div class="custom-control custom-checkbox">
|
<div class="custom-control custom-checkbox">
|
||||||
<input type="checkbox" class="custom-control-input" id="remember_me" name="remember_me">
|
<input type="checkbox" class="custom-control-input" id="remember_me" name="remember_me">
|
||||||
<label class="custom-control-label" for="remember_me">Remember Me</label>
|
<label class="custom-control-label" for="remember_me">Remember Me</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
?>
|
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary btn-block mb-3" name="login">Sign In</button>
|
|
||||||
|
|
||||||
<?php if($config_client_portal_enable == 1){ ?>
|
|
||||||
<hr>
|
|
||||||
<h5 class="text-center">Looking for the <a href="client">Client Portal?<a/></h5>
|
|
||||||
<?php } ?>
|
<?php } ?>
|
||||||
|
|
||||||
|
<?php if ($show_role_choice): ?>
|
||||||
|
<!-- When both agent & client accounts exist with same email/password -->
|
||||||
|
<div class="mb-2 text-center">
|
||||||
|
<button type="submit" class="btn btn-primary btn-block mb-2" name="role_choice" value="agent">
|
||||||
|
Log in as Agent
|
||||||
|
</button>
|
||||||
|
<button type="submit" class="btn btn-success btn-block" name="role_choice" value="client">
|
||||||
|
Log in as Client
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<?php else: ?>
|
||||||
|
<button type="submit" class="btn btn-primary btn-block mb-3" name="login">Sign In</button>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<?php if($config_client_portal_enable == 1){ ?>
|
||||||
|
<hr>
|
||||||
|
<?php if (!empty($config_smtp_host)) { ?>
|
||||||
|
<a href="client/login_reset.php">Forgot password?</a>
|
||||||
|
<?php } ?>
|
||||||
|
<?php if (!empty($azure_client_id)) { ?>
|
||||||
|
<div class="col text-center mt-2">
|
||||||
|
<a href="client/login_microsoft.php">
|
||||||
|
<button type="button" class="btn btn-secondary">Login with Microsoft Entra</button>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<?php } ?>
|
||||||
|
<?php } ?>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<!-- /.login-card-body -->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- /.login-box -->
|
|
||||||
|
<?php
|
||||||
|
if (!$config_whitelabel_enabled) {
|
||||||
|
echo '<small class="text-muted">Powered by ITFlow</small>';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
<!-- jQuery -->
|
<!-- jQuery -->
|
||||||
<script src="plugins/jquery/jquery.min.js"></script>
|
<script src="plugins/jquery/jquery.min.js"></script>
|
||||||
|
|
@ -410,8 +602,6 @@ if (isset($_POST['login'])) {
|
||||||
<!-- AdminLTE App -->
|
<!-- AdminLTE App -->
|
||||||
<script src="plugins/adminlte/js/adminlte.min.js"></script>
|
<script src="plugins/adminlte/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 -->
|
<!-- Prevents resubmit on refresh or back -->
|
||||||
<script src="js/login_prevent_resubmit.js"></script>
|
<script src="js/login_prevent_resubmit.js"></script>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue