diff --git a/go.mod b/go.mod index 093ccad..952cba6 100644 --- a/go.mod +++ b/go.mod @@ -10,8 +10,9 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + golang.org/x/crypto v0.40.0 golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect - golang.org/x/sys v0.33.0 // indirect + golang.org/x/sys v0.34.0 // indirect modernc.org/libc v1.65.10 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect diff --git a/go.sum b/go.sum index 9c471fd..42626eb 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= @@ -8,16 +10,40 @@ github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdh github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM= +golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY= golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM= golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8= +golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= +golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= +golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= +golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= +modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s= +modernc.org/cc/v4 v4.26.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU= +modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE= +modernc.org/fileutil v1.3.3 h1:3qaU+7f7xxTUmvU1pJTZiDLAIoJVdUSSauJNHg9yXoA= +modernc.org/fileutil v1.3.3/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= modernc.org/libc v1.65.10 h1:ZwEk8+jhW7qBjHIT+wd0d9VjitRyQef9BnzlzGwMODc= modernc.org/libc v1.65.10/go.mod h1:StFvYpx7i/mXtBAfVOjaU0PWZOvIRoZSgXhrwXzr8Po= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= +modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= modernc.org/sqlite v1.38.0 h1:+4OrfPQ8pxHKuWG4md1JpR/EYAh3Md7TdejuuzE7EUI= modernc.org/sqlite v1.38.0/go.mod h1:1Bj+yES4SVvBZ4cBOpVZ6QgesMCKpJZDq0nxYzOpmNE= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/main.go b/main.go index 2258a50..dbeeace 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,9 @@ package main import ( + "crypto/rand" "database/sql" + "encoding/hex" "encoding/json" "fmt" "html/template" @@ -15,6 +17,7 @@ import ( "sync" "time" + "golang.org/x/crypto/bcrypt" _ "modernc.org/sqlite" // statt github.com/mattn/go-sqlite3 ) @@ -37,14 +40,15 @@ func Enabled(k string, def bool) bool { } var ( - username = GetENV("KT_USERNAME", "root") - password = GetENV("KT_PASSWORD", "root") - membername = GetENV("KT_MEMBER", "demo") - productive = Enabled("KT_PRODUCTIVE", false) - hasimpressum = Enabled("KT_HASIMPRESSUM", false) - impressum = GetENV("KT_IMPRESSUM", "") - orte = []string{} - schiffe = []string{ + username = GetENV("KT_USERNAME", "root") + password = GetENV("KT_PASSWORD", "root") + membername = GetENV("KT_MEMBER", "demo") + productive = Enabled("KT_PRODUCTIVE", false) + hasimpressum = Enabled("KT_HASIMPRESSUM", false) + impressum = GetENV("KT_IMPRESSUM", "") + hashedPassword = "" + orte = []string{} + schiffe = []string{ "", "100i", "125a", "135c", "Arrow", "Aurora CL", "Aurora ES", "Aurora LN", "Aurora LX", "Aurora MR", "Avenger Stalker", "Avenger Titan", "Avenger Titan Renegade", "Avenger Warlock", "Blade", "Buccaneer", "C1 Spirit", "C2 Hercules Starlifter", "M2 Hercules Starlifter", "A2 Hercules Starlifter", "C8 Pisces", "C8R Pisces Rescue", @@ -190,7 +194,40 @@ func reverse(s string) string { func isAuthenticated(r *http.Request) bool { cookie, err := r.Cookie("session") - return err == nil && cookie.Value == "authenticated" + if err != nil { + fmt.Println("Debug-1:", err) + return false + } + // Prüfen, ob der Token im sessionStore existiert + a, ok := sessionStore[cookie.Value] + fmt.Println(ok, a) + fmt.Println(sessionStore) + return ok +} + +var sessionStore = make(map[string]string) // token → username +var loginAttempts = make(map[string]int) +var loginLastAttempt = make(map[string]time.Time) +var loginBlockedUntil = make(map[string]time.Time) +var loginMutex sync.Mutex + +func hashPassword(pw string) string { + hash, _ := bcrypt.GenerateFromPassword([]byte(pw), bcrypt.DefaultCost) + return string(hash) +} + +func checkPasswordHash(pw, hash string) bool { + err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(pw)) + return err == nil +} + +func generateSessionToken() string { + b := make([]byte, 32) + _, err := rand.Read(b) + if err != nil { + return "" // handle error besser im echten Code + } + return hex.EncodeToString(b) } func main() { @@ -220,6 +257,8 @@ func main() { } } + hashedPassword = hashPassword(password) + var pois []POI if err := json.Unmarshal(data, &pois); err != nil { panic(err) @@ -246,34 +285,88 @@ func main() { } http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) { + ip := strings.Split(r.RemoteAddr, ":")[0] + + loginMutex.Lock() + blockUntil, blocked := loginBlockedUntil[ip] + if blocked && time.Now().Before(blockUntil) { + loginMutex.Unlock() + http.Error(w, "Zu viele Fehlversuche. Bitte versuch es später erneut.", http.StatusTooManyRequests) + return + } + loginMutex.Unlock() + if r.Method == http.MethodPost { r.ParseForm() user := r.FormValue("username") pass := r.FormValue("password") - if user == username && pass == password { + + if user == username && checkPasswordHash(pass, hashedPassword) { + token := generateSessionToken() + + // Speichere Session + sessionStore[token] = user + fmt.Println("Login", token) + + // Cookie setzen http.SetCookie(w, &http.Cookie{ - Name: "session", - Value: "authenticated", - Path: "/", + Name: "session", + Value: token, + Path: "/", + HttpOnly: true, + Secure: true, + SameSite: http.SameSiteLaxMode, }) + + // Erfolgreich -> Versuche zurücksetzen + loginMutex.Lock() + delete(loginAttempts, ip) + delete(loginLastAttempt, ip) + delete(loginBlockedUntil, ip) + loginMutex.Unlock() + http.Redirect(w, r, "/", http.StatusSeeOther) return } + + // Fehlversuch behandeln + loginMutex.Lock() + loginAttempts[ip]++ + loginLastAttempt[ip] = time.Now() + if loginAttempts[ip] >= 5 { + loginBlockedUntil[ip] = time.Now().Add(10 * time.Minute) + } + loginMutex.Unlock() + http.Error(w, "Login fehlgeschlagen", http.StatusUnauthorized) return } + // GET: Login-Formular w.Header().Set("Content-Type", "text/html") w.Write([]byte(loginForm)) }) http.HandleFunc("/logout", func(w http.ResponseWriter, r *http.Request) { - http.SetCookie(w, &http.Cookie{ - Name: "session", - Value: "", - Path: "/", - MaxAge: -1, - }) + cookie, err := r.Cookie("session") + if err == nil { + token := cookie.Value + fmt.Println("Logout", token) + // Token aus dem serverseitigen Store löschen + delete(sessionStore, token) + + // Cookie ungültig machen + http.SetCookie(w, &http.Cookie{ + Name: "session", + Value: "", + Path: "/", + MaxAge: -1, + HttpOnly: true, + Secure: true, + SameSite: http.SameSiteLaxMode, + }) + } + http.Redirect(w, r, "/", http.StatusSeeOther) }) @@ -387,6 +480,7 @@ func main() { cacheMutex.RUnlock() if validCache { + cachedData.LoggedIn = isAuthenticated(r) tmpl.Execute(w, cachedData) return } @@ -529,7 +623,6 @@ func main() { OffeneSumme: offeneSumme, Abteilungen: abteilungen, Monatsstatistik: monatsStat, - LoggedIn: isAuthenticated(r), Member: membername, HasImpressum: hasimpressum, Impressum: impressum,