Změna hesla uživatele + GUI
parent
e1da2619a8
commit
eec6b692b2
|
|
@ -307,6 +307,40 @@ class DatabazeFacade
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Změna přihlašovacího jména (e-mailu) přihlášeného uživatele.
|
||||
*/
|
||||
public function changeOwnEmail(int $loginId, string $email): string
|
||||
{
|
||||
$this->updateUserCredentials($loginId, $email, null);
|
||||
|
||||
return mb_strtolower(trim($email));
|
||||
}
|
||||
|
||||
/**
|
||||
* Změna hesla. Původní heslo musí sedět s uloženým hashem.
|
||||
*/
|
||||
public function changeOwnPassword(int $loginId, string $currentPassword, string $newPassword): void
|
||||
{
|
||||
$row = $this->masterDb->fetch(
|
||||
'SELECT HESLO_HASH FROM dbo.LOGIN WHERE ID = ? AND AKTIVNI = 1',
|
||||
$loginId
|
||||
);
|
||||
$hash = $row !== null ? (string) ($row->HESLO_HASH ?? '') : '';
|
||||
if ($hash === '' || !$this->passwords->verify($currentPassword, $hash)) {
|
||||
throw new \InvalidArgumentException('Původní heslo není správné.');
|
||||
}
|
||||
if (mb_strlen($newPassword) < 6) {
|
||||
throw new \InvalidArgumentException('Heslo musí mít alespoň 6 znaků.');
|
||||
}
|
||||
|
||||
$this->masterDb->query(
|
||||
'UPDATE dbo.LOGIN SET HESLO_HASH = ? WHERE ID = ?',
|
||||
$this->passwords->hash($newPassword),
|
||||
$loginId
|
||||
);
|
||||
}
|
||||
|
||||
public function findLoginIdForFirma(string $database, int $firmaId, ?string $server = null): ?int
|
||||
{
|
||||
$server ??= $this->tenantServer;
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
</script>
|
||||
|
||||
{* naše vlastní styly *}
|
||||
<link href="{$basePath}/css/layout.css?v={$appVersion ?? '1.0'}-7" rel="stylesheet">
|
||||
<link href="{$basePath}/css/layout.css?v={$appVersion ?? '1.0'}-9" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
|
@ -77,10 +77,11 @@
|
|||
<!-- Uživatel a Odhlášení / Přihlášení -->
|
||||
<div class="user-panel ms-auto">
|
||||
{if $user->isLoggedIn()}
|
||||
{* Přihlášený uživatel *}
|
||||
<span class="text-white d-none d-sm-inline me-2">
|
||||
<i class="bi bi-person-circle me-1"></i> {$user->identity->jmeno ?: $user->identity->email}
|
||||
</span>
|
||||
{* Přihlášený uživatel — ikona / jméno → Nastavení *}
|
||||
<a n:href="Nastaveni:default" class="user-panel-link text-white me-2" title="Nastavení">
|
||||
<i class="bi bi-person-circle me-1"></i>
|
||||
<span class="d-none d-sm-inline">{$user->identity->jmeno ?: $user->identity->email}</span>
|
||||
</a>
|
||||
<a n:href="Sign:out" class="btn btn-sm btn-outline-light" title="Odhlásit se">
|
||||
<i class="bi bi-box-arrow-right me-1"></i> Odhlásit
|
||||
</a>
|
||||
|
|
|
|||
|
|
@ -3,14 +3,17 @@
|
|||
namespace App\UI\Nastaveni;
|
||||
|
||||
use App\Model\Faktura\FakturaCislovani;
|
||||
use App\Model\Facade\DatabazeFacade;
|
||||
use App\Model\Facade\FakturaFacade;
|
||||
use App\UI\BasePresenter;
|
||||
use Nette\Application\UI\Form;
|
||||
use Nette\Security\SimpleIdentity;
|
||||
|
||||
class NastaveniPresenter extends BasePresenter
|
||||
{
|
||||
public function __construct(
|
||||
private FakturaFacade $fakturaFacade
|
||||
private FakturaFacade $fakturaFacade,
|
||||
private DatabazeFacade $databazeFacade,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
|
@ -29,6 +32,14 @@ class NastaveniPresenter extends BasePresenter
|
|||
'obdobi' => $nastaveni['obdobi'],
|
||||
'splatnost' => $nastaveni['splatnostDny'],
|
||||
]);
|
||||
|
||||
$login = $this->databazeFacade->getById((int) $this->getUser()->getId());
|
||||
$email = $login !== null
|
||||
? (string) $login->EMAIL
|
||||
: (string) ($this->getUser()->getIdentity()->email ?? '');
|
||||
$this['jmenoForm']->setDefaults([
|
||||
'email' => $email,
|
||||
]);
|
||||
}
|
||||
|
||||
public function renderDefault(): void
|
||||
|
|
@ -36,6 +47,9 @@ class NastaveniPresenter extends BasePresenter
|
|||
$firmaId = (int) $this->getUser()->getIdentity()->firma_id;
|
||||
$dalsi = $this->fakturaFacade->getNextCislo($firmaId);
|
||||
$this->template->prikladCisla = $dalsi['cislo'];
|
||||
|
||||
$hesloForm = $this['hesloForm'];
|
||||
$this->template->otevritHesloModal = $hesloForm->isSubmitted() && !$hesloForm->isSuccess();
|
||||
}
|
||||
|
||||
protected function createComponentNastaveniForm(): Form
|
||||
|
|
@ -79,4 +93,87 @@ class NastaveniPresenter extends BasePresenter
|
|||
$this->flashMessage('Nastavení faktur bylo uloženo.', 'success');
|
||||
$this->redirect('this');
|
||||
}
|
||||
|
||||
protected function createComponentJmenoForm(): Form
|
||||
{
|
||||
$form = new Form;
|
||||
|
||||
$form->addText('email', 'Uživatelské jméno:')
|
||||
->setHtmlType('email')
|
||||
->setHtmlAttribute('autocomplete', 'username')
|
||||
->setRequired('Zadejte uživatelské jméno.')
|
||||
->addRule(Form::Email, 'Zadejte platný e-mail.')
|
||||
->addRule(Form::MaxLength, 'Uživatelské jméno může mít nejvýše 100 znaků.', 100);
|
||||
|
||||
$form->addSubmit('save', 'Uložit jméno');
|
||||
$form->onSuccess[] = [$this, 'jmenoFormSucceeded'];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
public function jmenoFormSucceeded(Form $form, \stdClass $data): void
|
||||
{
|
||||
try {
|
||||
$email = $this->databazeFacade->changeOwnEmail(
|
||||
(int) $this->getUser()->getId(),
|
||||
(string) $data->email
|
||||
);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
$form->addError($e->getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
$this->replaceIdentityEmail($email);
|
||||
$this->flashMessage('Uživatelské jméno bylo uloženo.', 'success');
|
||||
$this->redirect('this');
|
||||
}
|
||||
|
||||
protected function createComponentHesloForm(): Form
|
||||
{
|
||||
$form = new Form;
|
||||
|
||||
$form->addPassword('current', 'Původní heslo:')
|
||||
->setHtmlAttribute('autocomplete', 'current-password')
|
||||
->setRequired('Zadejte původní heslo.');
|
||||
|
||||
$form->addPassword('password', 'Nové heslo:')
|
||||
->setHtmlAttribute('autocomplete', 'new-password')
|
||||
->setRequired('Zadejte nové heslo.')
|
||||
->addRule(Form::MinLength, 'Heslo musí mít alespoň 6 znaků.', 6);
|
||||
|
||||
$form->addPassword('passwordVerify', 'Nové heslo znovu:')
|
||||
->setHtmlAttribute('autocomplete', 'new-password')
|
||||
->setRequired('Zadejte nové heslo znovu.')
|
||||
->addRule(Form::Equal, 'Hesla se neshodují.', $form['password']);
|
||||
|
||||
$form->addSubmit('save', 'Uložit heslo');
|
||||
$form->onSuccess[] = [$this, 'hesloFormSucceeded'];
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
public function hesloFormSucceeded(Form $form, \stdClass $data): void
|
||||
{
|
||||
try {
|
||||
$this->databazeFacade->changeOwnPassword(
|
||||
(int) $this->getUser()->getId(),
|
||||
(string) $data->current,
|
||||
(string) $data->password
|
||||
);
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
$form['current']->addError($e->getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
$this->flashMessage('Heslo bylo změněno.', 'success');
|
||||
$this->redirect('this');
|
||||
}
|
||||
|
||||
private function replaceIdentityEmail(string $email): void
|
||||
{
|
||||
$identity = $this->getUser()->getIdentity();
|
||||
$data = $identity->getData();
|
||||
$data['email'] = $email;
|
||||
$this->getUser()->login(new SimpleIdentity($identity->getId(), $identity->getRoles(), $data));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,7 +53,99 @@
|
|||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form n:name="jmenoForm" class="login-form mt-4">
|
||||
<div n:if="$form->errors" class="alert alert-danger mb-3">
|
||||
<ul class="mb-0 ps-3">
|
||||
<li n:foreach="$form->errors as $error">{$error}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<fieldset class="border rounded p-3">
|
||||
<legend class="float-none w-auto px-2 fs-6 fw-bold mb-0">Přihlášení</legend>
|
||||
|
||||
<div class="row align-items-center mb-3">
|
||||
<label n:name="email" class="col-sm-5 col-form-label">Uživatelské jméno:</label>
|
||||
<div class="col-sm-7">
|
||||
<div class="input-group">
|
||||
<input n:name="email" class="form-control">
|
||||
<button n:name="save" class="btn btn-outline-primary">Uložit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row align-items-center">
|
||||
<div class="col-sm-5 col-form-label">Heslo:</div>
|
||||
<div class="col-sm-7">
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#hesloModal">
|
||||
<i class="bi bi-key me-1"></i> Změnit heslo
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-muted small mt-3 mb-0">
|
||||
Uživatelské jméno je e-mail, kterým se přihlašujete.
|
||||
</p>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="hesloModal" tabindex="-1" aria-hidden="true" aria-labelledby="hesloModalLabel">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<form n:name="hesloForm">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="hesloModalLabel">
|
||||
<i class="bi bi-key me-1"></i>Změna hesla
|
||||
</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Zavřít"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div n:if="$form->errors" class="alert alert-danger mb-3">
|
||||
<ul class="mb-0 ps-3">
|
||||
<li n:foreach="$form->errors as $error">{$error}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label n:name="current" class="form-label">Původní heslo</label>
|
||||
<input n:name="current" class="form-control">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label n:name="password" class="form-label">Nové heslo</label>
|
||||
<input n:name="password" class="form-control">
|
||||
</div>
|
||||
<div class="mb-0">
|
||||
<label n:name="passwordVerify" class="form-label">Nové heslo znovu</label>
|
||||
<input n:name="passwordVerify" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Zrušit</button>
|
||||
<button n:name="save" class="btn btn-primary">Uložit heslo</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
var el = document.getElementById('hesloModal');
|
||||
if (!el || typeof bootstrap === 'undefined') {
|
||||
return;
|
||||
}
|
||||
el.addEventListener('shown.bs.modal', function () {
|
||||
var input = el.querySelector('input[name="current"]');
|
||||
if (input) {
|
||||
input.focus();
|
||||
}
|
||||
});
|
||||
{if $otevritHesloModal}
|
||||
bootstrap.Modal.getOrCreateInstance(el).show();
|
||||
{/if}
|
||||
});
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -85,6 +85,24 @@ body {
|
|||
margin-left: auto;
|
||||
}
|
||||
|
||||
.user-panel-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
opacity: 0.92;
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 0.2rem 0.35rem;
|
||||
transition: opacity 0.15s ease, background-color 0.15s ease;
|
||||
}
|
||||
|
||||
.user-panel-link:hover,
|
||||
.user-panel-link:focus-visible {
|
||||
opacity: 1;
|
||||
color: #fff;
|
||||
background-color: rgba(255, 255, 255, 0.08);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.user-panel .btn-outline-light {
|
||||
border-color: #5d7390;
|
||||
color: #e4ebf4;
|
||||
|
|
@ -294,6 +312,10 @@ body {
|
|||
margin-bottom: 0.35rem;
|
||||
}
|
||||
|
||||
.login-form .input-group > .form-control {
|
||||
width: 1%;
|
||||
}
|
||||
|
||||
.login-form .form-control {
|
||||
width: 100%;
|
||||
padding: 0.5rem 0.75rem;
|
||||
|
|
|
|||
Loading…
Reference in New Issue