All checks were successful
continuous-integration/drone/push Build is passing
Perform a dummy LDAP bind when SearchDN fails so that the response time is indistinguishable between unknown users and wrong passwords. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
97 lines
3.5 KiB
Go
97 lines
3.5 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"log"
|
|
"net/http"
|
|
"unicode"
|
|
)
|
|
|
|
func checkPasswdConstraint(password string) error {
|
|
if len(password) < 12 {
|
|
return errors.New("too short, please choose a password at least 12 characters long")
|
|
}
|
|
|
|
var hasUpper, hasLower, hasDigit bool
|
|
for _, r := range password {
|
|
switch {
|
|
case unicode.IsUpper(r):
|
|
hasUpper = true
|
|
case unicode.IsLower(r):
|
|
hasLower = true
|
|
case unicode.IsDigit(r):
|
|
hasDigit = true
|
|
}
|
|
}
|
|
if !hasUpper || !hasLower || !hasDigit {
|
|
return errors.New("password must contain at least one uppercase letter, one lowercase letter, and one digit")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func changePassword(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method == "POST" && !changeLimiter.Allow(remoteIP(r)) {
|
|
csrfToken, _ := setCSRFToken(w)
|
|
displayTmplError(w, http.StatusTooManyRequests, "change.html", map[string]any{"error": "Too many requests. Please try again later.", "csrf_token": csrfToken})
|
|
return
|
|
}
|
|
|
|
if r.Method != "POST" {
|
|
csrfToken, err := setCSRFToken(w)
|
|
if err != nil {
|
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
displayTmpl(w, "change.html", map[string]any{"csrf_token": csrfToken})
|
|
return
|
|
}
|
|
|
|
if !validateCSRF(r) {
|
|
csrfToken, _ := setCSRFToken(w)
|
|
displayTmplError(w, http.StatusForbidden, "change.html", map[string]any{"error": "Invalid or missing CSRF token. Please try again.", "csrf_token": csrfToken})
|
|
return
|
|
}
|
|
|
|
if !validateAltcha(r) {
|
|
csrfToken, _ := setCSRFToken(w)
|
|
displayTmplError(w, http.StatusForbidden, "change.html", map[string]any{"error": "Invalid or missing altcha response. Please try again.", "csrf_token": csrfToken})
|
|
return
|
|
}
|
|
|
|
renderError := func(status int, msg string) {
|
|
csrfToken, _ := setCSRFToken(w)
|
|
displayTmplError(w, status, "change.html", map[string]any{"error": msg, "csrf_token": csrfToken})
|
|
}
|
|
|
|
// Check the two new passwords are identical
|
|
if r.PostFormValue("newpassword") != r.PostFormValue("new2password") {
|
|
renderError(http.StatusNotAcceptable, "New passwords are not identical. Please retry.")
|
|
} else if len(r.PostFormValue("login")) == 0 {
|
|
renderError(http.StatusNotAcceptable, "Please provide a valid login")
|
|
} else if err := checkPasswdConstraint(r.PostFormValue("newpassword")); err != nil {
|
|
renderError(http.StatusNotAcceptable, "The password you chose doesn't respect all constraints: "+err.Error())
|
|
} else {
|
|
conn, err := myLDAP.Connect()
|
|
if err != nil || conn == nil {
|
|
log.Println(err)
|
|
renderError(http.StatusInternalServerError, "Unable to process your request. Please try again later.")
|
|
} else if err := conn.ServiceBind(); err != nil {
|
|
log.Println(err)
|
|
renderError(http.StatusInternalServerError, "Unable to process your request. Please try again later.")
|
|
} else if dn, err := conn.SearchDN(r.PostFormValue("login"), true); err != nil {
|
|
log.Println(err)
|
|
// User not found: perform a dummy bind to prevent username enumeration via timing.
|
|
conn.Bind("cn=dummy,"+myLDAP.BaseDN, r.PostFormValue("password"))
|
|
renderError(http.StatusUnauthorized, "Invalid login or password.")
|
|
} else if err := conn.Bind(dn, r.PostFormValue("password")); err != nil {
|
|
log.Println(err)
|
|
renderError(http.StatusUnauthorized, "Invalid login or password.")
|
|
} else if err := conn.ChangePassword(dn, r.PostFormValue("newpassword")); err != nil {
|
|
log.Println(err)
|
|
renderError(http.StatusInternalServerError, "Unable to process your request. Please try again later.")
|
|
} else {
|
|
displayMsg(w, "Password successfully changed!", http.StatusOK)
|
|
}
|
|
}
|
|
}
|