Skip to content

Commit 8f47923

Browse files
author
djmaze
committed
https://github.com/RainLoop/rainloop-webmail/pull/2054
1 parent bb05e3a commit 8f47923

File tree

2 files changed

+254
-0
lines changed

2 files changed

+254
-0
lines changed

plugins/hcaptcha/index.php

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
<?php
2+
3+
use RainLoop\Enumerations\PluginPropertyType;
4+
use RainLoop\Plugins\Property;
5+
6+
class HcaptchaPlugin extends \RainLoop\Plugins\AbstractPlugin
7+
{
8+
const
9+
NAME = 'hCaptcha',
10+
VERSION = '2.0',
11+
AUTHOR = 'Unkorneglosk',
12+
URL = 'https://github.com/Unkorneglosk',
13+
RELEASE = '2021-01-04',
14+
REQUIRED = '2.1.0',
15+
CATEGORY = 'Security',
16+
DESCRIPTION = 'A CAPTCHA is a program that can generate and grade tests that humans can pass but current computer programs cannot.
17+
For example, humans can read distorted text as the one shown below, but current computer programs can\'t.
18+
More info at https://hcaptcha.com';
19+
20+
/**
21+
* @return void
22+
*/
23+
public function Init() : void
24+
{
25+
$this->UseLangs(true);
26+
27+
$this->addJs('js/hcaptcha.js');
28+
29+
$this->addHook('json.action-pre-call', 'JsonActionPreCall');
30+
$this->addHook('filter.json-response', 'FilterJsonResponse');
31+
}
32+
33+
/**
34+
* @return array
35+
*/
36+
public function configMapping() : array
37+
{
38+
return array(
39+
Property::NewInstance('site_key')->SetLabel('Site key')
40+
->SetAllowedInJs(true)
41+
->SetDefaultValue(''),
42+
Property::NewInstance('secret_key')->SetLabel('Secret key')
43+
->SetDefaultValue(''),
44+
Property::NewInstance('theme')->SetLabel('Theme')
45+
->SetAllowedInJs(true)
46+
->SetType(PluginPropertyType::SELECTION)
47+
->SetDefaultValue(array('light', 'dark')),
48+
Property::NewInstance('error_limit')->SetLabel('Limit')
49+
->SetType(PluginPropertyType::SELECTION)
50+
->SetDefaultValue(array(0, 1, 2, 3, 4, 5))
51+
->SetDescription('')
52+
);
53+
}
54+
55+
/**
56+
* @return string
57+
*/
58+
private function getCaptchaCacherKey()
59+
{
60+
return 'CaptchaNew/Login/'.\RainLoop\Utils::GetConnectionToken();
61+
}
62+
63+
/**
64+
* @return int
65+
*/
66+
private function getLimit()
67+
{
68+
$iConfigLimit = $this->Config()->Get('plugin', 'error_limit', 0);
69+
if (0 < $iConfigLimit)
70+
{
71+
$oCacher = $this->Manager()->Actions()->Cacher();
72+
$sLimit = $oCacher && $oCacher->IsInited() ? $oCacher->Get($this->getCaptchaCacherKey()) : '0';
73+
74+
if (0 < \strlen($sLimit) && \is_numeric($sLimit))
75+
{
76+
$iConfigLimit -= (int) $sLimit;
77+
}
78+
}
79+
80+
return $iConfigLimit;
81+
}
82+
83+
/**
84+
* @return void
85+
*/
86+
public function FilterAppDataPluginSection(bool $bAdmin, bool $bAuth, array &$aConfig) : void
87+
{
88+
if (!$bAdmin && !$bAuth && \is_array($aData))
89+
{
90+
$aData['show_captcha_on_login'] = 1 > $this->getLimit();
91+
}
92+
}
93+
94+
/**
95+
* @param string $sAction
96+
*/
97+
public function JsonActionPreCall($sAction)
98+
{
99+
if ('Login' === $sAction && 0 >= $this->getLimit())
100+
{
101+
$bResult = false;
102+
103+
$sResult = $this->Manager()->Actions()->Http()->SendPostRequest(
104+
'https://hcaptcha.com/siteverify',
105+
array(
106+
'secret' => $this->Config()->Get('plugin', 'secret_key', ''),
107+
'response' => $this->Manager()->Actions()->GetActionParam('h-captcha-response', '')
108+
)
109+
);
110+
111+
if ($sResult)
112+
{
113+
$aResp = @\json_decode($sResult, true);
114+
if (\is_array($aResp) && isset($aResp['success']) && $aResp['success'])
115+
{
116+
$bResult = true;
117+
}
118+
}
119+
120+
if (!$bResult)
121+
{
122+
$this->Manager()->Actions()->Logger()->Write('HcaptchaResponse:'.$sResult);
123+
throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CaptchaError);
124+
}
125+
}
126+
}
127+
128+
/**
129+
* @param string $sAction
130+
* @param array $aResponseItem
131+
*/
132+
public function FilterJsonResponse($sAction, &$aResponseItem)
133+
{
134+
if ('Login' === $sAction && $aResponseItem && isset($aResponseItem['Result']))
135+
{
136+
$oCacher = $this->Manager()->Actions()->Cacher();
137+
$iConfigLimit = (int) $this->Config()->Get('plugin', 'error_limit', 0);
138+
139+
$sKey = $this->getCaptchaCacherKey();
140+
141+
if (0 < $iConfigLimit && $oCacher && $oCacher->IsInited())
142+
{
143+
if (false === $aResponseItem['Result'])
144+
{
145+
$iLimit = 0;
146+
$sLimut = $oCacher->Get($sKey);
147+
if (0 < \strlen($sLimut) && \is_numeric($sLimut))
148+
{
149+
$iLimit = (int) $sLimut;
150+
}
151+
152+
$oCacher->Set($sKey, ++$iLimit);
153+
154+
if ($iConfigLimit <= $iLimit)
155+
{
156+
$aResponseItem['Captcha'] = true;
157+
}
158+
}
159+
else
160+
{
161+
$oCacher->Delete($sKey);
162+
}
163+
}
164+
}
165+
}
166+
}

plugins/hcaptcha/js/hcaptcha.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
(rl => {
2+
if (rl) {
3+
var
4+
nId = null,
5+
bStarted = false
6+
;
7+
8+
function ShowHcaptcha()
9+
{
10+
if (window.hcaptcha && null === nId)
11+
{
12+
var
13+
oEl = null,
14+
oLink = document.getElementById('plugin-Login-BottomControlGroup')
15+
;
16+
17+
if (oLink)
18+
{
19+
oEl = document.createElement('div');
20+
oEl.className = 'controls';
21+
22+
oLink.append(oEl);
23+
24+
nId = hcaptcha.render(oEl, {
25+
'sitekey': rl.pluginSettingsGet('hcaptcha', 'site_key'),
26+
'theme': rl.pluginSettingsGet('hcaptcha', 'theme')
27+
});
28+
}
29+
}
30+
}
31+
32+
window.__globalShowHcaptcha = ShowHcaptcha;
33+
34+
function StartHcaptcha()
35+
{
36+
if (window.hcaptcha)
37+
{
38+
ShowHcaptcha();
39+
}
40+
else
41+
{
42+
const script = document.createElement('script');
43+
script.src = 'https://hcaptcha.com/1/api.js?onload=__globalShowHcaptcha&render=explicit';
44+
document.head.append(script);
45+
}
46+
}
47+
48+
rl.addHook('user-login-submit', fSubmitResult => {
49+
if (null !== nId && !window.hcaptcha.getResponse(nId))
50+
{
51+
fSubmitResult(105);
52+
}
53+
});
54+
55+
rl.addHook('view-model-on-show', (sName, oViewModel) => {
56+
if (!bStarted && oViewModel && 'LoginUserView' === sName
57+
&& rl.pluginSettingsGet('hcaptcha', 'show_captcha_on_login'))
58+
{
59+
bStarted = true;
60+
StartHcaptcha();
61+
}
62+
});
63+
64+
rl.addHook('json-default-request', (sAction, oParameters) => {
65+
if ('Login' === sAction && oParameters && null !== nId && window.hcaptcha)
66+
{
67+
oParameters['h-captcha-response'] = hcaptcha.getResponse(nId);
68+
}
69+
});
70+
71+
rl.addHook('json-default-response', (sAction, oData, sType) => {
72+
if ('Login' === sAction)
73+
{
74+
if (!oData || 'success' !== sType || !oData['Result'])
75+
{
76+
if (null !== nId && window.hcaptcha)
77+
{
78+
hcaptcha.reset(nId);
79+
}
80+
else if (oData && oData['Captcha'])
81+
{
82+
StartHcaptcha();
83+
}
84+
}
85+
}
86+
});
87+
}
88+
})(window.rl);

0 commit comments

Comments
 (0)