100 lines
2.6 KiB
Go
100 lines
2.6 KiB
Go
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)
|
|
}
|
|
}
|