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) } } }