Add option "Maximum Login Attempts" for maintenance login
This commit is contained in:
@@ -19,23 +19,15 @@
|
||||
* @license https://store.webkul.com/license.html
|
||||
*/
|
||||
|
||||
class MaintenanceAccessCore extends ObjectModel
|
||||
class MaintenanceAccessCore extends ObjectModel
|
||||
{
|
||||
|
||||
public $id_maintenance_access;
|
||||
public $ip_address;
|
||||
public $email;
|
||||
public $date_add;
|
||||
const USERNAME_ATTEMPTS_PER_QUARTER_HOUR = 3;
|
||||
const IP_ATTEMPTS_QUARTER_HOUR = 12;
|
||||
const USERNAME_ATTEMPTS_PER_HOUR = 6;
|
||||
const IP_ATTEMPTS_PER_HOUR = 24;
|
||||
const QUARTER_HOUR = 60*15;
|
||||
const HOUR = 60*60;
|
||||
|
||||
/**
|
||||
* @see ObjectModel::$definition
|
||||
*/
|
||||
const LOGIN_ATTEMPTS_WINDOW = 30; /* in minutes */
|
||||
|
||||
public static $definition = array(
|
||||
'table' => 'maintenance_access',
|
||||
'primary' => 'id_maintenance_access',
|
||||
@@ -46,51 +38,32 @@ class MaintenanceAccessCore extends ObjectModel
|
||||
),
|
||||
);
|
||||
|
||||
public function cleanData()
|
||||
public function removeFailedAttempts($email, $ipAddress)
|
||||
{
|
||||
$time = (time() - (self::HOUR*2));
|
||||
Db::getInstance()->execute(
|
||||
'DELETE FROM `' . _DB_PREFIX_ .'maintenance_access`
|
||||
WHERE `date_add` < "' . pSQL(date("Y-m-d H:i:s", $time)) . '"'
|
||||
'DELETE FROM `' . _DB_PREFIX_ .'maintenance_access` ma
|
||||
WHERE (ma.`email` = "'.$email.'" OR ma.`ip_address` = "'.$ipAddress.'")
|
||||
AND ma.`date_add` > "'.date('Y-m-d H:i:s', strtotime('-'.MaintenanceAccess::LOGIN_ATTEMPTS_WINDOW.' minutes')).'"'
|
||||
);
|
||||
}
|
||||
|
||||
public function getUserFailedCount($time, $email=false, $ip_address=false, $attempts)
|
||||
public function getAttemptsCount($email, $ipAddress)
|
||||
{
|
||||
$sql = "SELECT COUNT(`id_maintenance_access`) FROM `". _DB_PREFIX_ ."maintenance_access` WHERE `date_add` > '"
|
||||
. pSQL(date('Y-m-d H:i:s', $time)) ."'";
|
||||
|
||||
if ($email) {
|
||||
$sql .= " AND `email` = '".pSQL($email)."'";
|
||||
}
|
||||
|
||||
if ($ip_address) {
|
||||
$sql .= " AND `ip_address` = '".pSQL($ip_address)."'";
|
||||
}
|
||||
|
||||
return Db::getInstance()->getValue($sql) >= $attempts;
|
||||
return Db::getInstance()->getValue(
|
||||
'SELECT COUNT(ma.`id_maintenance_access`)
|
||||
FROM `'._DB_PREFIX_.'maintenance_access` ma
|
||||
WHERE (ma.`email` = "'.$email.'" OR ma.`ip_address` = "'.$ipAddress.'")
|
||||
AND ma.`date_add` > "'.date('Y-m-d H:i:s', strtotime('-'.MaintenanceAccess::LOGIN_ATTEMPTS_WINDOW.' minutes')).'"'
|
||||
);
|
||||
}
|
||||
|
||||
public function checkLimit($email)
|
||||
public function getLastAttempt($email, $ipAddress)
|
||||
{
|
||||
$has_error = false;
|
||||
$minutes_ago = time() - self::QUARTER_HOUR;
|
||||
$hours_ago = time() - self::HOUR;
|
||||
$ip_address = Tools::getRemoteAddr();
|
||||
|
||||
$has_error |= $this->getUserFailedCount(
|
||||
$minutes_ago, $email, false, self::USERNAME_ATTEMPTS_PER_QUARTER_HOUR
|
||||
)? true : $has_error;
|
||||
$has_error |= $this->getUserFailedCount(
|
||||
$minutes_ago, false, $ip_address, self::IP_ATTEMPTS_QUARTER_HOUR
|
||||
)? true : $has_error;
|
||||
$has_error |= $this->getUserFailedCount(
|
||||
$hours_ago, $email, false, self::USERNAME_ATTEMPTS_PER_HOUR
|
||||
)? true : $has_error;
|
||||
$has_error |= $this->getUserFailedCount(
|
||||
$hours_ago, false, $ip_address, self::IP_ATTEMPTS_PER_HOUR
|
||||
)? true : $has_error;
|
||||
|
||||
return $has_error;
|
||||
return Db::getInstance()->getRow(
|
||||
'SELECT *
|
||||
FROM `'._DB_PREFIX_.'maintenance_access` ma
|
||||
WHERE (ma.`email` = "'.$email.'" OR ma.`ip_address` = "'.$ipAddress.'")
|
||||
ORDER BY ma.`date_add` DESC'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -776,6 +776,7 @@ class FrontControllerCore extends Controller
|
||||
}
|
||||
|
||||
if (Tools::isSubmit('SubmitLogin')
|
||||
&& !Configuration::get('PS_SHOP_ENABLE')
|
||||
&& Configuration::get('PS_ALLOW_EMP')
|
||||
&& !Context::getContext()->cookie->enable_maintenance_view
|
||||
) {
|
||||
@@ -812,24 +813,31 @@ class FrontControllerCore extends Controller
|
||||
|
||||
protected function processLogin($email, $passwd)
|
||||
{
|
||||
$result = false;
|
||||
$remoteIpAddress = Tools::getRemoteAddr();
|
||||
$objMaintenanceAccess = new MaintenanceAccess();
|
||||
$attemptsCount = $objMaintenanceAccess->getAttemptsCount($email, $remoteIpAddress); // does not include the current one
|
||||
$maxAttempts = Configuration::get('PS_ALLOW_EMP_MAX_ATTEMPTS');
|
||||
|
||||
/* Check fields validity */
|
||||
if (empty($email)) {
|
||||
$this->errors[] = Tools::displayError('Email is empty.');
|
||||
$this->errors[] = Tools::displayError('The Email address field is blank.');
|
||||
} elseif (!Validate::isEmail($email)) {
|
||||
$this->errors[] = Tools::displayError('Invalid email address.');
|
||||
}
|
||||
|
||||
if (empty($passwd)) {
|
||||
$this->errors[] = Tools::displayError('The password field is blank.');
|
||||
$this->errors[] = Tools::displayError('The Password field is blank.');
|
||||
} elseif (!Validate::isPasswd($passwd)) {
|
||||
$this->errors[] = Tools::displayError('Invalid password.');
|
||||
}
|
||||
|
||||
if ($objMaintenanceAccess->checkLimit($email)) {
|
||||
$this->errors[] =Tools::displayError('You have exceeded the limit of login attempts, please try after some time');
|
||||
if (!count($this->errors)) {
|
||||
if ($attemptsCount >= $maxAttempts) {
|
||||
if ($lastAttempt = $objMaintenanceAccess->getLastAttempt($email, $remoteIpAddress)) {
|
||||
$minutes = round((time() - strtotime($lastAttempt['date_add'])) / 60);
|
||||
|
||||
$this->errors[] = Tools::displayError(sprintf('You have exceeded the limit of login attempts, please try after %d minutes.', MaintenanceAccess::LOGIN_ATTEMPTS_WINDOW - $minutes));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!count($this->errors)) {
|
||||
@@ -837,6 +845,7 @@ class FrontControllerCore extends Controller
|
||||
$this->context->employee = new Employee();
|
||||
$is_employee_loaded = $this->context->employee->getByEmail($email, $passwd);
|
||||
$employee_associated_shop = $this->context->employee->getAssociatedShops();
|
||||
|
||||
if (!$is_employee_loaded) {
|
||||
$this->errors[] = Tools::displayError('The Employee does not exist, or the password provided is incorrect.');
|
||||
$this->context->employee->logout();
|
||||
@@ -847,21 +856,40 @@ class FrontControllerCore extends Controller
|
||||
// Update cookie
|
||||
$cookie = Context::getContext()->cookie;
|
||||
$cookie->enable_maintenance_view = $this->context->employee->id;
|
||||
$cookie->remote_addr = ip2long(Tools::getRemoteAddr());
|
||||
$cookie->remote_addr = ip2long($remoteIpAddress);
|
||||
$cookie->write();
|
||||
|
||||
// Reset attempts count on successful login
|
||||
$objMaintenanceAccess->removeFailedAttempts($email, $remoteIpAddress);
|
||||
}
|
||||
|
||||
if (count($this->errors)) {
|
||||
$this->context->smarty->assign('errors', $this->errors);
|
||||
$objMaintenanceAccess = new MaintenanceAccess();
|
||||
$objMaintenanceAccess->email = $email;
|
||||
$objMaintenanceAccess->ip_address = Tools::getRemoteAddr();
|
||||
$objMaintenanceAccess->ip_address = $remoteIpAddress;
|
||||
$objMaintenanceAccess->save();
|
||||
|
||||
if ($attemptsCount < $maxAttempts) {
|
||||
$attemptsLeft = $maxAttempts - $attemptsCount - 1;
|
||||
|
||||
if ($attemptsLeft > 0) {
|
||||
if ($attemptsLeft > 1) {
|
||||
$this->errors[] = Tools::displayError(sprintf('%d attempts left.', ($maxAttempts - $attemptsCount - 1)));
|
||||
} else {
|
||||
$this->errors[] = Tools::displayError(sprintf('%d attempt left.', ($maxAttempts - $attemptsCount - 1)));
|
||||
}
|
||||
} else {
|
||||
$this->errors[] = Tools::displayError(sprintf('You have reached the limit of login attempts, please try after %d minutes.', MaintenanceAccess::LOGIN_ATTEMPTS_WINDOW));
|
||||
}
|
||||
}
|
||||
|
||||
$this->context->smarty->assign('errors', $this->errors);
|
||||
} else {
|
||||
Tools::redirect('index.php');
|
||||
}
|
||||
} else {
|
||||
$this->context->smarty->assign('errors', $this->errors);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -54,7 +54,16 @@ class AdminMaintenanceControllerCore extends AdminController
|
||||
'validation' => 'isBool',
|
||||
'cast' => 'intval',
|
||||
'type' => 'bool',
|
||||
'class' => "hello"
|
||||
'form_group_class' => (Tools::getValue('PS_SHOP_ENABLE', Configuration::get('PS_SHOP_ENABLE'))) ? 'collapse' : '',
|
||||
),
|
||||
'PS_ALLOW_EMP_MAX_ATTEMPTS' => array(
|
||||
'title' => $this->l('Maximum Login Attempts'),
|
||||
'validation' => 'isUnsignedInt',
|
||||
'cast' => 'intval',
|
||||
'type' => 'text',
|
||||
'class' => 'fixed-width-xl',
|
||||
'desc' => $this->l('Set the number of maximum login attempts allowed in 30 minutes.'),
|
||||
'form_group_class' => ((Tools::getValue('PS_SHOP_ENABLE', Configuration::get('PS_SHOP_ENABLE'))) || !(Tools::getValue('PS_ALLOW_EMP', Configuration::get('PS_ALLOW_EMP')))) ? ' collapse' : '',
|
||||
),
|
||||
'PS_MAINTENANCE_IP' => array(
|
||||
'title' => $this->l('Maintenance IP'),
|
||||
@@ -69,6 +78,41 @@ class AdminMaintenanceControllerCore extends AdminController
|
||||
);
|
||||
}
|
||||
|
||||
public function postProcess()
|
||||
{
|
||||
if (Tools::isSubmit('submitOptionsconfiguration')) {
|
||||
$shopEnable = Tools::getValue('PS_SHOP_ENABLE');
|
||||
$allowEmp = Tools::getValue('PS_ALLOW_EMP');
|
||||
$allowEmpMaxTries = trim(Tools::getValue('PS_ALLOW_EMP_MAX_ATTEMPTS'));
|
||||
$maintenanceIp = trim(Tools::getValue('PS_MAINTENANCE_IP'));
|
||||
|
||||
// validations
|
||||
if (!$shopEnable && $allowEmp) {
|
||||
if (!$allowEmpMaxTries) {
|
||||
$this->errors[] = $this->l('Maximum Tries is a required field.');
|
||||
} elseif (!Validate::isUnsignedInt($allowEmpMaxTries)) {
|
||||
$this->errors[] = $this->l('Maximum Tries is invalid.');
|
||||
}
|
||||
}
|
||||
|
||||
// update values
|
||||
if (!count($this->errors)) {
|
||||
Configuration::updateValue('PS_SHOP_ENABLE', $shopEnable);
|
||||
Configuration::updateValue('PS_MAINTENANCE_IP', $maintenanceIp);
|
||||
|
||||
if (!$shopEnable) {
|
||||
Configuration::updateValue('PS_ALLOW_EMP', $allowEmp);
|
||||
|
||||
if ($allowEmp) {
|
||||
Configuration::updateValue('PS_ALLOW_EMP_MAX_ATTEMPTS', $allowEmpMaxTries);
|
||||
}
|
||||
}
|
||||
|
||||
Tools::redirectAdmin(self::$currentIndex.'&token='.$this->token.'&conf=6');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function setMedia()
|
||||
{
|
||||
parent::setMedia();
|
||||
|
||||
@@ -92,6 +92,12 @@
|
||||
<configuration id="PS_SHOP_ENABLE" name="PS_SHOP_ENABLE">
|
||||
<value>1</value>
|
||||
</configuration>
|
||||
<configuration id="PS_ALLOW_EMP" name="PS_ALLOW_EMP">
|
||||
<value>0</value>
|
||||
</configuration>
|
||||
<configuration id="PS_ALLOW_EMP_MAX_ATTEMPTS" name="PS_ALLOW_EMP_MAX_ATTEMPTS">
|
||||
<value>3</value>
|
||||
</configuration>
|
||||
<configuration id="PS_NB_DAYS_NEW_PRODUCT" name="PS_NB_DAYS_NEW_PRODUCT">
|
||||
<value>20</value>
|
||||
</configuration>
|
||||
|
||||
@@ -1,22 +1,52 @@
|
||||
$(document).ready(function(){
|
||||
/**
|
||||
* Since 2010 Webkul.
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* All right is reserved,
|
||||
* Please go through this link for complete license : https://store.webkul.com/license.html
|
||||
*
|
||||
* DISCLAIMER
|
||||
*
|
||||
* Do not edit or add to this file if you wish to upgrade this module to newer
|
||||
* versions in the future. If you wish to customize this module for your
|
||||
* needs please refer to https://store.webkul.com/customisation-guidelines/ for more information.
|
||||
*
|
||||
* @author Webkul IN <support@webkul.com>
|
||||
* @copyright Since 2010 Webkul IN
|
||||
* @license https://store.webkul.com/license.html
|
||||
*/
|
||||
|
||||
if ($("#PS_SHOP_ENABLE_on").is(':checked')) {
|
||||
$('#conf_id_PS_ALLOW_EMP').hide();
|
||||
}
|
||||
if ($("#PS_SHOP_ENABLE_off").is(':checked')) {
|
||||
$("#conf_id_PS_ALLOW_EMP").show();
|
||||
function manageFieldMaximumAttempts(state) {
|
||||
if (state) {
|
||||
$('[name="PS_ALLOW_EMP_MAX_ATTEMPTS"]').closest('.form-group').show(200);
|
||||
} else {
|
||||
$('[name="PS_ALLOW_EMP_MAX_ATTEMPTS"]').closest('.form-group').hide(200);
|
||||
}
|
||||
}
|
||||
|
||||
$(".clicker.blue").on('click', function(){
|
||||
$('.hiddendiv').show('slow');
|
||||
$(document).ready(function() {
|
||||
// manage fields visibility
|
||||
$(document).on('change', '[name="PS_SHOP_ENABLE"]', function () {
|
||||
if (parseInt($('[name="PS_SHOP_ENABLE"]:checked').val())) {
|
||||
$('[name="PS_ALLOW_EMP"]').closest('.form-group').hide(200);
|
||||
manageFieldMaximumAttempts(false);
|
||||
} else {
|
||||
$('[name="PS_ALLOW_EMP"]').closest('.form-group').show(200);
|
||||
manageFieldMaximumAttempts(parseInt($('[name="PS_ALLOW_EMP"]:checked').val()));
|
||||
}
|
||||
});
|
||||
|
||||
$("#cancelLogin").on('click', function(){
|
||||
$('.hiddendiv').hide('slow');
|
||||
$(document).on('change', '[name="PS_ALLOW_EMP"]', function () {
|
||||
manageFieldMaximumAttempts(parseInt($('[name="PS_ALLOW_EMP"]:checked').val()));
|
||||
});
|
||||
|
||||
$("input[type='radio'][name='PS_SHOP_ENABLE']").change(function() {
|
||||
$("#conf_id_PS_ALLOW_EMP").toggle('slow');
|
||||
$('.clicker.blue').on('click', function() {
|
||||
$('.login-form-wrap').toggle(200);
|
||||
});
|
||||
|
||||
$('#cancelLogin').on('click', function() {
|
||||
$('.login-form-wrap').hide(200);
|
||||
});
|
||||
|
||||
// manage dropdowns
|
||||
|
||||
@@ -39,8 +39,7 @@ p {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.hiddendiv {
|
||||
display: none;
|
||||
.login-form-wrap {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
|
||||
@@ -98,37 +98,33 @@
|
||||
<p>{l s='Thanks for your patience!'}</p>
|
||||
{if isset($allowEmployee) && $allowEmployee}
|
||||
<div>
|
||||
<p class="clicker blue" tabindex="1">{l s='Are you member?'}</p>
|
||||
<div class="hiddendiv">
|
||||
<div class="allow-conatainer">
|
||||
<form action="index.php" method="post">
|
||||
<div class="form_content clearfix">
|
||||
<div class="form-group form-ok">
|
||||
<label class="" for="email">{l s='Email address'}</label>
|
||||
<br>
|
||||
<input class="form-control" placeholder="Email" type="email" id="email"
|
||||
name="email" value="">
|
||||
</div>
|
||||
<div class="form-group form-ok">
|
||||
<label class="" for="passwd">{l s='Password'}</label>
|
||||
<br>
|
||||
<input class="form-control" type="password" placeholder="Password" id="passwd"
|
||||
name="passwd" value="">
|
||||
</div>
|
||||
<button type="submit" id="SubmitLogin" name="SubmitLogin" class="btn btn-primary">
|
||||
<span>
|
||||
{l s='Log in'}
|
||||
</span>
|
||||
</button>
|
||||
<button type="button" id="cancelLogin" name="cancelLogin"
|
||||
class="btn btn-primary cancel-login">
|
||||
<span>
|
||||
{l s='Cancel'}
|
||||
</span>
|
||||
</button>
|
||||
<p class="clicker blue" tabindex="1">{l s='Are you a member?'}</p>
|
||||
<div class="login-form-wrap" {if !isset($smarty.post.SubmitLogin)}style="display:none;"{/if}>
|
||||
<form action="" method="post">
|
||||
<div class="form_content clearfix">
|
||||
<div class="form-group form-ok">
|
||||
<label class="" for="email">{l s='Email address'}</label>
|
||||
<br>
|
||||
<input class="form-control" placeholder="{l s='Email'}" type="email" id="email" name="email" {if isset($smarty.post.SubmitLogin)}value="{$smarty.post.email|escape:'html':'UTF-8'}"{/if}>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="form-group form-ok">
|
||||
<label class="" for="passwd">{l s='Password'}</label>
|
||||
<br>
|
||||
<input class="form-control" type="password" placeholder="{l s='Password'}" id="passwd" name="passwd" value="">
|
||||
</div>
|
||||
<button type="submit" id="SubmitLogin" name="SubmitLogin" class="btn btn-primary">
|
||||
<span>
|
||||
{l s='Log in'}
|
||||
</span>
|
||||
</button>
|
||||
<button type="button" id="cancelLogin" name="cancelLogin"
|
||||
class="btn btn-primary cancel-login">
|
||||
<span>
|
||||
{l s='Cancel'}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user