Implement account creation; add base class FormData to validate forms
This commit is contained in:
parent
23127dd193
commit
48925f283f
|
|
@ -158,13 +158,27 @@ table.bordered_table th {
|
||||||
|
|
||||||
/* --- Boxes --- */
|
/* --- Boxes --- */
|
||||||
.filter_options,
|
.filter_options,
|
||||||
.edit_box,
|
.form_box,
|
||||||
.confirmation_box {
|
.confirmation_box {
|
||||||
border: 1px solid #999999;
|
border: 1px solid #999999;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
margin: 1rem 0;
|
margin: 1rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* --- Detail boxes --- */
|
||||||
|
details {
|
||||||
|
margin: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
details > summary {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
details > p {
|
||||||
|
margin: 0.75rem 1rem;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
/* --- Detail columns --- */
|
/* --- Detail columns --- */
|
||||||
input#show_details_checkbox {
|
input#show_details_checkbox {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
|
|
@ -174,8 +188,8 @@ input#show_details_checkbox:not(:checked) ~ table .detail_column {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Edit box --- */
|
/* --- Form box --- */
|
||||||
.edit_box p:last-child {
|
.form_box p:last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace MailAccountAdmin\Common;
|
||||||
|
|
||||||
|
use MailAccountAdmin\Exceptions\InputValidationError;
|
||||||
|
|
||||||
|
abstract class FormData
|
||||||
|
{
|
||||||
|
// Abstract methods
|
||||||
|
|
||||||
|
abstract public static function createFromArray($raw): self;
|
||||||
|
|
||||||
|
|
||||||
|
// Input validation - Base types
|
||||||
|
|
||||||
|
protected static function validateString(string $raw, int $minLength = 0, int $maxLength = 5000, string $fieldName = 'Field'): string
|
||||||
|
{
|
||||||
|
if ($raw === '' && $minLength > 0) {
|
||||||
|
throw new InputValidationError("$fieldName is required.");
|
||||||
|
} elseif (strlen($raw) < $minLength) {
|
||||||
|
throw new InputValidationError("$fieldName is too short (minimum $minLength characters).");
|
||||||
|
} elseif (strlen($raw) > 100) {
|
||||||
|
throw new InputValidationError("$fieldName is too long (maximum $maxLength characters).");
|
||||||
|
}
|
||||||
|
return $raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function validateBoolOption(string $raw): bool
|
||||||
|
{
|
||||||
|
return $raw !== '';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Input validation - Application specific validators
|
||||||
|
|
||||||
|
protected static function validateUsername(string $username, bool $required = true): ?string
|
||||||
|
{
|
||||||
|
if (!$required && $username === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$username = strtolower(
|
||||||
|
self::validateString($username, 3, 100, '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;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function validatePassword(string $password, string $passwordRepeat, bool $required = true): ?string
|
||||||
|
{
|
||||||
|
if (!$required && $password === '' && $passwordRepeat === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$password = self::validateString($password, 6, 1000, 'Password');
|
||||||
|
|
||||||
|
if ($password !== $passwordRepeat) {
|
||||||
|
throw new InputValidationError('Passwords do not match.');
|
||||||
|
}
|
||||||
|
return $password;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function validateHomeDir(string $homeDir, bool $required = true): ?string
|
||||||
|
{
|
||||||
|
if (!$required && $homeDir === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$homeDir = self::validateString($homeDir, 0, 100, 'Home directory');
|
||||||
|
$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;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static function validateMemo(string $memo): string
|
||||||
|
{
|
||||||
|
return self::validateString($memo, 0, 5000, 'Admin memo');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -145,14 +145,13 @@ class Dependencies
|
||||||
$c->get(SessionHelper::class),
|
$c->get(SessionHelper::class),
|
||||||
$c->get(UserHelper::class),
|
$c->get(UserHelper::class),
|
||||||
$c->get(AccountHandler::class),
|
$c->get(AccountHandler::class),
|
||||||
$c->get(AccountRepository::class),
|
|
||||||
$c->get(AliasRepository::class),
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
$container->set(AccountHandler::class, function (ContainerInterface $c) {
|
$container->set(AccountHandler::class, function (ContainerInterface $c) {
|
||||||
return new AccountHandler(
|
return new AccountHandler(
|
||||||
$c->get(AccountRepository::class),
|
$c->get(AccountRepository::class),
|
||||||
$c->get(AliasRepository::class),
|
$c->get(AliasRepository::class),
|
||||||
|
$c->get(DomainRepository::class),
|
||||||
$c->get(PasswordHelper::class),
|
$c->get(PasswordHelper::class),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -52,8 +52,39 @@ class AccountController extends BaseController
|
||||||
|
|
||||||
public function showAccountCreate(Request $request, Response $response): Response
|
public function showAccountCreate(Request $request, Response $response): Response
|
||||||
{
|
{
|
||||||
// TODO: just a placeholder
|
$renderData = $this->accountHandler->getPageDataForCreate();
|
||||||
return $this->showAccounts($request, $response);
|
|
||||||
|
// If the form has been submitted, add the result message and form input data to the render data array
|
||||||
|
$lastActionResult = $this->sessionHelper->getLastActionResult();
|
||||||
|
if ($lastActionResult !== null) {
|
||||||
|
$renderData = array_merge($renderData, $lastActionResult->getRenderData());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->view->render($response, 'account_create.html.twig', $renderData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createAccount(Request $request, Response $response): Response
|
||||||
|
{
|
||||||
|
// Parse form data
|
||||||
|
$createData = $request->getParsedBody();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Validate input
|
||||||
|
$validatedCreateData = AccountCreateData::createFromArray($createData);
|
||||||
|
$newAccountId = $this->accountHandler->createNewAccount($validatedCreateData);
|
||||||
|
|
||||||
|
// Save success result
|
||||||
|
$newAccountName = $validatedCreateData->getUsername();
|
||||||
|
$this->sessionHelper->setLastActionResult(ActionResult::createSuccessResult(
|
||||||
|
'Account <a href="/accounts/' . $newAccountId . '">' . $newAccountName . '</a> was created.'
|
||||||
|
));
|
||||||
|
} catch (InputValidationError $e) {
|
||||||
|
// Save error result
|
||||||
|
$this->sessionHelper->setLastActionResult(ActionResult::createErrorResult($e->getMessage(), $createData));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect to edit form page via GET (PRG)
|
||||||
|
return $response->withHeader('Location', '/accounts/new')->withStatus(303);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -78,25 +109,20 @@ class AccountController extends BaseController
|
||||||
|
|
||||||
public function editAccount(Request $request, Response $response, array $args): Response
|
public function editAccount(Request $request, Response $response, array $args): Response
|
||||||
{
|
{
|
||||||
// Parse URL arguments
|
// Parse URL arguments and form data
|
||||||
$accountId = (int)$args['id'];
|
$accountId = (int)$args['id'];
|
||||||
|
|
||||||
// Parse form data
|
|
||||||
$editData = $request->getParsedBody();
|
$editData = $request->getParsedBody();
|
||||||
$errorMessage = null;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Validate input
|
// Validate input
|
||||||
$validatedEditData = AccountEditData::createFromArray($editData);
|
$validatedEditData = AccountEditData::createFromArray($editData);
|
||||||
$this->accountHandler->editAccountData($accountId, $validatedEditData);
|
$this->accountHandler->editAccountData($accountId, $validatedEditData);
|
||||||
} catch (InputValidationError $e) {
|
|
||||||
$errorMessage = $e->getMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (empty($errorMessage)) {
|
// Save success result
|
||||||
$this->sessionHelper->setLastActionResult(ActionResult::createSuccessResult('Account data was saved.'));
|
$this->sessionHelper->setLastActionResult(ActionResult::createSuccessResult('Account data was saved.'));
|
||||||
} else {
|
} catch (InputValidationError $e) {
|
||||||
$this->sessionHelper->setLastActionResult(ActionResult::createErrorResult($errorMessage, $editData));
|
// Save error result
|
||||||
|
$this->sessionHelper->setLastActionResult(ActionResult::createErrorResult($e->getMessage(), $editData));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Redirect to edit form page via GET (PRG)
|
// Redirect to edit form page via GET (PRG)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace MailAccountAdmin\Frontend\Accounts;
|
||||||
|
|
||||||
|
use MailAccountAdmin\Common\FormData;
|
||||||
|
|
||||||
|
class AccountCreateData extends FormData
|
||||||
|
{
|
||||||
|
private string $username;
|
||||||
|
private string $password;
|
||||||
|
private bool $active;
|
||||||
|
private ?string $homeDir;
|
||||||
|
private string $memo;
|
||||||
|
|
||||||
|
private function __construct(string $username, string $password, bool $active, ?string $homeDir, string $memo)
|
||||||
|
{
|
||||||
|
$this->username = $username;
|
||||||
|
$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::validatePassword(trim($raw['password'] ?? ''), trim($raw['password_repeat'] ?? '')),
|
||||||
|
self::validateBoolOption(trim($raw['is_active'] ?? '')),
|
||||||
|
self::validateHomeDir(trim($raw['home_dir'] ?? ''), false),
|
||||||
|
self::validateMemo(trim($raw['memo'] ?? '')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getUsername(): string
|
||||||
|
{
|
||||||
|
return $this->username;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,9 +3,9 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace MailAccountAdmin\Frontend\Accounts;
|
namespace MailAccountAdmin\Frontend\Accounts;
|
||||||
|
|
||||||
use MailAccountAdmin\Exceptions\InputValidationError;
|
use MailAccountAdmin\Common\FormData;
|
||||||
|
|
||||||
class AccountEditData
|
class AccountEditData extends FormData
|
||||||
{
|
{
|
||||||
private ?string $username;
|
private ?string $username;
|
||||||
private bool $usernameCreateAlias;
|
private bool $usernameCreateAlias;
|
||||||
|
|
@ -13,10 +13,10 @@ class AccountEditData
|
||||||
private ?string $password;
|
private ?string $password;
|
||||||
private bool $active;
|
private bool $active;
|
||||||
private ?string $homeDir;
|
private ?string $homeDir;
|
||||||
private ?string $memo;
|
private string $memo;
|
||||||
|
|
||||||
private function __construct(?string $username, bool $usernameCreateAlias, bool $usernameReplaceAlias, ?string $password, bool $active,
|
private function __construct(?string $username, bool $usernameCreateAlias, bool $usernameReplaceAlias, ?string $password, bool $active,
|
||||||
?string $homeDir, ?string $memo)
|
?string $homeDir, string $memo)
|
||||||
{
|
{
|
||||||
$this->username = $username;
|
$this->username = $username;
|
||||||
$this->usernameCreateAlias = $usernameCreateAlias;
|
$this->usernameCreateAlias = $usernameCreateAlias;
|
||||||
|
|
@ -30,70 +30,16 @@ class AccountEditData
|
||||||
public static function createFromArray($raw): self
|
public static function createFromArray($raw): self
|
||||||
{
|
{
|
||||||
return new self(
|
return new self(
|
||||||
self::validateUsername(trim($raw['username'] ?? '')),
|
self::validateUsername(trim($raw['username'] ?? ''), false),
|
||||||
self::validateBoolOption(trim($raw['username_create_alias'] ?? '')),
|
self::validateBoolOption(trim($raw['username_create_alias'] ?? '')),
|
||||||
self::validateBoolOption(trim($raw['username_replace_alias'] ?? '')),
|
self::validateBoolOption(trim($raw['username_replace_alias'] ?? '')),
|
||||||
self::validatePassword(trim($raw['password'] ?? ''), trim($raw['password_repeat'] ?? '')),
|
self::validatePassword(trim($raw['password'] ?? ''), trim($raw['password_repeat'] ?? ''), false),
|
||||||
self::validateBoolOption(trim($raw['is_active'] ?? '')),
|
self::validateBoolOption(trim($raw['is_active'] ?? '')),
|
||||||
self::validateHomeDir(trim($raw['home_dir'] ?? '')),
|
self::validateHomeDir(trim($raw['home_dir'] ?? ''), false),
|
||||||
trim($raw['memo'] ?? '')
|
self::validateMemo(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
|
public function getUsername(): ?string
|
||||||
{
|
{
|
||||||
return $this->username;
|
return $this->username;
|
||||||
|
|
|
||||||
|
|
@ -8,17 +8,21 @@ use MailAccountAdmin\Exceptions\AccountNotFoundException;
|
||||||
use MailAccountAdmin\Exceptions\InputValidationError;
|
use MailAccountAdmin\Exceptions\InputValidationError;
|
||||||
use MailAccountAdmin\Repositories\AccountRepository;
|
use MailAccountAdmin\Repositories\AccountRepository;
|
||||||
use MailAccountAdmin\Repositories\AliasRepository;
|
use MailAccountAdmin\Repositories\AliasRepository;
|
||||||
|
use MailAccountAdmin\Repositories\DomainRepository;
|
||||||
|
|
||||||
class AccountHandler
|
class AccountHandler
|
||||||
{
|
{
|
||||||
private AccountRepository $accountRepository;
|
private AccountRepository $accountRepository;
|
||||||
private AliasRepository $aliasRepository;
|
private AliasRepository $aliasRepository;
|
||||||
|
private DomainRepository $domainRepository;
|
||||||
private PasswordHelper $passwordHelper;
|
private PasswordHelper $passwordHelper;
|
||||||
|
|
||||||
public function __construct(AccountRepository $accountRepository, AliasRepository $aliasRepository, PasswordHelper $passwordHelper)
|
public function __construct(AccountRepository $accountRepository, AliasRepository $aliasRepository, DomainRepository $domainRepository,
|
||||||
|
PasswordHelper $passwordHelper)
|
||||||
{
|
{
|
||||||
$this->accountRepository = $accountRepository;
|
$this->accountRepository = $accountRepository;
|
||||||
$this->aliasRepository = $aliasRepository;
|
$this->aliasRepository = $aliasRepository;
|
||||||
|
$this->domainRepository = $domainRepository;
|
||||||
$this->passwordHelper = $passwordHelper;
|
$this->passwordHelper = $passwordHelper;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,6 +63,45 @@ class AccountHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// -- /accounts/new - Create new account
|
||||||
|
|
||||||
|
public function getPageDataForCreate(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'domainList' => array_keys($this->domainRepository->fetchDomainList()),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createNewAccount(AccountCreateData $createData): int
|
||||||
|
{
|
||||||
|
// Check if new username is still available
|
||||||
|
$username = $createData->getUsername();
|
||||||
|
if (!$this->accountRepository->checkUsernameAvailable($username) || !$this->aliasRepository->checkAliasAvailable($username)) {
|
||||||
|
throw new InputValidationError("Username \"$username\" is not available.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash new password
|
||||||
|
$passwordHash = $this->passwordHelper->hashPassword($createData->getPassword());
|
||||||
|
|
||||||
|
// Construct home directory from username if necessary
|
||||||
|
if ($createData->getHomeDir() !== null) {
|
||||||
|
$homeDir = $createData->getHomeDir();
|
||||||
|
} else {
|
||||||
|
[$localPart, $domainPart] = explode('@', $username, 2);
|
||||||
|
$homeDir = $domainPart . '/' . $localPart;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create account in database
|
||||||
|
return $this->accountRepository->insertAccount(
|
||||||
|
$username,
|
||||||
|
$passwordHash,
|
||||||
|
$createData->getActive(),
|
||||||
|
$homeDir,
|
||||||
|
$createData->getMemo()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// -- /accounts/{id}/edit - Edit account data
|
// -- /accounts/{id}/edit - Edit account data
|
||||||
|
|
||||||
public function getAccountDataForEdit(int $accountId): array
|
public function getAccountDataForEdit(int $accountId): array
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,27 @@ class AccountRepository extends BaseRepository
|
||||||
return $statement->rowCount() === 0;
|
return $statement->rowCount() === 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function insertAccount(string $username, string $passwordHash, bool $active, string $homeDir, string $memo): int
|
||||||
|
{
|
||||||
|
$query = '
|
||||||
|
INSERT INTO mail_users
|
||||||
|
(username, password, is_active, home_dir, memo)
|
||||||
|
VALUES
|
||||||
|
(:username, :password, :is_active, :home_dir, :memo)
|
||||||
|
';
|
||||||
|
|
||||||
|
$statement = $this->pdo->prepare($query);
|
||||||
|
$statement->execute([
|
||||||
|
'username' => $username,
|
||||||
|
'password' => $passwordHash,
|
||||||
|
'is_active' => $active ? '1' : '0',
|
||||||
|
'home_dir' => $homeDir,
|
||||||
|
'memo' => $memo,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (int)$this->pdo->lastInsertId();
|
||||||
|
}
|
||||||
|
|
||||||
public function updateAccountWithId(int $accountId, ?string $newUsername, ?string $newPasswordHash, bool $newActive,
|
public function updateAccountWithId(int $accountId, ?string $newUsername, ?string $newPasswordHash, bool $newActive,
|
||||||
?string $newHomeDir, string $newMemo): void
|
?string $newHomeDir, string $newMemo): void
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ class Routes
|
||||||
// Accounts
|
// Accounts
|
||||||
$app->get('/accounts', AccountController::class . ':showAccounts');
|
$app->get('/accounts', AccountController::class . ':showAccounts');
|
||||||
$app->get('/accounts/new', AccountController::class . ':showAccountCreate');
|
$app->get('/accounts/new', AccountController::class . ':showAccountCreate');
|
||||||
|
$app->post('/accounts/new', AccountController::class . ':createAccount');
|
||||||
$app->get('/accounts/{id:[1-9][0-9]*}', AccountController::class . ':showAccountDetails');
|
$app->get('/accounts/{id:[1-9][0-9]*}', AccountController::class . ':showAccountDetails');
|
||||||
$app->get('/accounts/{id:[1-9][0-9]*}/edit', AccountController::class . ':showAccountEdit');
|
$app->get('/accounts/{id:[1-9][0-9]*}/edit', AccountController::class . ':showAccountEdit');
|
||||||
$app->post('/accounts/{id:[1-9][0-9]*}/edit', AccountController::class . ':editAccount');
|
$app->post('/accounts/{id:[1-9][0-9]*}/edit', AccountController::class . ':editAccount');
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
{% extends "base.html.twig" %}
|
||||||
|
|
||||||
|
{% block title %}Create account{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h2>Accounts</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<b>Actions:</b>
|
||||||
|
<a href="/accounts">List accounts</a> |
|
||||||
|
<span>Create account</span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h3>Create new account</h3>
|
||||||
|
|
||||||
|
<form action="/accounts/new" method="POST">
|
||||||
|
{{ include('includes/form_result_box.html.twig') }}
|
||||||
|
|
||||||
|
<div class="form_box">
|
||||||
|
<h4>Username</h4>
|
||||||
|
<p>
|
||||||
|
This is the primary mail address of the account and the username to use for login.
|
||||||
|
The domain is part of the username (e.g. "user@example.com").
|
||||||
|
</p>
|
||||||
|
<details>
|
||||||
|
<summary>Show list of known domains</summary>
|
||||||
|
<p>{{ domainList ? domainList | join(', ') : 'No domains exist yet.' }}</p>
|
||||||
|
</details>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td><label for="create_username">Username:</label></td>
|
||||||
|
<td><input id="create_username" name="username" value="{{ formData['username'] | default('') }}"/></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form_box">
|
||||||
|
<h4>Password</h4>
|
||||||
|
<p>The password will be hashed using the current default hash algorithm.</p>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td><label for="create_password">New password:</label></td>
|
||||||
|
<td><input type="password" id="create_password" name="password" value="{{ formData['password'] | default('') }}"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><label for="create_password_repeat">Repeat password:</label></td>
|
||||||
|
<td><input type="password" id="create_password_repeat" name="password_repeat" value="{{ formData['password_repeat'] | default('') }}"/></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form_box">
|
||||||
|
<h4>Account status</h4>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>New account status:</td>
|
||||||
|
<td>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" name="is_active" {{ formData is not defined or formData['is_active'] | default() ? 'checked' : '' }}/>
|
||||||
|
Active
|
||||||
|
</label>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form_box">
|
||||||
|
<h4>Home directory</h4>
|
||||||
|
<p><b>Note:</b> By default the home directory will be determined automatically from the username and domain. Only change this if you know what you're doing!</p>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>Default home directory:</td>
|
||||||
|
<td><span class="gray">/srv/vmail/</span><samp><domain.tld></samp>/<samp><local_part></samp></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><label for="create_home_dir">Set custom home directory:</label></td>
|
||||||
|
<td>
|
||||||
|
<span class="gray">/srv/vmail/</span><input id="create_home_dir" name="home_dir" value="{{ formData['home_dir'] | default('') }}"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form_box">
|
||||||
|
<h4><label for="create_memo">Admin memo</label></h4>
|
||||||
|
<p>This field is only readable by admins.</p>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td><label for="create_memo">Admin memo:</label></td>
|
||||||
|
<td><textarea id="create_memo" name="memo" style="min-width: 40em;">{{ formData['memo'] | default('') }}</textarea></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit">Create account</button>
|
||||||
|
<button type="reset">Reset form</button>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
|
|
@ -16,21 +16,11 @@
|
||||||
<h3>Edit account data</h3>
|
<h3>Edit account data</h3>
|
||||||
|
|
||||||
<form action="/accounts/{{ id }}/edit" method="POST">
|
<form action="/accounts/{{ id }}/edit" method="POST">
|
||||||
{% if success is defined %}
|
{{ include('includes/form_result_box.html.twig') }}
|
||||||
<div class="success_box">
|
|
||||||
<h4>Success</h4>
|
|
||||||
{{ success }}
|
|
||||||
</div>
|
|
||||||
{% elseif error is defined %}
|
|
||||||
<div class="error_box">
|
|
||||||
<h4>Error</h4>
|
|
||||||
{{ error }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<input type="hidden" name="user_id" value="{{ id }}"/>
|
<input type="hidden" name="user_id" value="{{ id }}"/>
|
||||||
|
|
||||||
<div class="edit_box">
|
<div class="form_box">
|
||||||
<h4>Username</h4>
|
<h4>Username</h4>
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
|
|
@ -60,7 +50,7 @@
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="edit_box">
|
<div class="form_box">
|
||||||
<h4>Password</h4>
|
<h4>Password</h4>
|
||||||
<p>The new password will be hashed using the current default hash algorithm.</p>
|
<p>The new password will be hashed using the current default hash algorithm.</p>
|
||||||
<table>
|
<table>
|
||||||
|
|
@ -75,7 +65,7 @@
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="edit_box">
|
<div class="form_box">
|
||||||
<h4>Account status</h4>
|
<h4>Account status</h4>
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
|
|
@ -99,7 +89,7 @@
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="edit_box">
|
<div class="form_box">
|
||||||
<h4>Home directory</h4>
|
<h4>Home directory</h4>
|
||||||
<p><b>Important:</b> Changing the home directory here will <b>NOT</b> move any existing mail data, this needs to be done
|
<p><b>Important:</b> Changing the home directory here will <b>NOT</b> move any existing mail data, this needs to be done
|
||||||
manually!</p>
|
manually!</p>
|
||||||
|
|
@ -117,7 +107,7 @@
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="edit_box">
|
<div class="form_box">
|
||||||
<h4><label for="edit_memo">Admin memo</label></h4>
|
<h4><label for="edit_memo">Admin memo</label></h4>
|
||||||
<p>This field is only readable by admins.</p>
|
<p>This field is only readable by admins.</p>
|
||||||
<table>
|
<table>
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<b>Actions:</b>
|
<b>Actions:</b>
|
||||||
|
<span>List accounts</span> |
|
||||||
<a href="/accounts/new">Create account</a>
|
<a href="/accounts/new">Create account</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
{% if success is defined %}
|
||||||
|
<div class="success_box">
|
||||||
|
<h4>Success</h4>
|
||||||
|
{{ success | raw }}
|
||||||
|
</div>
|
||||||
|
{% elseif error is defined %}
|
||||||
|
<div class="error_box">
|
||||||
|
<h4>Error</h4>
|
||||||
|
{{ error | raw }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
Loading…
Reference in New Issue