From 1df0545061acdbb476ffea0d342defa537ae6f74 Mon Sep 17 00:00:00 2001 From: veselcraft Date: Mon, 31 Jan 2022 14:45:53 +0300 Subject: [PATCH 01/43] [WIP Maybe] Email: Add verification mechanism This does work only if email is required by instance. If you're sysadmin, you can configure it in openvk.yml. --- Email/verify-email.eml.latte | 204 ++++++++++++++++++++++ Web/Models/Entities/EmailVerification.php | 10 ++ Web/Models/Entities/PasswordReset.php | 7 +- Web/Models/Entities/User.php | 14 +- Web/Models/Repositories/Confirmations.php | 33 ++++ Web/Presenters/AboutPresenter.php | 1 + Web/Presenters/AuthPresenter.php | 67 ++++++- Web/Presenters/OpenVKPresenter.php | 35 +++- Web/Presenters/templates/@email.xml | 19 ++ Web/Presenters/templates/@layout.xml | 6 +- Web/di.yml | 3 +- Web/routes.yml | 4 + Web/static/css/style.css | 12 +- locales/en.strings | 15 ++ locales/ru.strings | 15 ++ 15 files changed, 431 insertions(+), 14 deletions(-) create mode 100755 Email/verify-email.eml.latte create mode 100755 Web/Models/Entities/EmailVerification.php create mode 100755 Web/Models/Repositories/Confirmations.php create mode 100755 Web/Presenters/templates/@email.xml diff --git a/Email/verify-email.eml.latte b/Email/verify-email.eml.latte new file mode 100755 index 00000000..40711f94 --- /dev/null +++ b/Email/verify-email.eml.latte @@ -0,0 +1,204 @@ + + + + + + Подтверждение Email + + + + + + + + +
+
+ + + + +
+   +
+ + + + +
+ + + + +
+ + + + +
+ + + + +
+   +
+

Подтверждение Email

+
+
+ + + + + +
+ + + + +
+
+ +
+ + + + + +
+   +
+ +
+ + + + + +
+   +
+ +

+ Здравствуйте, {$name}! Вы вероятно зарегистрировались на одном из инстансов OpenVK. Чтобы ваш аккаунт активировался, необходимо подтвердить Email. +

+ + + + + +
+   +
+ + + + + +
+ + + + +
+
+ Подтвердить Email! +
+
+
+ + + + + +
+   +
+ +

+ Если кнопка не работает, вы можете попробовать скопировать и вставить эту ссылку в адресную строку вашего веб-обозревателя: +

+ + + + + +
+ + http://{$_SERVER['HTTP_HOST']}/regFinish?key={$key} + +
+ +

+ Обратите внимание на то, что эту ссылку нельзя: +

+ +
    +
  • Передавать другим людям (даже друзьям, питомцам, соседам, любимым девушкам)
  • +
  • Использовать, если прошло более двух дней с её генерации
  • +
+ + + + + +
+

+ Ещё раз обратите внимание на то, что данную ссылку или письмо ни в коем случае нельзя передавать другим людям! Даже если они представляются службой поддержки.
+ Это письмо предназначено исключительно для одноразового, непосредственного использования владельцем аккаунта. +

+
+ + + + + +
+   +
+ +

+ С уважением, овк-тян. +

+ + + + + +
+   +
+ +
+ + + + + +
+   +
+ +

+ + Вы получили это письмо так как кто-то или вы зарегистрировались на инстансе OpenVK. Это не рассылка и от неё нельзя отписаться. Если вы всё равно хотите перестать получать подобные письма, деактивируйте ваш аккаунт. + +

+
+
+
+ + + + +
+   +
+
+
+ + diff --git a/Web/Models/Entities/EmailVerification.php b/Web/Models/Entities/EmailVerification.php new file mode 100755 index 00000000..cfd057f9 --- /dev/null +++ b/Web/Models/Entities/EmailVerification.php @@ -0,0 +1,10 @@ +getRecord()->timestamp > (time() - 301); + return $this->getRecord()->timestamp > (time() - (5 * MINUTE)); } + /** + * Token is valid only for 3 days. + */ function isStillValid(): bool { - return $this->getRecord()->timestamp > (time() - 172801); + return $this->getRecord()->timestamp > (time() - (3 * DAY)); } function verify(string $token): bool diff --git a/Web/Models/Entities/User.php b/Web/Models/Entities/User.php index 81c10695..84e48041 100644 --- a/Web/Models/Entities/User.php +++ b/Web/Models/Entities/User.php @@ -850,10 +850,10 @@ class User extends RowModel function isDeleted(): bool { - if ($this->getRecord()->deleted == 1) - return TRUE; - else - return FALSE; + if ($this->getRecord()->deleted == 1) + return TRUE; + else + return FALSE; } /** @@ -882,6 +882,12 @@ class User extends RowModel { return $this->getRecord()->website; } + + // ты устрица + function isActivated(): bool + { + return (bool) $this->getRecord()->activated; + } use Traits\TSubscribable; } diff --git a/Web/Models/Repositories/Confirmations.php b/Web/Models/Repositories/Confirmations.php new file mode 100755 index 00000000..1553b50f --- /dev/null +++ b/Web/Models/Repositories/Confirmations.php @@ -0,0 +1,33 @@ +context = DatabaseConnection::i()->getContext(); + $this->confirmations = $this->context->table("email_verifications"); + } + + function toEmailVerification(?ActiveRow $ar): ?EmailVerification + { + return is_null($ar) ? NULL : new EmailVerification($ar); + } + + function getByToken(string $token): ?EmailVerification + { + return $this->toEmailVerification($this->confirmations->where("key", $token)->fetch()); + } + + function getLatestByUser(User $user): ?EmailVerification + { + return $this->toEmailVerification($this->confirmations->where("profile", $user->getId())->order("timestamp DESC")->fetch()); + } +} diff --git a/Web/Presenters/AboutPresenter.php b/Web/Presenters/AboutPresenter.php index bd84efbc..e7351897 100644 --- a/Web/Presenters/AboutPresenter.php +++ b/Web/Presenters/AboutPresenter.php @@ -8,6 +8,7 @@ use Chandler\Session\Session; final class AboutPresenter extends OpenVKPresenter { protected $banTolerant = true; + protected $activationTolerant = true; function renderIndex(): void { diff --git a/Web/Presenters/AuthPresenter.php b/Web/Presenters/AuthPresenter.php index 9123f12a..d6970ff1 100644 --- a/Web/Presenters/AuthPresenter.php +++ b/Web/Presenters/AuthPresenter.php @@ -3,9 +3,11 @@ namespace openvk\Web\Presenters; use openvk\Web\Models\Entities\IP; use openvk\Web\Models\Entities\User; use openvk\Web\Models\Entities\PasswordReset; +use openvk\Web\Models\Entities\EmailVerification; use openvk\Web\Models\Repositories\IPs; use openvk\Web\Models\Repositories\Users; use openvk\Web\Models\Repositories\Restores; +use openvk\Web\Models\Repositories\Confirmations; use openvk\Web\Util\Validator; use Chandler\Session\Session; use Chandler\Security\User as ChandlerUser; @@ -16,19 +18,22 @@ use lfkeitel\phptotp\{Base32, Totp}; final class AuthPresenter extends OpenVKPresenter { protected $banTolerant = true; + protected $activationTolerant = true; private $authenticator; private $db; private $users; private $restores; + private $confirmations; - function __construct(Users $users, Restores $restores) + function __construct(Users $users, Restores $restores, Confirmations $confirmations) { $this->authenticator = Authenticator::i(); $this->db = DatabaseConnection::i()->getContext(); $this->users = $users; $this->restores = $restores; + $this->confirmations = $confirmations; parent::__construct(); } @@ -81,7 +86,7 @@ final class AuthPresenter extends OpenVKPresenter $this->flashFail("err", tr("invalid_email_address"), tr("invalid_email_address_comment")); if (strtotime($this->postParam("birthday")) > time()) - $this->flashFail("err", tr("invalid_birth_date"), tr("invalid_birth_date_comment")); + $this->flashFail("err", tr("invalid_birth_date"), tr("invalid_birth_date_comment")); $chUser = ChandlerUser::create($this->postParam("email"), $this->postParam("password")); if(!$chUser) @@ -96,12 +101,25 @@ final class AuthPresenter extends OpenVKPresenter $user->setSince(date("Y-m-d H:i:s")); $user->setRegistering_Ip(CONNECTING_IP); $user->setBirthday(strtotime($this->postParam("birthday"))); + $user->setActivated((int) !OPENVK_ROOT_CONF['openvk']['preferences']['security']['requireEmail']); $user->save(); if(!is_null($referer)) { $user->toggleSubscription($referer); $referer->toggleSubscription($user); } + + if (OPENVK_ROOT_CONF['openvk']['preferences']['security']['requireEmail']) { + $verifObj = new EmailVerification; + $verifObj->setProfile($user->getId()); + $verifObj->save(); + + $params = [ + "key" => $verifObj->getKey(), + "name" => $user->getCanonicalName(), + ]; + $this->sendmail($user->getEmail(), "verify-email", $params); #Vulnerability possible + } $this->authenticator->authenticate($chUser->getId()); $this->redirect("/id" . $user->getId(), static::REDIRECT_TEMPORARY); @@ -253,4 +271,49 @@ final class AuthPresenter extends OpenVKPresenter $this->flashFail("succ", tr("information_-1"), tr("password_reset_email_sent")); } } + + function renderResendEmail(): void + { + if(!is_null($this->user) && $this->user->identity->isActivated()) + $this->redirect("/id" . $this->user->id, static::REDIRECT_TEMPORARY); + + if($_SERVER["REQUEST_METHOD"] === "POST") { + $user = $this->user->identity; + if(!$user || $user->isDeleted() || $user->isActivated()) + $this->flashFail("err", tr("error"), tr("email_error")); + + $request = $this->confirmations->getLatestByUser($user); + if(!is_null($request) && $request->isNew()) + $this->flashFail("err", tr("forbidden"), tr("email_rate_limit_error")); + + $verifObj = new EmailVerification; + $verifObj->setProfile($user->getId()); + $verifObj->save(); + + $params = [ + "key" => $verifObj->getKey(), + "name" => $user->getCanonicalName(), + ]; + $this->sendmail($user->getEmail(), "verify-email", $params); #Vulnerability possible + + $this->flashFail("succ", tr("information_-1"), tr("email_sent")); + } + } + + function renderVerifyEmail(): void + { + $request = $this->confirmations->getByToken(str_replace(" ", "+", $this->queryParam("key"))); + if(!$request || !$request->isStillValid()) { + $this->flash("err", tr("token_manipulation_error"), tr("token_manipulation_error_comment")); + $this->redirect("/"); + return; + }else{ + $user = $request->getUser(); + $user->setActivated(1); + $user->save(); + + $this->flash("success", tr("email_verify_success")); + $this->redirect("/"); + } + } } diff --git a/Web/Presenters/OpenVKPresenter.php b/Web/Presenters/OpenVKPresenter.php index 51840c3d..30c0b0a6 100755 --- a/Web/Presenters/OpenVKPresenter.php +++ b/Web/Presenters/OpenVKPresenter.php @@ -13,6 +13,7 @@ use WhichBrowser; abstract class OpenVKPresenter extends SimplePresenter { protected $banTolerant = false; + protected $activationTolerant = false; protected $errorTemplate = "@error"; protected $user = NULL; @@ -212,6 +213,27 @@ abstract class OpenVKPresenter extends SimplePresenter $this->template->userTainted = $user->isTainted(); if($this->user->identity->isDeleted()) { + /* + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣴⠶⠶⣶⠶⠶⠶⠶⠶⠶⠶⠶⠶⢶⠶⠶⠶⠤⠤⠤⠤⣄⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⣠⡾⠋⠀⠀⠊⠀⠀⠀⠀⠀⠀⠀⠀⠒⠒⠒⠀⠀⠀⠀⠤⢤⣤⣄⠉⠉⠛⠛⠷⣦⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⣰⠟⠀⠀⠀⠀⠀⠐⠋⢑⣤⣶⣶⣤⡢⡀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣄⡂⠀⠀⠶⢄⠙⢷⣤⠀⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⣸⡿⠚⠉⡀⠀⠀⠀⠀⢰⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀⢢⠀⠀⡀⣰⣿⣿⣿⣿⣦⡀⠀⠀⠡⡀⢹⡆⠀⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⢀⣴⠏⠀⣀⣀⣀⡤⢤⣄⣠⣿⣿⣿⣿⣻⣿⣿⣷⠀⢋⣾⠈⠙⣶⠒⢿⣿⣿⣿⣿⡿⠟⠃⠀⡀⠡⠼⣧⡀⠀⠀⠀⠀⠀⠀ + ⠀⠀⢀⣴⣿⢃⡴⢊⢽⣶⣤⣀⠀⠊⠉⠉⡛⢿⣿⣿⣿⠿⠋⢀⡀⠁⠀⠀⢸⣁⣀⣉⣉⣉⡉⠀⠩⡡⠀⣩⣦⠀⠈⠻⣦⡀⠀⠀⠀⠀ + ⠀⢠⡟⢡⠇⡞⢀⠆⠀⢻⣿⣿⣷⣄⠀⢀⠈⠂⠈⢁⡤⠚⡟⠉⠀⣀⣀⠀⠈⠳⣍⠓⢆⢀⡠⢀⣨⣴⣿⣿⡏⢀⡆⠀⢸⡇⠀⠀⠀⠀ + ⠀⣾⠁⢸⠀⠀⢸⠀⠀⠀⠹⣿⣿⣿⣿⣶⣬⣦⣤⡈⠀⠀⠇⠀⠛⠉⣩⣤⣤⣤⣿⣤⣤⣴⣾⣿⣿⣿⣿⣿⣧⠞⠀⠀⢸⡇⠀⠀⠀⠀ + ⠀⢹⣆⠸⠀⠀⢸⠀⠀⠀⠀⠘⢿⣿⣿⣿⣿⣿⣿⣟⣛⠛⠛⣛⡛⠛⠛⣛⣋⡉⠉⣡⠶⢾⣿⣿⣿⣿⣿⣿⡇⠀⠀⢀⣾⠃⠀⠀⠀⠀ + ⠀⠀⠻⣆⡀⠀⠈⢂⠀⠀⠀⠠⡈⢻⣿⣿⣿⣿⡟⠁⠈⢧⡼⠉⠙⣆⡞⠁⠈⢹⣴⠃⠀⢸⣿⣿⣿⣿⣿⣿⠃⠀⡆⣾⠃⠀⠀⠀⠀⠀ + ⠀⠀⠀⠈⢻⣇⠀⠀⠀⠀⠀⠀⢡⠀⠹⣿⣿⣿⣷⡀⠀⣸⡇⠀⠀⣿⠁⠀⠀⠘⣿⠀⠀⠘⣿⣿⣿⣿⣿⣿⠀⠀⣿⡇⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠹⣇⠀⠠⠀⠀⠀⠀⠡⠐⢬⡻⣿⣿⣿⣿⣿⣷⣶⣶⣿⣦⣤⣤⣤⣿⣦⣶⣿⣿⣿⣿⣿⣿⣿⠀⠀⣿⡇⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠹⣧⡀⠡⡀⠀⠀⠀⠑⠄⠙⢎⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⠀⢿⡇⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠈⠳⣤⡐⡄⠀⠀⠀⠈⠂⠀⠱⣌⠻⣿⣿⣿⣿⣿⣿⣿⠿⣿⠟⢻⡏⢻⣿⣿⣿⣿⣿⣿⣿⠀⢸⡇⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⢮⣦⡀⠂⠀⢀⠀⠀⠈⠳⣈⠻⣿⣿⣿⡇⠘⡄⢸⠀⠀⣇⠀⣻⣿⣿⣿⣿⣿⡏⠀⠸⡇⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⢶⣤⣄⡑⠄⠀⠀⠈⠑⠢⠙⠻⢷⣶⣵⣞⣑⣒⣋⣉⣁⣻⣿⠿⠟⠱⠃⡸⠀⣧⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠻⣷⣄⡀⠐⠢⣄⣀⡀⠀⠉⠉⠉⠉⠛⠙⠭⠭⠄⠒⠈⠀⠐⠁⢀⣿⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠷⢦⣤⣤⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣒⡠⠄⣠⡾⠃⠀⠀⠀⠀⠀⠀ + ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠙⠛⠷⠶⣦⣤⣭⣤⣬⣭⣭⣴⠶⠛⠉⠀⠀⠀⠀⠀⠀⠀⠀ + */ Authenticator::i()->logout(); Session::i()->set("_su", NULL); $this->flashFail("err", tr("error"), tr("profile_not_found")); @@ -222,8 +244,17 @@ abstract class OpenVKPresenter extends SimplePresenter header("HTTP/1.1 403 Forbidden"); $this->getTemplatingEngine()->render(__DIR__ . "/templates/@banned.xml", [ "thisUser" => $this->user->identity, - "csrfToken" => $GLOBALS["csrfToken"], - "isTimezoned" => Session::i()->get("_timezoneOffset"), + "csrfToken" => $GLOBALS["csrfToken"] + ]); + exit; + } + + // ето для емейл уже надо (и по хорошему надо бы избавится от повторяющегося кода мда) + if(!$this->user->identity->isActivated() && !$this->activationTolerant) { + header("HTTP/1.1 403 Forbidden"); + $this->getTemplatingEngine()->render(__DIR__ . "/templates/@email.xml", [ + "thisUser" => $this->user->identity, + "csrfToken" => $GLOBALS["csrfToken"] ]); exit; } diff --git a/Web/Presenters/templates/@email.xml b/Web/Presenters/templates/@email.xml new file mode 100755 index 00000000..3de8b2e1 --- /dev/null +++ b/Web/Presenters/templates/@email.xml @@ -0,0 +1,19 @@ +{extends "@layout.xml"} +{block title}{_ec_header}{/block} + +{block header} +{_ec_header} +{/block} + +{block content} +
+

{_ec_title}

+

{tr("ec_1", htmlentities($thisUser->getCanonicalName()))|noescape}

+

{_ec_2}

+

+

+ +
+

+
+{/block} diff --git a/Web/Presenters/templates/@layout.xml b/Web/Presenters/templates/@layout.xml index 4834aed8..ffb425b8 100644 --- a/Web/Presenters/templates/@layout.xml +++ b/Web/Presenters/templates/@layout.xml @@ -100,7 +100,7 @@