2020-06-07 19:04:43 +03:00
|
|
|
|
<?php declare(strict_types=1);
|
|
|
|
|
namespace openvk\Web\Presenters;
|
2021-01-01 00:18:53 +03:00
|
|
|
|
use openvk\Web\Models\Entities\IP;
|
2020-06-07 19:04:43 +03:00
|
|
|
|
use openvk\Web\Models\Entities\User;
|
|
|
|
|
use openvk\Web\Models\Entities\PasswordReset;
|
2022-01-31 14:45:53 +03:00
|
|
|
|
use openvk\Web\Models\Entities\EmailVerification;
|
2022-03-26 13:24:57 +03:00
|
|
|
|
use openvk\Web\Models\Exceptions\InvalidUserNameException;
|
2021-01-01 00:18:53 +03:00
|
|
|
|
use openvk\Web\Models\Repositories\IPs;
|
2020-06-07 19:04:43 +03:00
|
|
|
|
use openvk\Web\Models\Repositories\Users;
|
|
|
|
|
use openvk\Web\Models\Repositories\Restores;
|
2022-01-31 15:35:35 +03:00
|
|
|
|
use openvk\Web\Models\Repositories\Verifications;
|
2021-12-08 21:06:05 +03:00
|
|
|
|
use openvk\Web\Util\Validator;
|
2020-06-07 19:04:43 +03:00
|
|
|
|
use Chandler\Session\Session;
|
|
|
|
|
use Chandler\Security\User as ChandlerUser;
|
|
|
|
|
use Chandler\Security\Authenticator;
|
|
|
|
|
use Chandler\Database\DatabaseConnection;
|
2021-12-02 18:31:32 +03:00
|
|
|
|
use lfkeitel\phptotp\{Base32, Totp};
|
2020-06-07 19:04:43 +03:00
|
|
|
|
|
|
|
|
|
final class AuthPresenter extends OpenVKPresenter
|
|
|
|
|
{
|
|
|
|
|
protected $banTolerant = true;
|
2022-01-31 14:45:53 +03:00
|
|
|
|
protected $activationTolerant = true;
|
2022-08-05 23:00:52 +03:00
|
|
|
|
protected $deactivationTolerant = true;
|
2020-06-07 19:04:43 +03:00
|
|
|
|
|
|
|
|
|
private $authenticator;
|
|
|
|
|
private $db;
|
|
|
|
|
private $users;
|
|
|
|
|
private $restores;
|
2022-01-31 15:35:35 +03:00
|
|
|
|
private $verifications;
|
2020-06-07 19:04:43 +03:00
|
|
|
|
|
2022-01-31 15:35:35 +03:00
|
|
|
|
function __construct(Users $users, Restores $restores, Verifications $verifications)
|
2020-06-07 19:04:43 +03:00
|
|
|
|
{
|
|
|
|
|
$this->authenticator = Authenticator::i();
|
|
|
|
|
$this->db = DatabaseConnection::i()->getContext();
|
|
|
|
|
|
|
|
|
|
$this->users = $users;
|
|
|
|
|
$this->restores = $restores;
|
2022-01-31 15:35:35 +03:00
|
|
|
|
$this->verifications = $verifications;
|
2020-06-07 19:04:43 +03:00
|
|
|
|
|
|
|
|
|
parent::__construct();
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-01 00:18:53 +03:00
|
|
|
|
private function ipValid(): bool
|
|
|
|
|
{
|
|
|
|
|
$ip = (new IPs)->get(CONNECTING_IP);
|
|
|
|
|
$res = $ip->rateLimit(0);
|
|
|
|
|
|
|
|
|
|
return $res === IP::RL_RESET || $res === IP::RL_CANEXEC;
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-07 19:04:43 +03:00
|
|
|
|
function renderRegister(): void
|
|
|
|
|
{
|
|
|
|
|
if(!is_null($this->user))
|
|
|
|
|
$this->redirect("/id" . $this->user->id, static::REDIRECT_TEMPORARY);
|
|
|
|
|
|
|
|
|
|
if(!$this->hasPermission("user", "register", -1)) exit("Вас забанили");
|
|
|
|
|
|
2021-09-13 06:22:05 +03:00
|
|
|
|
$referer = NULL;
|
|
|
|
|
if(!is_null($refLink = $this->queryParam("ref"))) {
|
|
|
|
|
$pieces = explode(" ", $refLink, 2);
|
|
|
|
|
if(sizeof($pieces) !== 2)
|
2021-12-09 00:01:46 +03:00
|
|
|
|
$this->flashFail("err", tr("error"), tr("referral_link_invalid"));
|
2021-09-13 06:22:05 +03:00
|
|
|
|
|
|
|
|
|
[$ref, $hash] = $pieces;
|
|
|
|
|
$ref = hexdec($ref);
|
|
|
|
|
$hash = base64_decode($hash);
|
|
|
|
|
|
|
|
|
|
$referer = (new Users)->get($ref);
|
|
|
|
|
if(!$referer)
|
2021-12-09 00:01:46 +03:00
|
|
|
|
$this->flashFail("err", tr("error"), tr("referral_link_invalid"));
|
2021-09-13 06:22:05 +03:00
|
|
|
|
|
|
|
|
|
if($referer->getRefLinkId() !== $refLink)
|
2021-12-09 00:01:46 +03:00
|
|
|
|
$this->flashFail("err", tr("error"), tr("referral_link_invalid"));
|
2021-09-13 06:22:05 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$this->template->referer = $referer;
|
|
|
|
|
|
2020-06-07 19:04:43 +03:00
|
|
|
|
if($_SERVER["REQUEST_METHOD"] === "POST") {
|
|
|
|
|
$this->assertCaptchaCheckPassed();
|
2021-09-13 19:00:54 +03:00
|
|
|
|
|
|
|
|
|
if(!OPENVK_ROOT_CONF['openvk']['preferences']['registration']['enable'] && !$referer)
|
2021-12-09 00:01:46 +03:00
|
|
|
|
$this->flashFail("err", tr("failed_to_register"), tr("registration_disabled"));
|
2020-06-07 19:04:43 +03:00
|
|
|
|
|
2021-01-01 00:18:53 +03:00
|
|
|
|
if(!$this->ipValid())
|
2021-12-09 00:01:46 +03:00
|
|
|
|
$this->flashFail("err", tr("suspicious_registration_attempt"), tr("suspicious_registration_attempt_comment"));
|
2021-01-01 00:18:53 +03:00
|
|
|
|
|
2021-12-08 21:06:05 +03:00
|
|
|
|
if(!Validator::i()->emailValid($this->postParam("email")))
|
2021-12-09 00:01:46 +03:00
|
|
|
|
$this->flashFail("err", tr("invalid_email_address"), tr("invalid_email_address_comment"));
|
2020-06-07 19:04:43 +03:00
|
|
|
|
|
2021-10-13 10:37:57 +03:00
|
|
|
|
if (strtotime($this->postParam("birthday")) > time())
|
2022-01-31 14:45:53 +03:00
|
|
|
|
$this->flashFail("err", tr("invalid_birth_date"), tr("invalid_birth_date_comment"));
|
2021-10-13 10:37:57 +03:00
|
|
|
|
|
2022-03-26 13:24:57 +03:00
|
|
|
|
try {
|
|
|
|
|
$user = new User;
|
|
|
|
|
$user->setFirst_Name($this->postParam("first_name"));
|
|
|
|
|
$user->setLast_Name($this->postParam("last_name"));
|
|
|
|
|
$user->setSex((int)($this->postParam("sex") === "female"));
|
|
|
|
|
$user->setEmail($this->postParam("email"));
|
|
|
|
|
$user->setSince(date("Y-m-d H:i:s"));
|
|
|
|
|
$user->setRegistering_Ip(CONNECTING_IP);
|
2022-07-09 17:33:55 +03:00
|
|
|
|
$user->setBirthday(empty($this->postParam("birthday")) ? NULL : strtotime($this->postParam("birthday")));
|
2022-03-26 13:24:57 +03:00
|
|
|
|
$user->setActivated((int)!OPENVK_ROOT_CONF['openvk']['preferences']['security']['requireEmail']);
|
|
|
|
|
} catch(InvalidUserNameException $ex) {
|
|
|
|
|
$this->flashFail("err", tr("error"), tr("invalid_real_name"));
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-07 19:04:43 +03:00
|
|
|
|
$chUser = ChandlerUser::create($this->postParam("email"), $this->postParam("password"));
|
|
|
|
|
if(!$chUser)
|
2021-12-09 00:01:46 +03:00
|
|
|
|
$this->flashFail("err", tr("failed_to_register"), tr("user_already_exists"));
|
2022-03-26 13:24:57 +03:00
|
|
|
|
|
2020-06-07 19:04:43 +03:00
|
|
|
|
$user->setUser($chUser->getId());
|
|
|
|
|
$user->save();
|
|
|
|
|
|
2021-09-13 06:22:05 +03:00
|
|
|
|
if(!is_null($referer)) {
|
|
|
|
|
$user->toggleSubscription($referer);
|
|
|
|
|
$referer->toggleSubscription($user);
|
|
|
|
|
}
|
2022-01-31 14:45:53 +03:00
|
|
|
|
|
|
|
|
|
if (OPENVK_ROOT_CONF['openvk']['preferences']['security']['requireEmail']) {
|
2022-01-31 15:35:35 +03:00
|
|
|
|
$verification = new EmailVerification;
|
|
|
|
|
$verification->setProfile($user->getId());
|
|
|
|
|
$verification->save();
|
2022-01-31 14:45:53 +03:00
|
|
|
|
|
|
|
|
|
$params = [
|
2022-01-31 15:35:35 +03:00
|
|
|
|
"key" => $verification->getKey(),
|
2022-01-31 14:45:53 +03:00
|
|
|
|
"name" => $user->getCanonicalName(),
|
|
|
|
|
];
|
|
|
|
|
$this->sendmail($user->getEmail(), "verify-email", $params); #Vulnerability possible
|
|
|
|
|
}
|
2021-09-13 06:22:05 +03:00
|
|
|
|
|
2020-06-07 19:04:43 +03:00
|
|
|
|
$this->authenticator->authenticate($chUser->getId());
|
|
|
|
|
$this->redirect("/id" . $user->getId(), static::REDIRECT_TEMPORARY);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function renderLogin(): void
|
|
|
|
|
{
|
2021-01-27 21:00:30 +03:00
|
|
|
|
$redirUrl = $this->requestParam("jReturnTo");
|
|
|
|
|
|
2020-06-07 19:04:43 +03:00
|
|
|
|
if(!is_null($this->user))
|
2021-01-27 21:00:30 +03:00
|
|
|
|
$this->redirect($redirUrl ?? "/id" . $this->user->id, static::REDIRECT_TEMPORARY);
|
2020-06-07 19:04:43 +03:00
|
|
|
|
|
|
|
|
|
if(!$this->hasPermission("user", "login", -1)) exit("Вас забанили");
|
|
|
|
|
|
|
|
|
|
if($_SERVER["REQUEST_METHOD"] === "POST") {
|
|
|
|
|
|
|
|
|
|
$user = $this->db->table("ChandlerUsers")->where("login", $this->postParam("login"))->fetch();
|
|
|
|
|
if(!$user)
|
2021-12-09 00:01:46 +03:00
|
|
|
|
$this->flashFail("err", tr("login_failed"), tr("invalid_username_or_password"));
|
2020-06-07 19:04:43 +03:00
|
|
|
|
|
2021-12-02 18:31:32 +03:00
|
|
|
|
if(!$this->authenticator->verifyCredentials($user->id, $this->postParam("password")))
|
2021-12-09 00:01:46 +03:00
|
|
|
|
$this->flashFail("err", tr("login_failed"), tr("invalid_username_or_password"));
|
2021-12-02 18:31:32 +03:00
|
|
|
|
|
2022-01-27 14:01:27 +03:00
|
|
|
|
$ovkUser = new User($user->related("profiles.user")->fetch());
|
2022-08-05 23:00:52 +03:00
|
|
|
|
if($ovkUser->isDeleted() && !$ovkUser->isDeactivated())
|
2022-01-27 14:01:27 +03:00
|
|
|
|
$this->flashFail("err", tr("login_failed"), tr("invalid_username_or_password"));
|
|
|
|
|
|
2021-12-02 18:31:32 +03:00
|
|
|
|
$secret = $user->related("profiles.user")->fetch()["2fa_secret"];
|
|
|
|
|
$code = $this->postParam("code");
|
|
|
|
|
if(!is_null($secret)) {
|
|
|
|
|
$this->template->_template = "Auth/LoginSecondFactor.xml";
|
|
|
|
|
$this->template->login = $this->postParam("login");
|
|
|
|
|
$this->template->password = $this->postParam("password");
|
|
|
|
|
|
|
|
|
|
if(is_null($code))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if(!($code === (new Totp)->GenerateToken(Base32::decode($secret)) || $ovkUser->use2faBackupCode((int) $code))) {
|
2021-12-09 00:01:46 +03:00
|
|
|
|
$this->flash("err", tr("login_failed"), tr("incorrect_2fa_code"));
|
2021-12-02 18:31:32 +03:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-06-07 19:04:43 +03:00
|
|
|
|
|
2021-12-02 18:31:32 +03:00
|
|
|
|
$this->authenticator->authenticate($user->id);
|
2021-01-27 21:00:30 +03:00
|
|
|
|
$this->redirect($redirUrl ?? "/id" . $user->related("profiles.user")->fetch()->id, static::REDIRECT_TEMPORARY);
|
2020-06-07 19:04:43 +03:00
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function renderSu(string $uuid): void
|
|
|
|
|
{
|
|
|
|
|
$this->assertNoCSRF();
|
|
|
|
|
$this->assertUserLoggedIn();
|
|
|
|
|
|
|
|
|
|
if($uuid === "unset") {
|
|
|
|
|
Session::i()->set("_su", NULL);
|
|
|
|
|
$this->redirect("/", static::REDIRECT_TEMPORARY);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(!$this->db->table("ChandlerUsers")->where("id", $uuid))
|
2021-12-09 00:01:46 +03:00
|
|
|
|
$this->flashFail("err", tr("token_manipulation_error"), tr("profile_not_found"));
|
2020-06-07 19:04:43 +03:00
|
|
|
|
|
|
|
|
|
$this->assertPermission('openvk\Web\Models\Entities\User', 'substitute', 0);
|
|
|
|
|
Session::i()->set("_su", $uuid);
|
2021-12-09 00:01:46 +03:00
|
|
|
|
$this->flash("succ", tr("profile_changed"), tr("profile_changed_comment"));
|
2020-06-07 19:04:43 +03:00
|
|
|
|
$this->redirect("/", static::REDIRECT_TEMPORARY);
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function renderLogout(): void
|
|
|
|
|
{
|
|
|
|
|
$this->assertUserLoggedIn();
|
2021-12-05 01:48:25 +03:00
|
|
|
|
$this->assertNoCSRF();
|
2020-06-07 19:04:43 +03:00
|
|
|
|
$this->authenticator->logout();
|
|
|
|
|
Session::i()->set("_su", NULL);
|
|
|
|
|
|
|
|
|
|
$this->redirect("/", static::REDIRECT_TEMPORARY_PRESISTENT);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function renderFinishRestoringPassword(): void
|
|
|
|
|
{
|
|
|
|
|
$request = $this->restores->getByToken(str_replace(" ", "+", $this->queryParam("key")));
|
|
|
|
|
if(!$request || !$request->isStillValid()) {
|
2021-12-09 00:01:46 +03:00
|
|
|
|
$this->flash("err", tr("token_manipulation_error"), tr("token_manipulation_error_comment"));
|
2020-06-07 19:04:43 +03:00
|
|
|
|
$this->redirect("/");
|
2021-12-09 00:01:46 +03:00
|
|
|
|
return;
|
2020-06-07 19:04:43 +03:00
|
|
|
|
}
|
2021-12-02 18:31:32 +03:00
|
|
|
|
|
|
|
|
|
$this->template->is2faEnabled = $request->getUser()->is2faEnabled();
|
2020-06-07 19:04:43 +03:00
|
|
|
|
|
|
|
|
|
if($_SERVER["REQUEST_METHOD"] === "POST") {
|
2021-12-02 18:31:32 +03:00
|
|
|
|
if($request->getUser()->is2faEnabled()) {
|
|
|
|
|
$user = $request->getUser();
|
|
|
|
|
$code = $this->postParam("code");
|
|
|
|
|
$secret = $user->get2faSecret();
|
|
|
|
|
if(!($code === (new Totp)->GenerateToken(Base32::decode($secret)) || $user->use2faBackupCode((int) $code))) {
|
|
|
|
|
$this->flash("err", tr("error"), tr("incorrect_2fa_code"));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-07 19:04:43 +03:00
|
|
|
|
$user = $request->getUser()->getChandlerUser();
|
|
|
|
|
$this->db->table("ChandlerTokens")->where("user", $user->getId())->delete(); #Logout from everywhere
|
|
|
|
|
|
|
|
|
|
$user->updatePassword($this->postParam("password"));
|
|
|
|
|
$this->authenticator->authenticate($user->getId());
|
|
|
|
|
|
|
|
|
|
$request->delete(false);
|
2021-12-09 00:01:46 +03:00
|
|
|
|
$this->flash("succ", tr("information_-1"), tr("password_successfully_reset"));
|
2020-06-07 19:04:43 +03:00
|
|
|
|
$this->redirect("/settings");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function renderRestore(): void
|
|
|
|
|
{
|
2021-12-31 07:35:05 +03:00
|
|
|
|
if(!is_null($this->user))
|
|
|
|
|
$this->redirect("/id" . $this->user->id, static::REDIRECT_TEMPORARY);
|
|
|
|
|
|
2020-06-07 19:04:43 +03:00
|
|
|
|
if(($this->queryParam("act") ?? "default") === "finish")
|
|
|
|
|
$this->pass("openvk!Auth->finishRestoringPassword");
|
|
|
|
|
|
|
|
|
|
if($_SERVER["REQUEST_METHOD"] === "POST") {
|
|
|
|
|
$uRow = $this->db->table("ChandlerUsers")->where("login", $this->postParam("login"))->fetch();
|
|
|
|
|
if(!$uRow) {
|
|
|
|
|
#Privacy of users must be protected. We will not tell if email is bound to a user or not.
|
2021-12-09 00:01:46 +03:00
|
|
|
|
$this->flashFail("succ", tr("information_-1"), tr("password_reset_email_sent"));
|
2020-06-07 19:04:43 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$user = $this->users->getByChandlerUser(new ChandlerUser($uRow));
|
2022-01-27 14:01:27 +03:00
|
|
|
|
if(!$user || $user->isDeleted())
|
2021-12-09 00:01:46 +03:00
|
|
|
|
$this->flashFail("err", tr("error"), tr("password_reset_error"));
|
2020-06-07 19:04:43 +03:00
|
|
|
|
|
|
|
|
|
$request = $this->restores->getLatestByUser($user);
|
|
|
|
|
if(!is_null($request) && $request->isNew())
|
2021-12-09 00:01:46 +03:00
|
|
|
|
$this->flashFail("err", tr("forbidden"), tr("password_reset_rate_limit_error"));
|
2020-06-07 19:04:43 +03:00
|
|
|
|
|
|
|
|
|
$resetObj = new PasswordReset;
|
|
|
|
|
$resetObj->setProfile($user->getId());
|
|
|
|
|
$resetObj->save();
|
|
|
|
|
|
|
|
|
|
$params = [
|
|
|
|
|
"key" => $resetObj->getKey(),
|
|
|
|
|
"name" => $user->getCanonicalName(),
|
|
|
|
|
];
|
|
|
|
|
$this->sendmail($uRow->login, "password-reset", $params); #Vulnerability possible
|
|
|
|
|
|
|
|
|
|
|
2021-12-09 00:01:46 +03:00
|
|
|
|
$this->flashFail("succ", tr("information_-1"), tr("password_reset_email_sent"));
|
2020-06-07 19:04:43 +03:00
|
|
|
|
}
|
|
|
|
|
}
|
2022-01-31 14:45:53 +03:00
|
|
|
|
|
|
|
|
|
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"));
|
|
|
|
|
|
2022-01-31 15:35:35 +03:00
|
|
|
|
$request = $this->verifications->getLatestByUser($user);
|
2022-01-31 14:45:53 +03:00
|
|
|
|
if(!is_null($request) && $request->isNew())
|
|
|
|
|
$this->flashFail("err", tr("forbidden"), tr("email_rate_limit_error"));
|
|
|
|
|
|
2022-01-31 15:35:35 +03:00
|
|
|
|
$verification = new EmailVerification;
|
|
|
|
|
$verification->setProfile($user->getId());
|
|
|
|
|
$verification->save();
|
2022-01-31 14:45:53 +03:00
|
|
|
|
|
|
|
|
|
$params = [
|
2022-01-31 15:35:35 +03:00
|
|
|
|
"key" => $verification->getKey(),
|
2022-01-31 14:45:53 +03:00
|
|
|
|
"name" => $user->getCanonicalName(),
|
|
|
|
|
];
|
|
|
|
|
$this->sendmail($user->getEmail(), "verify-email", $params); #Vulnerability possible
|
|
|
|
|
|
|
|
|
|
$this->flashFail("succ", tr("information_-1"), tr("email_sent"));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function renderVerifyEmail(): void
|
|
|
|
|
{
|
2022-01-31 15:35:35 +03:00
|
|
|
|
$request = $this->verifications->getByToken(str_replace(" ", "+", $this->queryParam("key")));
|
2022-01-31 14:45:53 +03:00
|
|
|
|
if(!$request || !$request->isStillValid()) {
|
|
|
|
|
$this->flash("err", tr("token_manipulation_error"), tr("token_manipulation_error_comment"));
|
|
|
|
|
$this->redirect("/");
|
2022-01-31 15:35:35 +03:00
|
|
|
|
} else {
|
2022-01-31 14:45:53 +03:00
|
|
|
|
$user = $request->getUser();
|
|
|
|
|
$user->setActivated(1);
|
|
|
|
|
$user->save();
|
|
|
|
|
|
|
|
|
|
$this->flash("success", tr("email_verify_success"));
|
|
|
|
|
$this->redirect("/");
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-08-05 23:00:52 +03:00
|
|
|
|
|
|
|
|
|
function renderReactivatePage(): void
|
|
|
|
|
{
|
|
|
|
|
$this->assertUserLoggedIn();
|
|
|
|
|
$this->willExecuteWriteAction();
|
|
|
|
|
|
|
|
|
|
$this->user->identity->reactivate();
|
|
|
|
|
|
|
|
|
|
$this->redirect("/", 2);
|
|
|
|
|
}
|
2020-06-07 19:04:43 +03:00
|
|
|
|
}
|