Files
unifi-adblocker/auth.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
}