Rifiuta il riuso della password attuale in cambio e reset.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-08 11:31:47 +02:00
co-authored by Cursor
parent 53449c5d3c
commit dd9901519a
17 changed files with 60 additions and 0 deletions
@@ -24,6 +24,11 @@ module Admin
return render :edit, status: :unprocessable_entity return render :edit, status: :unprocessable_entity
end end
if PasswordComplexity.same_as_current?(current_admin_account, params[:password])
flash.now[:alert] = t("admin.flash.password_same_as_current")
return render :edit, status: :unprocessable_entity
end
if current_admin_account.update(password: params[:password]) if current_admin_account.update(password: params[:password])
redirect_to admin_root_path, notice: t("admin.flash.password_updated") redirect_to admin_root_path, notice: t("admin.flash.password_updated")
else else
@@ -51,6 +51,7 @@ module Api
when :too_long then "Password cannot exceed 72 characters" when :too_long then "Password cannot exceed 72 characters"
when :too_weak when :too_weak
"Password must include at least 3 of: lowercase, uppercase, number, symbol" "Password must include at least 3 of: lowercase, uppercase, number, symbol"
when :same_as_current then "New password must be different from the current password"
when :mismatch then "Passwords do not match" when :mismatch then "Passwords do not match"
else "Unable to update password" else "Unable to update password"
end end
@@ -40,6 +40,12 @@ module Public
return render :edit, status: :unprocessable_entity return render :edit, status: :unprocessable_entity
end end
if PasswordComplexity.same_as_current?(@user, params[:password])
flash.now[:alert] = t("flash.password_resets.password_same_as_current")
@token = params[:token]
return render :edit, status: :unprocessable_entity
end
@user.update!(password: params[:password]) @user.update!(password: params[:password])
@user.clear_password_reset! @user.clear_password_reset!
redirect_to public_login_path, notice: t("flash.password_resets.password_updated") redirect_to public_login_path, notice: t("flash.password_resets.password_updated")
@@ -31,6 +31,13 @@ module PasswordComplexity
matched >= REQUIRED_CLASSES matched >= REQUIRED_CLASSES
end end
# Confronta con il digest già salvato (prima di assegnare la nuova password).
def same_as_current?(record, password)
return false if password.blank? || !record.respond_to?(:authenticate)
record.authenticate(password).present?
end
def requirement_summary def requirement_summary
I18n.t("password_policy.hint") I18n.t("password_policy.hint")
end end
@@ -31,6 +31,10 @@ module Users
return Result.new(ok?: false, error: code == :blank ? :too_short : code) return Result.new(ok?: false, error: code == :blank ? :too_short : code)
end end
if PasswordComplexity.same_as_current?(@user, @password)
return Result.new(ok?: false, error: :same_as_current)
end
unless @user.update(password: @password) unless @user.update(password: @password)
complexity_error = @user.errors.details[:password]&.any? { |d| d[:error] == :complexity } complexity_error = @user.errors.details[:password]&.any? { |d| d[:error] == :complexity }
return Result.new(ok?: false, error: complexity_error ? :too_weak : :too_short) return Result.new(ok?: false, error: complexity_error ? :too_weak : :too_short)
+1
View File
@@ -20,6 +20,7 @@ de:
password_too_short: Das neue Passwort muss mindestens 8 Zeichen lang sein password_too_short: Das neue Passwort muss mindestens 8 Zeichen lang sein
password_too_long: Das neue Passwort darf höchstens 72 Zeichen lang sein password_too_long: Das neue Passwort darf höchstens 72 Zeichen lang sein
password_too_weak: Das neue Passwort muss mindestens 3 aus Kleinbuchstaben, Großbuchstaben, Zahlen und Symbolen enthalten password_too_weak: Das neue Passwort muss mindestens 3 aus Kleinbuchstaben, Großbuchstaben, Zahlen und Symbolen enthalten
password_same_as_current: Das neue Passwort muss sich vom aktuellen unterscheiden
password_mismatch: Die Passwörter stimmen nicht überein password_mismatch: Die Passwörter stimmen nicht überein
password_updated: Passwort aktualisiert password_updated: Passwort aktualisiert
ops_acknowledged: Vorfall übernommen ops_acknowledged: Vorfall übernommen
+1
View File
@@ -20,6 +20,7 @@ en:
password_too_short: The new password must be at least 8 characters long password_too_short: The new password must be at least 8 characters long
password_too_long: New password cannot exceed 72 characters password_too_long: New password cannot exceed 72 characters
password_too_weak: "New password must include at least 3 of: lowercase, uppercase, numbers and symbols" password_too_weak: "New password must include at least 3 of: lowercase, uppercase, numbers and symbols"
password_same_as_current: New password must be different from the current password
password_mismatch: Passwords do not match password_mismatch: Passwords do not match
password_updated: Password updated password_updated: Password updated
ops_acknowledged: Incident acknowledged ops_acknowledged: Incident acknowledged
+1
View File
@@ -20,6 +20,7 @@ es:
password_too_short: La nueva contraseña debe tener al menos 8 caracteres password_too_short: La nueva contraseña debe tener al menos 8 caracteres
password_too_long: La nueva contraseña no puede superar los 72 caracteres password_too_long: La nueva contraseña no puede superar los 72 caracteres
password_too_weak: "La nueva contraseña debe incluir al menos 3 entre: minúsculas, mayúsculas, números y símbolos" password_too_weak: "La nueva contraseña debe incluir al menos 3 entre: minúsculas, mayúsculas, números y símbolos"
password_same_as_current: La nueva contraseña debe ser distinta de la actual
password_mismatch: Las contraseñas no coinciden password_mismatch: Las contraseñas no coinciden
password_updated: Contraseña actualizada password_updated: Contraseña actualizada
ops_acknowledged: Incidencia asumida ops_acknowledged: Incidencia asumida
+1
View File
@@ -20,6 +20,7 @@ fr:
password_too_short: Le nouveau mot de passe doit contenir au moins 8 caractères password_too_short: Le nouveau mot de passe doit contenir au moins 8 caractères
password_too_long: Le nouveau mot de passe ne peut pas dépasser 72 caractères password_too_long: Le nouveau mot de passe ne peut pas dépasser 72 caractères
password_too_weak: "Le nouveau mot de passe doit inclure au moins 3 parmi : minuscules, majuscules, chiffres et symboles" password_too_weak: "Le nouveau mot de passe doit inclure au moins 3 parmi : minuscules, majuscules, chiffres et symboles"
password_same_as_current: "Le nouveau mot de passe doit être différent de l'actuel"
password_mismatch: Les mots de passe ne correspondent pas password_mismatch: Les mots de passe ne correspondent pas
password_updated: Mot de passe mis à jour password_updated: Mot de passe mis à jour
ops_acknowledged: Incident pris en charge ops_acknowledged: Incident pris en charge
+1
View File
@@ -20,6 +20,7 @@ it:
password_too_short: La nuova password deve avere almeno 8 caratteri password_too_short: La nuova password deve avere almeno 8 caratteri
password_too_long: La nuova password non può superare i 72 caratteri password_too_long: La nuova password non può superare i 72 caratteri
password_too_weak: "La nuova password deve includere almeno 3 tra: minuscole, maiuscole, numeri e simboli" password_too_weak: "La nuova password deve includere almeno 3 tra: minuscole, maiuscole, numeri e simboli"
password_same_as_current: La nuova password deve essere diversa da quella attuale
password_mismatch: Le password non coincidono password_mismatch: Le password non coincidono
password_updated: Password aggiornata password_updated: Password aggiornata
ops_acknowledged: Incidente preso in carico ops_acknowledged: Incidente preso in carico
+2
View File
@@ -624,6 +624,7 @@ de:
password_too_short: Das Passwort muss mindestens 8 Zeichen lang sein password_too_short: Das Passwort muss mindestens 8 Zeichen lang sein
password_too_long: Das Passwort darf höchstens 72 Zeichen lang sein password_too_long: Das Passwort darf höchstens 72 Zeichen lang sein
password_too_weak: Das Passwort muss mindestens 3 aus Kleinbuchstaben, Großbuchstaben, Zahlen und Symbolen enthalten password_too_weak: Das Passwort muss mindestens 3 aus Kleinbuchstaben, Großbuchstaben, Zahlen und Symbolen enthalten
password_same_as_current: Das neue Passwort muss sich vom aktuellen unterscheiden
password_mismatch: Die Passwörter stimmen nicht überein password_mismatch: Die Passwörter stimmen nicht überein
password_updated: Passwort aktualisiert. Du kannst dich jetzt anmelden. password_updated: Passwort aktualisiert. Du kannst dich jetzt anmelden.
accounts: accounts:
@@ -633,6 +634,7 @@ de:
password_too_short: Das Passwort muss mindestens 8 Zeichen haben password_too_short: Das Passwort muss mindestens 8 Zeichen haben
password_too_long: Das Passwort darf höchstens 72 Zeichen lang sein password_too_long: Das Passwort darf höchstens 72 Zeichen lang sein
password_too_weak: Das Passwort muss mindestens 3 aus Kleinbuchstaben, Großbuchstaben, Zahlen und Symbolen enthalten password_too_weak: Das Passwort muss mindestens 3 aus Kleinbuchstaben, Großbuchstaben, Zahlen und Symbolen enthalten
password_same_as_current: Das neue Passwort muss sich vom aktuellen unterscheiden
password_mismatch: Die Passwörter stimmen nicht überein password_mismatch: Die Passwörter stimmen nicht überein
password_updated: Passwort aktualisiert. password_updated: Passwort aktualisiert.
replay: replay:
+2
View File
@@ -624,6 +624,7 @@ en:
password_too_short: Password must be at least 8 characters password_too_short: Password must be at least 8 characters
password_too_long: Password cannot exceed 72 characters password_too_long: Password cannot exceed 72 characters
password_too_weak: "Password must include at least 3 of: lowercase, uppercase, numbers and symbols" password_too_weak: "Password must include at least 3 of: lowercase, uppercase, numbers and symbols"
password_same_as_current: New password must be different from the current password
password_mismatch: Passwords don't match password_mismatch: Passwords don't match
password_updated: Password updated. You can now log in. password_updated: Password updated. You can now log in.
accounts: accounts:
@@ -633,6 +634,7 @@ en:
password_too_short: Password must be at least 8 characters password_too_short: Password must be at least 8 characters
password_too_long: Password cannot exceed 72 characters password_too_long: Password cannot exceed 72 characters
password_too_weak: "Password must include at least 3 of: lowercase, uppercase, numbers and symbols" password_too_weak: "Password must include at least 3 of: lowercase, uppercase, numbers and symbols"
password_same_as_current: New password must be different from the current password
password_mismatch: Passwords do not match password_mismatch: Passwords do not match
password_updated: Password updated. password_updated: Password updated.
replay: replay:
+2
View File
@@ -624,6 +624,7 @@ es:
password_too_short: La contraseña debe tener al menos 8 caracteres password_too_short: La contraseña debe tener al menos 8 caracteres
password_too_long: La contraseña no puede superar los 72 caracteres password_too_long: La contraseña no puede superar los 72 caracteres
password_too_weak: "La contraseña debe incluir al menos 3 entre: minúsculas, mayúsculas, números y símbolos" password_too_weak: "La contraseña debe incluir al menos 3 entre: minúsculas, mayúsculas, números y símbolos"
password_same_as_current: La nueva contraseña debe ser distinta de la actual
password_mismatch: Las contraseñas no coinciden password_mismatch: Las contraseñas no coinciden
password_updated: Contraseña actualizada. Ya puedes iniciar sesión. password_updated: Contraseña actualizada. Ya puedes iniciar sesión.
accounts: accounts:
@@ -633,6 +634,7 @@ es:
password_too_short: La contraseña debe tener al menos 8 caracteres password_too_short: La contraseña debe tener al menos 8 caracteres
password_too_long: La contraseña no puede superar los 72 caracteres password_too_long: La contraseña no puede superar los 72 caracteres
password_too_weak: "La contraseña debe incluir al menos 3 entre: minúsculas, mayúsculas, números y símbolos" password_too_weak: "La contraseña debe incluir al menos 3 entre: minúsculas, mayúsculas, números y símbolos"
password_same_as_current: La nueva contraseña debe ser distinta de la actual
password_mismatch: Las contraseñas no coinciden password_mismatch: Las contraseñas no coinciden
password_updated: Contraseña actualizada. password_updated: Contraseña actualizada.
replay: replay:
+2
View File
@@ -624,6 +624,7 @@ fr:
password_too_short: Le mot de passe doit comporter au moins 8 caractères password_too_short: Le mot de passe doit comporter au moins 8 caractères
password_too_long: Le mot de passe ne peut pas dépasser 72 caractères password_too_long: Le mot de passe ne peut pas dépasser 72 caractères
password_too_weak: "Le mot de passe doit inclure au moins 3 parmi : minuscules, majuscules, chiffres et symboles" password_too_weak: "Le mot de passe doit inclure au moins 3 parmi : minuscules, majuscules, chiffres et symboles"
password_same_as_current: "Le nouveau mot de passe doit être différent de l'actuel"
password_mismatch: Les mots de passe ne correspondent pas password_mismatch: Les mots de passe ne correspondent pas
password_updated: Mot de passe mis à jour. Tu peux maintenant te connecter. password_updated: Mot de passe mis à jour. Tu peux maintenant te connecter.
accounts: accounts:
@@ -633,6 +634,7 @@ fr:
password_too_short: Le mot de passe doit contenir au moins 8 caractères password_too_short: Le mot de passe doit contenir au moins 8 caractères
password_too_long: Le mot de passe ne peut pas dépasser 72 caractères password_too_long: Le mot de passe ne peut pas dépasser 72 caractères
password_too_weak: "Le mot de passe doit inclure au moins 3 parmi : minuscules, majuscules, chiffres et symboles" password_too_weak: "Le mot de passe doit inclure au moins 3 parmi : minuscules, majuscules, chiffres et symboles"
password_same_as_current: "Le nouveau mot de passe doit être différent de l'actuel"
password_mismatch: Les mots de passe ne correspondent pas password_mismatch: Les mots de passe ne correspondent pas
password_updated: Mot de passe mis à jour. password_updated: Mot de passe mis à jour.
replay: replay:
+2
View File
@@ -624,6 +624,7 @@ it:
password_too_short: La password deve avere almeno 8 caratteri password_too_short: La password deve avere almeno 8 caratteri
password_too_long: La password non può superare i 72 caratteri password_too_long: La password non può superare i 72 caratteri
password_too_weak: "La password deve includere almeno 3 tra: minuscole, maiuscole, numeri e simboli" password_too_weak: "La password deve includere almeno 3 tra: minuscole, maiuscole, numeri e simboli"
password_same_as_current: La nuova password deve essere diversa da quella attuale
password_too_short: La password deve avere almeno 8 caratteri password_too_short: La password deve avere almeno 8 caratteri
password_too_long: La password non può superare i 72 caratteri password_too_long: La password non può superare i 72 caratteri
password_too_weak: "La password deve includere almeno 3 tra: minuscole, maiuscole, numeri e simboli" password_too_weak: "La password deve includere almeno 3 tra: minuscole, maiuscole, numeri e simboli"
@@ -636,6 +637,7 @@ it:
password_too_short: La password deve avere almeno 8 caratteri password_too_short: La password deve avere almeno 8 caratteri
password_too_long: La password non può superare i 72 caratteri password_too_long: La password non può superare i 72 caratteri
password_too_weak: "La password deve includere almeno 3 tra: minuscole, maiuscole, numeri e simboli" password_too_weak: "La password deve includere almeno 3 tra: minuscole, maiuscole, numeri e simboli"
password_same_as_current: La nuova password deve essere diversa da quella attuale
password_too_long: La password non può superare i 72 caratteri password_too_long: La password non può superare i 72 caratteri
password_too_weak: "La password deve includere almeno 3 tra: minuscole, maiuscole, numeri e simboli" password_too_weak: "La password deve includere almeno 3 tra: minuscole, maiuscole, numeri e simboli"
password_mismatch: Le password non coincidono password_mismatch: Le password non coincidono
@@ -21,6 +21,15 @@ RSpec.describe PasswordComplexity do
end end
end end
describe ".same_as_current?" do
let!(:user) { User.create!(email: "same@example.com", name: "Same", password: "Password123", role: "coach") }
it "detects when the new password matches the current one" do
expect(described_class.same_as_current?(user, "Password123")).to eq(true)
expect(described_class.same_as_current?(user, "OtherPass123")).to eq(false)
end
end
describe "User validation" do describe "User validation" do
it "blocks weak passwords on create" do it "blocks weak passwords on create" do
user = User.new(email: "weak@example.com", name: "Weak", password: "password123", role: "coach") user = User.new(email: "weak@example.com", name: "Weak", password: "password123", role: "coach")
+13
View File
@@ -73,6 +73,19 @@ RSpec.describe "Account API", type: :request do
expect(JSON.parse(response.body)["error"]).to match(/3 of/i) expect(JSON.parse(response.body)["error"]).to match(/3 of/i)
expect(user.reload.authenticate("Password123")).to be_truthy expect(user.reload.authenticate("Password123")).to be_truthy
end end
it "rejects reusing the current password" do
patch "/api/v1/account/password",
params: {
current_password: "Password123",
password: "Password123",
password_confirmation: "Password123"
},
headers: auth_headers
expect(response).to have_http_status(:unprocessable_entity)
expect(JSON.parse(response.body)["error"]).to match(/different/i)
expect(user.reload.authenticate("Password123")).to be_truthy
end
end end
describe "POST /api/v1/auth/password/forgot" do describe "POST /api/v1/auth/password/forgot" do