123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167 |
- package explogin
- import (
- "errors"
- "math"
- "net"
- "net/http"
- "strings"
- "sync"
- "time"
- )
- /*
- Explogin.go
- Package to handle expotential login time
- so as to prevent someone from brute forcing your password
- Author: tobychui
- */
- type UserLoginEntry struct {
- Username string //Username of account
- TargetIP string //Request IP address
- PreviousTryTimestamp int64 //Previous failed attempt timestamp
- NextAllowedTimestamp int64 //Next allowed login timestamp
- RetryCount int //Retry count total before success login
- }
- type ExpLoginHandler struct {
- LoginRecord *sync.Map //Sync map to store UserLoginEntry, username+ip as key
- BaseDelay int //Base delay exponent
- DelayCeiling int //Max delay time
- }
- //Create a new exponential login handler object
- func NewExponentialLoginHandler(baseDelay int, ceiling int) *ExpLoginHandler {
- recordMap := sync.Map{}
- return &ExpLoginHandler{
- LoginRecord: &recordMap,
- BaseDelay: baseDelay,
- DelayCeiling: ceiling,
- }
- }
- //Check allow access now, if false return how many seconds till next retry
- func (e *ExpLoginHandler) AllowImmediateAccess(username string, r *http.Request) (bool, int64) {
- userip, err := getIpFromRequest(r)
- if err != nil {
- //No ip information. Use 0.0.0.0
- userip = "0.0.0.0"
- }
- //Get the login entry from sync map
- key := username + "/" + userip
- val, ok := e.LoginRecord.Load(key)
- if !ok {
- //No record found for this user. Allow immediate access
- return true, 0
- }
- //Record exists. Check his retry count and target
- targerRecord := val.(*UserLoginEntry)
- if targerRecord.NextAllowedTimestamp > time.Now().Unix() {
- //Return next login request time left in seconds
- return false, targerRecord.NextAllowedTimestamp - time.Now().Unix()
- }
- //Ok to login now
- return true, 0
- }
- //Add a user retry count after failed login
- func (e *ExpLoginHandler) AddUserRetrycount(username string, r *http.Request) {
- userip, err := getIpFromRequest(r)
- if err != nil {
- //No ip information. Use 0.0.0.0
- userip = "0.0.0.0"
- }
- key := username + "/" + userip
- val, ok := e.LoginRecord.Load(key)
- if !ok {
- //Create an entry for the retry
- thisUserNewRecord := UserLoginEntry{
- Username: username,
- TargetIP: userip,
- PreviousTryTimestamp: time.Now().Unix(),
- NextAllowedTimestamp: time.Now().Unix() + e.getDelayTimeFromRetryCount(1),
- RetryCount: 1,
- }
- e.LoginRecord.Store(key, &thisUserNewRecord)
- } else {
- //Add to the value in the structure
- matchingLoginEntry := val.(*UserLoginEntry)
- matchingLoginEntry.RetryCount++
- matchingLoginEntry.PreviousTryTimestamp = time.Now().Unix()
- matchingLoginEntry.NextAllowedTimestamp = time.Now().Unix() + e.getDelayTimeFromRetryCount(matchingLoginEntry.RetryCount)
- //Store it back to the map
- e.LoginRecord.Store(key, matchingLoginEntry)
- }
- }
- //Reset a user retry count after successful login
- func (e *ExpLoginHandler) ResetUserRetryCount(username string, r *http.Request) {
- userip, err := getIpFromRequest(r)
- if err != nil {
- //No ip information. Use 0.0.0.0
- userip = "0.0.0.0"
- }
- key := username + "/" + userip
- e.LoginRecord.Delete(key)
- }
- //Reset all Login exponential record
- func (e *ExpLoginHandler) ResetAllUserRetryCounter() {
- e.LoginRecord.Range(func(key interface{}, value interface{}) bool {
- e.LoginRecord.Delete(key)
- return true
- })
- }
- //Get the next delay time
- func (e *ExpLoginHandler) getDelayTimeFromRetryCount(retryCount int) int64 {
- delaySecs := int64(math.Floor((math.Pow(2, float64(retryCount)) - 1) * 0.5))
- if delaySecs > int64(e.DelayCeiling)-int64(e.BaseDelay) {
- delaySecs = int64(e.DelayCeiling) - int64(e.BaseDelay)
- }
- return int64(e.BaseDelay) + delaySecs
- }
- /*
- Helper functions
- */
- func getIpFromRequest(r *http.Request) (string, error) {
- ip := r.Header.Get("X-REAL-IP")
- netIP := net.ParseIP(ip)
- if netIP != nil {
- return ip, nil
- }
- ips := r.Header.Get("X-FORWARDED-FOR")
- splitIps := strings.Split(ips, ",")
- for _, ip := range splitIps {
- netIP := net.ParseIP(ip)
- if netIP != nil {
- return ip, nil
- }
- }
- ip, _, err := net.SplitHostPort(r.RemoteAddr)
- if err != nil {
- return "", err
- }
- netIP = net.ParseIP(ip)
- if netIP != nil {
- return ip, nil
- }
- return "", errors.New("No IP information found")
- }
|