From 6ace0728410b75f2215be2b3a3374ce256a1e552 Mon Sep 17 00:00:00 2001 From: binaryDiv Date: Sun, 15 Aug 2021 17:40:35 +0200 Subject: [PATCH] Implement account edit; add SessionHelper, PasswordHelper and ActionResult --- src/Common/ActionResult.php | 83 ++++++++++++ src/Common/PasswordHelper.php | 27 ++++ src/Common/SessionHelper.php | 52 ++++++++ src/Common/UserHelper.php | 11 +- src/Dependencies.php | 34 ++++- src/Exceptions/InputValidationError.php | 8 ++ src/Frontend/Accounts/AccountController.php | 95 +++++++------- src/Frontend/Accounts/AccountEditData.php | 138 ++++++++++++++++++++ src/Frontend/Accounts/AccountHandler.php | 127 ++++++++++++++++++ src/Frontend/BaseController.php | 6 +- src/Frontend/Domains/DomainController.php | 5 +- src/Frontend/Login/LoginController.php | 17 ++- src/Repositories/AccountRepository.php | 49 +++++++ src/Repositories/AliasRepository.php | 16 +++ templates/account_edit.html.twig | 37 ++++-- 15 files changed, 637 insertions(+), 68 deletions(-) create mode 100644 src/Common/ActionResult.php create mode 100644 src/Common/PasswordHelper.php create mode 100644 src/Common/SessionHelper.php create mode 100644 src/Exceptions/InputValidationError.php create mode 100644 src/Frontend/Accounts/AccountEditData.php create mode 100644 src/Frontend/Accounts/AccountHandler.php diff --git a/src/Common/ActionResult.php b/src/Common/ActionResult.php new file mode 100644 index 0000000..1db2315 --- /dev/null +++ b/src/Common/ActionResult.php @@ -0,0 +1,83 @@ +status = $status; + $this->message = $message; + $this->inputData = $inputData; + } + + public static function createSuccessResult(string $successMessage): self + { + return new self(self::STATUS_SUCCESS, $successMessage); + } + + public static function createErrorResult(string $errorMessage, array $inputData): self + { + return new self(self::STATUS_ERROR, $errorMessage, $inputData); + } + + + // Array serialization for session usage + + public static function createFromArray(array $array): self + { + return new self( + $array['status'], + $array['message'], + $array['input_data'] ?? null, + ); + } + + public function toArray(): array + { + return [ + 'status' => $this->status, + 'message' => $this->message, + 'input_data' => $this->inputData, + ]; + } + + + // Getters + + public function getStatus(): string + { + return $this->status; + } + + public function isSuccess(): bool + { + return $this->status === self::STATUS_SUCCESS; + } + + public function isError(): bool + { + return $this->status === self::STATUS_ERROR; + } + + public function getMessage(): string + { + return $this->message; + } + + public function getInputData(): ?array + { + return $this->inputData; + } +} diff --git a/src/Common/PasswordHelper.php b/src/Common/PasswordHelper.php new file mode 100644 index 0000000..2b14c66 --- /dev/null +++ b/src/Common/PasswordHelper.php @@ -0,0 +1,27 @@ +get('user_id'); + } + + public function setUserId(int $userId): void + { + $this->set('user_id', $userId); + } + + // Form processing + public function setLastActionResult(ActionResult $actionResult): void + { + $this->set('last_action_result', $actionResult->toArray()); + } + + public function getLastActionResult(bool $delete = true): ?ActionResult + { + $resultArray = $this->get('last_action_result'); + if ($resultArray) { + if ($delete) { + $this->unset('last_action_result'); + } + return ActionResult::createFromArray($resultArray); + } + return null; + } +} diff --git a/src/Common/UserHelper.php b/src/Common/UserHelper.php index 9d65b55..38ba0ec 100644 --- a/src/Common/UserHelper.php +++ b/src/Common/UserHelper.php @@ -3,28 +3,31 @@ declare(strict_types=1); namespace MailAccountAdmin\Common; -use http\Exception\RuntimeException; +use RuntimeException; use MailAccountAdmin\Models\AdminUser; use MailAccountAdmin\Repositories\AdminUserRepository; class UserHelper { + /** @var SessionHelper */ + private $sessionHelper; /** @var AdminUserRepository */ private $adminUserRepository; - public function __construct(AdminUserRepository $adminUserRepository) + public function __construct(SessionHelper $sessionHelper, AdminUserRepository $adminUserRepository) { + $this->sessionHelper = $sessionHelper; $this->adminUserRepository = $adminUserRepository; } public function isLoggedIn(): bool { - return !empty($_SESSION['user_id']); + return $this->sessionHelper->getUserId() !== null; } public function getCurrentUser(): AdminUser { - $userId = $_SESSION['user_id'] ?? null; + $userId = $this->sessionHelper->getUserId(); if (empty($userId)) { throw new RuntimeException('Not logged in!'); } diff --git a/src/Dependencies.php b/src/Dependencies.php index fc3d767..8d49ed9 100644 --- a/src/Dependencies.php +++ b/src/Dependencies.php @@ -4,8 +4,11 @@ declare(strict_types=1); namespace MailAccountAdmin; use DI\Container; +use MailAccountAdmin\Common\PasswordHelper; +use MailAccountAdmin\Common\SessionHelper; use MailAccountAdmin\Common\UserHelper; use MailAccountAdmin\Frontend\Accounts\AccountController; +use MailAccountAdmin\Frontend\Accounts\AccountHandler; use MailAccountAdmin\Frontend\Domains\DomainController; use MailAccountAdmin\Frontend\Login\LoginController; use MailAccountAdmin\Frontend\Dashboard\DashboardController; @@ -89,41 +92,70 @@ class Dependencies }); // Helper classes + $container->set(SessionHelper::class, function (ContainerInterface $c) { + return new SessionHelper(); + }); + $container->set(UserHelper::class, function (ContainerInterface $c) { return new UserHelper( + $c->get(SessionHelper::class), $c->get(AdminUserRepository::class), ); }); - // Frontend controllers + $container->set(PasswordHelper::class, function (ContainerInterface $c) { + return new PasswordHelper(); + }); + + // Frontend controllers and handlers + // -> Login page $container->set(LoginController::class, function (ContainerInterface $c) { return new LoginController( $c->get(self::TWIG), + $c->get(SessionHelper::class), $c->get(UserHelper::class), $c->get(AdminUserRepository::class), + $c->get(PasswordHelper::class), ); }); + + // -> Dashboard $container->set(DashboardController::class, function (ContainerInterface $c) { return new DashboardController( $c->get(self::TWIG), + $c->get(SessionHelper::class), $c->get(UserHelper::class), ); }); + + // -> Domains $container->set(DomainController::class, function (ContainerInterface $c) { return new DomainController( $c->get(self::TWIG), + $c->get(SessionHelper::class), $c->get(UserHelper::class), $c->get(DomainRepository::class), ); }); + + // -> Accounts $container->set(AccountController::class, function (ContainerInterface $c) { return new AccountController( $c->get(self::TWIG), + $c->get(SessionHelper::class), $c->get(UserHelper::class), + $c->get(AccountHandler::class), $c->get(AccountRepository::class), $c->get(AliasRepository::class), ); }); + $container->set(AccountHandler::class, function (ContainerInterface $c) { + return new AccountHandler( + $c->get(AccountRepository::class), + $c->get(AliasRepository::class), + $c->get(PasswordHelper::class), + ); + }); return $container; } diff --git a/src/Exceptions/InputValidationError.php b/src/Exceptions/InputValidationError.php new file mode 100644 index 0000000..c0ec687 --- /dev/null +++ b/src/Exceptions/InputValidationError.php @@ -0,0 +1,8 @@ +accountHandler = $accountHandler; $this->accountRepository = $accountRepository; $this->aliasRepository = $aliasRepository; } @@ -33,13 +38,9 @@ class AccountController extends BaseController { // Parse query parameters for filters $queryParams = $request->getQueryParams(); - $filterByDomain = $queryParams['domain'] ?? null; - - $renderData = [ - 'filterDomain' => $filterByDomain, - 'accountList' => $this->accountRepository->fetchAccountList($filterByDomain), - ]; + $filterByDomain = $queryParams['domain'] ?? ''; + $renderData = $this->accountHandler->listAccounts($filterByDomain); return $this->view->render($response, 'accounts.html.twig', $renderData); } @@ -51,28 +52,7 @@ class AccountController extends BaseController // Parse URL arguments $accountId = (int)$args['id']; - // Get account data from database - $account = $this->accountRepository->fetchAccountById($accountId); - - // Don't display the password hash, but at least the type of hash (used hash algorithm) - if ($account->getPasswordHash() === '') { - $passwordHashType = 'empty'; - } else { - $passwordHashInfo = password_get_info($account->getPasswordHash()); - $passwordHashType = $passwordHashInfo['algoName'] ?? 'unknown'; - } - - // Get list of aliases for this account - $aliases = $this->aliasRepository->fetchAliasesForUserId($accountId); - - $renderData = [ - 'id' => $accountId, - 'accountUsername' => $account->getUsername(), - 'account' => $account, - 'passwordHashType' => $passwordHashType, - 'aliases' => $aliases, - ]; - + $renderData = $this->accountHandler->getAccountDetails($accountId); return $this->view->render($response, 'account_details.html.twig', $renderData); } @@ -81,6 +61,7 @@ class AccountController extends BaseController public function showAccountCreate(Request $request, Response $response): Response { + // TODO: just a placeholder return $this->showAccounts($request, $response); } @@ -95,28 +76,52 @@ class AccountController extends BaseController // Get account data from database $account = $this->accountRepository->fetchAccountById($accountId); - // Render page - return $this->renderEditPage($response, $account); - } - - public function editAccount(Request $request, Response $response, array $args): Response - { - // TODO: just a placeholder - $this->view->getEnvironment()->addGlobal('error', 'Not implemented yet!'); - return $this->showAccountEdit($request, $response, $args); - } - - private function renderEditPage(Response $response, Account $account, array $extraRenderData = []): Response - { $renderData = [ 'id' => $account->getId(), 'accountUsername' => $account->getUsername(), 'account' => $account, ]; - return $this->view->render($response, 'account_edit.html.twig', array_merge($renderData, $extraRenderData)); + $lastActionResult = $this->sessionHelper->getLastActionResult(); + if ($lastActionResult !== null) { + $resultData = $lastActionResult->isSuccess() + ? ['success' => $lastActionResult->getMessage()] + : ['error' => $lastActionResult->getMessage()]; + $resultData['editData'] = $lastActionResult->getInputData(); + $renderData = array_merge($renderData, $resultData); + } + + return $this->view->render($response, 'account_edit.html.twig', $renderData); } + public function editAccount(Request $request, Response $response, array $args): Response + { + // Parse URL arguments + $accountId = (int)$args['id']; + + // Parse form data + $editData = $request->getParsedBody(); + $errorMessage = null; + + try { + // Validate input + $validatedEditData = AccountEditData::createFromArray($editData); + $this->accountHandler->editAccountData($accountId, $validatedEditData); + } catch (InputValidationError $e) { + $errorMessage = $e->getMessage(); + } + + if (empty($errorMessage)) { + $this->sessionHelper->setLastActionResult(ActionResult::createSuccessResult('Account data was saved.')); + } else { + $this->sessionHelper->setLastActionResult(ActionResult::createErrorResult($errorMessage, $editData)); + } + + // Redirect to edit form page via GET (PRG) + return $response->withHeader('Location', '/accounts/' . $accountId . '/edit')->withStatus(303); + } + + // -- /accounts/{id}/delete - Delete account public function showAccountDelete(Request $request, Response $response, array $args): Response diff --git a/src/Frontend/Accounts/AccountEditData.php b/src/Frontend/Accounts/AccountEditData.php new file mode 100644 index 0000000..1422c34 --- /dev/null +++ b/src/Frontend/Accounts/AccountEditData.php @@ -0,0 +1,138 @@ +username = $username; + $this->usernameCreateAlias = $usernameCreateAlias; + $this->usernameReplaceAlias = $usernameReplaceAlias; + $this->password = $password; + $this->active = $active; + $this->homeDir = $homeDir; + $this->memo = $memo; + } + + public static function createFromArray($raw): self + { + return new self( + self::validateUsername(trim($raw['username'] ?? '')), + self::validateBoolOption(trim($raw['username_create_alias'] ?? '')), + self::validateBoolOption(trim($raw['username_replace_alias'] ?? '')), + self::validatePassword(trim($raw['password'] ?? ''), trim($raw['password_repeat'] ?? '')), + self::validateBoolOption(trim($raw['is_active'] ?? '')), + self::validateHomeDir(trim($raw['home_dir'] ?? '')), + trim($raw['memo'] ?? '') + ); + } + + + // Input validation + + private static function validateUsername(string $username): ?string + { + if ($username === '') { + return null; + } + if (strlen($username) > 100) { + throw new InputValidationError('Username is too long.'); + } + + $username = strtolower($username); + if (!preg_match('/^[a-z0-9._+-]+@[a-z0-9.-]+$/', $username) || preg_match('/^\\.|\\.\\.|\\.@|@\\.|\\.$/', $username)) { + throw new InputValidationError('Username is not valid (must be a valid mail address).'); + } + return $username; + } + + private static function validatePassword(string $password, string $passwordRepeat): ?string + { + if ($password === '') { + return null; + } + if ($password !== $passwordRepeat) { + throw new InputValidationError('Passwords do not match.'); + } + return $password; + } + + private static function validateHomeDir(string $homeDir): ?string + { + if ($homeDir === '') { + return null; + } + if (strlen($homeDir) > 100) { + throw new InputValidationError('Home directory is too long.'); + } + + $homeDir = trim($homeDir, '/'); + if (!preg_match('!^[a-z0-9._+-]+(/[a-z0-9._+-]+)*$!i', $homeDir)) { + throw new InputValidationError('Home directory is not a valid path.'); + } + return $homeDir; + } + + private static function validateBoolOption(string $raw): bool + { + return $raw !== ''; + } + + + // Getters + + public function getUsername(): ?string + { + return $this->username; + } + + public function getUsernameCreateAlias(): bool + { + return $this->usernameCreateAlias; + } + + public function getUsernameReplaceAlias(): bool + { + return $this->usernameReplaceAlias; + } + + public function getPassword(): ?string + { + return $this->password; + } + + public function getActive(): bool + { + return $this->active; + } + + public function getHomeDir(): ?string + { + return $this->homeDir; + } + + public function getMemo(): string + { + return $this->memo; + } +} diff --git a/src/Frontend/Accounts/AccountHandler.php b/src/Frontend/Accounts/AccountHandler.php new file mode 100644 index 0000000..5756400 --- /dev/null +++ b/src/Frontend/Accounts/AccountHandler.php @@ -0,0 +1,127 @@ +accountRepository = $accountRepository; + $this->aliasRepository = $aliasRepository; + $this->passwordHelper = $passwordHelper; + } + + + // -- /accounts - List all accounts + + public function listAccounts(string $filterByDomain): array + { + $accountList = $this->accountRepository->fetchAccountList($filterByDomain); + + return [ + 'filterDomain' => $filterByDomain, + 'accountList' => $accountList, + ]; + } + + + // -- /accounts/{id} - Show account details + + public function getAccountDetails(int $accountId): array + { + // Get account data from database + $account = $this->accountRepository->fetchAccountById($accountId); + + // Don't display the password hash, but at least the type of hash (used hash algorithm) + $passwordHashType = $this->passwordHelper->getPasswordHashType($account->getPasswordHash()); + + // Get list of aliases for this account + $aliases = $this->aliasRepository->fetchAliasesForUserId($accountId); + + return [ + 'id' => $accountId, + 'accountUsername' => $account->getUsername(), + 'account' => $account, + 'passwordHashType' => $passwordHashType, + 'aliases' => $aliases, + ]; + } + + + // -- /accounts/{id}/edit - Edit account data + + public function editAccountData(int $accountId, AccountEditData $editData): void + { + // Check if account exists + try { + $account = $this->accountRepository->fetchAccountById($accountId); + } catch (AccountNotFoundException $e) { + throw new InputValidationError('Account with ID ' . $accountId . ' does not exist!'); + } + + // TODO: Use database transactions (beginTransaction/commit/rollBack in BaseRepository maybe?) + + $newUsername = $editData->getUsername(); + if ($newUsername === $account->getUsername()) { + // Username is unchanged + $newUsername = null; + } + + // TODO: This feature is not supported yet. If the alias exists, the availability check (next) would fail anyway. + if ($editData->getUsernameReplaceAlias()) { + throw new InputValidationError('Replace alias: Not implemented yet.'); + } + + // Check if new username is still available + if ($newUsername !== null) { + if (!$this->accountRepository->checkUsernameAvailable($newUsername) || !$this->aliasRepository->checkAliasAvailable($newUsername)) { + throw new InputValidationError("Username \"$newUsername\" is not available."); + } + } + + // Create alias for old username (if wanted) + if ($editData->getUsernameCreateAlias()) { + $oldUsername = $account->getUsername(); + if (!$this->aliasRepository->checkAliasAvailable($oldUsername)) { + throw new InputValidationError("Alias \"$oldUsername\" cannot be created: Alias already exists."); + } + $this->aliasRepository->createNewAlias($accountId, $oldUsername); + } + + // Hash new password + $newPasswordHash = null; + if ($editData->getPassword() !== null) { + $newPasswordHash = $this->passwordHelper->hashPassword($editData->getPassword()); + } + + // Update account in database + $this->accountRepository->updateAccountWithId( + $accountId, + $newUsername, + $newPasswordHash, + $editData->getActive(), + $editData->getHomeDir(), + $editData->getMemo() + ); + + // Remove existing alias for new username (if wanted) + // TODO: See above. This is the point where the alias should be deleted. + // if ($editData->getUsernameReplaceAlias()) { + // throw new InputValidationError('Replace alias: Not implemented yet.'); + // } + } +} diff --git a/src/Frontend/BaseController.php b/src/Frontend/BaseController.php index 70875b4..f460fe4 100644 --- a/src/Frontend/BaseController.php +++ b/src/Frontend/BaseController.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace MailAccountAdmin\Frontend; +use MailAccountAdmin\Common\SessionHelper; use MailAccountAdmin\Common\UserHelper; use Slim\Views\Twig; @@ -10,12 +11,15 @@ class BaseController { /** @var Twig */ protected $view; + /** @var SessionHelper */ + protected $sessionHelper; /** @var UserHelper */ protected $userHelper; - public function __construct(Twig $view, UserHelper $userHelper) + public function __construct(Twig $view, SessionHelper $sessionHelper, UserHelper $userHelper) { $this->view = $view; + $this->sessionHelper = $sessionHelper; $this->userHelper = $userHelper; // Register globals diff --git a/src/Frontend/Domains/DomainController.php b/src/Frontend/Domains/DomainController.php index 5451a1c..b1929be 100644 --- a/src/Frontend/Domains/DomainController.php +++ b/src/Frontend/Domains/DomainController.php @@ -3,6 +3,7 @@ declare(strict_types=1); namespace MailAccountAdmin\Frontend\Domains; +use MailAccountAdmin\Common\SessionHelper; use MailAccountAdmin\Common\UserHelper; use MailAccountAdmin\Frontend\BaseController; use MailAccountAdmin\Repositories\DomainRepository; @@ -15,9 +16,9 @@ class DomainController extends BaseController /** @var DomainRepository */ private $domainRepository; - public function __construct(Twig $view, UserHelper $userHelper, DomainRepository $domainRepository) + public function __construct(Twig $view, SessionHelper $sessionHelper, UserHelper $userHelper, DomainRepository $domainRepository) { - parent::__construct($view, $userHelper); + parent::__construct($view, $sessionHelper, $userHelper); $this->domainRepository = $domainRepository; } diff --git a/src/Frontend/Login/LoginController.php b/src/Frontend/Login/LoginController.php index c3695fb..3e0f875 100644 --- a/src/Frontend/Login/LoginController.php +++ b/src/Frontend/Login/LoginController.php @@ -3,6 +3,8 @@ declare(strict_types=1); namespace MailAccountAdmin\Frontend\Login; +use MailAccountAdmin\Common\PasswordHelper; +use MailAccountAdmin\Common\SessionHelper; use MailAccountAdmin\Common\UserHelper; use MailAccountAdmin\Exceptions\AdminUserNotFoundException; use MailAccountAdmin\Frontend\BaseController; @@ -15,11 +17,15 @@ class LoginController extends BaseController { /** @var AdminUserRepository */ private $adminUserRepository; + /** @var PasswordHelper */ + private $passwordHelper; - public function __construct(Twig $view, UserHelper $userHelper, AdminUserRepository $adminUserRepository) + public function __construct(Twig $view, SessionHelper $sessionHelper, UserHelper $userHelper, AdminUserRepository $adminUserRepository, + PasswordHelper $passwordHelper) { - parent::__construct($view, $userHelper); + parent::__construct($view, $sessionHelper, $userHelper); $this->adminUserRepository = $adminUserRepository; + $this->passwordHelper = $passwordHelper; } private function renderLoginPage(Response $response, array $renderData = []): Response @@ -54,19 +60,18 @@ class LoginController extends BaseController try { $user = $this->adminUserRepository->getUserByName($loginUsername); - } - catch (AdminUserNotFoundException $e) { + } catch (AdminUserNotFoundException $e) { $user = null; } - if ($user === null || !password_verify($loginPassword, $user->getPasswordHash())) { + if ($user === null || !$this->passwordHelper->verifyPassword($loginPassword, $user->getPasswordHash())) { return $this->renderLoginPage($response, ['error' => 'Wrong username or password!']); } elseif (!$user->isActive()) { return $this->renderLoginPage($response, ['error' => 'User is inactive!']); } // Set login session - $_SESSION['user_id'] = $user->getId(); + $this->sessionHelper->setUserId($user->getId()); return $response ->withHeader('Location', '/') ->withStatus(303); diff --git a/src/Repositories/AccountRepository.php b/src/Repositories/AccountRepository.php index 03a6066..7dba5a0 100644 --- a/src/Repositories/AccountRepository.php +++ b/src/Repositories/AccountRepository.php @@ -42,6 +42,9 @@ class AccountRepository extends BaseRepository return $accountList; } + /** + * @throws AccountNotFoundException + */ public function fetchAccountById(int $accountId): Account { $statement = $this->pdo->prepare('SELECT * FROM mail_users WHERE user_id = :user_id LIMIT 1'); @@ -54,4 +57,50 @@ class AccountRepository extends BaseRepository $row = $statement->fetch(PDO::FETCH_ASSOC); return Account::createFromArray($row); } + + public function checkUsernameAvailable(string $username): bool + { + $statement = $this->pdo->prepare('SELECT 1 FROM mail_users WHERE username = :username LIMIT 1'); + $statement->execute(['username' => $username]); + return $statement->rowCount() === 0; + } + + public function updateAccountWithId(int $accountId, ?string $newUsername, ?string $newPasswordHash, bool $newActive, + ?string $newHomeDir, string $newMemo): void + { + $queryParams = [ + 'user_id' => $accountId, + 'new_memo' => $newMemo, + 'new_active' => $newActive ? '1' : '0', + ]; + + $querySet = ''; + if (isset($newUsername)) { + $querySet .= 'username = :new_username, '; + $queryParams['new_username'] = $newUsername; + } + if (isset($newPasswordHash)) { + $querySet .= 'password = :new_password, '; + $queryParams['new_password'] = $newPasswordHash; + } + if (isset($newHomeDir)) { + $querySet .= 'home_dir = :new_home_dir, '; + $queryParams['new_home_dir'] = $newHomeDir; + } + + $query = ' + UPDATE mail_users + SET + ' . $querySet . ' + memo = :new_memo, + is_active = :new_active, + modified_at = CURRENT_TIMESTAMP() + WHERE + user_id = :user_id + LIMIT 1 + '; + + $statement = $this->pdo->prepare($query); + $statement->execute($queryParams); + } } diff --git a/src/Repositories/AliasRepository.php b/src/Repositories/AliasRepository.php index ac212c2..0c779e5 100644 --- a/src/Repositories/AliasRepository.php +++ b/src/Repositories/AliasRepository.php @@ -13,4 +13,20 @@ class AliasRepository extends BaseRepository $statement->execute(['user_id' => $userId]); return $statement->fetchAll(PDO::FETCH_ASSOC); } + + public function checkAliasAvailable(string $mailAddress): bool + { + $statement = $this->pdo->prepare('SELECT 1 FROM mail_aliases WHERE mail_address = :mail_address LIMIT 1'); + $statement->execute(['mail_address' => $mailAddress]); + return $statement->rowCount() === 0; + } + + public function createNewAlias(int $userId, string $mailAddress): void + { + $statement = $this->pdo->prepare('INSERT INTO mail_aliases (user_id, mail_address) VALUES (:user_id, :mail_address)'); + $statement->execute([ + 'user_id' => $userId, + 'mail_address' => $mailAddress, + ]); + } } diff --git a/templates/account_edit.html.twig b/templates/account_edit.html.twig index 3d48be6..ff1ec87 100644 --- a/templates/account_edit.html.twig +++ b/templates/account_edit.html.twig @@ -16,13 +16,20 @@

Edit account data

- {% if error is defined %} + {% if success is defined %} +
+

Success

+ {{ success }} +
+ {% elseif error is defined %}

Error

{{ error }}
{% endif %} + +

Username

@@ -32,16 +39,22 @@ - +
- +
- +
@@ -53,11 +66,11 @@ - + - +
@@ -73,7 +86,13 @@ New status: @@ -92,7 +111,7 @@ - /srv/vmail/ + /srv/vmail/ @@ -104,7 +123,7 @@ - +