summaryrefslogtreecommitdiff
path: root/password_login.go
diff options
context:
space:
mode:
authorT <t@tjp.lol>2025-06-26 11:42:17 -0600
committerT <t@tjp.lol>2025-07-01 17:50:49 -0600
commit639ad6a02cbb4b713434671ec09f309aa5410921 (patch)
tree7dde9cce8136636d11f2f7c961072984cfc705e7 /password_login.go
Create authentic_kate: user authentication for go HTTP applications
Diffstat (limited to 'password_login.go')
-rw-r--r--password_login.go105
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)
+ })
+}