2015-01-24 02:25:12 +02:00
|
|
|
// Package lock implements user locking after N bad sign-in attempts.
|
|
|
|
package lock
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"gopkg.in/authboss.v0"
|
|
|
|
)
|
|
|
|
|
2015-03-16 23:42:45 +02:00
|
|
|
// Storage key constants
|
2015-01-24 02:25:12 +02:00
|
|
|
const (
|
2015-02-16 23:31:26 +02:00
|
|
|
StoreAttemptNumber = "attempt_number"
|
|
|
|
StoreAttemptTime = "attempt_time"
|
|
|
|
StoreLocked = "locked"
|
2015-01-24 02:25:12 +02:00
|
|
|
)
|
|
|
|
|
2015-02-22 22:55:09 +02:00
|
|
|
var (
|
|
|
|
errUserMissing = errors.New("lock: user not loaded in BeforeAuth callback")
|
|
|
|
)
|
|
|
|
|
2015-01-24 02:25:12 +02:00
|
|
|
func init() {
|
2016-05-09 19:20:10 +02:00
|
|
|
authboss.RegisterModule("lock", &Lock{})
|
2015-01-24 02:25:12 +02:00
|
|
|
}
|
|
|
|
|
2015-03-16 23:42:45 +02:00
|
|
|
// Lock module
|
2015-01-24 02:25:12 +02:00
|
|
|
type Lock struct {
|
2015-04-01 00:27:47 +02:00
|
|
|
*authboss.Authboss
|
2015-01-24 02:25:12 +02:00
|
|
|
}
|
|
|
|
|
2015-03-16 23:42:45 +02:00
|
|
|
// Initialize the module
|
2015-04-01 00:27:47 +02:00
|
|
|
func (l *Lock) Initialize(ab *authboss.Authboss) error {
|
|
|
|
l.Authboss = ab
|
2016-05-07 08:12:20 +02:00
|
|
|
if l.Storer == nil && l.StoreMaker == nil {
|
2015-03-16 23:42:45 +02:00
|
|
|
return errors.New("lock: Need a Storer")
|
2015-01-24 02:25:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Events
|
2015-09-22 05:53:51 +02:00
|
|
|
l.Callbacks.After(authboss.EventGetUser, func(ctx *authboss.Context) error {
|
|
|
|
_, err := l.beforeAuth(ctx)
|
|
|
|
return err
|
|
|
|
})
|
2015-04-01 00:27:47 +02:00
|
|
|
l.Callbacks.Before(authboss.EventAuth, l.beforeAuth)
|
|
|
|
l.Callbacks.After(authboss.EventAuth, l.afterAuth)
|
|
|
|
l.Callbacks.After(authboss.EventAuthFail, l.afterAuthFail)
|
2015-01-24 02:25:12 +02:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-03-16 23:42:45 +02:00
|
|
|
// Routes for the module
|
2015-01-24 02:25:12 +02:00
|
|
|
func (l *Lock) Routes() authboss.RouteTable {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-03-16 23:42:45 +02:00
|
|
|
// Storage requirements
|
2015-01-24 02:25:12 +02:00
|
|
|
func (l *Lock) Storage() authboss.StorageOptions {
|
|
|
|
return authboss.StorageOptions{
|
2015-04-01 00:27:47 +02:00
|
|
|
l.PrimaryID: authboss.String,
|
|
|
|
StoreAttemptNumber: authboss.Integer,
|
|
|
|
StoreAttemptTime: authboss.DateTime,
|
|
|
|
StoreLocked: authboss.DateTime,
|
2015-01-24 02:25:12 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-16 23:42:45 +02:00
|
|
|
// beforeAuth ensures the account is not locked.
|
|
|
|
func (l *Lock) beforeAuth(ctx *authboss.Context) (authboss.Interrupt, error) {
|
2015-01-24 02:25:12 +02:00
|
|
|
if ctx.User == nil {
|
2015-02-22 22:55:09 +02:00
|
|
|
return authboss.InterruptNone, errUserMissing
|
2015-01-24 02:25:12 +02:00
|
|
|
}
|
|
|
|
|
2015-03-15 17:38:50 +02:00
|
|
|
if locked, ok := ctx.User.DateTime(StoreLocked); ok && locked.After(time.Now().UTC()) {
|
2015-02-22 22:43:28 +02:00
|
|
|
return authboss.InterruptAccountLocked, nil
|
2015-01-24 02:25:12 +02:00
|
|
|
}
|
|
|
|
|
2015-02-22 22:43:28 +02:00
|
|
|
return authboss.InterruptNone, nil
|
2015-01-24 02:25:12 +02:00
|
|
|
}
|
|
|
|
|
2015-03-16 23:42:45 +02:00
|
|
|
// afterAuth resets the attempt number field.
|
|
|
|
func (l *Lock) afterAuth(ctx *authboss.Context) error {
|
2015-01-24 02:25:12 +02:00
|
|
|
if ctx.User == nil {
|
2015-02-22 22:55:09 +02:00
|
|
|
return errUserMissing
|
2015-01-24 02:25:12 +02:00
|
|
|
}
|
|
|
|
|
2015-03-04 08:34:37 +02:00
|
|
|
ctx.User[StoreAttemptNumber] = int64(0)
|
2015-02-16 23:31:26 +02:00
|
|
|
ctx.User[StoreAttemptTime] = time.Now().UTC()
|
2015-01-24 02:25:12 +02:00
|
|
|
|
2015-02-18 18:57:50 +02:00
|
|
|
if err := ctx.SaveUser(); err != nil {
|
2015-02-22 22:43:28 +02:00
|
|
|
return err
|
2015-01-24 02:25:12 +02:00
|
|
|
}
|
2015-02-22 22:43:28 +02:00
|
|
|
|
|
|
|
return nil
|
2015-01-24 02:25:12 +02:00
|
|
|
}
|
|
|
|
|
2015-03-16 23:42:45 +02:00
|
|
|
// afterAuthFail adjusts the attempt number and time.
|
|
|
|
func (l *Lock) afterAuthFail(ctx *authboss.Context) error {
|
2015-01-24 02:25:12 +02:00
|
|
|
if ctx.User == nil {
|
2015-02-22 22:55:09 +02:00
|
|
|
return errUserMissing
|
2015-01-24 02:25:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
lastAttempt := time.Now().UTC()
|
2015-02-20 00:34:29 +02:00
|
|
|
if attemptTime, ok := ctx.User.DateTime(StoreAttemptTime); ok {
|
|
|
|
lastAttempt = attemptTime
|
2015-01-24 02:25:12 +02:00
|
|
|
}
|
|
|
|
|
2015-03-04 08:34:37 +02:00
|
|
|
var nAttempts int64
|
|
|
|
if attempts, ok := ctx.User.Int64(StoreAttemptNumber); ok {
|
2015-02-20 00:34:29 +02:00
|
|
|
nAttempts = attempts
|
2015-01-24 02:25:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
nAttempts++
|
|
|
|
|
2015-04-01 00:27:47 +02:00
|
|
|
if time.Now().UTC().Sub(lastAttempt) <= l.LockWindow {
|
|
|
|
if nAttempts >= int64(l.LockAfter) {
|
|
|
|
ctx.User[StoreLocked] = time.Now().UTC().Add(l.LockDuration)
|
2015-01-24 02:25:12 +02:00
|
|
|
}
|
|
|
|
|
2015-02-16 23:31:26 +02:00
|
|
|
ctx.User[StoreAttemptNumber] = nAttempts
|
2015-01-25 08:19:22 +02:00
|
|
|
} else {
|
2015-03-04 08:34:37 +02:00
|
|
|
ctx.User[StoreAttemptNumber] = int64(0)
|
2015-01-24 02:25:12 +02:00
|
|
|
}
|
2015-02-16 23:31:26 +02:00
|
|
|
ctx.User[StoreAttemptTime] = time.Now().UTC()
|
2015-01-24 02:25:12 +02:00
|
|
|
|
2015-02-18 18:57:50 +02:00
|
|
|
if err := ctx.SaveUser(); err != nil {
|
2015-02-22 22:43:28 +02:00
|
|
|
return err
|
2015-01-24 02:25:12 +02:00
|
|
|
}
|
2015-02-22 22:43:28 +02:00
|
|
|
|
|
|
|
return nil
|
2015-01-24 02:25:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Lock a user manually.
|
2015-02-22 22:43:28 +02:00
|
|
|
func (l *Lock) Lock(key string) error {
|
2015-04-01 00:27:47 +02:00
|
|
|
user, err := l.Storer.Get(key)
|
2015-01-24 02:25:12 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
attr := authboss.Unbind(user)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-04-01 00:27:47 +02:00
|
|
|
attr[StoreLocked] = time.Now().UTC().Add(l.LockDuration)
|
2015-01-24 02:25:12 +02:00
|
|
|
|
2015-04-01 00:27:47 +02:00
|
|
|
return l.Storer.Put(key, attr)
|
2015-01-24 02:25:12 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Unlock a user that was locked by this module.
|
2015-02-22 22:43:28 +02:00
|
|
|
func (l *Lock) Unlock(key string) error {
|
2015-04-01 00:27:47 +02:00
|
|
|
user, err := l.Storer.Get(key)
|
2015-01-24 02:25:12 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
attr := authboss.Unbind(user)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-01-25 08:19:22 +02:00
|
|
|
// Set the last attempt to be -window*2 to avoid immediately
|
|
|
|
// giving another login failure.
|
2015-04-01 00:27:47 +02:00
|
|
|
attr[StoreAttemptTime] = time.Now().UTC().Add(-l.LockWindow * 2)
|
2015-03-04 08:34:37 +02:00
|
|
|
attr[StoreAttemptNumber] = int64(0)
|
2015-04-01 00:27:47 +02:00
|
|
|
attr[StoreLocked] = time.Now().UTC().Add(-l.LockDuration)
|
2015-01-24 02:25:12 +02:00
|
|
|
|
2015-04-01 00:27:47 +02:00
|
|
|
return l.Storer.Put(key, attr)
|
2015-01-24 02:25:12 +02:00
|
|
|
}
|