Merge branch 'master' into similartovk

This commit is contained in:
celestora 2022-02-05 16:38:27 +02:00 committed by GitHub
commit 47626b91c7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 490 additions and 34 deletions

204
Email/verify-email.eml.latte Executable file
View file

@ -0,0 +1,204 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Подтверждение Email</title>
<link rel="stylesheet" href="foundation.css" />
<style>
.container {
border-top: 5px solid pink;
}
</style>
</head>
<body>
<table class="body" data-made-with-foundation="">
<tr>
<td class="float-center" align="center" valign="top">
<center>
<table class="spacer">
<tr>
<td>
&nbsp;
</td>
</tr>
</table>
<table class="container">
<tr>
<td>
<table class="row header">
<tr>
<th class="small-12 large-12 columns first last">
<table>
<tr>
<th>
<table class="spacer">
<tr>
<td>
&nbsp;
</td>
</tr>
</table>
<h4 class="text-center">Подтверждение Email</h4>
</th>
</tr>
</table>
</th>
</tr>
</table>
<table class="row">
<tr>
<th class="small-12 large-12 columns first last">
<table class="row">
<tr>
<td>
<center>
<img src="pictures/lock.jpeg" align="center" class="float-center" width=128 height=128 />
</center>
<table class="spacer">
<tr>
<td>
&nbsp;
</td>
</tr>
</table>
<hr/>
<table class="spacer">
<tr>
<td>
&nbsp;
</td>
</tr>
</table>
<p class="text-left">
Здравствуйте, {$name}! Вы вероятно зарегистрировались на одном из инстансов OpenVK. Чтобы ваш аккаунт активировался, необходимо подтвердить Email.
</p>
<table class="spacer">
<tr>
<td>
&nbsp;
</td>
</tr>
</table>
<table class="button large expand success">
<tr>
<td>
<table>
<tr>
<td>
<center>
<a href="http://{$_SERVER['HTTP_HOST']}/regFinish?key={rawurlencode($key)}" align="center" class="float-center">Подтвердить Email!</a>
</center>
</td>
</tr>
</table>
</td>
</tr>
</table>
<table class="spacer">
<tr>
<td>
&nbsp;
</td>
</tr>
</table>
<p class="text-left">
Если кнопка не работает, вы можете попробовать скопировать и вставить эту ссылку в адресную строку вашего веб-обозревателя:
</p>
<table class="callout">
<tr>
<th class="callout-inner primary">
<a href="http://{$_SERVER['HTTP_HOST']}/regFinish?key={$key}" style="color: #000; text-decoration: none;">
http://{$_SERVER['HTTP_HOST']}/regFinish?key={$key}
</a>
</th>
</tr>
</table>
<p class="text-left">
Обратите внимание на то, что эту ссылку нельзя:
</p>
<ul>
<li>Передавать другим людям (даже друзьям, питомцам, соседам, любимым девушкам)</li>
<li>Использовать, если прошло более двух дней с её генерации</li>
</ul>
<table class="callout">
<tr>
<th class="callout-inner alert">
<p>
Ещё раз <b>обратите внимание</b> на то, что данную ссылку или письмо <b>ни в коем случае нельзя</b> передавать другим людям! Даже если они представляются службой поддержки.<br/>
Это письмо предназначено исключительно для одноразового, <b>непосредственного</b> использования владельцем аккаунта.
</p>
</th>
</tr>
</table>
<table class="spacer">
<tr>
<td>
&nbsp;
</td>
</tr>
</table>
<p class="text-right">
С уважением, овк-тян.
</p>
<table class="spacer">
<tr>
<td>
&nbsp;
</td>
</tr>
</table>
<hr/>
<table class="spacer">
<tr>
<td>
&nbsp;
</td>
</tr>
</table>
<p class="text-left">
<small>
Вы получили это письмо так как кто-то или вы зарегистрировались на инстансе OpenVK. Это не рассылка и от неё нельзя отписаться. Если вы всё равно хотите перестать получать подобные письма, деактивируйте ваш аккаунт.
</small>
</p>
</td>
</tr>
</table>
</th>
</tr>
</table>
</td>
</tr>
</table>
<table class="spacer">
<tr>
<td>
&nbsp;
</td>
</tr>
</table>
</center>
</td>
</tr>
</table>
</body>
</html>

View file

@ -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/ 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** 5. Import `install/init-event-db.sql` to **separate database**
6. Copy `openvk-example.yml` to `openvk.yml` and change options 6. Copy `openvk-example.yml` to `openvk.yml` and change options
7. Run `composer install` in OpenVK directory 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) * [Bug-tracker](https://github.com/openvk/openvk/projects/1)
* [Ticketing system](https://openvk.su/support?act=new) * [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/) * [Reddit](https://www.reddit.com/r/openvk/)
* [Discussions](https://github.com/openvk/openvk/discussions) * [Discussions](https://github.com/openvk/openvk/discussions)

View file

@ -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/ 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` в **отдельную базу данных** 5. Импортируйте `install/init-event-db.sql` в **отдельную базу данных**
6. Скопируйте `openvk-example.yml` в `openvk.yml` и измените параметры 6. Скопируйте `openvk-example.yml` в `openvk.yml` и измените параметры
7. Запустите `composer install` в директории OpenVK 7. Запустите `composer install` в директории OpenVK

View file

@ -0,0 +1,10 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Entities;
use openvk\Web\Models\Repositories\Users;
use openvk\Web\Models\RowModel;
use openvk\Web\Util\DateTime;
class EmailVerification extends PasswordReset
{
protected $tableName = "email_verifications";
}

View file

@ -34,12 +34,15 @@ class PasswordReset extends RowModel
*/ */
function isNew(): bool function isNew(): bool
{ {
return $this->getRecord()->timestamp > (time() - 301); return $this->getRecord()->timestamp > (time() - (5 * MINUTE));
} }
/**
* Token is valid only for 3 days.
*/
function isStillValid(): bool function isStillValid(): bool
{ {
return $this->getRecord()->timestamp > (time() - 172801); return $this->getRecord()->timestamp > (time() - (3 * DAY));
} }
function verify(string $token): bool function verify(string $token): bool

View file

@ -850,10 +850,10 @@ class User extends RowModel
function isDeleted(): bool function isDeleted(): bool
{ {
if ($this->getRecord()->deleted == 1) if ($this->getRecord()->deleted == 1)
return TRUE; return TRUE;
else else
return FALSE; return FALSE;
} }
/** /**
@ -882,6 +882,12 @@ class User extends RowModel
{ {
return $this->getRecord()->website; return $this->getRecord()->website;
} }
// ты устрица
function isActivated(): bool
{
return (bool) $this->getRecord()->activated;
}
use Traits\TSubscribable; use Traits\TSubscribable;
} }

View file

@ -0,0 +1,33 @@
<?php declare(strict_types=1);
namespace openvk\Web\Models\Repositories;
use Chandler\Database\DatabaseConnection;
use openvk\Web\Models\Entities\EmailVerification;
use openvk\Web\Models\Entities\User;
use Nette\Database\Table\ActiveRow;
class Verifications
{
private $context;
private $verifications;
function __construct()
{
$this->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());
}
}

View file

@ -8,6 +8,7 @@ use Chandler\Session\Session;
final class AboutPresenter extends OpenVKPresenter final class AboutPresenter extends OpenVKPresenter
{ {
protected $banTolerant = true; protected $banTolerant = true;
protected $activationTolerant = true;
function renderIndex(): void function renderIndex(): void
{ {

View file

@ -3,9 +3,11 @@ namespace openvk\Web\Presenters;
use openvk\Web\Models\Entities\IP; use openvk\Web\Models\Entities\IP;
use openvk\Web\Models\Entities\User; use openvk\Web\Models\Entities\User;
use openvk\Web\Models\Entities\PasswordReset; use openvk\Web\Models\Entities\PasswordReset;
use openvk\Web\Models\Entities\EmailVerification;
use openvk\Web\Models\Repositories\IPs; use openvk\Web\Models\Repositories\IPs;
use openvk\Web\Models\Repositories\Users; use openvk\Web\Models\Repositories\Users;
use openvk\Web\Models\Repositories\Restores; use openvk\Web\Models\Repositories\Restores;
use openvk\Web\Models\Repositories\Verifications;
use openvk\Web\Util\Validator; use openvk\Web\Util\Validator;
use Chandler\Session\Session; use Chandler\Session\Session;
use Chandler\Security\User as ChandlerUser; use Chandler\Security\User as ChandlerUser;
@ -16,19 +18,22 @@ use lfkeitel\phptotp\{Base32, Totp};
final class AuthPresenter extends OpenVKPresenter final class AuthPresenter extends OpenVKPresenter
{ {
protected $banTolerant = true; protected $banTolerant = true;
protected $activationTolerant = true;
private $authenticator; private $authenticator;
private $db; private $db;
private $users; private $users;
private $restores; private $restores;
private $verifications;
function __construct(Users $users, Restores $restores) function __construct(Users $users, Restores $restores, Verifications $verifications)
{ {
$this->authenticator = Authenticator::i(); $this->authenticator = Authenticator::i();
$this->db = DatabaseConnection::i()->getContext(); $this->db = DatabaseConnection::i()->getContext();
$this->users = $users; $this->users = $users;
$this->restores = $restores; $this->restores = $restores;
$this->verifications = $verifications;
parent::__construct(); parent::__construct();
} }
@ -81,7 +86,7 @@ final class AuthPresenter extends OpenVKPresenter
$this->flashFail("err", tr("invalid_email_address"), tr("invalid_email_address_comment")); $this->flashFail("err", tr("invalid_email_address"), tr("invalid_email_address_comment"));
if (strtotime($this->postParam("birthday")) > time()) 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")); $chUser = ChandlerUser::create($this->postParam("email"), $this->postParam("password"));
if(!$chUser) if(!$chUser)
@ -96,12 +101,25 @@ final class AuthPresenter extends OpenVKPresenter
$user->setSince(date("Y-m-d H:i:s")); $user->setSince(date("Y-m-d H:i:s"));
$user->setRegistering_Ip(CONNECTING_IP); $user->setRegistering_Ip(CONNECTING_IP);
$user->setBirthday(strtotime($this->postParam("birthday"))); $user->setBirthday(strtotime($this->postParam("birthday")));
$user->setActivated((int) !OPENVK_ROOT_CONF['openvk']['preferences']['security']['requireEmail']);
$user->save(); $user->save();
if(!is_null($referer)) { if(!is_null($referer)) {
$user->toggleSubscription($referer); $user->toggleSubscription($referer);
$referer->toggleSubscription($user); $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->authenticator->authenticate($chUser->getId());
$this->redirect("/id" . $user->getId(), static::REDIRECT_TEMPORARY); $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")); $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("/");
}
}
} }

View file

@ -13,6 +13,7 @@ use WhichBrowser;
abstract class OpenVKPresenter extends SimplePresenter abstract class OpenVKPresenter extends SimplePresenter
{ {
protected $banTolerant = false; protected $banTolerant = false;
protected $activationTolerant = false;
protected $errorTemplate = "@error"; protected $errorTemplate = "@error";
protected $user = NULL; protected $user = NULL;
@ -212,6 +213,27 @@ abstract class OpenVKPresenter extends SimplePresenter
$this->template->userTainted = $user->isTainted(); $this->template->userTainted = $user->isTainted();
if($this->user->identity->isDeleted()) { if($this->user->identity->isDeleted()) {
/*
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣴⠶⠶⣶⠶⠶⠶⠶⠶⠶⠶⠶⠶⢶⠶⠶⠶⠤⠤⠤⠤⣄⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⣠⡾⠋⠀⠀⠊⠀⠀⠀⠀⠀⠀⠀⠀⠒⠒⠒⠀⠀⠀⠀⠤⢤⣤⣄⠉⠉⠛⠛⠷⣦⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⣰⠟⠀⠀⠀⠀⠀⠐⠋⢑⣤⣶⣶⣤⡢⡀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣄⡂⠀⠀⠶⢄⠙⢷⣤⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⣸⡿⠚⠉⡀⠀⠀⠀⠀⢰⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀⢢⠀⠀⡀⣰⣿⣿⣿⣿⣦⡀⠀⠀⠡⡀⢹⡆⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⢀⣴⠏⠀⣀⣀⣀⡤⢤⣄⣠⣿⣿⣿⣿⣻⣿⣿⣷⠀⢋⣾⠈⠙⣶⠒⢿⣿⣿⣿⣿⡿⠟⠃⠀⡀⠡⠼⣧⡀⠀⠀⠀⠀⠀⠀
⠀⠀⢀⣴⣿⢃⡴⢊⢽⣶⣤⣀⠀⠊⠉⠉⡛⢿⣿⣿⣿⠿⠋⢀⡀⠁⠀⠀⢸⣁⣀⣉⣉⣉⡉⠀⠩⡡⠀⣩⣦⠀⠈⠻⣦⡀⠀⠀⠀⠀
⠀⢠⡟⢡⠇⡞⢀⠆⠀⢻⣿⣿⣷⣄⠀⢀⠈⠂⠈⢁⡤⠚⡟⠉⠀⣀⣀⠀⠈⠳⣍⠓⢆⢀⡠⢀⣨⣴⣿⣿⡏⢀⡆⠀⢸⡇⠀⠀⠀⠀
⠀⣾⠁⢸⠀⠀⢸⠀⠀⠀⠹⣿⣿⣿⣿⣶⣬⣦⣤⡈⠀⠀⠇⠀⠛⠉⣩⣤⣤⣤⣿⣤⣤⣴⣾⣿⣿⣿⣿⣿⣧⠞⠀⠀⢸⡇⠀⠀⠀⠀
⠀⢹⣆⠸⠀⠀⢸⠀⠀⠀⠀⠘⢿⣿⣿⣿⣿⣿⣿⣟⣛⠛⠛⣛⡛⠛⠛⣛⣋⡉⠉⣡⠶⢾⣿⣿⣿⣿⣿⣿⡇⠀⠀⢀⣾⠃⠀⠀⠀⠀
⠀⠀⠻⣆⡀⠀⠈⢂⠀⠀⠀⠠⡈⢻⣿⣿⣿⣿⡟⠁⠈⢧⡼⠉⠙⣆⡞⠁⠈⢹⣴⠃⠀⢸⣿⣿⣿⣿⣿⣿⠃⠀⡆⣾⠃⠀⠀⠀⠀⠀
⠀⠀⠀⠈⢻⣇⠀⠀⠀⠀⠀⠀⢡⠀⠹⣿⣿⣿⣷⡀⠀⣸⡇⠀⠀⣿⠁⠀⠀⠘⣿⠀⠀⠘⣿⣿⣿⣿⣿⣿⠀⠀⣿⡇⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠹⣇⠀⠠⠀⠀⠀⠀⠡⠐⢬⡻⣿⣿⣿⣿⣿⣷⣶⣶⣿⣦⣤⣤⣤⣿⣦⣶⣿⣿⣿⣿⣿⣿⣿⠀⠀⣿⡇⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠹⣧⡀⠡⡀⠀⠀⠀⠑⠄⠙⢎⠻⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⠀⢿⡇⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠈⠳⣤⡐⡄⠀⠀⠀⠈⠂⠀⠱⣌⠻⣿⣿⣿⣿⣿⣿⣿⠿⣿⠟⢻⡏⢻⣿⣿⣿⣿⣿⣿⣿⠀⢸⡇⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⢮⣦⡀⠂⠀⢀⠀⠀⠈⠳⣈⠻⣿⣿⣿⡇⠘⡄⢸⠀⠀⣇⠀⣻⣿⣿⣿⣿⣿⡏⠀⠸⡇⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⢶⣤⣄⡑⠄⠀⠀⠈⠑⠢⠙⠻⢷⣶⣵⣞⣑⣒⣋⣉⣁⣻⣿⠿⠟⠱⠃⡸⠀⣧⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠻⣷⣄⡀⠐⠢⣄⣀⡀⠀⠉⠉⠉⠉⠛⠙⠭⠭⠄⠒⠈⠀⠐⠁⢀⣿⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠷⢦⣤⣤⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣒⡠⠄⣠⡾⠃⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠙⠛⠷⠶⣦⣤⣭⣤⣬⣭⣭⣴⠶⠛⠉⠀⠀⠀⠀⠀⠀⠀⠀
*/
Authenticator::i()->logout(); Authenticator::i()->logout();
Session::i()->set("_su", NULL); Session::i()->set("_su", NULL);
$this->flashFail("err", tr("error"), tr("profile_not_found")); $this->flashFail("err", tr("error"), tr("profile_not_found"));
@ -227,6 +249,17 @@ abstract class OpenVKPresenter extends SimplePresenter
]); ]);
exit; 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) { if ($this->user->identity->onlineStatus() == 0) {
$this->user->identity->setOnline(time()); $this->user->identity->setOnline(time());

View file

@ -0,0 +1,19 @@
{extends "@layout.xml"}
{block title}{_ec_header}{/block}
{block header}
{_ec_header}
{/block}
{block content}
<div class="border-block center">
<h4>{_ec_title}</h4>
<p>{tr("ec_1", htmlentities($thisUser->getCanonicalName()))|noescape}<br/></p>
<p>{_ec_2}</p>
<p>
<form action="/reg/resend" method="post">
<input type="submit" class="button" value="{_ec_resend}">
</form>
</p>
</div>
{/block}

View file

@ -100,7 +100,7 @@
<div id="xhead" class="dm"></div> <div id="xhead" class="dm"></div>
<div class="page_header {if $instance_name != OPENVK_DEFAULT_INSTANCE_NAME}page_custom_header{/if}"> <div class="page_header {if $instance_name != OPENVK_DEFAULT_INSTANCE_NAME}page_custom_header{/if}">
<a href="/" class="home_button {if $instance_name != OPENVK_DEFAULT_INSTANCE_NAME}home_button_custom{/if}" title="{$instance_name}">{$instance_name}</a> <a href="/" class="home_button {if $instance_name != OPENVK_DEFAULT_INSTANCE_NAME}home_button_custom{/if}" title="{$instance_name}">{$instance_name}</a>
<div n:if="isset($thisUser) ? !$thisUser->isBanned() : true" class="header_navigation"> <div n:if="isset($thisUser) ? (!$thisUser->isBanned() XOR !$thisUser->isActivated()) : true" class="header_navigation">
{ifset $thisUser} {ifset $thisUser}
<div class="link"> <div class="link">
<a href="/">{_header_home}</a> <a href="/">{_header_home}</a>
@ -142,7 +142,7 @@
<div class="sidebar"> <div class="sidebar">
<div class="navigation"> <div class="navigation">
{ifset $thisUser} {ifset $thisUser}
{if !$thisUser->isBanned()} {if !$thisUser->isBanned() XOR !$thisUser->isActivated()}
<a href="/edit" class="link edit-button">{_edit_button}</a> <a href="/edit" class="link edit-button">{_edit_button}</a>
<a href="{$thisUser->getURL()}" class="link">{_my_page}</a> <a href="{$thisUser->getURL()}" class="link">{_my_page}</a>
<a href="/friends{$thisUser->getId()}" class="link">{_my_friends} <a href="/friends{$thisUser->getId()}" class="link">{_my_friends}
@ -189,6 +189,8 @@
<a n:if="OPENVK_ROOT_CONF['openvk']['preferences']['adPoster']['enable'] && $thisUser->getLeftMenuItemStatus('poster')" href="{php echo OPENVK_ROOT_CONF['openvk']['preferences']['adPoster']['link']}" > <a n:if="OPENVK_ROOT_CONF['openvk']['preferences']['adPoster']['enable'] && $thisUser->getLeftMenuItemStatus('poster')" href="{php echo OPENVK_ROOT_CONF['openvk']['preferences']['adPoster']['link']}" >
<img src="{php echo OPENVK_ROOT_CONF['openvk']['preferences']['adPoster']['src']}" alt="{php echo OPENVK_ROOT_CONF['openvk']['preferences']['adPoster']['caption']}" class="psa-poster" style="max-width: 100%; margin-top: 50px;" /> <img src="{php echo OPENVK_ROOT_CONF['openvk']['preferences']['adPoster']['src']}" alt="{php echo OPENVK_ROOT_CONF['openvk']['preferences']['adPoster']['caption']}" class="psa-poster" style="max-width: 100%; margin-top: 50px;" />
</a> </a>
{elseif !$thisUser->isActivated()}
<a href="/logout?hash={urlencode($csrfToken)}" class="link">{_menu_logout}</a>
{else} {else}
<a href="/support" class="link">{_menu_support} <a href="/support" class="link">{_menu_support}
{if $ticketAnsweredCount > 0} {if $ticketAnsweredCount > 0}

View file

@ -2,14 +2,14 @@
{block wrap} {block wrap}
<div class="page_wrap"> <div class="page_wrap">
<div n:ifset="tabs" class="tabs"> <div n:ifset="tabs" n:ifcontent class="tabs">
{include tabs} {include tabs}
</div> </div>
{ifset size} {ifset size}
{include size, x => $dat} {include size, x => $dat}
{/ifset} {/ifset}
{ifset specpage} {ifset specpage}
{include specpage, x => $dat} {include specpage, x => $dat}
{else} {else}
@ -30,12 +30,12 @@
{ifset infotable} {ifset infotable}
{include infotable, x => $dat} {include infotable, x => $dat}
{else} {else}
<a href="{include link, x => $dat}"> <a href="{include link, x => $dat}">
<b> <b>
{include name, x => $dat} {include name, x => $dat}
</b> </b>
</a> </a>
<br/> <br/>
{include description, x => $dat} {include description, x => $dat}
{/ifset} {/ifset}
</td> </td>
@ -67,4 +67,4 @@
{include bottom} {include bottom}
{/ifset} {/ifset}
</div> </div>
{/block} {/block}

View file

@ -18,13 +18,13 @@
{block tabs} {block tabs}
{if !is_null($thisUser) && $user->getId() === $thisUser->getId()} {if !is_null($thisUser) && $user->getId() === $thisUser->getId()}
<div {if !$admin}id="activetabs"{/if} class="tab"> <div n:attr='id => ($admin ? false : "activetabs")' class="tab">
<a {if !$admin}id="act_tab_a"{/if} href="/groups{$user->getId()}"> <a n:attr='id => ($admin ? false : "act_tab_a")' href="/groups{$user->getId()}">
{_groups} {_groups}
</a> </a>
</div> </div>
<div {if $admin}id="activetabs"{/if} class="tab"> <div n:attr='id => (!$admin ? false : "activetabs")' class="tab">
<a {if $admin}id="act_tab_a"{/if} href="/groups{$user->getId()}?act=managed"> <a n:attr='id => (!$admin ? false : "act_tab_a")' href="/groups{$user->getId()}?act=managed">
{_managed} {_managed}
</a> </a>
</div> </div>
@ -51,15 +51,15 @@
<img src="{$x->getAvatarUrl()}" width="75" alt="Фотография группы" /> <img src="{$x->getAvatarUrl()}" width="75" alt="Фотография группы" />
{/block} {/block}
{block infotable} {block infoTable}
<table id="basicInfo" class="ugc-table group_info" cellspacing="0" cellpadding="0" border="0"> <table id="basicInfo" class="ugc-table group_info" cellspacing="0" cellpadding="0" border="0">
<tbody> <tbody>
<tr> <tr>
<td class="label"><span class="nobold">Название: </span></td> <td class="label"><span class="nobold">{_name}: </span></td>
<td class="data"><a href="{$x->getURL()}">{$x->getName()}</a></td> <td class="data"><a href="{$x->getURL()}">{$x->getName()}</a></td>
</tr> </tr>
<tr> <tr>
<td class="label"><span class="nobold">Размер:</span></td> <td class="label"><span class="nobold">{_size}:</span></td>
<td class="data"><a href="/club{$x->getId()}/followers">{tr("participants", $x->getFollowersCount())}</a></td> <td class="data"><a href="/club{$x->getId()}/followers">{tr("participants", $x->getFollowersCount())}</a></td>
</tr> </tr>
</tbody> </tbody>
@ -89,9 +89,31 @@
<input type="hidden" name="act" value="rem" /> <input type="hidden" name="act" value="rem" />
<input type="hidden" name="id" value="{$x->getId()}" /> <input type="hidden" name="id" value="{$x->getId()}" />
<input type="hidden" name="hash" value="{$csrfToken}" /> <input type="hidden" name="hash" value="{$csrfToken}" />
<input style="text-transform: lowercase; border-bottom: none; width: 140px;" type="submit" id="profile_link" value="{_"leave_community"}" /> <input style="width: 140px; text-transform: lowercase;" type="submit" id="profile_link" value="{_leave_community}" />
</form> </form>
{/if}
{/block}
{block bottom}
{if !is_null($thisUser) && $user->getId() === $thisUser->getId()}
<div class="groups_options">
<div id="gp_container" style="width: 200px; margin-right: 40px;">
<h4>{_open_new_group}</h4>
<span>{_open_group_desc}</span>
<form action="/groups_create">
<button class="button">{_create_group}</button>
</form>
</div>
<div id="gp_container" style="width: 344px;">
<h4>{_search_group}</h4>
<span>{_search_group_desc}</span>
<form action="/search">
<input name="type" type="hidden" value="groups">
<input name="query" class="header_search_input" value="" style="background: none; width: 155px; padding-left: 3px;">
<button class="button">{_search_by_groups}</button>
</form>
</div>
</div>
{/if} {/if}
{/block} {/block}

View file

@ -31,7 +31,8 @@ services:
- openvk\Web\Models\Repositories\Notes - openvk\Web\Models\Repositories\Notes
- openvk\Web\Models\Repositories\Tickets - openvk\Web\Models\Repositories\Tickets
- openvk\Web\Models\Repositories\Messages - openvk\Web\Models\Repositories\Messages
- openvk\Web\Models\Repositories\Restores - openvk\Web\Models\Repositories\Restores
- openvk\Web\Models\Repositories\Verifications
- openvk\Web\Models\Repositories\Notifications - openvk\Web\Models\Repositories\Notifications
- openvk\Web\Models\Repositories\TicketComments - openvk\Web\Models\Repositories\TicketComments
- openvk\Web\Models\Repositories\IPs - openvk\Web\Models\Repositories\IPs

View file

@ -59,6 +59,10 @@ routes:
handler: "Auth->restore" handler: "Auth->restore"
- url: "/restore/internal-finish" - url: "/restore/internal-finish"
handler: "Auth->finishRestoringPassword" handler: "Auth->finishRestoringPassword"
- url: "/reg/resend"
handler: "Auth->resendEmail"
- url: "/regFinish"
handler: "Auth->verifyEmail"
- url: "/setSID/{slug}" - url: "/setSID/{slug}"
handler: "Auth->su" handler: "Auth->su"
- url: "/settings" - url: "/settings"

View file

@ -1855,11 +1855,11 @@ body.scrolled .toTop:hover {
.container_gray .content:last-child { .container_gray .content:last-child {
margin-bottom: 0; margin-bottom: 0;
} }
.group_info { .group_info {
padding: 0 0 0 5px !important; padding: 0 0 0 5px !important;
} }
.group_info .label { .group_info .label {
width: auto !important; width: auto !important;
@ -1876,3 +1876,13 @@ table td[width="120"] {
display: inline-block; display: inline-block;
vertical-align: top; vertical-align: top;
} }
.border-block {
box-shadow: inset 0px 0 0px 1px #b6bfca, inset 0px 0 0px 10px #d8dfe7;
width: 300px;
padding: 20px;
}
.center {
margin: 0 auto;
}

View file

@ -231,7 +231,7 @@ return (function() {
if(is_dir($gitDir = OPENVK_ROOT . "/.git") && $showCommitHash) if(is_dir($gitDir = OPENVK_ROOT . "/.git") && $showCommitHash)
$ver = trim(`git --git-dir="$gitDir" log --pretty="%h" -n1 HEAD` ?? "Unknown version") . "-nightly"; $ver = trim(`git --git-dir="$gitDir" log --pretty="%h" -n1 HEAD` ?? "Unknown version") . "-nightly";
else else
$ver = "Build 15"; $ver = "Public Technical Preview 3";
// Unix time constants // Unix time constants
define('MINUTE', 60); define('MINUTE', 60);

View file

@ -0,0 +1,9 @@
ALTER TABLE `profiles` ADD `activated` tinyint(3) NULL DEFAULT '1' AFTER `2fa_secret`;
CREATE TABLE IF NOT EXISTS `email_verifications` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`profile` bigint(20) unsigned NOT NULL,
`key` char(64) COLLATE utf8mb4_general_nopad_ci NOT NULL,
`timestamp` bigint(20) unsigned NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_nopad_ci;

View file

@ -35,6 +35,13 @@
"password_reset_error" = "An unexpected error occurred while resetting the password."; "password_reset_error" = "An unexpected error occurred while resetting the password.";
"password_reset_rate_limit_error" = "You can't do it that often, sorry."; "password_reset_rate_limit_error" = "You can't do it that often, sorry.";
"email_sent" = "Mail has been successfully sent.";
"email_sent_desc" = "If your email addess exists, you will receive instructions.";
"email_error" = "An unexpected error occurred while sending the email.";
"email_rate_limit_error" = "You can't do it that often, sorry.";
"email_verify_success" = "Your Email has been verified. Have a great time!";
"registration_disabled_info" = "Registration has been disabled by the system administrator. If possible, ask for an invitation from your friend, if he is registered on this site."; "registration_disabled_info" = "Registration has been disabled by the system administrator. If possible, ask for an invitation from your friend, if he is registered on this site.";
"registration_closed" = "Registration is closed."; "registration_closed" = "Registration is closed.";
"invites_you_to" = "<strong>$1</strong> invites you to $2"; "invites_you_to" = "<strong>$1</strong> invites you to $2";
@ -216,6 +223,7 @@
"subscriptions" = "Subscriptions"; "subscriptions" = "Subscriptions";
"join_community" = "Join community"; "join_community" = "Join community";
"leave_community" = "Leave community"; "leave_community" = "Leave community";
"check_community" = "View community";
"min_6_community" = "Name of the group must have more that 6 characters"; "min_6_community" = "Name of the group must have more that 6 characters";
"participants" = "Participants"; "participants" = "Participants";
"groups" = "Groups"; "groups" = "Groups";
@ -233,6 +241,7 @@
"only_administrators" = "Only administrators"; "only_administrators" = "Only administrators";
"website" = "Website"; "website" = "Website";
"managed" = "Managed"; "managed" = "Managed";
"size" = "Size";
"administrators_one" = "$1 administrator"; "administrators_one" = "$1 administrator";
"administrators_other" = "$1 administrators"; "administrators_other" = "$1 administrators";
@ -265,6 +274,10 @@
"groups_one" = "$1 group"; "groups_one" = "$1 group";
"groups_other" = "$1 groups"; "groups_other" = "$1 groups";
"groups_list_zero" = "You are not a participant in any group";
"groups_list_one" = "You are participating in one group";
"groups_list_other" = "You are a participant of $1 groups";
"meetings_zero" = "No meetings"; "meetings_zero" = "No meetings";
"meetings_one" = "$1 meeting"; "meetings_one" = "$1 meeting";
"meetings_other" = "$1 meetings"; "meetings_other" = "$1 meetings";
@ -655,6 +668,14 @@
"banned_2" = "And the reason for this is simple: <b>$1</b>. Unfortunately, this time we had to block you forever."; "banned_2" = "And the reason for this is simple: <b>$1</b>. Unfortunately, this time we had to block you forever.";
"banned_3" = "You can still <a href=\"/support?act=new\">write to the support</a> if you think there was an error or <a href=\"/logout?hash=$1\">logout</a>."; "banned_3" = "You can still <a href=\"/support?act=new\">write to the support</a> if you think there was an error or <a href=\"/logout?hash=$1\">logout</a>.";
/* Registration confirm */
"ec_header" = "Registration confirmation";
"ec_title" = "Thanks!";
"ec_1" = "<b>$1</b>, your registration is almost done. In a few minutes you should receive an mail with a link to confirm your email address.";
"ec_2" = "If for some reason you don't get the mail, check your spam folder. If you don't find the email there, you can resend it.";
"ec_resend" = "Resend mail";
/* Discussions */ /* Discussions */
"discussions" = "Discussions"; "discussions" = "Discussions";

View file

@ -36,6 +36,13 @@
"password_reset_error" = "Непредвиденная ошибка при сбросе пароля."; "password_reset_error" = "Непредвиденная ошибка при сбросе пароля.";
"password_reset_rate_limit_error" = "Нельзя делать это так часто, извините."; "password_reset_rate_limit_error" = "Нельзя делать это так часто, извините.";
"email_sent" = "Письмо было успешно отправлено.";
"email_sent_desc" = "Если ваш электронный адрес существует, вы получите письмо.";
"email_error" = "Непредвиденная ошибка при отправке письма.";
"email_rate_limit_error" = "Нельзя делать это так часто, извините.";
"email_verify_success" = "Ваш Email был подтверждён. Приятного времяпрепровождения!";
"registration_disabled_info" = "Регистрация отключена системным администратором. При возможности попросите приглашение у вашего знакомого, если он зарегистрирован на этом сайте."; "registration_disabled_info" = "Регистрация отключена системным администратором. При возможности попросите приглашение у вашего знакомого, если он зарегистрирован на этом сайте.";
"registration_closed" = "Регистрация закрыта."; "registration_closed" = "Регистрация закрыта.";
"invites_you_to" = "<strong>$1</strong> приглашает вас в $2"; "invites_you_to" = "<strong>$1</strong> приглашает вас в $2";
@ -243,6 +250,7 @@
"only_administrators" = "Только администраторы"; "only_administrators" = "Только администраторы";
"website" = "Сайт"; "website" = "Сайт";
"managed" = "Управляемые"; "managed" = "Управляемые";
"size" = "Размер";
"administrators_one" = "$1 администратор"; "administrators_one" = "$1 администратор";
"administrators_few" = "$1 администратора"; "administrators_few" = "$1 администратора";
@ -695,6 +703,14 @@
"banned_2" = "А причина этому проста: <b>$1</b>. К сожалению, на этот раз нам пришлось заблокировать вас навсегда."; "banned_2" = "А причина этому проста: <b>$1</b>. К сожалению, на этот раз нам пришлось заблокировать вас навсегда.";
"banned_3" = "Вы всё ещё можете <a href=\"/support?act=new\">написать в службу поддержки</a>, если считаете что произошла ошибка или <a href=\"/logout?hash=$1\">выйти</a>."; "banned_3" = "Вы всё ещё можете <a href=\"/support?act=new\">написать в службу поддержки</a>, если считаете что произошла ошибка или <a href=\"/logout?hash=$1\">выйти</a>.";
/* Registration confirm */
"ec_header" = "Подтверждение регистрации";
"ec_title" = "Спасибо!";
"ec_1" = "<b>$1</b>, ваша регистрация почти закончена. В течении нескольких минут на ваш адрес E-mail должно прийти письмо с ссылкой для подтверждения вашего адреса почты.";
"ec_2" = "Если по каким-то причинам вам не пришло письмо, то проверьте папку Спам. Если письма не окажется и там, то вы можете переотправить письмо.";
"ec_resend" = "Переотправить письмо";
/* Discussions */ /* Discussions */
"discussions" = "Обсуждения"; "discussions" = "Обсуждения";