Prechádzať zdrojové kódy

Added exponential login time

Toby Chui 3 rokov pred
rodič
commit
ff08a1e558
5 zmenil súbory, kde vykonal 158 pridanie a 16 odobranie
  1. 2 1
      auth.go
  2. 26 0
      mod/auth/auth.go
  3. 127 11
      mod/auth/explogin/explogin.go
  4. 2 3
      notification.go
  5. 1 1
      web/login.system

+ 2 - 1
auth.go

@@ -52,7 +52,6 @@ func AuthInit() {
 	http.HandleFunc("/api/auth/login", authAgent.HandleAutologinTokenLogin)
 
 	authAgent.LoadAutologinTokenFromDB()
-
 }
 
 func AuthSettingsInit() {
@@ -105,4 +104,6 @@ func AuthSettingsInit() {
 	adminRouter.HandleFunc("/system/auth/blacklist/ban", authAgent.BlacklistManager.HandleAddBannedIP)
 	adminRouter.HandleFunc("/system/auth/blacklist/unban", authAgent.BlacklistManager.HandleRemoveBannedIP)
 
+	//Register nightly task for clearup all user retry counter
+	nightlyManager.RegisterNightlyTask(authAgent.ExpDelayHandler.ResetAllUserRetryCounter)
 }

+ 26 - 0
mod/auth/auth.go

@@ -23,6 +23,7 @@ import (
 	"crypto/sha512"
 	"errors"
 	"net/http"
+	"strconv"
 	"strings"
 	"sync"
 
@@ -35,6 +36,7 @@ import (
 	"imuslab.com/arozos/mod/auth/accesscontrol/blacklist"
 	"imuslab.com/arozos/mod/auth/accesscontrol/whitelist"
 	"imuslab.com/arozos/mod/auth/authlogger"
+	"imuslab.com/arozos/mod/auth/explogin"
 	db "imuslab.com/arozos/mod/database"
 	"imuslab.com/arozos/mod/network"
 )
@@ -56,6 +58,9 @@ type AuthAgent struct {
 	AllowAutoLogin  bool
 	autoLoginTokens []*AutoLoginToken
 
+	//Exponential Delay Retry Handler
+	ExpDelayHandler *explogin.ExpLoginHandler
+
 	//IPLists manager
 	WhitelistManager *whitelist.WhiteList
 	BlacklistManager *blacklist.BlackList
@@ -85,6 +90,9 @@ func NewAuthenticationAgent(sessionName string, key []byte, sysdb *db.Database,
 	ticker := time.NewTicker(300 * time.Second)
 	done := make(chan bool)
 
+	//Create a exponential login delay handler
+	expLoginHandler := explogin.NewExponentialLoginHandler(2, 10800)
+
 	//Create a new whitelist manager
 	thisWhitelistManager := whitelist.NewWhitelistManager(sysdb)
 
@@ -115,6 +123,7 @@ func NewAuthenticationAgent(sessionName string, key []byte, sysdb *db.Database,
 		//Blacklist management
 		WhitelistManager: thisWhitelistManager,
 		BlacklistManager: thisBlacklistManager,
+		ExpDelayHandler:  expLoginHandler,
 		Logger:           newLogger,
 	}
 
@@ -184,6 +193,15 @@ func (a *AuthAgent) HandleLogin(w http.ResponseWriter, r *http.Request) {
 		rememberme = true
 	}
 
+	//Check Exponential Login Handler
+	ok, nextRetryIn := a.ExpDelayHandler.AllowImmediateAccess(username, r)
+	if !ok {
+		//Too many request! (maybe the account is under brute force attack?)
+		a.ExpDelayHandler.AddUserRetrycount(username, r)
+		sendErrorResponse(w, "Too many request! Next retry in "+strconv.Itoa(int(nextRetryIn))+" seconds")
+		return
+	}
+
 	//Check the database and see if this user is in the database
 	passwordCorrect, rejectionReason := a.ValidateUsernameAndPasswordWithReason(username, password)
 	//The database contain this user information. Check its password if it is correct
@@ -195,8 +213,13 @@ func (a *AuthAgent) HandleLogin(w http.ResponseWriter, r *http.Request) {
 			sendErrorResponse(w, reasons.Error())
 			return
 		}
+
 		// Set user as authenticated
 		a.LoginUserByRequest(w, r, username, rememberme)
+
+		//Reset user retry count if any
+		a.ExpDelayHandler.ResetUserRetryCount(username, r)
+
 		//Print the login message to console
 		log.Println(username + " logged in.")
 		a.Logger.LogAuth(r, true)
@@ -204,6 +227,9 @@ func (a *AuthAgent) HandleLogin(w http.ResponseWriter, r *http.Request) {
 	} else {
 		//Password incorrect
 		log.Println(username + " login request rejected: " + rejectionReason)
+
+		//Add to retry count
+		a.ExpDelayHandler.AddUserRetrycount(username, r)
 		sendErrorResponse(w, rejectionReason)
 		a.Logger.LogAuth(r, false)
 		return

+ 127 - 11
mod/auth/explogin/explogin.go

@@ -1,8 +1,13 @@
 package explogin
 
 import (
+	"errors"
 	"math"
+	"net"
+	"net/http"
+	"strings"
 	"sync"
+	"time"
 )
 
 /*
@@ -14,10 +19,11 @@ import (
 */
 
 type UserLoginEntry struct {
-	Username   string
-	TargetIP   string
-	Timestamp  int64
-	RetryCount int64
+	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 {
@@ -27,25 +33,135 @@ type ExpLoginHandler struct {
 }
 
 //Create a new exponential login handler object
-func NewExponentialLoginHandler(baseDelay int) *ExpLoginHandler {
+func NewExponentialLoginHandler(baseDelay int, ceiling int) *ExpLoginHandler {
 	recordMap := sync.Map{}
 
 	return &ExpLoginHandler{
-		LoginRecord: &recordMap,
-		BaseDelay:   baseDelay,
+		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, ip string) (bool, int64) {
+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) {
-		delaySecs = int64(e.DelayCeiling)
+	if delaySecs > int64(e.DelayCeiling)-int64(e.BaseDelay) {
+		delaySecs = int64(e.DelayCeiling) - int64(e.BaseDelay)
 	}
 
-	return delaySecs
+	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")
 }

+ 2 - 3
notification.go

@@ -1,7 +1,6 @@
 package main
 
 import (
-	"log"
 	"strconv"
 	"time"
 
@@ -29,12 +28,12 @@ func notificationInit() {
 		}
 	}
 
-	log.Println(userEmailmap)
+	//log.Println(userEmailmap)
 
 	smtpAgent := smtpn.Agent{
 		Hostname:           *host_name,
 		SMTPSender:         "[email protected]",
-		SMTPPassword:       "noreplypassword",
+		SMTPPassword:       "",
 		SMTPDomain:         "mail.gandi.net",
 		SMTPPort:           587,
 		UsernameToEmailMap: userEmailmap,

+ 1 - 1
web/login.system

@@ -272,7 +272,7 @@
                 if (data.error !== undefined){
                     //Something went wrong during the login
                     $("#errmsg").text(data.error);
-                    $("#errmsg").parent().slideDown('fast').delay(5000).slideUp('fast');
+                    $("#errmsg").parent().stop().finish().slideDown('fast').delay(5000).slideUp('fast');
                 }else if(data.redirect !== undefined){
                     //LDAP Related Code
                     window.location.href = data.redirect;