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/README.md b/README.md index 56211045..41c12559 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ ln -s /path/to/chandler/extensions/available/commitcaptcha /path/to/chandler/ext ln -s /path/to/chandler/extensions/available/openvk /path/to/chandler/extensions/enabled/ ``` -4. Import `install/init-static-db.sql` to **same database** you installed Chandler to +4. Import `install/init-static-db.sql` to **same database** you installed Chandler to and import all sqls from `install/sqls` to **same database** 5. Import `install/init-event-db.sql` to **separate database** 6. Copy `openvk-example.yml` to `openvk.yml` and change options 7. Run `composer install` in OpenVK directory @@ -75,7 +75,7 @@ You may reach out to us via: * [Bug-tracker](https://github.com/openvk/openvk/projects/1) * [Ticketing system](https://openvk.su/support?act=new) -* Telegram chat: Go to [our channel](https://t.me/openvkch) and open discussion in our channel menu. +* Telegram chat: Go to [our channel](https://t.me/openvkenglish) and open discussion in our channel menu. * [Reddit](https://www.reddit.com/r/openvk/) * [Discussions](https://github.com/openvk/openvk/discussions) diff --git a/README_RU.md b/README_RU.md index 2ed13a7b..480552a3 100644 --- a/README_RU.md +++ b/README_RU.md @@ -48,7 +48,7 @@ ln -s /path/to/chandler/extensions/available/commitcaptcha /path/to/chandler/ext ln -s /path/to/chandler/extensions/available/openvk /path/to/chandler/extensions/enabled/ ``` -4. Импортируйте `install/init-static-db.sql` в **ту же базу данных**, в которую вы установили Chandler +4. Импортируйте `install/init-static-db.sql` в **ту же базу данных**, в которую вы установили Chandler, и импортируйте все SQL файлы из папки `install/sqls` в **ту же базу данных** 5. Импортируйте `install/init-event-db.sql` в **отдельную базу данных** 6. Скопируйте `openvk-example.yml` в `openvk.yml` и измените параметры 7. Запустите `composer install` в директории 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/Verifications.php b/Web/Models/Repositories/Verifications.php new file mode 100755 index 00000000..83c13e59 --- /dev/null +++ b/Web/Models/Repositories/Verifications.php @@ -0,0 +1,33 @@ +context = DatabaseConnection::i()->getContext(); + $this->verifications = $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->verifications->where("key", $token)->fetch()); + } + + function getLatestByUser(User $user): ?EmailVerification + { + return $this->toEmailVerification($this->verifications->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..a80bbc68 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\Verifications; 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 $verifications; - function __construct(Users $users, Restores $restores) + function __construct(Users $users, Restores $restores, Verifications $verifications) { $this->authenticator = Authenticator::i(); $this->db = DatabaseConnection::i()->getContext(); $this->users = $users; $this->restores = $restores; + $this->verifications = $verifications; 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']) { + $verification = new EmailVerification; + $verification->setProfile($user->getId()); + $verification->save(); + + $params = [ + "key" => $verification->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,48 @@ 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->verifications->getLatestByUser($user); + if(!is_null($request) && $request->isNew()) + $this->flashFail("err", tr("forbidden"), tr("email_rate_limit_error")); + + $verification = new EmailVerification; + $verification->setProfile($user->getId()); + $verification->save(); + + $params = [ + "key" => $verification->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->verifications->getByToken(str_replace(" ", "+", $this->queryParam("key"))); + if(!$request || !$request->isStillValid()) { + $this->flash("err", tr("token_manipulation_error"), tr("token_manipulation_error_comment")); + $this->redirect("/"); + } 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..e307c134 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")); @@ -227,6 +249,17 @@ abstract class OpenVKPresenter extends SimplePresenter ]); 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"], + "isTimezoned" => Session::i()->get("_timezoneOffset"), + ]); + exit; + } if ($this->user->identity->onlineStatus() == 0) { $this->user->identity->setOnline(time()); 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 f6542040..9d723096 100644 --- a/Web/Presenters/templates/@layout.xml +++ b/Web/Presenters/templates/@layout.xml @@ -100,7 +100,7 @@