1
0
mirror of https://github.com/volatiletech/authboss.git synced 2025-01-08 04:03:53 +02:00
authboss/lock/lock.go

161 lines
3.4 KiB
Go
Raw Normal View History

// Package lock implements user locking after N bad sign-in attempts.
package lock
import (
"errors"
"time"
"gopkg.in/authboss.v0"
)
const (
StoreAttemptNumber = "attempt_number"
StoreAttemptTime = "attempt_time"
StoreLocked = "locked"
)
2015-02-22 22:55:09 +02:00
var (
errUserMissing = errors.New("lock: user not loaded in BeforeAuth callback")
)
// L is the singleton instance of the lock module which will have been
// configured and ready to use after authboss.Init()
var L *Lock
func init() {
L = &Lock{}
authboss.RegisterModule("lock", L)
}
type Lock struct {
}
func (l *Lock) Initialize() error {
if authboss.Cfg.Storer == nil {
return errors.New("lock: Need a Storer.")
}
// Events
authboss.Cfg.Callbacks.Before(authboss.EventGet, l.BeforeAuth)
authboss.Cfg.Callbacks.Before(authboss.EventAuth, l.BeforeAuth)
authboss.Cfg.Callbacks.After(authboss.EventAuth, l.AfterAuth)
authboss.Cfg.Callbacks.After(authboss.EventAuthFail, l.AfterAuthFail)
return nil
}
func (l *Lock) Routes() authboss.RouteTable {
return nil
}
func (l *Lock) Storage() authboss.StorageOptions {
return authboss.StorageOptions{
StoreAttemptNumber: authboss.Integer,
StoreAttemptTime: authboss.DateTime,
StoreLocked: authboss.Bool,
}
}
// BeforeAuth ensures the account is not locked.
2015-02-22 22:43:28 +02:00
func (l *Lock) BeforeAuth(ctx *authboss.Context) (authboss.Interrupt, error) {
if ctx.User == nil {
2015-02-22 22:55:09 +02:00
return authboss.InterruptNone, errUserMissing
}
2015-02-22 22:43:28 +02:00
if locked, ok := ctx.User.Bool(StoreLocked); ok && locked {
return authboss.InterruptAccountLocked, nil
}
2015-02-22 22:43:28 +02:00
return authboss.InterruptNone, nil
}
// AfterAuth resets the attempt number field.
2015-02-22 22:43:28 +02:00
func (l *Lock) AfterAuth(ctx *authboss.Context) error {
if ctx.User == nil {
2015-02-22 22:55:09 +02:00
return errUserMissing
}
ctx.User[StoreAttemptNumber] = 0
ctx.User[StoreAttemptTime] = time.Now().UTC()
if err := ctx.SaveUser(); err != nil {
2015-02-22 22:43:28 +02:00
return err
}
2015-02-22 22:43:28 +02:00
return nil
}
// AfterAuthFail adjusts the attempt number and time.
2015-02-22 22:43:28 +02:00
func (l *Lock) AfterAuthFail(ctx *authboss.Context) error {
if ctx.User == nil {
2015-02-22 22:55:09 +02:00
return errUserMissing
}
lastAttempt := time.Now().UTC()
if attemptTime, ok := ctx.User.DateTime(StoreAttemptTime); ok {
lastAttempt = attemptTime
}
nAttempts := 0
if attempts, ok := ctx.User.Int(StoreAttemptNumber); ok {
nAttempts = attempts
}
nAttempts++
if time.Now().UTC().Sub(lastAttempt) <= authboss.Cfg.LockWindow {
if nAttempts >= authboss.Cfg.LockAfter {
ctx.User[StoreLocked] = true
}
ctx.User[StoreAttemptNumber] = nAttempts
} else {
ctx.User[StoreAttemptNumber] = 0
}
ctx.User[StoreAttemptTime] = time.Now().UTC()
if err := ctx.SaveUser(); err != nil {
2015-02-22 22:43:28 +02:00
return err
}
2015-02-22 22:43:28 +02:00
return nil
}
// Lock a user manually.
2015-02-22 22:43:28 +02:00
func (l *Lock) Lock(key string) error {
user, err := authboss.Cfg.Storer.Get(key, authboss.ModuleAttrMeta)
if err != nil {
return err
}
attr := authboss.Unbind(user)
if err != nil {
return err
}
attr[StoreLocked] = true
2015-02-22 22:43:28 +02:00
return authboss.Cfg.Storer.Put(key, attr)
}
// Unlock a user that was locked by this module.
2015-02-22 22:43:28 +02:00
func (l *Lock) Unlock(key string) error {
user, err := authboss.Cfg.Storer.Get(key, authboss.ModuleAttrMeta)
if err != nil {
return err
}
attr := authboss.Unbind(user)
if err != nil {
return err
}
// Set the last attempt to be -window*2 to avoid immediately
// giving another login failure.
attr[StoreAttemptTime] = time.Now().UTC().Add(-authboss.Cfg.LockWindow * 2)
attr[StoreAttemptNumber] = 0
attr[StoreLocked] = false
2015-02-22 22:43:28 +02:00
return authboss.Cfg.Storer.Put(key, attr)
}