diff --git a/app/v1/init.go b/app/v1/init.go index fab45d1..993a1af 100644 --- a/app/v1/init.go +++ b/app/v1/init.go @@ -1,9 +1,5 @@ package v1 -import ( - "time" -) - func init() { - upSince = time.Now() + go removeUseless() } diff --git a/app/v1/login_attempts.go b/app/v1/login_attempts.go new file mode 100644 index 0000000..9955f89 --- /dev/null +++ b/app/v1/login_attempts.go @@ -0,0 +1,61 @@ +package v1 + +import ( + "sync" + "time" +) + +type failedAttempt struct { + attempt time.Time + ID int +} + +var failedAttempts []failedAttempt +var failedAttemptsMutex = new(sync.RWMutex) + +// removeUseless removes the expired attempts in failedAttempts +func removeUseless() { + for { + failedAttemptsMutex.RLock() + var localCopy = make([]failedAttempt, len(failedAttempts)) + copy(localCopy, failedAttempts) + failedAttemptsMutex.RUnlock() + var newStartFrom int + for k, v := range localCopy { + if time.Since(v.attempt) > time.Minute*10 { + newStartFrom = k + 1 + } else { + break + } + } + copySl := localCopy[newStartFrom:] + failedAttemptsMutex.Lock() + failedAttempts = make([]failedAttempt, len(copySl)) + for i, v := range copySl { + failedAttempts[i] = v + } + failedAttemptsMutex.Unlock() + time.Sleep(time.Minute * 10) + } +} + +func addFailedAttempt(uid int) { + failedAttemptsMutex.Lock() + failedAttempts = append(failedAttempts, failedAttempt{ + attempt: time.Now(), + ID: uid, + }) + failedAttemptsMutex.Unlock() +} + +func nFailedAttempts(uid int) int { + var count int + failedAttemptsMutex.RLock() + for _, i := range failedAttempts { + if i.ID == uid && time.Since(i.attempt) < time.Minute*10 { + count++ + } + } + failedAttemptsMutex.RUnlock() + return count +} diff --git a/app/v1/meta.go b/app/v1/meta.go index 5a3c86a..5d9a9c9 100644 --- a/app/v1/meta.go +++ b/app/v1/meta.go @@ -50,7 +50,7 @@ func MetaKillGET(md common.MethodData) (r common.Response) { return } -var upSince time.Time +var upSince = time.Now() // MetaUpSinceGET retrieves the moment the API application was started. // Mainly used to get if the API was restarted. diff --git a/app/v1/token.go b/app/v1/token.go index 2d2d277..1f9cf1a 100644 --- a/app/v1/token.go +++ b/app/v1/token.go @@ -77,6 +77,12 @@ func TokenNewPOST(md common.MethodData) (r common.Response) { return } + if nFailedAttempts(ret.ID) > 20 { + r.Code = 429 + r.Message = "You've made too many login attempts. Try again later." + return + } + if pwVersion == 1 { r.Code = 418 // Teapots! r.Message = "That user still has a password in version 1. Unfortunately, in order for the API to check for the password to be OK, the user has to first log in through the website." @@ -84,6 +90,7 @@ func TokenNewPOST(md common.MethodData) (r common.Response) { } if err := bcrypt.CompareHashAndPassword([]byte(pw), []byte(fmt.Sprintf("%x", md5.Sum([]byte(data.Password))))); err != nil { if err == bcrypt.ErrMismatchedHashAndPassword { + go addFailedAttempt(ret.ID) r.Code = 403 r.Message = "That password doesn't match!" return