package kate import "net/http" // PasswordLoginConfig configures the password login handler behavior. type PasswordLoginConfig[T any] struct { // UserData provides user data lookup and password hash extraction UserData PasswordUserDataStore[T] // Redirects configures post-authentication redirect behavior Redirects Redirects // UsernameField is the form field name for username UsernameField string // PasswordField is the form field name for password PasswordField string // LogError is called when the login handler encounters unexpected errors LogError func(error) } // PasswordUserDataStore provides user data lookup for password authentication. type PasswordUserDataStore[T any] interface { // Fetch retrieves user data by username. // // Returns the user data, whether the user was found, and any error. // If the user is not found, should return (zero value, false, nil). Fetch(username string) (T, bool, error) // GetPassHash extracts the password hash from user data. // // Returns the stored password hash for comparison with the provided password. GetPassHash(userData T) string } func (lc *PasswordLoginConfig[T]) setDefaults() { if lc.UsernameField == "" { lc.UsernameField = "username" } if lc.PasswordField == "" { lc.PasswordField = "password" } } func (lc PasswordLoginConfig[T]) logError(err error) { if lc.LogError != nil { lc.LogError(err) } } // PasswordLoginHandler returns an HTTP handler that processes password login form submissions. func (a Auth[T]) PasswordLoginHandler(config PasswordLoginConfig[T]) http.Handler { config.setDefaults() return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } if err := r.ParseForm(); err != nil { http.Error(w, "Invalid form data", http.StatusBadRequest) return } username := r.PostForm.Get(config.UsernameField) password := r.PostForm.Get(config.PasswordField) if username == "" || password == "" { http.Error(w, "Username and password required", http.StatusBadRequest) return } userData, ok, err := config.UserData.Fetch(username) if err != nil { config.logError(err) http.Error(w, "Error fetching user", http.StatusInternalServerError) return } if !ok { http.Error(w, "Authentication failed", http.StatusUnauthorized) return } match, err := ComparePassword(password, config.UserData.GetPassHash(userData)) if err != nil { config.logError(err) http.Error(w, "Authentication failed", http.StatusUnauthorized) return } if !match { http.Error(w, "Authentication failed", http.StatusUnauthorized) return } if err := a.Set(w, userData); err != nil { config.logError(err) http.Error(w, "Failed to set authentication", http.StatusInternalServerError) return } http.Redirect(w, r, config.Redirects.target(r), http.StatusSeeOther) }) }