Allow SSO sessions to set a password without the current one

Accounts provisioned by trusted-header SSO get a random password the user
never saw, so /change_password was unusable for them. When the trusted
header asserts the same email as the session user, waive the current
password check and hide the field, so native clients (Yattee) can be given
a password to log in with.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
NeskireDK 2026-08-12 21:02:26 +02:00
parent c644d73def
commit 5025d06c00
2 changed files with 14 additions and 3 deletions

View File

@ -23,6 +23,8 @@ module Invidious::Routes::Account
sid = sid.as(String)
csrf_token = generate_response(sid, {":change_password"}, HMAC_KEY)
sso_verified = Invidious::TrustedHeaderAuth.asserted_email(env) == user.email
templated "user/change_password"
end
@ -48,8 +50,13 @@ module Invidious::Routes::Account
return error_template(400, ex)
end
# An SSO session proves the identity through the trusted header already, so
# the current password is waived. Accounts provisioned by SSO were given a
# random password the user never saw and could never type here.
sso_verified = Invidious::TrustedHeaderAuth.asserted_email(env) == user.email
password = env.params.body["password"]?
if password.nil? || password.empty?
if !sso_verified && (password.nil? || password.empty?)
return error_template(401, "Password is a required field")
end
@ -68,8 +75,10 @@ module Invidious::Routes::Account
return error_template(400, "Password cannot be longer than 55 characters")
end
if !Crypto::Bcrypt::Password.new(user.password.not_nil!).verify(password.byte_slice(0, 55))
return error_template(401, "Incorrect password")
if !sso_verified
if !Crypto::Bcrypt::Password.new(user.password.not_nil!).verify(password.not_nil!.byte_slice(0, 55))
return error_template(401, "Incorrect password")
end
end
new_password = Crypto::Bcrypt::Password.create(new_password, cost: 10)

View File

@ -10,8 +10,10 @@
<legend><%= I18n.translate(locale, "Change password") %></legend>
<fieldset>
<% if !sso_verified %>
<label for="password"><%= I18n.translate(locale, "Password") %> :</label>
<input required class="pure-input-1" name="password" type="password" placeholder="<%= I18n.translate(locale, "Password") %>">
<% end %>
<label for="new_password[0]"><%= I18n.translate(locale, "New password") %> :</label>
<input required class="pure-input-1" name="new_password[0]" type="password" placeholder="<%= I18n.translate(locale, "New password") %>">