package internal import ( "net/http" "time" "github.com/julienschmidt/httprouter" log "github.com/sirupsen/logrus" "git.mills.io/prologic/spyda/internal/session" ) // LoginHandler ... func (s *Server) LoginHandler() httprouter.Handle { // #239: Throttle failed login attempts and lock user account. failures := NewTTLCache(5 * time.Minute) return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { ctx := NewContext(s.config, s.db, r) if r.Method == "GET" { s.render("login", w, ctx) return } username := NormalizeUsername(r.FormValue("username")) password := r.FormValue("password") rememberme := r.FormValue("rememberme") == "on" // Error: no username or password provided if username == "" || password == "" { log.Warn("no username or password provided") http.Redirect(w, r, "/login", http.StatusFound) return } // Lookup user user, err := s.db.GetUser(username) if err != nil { ctx.Error = true ctx.Message = "Invalid username! Hint: Register an account?" s.render("error", w, ctx) return } // #239: Throttle failed login attempts and lock user account. if failures.Get(user.Username) > MaxFailedLogins { ctx.Error = true ctx.Message = "Too many failed login attempts. Account temporarily locked! Please try again later." s.render("error", w, ctx) return } // Validate cleartext password against KDF hash err = s.pm.CheckPassword(user.Password, password) if err != nil { // #239: Throttle failed login attempts and lock user account. failed := failures.Inc(user.Username) time.Sleep(time.Duration(IntPow(2, failed)) * time.Second) ctx.Error = true ctx.Message = "Invalid password! Hint: Reset your password?" s.render("error", w, ctx) return } // #239: Throttle failed login attempts and lock user account. failures.Reset(user.Username) // Login successful log.Infof("login successful: %s", username) // Lookup session sess := r.Context().Value(session.SessionKey) if sess == nil { log.Warn("no session found") http.Redirect(w, r, "/login", http.StatusFound) return } // Authorize session _ = sess.(*session.Session).Set("username", username) // Persist session? if rememberme { _ = sess.(*session.Session).Set("persist", "1") } http.Redirect(w, r, RedirectRefererURL(r, s.config, "/"), http.StatusFound) } } // LogoutHandler ... func (s *Server) LogoutHandler() httprouter.Handle { return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) { s.sm.Delete(w, r) http.Redirect(w, r, "/", http.StatusFound) } }