diff options
author | T <t@tjp.lol> | 2025-06-26 11:42:17 -0600 |
---|---|---|
committer | T <t@tjp.lol> | 2025-07-01 17:50:49 -0600 |
commit | 639ad6a02cbb4b713434671ec09f309aa5410921 (patch) | |
tree | 7dde9cce8136636d11f2f7c961072984cfc705e7 /password_login.go |
Create authentic_kate: user authentication for go HTTP applications
Diffstat (limited to 'password_login.go')
-rw-r--r-- | password_login.go | 105 |
1 files changed, 105 insertions, 0 deletions
diff --git a/password_login.go b/password_login.go new file mode 100644 index 0000000..837d9c9 --- /dev/null +++ b/password_login.go @@ -0,0 +1,105 @@ +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) + }) +} |