mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-07-08 22:18:28 +03:00
Added support for reCaptcha #222
This commit is contained in:
parent
44ee236709
commit
fecc9f96eb
3 changed files with 214 additions and 2 deletions
|
|
@ -1,6 +1,6 @@
|
|||
import { Notification } from 'Common/Enums';
|
||||
import { ClientSideKeyName } from 'Common/EnumsUser';
|
||||
import { SettingsGet } from 'Common/Globals';
|
||||
import { SettingsGet, fireEvent } from 'Common/Globals';
|
||||
import { getNotification, translatorReload, convertLangName } from 'Common/Translator';
|
||||
|
||||
import { LanguageStore } from 'Stores/Language';
|
||||
|
|
@ -89,7 +89,7 @@ class LoginUserView extends AbstractViewLogin {
|
|||
submitCommand(self, event) {
|
||||
let form = event.target.form,
|
||||
data = new FormData(form),
|
||||
valid = form.reportValidity();
|
||||
valid = form.reportValidity() && fireEvent('sm-user-login', data);
|
||||
|
||||
this.emailError(!this.email());
|
||||
this.passwordError(!this.password());
|
||||
|
|
@ -101,6 +101,10 @@ class LoginUserView extends AbstractViewLogin {
|
|||
data.set('SignMe', this.signMe() ? 1 : 0);
|
||||
Remote.request('Login',
|
||||
(iError, oData) => {
|
||||
fireEvent('sm-user-login-response', {
|
||||
error: iError,
|
||||
data: oData
|
||||
});
|
||||
if (iError) {
|
||||
this.submitRequest(false);
|
||||
if (Notification.InvalidInputArgument == iError) {
|
||||
|
|
|
|||
143
plugins/recaptcha/index.php
Normal file
143
plugins/recaptcha/index.php
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
<?php
|
||||
|
||||
class RecaptchaPlugin extends \RainLoop\Plugins\AbstractPlugin
|
||||
{
|
||||
const
|
||||
NAME = 'reCaptcha',
|
||||
AUTHOR = 'SnappyMail',
|
||||
URL = 'https://snappymail.eu/',
|
||||
VERSION = '2.12',
|
||||
RELEASE = '2022-02-11',
|
||||
REQUIRED = '2.12.0',
|
||||
CATEGORY = 'General',
|
||||
LICENSE = 'MIT',
|
||||
DESCRIPTION = 'A CAPTCHA (v2) is a program that can generate and grade tests that humans can pass but current computer programs cannot. For example, humans can read distorted text as the one shown below, but current computer programs can\'t. More info at https://developers.google.com/recaptcha';
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function Init() : void
|
||||
{
|
||||
$this->UseLangs(true);
|
||||
|
||||
$this->addJs('js/recaptcha.js');
|
||||
|
||||
$this->addHook('json.action-pre-call', 'AjaxActionPreCall');
|
||||
$this->addHook('filter.json-response', 'FilterAjaxResponse');
|
||||
}
|
||||
|
||||
protected function configMapping() : array
|
||||
{
|
||||
return array(
|
||||
\RainLoop\Plugins\Property::NewInstance('public_key')->SetLabel('Site key')
|
||||
->SetAllowedInJs(true)
|
||||
->SetDefaultValue(''),
|
||||
\RainLoop\Plugins\Property::NewInstance('private_key')->SetLabel('Secret key')
|
||||
->SetDefaultValue(''),
|
||||
\RainLoop\Plugins\Property::NewInstance('theme')->SetLabel('Theme')
|
||||
->SetAllowedInJs(true)
|
||||
->SetType(\RainLoop\Enumerations\PluginPropertyType::SELECTION)
|
||||
->SetDefaultValue(array('light', 'dark')),
|
||||
\RainLoop\Plugins\Property::NewInstance('error_limit')->SetLabel('Limit')
|
||||
->SetType(\RainLoop\Enumerations\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 (\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) {
|
||||
$aConfig['show_captcha_on_login'] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sAction
|
||||
*/
|
||||
public function AjaxActionPreCall(string $sAction)
|
||||
{
|
||||
if ('Login' === $sAction && 0 >= $this->getLimit()) {
|
||||
$bResult = false;
|
||||
|
||||
$HTTP = \SnappyMail\HTTP\Request::factory();
|
||||
$oResponse = $HTTP->doRequest('POST', 'https://www.google.com/recaptcha/api/siteverify', array(
|
||||
'secret' => $this->Config()->Get('plugin', 'private_key', ''),
|
||||
'response' => $this->Manager()->Actions()->GetActionParam('RecaptchaResponse', '')
|
||||
));
|
||||
|
||||
if ($oResponse) {
|
||||
$aResp = \json_decode($oResponse->body, true);
|
||||
if (\is_array($aResp) && isset($aResp['success']) && $aResp['success']) {
|
||||
$bResult = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$bResult) {
|
||||
$this->Manager()->Actions()->Logger()->Write('RecaptchaResponse:'.$sResult);
|
||||
throw new \RainLoop\Exceptions\ClientException(105);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sAction
|
||||
* @param array $aResponseItem
|
||||
*/
|
||||
public function FilterAjaxResponse(string $sAction, array &$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 (\strlen($sLimut) && \is_numeric($sLimut)) {
|
||||
$iLimit = (int) $sLimut;
|
||||
}
|
||||
|
||||
$oCacher->Set($sKey, ++$iLimit);
|
||||
|
||||
if ($iConfigLimit <= $iLimit) {
|
||||
$aResponseItem['Captcha'] = true;
|
||||
}
|
||||
} else {
|
||||
$oCacher->Delete($sKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
65
plugins/recaptcha/js/recaptcha.js
Normal file
65
plugins/recaptcha/js/recaptcha.js
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
|
||||
(rl => {
|
||||
|
||||
rl && addEventListener('rl-view-model', e => {
|
||||
if (e.detail && 'Login' === e.detail.viewModelTemplateID
|
||||
&& rl.pluginSettingsGet('recaptcha', 'show_captcha_on_login')) {
|
||||
let
|
||||
nId = null,
|
||||
script;
|
||||
|
||||
const
|
||||
doc = document,
|
||||
|
||||
container = e.detail.viewModelDom.querySelector('#plugin-Login-BottomControlGroup'),
|
||||
|
||||
ShowRecaptcha = () => {
|
||||
if (window.grecaptcha && null === nId && container) {
|
||||
const oEl = doc.createElement('div');
|
||||
oEl.className = 'controls';
|
||||
|
||||
container.after(oEl);
|
||||
|
||||
nId = window.grecaptcha.render(oEl, {
|
||||
'sitekey': rl.pluginSettingsGet('recaptcha', 'public_key'),
|
||||
'theme': rl.pluginSettingsGet('recaptcha', 'theme')
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
StartRecaptcha = () => {
|
||||
if (window.grecaptcha) {
|
||||
ShowRecaptcha();
|
||||
} else if (!script) {
|
||||
script = doc.createElement('script');
|
||||
// script.onload = ShowRecaptcha;
|
||||
script.src = 'https://www.google.com/recaptcha/api.js?onload=ShowRecaptcha&render=explicit&hl=' + doc.documentElement.lang;
|
||||
doc.head.append(script);
|
||||
}
|
||||
};
|
||||
|
||||
window.ShowRecaptcha = ShowRecaptcha;
|
||||
|
||||
StartRecaptcha();
|
||||
|
||||
addEventListener('sm-user-login', e => {
|
||||
if (null !== nId && window.grecaptcha) {
|
||||
e.detail.set('RecaptchaResponse', window.grecaptcha.getResponse(nId));
|
||||
} else {
|
||||
e.preventDefault()
|
||||
}
|
||||
});
|
||||
|
||||
addEventListener('sm-user-login-response', e => {
|
||||
if (e.detail.error) {
|
||||
if (null !== nId && window.grecaptcha) {
|
||||
window.grecaptcha.reset(nId);
|
||||
} else if (e.detail.data && e.detail.data.Captcha) {
|
||||
StartRecaptcha();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
})(window.rl);
|
||||
Loading…
Add table
Add a link
Reference in a new issue