From 8a6e0c7e85b86eeb1fa55e43038801391a0b2508 Mon Sep 17 00:00:00 2001 From: Giuseppe Criscione <18699708+giuscris@users.noreply.github.com> Date: Tue, 8 Jul 2025 20:49:21 +0200 Subject: [PATCH 1/5] Remove unneeded pattern --- panel/modals/newUser.yaml | 2 +- panel/schemes/forms/register.yaml | 2 +- site/schemes/users/user.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/panel/modals/newUser.yaml b/panel/modals/newUser.yaml index 7fa31ea33..7270d6e32 100644 --- a/panel/modals/newUser.yaml +++ b/panel/modals/newUser.yaml @@ -20,7 +20,7 @@ fields: label: '{{user.password}}' suggestion: '{{panel.users.newUser.password.suggestion}}' required: true - pattern: '^.{8,}$' + minLength: 8 autocomplete: new-password email: diff --git a/panel/schemes/forms/register.yaml b/panel/schemes/forms/register.yaml index a9e2e357b..00ca9d08e 100644 --- a/panel/schemes/forms/register.yaml +++ b/panel/schemes/forms/register.yaml @@ -18,7 +18,7 @@ fields: label: '{{user.password}}' suggestion: '{{panel.users.newUser.password.suggestion}}' required: true - pattern: '^.{8,}$' + minLength: 8 autocomplete: new-password email: diff --git a/site/schemes/users/user.yaml b/site/schemes/users/user.yaml index 61b7a4965..5e5dc9b2f 100644 --- a/site/schemes/users/user.yaml +++ b/site/schemes/users/user.yaml @@ -23,7 +23,7 @@ fields: label: '{{user.password}}' placeholder: '{{user.password.placeholder}}' disabled: true - pattern: '^.{8,}$' + minLength: 8 autocomplete: new-password language: From 2a32031cff288f11ed4f4e08cd557233ce9ee7e4 Mon Sep 17 00:00:00 2001 From: Giuseppe Criscione <18699708+giuscris@users.noreply.github.com> Date: Tue, 8 Jul 2025 21:11:57 +0200 Subject: [PATCH 2/5] Use more reasonable username pattern --- panel/config/routes/routes.php | 6 +++--- panel/modals/newUser.yaml | 4 +++- panel/schemes/forms/register.yaml | 4 +++- panel/translations/de.yaml | 2 +- panel/translations/en.yaml | 2 +- panel/translations/es.yaml | 2 +- panel/translations/fr.yaml | 2 +- panel/translations/it.yaml | 2 +- panel/translations/pl.yaml | 2 +- panel/translations/pt.yaml | 2 +- panel/translations/ru.yaml | 2 +- panel/translations/uk.yaml | 2 +- 12 files changed, 18 insertions(+), 14 deletions(-) diff --git a/panel/config/routes/routes.php b/panel/config/routes/routes.php index 2375a775f..fa98d8aed 100644 --- a/panel/config/routes/routes.php +++ b/panel/config/routes/routes.php @@ -167,7 +167,7 @@ ], 'panel.users.profile' => [ - 'path' => '/users/{user:[a-z0-9_-]+}/profile/', + 'path' => '/users/{user:[a-zA-Z][a-zA-Z0-9]*(?:[-._][a-zA-Z0-9]+)*}/profile/', 'action' => 'Formwork\Panel\Controllers\UsersController@profile', 'methods' => ['GET', 'POST'], ], @@ -179,13 +179,13 @@ ], 'panel.users.delete' => [ - 'path' => '/users/{user:[a-z0-9_-]+}/delete/', + 'path' => '/users/{user:[a-zA-Z][a-zA-Z0-9]*(?:[-._][a-zA-Z0-9]+)*}/delete/', 'action' => 'Formwork\Panel\Controllers\UsersController@delete', 'methods' => ['POST'], ], 'panel.users.deleteImage' => [ - 'path' => '/users/{user:[a-z0-9_-]+}/image/delete/', + 'path' => '/users/{user:[a-zA-Z][a-zA-Z0-9]*(?:[-._][a-zA-Z0-9]+)*}/image/delete/', 'action' => 'Formwork\Panel\Controllers\UsersController@deleteImage', 'methods' => ['POST'], ], diff --git a/panel/modals/newUser.yaml b/panel/modals/newUser.yaml index 7270d6e32..d52fbd02b 100644 --- a/panel/modals/newUser.yaml +++ b/panel/modals/newUser.yaml @@ -13,7 +13,9 @@ fields: label: '{{user.username}}' suggestion: '{{panel.users.newUser.username.suggestion}}' required: true - pattern: '^[a-z0-9_\-]{3,20}$' + pattern: '[a-zA-Z][a-zA-Z0-9]*([\-._][a-zA-Z0-9]+)*' + minLength: 3 + maxLength: 20 password: type: password diff --git a/panel/schemes/forms/register.yaml b/panel/schemes/forms/register.yaml index 00ca9d08e..0d234d22f 100644 --- a/panel/schemes/forms/register.yaml +++ b/panel/schemes/forms/register.yaml @@ -11,7 +11,9 @@ fields: label: '{{user.username}}' suggestion: '{{panel.users.newUser.username.suggestion}}' required: true - pattern: '^[a-z0-9_\-]{3,20}$' + pattern: '[a-zA-Z][a-zA-Z0-9]*([\-._][a-zA-Z0-9]+)*' + minLength: 3 + maxLength: 20 password: type: password diff --git a/panel/translations/de.yaml b/panel/translations/de.yaml index 8ad452175..bda88ecf8 100644 --- a/panel/translations/de.yaml +++ b/panel/translations/de.yaml @@ -317,7 +317,7 @@ panel.users.deleteUser: Benutzer löschen panel.users.deleteUser.prompt: Möchten Sie diesen Benutzer wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden. panel.users.newUser: Neuer Benutzer panel.users.newUser.password.suggestion: mindestens 8 Zeichen -panel.users.newUser.username.suggestion: zwischen 3-20 Buchstaben, Ziffern und Bindestriche +panel.users.newUser.username.suggestion: zwischen 3-20 Buchstaben, Ziffern und - . _ panel.users.nextUser: Nächster Benutzer panel.users.options: Optionen panel.users.previousUser: Vorheriger Benutzer diff --git a/panel/translations/en.yaml b/panel/translations/en.yaml index d69384f95..6ca98c09f 100644 --- a/panel/translations/en.yaml +++ b/panel/translations/en.yaml @@ -317,7 +317,7 @@ panel.users.deleteUser: Delete user panel.users.deleteUser.prompt: Are you sure you want to delete this user? This action can’t be undone. panel.users.newUser: New user panel.users.newUser.password.suggestion: at least 8 characters -panel.users.newUser.username.suggestion: between 3-20 letters, digits and dashes +panel.users.newUser.username.suggestion: between 3-20 letters, digits and - . _ panel.users.nextUser: Next user panel.users.options: Options panel.users.previousUser: Previous user diff --git a/panel/translations/es.yaml b/panel/translations/es.yaml index 2c41e5259..f8a32e654 100644 --- a/panel/translations/es.yaml +++ b/panel/translations/es.yaml @@ -317,7 +317,7 @@ panel.users.deleteUser: Borrar usuario panel.users.deleteUser.prompt: ¿Estás seguro de que quieres borrar este usuario? Esta acción no se puede deshacer. panel.users.newUser: Nuevo usuario panel.users.newUser.password.suggestion: al menos 8 caracteres -panel.users.newUser.username.suggestion: entre 3 y 20 letras, dígitos y guiones +panel.users.newUser.username.suggestion: entre 3 y 20 letras, dígitos y - . _ panel.users.nextUser: Usuario siguiente panel.users.options: Opciones panel.users.previousUser: Usuario anterior diff --git a/panel/translations/fr.yaml b/panel/translations/fr.yaml index fef53b69c..abfca2de6 100644 --- a/panel/translations/fr.yaml +++ b/panel/translations/fr.yaml @@ -317,7 +317,7 @@ panel.users.deleteUser: Supprimer l’utilisateur panel.users.deleteUser.prompt: ÊtesVous sûr de vouloir supprimer cet utilisateur? Cette action est irréversible. panel.users.newUser: Nouvel utilisateur panel.users.newUser.password.suggestion: au moins 8 caractères -panel.users.newUser.username.suggestion: entre 3-20 lettres, chiffres et tirets +panel.users.newUser.username.suggestion: entre 3-20 lettres, chiffres et - . _ panel.users.nextUser: Utilisateur suivant panel.users.options: Options panel.users.previousUser: Utilisateur précédent diff --git a/panel/translations/it.yaml b/panel/translations/it.yaml index 53cbb52e6..2bc585448 100644 --- a/panel/translations/it.yaml +++ b/panel/translations/it.yaml @@ -317,7 +317,7 @@ panel.users.deleteUser: Elimina utente panel.users.deleteUser.prompt: Si è sicuri di eliminare l’utente? Questa azione non può essere annullata. panel.users.newUser: Nuovo utente panel.users.newUser.password.suggestion: almeno 8 caratteri -panel.users.newUser.username.suggestion: da 3 a 20 lettere, numeri, - e _ +panel.users.newUser.username.suggestion: da 3 a 20 lettere, numeri e - . _ panel.users.nextUser: Utente successivo panel.users.options: Opzioni panel.users.previousUser: Utente precedente diff --git a/panel/translations/pl.yaml b/panel/translations/pl.yaml index 72dd0073e..85191a6f4 100644 --- a/panel/translations/pl.yaml +++ b/panel/translations/pl.yaml @@ -317,7 +317,7 @@ panel.users.deleteUser: Usuń użytkownika panel.users.deleteUser.prompt: Czy na pewno chcesz usunąć tego użytkownika? Ta operacja jest nieodwracalna. panel.users.newUser: Nowy użytkownik panel.users.newUser.password.suggestion: co najmniej 8 znaków -panel.users.newUser.username.suggestion: od 3 do 20 liter, cyfr i myślników +panel.users.newUser.username.suggestion: od 3 do 20 liter, cyfr i - . _ panel.users.nextUser: Następny użytkownik panel.users.options: Opcje panel.users.previousUser: Poprzedni użytkownik diff --git a/panel/translations/pt.yaml b/panel/translations/pt.yaml index d3f1da875..f5b256348 100644 --- a/panel/translations/pt.yaml +++ b/panel/translations/pt.yaml @@ -317,7 +317,7 @@ panel.users.deleteUser: Apagar utilizador panel.users.deleteUser.prompt: Tem a certeza de que deseja excluir este utilizador? Esta acção não pode ser revertida. panel.users.newUser: Novo utilizador panel.users.newUser.password.suggestion: pelo menos 8 caracteres -panel.users.newUser.username.suggestion: entre 3 a 20 letras, números e hifens. +panel.users.newUser.username.suggestion: entre 3 a 20 letras, números e - . _ panel.users.nextUser: Utilizador seguinte panel.users.options: Opções panel.users.previousUser: Utilizador anterior diff --git a/panel/translations/ru.yaml b/panel/translations/ru.yaml index fd950a922..188c81e56 100644 --- a/panel/translations/ru.yaml +++ b/panel/translations/ru.yaml @@ -317,7 +317,7 @@ panel.users.deleteUser: Удалить пользователя panel.users.deleteUser.prompt: Вы уверены, что хотите удалить этого пользователя? Это действие не может быть отменено. panel.users.newUser: Новый пользователь panel.users.newUser.password.suggestion: не менее 8 символов -panel.users.newUser.username.suggestion: между 3-20 букв, цифр и тире +panel.users.newUser.username.suggestion: между 3-20 букв, цифр и - . _ panel.users.nextUser: Следующий пользователь panel.users.options: Параметры panel.users.previousUser: Предыдущий пользователь diff --git a/panel/translations/uk.yaml b/panel/translations/uk.yaml index 9d87bf6f6..db5c7127a 100644 --- a/panel/translations/uk.yaml +++ b/panel/translations/uk.yaml @@ -317,7 +317,7 @@ panel.users.deleteUser: Видалити користувача panel.users.deleteUser.prompt: Ви впевнені, що хочете видалити цього користувача? Цю дію не можна скасувати. panel.users.newUser: Новий користувач panel.users.newUser.password.suggestion: щонайменше 8 символів -panel.users.newUser.username.suggestion: від 3 до 20 літер, цифр та дефісів +panel.users.newUser.username.suggestion: від 3 до 20 літер, цифр та - . _ panel.users.nextUser: Наступний користувач panel.users.options: Налаштування panel.users.previousUser: Попередній користувач From b55479b93b3e22aff8ed2df783e6ad77d7cd22b5 Mon Sep 17 00:00:00 2001 From: Giuseppe Criscione <18699708+giuscris@users.noreply.github.com> Date: Tue, 8 Jul 2025 21:26:34 +0200 Subject: [PATCH 3/5] Ensure email addresses are not used by different accounts --- formwork/src/Panel/Controllers/UsersController.php | 13 +++++++++++++ panel/translations/de.yaml | 2 ++ panel/translations/en.yaml | 2 ++ panel/translations/es.yaml | 2 ++ panel/translations/fr.yaml | 2 ++ panel/translations/it.yaml | 2 ++ panel/translations/pl.yaml | 2 ++ panel/translations/pt.yaml | 2 ++ panel/translations/ru.yaml | 2 ++ panel/translations/uk.yaml | 2 ++ 10 files changed, 31 insertions(+) diff --git a/formwork/src/Panel/Controllers/UsersController.php b/formwork/src/Panel/Controllers/UsersController.php index c281eef5f..c054b96dd 100644 --- a/formwork/src/Panel/Controllers/UsersController.php +++ b/formwork/src/Panel/Controllers/UsersController.php @@ -64,6 +64,14 @@ public function create(): Response return $this->redirect($this->generateRoute('panel.users')); } + $email = $data->get('email'); + + // Ensure email is not already used by another user + if ($this->site->users()->filterBy('email', $email)->count() > 0) { + $this->panel->notify($this->translate('panel.users.user.cannotCreate.emailAlreadyUsed'), 'error'); + return $this->redirect($this->generateRoute('panel.users')); + } + Yaml::encodeToFile([ 'username' => $username, 'fullname' => $data->get('fullname'), @@ -227,6 +235,11 @@ private function updateUser(User $user, FieldCollection $fieldCollection): void continue; } + // Ensure email is not already used by another user + if ($field->name() === 'email' && $field->value() !== $user->email() && $this->site->users()->filterBy('email', $field->value())->count() > 0) { + throw new TranslatedException(sprintf('Cannot change the email of %s, the address is already used', $user->username()), 'panel.users.user.cannotChangeEmail.alreadyUsed'); + } + if ($field->name() === 'password') { // Ensure that password can be changed if (!$this->panel->user()->canChangePasswordOf($user)) { diff --git a/panel/translations/de.yaml b/panel/translations/de.yaml index bda88ecf8..dcf88f0fa 100644 --- a/panel/translations/de.yaml +++ b/panel/translations/de.yaml @@ -321,9 +321,11 @@ panel.users.newUser.username.suggestion: zwischen 3-20 Buchstaben, Ziffern und - panel.users.nextUser: Nächster Benutzer panel.users.options: Optionen panel.users.previousUser: Vorheriger Benutzer +panel.users.user.cannotChangeEmail.alreadyUsed: E-Mail des Benutzers kann nicht geändert werden, die Adresse ist bereits mit einem Konto verknüpft panel.users.user.cannotChangePassword: Das Passwort eines anderen Benutzers kann nicht geändert werden. Die Aktion ist nicht erlaubt. panel.users.user.cannotChangeRole: Die Rolle von %s kann nicht geändert werden. Die Aktion ist nicht erlaubt. panel.users.user.cannotCreate.alreadyExists: Benutzer kann nicht erstellt werden, ein Benutzer mit demselben Namen existiert bereits +panel.users.user.cannotCreate.emailAlreadyUsed: Benutzer kann nicht erstellt werden, die E-Mail-Adresse ist bereits mit einem Konto verknüpft panel.users.user.cannotCreate.varMissing: Benutzer kann nicht erstellt werden, eine Variable fehlt panel.users.user.cannotDelete: Benutzer kann nicht gelöscht werden. Sie müssen ein Administrator sein und der Benutzer darf nicht angemeldet sein. panel.users.user.cannotEdit: "%s Benutzer kann nicht bearbeitet werden. Die Aktion ist nicht erlaubt." diff --git a/panel/translations/en.yaml b/panel/translations/en.yaml index 6ca98c09f..3424e57d8 100644 --- a/panel/translations/en.yaml +++ b/panel/translations/en.yaml @@ -321,9 +321,11 @@ panel.users.newUser.username.suggestion: between 3-20 letters, digits and - . _ panel.users.nextUser: Next user panel.users.options: Options panel.users.previousUser: Previous user +panel.users.user.cannotChangeEmail.alreadyUsed: Cannot change the user email, the address is already associated with an account panel.users.user.cannotChangePassword: Cannot change the password of another user. The action is not allowed. panel.users.user.cannotChangeRole: Cannot change the role of %s. The action is not allowed. panel.users.user.cannotCreate.alreadyExists: Cannot create user, a user with the same name already exists +panel.users.user.cannotCreate.emailAlreadyUsed: Cannot create user, the email address is already associated with an account panel.users.user.cannotCreate.varMissing: Cannot create user, missing a variable panel.users.user.cannotDelete: Cannot delete user. You must be an administrator and the user must not be logged in. panel.users.user.cannotEdit: Cannot edit user %s. The action is not allowed. diff --git a/panel/translations/es.yaml b/panel/translations/es.yaml index f8a32e654..cf6ce8538 100644 --- a/panel/translations/es.yaml +++ b/panel/translations/es.yaml @@ -321,9 +321,11 @@ panel.users.newUser.username.suggestion: entre 3 y 20 letras, dígitos y - . _ panel.users.nextUser: Usuario siguiente panel.users.options: Opciones panel.users.previousUser: Usuario anterior +panel.users.user.cannotChangeEmail.alreadyUsed: No se puede cambiar el correo del usuario, la dirección ya está asociada a una cuenta panel.users.user.cannotChangePassword: No se puede cambiar la contraseña de otro usuario. La acción no está permitida. panel.users.user.cannotChangeRole: No se puede cambiar el rol de %s. La acción no está permitida. panel.users.user.cannotCreate.alreadyExists: No se puede crear el usuario, ya existe un usuario con el mismo nombre +panel.users.user.cannotCreate.emailAlreadyUsed: No se puede crear el usuario, la dirección de correo ya está asociada a una cuenta panel.users.user.cannotCreate.varMissing: No se puede crear el usuario, falta una variable panel.users.user.cannotDelete: No se puede borrar el usuario. Debes ser un administrador y el usuario no debe estar conectado. panel.users.user.cannotEdit: No se puede editar el usuario %s. La acción no está permitida. diff --git a/panel/translations/fr.yaml b/panel/translations/fr.yaml index abfca2de6..97109b136 100644 --- a/panel/translations/fr.yaml +++ b/panel/translations/fr.yaml @@ -321,9 +321,11 @@ panel.users.newUser.username.suggestion: entre 3-20 lettres, chiffres et - . _ panel.users.nextUser: Utilisateur suivant panel.users.options: Options panel.users.previousUser: Utilisateur précédent +panel.users.user.cannotChangeEmail.alreadyUsed: Impossible de modifier l’e-mail de l’utilisateur, l’adresse est déjà associée à un compte panel.users.user.cannotChangePassword: Impossible de changer le mot de passe d’un autre utilisateur. L’action n’est pas autorisée. panel.users.user.cannotChangeRole: Impossible de changer le rôle de %s. L’action n’est pas autorisée. panel.users.user.cannotCreate.alreadyExists: Impossible de créer l’utilisateur, un utilisateur portant le même nom existe déjà +panel.users.user.cannotCreate.emailAlreadyUsed: Impossible de créer l’utilisateur, l’adresse e-mail est déjà associée à un compte panel.users.user.cannotCreate.varMissing: Impossible de créer l’utilisateur, il manque une variable panel.users.user.cannotDelete: Impossible de supprimer l’utilisateur. Vous devez être un administrateur et l’utilisateur ne doit pas être connecté. panel.users.user.cannotEdit: Impossible de modifier l’utilisateur %s. L’action n’est pas autorisée. diff --git a/panel/translations/it.yaml b/panel/translations/it.yaml index 2bc585448..557663229 100644 --- a/panel/translations/it.yaml +++ b/panel/translations/it.yaml @@ -321,9 +321,11 @@ panel.users.newUser.username.suggestion: da 3 a 20 lettere, numeri e - . _ panel.users.nextUser: Utente successivo panel.users.options: Opzioni panel.users.previousUser: Utente precedente +panel.users.user.cannotChangeEmail.alreadyUsed: Impossibile modificare l’email dell’utente, l’indirizzo è già associato a un account panel.users.user.cannotChangePassword: Impossibile cambiare la password di un altro utente. L’azione non è consentita. panel.users.user.cannotChangeRole: Impossible cambare il ruolo di %s. L’azione non è consentita. panel.users.user.cannotCreate.alreadyExists: Impossibile creare l’utente, un utente con lo stesso nome esiste già +panel.users.user.cannotCreate.emailAlreadyUsed: Impossibile creare l’utente, l’indirizzo email è già associato a un account panel.users.user.cannotCreate.varMissing: Impossibile creare l’utente, manca una variabile panel.users.user.cannotDelete: Impossibile eliminare l’utente. Devi essere un amministratore e l’utente non deve essere connesso. panel.users.user.cannotEdit: Impossibile modificare l’utente %s. L’azione non è consentita. diff --git a/panel/translations/pl.yaml b/panel/translations/pl.yaml index 85191a6f4..c4b744e14 100644 --- a/panel/translations/pl.yaml +++ b/panel/translations/pl.yaml @@ -321,9 +321,11 @@ panel.users.newUser.username.suggestion: od 3 do 20 liter, cyfr i - . _ panel.users.nextUser: Następny użytkownik panel.users.options: Opcje panel.users.previousUser: Poprzedni użytkownik +panel.users.user.cannotChangeEmail.alreadyUsed: Nie można zmienić adresu e-mail użytkownika, adres jest już powiązany z kontem panel.users.user.cannotChangePassword: Nie można zmienić hasła innego użytkownika. Ta operacja jest niedozwolona. panel.users.user.cannotChangeRole: Nie można zmienić roli użytkownika %s. Ta operacja jest niedozwolona. panel.users.user.cannotCreate.alreadyExists: Nie można utworzyć użytkownika, użytkownik o tej samej nazwie już istnieje +panel.users.user.cannotCreate.emailAlreadyUsed: Nie można utworzyć użytkownika, adres e-mail jest już powiązany z kontem panel.users.user.cannotCreate.varMissing: Nie można utworzyć użytkownika, brak zmiennej panel.users.user.cannotDelete: Nie można usunąć użytkownika. Musisz być administratorem, a użytkownik nie może być zalogowany. panel.users.user.cannotEdit: Nie można edytować użytkownika %s. Ta operacja jest niedozwolona. diff --git a/panel/translations/pt.yaml b/panel/translations/pt.yaml index f5b256348..510164149 100644 --- a/panel/translations/pt.yaml +++ b/panel/translations/pt.yaml @@ -321,9 +321,11 @@ panel.users.newUser.username.suggestion: entre 3 a 20 letras, números e - . _ panel.users.nextUser: Utilizador seguinte panel.users.options: Opções panel.users.previousUser: Utilizador anterior +panel.users.user.cannotChangeEmail.alreadyUsed: Não é possível alterar o e-mail do utilizador, o endereço já está associado a uma conta panel.users.user.cannotChangePassword: Não é possível alterar a senha de outro utilizador. A acção não é permitida. panel.users.user.cannotChangeRole: Não é possível alterar o cargo de %s. A acção não é permitida. panel.users.user.cannotCreate.alreadyExists: Não é possível criar utilizador, já existe um utilizador com o mesmo nome +panel.users.user.cannotCreate.emailAlreadyUsed: Não é possível criar o utilizador, o endereço de e-mail já está associado a uma conta panel.users.user.cannotCreate.varMissing: Não é possível criar o utilizador, falta uma variável panel.users.user.cannotDelete: Não é possível remover o utilizador. Você deve ser um administrador e o utilizador não deve estar logado. panel.users.user.cannotEdit: Não é possível editar o utilizador %s. A acção não é permitida. diff --git a/panel/translations/ru.yaml b/panel/translations/ru.yaml index 188c81e56..3fdebc5dd 100644 --- a/panel/translations/ru.yaml +++ b/panel/translations/ru.yaml @@ -321,9 +321,11 @@ panel.users.newUser.username.suggestion: между 3-20 букв, цифр и - panel.users.nextUser: Следующий пользователь panel.users.options: Параметры panel.users.previousUser: Предыдущий пользователь +panel.users.user.cannotChangeEmail.alreadyUsed: Невозможно изменить адрес электронной почты, он уже связан с другим аккаунтом panel.users.user.cannotChangePassword: Невозможно изменить пароль другого пользователя. Действие не допускается. panel.users.user.cannotChangeRole: Невозможно изменить роль %s. Действие не допускается. panel.users.user.cannotCreate.alreadyExists: Невозможно создать пользователь, пользователь с таким именем уже существует +panel.users.user.cannotCreate.emailAlreadyUsed: Невозможно создать пользователя, адрес электронной почты уже связан с другим аккаунтом panel.users.user.cannotCreate.varMissing: Невозможно создать пользователя, пропуская переменную panel.users.user.cannotDelete: Невозможно удалить пользователь. Вы должны быть администратором, и пользователь не должен быть авторизован. panel.users.user.cannotEdit: Невозможно изменить пользователя %s. Действие не допускается. diff --git a/panel/translations/uk.yaml b/panel/translations/uk.yaml index db5c7127a..99f7c2378 100644 --- a/panel/translations/uk.yaml +++ b/panel/translations/uk.yaml @@ -321,9 +321,11 @@ panel.users.newUser.username.suggestion: від 3 до 20 літер, цифр panel.users.nextUser: Наступний користувач panel.users.options: Налаштування panel.users.previousUser: Попередній користувач +panel.users.user.cannotChangeEmail.alreadyUsed: Неможливо змінити електронну адресу користувача, ця адреса вже використовується panel.users.user.cannotChangePassword: Неможливо змінити пароль іншого користувача. Ця дія заборонена. panel.users.user.cannotChangeRole: Неможливо змінити роль %s. Ця дія заборонена. panel.users.user.cannotCreate.alreadyExists: Неможливо створити користувача, користувач з таким ім'ям вже існує +panel.users.user.cannotCreate.emailAlreadyUsed: Неможливо створити користувача, електронна адреса вже прив’язана до облікового запису panel.users.user.cannotCreate.varMissing: Неможливо створити користувача, відсутня змінна panel.users.user.cannotDelete: Неможливо видалити користувача. Ви повинні бути адміністратором, і користувач не повинен бути в системі. panel.users.user.cannotEdit: Неможливо редагувати користувача %s. Ця дія заборонена. From e9d58afa78884480a323f8772de0d8b10ee6d8ec Mon Sep 17 00:00:00 2001 From: Giuseppe Criscione <18699708+giuscris@users.noreply.github.com> Date: Wed, 9 Jul 2025 01:33:24 +0200 Subject: [PATCH 4/5] Refactor login and allow using e-mail to authenticate --- formwork/fields/text.php | 11 +++++++ .../Controllers/AuthenticationController.php | 32 +++++++++++-------- panel/schemes/forms/login.yaml | 14 ++++++++ panel/src/scss/components/_login.scss | 26 +++++++-------- panel/translations/de.yaml | 2 +- panel/translations/en.yaml | 2 +- panel/translations/es.yaml | 2 +- panel/translations/fr.yaml | 2 +- panel/translations/it.yaml | 2 +- panel/translations/pl.yaml | 2 +- panel/translations/pt.yaml | 2 +- panel/translations/ru.yaml | 2 +- panel/translations/uk.yaml | 2 +- panel/views/authentication/login.php | 13 ++++---- panel/views/fields/email.php | 27 ++++++++-------- panel/views/fields/password.php | 12 +++---- panel/views/fields/text.php | 27 ++++++++-------- panel/views/layouts/login.php | 12 ++++--- panel/views/partials/login/notification.php | 8 ++--- panel/views/register/register.php | 6 ++-- 20 files changed, 121 insertions(+), 85 deletions(-) create mode 100644 panel/schemes/forms/login.yaml diff --git a/formwork/fields/text.php b/formwork/fields/text.php index 0f925013e..af2acdb51 100644 --- a/formwork/fields/text.php +++ b/formwork/fields/text.php @@ -36,6 +36,17 @@ return $field->get('pattern'); }, + /** + * Return the autocomplete attribute for the field + * + * This is used to specify the type of data that the field represents, + * which can help browsers to provide better autocomplete suggestions. + * If not set, no autocomplete attribute is added. + */ + 'autocomplete' => function (Field $field): ?string { + return $field->get('autocomplete'); + }, + 'validate' => function (Field $field, $value): string { if (Constraint::isEmpty($value)) { return ''; diff --git a/formwork/src/Panel/Controllers/AuthenticationController.php b/formwork/src/Panel/Controllers/AuthenticationController.php index 78518c7b0..cac01fd21 100644 --- a/formwork/src/Panel/Controllers/AuthenticationController.php +++ b/formwork/src/Panel/Controllers/AuthenticationController.php @@ -2,12 +2,14 @@ namespace Formwork\Panel\Controllers; +use Formwork\Fields\Exceptions\ValidationException; use Formwork\Http\RedirectResponse; use Formwork\Http\RequestMethod; use Formwork\Http\Response; use Formwork\Log\Log; use Formwork\Log\Registry; use Formwork\Panel\Security\AccessLimiter; +use Formwork\Schemes\Schemes; use Formwork\Users\Exceptions\AuthenticationFailedException; use Formwork\Users\Exceptions\UserNotLoggedException; use Formwork\Users\User; @@ -23,18 +25,20 @@ final class AuthenticationController extends AbstractController /** * Authentication@login action */ - public function login(AccessLimiter $accessLimiter): Response + public function login(AccessLimiter $accessLimiter, Schemes $schemes): Response { if ($this->panel->isLoggedIn()) { return $this->redirect($this->generateRoute('panel.index')); } + $fields = $schemes->get('forms.login')->fields(); + $csrfTokenName = $this->panel->getCsrfTokenName(); if ($accessLimiter->hasReachedLimit()) { $minutes = round($this->config->get('system.panel.loginResetTime') / 60); $this->csrfToken->generate($csrfTokenName); - return $this->error($this->translate('panel.login.attempt.tooMany', $minutes)); + return $this->error($this->translate('panel.login.attempt.tooMany', $minutes), ['fields' => $fields]); } if ($this->request->method() === RequestMethod::POST) { @@ -43,18 +47,20 @@ public function login(AccessLimiter $accessLimiter): Response $data = $this->request->input(); - // Ensure no required data is missing - if (!$data->hasMultiple(['username', 'password'])) { + try { + $fields->setValues($data)->validate(); + } catch (ValidationException) { + // If validation fails, generate a new CSRF token and return an error $this->csrfToken->generate($csrfTokenName); - $this->error($this->translate('panel.login.attempt.failed')); + $this->error($this->translate('panel.login.attempt.failed'), ['fields' => $fields]); } $accessLimiter->registerAttempt(); - $username = $data->get('username'); + $login = $data->get('login'); /** @var ?User */ - $user = $this->site->users()->get($username); + $user = $this->site->users()->find(fn($user) => $user->username() === $login || $user->email() === $login); // Authenticate user if ($user !== null) { @@ -67,6 +73,8 @@ public function login(AccessLimiter $accessLimiter): Response $accessLog = new Log(FileSystem::joinPaths($this->config->get('system.panel.paths.logs'), 'access.json')); $lastAccessRegistry = new Registry(FileSystem::joinPaths($this->config->get('system.panel.paths.logs'), 'lastAccess.json')); + $username = $user->username(); + $time = $accessLog->log($username); $lastAccessRegistry->set($username, $time); @@ -85,17 +93,15 @@ public function login(AccessLimiter $accessLimiter): Response $this->csrfToken->generate($csrfTokenName); - return $this->error($this->translate('panel.login.attempt.failed'), [ - 'username' => $username, - 'error' => true, - ]); + return $this->error($this->translate('panel.login.attempt.failed'), ['fields' => $fields]); } // Always generate a new CSRF token $this->csrfToken->generate($csrfTokenName); return new Response($this->view('authentication.login', [ - 'title' => $this->translate('panel.login.login'), + 'title' => $this->translate('panel.login.login'), + 'fields' => $fields, ])); } @@ -127,7 +133,7 @@ public function logout(): RedirectResponse */ private function error(string $message, array $data = []): Response { - $defaults = ['title' => $this->translate('panel.login.login')]; + $defaults = ['title' => $this->translate('panel.login.login'), 'error' => true]; $this->panel->notify($message, 'error'); return new Response($this->view('authentication.login', [...$defaults, ...$data])); } diff --git a/panel/schemes/forms/login.yaml b/panel/schemes/forms/login.yaml new file mode 100644 index 000000000..9629097a9 --- /dev/null +++ b/panel/schemes/forms/login.yaml @@ -0,0 +1,14 @@ +title: Login + +fields: + login: + type: text + label: '{{panel.login.usernameOrEmail}}' + required: true + autocomplete: username + + password: + type: password + label: '{{panel.login.password}}' + required: true + autocomplete: current-password diff --git a/panel/src/scss/components/_login.scss b/panel/src/scss/components/_login.scss index f4ad7a6b2..b59bd4f71 100644 --- a/panel/src/scss/components/_login.scss +++ b/panel/src/scss/components/_login.scss @@ -1,27 +1,23 @@ @use "mixins" as *; -.login-modal-container { +.login-container { max-width: 24rem; - padding: 2rem; - border-radius: var(--border-radius); margin: 3rem 0.5rem; - background-color: var(--color-base-900); - box-shadow: var(--box-shadow-sm); } @media (width >= 24rem) { - .login-modal-container { + .login-container { margin: 3rem auto; } } -.login-modal-info, -.login-modal-success, -.login-modal-warning, -.login-modal-danger { +.login-notification-info, +.login-notification-success, +.login-notification-warning, +.login-notification-danger { position: relative; padding: 1rem 2rem 1rem 3.5rem; - margin: -2rem -2rem 1rem; + margin: -1.5rem -1.75rem 1rem; border-top-left-radius: var(--border-radius); border-top-right-radius: var(--border-radius); @@ -41,22 +37,22 @@ } } -.login-modal-info { +.login-notification-info { background-color: var(--color-accent-500); color: var(--color-white); } -.login-modal-success { +.login-notification-success { background-color: var(--color-success-400); color: var(--color-white); } -.login-modal-warning { +.login-notification-warning { background-color: var(--color-warning-500); color: var(--color-white); } -.login-modal-danger { +.login-notification-danger { background-color: var(--color-danger-500); color: var(--color-white); } diff --git a/panel/translations/de.yaml b/panel/translations/de.yaml index dcf88f0fa..93754b26d 100644 --- a/panel/translations/de.yaml +++ b/panel/translations/de.yaml @@ -117,7 +117,7 @@ panel.login.login: Anmelden panel.login.logout: Abmelden panel.login.password: Passwort panel.login.suspiciousRequestDetected: Es wurde eine verdächtige Anfrage festgestellt, und aus Sicherheitsgründen wurden Sie abgemeldet. Bitte melden Sie sich erneut an. -panel.login.username: Benutzername +panel.login.usernameOrEmail: Benutzername oder E-Mail panel.manage: Verwalten panel.modal.action.cancel: Abbrechen panel.modal.action.continue: Fortfahren diff --git a/panel/translations/en.yaml b/panel/translations/en.yaml index 3424e57d8..c87c86c4d 100644 --- a/panel/translations/en.yaml +++ b/panel/translations/en.yaml @@ -117,7 +117,7 @@ panel.login.login: Login panel.login.logout: Logout panel.login.password: Password panel.login.suspiciousRequestDetected: A suspicious request has been detected, and for security reasons you have been logged out. Please log in again. -panel.login.username: Username +panel.login.usernameOrEmail: Username or e-mail panel.manage: Manage panel.modal.action.cancel: Cancel panel.modal.action.continue: Continue diff --git a/panel/translations/es.yaml b/panel/translations/es.yaml index cf6ce8538..d10cdcdf4 100644 --- a/panel/translations/es.yaml +++ b/panel/translations/es.yaml @@ -117,7 +117,7 @@ panel.login.login: Iniciar sesión panel.login.logout: Cerrar sesión panel.login.password: Contraseña panel.login.suspiciousRequestDetected: Se detectó una solicitud sospechosa y, por razones de seguridad, se ha cerrado tu sesión. Por favor, inicia sesión nuevamente. -panel.login.username: Nombre de usuario +panel.login.usernameOrEmail: Nombre de usuario o email panel.manage: Gestionar panel.modal.action.cancel: Cancelar panel.modal.action.continue: Continuar diff --git a/panel/translations/fr.yaml b/panel/translations/fr.yaml index 97109b136..bc6d91975 100644 --- a/panel/translations/fr.yaml +++ b/panel/translations/fr.yaml @@ -117,7 +117,7 @@ panel.login.login: S’identifier panel.login.logout: Déconnexion panel.login.password: Mot de passe panel.login.suspiciousRequestDetected: Une demande suspecte a été détectée et pour des raisons de sécurité, vous avez été déconnecté. Veuillez vous reconnecter. -panel.login.username: Nom d’utilisateur +panel.login.usernameOrEmail: Nom d’utilisateur ou e-mail panel.manage: Gérer panel.modal.action.cancel: Annuler panel.modal.action.continue: Continuer diff --git a/panel/translations/it.yaml b/panel/translations/it.yaml index 557663229..63e431bd6 100644 --- a/panel/translations/it.yaml +++ b/panel/translations/it.yaml @@ -117,7 +117,7 @@ panel.login.login: Accedi panel.login.logout: Esci panel.login.password: Password panel.login.suspiciousRequestDetected: È stata rilevata una richiesta sospetta e per ragioni di sicurezza si è usciti dalla sessione. Effettua nuovamente l’accesso. -panel.login.username: Nome utente +panel.login.usernameOrEmail: Nome utente o e-mail panel.manage: Gestione panel.modal.action.cancel: Annulla panel.modal.action.continue: Continua diff --git a/panel/translations/pl.yaml b/panel/translations/pl.yaml index c4b744e14..6fa86c71a 100644 --- a/panel/translations/pl.yaml +++ b/panel/translations/pl.yaml @@ -117,7 +117,7 @@ panel.login.login: Zaloguj się panel.login.logout: Wyloguj się panel.login.password: Hasło panel.login.suspiciousRequestDetected: Wykryto podejrzane żądanie, dlatego z powodów bezpieczeństwa zostałeś wylogowany. Zaloguj się ponownie. -panel.login.username: Nazwa użytkownika +panel.login.usernameOrEmail: Nazwa użytkownika lub e-mail panel.manage: Zarządzaj panel.modal.action.cancel: Anuluj panel.modal.action.continue: Kontynuuj diff --git a/panel/translations/pt.yaml b/panel/translations/pt.yaml index 510164149..d06173b80 100644 --- a/panel/translations/pt.yaml +++ b/panel/translations/pt.yaml @@ -117,7 +117,7 @@ panel.login.login: Login panel.login.logout: Logout panel.login.password: Password panel.login.suspiciousRequestDetected: Uma solicitação suspeita foi detectada e, por motivos de segurança, você foi desconectado. Por favor faça login novamente. -panel.login.username: Nome do utilizador +panel.login.usernameOrEmail: Nome de utilizador ou e-mail panel.manage: Administrar panel.modal.action.cancel: Cancelar panel.modal.action.continue: Continuar diff --git a/panel/translations/ru.yaml b/panel/translations/ru.yaml index 3fdebc5dd..239ac8b6c 100644 --- a/panel/translations/ru.yaml +++ b/panel/translations/ru.yaml @@ -117,7 +117,7 @@ panel.login.login: Авторизация panel.login.logout: Выйти panel.login.password: Пароль panel.login.suspiciousRequestDetected: Подозрительный запрос был обнаружен, и по соображениям безопасности вы вышли из системы. Пожалуйста, войдите снова. -panel.login.username: Имя пользователя +panel.login.usernameOrEmail: Имя пользователя или e-mail panel.manage: Меню panel.modal.action.cancel: Отмена panel.modal.action.continue: Продолжить diff --git a/panel/translations/uk.yaml b/panel/translations/uk.yaml index 99f7c2378..25925b7d5 100644 --- a/panel/translations/uk.yaml +++ b/panel/translations/uk.yaml @@ -117,7 +117,7 @@ panel.login.login: Увійти panel.login.logout: Вийти panel.login.password: Пароль panel.login.suspiciousRequestDetected: Виявлено підозрілий запит, і з міркувань безпеки ви вийшли з системи. Будь ласка, увійдіть знову. -panel.login.username: Ім'я користувача +panel.login.usernameOrEmail: Ім’я користувача або e-mail panel.manage: Керувати panel.modal.action.cancel: Скасувати panel.modal.action.continue: Продовжити diff --git a/panel/views/authentication/login.php b/panel/views/authentication/login.php index 346156214..60cd7d63f 100644 --- a/panel/views/authentication/login.php +++ b/panel/views/authentication/login.php @@ -1,10 +1,11 @@ layout('login') ?> -
translate('panel.login.login') ?>
+
+
translate('panel.login.login') ?>
+
- - value="" autofocus maxlength="20"> - - autofocus id="password" type="password" required name="password"> + + insert('fields.' . $field->type(), ['field' => $field]) ?> + -
\ No newline at end of file + diff --git a/panel/views/fields/email.php b/panel/views/fields/email.php index 98f0bbb82..eb4611595 100644 --- a/panel/views/fields/email.php +++ b/panel/views/fields/email.php @@ -4,17 +4,18 @@ icon($field->get('icon')) ?> attr([ - 'class' => 'form-input', - 'type' => 'email', - 'id' => $field->name(), - 'name' => $field->formName(), - 'value' => $field->value(), - 'placeholder' => $field->placeholder(), - 'minlength' => $field->minLength(), - 'maxlength' => $field->maxLength(), - 'pattern' => $field->pattern(), - 'required' => $field->isRequired(), - 'disabled' => $field->isDisabled(), - 'hidden' => $field->isHidden(), + 'class' => 'form-input', + 'type' => 'email', + 'id' => $field->name(), + 'name' => $field->formName(), + 'value' => $field->value(), + 'placeholder' => $field->placeholder(), + 'minlength' => $field->minLength(), + 'maxlength' => $field->maxLength(), + 'pattern' => $field->pattern(), + 'autocomplete' => $field->autocomplete(), + 'required' => $field->isRequired(), + 'disabled' => $field->isDisabled(), + 'hidden' => $field->isHidden(), ]) ?>> - \ No newline at end of file + diff --git a/panel/views/fields/password.php b/panel/views/fields/password.php index e8ab42252..a4a611e38 100644 --- a/panel/views/fields/password.php +++ b/panel/views/fields/password.php @@ -4,18 +4,18 @@ icon($field->get('icon')) ?> attr([ - 'class' => 'form-input', + 'class' => 'form-input', 'type' => 'password', 'id' => $field->name(), 'name' => $field->formName(), 'value' => $field->value(), 'placeholder' => $field->placeholder(), - 'minlength' => $field->minLength(), - 'maxlength' => $field->maxLength(), - 'pattern' => $field->pattern(), - 'autocomplete' => $field->get('autocomplete'), + 'minlength' => $field->minLength(), + 'maxlength' => $field->maxLength(), + 'pattern' => $field->pattern(), + 'autocomplete' => $field->autocomplete(), 'required' => $field->isRequired(), 'disabled' => $field->isDisabled(), 'hidden' => $field->isHidden(), ]) ?>> - \ No newline at end of file + diff --git a/panel/views/fields/text.php b/panel/views/fields/text.php index 73ea74d29..6e3a73e5d 100644 --- a/panel/views/fields/text.php +++ b/panel/views/fields/text.php @@ -4,17 +4,18 @@ icon($field->get('icon')) ?> attr([ - 'class' => ['form-input', $field->get('class')], - 'type' => 'text', - 'id' => $field->name(), - 'name' => $field->formName(), - 'value' => $field->value(), - 'placeholder' => $field->placeholder(), - 'minlength' => $field->minLength(), - 'maxlength' => $field->maxLength(), - 'pattern' => $field->pattern(), - 'required' => $field->isRequired(), - 'disabled' => $field->isDisabled(), - 'hidden' => $field->isHidden(), + 'class' => ['form-input', $field->get('class')], + 'type' => 'text', + 'id' => $field->name(), + 'name' => $field->formName(), + 'value' => $field->value(), + 'placeholder' => $field->placeholder(), + 'minlength' => $field->minLength(), + 'maxlength' => $field->maxLength(), + 'pattern' => $field->pattern(), + 'autocomplete' => $field->autocomplete(), + 'required' => $field->isRequired(), + 'disabled' => $field->isDisabled(), + 'hidden' => $field->isHidden(), ]) ?>> - \ No newline at end of file + diff --git a/panel/views/layouts/login.php b/panel/views/layouts/login.php index 8ffbb246f..8ec4ded52 100644 --- a/panel/views/layouts/login.php +++ b/panel/views/layouts/login.php @@ -17,9 +17,13 @@
-
@@ -27,4 +31,4 @@ insert('partials.scripts') ?> - \ No newline at end of file + diff --git a/panel/views/partials/login/notification.php b/panel/views/partials/login/notification.php index 4995ca579..ba7e9ab13 100644 --- a/panel/views/partials/login/notification.php +++ b/panel/views/partials/login/notification.php @@ -1,11 +1,11 @@ notifications()) : ?> -
icon('exclamation-octagon') ?> escape($notification[0]['text']) ?>
+
icon('exclamation-octagon') ?> escape($notification[0]['text']) ?>
-
icon('exclamation-triangle') ?> escape($notification[0]['text']) ?>
+
icon('exclamation-triangle') ?> escape($notification[0]['text']) ?>
-
icon('check-circle') ?> escape($notification[0]['text']) ?>
+
icon('check-circle') ?> escape($notification[0]['text']) ?>
-
icon('info-circle') ?> escape($notification[0]['text']) ?>
+
icon('info-circle') ?> escape($notification[0]['text']) ?>
diff --git a/panel/views/register/register.php b/panel/views/register/register.php index 6c3a9aaf3..9db08cb04 100644 --- a/panel/views/register/register.php +++ b/panel/views/register/register.php @@ -1,5 +1,7 @@ layout('login') ?> -
translate('panel.register.register') ?>
+
+
translate('panel.register.register') ?>
+

translate('panel.register.createUser') ?>

@@ -7,4 +9,4 @@ -
\ No newline at end of file + From f1bb6f8f4da351450fbb4e61987dfb71a0130ace Mon Sep 17 00:00:00 2001 From: Giuseppe Criscione <18699708+giuscris@users.noreply.github.com> Date: Sat, 12 Jul 2025 12:57:08 +0200 Subject: [PATCH 5/5] Update formwork/src/Panel/Controllers/AuthenticationController.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- formwork/src/Panel/Controllers/AuthenticationController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/formwork/src/Panel/Controllers/AuthenticationController.php b/formwork/src/Panel/Controllers/AuthenticationController.php index cac01fd21..efadd4ae8 100644 --- a/formwork/src/Panel/Controllers/AuthenticationController.php +++ b/formwork/src/Panel/Controllers/AuthenticationController.php @@ -52,7 +52,7 @@ public function login(AccessLimiter $accessLimiter, Schemes $schemes): Response } catch (ValidationException) { // If validation fails, generate a new CSRF token and return an error $this->csrfToken->generate($csrfTokenName); - $this->error($this->translate('panel.login.attempt.failed'), ['fields' => $fields]); + return $this->error($this->translate('panel.login.attempt.failed'), ['fields' => $fields]); } $accessLimiter->registerAttempt();