chldapasswd/reset.go
Pierre-Olivier Mercier 4a68d0700d fix(ldap): add Close() method and defer conn.Close() at all call sites
LDAP connections were never closed, leaking TCP connections on every
request. Also refactors change.go from chained else-if to early returns
for cleaner defer placement.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 14:40:40 +07:00

100 lines
2.9 KiB
Go

package main
import (
"log"
"net/http"
)
func resetPassword(w http.ResponseWriter, r *http.Request) {
if r.Method == "POST" && !resetLimiter.Allow(remoteIP(r)) {
http.Error(w, "Too many requests. Please try again later.", http.StatusTooManyRequests)
return
}
if len(r.URL.Query().Get("l")) == 0 || len(r.URL.Query().Get("t")) == 0 {
http.Redirect(w, r, "lost", http.StatusFound)
return
}
base := map[string]any{
"login": r.URL.Query().Get("l"),
"token": r.URL.Query().Get("t"),
}
if r.Method != "POST" {
if !peekResetToken(r.URL.Query().Get("t")) {
displayTmplError(w, http.StatusGone, "lost.html", map[string]any{
"error": "Token invalid or expired, please retry the lost password procedure. Tokens expire after 1 hour.",
})
return
}
csrfToken, err := setCSRFToken(w)
if err != nil {
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
base["csrf_token"] = csrfToken
displayTmpl(w, "reset.html", base)
return
}
renderError := func(status int, msg string) {
csrfToken, _ := setCSRFToken(w)
base["error"] = msg
base["csrf_token"] = csrfToken
displayTmplError(w, status, "reset.html", base)
}
if !validateCSRF(r) {
renderError(http.StatusForbidden, "Invalid or missing CSRF token. Please try again.")
return
}
if !validateAltcha(r) {
renderError(http.StatusForbidden, "Invalid or missing altcha response. Please try again.")
return
}
// Check the two new passwords are identical
if r.PostFormValue("newpassword") != r.PostFormValue("new2password") {
renderError(http.StatusNotAcceptable, "New passwords are not identical. Please retry.")
return
} else if err := checkPasswdConstraint(r.PostFormValue("newpassword")); err != nil {
renderError(http.StatusNotAcceptable, "The password you chose doesn't respect all constraints: "+err.Error())
return
}
// Validate and consume the token (single-use, server-side)
token := r.PostFormValue("token")
dn, ok := consumeResetToken(token)
if !ok {
renderError(http.StatusNotAcceptable, "Token invalid or expired, please retry the lost password procedure. Tokens expire after 1 hour.")
return
}
// Connect to the LDAP server
conn, err := myLDAP.Connect()
if err != nil || conn == nil {
log.Println(err)
renderError(http.StatusInternalServerError, "Unable to process your request. Please try again later.")
return
}
defer conn.Close()
// Bind as service to perform the password change
err = conn.ServiceBind()
if err != nil {
log.Println(err)
renderError(http.StatusInternalServerError, "Unable to process your request. Please try again later.")
return
}
// Replace the password by the new given
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.")
return
}
displayMsg(w, "Password successfully changed!", http.StatusOK)
}