1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
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)
})
}
|