125 lines
3.3 KiB
Go
125 lines
3.3 KiB
Go
package main
|
|
|
|
import (
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/gorilla/sessions"
|
|
"github.com/pquerna/otp/totp"
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
var (
|
|
store = sessions.NewCookieStore([]byte("super-secret-key")) // Change this in production
|
|
appStartupTime time.Time
|
|
)
|
|
|
|
func initAuth() {
|
|
appStartupTime = time.Now()
|
|
}
|
|
|
|
func authMiddleware(next http.HandlerFunc) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
session, _ := store.Get(r, "session")
|
|
if auth, ok := session.Values["authenticated"].(bool); !ok || !auth {
|
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
|
return
|
|
}
|
|
|
|
// Validate session version - invalidate if app restarted or password changed
|
|
if sessionVersion, ok := session.Values["version"].(string); !ok || sessionVersion != getSessionVersion() {
|
|
session.Values["authenticated"] = false
|
|
session.Save(r, w)
|
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
|
return
|
|
}
|
|
|
|
// Check session startup time - invalidate if app was restarted
|
|
if sessionStartupTime, ok := session.Values["startup_time"].(int64); !ok || sessionStartupTime != appStartupTime.Unix() {
|
|
session.Values["authenticated"] = false
|
|
session.Save(r, w)
|
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
|
return
|
|
}
|
|
|
|
// Check session expiration
|
|
if sessionCreatedAt, ok := session.Values["created_at"].(int64); ok {
|
|
configMu.RLock()
|
|
timeoutMins := config.SessionTimeoutMins
|
|
configMu.RUnlock()
|
|
|
|
if time.Now().Unix()-sessionCreatedAt > int64(timeoutMins*60) {
|
|
session.Values["authenticated"] = false
|
|
session.Save(r, w)
|
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
|
return
|
|
}
|
|
}
|
|
|
|
next(w, r)
|
|
}
|
|
}
|
|
|
|
func loginHandler(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method == "POST" {
|
|
username := r.FormValue("username")
|
|
password := r.FormValue("password")
|
|
mfaCode := r.FormValue("mfa_code")
|
|
|
|
configMu.RLock()
|
|
defer configMu.RUnlock()
|
|
|
|
if username != config.Username || bcrypt.CompareHashAndPassword([]byte(config.HashedPass), []byte(password)) != nil {
|
|
renderTemplate(w, r, "login.html", struct {
|
|
Notification string
|
|
NotificationType string
|
|
}{
|
|
Notification: "Invalid username or password",
|
|
NotificationType: "error",
|
|
})
|
|
return
|
|
}
|
|
|
|
if config.MFASecret != "" {
|
|
valid := totp.Validate(mfaCode, config.MFASecret)
|
|
if !valid {
|
|
renderTemplate(w, r, "login.html", struct {
|
|
Notification string
|
|
NotificationType string
|
|
}{
|
|
Notification: "Invalid MFA code",
|
|
NotificationType: "error",
|
|
})
|
|
return
|
|
}
|
|
}
|
|
|
|
session, _ := store.Get(r, "session")
|
|
session.Values["authenticated"] = true
|
|
session.Values["version"] = getSessionVersion()
|
|
session.Values["startup_time"] = appStartupTime.Unix()
|
|
session.Values["created_at"] = time.Now().Unix()
|
|
session.Save(r, w)
|
|
|
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
|
return
|
|
}
|
|
|
|
renderTemplate(w, r, "login.html", nil)
|
|
}
|
|
|
|
func logoutHandler(w http.ResponseWriter, r *http.Request) {
|
|
session, _ := store.Get(r, "session")
|
|
session.Values["authenticated"] = false
|
|
session.Values["version"] = ""
|
|
session.Save(r, w)
|
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
|
}
|
|
|
|
func getSessionVersion() string {
|
|
configMu.RLock()
|
|
defer configMu.RUnlock()
|
|
// Create a version string from password hash - changes when password changes
|
|
return config.HashedPass
|
|
}
|