Ask for the current password to change it

Changing the password now ends every other session, so whoever holds
one could shut the owner out of their own account with a new password.
The profile asks for the current password whenever the password
changes; without it, or with a wrong one, nothing is saved and the page
says why. Other profile edits don't need it, and the admin
reset-password script still works without it.

Attempts are rate limited like signing in, 10 in 3 minutes per address,
so the current password can't be guessed here instead.

From #371. The cached-response test it touches isn't in 1.5.2.

(cherry picked from commit 44a92ff6c6)
This commit is contained in:
Jeremy Daer committed 2026-10-19 12:17:27 -07:00
1 parent 58a25c6281
commit 417f087964
6 files changed
+133 -9

No files matched your search

+32 -4
View File
@@ -1,16 +1,23 @@
class Users::ProfilesController < ApplicationController
before_action :set_user
# Limited like signing in, so the current password can't be guessed here instead.
rate_limit to: 10, within: 3.minutes, only: :update, if: :changing_password?,
with: -> { redirect_to user_profile_url, alert: "Too many attempts. Try again in a few minutes." }
def show
@direct_memberships, @shared_memberships =
Current.user.memberships.with_ordered_room.partition { |m| m.room.direct? }
end
def update
@user.update user_params
start_new_session_for @user if @user.saved_change_to_password_digest?
if @user.update user_params
start_new_session_for @user if @user.saved_change_to_password_digest?
redirect_to user_profile_url, notice: update_notice
redirect_to user_profile_url, notice: update_notice
else
redirect_to user_profile_url, alert: update_alert
end
end
private
@@ -19,10 +26,31 @@ class Users::ProfilesController < ApplicationController
end
def user_params
params.require(:user).permit(:name, :avatar, :email_address, :password, :bio).compact
permitted = params.require(:user).permit(:name, :avatar, :email_address, :password, :bio).compact
if changing_password?
permitted.merge(password_challenge: params[:user][:password_challenge].to_s)
else
permitted
end
end
# As has_secure_password reads it: an empty password leaves the current one alone.
def changing_password?
!params.dig(:user, :password).to_s.empty?
end
def update_notice
params[:user][:avatar] ? "It may take up to 30 minutes to change everywhere." : "✓"
end
def update_alert
if !@user.errors.include?(:password_challenge)
@user.errors.full_messages.to_sentence
elsif params[:user][:password_challenge].blank?
"Enter your current password to change it."
else
"That isn't your current password."
end
end
end