authenticator = Authenticator::i(); $this->db = DatabaseConnection::i()->getContext(); $this->users = $users; $this->restores = $restores; $this->verifications = $verifications; parent::__construct(); } private function ipValid(): bool { $ip = (new IPs())->get(CONNECTING_IP); $res = $ip->rateLimit(0); return $res === IP::RL_RESET || $res === IP::RL_CANEXEC; } public function renderRegister(): void { if (!is_null($this->user)) { $this->redirect($this->user->identity->getURL()); } if (!$this->hasPermission("user", "register", -1)) { exit("Вас забанили"); } $referer = null; if (!is_null($refLink = $this->queryParam("ref"))) { $pieces = explode(" ", $refLink, 2); if (sizeof($pieces) !== 2) { $this->flashFail("err", tr("error"), tr("referral_link_invalid")); } [$ref, $hash] = $pieces; $ref = hexdec($ref); $hash = base64_decode($hash); $referer = (new Users())->get($ref); if (!$referer) { $this->flashFail("err", tr("error"), tr("referral_link_invalid")); } if ($referer->getRefLinkId() !== $refLink) { $this->flashFail("err", tr("error"), tr("referral_link_invalid")); } } $this->template->referer = $referer; if ($_SERVER["REQUEST_METHOD"] === "POST") { $this->assertCaptchaCheckPassed(); if (!OPENVK_ROOT_CONF['openvk']['preferences']['registration']['enable'] && !$referer) { $this->flashFail("err", tr("failed_to_register"), tr("registration_disabled")); } if (!$this->ipValid()) { $this->flashFail("err", tr("suspicious_registration_attempt"), tr("suspicious_registration_attempt_comment")); } if (!Validator::i()->emailValid($this->postParam("email"))) { $this->flashFail("err", tr("invalid_email_address"), tr("invalid_email_address_comment")); } if (OPENVK_ROOT_CONF['openvk']['preferences']['security']['forceStrongPassword']) { if (!Validator::i()->passwordStrong($this->postParam("password"))) { $this->flashFail("err", tr("error"), tr("error_weak_password")); } } if (strtotime($this->postParam("birthday")) > time()) { $this->flashFail("err", tr("invalid_birth_date"), tr("invalid_birth_date_comment")); } if (!$this->postParam("confirmation")) { $this->flashFail("err", tr("error"), tr("checkbox_in_registration_unchecked")); } try { $user = new User(); $user->setFirst_Name($this->postParam("first_name")); $user->setLast_Name($this->postParam("last_name")); switch ($this->postParam("pronouns")) { case 'male': $user->setSex(0); break; case 'female': $user->setSex(1); break; case 'neutral': $user->setSex(2); break; } $user->setEmail($this->postParam("email")); $user->setSince(date("Y-m-d H:i:s")); $user->setRegistering_Ip(CONNECTING_IP); $user->setBirthday(empty($this->postParam("birthday")) ? null : strtotime($this->postParam("birthday"))); $user->setActivated((int) !OPENVK_ROOT_CONF['openvk']['preferences']['security']['requireEmail']); } catch (InvalidUserNameException $ex) { $this->flashFail("err", tr("error"), tr("invalid_real_name")); } $chUser = ChandlerUser::create($this->postParam("email"), $this->postParam("password")); if (!$chUser) { $this->flashFail("err", tr("failed_to_register"), tr("user_already_exists")); } $user->setUser($chUser->getId()); $user->save(false); 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()); $user->save(); } } public function renderLogin(): void { $redirUrl = $this->requestParam("jReturnTo"); if (!is_null($this->user)) { $this->redirect($redirUrl ?? $this->user->identity->getURL()); } 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) { $this->flashFail("err", tr("login_failed"), tr("invalid_username_or_password")); } if (!$this->authenticator->verifyCredentials($user->id, $this->postParam("password"))) { $this->flashFail("err", tr("login_failed"), tr("invalid_username_or_password")); } $ovkUser = new User($user->related("profiles.user")->fetch()); if ($ovkUser->isDeleted() && !$ovkUser->isDeactivated()) { $this->flashFail("err", tr("login_failed"), tr("invalid_username_or_password")); } $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))) { $this->flash("err", tr("login_failed"), tr("incorrect_2fa_code")); return; } } $this->authenticator->authenticate($user->id); $this->redirect($redirUrl ?? $ovkUser->getURL()); } } public function renderSu(string $uuid): void { $this->assertNoCSRF(); $this->assertUserLoggedIn(); if ($uuid === "unset") { Session::i()->set("_su", null); $this->redirect("/"); } if (!$this->db->table("ChandlerUsers")->where("id", $uuid)) { $this->flashFail("err", tr("token_manipulation_error"), tr("profile_not_found")); } $this->assertPermission('openvk\Web\Models\Entities\User', 'substitute', 0); Session::i()->set("_su", $uuid); $this->flash("succ", tr("profile_changed"), tr("profile_changed_comment")); $this->redirect("/"); } public function renderLogout(): void { $this->assertUserLoggedIn(); $this->assertNoCSRF(); $this->authenticator->logout(); Session::i()->set("_su", null); $this->redirect("/"); } public function renderFinishRestoringPassword(): void { if (OPENVK_ROOT_CONF['openvk']['preferences']['security']['disablePasswordRestoring']) { $this->notFound(); } $request = $this->restores->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; } $this->template->disable_ajax = 1; $this->template->is2faEnabled = $request->getUser()->is2faEnabled(); if ($_SERVER["REQUEST_METHOD"] === "POST") { 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; } } $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); $this->flash("succ", tr("information_-1"), tr("password_successfully_reset")); $this->redirect("/settings"); } } public function renderRestore(): void { if (OPENVK_ROOT_CONF['openvk']['preferences']['security']['disablePasswordRestoring']) { $this->notFound(); } if (!is_null($this->user)) { $this->redirect($this->user->identity->getURL()); } 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. $this->flashFail("succ", tr("information_-1"), tr("password_reset_email_sent")); } $user = $this->users->getByChandlerUser(new ChandlerUser($uRow)); if (!$user || $user->isDeleted()) { $this->flashFail("err", tr("error"), tr("password_reset_error")); } $request = $this->restores->getLatestByUser($user); if (!is_null($request) && $request->isNew()) { $this->flashFail("err", tr("forbidden"), tr("password_reset_rate_limit_error")); } $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 $this->flashFail("succ", tr("information_-1"), tr("password_reset_email_sent")); } } public function renderResendEmail(): void { if (!is_null($this->user) && $this->user->identity->isActivated()) { $this->redirect($this->user->identity->getURL()); } 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")); } } public 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("/"); } } public function renderReactivatePage(): void { $this->assertUserLoggedIn(); $this->willExecuteWriteAction(); $this->user->identity->reactivate(); $this->redirect("/"); } public function renderUnbanThemself(): void { $this->assertUserLoggedIn(); $this->willExecuteWriteAction(); if (!$this->user->identity->canUnbanThemself()) { $this->flashFail("err", tr("error"), tr("forbidden")); } $user = $this->users->get($this->user->id); $ban = (new Bans())->get((int) $user->getRawBanReason()); if (!$ban || $ban->isOver() || $ban->isPermanent()) { $this->flashFail("err", tr("error"), tr("forbidden")); } $ban->setRemoved_Manually(2); $ban->setRemoved_By($this->user->identity->getId()); $ban->save(); $user->setBlock_Reason(null); // $user->setUnblock_Time(NULL); $user->save(); $this->flashFail("succ", tr("banned_unban_title"), tr("banned_unban_description")); } /* * This function will revoke all tokens, including API and Web tokens and except active one * * OF COURSE it requires CSRF */ public function renderRevokeAllTokens(): void { $this->assertUserLoggedIn(); $this->willExecuteWriteAction(); $this->assertNoCSRF(); // API tokens $this->db->table("api_tokens")->where("user", $this->user->identity->getId())->delete(); // Web tokens $this->db->table("ChandlerTokens")->where("user", $this->user->identity->getChandlerGUID())->where("token != ?", Session::i()->get("tok"))->delete(); $this->flashFail("succ", tr("information_-1"), tr("end_all_sessions_done")); } }