Reseni uzivatelu bez pouziti Nette/Security
- Michwuanquana
- Člen | 22
Zdravim,
nejak mi nesedi reseni uzivatelu v nette, pokusil jsem se tedy udelat
vlastni reseni, ale citim, ze to neni nejlepsi. Jsem amater, velmi bych ocenil
rady ohledne toho co delam spatne, co by slo zlepsit apod. Jakakoliv rada dobra.
Prihlasovani, registrace i opravneni funguji, ale jak jsem rekl, moje reseni se
mi tolik nelibi; necitim se tolik zkuseny abych to posoudil, nebo napsal
pekne.
Jeste poznamka, ze jsem zatim neimplementoval overeni, ale to zde zatim nehraje
roli, obzvlast, pokud jsem se vydal spatnou cestou.
App/Model/UserManager
<?php
namespace App\Model;
use Nette;
use Nette\Utils\DateTime;
class UserManager {
use Nette\SmartObject;
private Nette\Database\Explorer $database;
const default_expiration = 60 * 60 * 48;
public function __construct(Nette\Database\Explorer $database) {
$this->database = $database;
}
public function login($email, $password, $stay = false) {
$users = $this->database->table('users');
if (!$user = $users->where('email', $email)->fetch())
return false;
$testPwd = sha1($user->salt . $password);
if ($user->password != $testPwd)
return false;
$ses = sha1(time() . $testPwd);
$count = $this->database->table('users')
->where('email', $email)
->update(['session' => $ses]);
setcookie('login_session', $ses, $stay ? (time() + (10 * 365 * 24 * 60 * 60)) : (time() + self::default_expiration), '/', NULL, 0);
return true;
}
public function logout() {
if (!$this->isLoggined())
return false;
unset($_COOKIE['login_session']);
setcookie('login_session', null, -1, '/');
}
public function register($email, $name, $password) {
if (!filter_var($email, FILTER_VALIDATE_EMAIL) || $this->emailExists($email))
throw new \Exception('Invalid email', 1);
if (!preg_match("/^[0-9a-zA-Z-' ]*$/", $name) || strlen($name) > 64 || strlen($name) < 3)
throw new \Exception('Invalid name. Name can contain only these characters: A-z, 0-9 and dash', 2);
$salt = sha1(rand());
$hashPass = sha1($salt . $password);
$this->database->table('users')->insert([
'name' => $name,
'email' => $email,
'password' => $hashPass,
'salt' => $salt,
'auth' => 0,
'registered' => date('Y-m-d')
]);
return true;
}
public function requireAccess($level): bool {
if (!$this->isLoggined())
return false;
$userLevel = $this->getUser()->auth;
if ($userLevel === 0) {
return $level == 0;
} elseif($userLevel > 0) {
if ($userLevel == $level || $userLevel < $level)
return true;
} else
return false;
}
public function emailExists($email): bool {
return (bool) $this->database->table('users')->where('email', $email)->fetch();
}
public function sendVerificationEmail($id) {
$user = $this->database->table('users')->where('id', $id)->fetch();
$email = $user->email;
// TODO mail send
}
public function getUser($id = 0) {
$users = $this->database->table('users');
if ($id == 0 && !isset($_COOKIE['login_session']))
return false;
if ($id == 0 && isset($_COOKIE['login_session'])) {
if ($user = $users->where('session', $_COOKIE['login_session'])) {
return $user->fetch();
} else
return false;
}
if ($id != 0) {
if (!$user = $users->get($id))
return false;
else
return $user;
}
return false;
}
public function isLoggined() {
return (bool) $this->getUser();
}
}
a presenter UserPresenter.php
<?php
declare(strict_types=1);
namespace App\Presenters;
use Nette;
use Nette\Application\UI\Form;
use App\Model\UserManager;
final class UserPresenter extends Nette\Application\UI\Presenter
{
private UserManager $userManager;
public function __construct(UserManager $userManager) {
$this->userManager = $userManager;
}
public function renderDefault() {
if($this->userManager->isLoggined())
$this->redirect('Homepage:default');
else
$this->redirect('User:login');
}
public function renderLogin() {
if($this->userManager->isLoggined())
$this->redirect('Homepage:default');
}
public function renderLogout() {
if(!$this->userManager->isLoggined())
$this->redirect('User:login');
else {
$this->userManager->logout();
$this->flashMessage('Succesfully logged off!', 'success');
$this->redirect('Homepage:default');
}
}
public function renderRegister() {
if($this->userManager->isLoggined())
$this->redirect('Homepage:default');
}
public function renderProfile($id = 0) {
if(!$this->template->user =$this->userManager->getUser($id))
$this->redirect('Homepage:default');
}
/**************************************************************************/
protected function createComponentRegisterForm(): Form {
$form = new Form;
$form->addText('name');
$form->addEmail('email');
$form->addPassword('pass');
$form->addPassword('passver');
$form->addSubmit('send');
$form->onSuccess[] = [$this, 'registerSucceeded'];
return $form;
}
protected function createComponentLoginForm(): Form {
$form = new Form;
$form->addEmail('email');
$form->addPassword('pass');
$form->addCheckbox("stay");
$form->addSubmit('send');
$form->onSuccess[] = [$this, 'loginSucceeded'];
return $form;
}
public function loginSucceeded(\stdClass $values): void {
$status = $this->userManager->login($values->email, $values->pass, (bool)$values->stay);
if( $status === true ) {
$this->flashMessage('Logged in!', 'success');
$this->redirect('Homepage:default');
} elseif( $status === -1 ) {
$this->flashMessage('Account not verified, please check email', 'danger');
$this->redirect('User:login');
} else {
$this->flashMessage('Wrong email or password', 'danger');
$this->redirect('User:login');
}
}
public function registerSucceeded(\stdClass $values): void {
if($this->userManager->emailExists($values->email)) {
$this->flashMessage('Account with this email exists', 'danger');
$this->redirect('User:register');
}
if( $values->pass != $values->passver) {
$this->flashMessage('Passwords do not match', 'danger');
$this->redirect('User:register');
}
$success = false;
try {
$this->userManager->register($values->email, $values->name, $values->pass);
$this->flashMessage('Registration was successful, please check your email for veriffication.', 'success');
$success = true;
} catch (\Exception $ex) {
$this->flashMessage($ex->getMessage(), 'danger');
$this->redirect('User:register');
}
if($success)
$this->redirect('User:login');
}
}
Editoval Michwuanquana (13. 3. 2022 16:10)
- Michwuanquana
- Člen | 22
Zkratka nejsem si jisty, jestli to, jak to resim je idealni… Nette implementaci prilis nerozumim, nevim, jak to s jeho pomoci napsat tak, aby se to chovalo stejne.
- Pepino
- Člen | 257
Cetl jsi dokumentaci? https://doc.nette.org/…thentication
Jen ve zkratce. Tvojemu usermanageru
naimplementuj
Nette\Security\Authenticator
, metodu login
prepis na
authenticate
a vracej v ni identitu
. Metodu
logout
a isLoggedIn
muzes smazat. V prezenteru je
pristup k uzivatelovi pres $this->getUser()
takze prihlaseni
bude $this->getUser()->login(user, pw, ..)
, odhlaseni
$this->getUser()->logout()
, jestli je prihlaseny
$this->getUser()->isLoggedIn()
atd..
Jestli ti jde o pouziti cookies tak https://doc.nette.org/…thentication#…
- nightfish
- Člen | 519
Nikdy nepoužívej SHA1 pro hashování hesel. Hashování hesel a ověřování jejich správnosti by měly mít na starosti funkce password_hash() a password_verify(), případně jejich Nette obálky.
- Michwuanquana
- Člen | 22
Dekuji vam mnohokrat, nakopli jste me na lepsi cestu. Upravil jsem kod takto a je funkcni a libi se mi vice, trochou treninku casem udelam i pekny kod :D
UserManager.php
<?php
namespace App\Model;
use Nette;
use Nette\Security\SimpleIdentity;
use Nette\Security\Passwords;
use Nette\Database\Explorer;
class UserManager implements Nette\Security\Authenticator {
use Nette\SmartObject;
private Passwords $passwords;
private Explorer $database;
const default_expiration = 60 * 60 * 48;
public function __construct(Explorer $database, Passwords $passwords) {
$this->database = $database;
$this->passwords = $passwords;
}
public function authenticate($email, $password, $stay = false): SimpleIdentity {
$users = $this->database->table('users');
if (!$row = $users->where('email', $email)->fetch())
throw new Nette\Security\AuthenticationException('User not found.');
if (!$this->passwords->verify($password, $row->password))
throw new Nette\Security\AuthenticationException('Invalid password.');
return new SimpleIdentity(
$row->id,
$row->role,
[ 'name' => $row->name,
'email' => $row->email,
'registered' => $row->registered
]
);
}
public function register($email, $name, $password) {
if (!filter_var($email, FILTER_VALIDATE_EMAIL) || $this->emailExists($email))
throw new Nette\Security\AuthenticationException('Invalid email or email already exists.', 1);
if (!preg_match("/^[0-9a-zA-Z-' ]*$/", $name) || strlen($name) > 64 || strlen($name) < 3)
throw new Nette\Security\AuthenticationException('Invalid name. Name can contain only these characters: A-z, 0-9 and dash', 2);
if ($this->nameExists($name))
throw new Nette\Security\AuthenticationException('Name is already registered.', 3);
$res = $this->database->table('users')->insert([
'name' => $name,
'email' => $email,
'password' => $this->passwords->hash($password),
'role' => 0,
'registered' => date('Y-m-d'),
]);
if(!$res instanceof \Nette\Database\Table\ActiveRow)
throw new \Exception('Internal register error');
return $res->id;
}
public function nameExists($name): bool {
return (bool) $this->database->table('users')->where('name', $name)->fetch();
}
public function emailExists($email): bool {
return (bool) $this->database->table('users')->where('email', $email)->fetch();
}
}
UserPresenter.php
<?php
declare(strict_types=1);
namespace App\Presenters;
use Nette;
use Nette\Application\UI\Form;
use Nette\Security\User;
final class UserPresenter extends Nette\Application\UI\Presenter
{
private User $user;
public function __construct(User $user) {
$this->user = $user;
}
public function renderDefault() {
if($this->user->isLoggedIn())
$this->redirect('User:profile');
else
$this->redirect('User:login');
}
public function renderLogin() {
if($this->user->isLoggedIn())
$this->redirect('User:profile');
}
public function renderLogout() {
if($this->user->isLoggedIn()) {
$this->user->logout();
$this->flashMessage('Succesfully logged off!', 'success');
}
$this->redirect('User:login');
}
public function renderRegister() {
if($this->user->isLoggedIn())
$this->redirect('User:profile');
}
public function renderProfile($id = 0) {
if($this->user->isLoggedIn()) {
$this->template->user = $this->user->getIdentity();
} else
$this->redirect('User:login');
}
/**************************************************************************/
protected function createComponentRegisterForm(): Form {
$form = new Form;
$form->addText('name');
$form->addEmail('email');
$form->addPassword('password');
$form->addPassword('passver');
$form->addSubmit('send');
$form->onSuccess[] = [$this, 'registerSucceeded'];
return $form;
}
protected function createComponentLoginForm(): Form {
$form = new Form;
$form->addEmail('email');
$form->addPassword('password');
$form->addCheckbox("stay");
$form->addSubmit('send');
$form->onSuccess[] = [$this, 'loginSucceeded'];
return $form;
}
public function loginSucceeded(\stdClass $values): void {
try {
$this->user->login($values->email, $values->password);
$this->flashMessage('Logged in!', 'success');
$this->redirect('User:profile');
} catch(Nette\Security\AuthenticationException $e) {
$this->flashMessage($e->getMessage(), 'danger');
}
}
public function registerSucceeded(\stdClass $values): void {
if( $values->password != $values->passver) {
$this->flashMessage('Passwords do not match', 'danger');
$this->redirect('User:register');
}
$success = false;
try {
$this->user->getAuthenticator()->register($values->email, $values->name, $values->password);
//$this->userManager
$this->flashMessage('Registration was successful, please check your email for veriffication.', 'success');
$success = true;
} catch (\Exception $ex) {
$this->flashMessage($ex->getMessage(), 'danger');
}
if($success)
$this->redirect('User:login');
}
}
- petr.pavel
- Člen | 535
Ještě ti tam vypadlo zpracování ‚stay‘. Potřebuješ něco jako
if (!$values['stay']) {
$this->user->setExpiration('+ 2 days', true);
}
Nemá moc smysl nastavovat těch 10 let, pokud současně nenastavuješ i session.gc_maxlifetime (cookie ti vydrží, ale ne soubor na serveru). K tomu slouží nastavení v configu (např. local.neon):
session:
expiration: 10 years # sets session.gc_maxlifetime and cookie.lifetime
- Marek Bartoš
- Nette Blogger | 1280
petr.pavel napsal(a):
Ještě ti tam vypadlo zpracování ‚stay‘. Potřebuješ něco jako
if (!$values['stay']) { $this->user->setExpiration('+ 2 days', true); }
Nemá moc smysl nastavovat těch 10 let, pokud současně nenastavuješ i session.gc_maxlifetime (cookie ti vydrží, ale ne soubor na serveru). K tomu slouží nastavení v configu (např. local.neon):
session: expiration: 10 years # sets session.gc_maxlifetime and cookie.lifetime
Ono je nastavení stay signed přežitek, jednodušší je vyhodit jej a mít dlouhé přihlášení vždy.
Na to nastavení expirace v neonu pozor, cron co na serveru maže soubory se sessions to nebude respektovat.
- petr.pavel
- Člen | 535
Ono je nastavení stay signed přežitek, jednodušší je vyhodit jej a mít dlouhé přihlášení vždy.
Nesouhlasím. Uživatel by měl mít možnost zvolit „nezapamatovat“, pokud se přihlašuje ze sdíleného počítače. Pro případ, že by se zapomněl odhlásit.
- Polki
- Člen | 553
@petrpavel to záleží, jaký veřejný PC používáš. Například
různé kiosky mají pořád přihlášeného uživatale, kde se jen loguje
návštěva a je tam natvrdo spuštěný prohlížeč, takže když se
zapomeneš odhlásit, tak prohlížeč pořád nechá spuštěnou session
i když máš zavřenou záložku zrovna…
Proto souhlasím spíše s Markem, že to je přežitek.
Pokud chceš něco takového udělat, tak to dělej jako internetové bankovnictví a to tak, že uživateli nastavíš expiraci session na 2 minuty a poté uživatele odhlásíš, nebo při každé akci, co uživatel vyvolá tu session prodloužíš… K tomu můžeš ještě checkovat to jestli uživatel kouká na stránku tak, že si budeš dělat nějaký ajax request na pozadí co 5 vteřin a když request nepřijde, tak je stránka zavřená a proběhne invalidace session, nebo mít spuštěný natvrdo nějaký tunel, který se zavře po zavření tabu, například pomocí websocketu a jak se zavře spojení, tak se uživatel odhlásí atp.
- Marek Bartoš
- Nette Blogger | 1280
rumcais1 napsal(a):
V novem nette už třetí parametr neprojde. Neřešil jste už někdo.
public function authenticate($email, $password, $stay = false): SimpleIdentity
Předávej si do login() metody už vytvořenou Identity. Můžeš v podstatě dělat, že interface Authenticator neexistuje, nepotřebuješ ho.