Added support for reCaptcha #222

This commit is contained in:
the-djmaze 2022-02-11 14:21:41 +01:00
parent 44ee236709
commit fecc9f96eb
3 changed files with 214 additions and 2 deletions

View file

@ -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
View 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);
}
}
}
}
}

View 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);