diff --git a/handlers/account.go b/handlers/account.go index 32d3933..eb9a49f 100644 --- a/handlers/account.go +++ b/handlers/account.go @@ -2,6 +2,7 @@ package handlers import ( "html/template" + "log" "net/http" "synlotto-website/helpers" "synlotto-website/models" @@ -26,7 +27,11 @@ func Login(w http.ResponseWriter, r *http.Request) { context := helpers.TemplateContext(w, r) context["csrfField"] = csrf.TemplateField(r) - tmpl.ExecuteTemplate(w, "layout", context) + err := tmpl.ExecuteTemplate(w, "layout", context) + if err != nil { + log.Println("โ Template render error:", err) + http.Error(w, "Error rendering login page", http.StatusInternalServerError) + } return } @@ -40,34 +45,55 @@ func Login(w http.ResponseWriter, r *http.Request) { } session, _ := helpers.GetSession(w, r) - session.Options.MaxAge = -1 - session.Save(r, w) - remember := r.FormValue("remember") == "on" - - newSession, _ := helpers.GetSession(w, r) - newSession.Values["user_id"] = user.Id - newSession.Values["last_activity"] = time.Now() - - if remember { - newSession.Options.MaxAge = 60 * 60 * 24 * 30 // 30 days - } else { - newSession.Options.MaxAge = 0 + for k := range session.Values { + delete(session.Values, k) } - newSession.Save(r, w) + session.Values["user_id"] = user.Id + session.Values["last_activity"] = time.Now() + + remember := r.FormValue("remember") == "on" + if remember { + session.Options.MaxAge = 60 * 60 * 24 * 30 + } else { + session.Options.MaxAge = 0 + } + + err := session.Save(r, w) + if err != nil { + log.Println("โ Failed to save session:", err) + } else { + log.Printf("โ Login saved: user_id=%d, maxAge=%d", user.Id, session.Options.MaxAge) + for _, c := range r.Cookies() { + log.Printf("๐ช Cookie after login: %s = %s", c.Name, c.Value) + } + } + + if user == nil || !helpers.CheckPasswordHash(user.PasswordHash, password) { + models.LogLoginAttempt(username, false) + http.Error(w, "Invalid credentials", http.StatusUnauthorized) + return + } + models.LogLoginAttempt(username, true) http.Redirect(w, r, "/", http.StatusSeeOther) } func Logout(w http.ResponseWriter, r *http.Request) { session, _ := helpers.GetSession(w, r) - session.Options.MaxAge = -1 - session.Save(r, w) - newSession, _ := helpers.GetSession(w, r) - newSession.Values["flash"] = "Youโve been logged out" - newSession.Save(r, w) + for k := range session.Values { + delete(session.Values, k) + } + + session.Values["flash"] = "You've been logged out." + session.Options.MaxAge = 5 + + err := session.Save(r, w) + if err != nil { + log.Println("โ Logout session save failed:", err) + } http.Redirect(w, r, "/login", http.StatusSeeOther) } diff --git a/helpers/session.go b/helpers/session.go index 5c86c67..3429c55 100644 --- a/helpers/session.go +++ b/helpers/session.go @@ -1,27 +1,34 @@ package helpers import ( + "encoding/gob" "net/http" "time" "github.com/gorilla/sessions" ) -var store = sessions.NewCookieStore([]byte("super-secret-key")) // //ToDo make key global +var authKey = []byte("12345678901234567890123456789012") // ToDo: Make env var +var encryptKey = []byte("abcdefghijklmnopqrstuvwx12345678") // ToDo: Make env var +var sessionName = "synlotto-session" +var store = sessions.NewCookieStore(authKey, encryptKey) + const SessionTimeout = 30 * time.Minute func init() { + gob.Register(time.Time{}) + store.Options = &sessions.Options{ Path: "/", MaxAge: 86400 * 1, HttpOnly: true, - Secure: true, - SameSite: http.SameSiteStrictMode, + Secure: false, // TODO: make env-configurable + SameSite: http.SameSiteLaxMode, } } func GetSession(w http.ResponseWriter, r *http.Request) (*sessions.Session, error) { - return store.Get(r, "session-name") + return store.Get(r, sessionName) } func IsSessionExpired(session *sessions.Session) bool { diff --git a/helpers/template.go b/helpers/template.go index 7d92a3a..a7d6910 100644 --- a/helpers/template.go +++ b/helpers/template.go @@ -2,6 +2,7 @@ package helpers import ( "html/template" + "log" "net/http" "synlotto-website/models" ) @@ -37,10 +38,18 @@ func TemplateContext(w http.ResponseWriter, r *http.Request) map[string]interfac } var currentUser *models.User - if userId, ok := session.Values["user_id"].(int); ok { - currentUser = models.GetUserByID(userId) + + switch v := session.Values["user_id"].(type) { + case int: + currentUser = models.GetUserByID(v) + case int64: + currentUser = models.GetUserByID(int(v)) + default: + currentUser = nil } + log.Printf("๐งช TemplateContext user: %#v", currentUser) + return map[string]interface{}{ "Flash": flash, "User": currentUser, diff --git a/models/user.go b/models/user.go index 1744e48..02a67ae 100644 --- a/models/user.go +++ b/models/user.go @@ -3,6 +3,7 @@ package models import ( "database/sql" "log" + "time" ) type User struct { @@ -47,5 +48,21 @@ func GetUserByID(id int) *User { } return nil } + log.Printf("๐ฆ Looking up user ID %d", id) return &user } + +func LogLoginAttempt(username string, success bool) { + _, err := db.Exec("INSERT INTO login_audit (username, success, timestamp) VALUES (?, ?, ?)", + username, boolToInt(success), time.Now().Format(time.RFC3339)) + if err != nil { + log.Println("โ Failed to log login:", err) + } +} + +func boolToInt(b bool) int { + if b { + return 1 + } + return 0 +} diff --git a/storage/db.go b/storage/db.go index 35e262c..bc34a07 100644 --- a/storage/db.go +++ b/storage/db.go @@ -62,5 +62,17 @@ func InitDB(filepath string) *sql.DB { log.Fatal("โ Failed to create Users table:", err) } + createAuditLogTable := ` + CREATE TABLE IF NOT EXISTS auditlog ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT, + success INTEGER, + timestamp TEXT + );` + + if _, err := db.Exec(createAuditLogTable); err != nil { + log.Fatal("โ Failed to create Users table:", err) + } + return db } diff --git a/templates/account/login.html b/templates/account/login.html index c918d5f..74279b0 100644 --- a/templates/account/login.html +++ b/templates/account/login.html @@ -1,7 +1,4 @@ {{ define "content" }} -{{ if .Flash }} -
{{ .Flash }}
-{{ end }}