Rafforza i requisiti password con regole di complessità di mercato.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-08 11:30:37 +02:00
co-authored by Cursor
parent 5857f60d79
commit db908e3109
35 changed files with 296 additions and 47 deletions
@@ -9,13 +9,18 @@ module Admin
return render :edit, status: :unprocessable_entity return render :edit, status: :unprocessable_entity
end end
if params[:password].blank? || params[:password].length < 8 if params[:password] != params[:password_confirmation]
flash.now[:alert] = t("admin.flash.password_too_short") flash.now[:alert] = t("admin.flash.password_mismatch")
return render :edit, status: :unprocessable_entity return render :edit, status: :unprocessable_entity
end end
if params[:password] != params[:password_confirmation] if (code = PasswordComplexity.violation(params[:password]))
flash.now[:alert] = t("admin.flash.password_mismatch") key = case code
when :blank, :too_short then :password_too_short
when :too_long then :password_too_long
else :password_too_weak
end
flash.now[:alert] = t("admin.flash.#{key}")
return render :edit, status: :unprocessable_entity return render :edit, status: :unprocessable_entity
end end
@@ -48,6 +48,9 @@ module Api
case code case code
when :current_incorrect then "Current password is incorrect" when :current_incorrect then "Current password is incorrect"
when :too_short then "Password must be at least 8 characters" when :too_short then "Password must be at least 8 characters"
when :too_long then "Password cannot exceed 72 characters"
when :too_weak
"Password must include at least 3 of: lowercase, uppercase, number, symbol"
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
@@ -28,14 +28,14 @@ module Public
return return
end end
if params[:password].blank? || params[:password].length < 8 if params[:password] != params[:password_confirmation]
flash.now[:alert] = t("flash.password_resets.password_min_length") flash.now[:alert] = t("flash.password_resets.password_mismatch")
@token = params[:token] @token = params[:token]
return render :edit, status: :unprocessable_entity return render :edit, status: :unprocessable_entity
end end
if params[:password] != params[:password_confirmation] if (code = PasswordComplexity.violation(params[:password]))
flash.now[:alert] = t("flash.password_resets.password_mismatch") flash.now[:alert] = t("flash.password_resets.password_#{code == :blank ? :too_short : code}")
@token = params[:token] @token = params[:token]
return render :edit, status: :unprocessable_entity return render :edit, status: :unprocessable_entity
end end
+2
View File
@@ -1,4 +1,6 @@
class AdminAccount < ApplicationRecord class AdminAccount < ApplicationRecord
include PasswordComplexity
has_secure_password has_secure_password
validates :username, presence: true, uniqueness: true validates :username, presence: true, uniqueness: true
@@ -0,0 +1,55 @@
# Criteri "di mercato" (stile Cognito/Auth0 bilanciato):
# - minimo 8 caratteri (max 72 per bcrypt)
# - almeno 3 classi su 4: minuscole, maiuscole, numeri, simboli
module PasswordComplexity
extend ActiveSupport::Concern
MIN_LENGTH = 8
MAX_LENGTH = 72
REQUIRED_CLASSES = 3
CLASS_CHECKS = {
lowercase: /[a-z]/,
uppercase: /[A-Z]/,
digit: /\d/,
symbol: /[^A-Za-z0-9]/
}.freeze
class << self
def violation(password)
value = password.to_s
return :blank if value.blank?
return :too_short if value.length < MIN_LENGTH
return :too_long if value.bytesize > MAX_LENGTH
return :too_weak unless strong_enough?(value)
nil
end
def strong_enough?(password)
matched = CLASS_CHECKS.count { |_, pattern| password.match?(pattern) }
matched >= REQUIRED_CLASSES
end
def requirement_summary
I18n.t("password_policy.hint")
end
end
included do
validate :password_meets_complexity_policy, if: -> { password.present? }
end
private
def password_meets_complexity_policy
case PasswordComplexity.violation(password)
when :too_short
errors.add(:password, :too_short, count: MIN_LENGTH)
when :too_long
errors.add(:password, :too_long, count: MAX_LENGTH)
when :too_weak
errors.add(:password, :complexity)
end
end
end
+2
View File
@@ -1,4 +1,6 @@
class User < ApplicationRecord class User < ApplicationRecord
include PasswordComplexity
ROLES = %w[admin coach parent volunteer].freeze ROLES = %w[admin coach parent volunteer].freeze
has_secure_password has_secure_password
@@ -23,15 +23,19 @@ module Users
return Result.new(ok?: false, error: :current_incorrect) return Result.new(ok?: false, error: :current_incorrect)
end end
if @password.blank? || @password.length < 8
return Result.new(ok?: false, error: :too_short)
end
if @password != @password_confirmation if @password != @password_confirmation
return Result.new(ok?: false, error: :mismatch) return Result.new(ok?: false, error: :mismatch)
end end
@user.update!(password: @password) if (code = PasswordComplexity.violation(@password))
return Result.new(ok?: false, error: code == :blank ? :too_short : code)
end
unless @user.update(password: @password)
complexity_error = @user.errors.details[:password]&.any? { |d| d[:error] == :complexity }
return Result.new(ok?: false, error: complexity_error ? :too_weak : :too_short)
end
@user.clear_password_reset! @user.clear_password_reset!
Result.new(ok?: true) Result.new(ok?: true)
end end
@@ -30,6 +30,7 @@
<div class="card"> <div class="card">
<h2 style="font-size:1.1rem;margin-top:0"><%= t("auth.account.password_heading") %></h2> <h2 style="font-size:1.1rem;margin-top:0"><%= t("auth.account.password_heading") %></h2>
<p class="muted" style="font-size:0.9rem"><%= t("password_policy.hint") %></p>
<%= form_with url: public_account_password_path, method: :patch, local: true do %> <%= form_with url: public_account_password_path, method: :patch, local: true do %>
<%= render "shared/input_toggle", <%= render "shared/input_toggle",
name: :current_password, name: :current_password,
@@ -4,6 +4,7 @@
<section class="auth-page"> <section class="auth-page">
<h1><%= t("auth.password_reset.title") %></h1> <h1><%= t("auth.password_reset.title") %></h1>
<div class="card"> <div class="card">
<p class="muted" style="font-size:0.9rem"><%= t("password_policy.hint") %></p>
<%= form_with url: public_password_reset_path, method: :patch, local: true do %> <%= form_with url: public_password_reset_path, method: :patch, local: true do %>
<%= hidden_field_tag :token, @token %> <%= hidden_field_tag :token, @token %>
<%= render "shared/input_toggle", <%= render "shared/input_toggle",
@@ -25,6 +25,7 @@
required: true, required: true,
minlength: 8, minlength: 8,
autocomplete: "new-password" %> autocomplete: "new-password" %>
<p class="muted" style="font-size:0.9rem;margin-top:-0.5rem"><%= t("password_policy.hint") %></p>
<%= render "shared/input_toggle", <%= render "shared/input_toggle",
name: "user[password_confirmation]", name: "user[password_confirmation]",
id: "user_password_confirmation", id: "user_password_confirmation",
+3 -1
View File
@@ -18,6 +18,8 @@ de:
logout_success: Abgemeldet logout_success: Abgemeldet
password_current_incorrect: Das aktuelle Passwort ist falsch password_current_incorrect: Das aktuelle Passwort ist falsch
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_weak: Das neue Passwort muss mindestens 3 aus Kleinbuchstaben, Großbuchstaben, Zahlen und Symbolen enthalten
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
@@ -53,7 +55,7 @@ de:
edit: edit:
title: Passwort ändern title: Passwort ändern
current_password_label: Aktuelles Passwort current_password_label: Aktuelles Passwort
new_password_label: "Neues Passwort (mind. 8 Zeichen)" new_password_label: "Neues Passwort (mind. 8, mindestens 3 Zeichenarten)"
confirm_password_label: Neues Passwort bestätigen confirm_password_label: Neues Passwort bestätigen
submit: Passwort speichern submit: Passwort speichern
cancel: Abbrechen cancel: Abbrechen
+3 -1
View File
@@ -18,6 +18,8 @@ en:
logout_success: Signed out logout_success: Signed out
password_current_incorrect: Current password is incorrect password_current_incorrect: Current password is incorrect
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_weak: "New password must include at least 3 of: lowercase, uppercase, numbers and symbols"
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
@@ -53,7 +55,7 @@ en:
edit: edit:
title: Change password title: Change password
current_password_label: Current password current_password_label: Current password
new_password_label: "New password (min. 8 characters)" new_password_label: "New password (min. 8, at least 3 character types)"
confirm_password_label: Confirm new password confirm_password_label: Confirm new password
submit: Save password submit: Save password
cancel: Cancel cancel: Cancel
+3 -1
View File
@@ -18,6 +18,8 @@ es:
logout_success: Sesión cerrada logout_success: Sesión cerrada
password_current_incorrect: La contraseña actual no es correcta password_current_incorrect: La contraseña actual no es correcta
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_weak: "La nueva contraseña debe incluir al menos 3 entre: minúsculas, mayúsculas, números y símbolos"
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
@@ -53,7 +55,7 @@ es:
edit: edit:
title: Cambiar contraseña title: Cambiar contraseña
current_password_label: Contraseña actual current_password_label: Contraseña actual
new_password_label: "Nueva contraseña (mín. 8 caracteres)" new_password_label: "Nueva contraseña (mín. 8, al menos 3 tipos de caracteres)"
confirm_password_label: Confirma la nueva contraseña confirm_password_label: Confirma la nueva contraseña
submit: Guardar contraseña submit: Guardar contraseña
cancel: Cancelar cancel: Cancelar
+3 -1
View File
@@ -18,6 +18,8 @@ fr:
logout_success: Déconnecté logout_success: Déconnecté
password_current_incorrect: Le mot de passe actuel est incorrect password_current_incorrect: Le mot de passe actuel est incorrect
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_weak: "Le nouveau mot de passe doit inclure au moins 3 parmi : minuscules, majuscules, chiffres et symboles"
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
@@ -53,7 +55,7 @@ fr:
edit: edit:
title: Changer le mot de passe title: Changer le mot de passe
current_password_label: Mot de passe actuel current_password_label: Mot de passe actuel
new_password_label: "Nouveau mot de passe (min. 8 caractères)" new_password_label: "Nouveau mot de passe (min. 8, au moins 3 types de caractères)"
confirm_password_label: Confirmer le nouveau mot de passe confirm_password_label: Confirmer le nouveau mot de passe
submit: Enregistrer le mot de passe submit: Enregistrer le mot de passe
cancel: Annuler cancel: Annuler
+3 -1
View File
@@ -18,6 +18,8 @@ it:
logout_success: Disconnesso logout_success: Disconnesso
password_current_incorrect: Password attuale non corretta password_current_incorrect: Password attuale non corretta
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_weak: "La nuova password deve includere almeno 3 tra: minuscole, maiuscole, numeri e simboli"
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
@@ -53,7 +55,7 @@ it:
edit: edit:
title: Cambia password title: Cambia password
current_password_label: Password attuale current_password_label: Password attuale
new_password_label: "Nuova password (min. 8 caratteri)" new_password_label: "Nuova password (min. 8, almeno 3 tipi di caratteri)"
confirm_password_label: Conferma nuova password confirm_password_label: Conferma nuova password
submit: Salva password submit: Salva password
cancel: Annulla cancel: Annulla
+22
View File
@@ -1,4 +1,21 @@
de: de:
password_policy:
hint: "Mindestens 8 Zeichen, mit mindestens 3 aus: Kleinbuchstaben, Großbuchstaben, Zahlen und Symbolen."
activerecord:
errors:
models:
user:
attributes:
password:
too_short: "is too short (minimum is %{count} characters)"
too_long: "is too long (maximum is %{count} characters)"
complexity: "muss mindestens 3 aus Kleinbuchstaben, Großbuchstaben, Zahlen und Symbolen enthalten"
admin_account:
attributes:
password:
too_short: "is too short (minimum is %{count} characters)"
too_long: "is too long (maximum is %{count} characters)"
complexity: "muss mindestens 3 aus Kleinbuchstaben, Großbuchstaben, Zahlen und Symbolen enthalten"
club: club:
back_to_club: "← Verein" back_to_club: "← Verein"
sport_label: Hauptsportart sport_label: Hauptsportart
@@ -604,6 +621,9 @@ de:
email_sent: Wenn die E-Mail registriert ist, erhältst du in Kürze einen Link zum Zurücksetzen des Passworts. email_sent: Wenn die E-Mail registriert ist, erhältst du in Kürze einen Link zum Zurücksetzen des Passworts.
invalid_or_expired_link: Link ungültig oder abgelaufen. Fordere ein neues Zurücksetzen des Passworts an. invalid_or_expired_link: Link ungültig oder abgelaufen. Fordere ein neues Zurücksetzen des Passworts an.
password_min_length: Das Passwort muss mindestens 8 Zeichen lang sein password_min_length: 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_weak: Das Passwort muss mindestens 3 aus Kleinbuchstaben, Großbuchstaben, Zahlen und Symbolen enthalten
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:
@@ -611,6 +631,8 @@ de:
profile_updated: Profil aktualisiert. profile_updated: Profil aktualisiert.
password_current_incorrect: Das aktuelle Passwort ist falsch password_current_incorrect: Das aktuelle Passwort ist falsch
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_weak: Das Passwort muss mindestens 3 aus Kleinbuchstaben, Großbuchstaben, Zahlen und Symbolen enthalten
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:
+22
View File
@@ -1,4 +1,21 @@
en: en:
password_policy:
hint: "At least 8 characters, including 3 of: lowercase, uppercase, numbers and symbols."
activerecord:
errors:
models:
user:
attributes:
password:
too_short: "is too short (minimum is %{count} characters)"
too_long: "is too long (maximum is %{count} characters)"
complexity: "must include at least 3 of: lowercase letters, uppercase letters, numbers and symbols"
admin_account:
attributes:
password:
too_short: "is too short (minimum is %{count} characters)"
too_long: "is too long (maximum is %{count} characters)"
complexity: "must include at least 3 of: lowercase letters, uppercase letters, numbers and symbols"
club: club:
back_to_club: "← Club" back_to_club: "← Club"
sport_label: Main sport sport_label: Main sport
@@ -604,6 +621,9 @@ en:
email_sent: If the email is registered, you'll receive a password reset link shortly. email_sent: If the email is registered, you'll receive a password reset link shortly.
invalid_or_expired_link: Invalid or expired link. Request a new password reset. invalid_or_expired_link: Invalid or expired link. Request a new password reset.
password_min_length: Password must be at least 8 characters password_min_length: 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_weak: "Password must include at least 3 of: lowercase, uppercase, numbers and symbols"
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:
@@ -611,6 +631,8 @@ en:
profile_updated: Profile updated. profile_updated: Profile updated.
password_current_incorrect: Current password is incorrect password_current_incorrect: Current password is incorrect
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_weak: "Password must include at least 3 of: lowercase, uppercase, numbers and symbols"
password_mismatch: Passwords do not match password_mismatch: Passwords do not match
password_updated: Password updated. password_updated: Password updated.
replay: replay:
+22
View File
@@ -1,4 +1,21 @@
es: es:
password_policy:
hint: "Mínimo 8 caracteres, con al menos 3 entre: minúsculas, mayúsculas, números y símbolos."
activerecord:
errors:
models:
user:
attributes:
password:
too_short: "is too short (minimum is %{count} characters)"
too_long: "is too long (maximum is %{count} characters)"
complexity: "debe incluir al menos 3 entre: minúsculas, mayúsculas, números y símbolos"
admin_account:
attributes:
password:
too_short: "is too short (minimum is %{count} characters)"
too_long: "is too long (maximum is %{count} characters)"
complexity: "debe incluir al menos 3 entre: minúsculas, mayúsculas, números y símbolos"
club: club:
back_to_club: "← Club" back_to_club: "← Club"
sport_label: Deporte principal sport_label: Deporte principal
@@ -604,6 +621,9 @@ es:
email_sent: Si el correo está registrado, recibirás en breve un enlace para restablecer la contraseña. email_sent: Si el correo está registrado, recibirás en breve un enlace para restablecer la contraseña.
invalid_or_expired_link: Enlace no válido o caducado. Solicita un nuevo restablecimiento de contraseña. invalid_or_expired_link: Enlace no válido o caducado. Solicita un nuevo restablecimiento de contraseña.
password_min_length: La contraseña debe tener al menos 8 caracteres password_min_length: 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_weak: "La contraseña debe incluir al menos 3 entre: minúsculas, mayúsculas, números y símbolos"
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:
@@ -611,6 +631,8 @@ es:
profile_updated: Perfil actualizado. profile_updated: Perfil actualizado.
password_current_incorrect: La contraseña actual no es correcta password_current_incorrect: La contraseña actual no es correcta
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_weak: "La contraseña debe incluir al menos 3 entre: minúsculas, mayúsculas, números y símbolos"
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:
+22
View File
@@ -1,4 +1,21 @@
fr: fr:
password_policy:
hint: "Au moins 8 caractères, avec au moins 3 parmi : minuscules, majuscules, chiffres et symboles."
activerecord:
errors:
models:
user:
attributes:
password:
too_short: "is too short (minimum is %{count} characters)"
too_long: "is too long (maximum is %{count} characters)"
complexity: "doit inclure au moins 3 parmi : minuscules, majuscules, chiffres et symboles"
admin_account:
attributes:
password:
too_short: "is too short (minimum is %{count} characters)"
too_long: "is too long (maximum is %{count} characters)"
complexity: "doit inclure au moins 3 parmi : minuscules, majuscules, chiffres et symboles"
club: club:
back_to_club: "← Club" back_to_club: "← Club"
sport_label: Sport principal sport_label: Sport principal
@@ -604,6 +621,9 @@ fr:
email_sent: Si l'e-mail est enregistré, tu recevras bientôt un lien pour réinitialiser le mot de passe. email_sent: Si l'e-mail est enregistré, tu recevras bientôt un lien pour réinitialiser le mot de passe.
invalid_or_expired_link: Lien invalide ou expiré. Demande une nouvelle réinitialisation du mot de passe. invalid_or_expired_link: Lien invalide ou expiré. Demande une nouvelle réinitialisation du mot de passe.
password_min_length: Le mot de passe doit comporter au moins 8 caractères password_min_length: 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_weak: "Le mot de passe doit inclure au moins 3 parmi : minuscules, majuscules, chiffres et symboles"
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:
@@ -611,6 +631,8 @@ fr:
profile_updated: Profil mis à jour. profile_updated: Profil mis à jour.
password_current_incorrect: Le mot de passe actuel est incorrect password_current_incorrect: Le mot de passe actuel est incorrect
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_weak: "Le mot de passe doit inclure au moins 3 parmi : minuscules, majuscules, chiffres et symboles"
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:
+27
View File
@@ -1,4 +1,21 @@
it: it:
password_policy:
hint: "Minimo 8 caratteri, con almeno 3 tra: minuscole, maiuscole, numeri e simboli."
activerecord:
errors:
models:
user:
attributes:
password:
too_short: "è troppo corta (minimo %{count} caratteri)"
too_long: "è troppo lunga (massimo %{count} caratteri)"
complexity: "deve includere almeno 3 tra: lettere minuscole, maiuscole, numeri e simboli"
admin_account:
attributes:
password:
too_short: "è troppo corta (minimo %{count} caratteri)"
too_long: "è troppo lunga (massimo %{count} caratteri)"
complexity: "deve includere almeno 3 tra: lettere minuscole, maiuscole, numeri e simboli"
club: club:
back_to_club: "← Società" back_to_club: "← Società"
sport_label: Sport principale sport_label: Sport principale
@@ -604,6 +621,12 @@ it:
email_sent: Se l'email è registrata, riceverai a breve un link per reimpostare la password. email_sent: Se l'email è registrata, riceverai a breve un link per reimpostare la password.
invalid_or_expired_link: Link non valido o scaduto. Richiedi un nuovo reset password. invalid_or_expired_link: Link non valido o scaduto. Richiedi un nuovo reset password.
password_min_length: La password deve avere almeno 8 caratteri password_min_length: 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_weak: "La password deve includere almeno 3 tra: minuscole, maiuscole, numeri e simboli"
password_too_short: La password deve avere almeno 8 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_mismatch: Le password non coincidono password_mismatch: Le password non coincidono
password_updated: Password aggiornata. Ora puoi accedere. password_updated: Password aggiornata. Ora puoi accedere.
accounts: accounts:
@@ -611,6 +634,10 @@ it:
profile_updated: Profilo aggiornato. profile_updated: Profilo aggiornato.
password_current_incorrect: La password attuale non è corretta password_current_incorrect: La password attuale non è corretta
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_weak: "La password deve includere almeno 3 tra: minuscole, maiuscole, numeri e simboli"
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_mismatch: Le password non coincidono password_mismatch: Le password non coincidono
password_updated: Password aggiornata. password_updated: Password aggiornata.
replay: replay:
+2 -2
View File
@@ -103,7 +103,7 @@ de:
password_reset: password_reset:
meta_title: "Neues Passwort — Match Live TV" meta_title: "Neues Passwort — Match Live TV"
title: Neues Passwort wählen title: Neues Passwort wählen
new_password_label: "Neues Passwort (min. 8 Zeichen)" new_password_label: "Neues Passwort (mind. 8, mindestens 3 Zeichenarten)"
submit: Passwort speichern submit: Passwort speichern
back_to_login: Zurück zur Anmeldung back_to_login: Zurück zur Anmeldung
invitation: invitation:
@@ -128,7 +128,7 @@ de:
name_label: Name name_label: Name
role_label: "Rolle: %{role}" role_label: "Rolle: %{role}"
current_password_label: Aktuelles Passwort current_password_label: Aktuelles Passwort
new_password_label: "Neues Passwort (mind. 8 Zeichen)" new_password_label: "Neues Passwort (mind. 8, mindestens 3 Zeichenarten)"
save_profile: Profil speichern save_profile: Profil speichern
save_password: Passwort aktualisieren save_password: Passwort aktualisieren
common: common:
+2 -2
View File
@@ -103,7 +103,7 @@ en:
password_reset: password_reset:
meta_title: "New password — Match Live TV" meta_title: "New password — Match Live TV"
title: Choose a new password title: Choose a new password
new_password_label: "New password (min. 8 characters)" new_password_label: "New password (min. 8, at least 3 character types)"
submit: Save password submit: Save password
back_to_login: Back to login back_to_login: Back to login
invitation: invitation:
@@ -128,7 +128,7 @@ en:
name_label: Name name_label: Name
role_label: "Role: %{role}" role_label: "Role: %{role}"
current_password_label: Current password current_password_label: Current password
new_password_label: "New password (min. 8 characters)" new_password_label: "New password (min. 8, at least 3 character types)"
save_profile: Save profile save_profile: Save profile
save_password: Update password save_password: Update password
common: common:
+2 -2
View File
@@ -103,7 +103,7 @@ es:
password_reset: password_reset:
meta_title: "Nueva contraseña — Match Live TV" meta_title: "Nueva contraseña — Match Live TV"
title: Elige una nueva contraseña title: Elige una nueva contraseña
new_password_label: "Nueva contraseña (mín. 8 caracteres)" new_password_label: "Nueva contraseña (mín. 8, al menos 3 tipos de caracteres)"
submit: Guardar contraseña submit: Guardar contraseña
back_to_login: Volver al inicio de sesión back_to_login: Volver al inicio de sesión
invitation: invitation:
@@ -128,7 +128,7 @@ es:
name_label: Nombre name_label: Nombre
role_label: "Rol: %{role}" role_label: "Rol: %{role}"
current_password_label: Contraseña actual current_password_label: Contraseña actual
new_password_label: "Nueva contraseña (mín. 8 caracteres)" new_password_label: "Nueva contraseña (mín. 8, al menos 3 tipos de caracteres)"
save_profile: Guardar perfil save_profile: Guardar perfil
save_password: Actualizar contraseña save_password: Actualizar contraseña
common: common:
+2 -2
View File
@@ -103,7 +103,7 @@ fr:
password_reset: password_reset:
meta_title: "Nouveau mot de passe — Match Live TV" meta_title: "Nouveau mot de passe — Match Live TV"
title: Choisissez un nouveau mot de passe title: Choisissez un nouveau mot de passe
new_password_label: "Nouveau mot de passe (8 caractères min.)" new_password_label: "Nouveau mot de passe (min. 8, au moins 3 types de caractères)"
submit: Enregistrer le mot de passe submit: Enregistrer le mot de passe
back_to_login: Retour à la connexion back_to_login: Retour à la connexion
invitation: invitation:
@@ -128,7 +128,7 @@ fr:
name_label: Nom name_label: Nom
role_label: "Rôle : %{role}" role_label: "Rôle : %{role}"
current_password_label: Mot de passe actuel current_password_label: Mot de passe actuel
new_password_label: "Nouveau mot de passe (min. 8 caractères)" new_password_label: "Nouveau mot de passe (min. 8, au moins 3 types de caractères)"
save_profile: Enregistrer le profil save_profile: Enregistrer le profil
save_password: Mettre à jour le mot de passe save_password: Mettre à jour le mot de passe
common: common:
+2 -2
View File
@@ -103,7 +103,7 @@ it:
password_reset: password_reset:
meta_title: "Nuova password — Match Live TV" meta_title: "Nuova password — Match Live TV"
title: Scegli una nuova password title: Scegli una nuova password
new_password_label: "Nuova password (min. 8 caratteri)" new_password_label: "Nuova password (min. 8, almeno 3 tipi di caratteri)"
submit: Salva password submit: Salva password
back_to_login: Torna al login back_to_login: Torna al login
invitation: invitation:
@@ -128,7 +128,7 @@ it:
name_label: Nome name_label: Nome
role_label: "Ruolo: %{role}" role_label: "Ruolo: %{role}"
current_password_label: Password attuale current_password_label: Password attuale
new_password_label: "Nuova password (min. 8 caratteri)" new_password_label: "Nuova password (min. 8, almeno 3 tipi di caratteri)"
save_profile: Salva profilo save_profile: Salva profilo
save_password: Aggiorna password save_password: Aggiorna password
common: common:
@@ -0,0 +1,36 @@
require "rails_helper"
RSpec.describe PasswordComplexity do
describe ".violation" do
it "accepts a password with 3 character classes" do
expect(described_class.violation("NewPass123")).to be_nil
end
it "accepts symbols instead of one letter class" do
expect(described_class.violation("newpass1!")).to be_nil
end
it "rejects passwords that are too short" do
expect(described_class.violation("Ab1!")).to eq(:too_short)
end
it "rejects passwords with fewer than 3 classes" do
expect(described_class.violation("password123")).to eq(:too_weak)
expect(described_class.violation("PASSWORD123")).to eq(:too_weak)
expect(described_class.violation("Password")).to eq(:too_weak)
end
end
describe "User validation" do
it "blocks weak passwords on create" do
user = User.new(email: "weak@example.com", name: "Weak", password: "password123", role: "coach")
expect(user).not_to be_valid
expect(user.errors[:password]).to be_present
end
it "allows strong passwords on create" do
user = User.new(email: "strong@example.com", name: "Strong", password: "NewPass123", role: "coach")
expect(user).to be_valid
end
end
end
+20 -7
View File
@@ -1,9 +1,9 @@
require "rails_helper" require "rails_helper"
RSpec.describe "Account API", type: :request do RSpec.describe "Account API", type: :request do
let!(:user) { User.create!(email: "account@example.com", name: "Account User", password: "password123", role: "coach") } let!(:user) { User.create!(email: "account@example.com", name: "Account User", password: "Password123", role: "coach") }
let(:auth_headers) do let(:auth_headers) do
post "/api/v1/auth/login", params: { email: user.email, password: "password123" } post "/api/v1/auth/login", params: { email: user.email, password: "Password123" }
token = JSON.parse(response.body).fetch("access_token") token = JSON.parse(response.body).fetch("access_token")
{ "Authorization" => "Bearer #{token}" } { "Authorization" => "Bearer #{token}" }
end end
@@ -40,25 +40,38 @@ RSpec.describe "Account API", type: :request do
it "changes the password with the current password" do it "changes the password with the current password" do
patch "/api/v1/account/password", patch "/api/v1/account/password",
params: { params: {
current_password: "password123", current_password: "Password123",
password: "newpass123", password: "NewPass123",
password_confirmation: "newpass123" password_confirmation: "NewPass123"
}, },
headers: auth_headers headers: auth_headers
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:ok)
expect(user.reload.authenticate("newpass123")).to be_truthy expect(user.reload.authenticate("NewPass123")).to be_truthy
end end
it "rejects an incorrect current password" do it "rejects an incorrect current password" do
patch "/api/v1/account/password", patch "/api/v1/account/password",
params: { params: {
current_password: "wrong", current_password: "wrong",
password: "NewPass123",
password_confirmation: "NewPass123"
},
headers: auth_headers
expect(response).to have_http_status(:unprocessable_entity)
expect(user.reload.authenticate("Password123")).to be_truthy
end
it "rejects a password that fails complexity rules" do
patch "/api/v1/account/password",
params: {
current_password: "Password123",
password: "newpass123", password: "newpass123",
password_confirmation: "newpass123" password_confirmation: "newpass123"
}, },
headers: auth_headers headers: auth_headers
expect(response).to have_http_status(:unprocessable_entity) expect(response).to have_http_status(:unprocessable_entity)
expect(user.reload.authenticate("password123")).to be_truthy expect(JSON.parse(response.body)["error"]).to match(/3 of/i)
expect(user.reload.authenticate("Password123")).to be_truthy
end end
end end
+2 -2
View File
@@ -1,10 +1,10 @@
require "rails_helper" require "rails_helper"
RSpec.describe "Auth API", type: :request do RSpec.describe "Auth API", type: :request do
let!(:user) { User.create!(email: "test@example.com", name: "Test", password: "password123", role: "coach") } let!(:user) { User.create!(email: "test@example.com", name: "Test", password: "Password123", role: "coach") }
it "logs in with valid credentials" do it "logs in with valid credentials" do
post "/api/v1/auth/login", params: { email: user.email, password: "password123" } post "/api/v1/auth/login", params: { email: user.email, password: "Password123" }
expect(response).to have_http_status(:ok) expect(response).to have_http_status(:ok)
expect(JSON.parse(response.body)).to have_key("access_token") expect(JSON.parse(response.body)).to have_key("access_token")
end end
+1
View File
@@ -5,6 +5,7 @@
*.iml *.iml
/captures/ /captures/
/app/build/ /app/build/
/dist/
.DS_Store .DS_Store
keystore.properties keystore.properties
keystore/ keystore/
@@ -252,7 +252,7 @@ Dies kann nicht rückgängig gemacht werden.</string>
<string name="account_name_label">Name</string> <string name="account_name_label">Name</string>
<string name="account_role">Rolle: %1$s</string> <string name="account_role">Rolle: %1$s</string>
<string name="account_current_password">Aktuelles Passwort</string> <string name="account_current_password">Aktuelles Passwort</string>
<string name="account_new_password">Neues Passwort (mind. 8 Zeichen)</string> <string name="account_new_password">Neues Passwort (mind. 8, mindestens 3 Zeichenarten)</string>
<string name="account_confirm_password">Passwort bestätigen</string> <string name="account_confirm_password">Passwort bestätigen</string>
<string name="account_save_profile">Profil speichern</string> <string name="account_save_profile">Profil speichern</string>
<string name="account_save_password">Passwort aktualisieren</string> <string name="account_save_password">Passwort aktualisieren</string>
@@ -252,7 +252,7 @@ This cannot be undone.</string>
<string name="account_name_label">Name</string> <string name="account_name_label">Name</string>
<string name="account_role">Role: %1$s</string> <string name="account_role">Role: %1$s</string>
<string name="account_current_password">Current password</string> <string name="account_current_password">Current password</string>
<string name="account_new_password">New password (min. 8 characters)</string> <string name="account_new_password">New password (min. 8, at least 3 character types)</string>
<string name="account_confirm_password">Confirm password</string> <string name="account_confirm_password">Confirm password</string>
<string name="account_save_profile">Save profile</string> <string name="account_save_profile">Save profile</string>
<string name="account_save_password">Update password</string> <string name="account_save_password">Update password</string>
@@ -252,7 +252,7 @@ Esta acción no se puede deshacer.</string>
<string name="account_name_label">Nombre</string> <string name="account_name_label">Nombre</string>
<string name="account_role">Rol: %1$s</string> <string name="account_role">Rol: %1$s</string>
<string name="account_current_password">Contraseña actual</string> <string name="account_current_password">Contraseña actual</string>
<string name="account_new_password">Nueva contraseña (mín. 8 caracteres)</string> <string name="account_new_password">Nueva contraseña (mín. 8, al menos 3 tipos de caracteres)</string>
<string name="account_confirm_password">Confirmar contraseña</string> <string name="account_confirm_password">Confirmar contraseña</string>
<string name="account_save_profile">Guardar perfil</string> <string name="account_save_profile">Guardar perfil</string>
<string name="account_save_password">Actualizar contraseña</string> <string name="account_save_password">Actualizar contraseña</string>
@@ -252,7 +252,7 @@ Cette action est irréversible.</string>
<string name="account_name_label">Nom</string> <string name="account_name_label">Nom</string>
<string name="account_role">Rôle : %1$s</string> <string name="account_role">Rôle : %1$s</string>
<string name="account_current_password">Mot de passe actuel</string> <string name="account_current_password">Mot de passe actuel</string>
<string name="account_new_password">Nouveau mot de passe (min. 8 caractères)</string> <string name="account_new_password">Nouveau mot de passe (min. 8, au moins 3 types de caractères)</string>
<string name="account_confirm_password">Confirmer le mot de passe</string> <string name="account_confirm_password">Confirmer le mot de passe</string>
<string name="account_save_profile">Enregistrer le profil</string> <string name="account_save_profile">Enregistrer le profil</string>
<string name="account_save_password">Mettre à jour le mot de passe</string> <string name="account_save_password">Mettre à jour le mot de passe</string>
@@ -252,7 +252,7 @@ L\'operazione non si può annullare.</string>
<string name="account_name_label">Nome</string> <string name="account_name_label">Nome</string>
<string name="account_role">Ruolo: %1$s</string> <string name="account_role">Ruolo: %1$s</string>
<string name="account_current_password">Password attuale</string> <string name="account_current_password">Password attuale</string>
<string name="account_new_password">Nuova password (min. 8 caratteri)</string> <string name="account_new_password">Nuova password (min. 8, almeno 3 tipi di caratteri)</string>
<string name="account_confirm_password">Conferma password</string> <string name="account_confirm_password">Conferma password</string>
<string name="account_save_profile">Salva profilo</string> <string name="account_save_profile">Salva profilo</string>
<string name="account_save_password">Aggiorna password</string> <string name="account_save_password">Aggiorna password</string>
@@ -81,7 +81,7 @@ enum L10n {
"account.confirm.password": "Conferma password", "account.confirm.password": "Conferma password",
"account.current.password": "Password attuale", "account.current.password": "Password attuale",
"account.name.label": "Nome", "account.name.label": "Nome",
"account.new.password": "Nuova password (min. 8 caratteri)", "account.new.password": "Nuova password (min. 8, almeno 3 tipi di caratteri)",
"account.password.heading": "Cambia password", "account.password.heading": "Cambia password",
"account.password.saved": "Password aggiornata", "account.password.saved": "Password aggiornata",
"account.profile.heading": "Profilo", "account.profile.heading": "Profilo",
@@ -372,7 +372,7 @@ enum L10n {
"account.confirm.password": "Confirm password", "account.confirm.password": "Confirm password",
"account.current.password": "Current password", "account.current.password": "Current password",
"account.name.label": "Name", "account.name.label": "Name",
"account.new.password": "New password (min. 8 characters)", "account.new.password": "New password (min. 8, at least 3 character types)",
"account.password.heading": "Change password", "account.password.heading": "Change password",
"account.password.saved": "Password updated", "account.password.saved": "Password updated",
"account.profile.heading": "Profile", "account.profile.heading": "Profile",
@@ -663,7 +663,7 @@ enum L10n {
"account.confirm.password": "Confirmer le mot de passe", "account.confirm.password": "Confirmer le mot de passe",
"account.current.password": "Mot de passe actuel", "account.current.password": "Mot de passe actuel",
"account.name.label": "Nom", "account.name.label": "Nom",
"account.new.password": "Nouveau mot de passe (min. 8 caractères)", "account.new.password": "Nouveau mot de passe (min. 8, au moins 3 types de caractères)",
"account.password.heading": "Changer le mot de passe", "account.password.heading": "Changer le mot de passe",
"account.password.saved": "Mot de passe mis à jour", "account.password.saved": "Mot de passe mis à jour",
"account.profile.heading": "Profil", "account.profile.heading": "Profil",
@@ -954,7 +954,7 @@ enum L10n {
"account.confirm.password": "Passwort bestätigen", "account.confirm.password": "Passwort bestätigen",
"account.current.password": "Aktuelles Passwort", "account.current.password": "Aktuelles Passwort",
"account.name.label": "Name", "account.name.label": "Name",
"account.new.password": "Neues Passwort (mind. 8 Zeichen)", "account.new.password": "Neues Passwort (mind. 8, mindestens 3 Zeichenarten)",
"account.password.heading": "Passwort ändern", "account.password.heading": "Passwort ändern",
"account.password.saved": "Passwort aktualisiert", "account.password.saved": "Passwort aktualisiert",
"account.profile.heading": "Profil", "account.profile.heading": "Profil",
@@ -1245,7 +1245,7 @@ enum L10n {
"account.confirm.password": "Confirmar contraseña", "account.confirm.password": "Confirmar contraseña",
"account.current.password": "Contraseña actual", "account.current.password": "Contraseña actual",
"account.name.label": "Nombre", "account.name.label": "Nombre",
"account.new.password": "Nueva contraseña (mín. 8 caracteres)", "account.new.password": "Nueva contraseña (mín. 8, al menos 3 tipos de caracteres)",
"account.password.heading": "Cambiar contraseña", "account.password.heading": "Cambiar contraseña",
"account.password.saved": "Contraseña actualizada", "account.password.saved": "Contraseña actualizada",
"account.profile.heading": "Perfil", "account.profile.heading": "Perfil",