Skip to content

Commit

Permalink
db, server: Limit account login attempts
Browse files Browse the repository at this point in the history
close bakape#45
  • Loading branch information
Madouura committed Nov 5, 2019
1 parent f9f8d1a commit 500a9c5
Show file tree
Hide file tree
Showing 6 changed files with 391 additions and 586 deletions.
85 changes: 85 additions & 0 deletions db/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package db
import (
"database/sql"
"errors"
"sync"
"time"

"github.com/Masterminds/squirrel"
Expand All @@ -13,6 +14,7 @@ import (
// Common errors
var (
ErrUserNameTaken = errors.New("user name already taken")
mu sync.RWMutex
)

// IsLoggedIn check if the user is logged in with the specified session
Expand Down Expand Up @@ -105,6 +107,89 @@ func LogOutAll(account string) error {
return err
}

// IncrementLoginAttempts increments or creates the login attempt counter for
// user
func IncrementLoginAttempts(ip, account string) (attempts uint8, err error) {
mu.Lock()
defer mu.Unlock()
// Get login attempts counter
err = sq.Select("attempts").
From("attempted_logins").
Where("ip = ? and account = ?", ip, account).
Scan(&attempts)
switch err {
case nil:
// Don't increment if over 5 attempts
if attempts > 5 {
return
}
// Increment the counter
attempts++
_, err = sq.Update("attempted_logins").
Set("attempts", attempts).
Where("ip = ? and account = ?", ip, account).
Exec()
case sql.ErrNoRows:
// If not, create it
attempts = 1
_, err = sq.Insert("attempted_logins").
Columns("ip", "account", "attempts", "expires").
Values(ip, account, attempts, time.Now().Add(time.Hour*24).UTC()).
Exec()
}
return
}

// ClearLoginAttempts clears login attempts for account for IP
func ClearLoginAttempts(ip, account string) error {
mu.Lock()
defer mu.Unlock()
return clearLoginAttempts(ip, account)
}

func clearLoginAttempts(ip, account string) error {
_, err := sq.Delete("attempted_logins").
Where("ip = ? and account = ?", ip, account).
Exec()
return err
}

// Decrements all login attempt counts, deletes if under 2
func decrementAllLoginAttempts() (err error) {
var ip, account string
var attempts uint8
mu.Lock()
defer mu.Unlock()
r, err := sq.Select("ip", "account", "attempts").
From("attempted_logins").
Query()
if err != nil {
return
}
defer r.Close()
for r.Next() {
err = r.Scan(&ip, &account, &attempts)
if err != nil {
return
}
if attempts < 2 {
err = clearLoginAttempts(ip, account)
if err != nil {
return
}
continue
}
_, err = sq.Update("attempted_logins").
Set("attempts", attempts-1).
Where("ip = ? and account = ?", ip, account).
Exec()
if err != nil {
return
}
}
return r.Err()
}

// ChangePassword changes an existing user's login password
func ChangePassword(account string, hash []byte) error {
_, err := sq.Update("accounts").
Expand Down
12 changes: 12 additions & 0 deletions db/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -1497,6 +1497,18 @@ var migrations = []func(*sql.Tx) error{
)`,
)
},
func(tx *sql.Tx) (err error) {
return execAll(tx,
`create table attempted_logins (
ip inet not null,
account varchar(20) not null,
attempts smallint not null,
expires timestamp not null,
primary key (ip, account)
)`,
createIndex("attempted_logins", "expires"),
)
},
}

func createIndex(table string, columns ...string) string {
Expand Down
5 changes: 3 additions & 2 deletions db/upkeep.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func runCleanupTasks() {
func runMinuteTasks() {
if config.Server.ImagerMode != config.ImagerOnly {
logError("open post cleanup", closeDanglingPosts())
expireRows("image_tokens", "bans", "failed_captchas")
expireRows("image_tokens", "bans", "failed_captchas", "attempted_logins")
}
}

Expand All @@ -62,8 +62,9 @@ func runHourTasks() {
logError("thread cleanup", deleteOldThreads())
logError("board cleanup", deleteUnusedBoards())
logError("delete dangling open post bodies", cleanUpOpenPostBodies())
logError("decrement all login attempts", decrementAllLoginAttempts())
_, err := db.Exec(`vacuum`)
logError("vaccum database", err)
logError("vacuum database", err)
}
if config.Server.ImagerMode != config.NoImager {
logError("image cleanup", deleteUnusedImages())
Expand Down
Loading

0 comments on commit 500a9c5

Please sign in to comment.