Improvements to "max login attempts" feature

- increasing login attempt is now atomic within a process
- renamed some functions to be less confusing
This commit is contained in:
Ev Kontsevoy 2016-05-30 19:11:14 -07:00
parent 9f9c586989
commit 0720fbc8c2

View file

@ -19,6 +19,7 @@ package local
import (
"encoding/json"
"fmt"
"sync"
"time"
log "github.com/Sirupsen/logrus"
@ -35,6 +36,7 @@ import (
// IdentityService is responsible for managing web users and currently
// user accounts as well
type IdentityService struct {
sync.Mutex
backend backend.Backend
lockAfter byte
lockDuration time.Duration
@ -209,9 +211,12 @@ func (s *IdentityService) UpsertWebSession(user, sid string, session services.We
return trace.Wrap(err)
}
// LockUser bumps "login attempt" counter for the given user. If the counter
// IncreaseLoginAttempts bumps "login attempt" counter for the given user. If the counter
// reaches 'lockAfter' value, it locks the account and returns access denied error.
func (s *IdentityService) LockUser(user string) error {
func (s *IdentityService) IncreaseLoginAttempts(user string) error {
s.Lock()
defer s.Unlock()
bucket := []string{"web", "users", user}
data, _, err := s.backend.GetValAndTTL(bucket, "lock")
// unexpected error?
@ -227,12 +232,11 @@ func (s *IdentityService) LockUser(user string) error {
if len(data) > 0 && data[0] > s.lockAfter {
return trace.AccessDenied("this account has been locked for %v", s.lockDuration)
}
return trace.Wrap(
s.backend.UpsertVal(bucket, "lock", data, s.lockDuration))
return s.backend.UpsertVal(bucket, "lock", data, s.lockDuration)
}
// UnlockUser resets the "login attempt" counter to zero.
func (s *IdentityService) UnlockUser(user string) error {
// ResetLoginAttempts resets the "login attempt" counter to zero.
func (s *IdentityService) ResetLoginAttempts(user string) error {
err := s.backend.DeleteKey([]string{"web", "users", user}, "lock")
if trace.IsNotFound(err) {
return nil
@ -312,7 +316,7 @@ func (s *IdentityService) CheckPassword(user string, password []byte, hotpToken
if err != nil {
return trace.Wrap(err)
}
if err = s.LockUser(user); err != nil {
if err = s.IncreaseLoginAttempts(user); err != nil {
return trace.Wrap(err)
}
if err := bcrypt.CompareHashAndPassword(hash, password); err != nil {
@ -325,7 +329,7 @@ func (s *IdentityService) CheckPassword(user string, password []byte, hotpToken
if !otp.Scan(hotpToken, defaults.HOTPFirstTokensRange) {
return trace.AccessDenied("bad one time token")
}
defer s.UnlockUser(user)
defer s.ResetLoginAttempts(user)
if err := s.UpsertHOTP(user, otp); err != nil {
return trace.Wrap(err)
}
@ -342,13 +346,13 @@ func (s *IdentityService) CheckPasswordWOToken(user string, password []byte) err
if err != nil {
return trace.Wrap(err)
}
if err = s.LockUser(user); err != nil {
if err = s.IncreaseLoginAttempts(user); err != nil {
return trace.Wrap(err)
}
if err := bcrypt.CompareHashAndPassword(hash, password); err != nil {
return trace.BadParameter("passwords do not match")
}
defer s.UnlockUser(user)
defer s.ResetLoginAttempts(user)
return nil
}