Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions formwork/fields/text.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 '';
Expand Down
32 changes: 19 additions & 13 deletions formwork/src/Panel/Controllers/AuthenticationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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) {
Expand All @@ -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'));
return $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) {
Expand All @@ -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);

Expand All @@ -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,
]));
}

Expand Down Expand Up @@ -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]));
}
Expand Down
13 changes: 13 additions & 0 deletions formwork/src/Panel/Controllers/UsersController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
Expand Down Expand Up @@ -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)) {
Expand Down
6 changes: 3 additions & 3 deletions panel/config/routes/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
],
Expand All @@ -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'],
],
Expand Down
6 changes: 4 additions & 2 deletions panel/modals/newUser.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@ 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
label: '{{user.password}}'
suggestion: '{{panel.users.newUser.password.suggestion}}'
required: true
pattern: '^.{8,}$'
minLength: 8
autocomplete: new-password

email:
Expand Down
14 changes: 14 additions & 0 deletions panel/schemes/forms/login.yaml
Original file line number Diff line number Diff line change
@@ -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
6 changes: 4 additions & 2 deletions panel/schemes/forms/register.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@ 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
label: '{{user.password}}'
suggestion: '{{panel.users.newUser.password.suggestion}}'
required: true
pattern: '^.{8,}$'
minLength: 8
autocomplete: new-password

email:
Expand Down
26 changes: 11 additions & 15 deletions panel/src/scss/components/_login.scss
Original file line number Diff line number Diff line change
@@ -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);

Expand All @@ -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);
}
6 changes: 4 additions & 2 deletions panel/translations/de.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -317,13 +317,15 @@ 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
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."
Expand Down
6 changes: 4 additions & 2 deletions panel/translations/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -317,13 +317,15 @@ 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
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.
Expand Down
6 changes: 4 additions & 2 deletions panel/translations/es.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -317,13 +317,15 @@ 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
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.
Expand Down
6 changes: 4 additions & 2 deletions panel/translations/fr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -317,13 +317,15 @@ 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
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.
Expand Down
Loading