The agiletoolkit Auth/basic class allow to try to login without any limitation. And i'm searching a way to limit the number of failed login attempt, i've tried to override some methods of this class but it use ajax reloading so php code is not executed correctly.
Any suggestion is welcome.
Many Thanks,
Inspired by StackOverflow I have implemented the following protection:
for each user we store soft / hard lock. Hard lock would refuse to verify password even if it's correct. soft lock is a period of time after which we will reset "unsuccessful attempt" counter. every time you type incorrect password both soft and hard lock increase making you wait longer (but not forever) gradual increase puts a reasonable limit so if your attacker tries to hack your account for a hour - he will only get several attempts in, yet your account will be OK to log-in after few hours. I have implemented this in one of my projects, although it's not a controller, but a built-in code. Please look through comments and I hope this will help you and others:
In User model add:
$this->addField('pwd_locked_until');
$this->addField('pwd_failure_count');
$this->addField('pwd_soft_unlock');
You'll also need two methods:
/* Must be called when user unsuccessfully tried to log-in */
function passwordIncorrect(){
$su=strtotime($this['pwd_soft_unlock']);
if($su && $su>time()){
// aw, they repeatedly typed password in, lets teach them power of two!
$this['pwd_failure_count']=$this['pwd_failure_count']+1;
if($this['pwd_failure_count']>3){
$this['pwd_locked_until']=date('Y-m-d H:i:s',time()
+pow(2,min($this['pwd_failure_count'],20)));
$this['pwd_soft_unlock']=date('Y-m-d H:i:s',time()
+max(2*pow(2,min($this['pwd_failure_count'],20)),60*5));
}
}else{
$this['pwd_failure_count']=1;
$this['pwd_soft_unlock']=date('Y-m-d H:i:s',time() +60*5);
}
$this->save();
}
and
/* Must be called when user logs in successfully */
function loginSuccessful(){
$this['last_seen']=date('Y-m-d H:i:s');
$this['pwd_soft_unlock']=null;
$this->save();
}
Finally - you can use this as a login form:
class Form_Login extends Form {
function init(){
parent::init();
$form=$this;
$form->setModel('User',array('email','password'));
$form->addSubmit('Login');
if($form->isSubmitted()){
$auth=$this->api->auth;
$l=$form->get('email');
$p=$form->get('password');
// check to see if user with such email exist
$u=$this->add('Model_User');
$u->tryLoadBy('email',$form->get('email'));
// user may have also typed his username
if(!$u->loaded()){
$u->tryLoadBy('user_name',$form->get('email'));
}
// incorrect email - but say that password is wrong
if(!$u->loaded())$form->getElement('password')
->displayFieldError('Incorrect Login');
// if login is locked, don't verify password at all
$su=strtotime($u['pwd_locked_until']);
if($su>time()){
$form->getElement('password')
->displayFieldError('Account is locked for '.
$this->add('Controller_Fancy')
->fancy_datetime($u['pwd_locked_until']));
}
// check account
if($auth->verifyCredentials($u['email'],$p)){
// resets incorrect login statistics
$u->loginSuccessful();
$auth->login($l);
// redirect user
$form->js()->univ()->location($this->api->url('/'))->execute();
}else{
// incorrect password, register failed attempt
$u->passwordIncorrect();
$form->getElement('password')->displayFieldError('Incorrect Login');
}
}
}
}
This should be converted into add-on by someone.