From 8f479235d2e0a848fcb69b3d3ca624aeec2e29fe Mon Sep 17 00:00:00 2001 From: djmaze Date: Thu, 25 Mar 2021 14:16:59 +0100 Subject: [PATCH] https://github.com/RainLoop/rainloop-webmail/pull/2054 --- plugins/hcaptcha/index.php | 166 ++++++++++++++++++++++++++++++++ plugins/hcaptcha/js/hcaptcha.js | 88 +++++++++++++++++ 2 files changed, 254 insertions(+) create mode 100644 plugins/hcaptcha/index.php create mode 100644 plugins/hcaptcha/js/hcaptcha.js diff --git a/plugins/hcaptcha/index.php b/plugins/hcaptcha/index.php new file mode 100644 index 000000000..6ea2a5ed3 --- /dev/null +++ b/plugins/hcaptcha/index.php @@ -0,0 +1,166 @@ +UseLangs(true); + + $this->addJs('js/hcaptcha.js'); + + $this->addHook('json.action-pre-call', 'JsonActionPreCall'); + $this->addHook('filter.json-response', 'FilterJsonResponse'); + } + + /** + * @return array + */ + public function configMapping() : array + { + return array( + Property::NewInstance('site_key')->SetLabel('Site key') + ->SetAllowedInJs(true) + ->SetDefaultValue(''), + Property::NewInstance('secret_key')->SetLabel('Secret key') + ->SetDefaultValue(''), + Property::NewInstance('theme')->SetLabel('Theme') + ->SetAllowedInJs(true) + ->SetType(PluginPropertyType::SELECTION) + ->SetDefaultValue(array('light', 'dark')), + Property::NewInstance('error_limit')->SetLabel('Limit') + ->SetType(PluginPropertyType::SELECTION) + ->SetDefaultValue(array(0, 1, 2, 3, 4, 5)) + ->SetDescription('') + ); + } + + /** + * @return string + */ + private function getCaptchaCacherKey() + { + return 'CaptchaNew/Login/'.\RainLoop\Utils::GetConnectionToken(); + } + + /** + * @return int + */ + private function getLimit() + { + $iConfigLimit = $this->Config()->Get('plugin', 'error_limit', 0); + if (0 < $iConfigLimit) + { + $oCacher = $this->Manager()->Actions()->Cacher(); + $sLimit = $oCacher && $oCacher->IsInited() ? $oCacher->Get($this->getCaptchaCacherKey()) : '0'; + + if (0 < \strlen($sLimit) && \is_numeric($sLimit)) + { + $iConfigLimit -= (int) $sLimit; + } + } + + return $iConfigLimit; + } + + /** + * @return void + */ + public function FilterAppDataPluginSection(bool $bAdmin, bool $bAuth, array &$aConfig) : void + { + if (!$bAdmin && !$bAuth && \is_array($aData)) + { + $aData['show_captcha_on_login'] = 1 > $this->getLimit(); + } + } + + /** + * @param string $sAction + */ + public function JsonActionPreCall($sAction) + { + if ('Login' === $sAction && 0 >= $this->getLimit()) + { + $bResult = false; + + $sResult = $this->Manager()->Actions()->Http()->SendPostRequest( + 'https://hcaptcha.com/siteverify', + array( + 'secret' => $this->Config()->Get('plugin', 'secret_key', ''), + 'response' => $this->Manager()->Actions()->GetActionParam('h-captcha-response', '') + ) + ); + + if ($sResult) + { + $aResp = @\json_decode($sResult, true); + if (\is_array($aResp) && isset($aResp['success']) && $aResp['success']) + { + $bResult = true; + } + } + + if (!$bResult) + { + $this->Manager()->Actions()->Logger()->Write('HcaptchaResponse:'.$sResult); + throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CaptchaError); + } + } + } + + /** + * @param string $sAction + * @param array $aResponseItem + */ + public function FilterJsonResponse($sAction, &$aResponseItem) + { + if ('Login' === $sAction && $aResponseItem && isset($aResponseItem['Result'])) + { + $oCacher = $this->Manager()->Actions()->Cacher(); + $iConfigLimit = (int) $this->Config()->Get('plugin', 'error_limit', 0); + + $sKey = $this->getCaptchaCacherKey(); + + if (0 < $iConfigLimit && $oCacher && $oCacher->IsInited()) + { + if (false === $aResponseItem['Result']) + { + $iLimit = 0; + $sLimut = $oCacher->Get($sKey); + if (0 < \strlen($sLimut) && \is_numeric($sLimut)) + { + $iLimit = (int) $sLimut; + } + + $oCacher->Set($sKey, ++$iLimit); + + if ($iConfigLimit <= $iLimit) + { + $aResponseItem['Captcha'] = true; + } + } + else + { + $oCacher->Delete($sKey); + } + } + } + } +} diff --git a/plugins/hcaptcha/js/hcaptcha.js b/plugins/hcaptcha/js/hcaptcha.js new file mode 100644 index 000000000..558932255 --- /dev/null +++ b/plugins/hcaptcha/js/hcaptcha.js @@ -0,0 +1,88 @@ +(rl => { + if (rl) { + var + nId = null, + bStarted = false + ; + + function ShowHcaptcha() + { + if (window.hcaptcha && null === nId) + { + var + oEl = null, + oLink = document.getElementById('plugin-Login-BottomControlGroup') + ; + + if (oLink) + { + oEl = document.createElement('div'); + oEl.className = 'controls'; + + oLink.append(oEl); + + nId = hcaptcha.render(oEl, { + 'sitekey': rl.pluginSettingsGet('hcaptcha', 'site_key'), + 'theme': rl.pluginSettingsGet('hcaptcha', 'theme') + }); + } + } + } + + window.__globalShowHcaptcha = ShowHcaptcha; + + function StartHcaptcha() + { + if (window.hcaptcha) + { + ShowHcaptcha(); + } + else + { + const script = document.createElement('script'); + script.src = 'https://hcaptcha.com/1/api.js?onload=__globalShowHcaptcha&render=explicit'; + document.head.append(script); + } + } + + rl.addHook('user-login-submit', fSubmitResult => { + if (null !== nId && !window.hcaptcha.getResponse(nId)) + { + fSubmitResult(105); + } + }); + + rl.addHook('view-model-on-show', (sName, oViewModel) => { + if (!bStarted && oViewModel && 'LoginUserView' === sName + && rl.pluginSettingsGet('hcaptcha', 'show_captcha_on_login')) + { + bStarted = true; + StartHcaptcha(); + } + }); + + rl.addHook('json-default-request', (sAction, oParameters) => { + if ('Login' === sAction && oParameters && null !== nId && window.hcaptcha) + { + oParameters['h-captcha-response'] = hcaptcha.getResponse(nId); + } + }); + + rl.addHook('json-default-response', (sAction, oData, sType) => { + if ('Login' === sAction) + { + if (!oData || 'success' !== sType || !oData['Result']) + { + if (null !== nId && window.hcaptcha) + { + hcaptcha.reset(nId); + } + else if (oData && oData['Captcha']) + { + StartHcaptcha(); + } + } + } + }); + } +})(window.rl);