explogin.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. package explogin
  2. import (
  3. "errors"
  4. "math"
  5. "net"
  6. "net/http"
  7. "strings"
  8. "sync"
  9. "time"
  10. )
  11. /*
  12. Explogin.go
  13. Package to handle expotential login time
  14. so as to prevent someone from brute forcing your password
  15. Author: tobychui
  16. */
  17. type UserLoginEntry struct {
  18. Username string //Username of account
  19. TargetIP string //Request IP address
  20. PreviousTryTimestamp int64 //Previous failed attempt timestamp
  21. NextAllowedTimestamp int64 //Next allowed login timestamp
  22. RetryCount int //Retry count total before success login
  23. }
  24. type ExpLoginHandler struct {
  25. LoginRecord *sync.Map //Sync map to store UserLoginEntry, username+ip as key
  26. BaseDelay int //Base delay exponent
  27. DelayCeiling int //Max delay time
  28. }
  29. //Create a new exponential login handler object
  30. func NewExponentialLoginHandler(baseDelay int, ceiling int) *ExpLoginHandler {
  31. recordMap := sync.Map{}
  32. return &ExpLoginHandler{
  33. LoginRecord: &recordMap,
  34. BaseDelay: baseDelay,
  35. DelayCeiling: ceiling,
  36. }
  37. }
  38. //Check allow access now, if false return how many seconds till next retry
  39. func (e *ExpLoginHandler) AllowImmediateAccess(username string, r *http.Request) (bool, int64) {
  40. userip, err := getIpFromRequest(r)
  41. if err != nil {
  42. //No ip information. Use 0.0.0.0
  43. userip = "0.0.0.0"
  44. }
  45. //Get the login entry from sync map
  46. key := username + "/" + userip
  47. val, ok := e.LoginRecord.Load(key)
  48. if !ok {
  49. //No record found for this user. Allow immediate access
  50. return true, 0
  51. }
  52. //Record exists. Check his retry count and target
  53. targerRecord := val.(*UserLoginEntry)
  54. if targerRecord.NextAllowedTimestamp > time.Now().Unix() {
  55. //Return next login request time left in seconds
  56. return false, targerRecord.NextAllowedTimestamp - time.Now().Unix()
  57. }
  58. //Ok to login now
  59. return true, 0
  60. }
  61. //Add a user retry count after failed login
  62. func (e *ExpLoginHandler) AddUserRetrycount(username string, r *http.Request) {
  63. userip, err := getIpFromRequest(r)
  64. if err != nil {
  65. //No ip information. Use 0.0.0.0
  66. userip = "0.0.0.0"
  67. }
  68. key := username + "/" + userip
  69. val, ok := e.LoginRecord.Load(key)
  70. if !ok {
  71. //Create an entry for the retry
  72. thisUserNewRecord := UserLoginEntry{
  73. Username: username,
  74. TargetIP: userip,
  75. PreviousTryTimestamp: time.Now().Unix(),
  76. NextAllowedTimestamp: time.Now().Unix() + e.getDelayTimeFromRetryCount(1),
  77. RetryCount: 1,
  78. }
  79. e.LoginRecord.Store(key, &thisUserNewRecord)
  80. } else {
  81. //Add to the value in the structure
  82. matchingLoginEntry := val.(*UserLoginEntry)
  83. matchingLoginEntry.RetryCount++
  84. matchingLoginEntry.PreviousTryTimestamp = time.Now().Unix()
  85. matchingLoginEntry.NextAllowedTimestamp = time.Now().Unix() + e.getDelayTimeFromRetryCount(matchingLoginEntry.RetryCount)
  86. //Store it back to the map
  87. e.LoginRecord.Store(key, matchingLoginEntry)
  88. }
  89. }
  90. //Reset a user retry count after successful login
  91. func (e *ExpLoginHandler) ResetUserRetryCount(username string, r *http.Request) {
  92. userip, err := getIpFromRequest(r)
  93. if err != nil {
  94. //No ip information. Use 0.0.0.0
  95. userip = "0.0.0.0"
  96. }
  97. key := username + "/" + userip
  98. e.LoginRecord.Delete(key)
  99. }
  100. //Reset all Login exponential record
  101. func (e *ExpLoginHandler) ResetAllUserRetryCounter() {
  102. e.LoginRecord.Range(func(key interface{}, value interface{}) bool {
  103. e.LoginRecord.Delete(key)
  104. return true
  105. })
  106. }
  107. //Get the next delay time
  108. func (e *ExpLoginHandler) getDelayTimeFromRetryCount(retryCount int) int64 {
  109. delaySecs := int64(math.Floor((math.Pow(2, float64(retryCount)) - 1) * 0.5))
  110. if delaySecs > int64(e.DelayCeiling)-int64(e.BaseDelay) {
  111. delaySecs = int64(e.DelayCeiling) - int64(e.BaseDelay)
  112. }
  113. return int64(e.BaseDelay) + delaySecs
  114. }
  115. /*
  116. Helper functions
  117. */
  118. func getIpFromRequest(r *http.Request) (string, error) {
  119. ip := r.Header.Get("X-REAL-IP")
  120. netIP := net.ParseIP(ip)
  121. if netIP != nil {
  122. return ip, nil
  123. }
  124. ips := r.Header.Get("X-FORWARDED-FOR")
  125. splitIps := strings.Split(ips, ",")
  126. for _, ip := range splitIps {
  127. netIP := net.ParseIP(ip)
  128. if netIP != nil {
  129. return ip, nil
  130. }
  131. }
  132. ip, _, err := net.SplitHostPort(r.RemoteAddr)
  133. if err != nil {
  134. return "", err
  135. }
  136. netIP = net.ParseIP(ip)
  137. if netIP != nil {
  138. return ip, nil
  139. }
  140. return "", errors.New("No IP information found")
  141. }