recaptcha v3

This commit is contained in:
Dro1d.Ru 2018-12-14 23:19:17 +05:00
parent a49502f9de
commit 68e3b2470c
12 changed files with 195 additions and 7 deletions

View file

@ -17,9 +17,14 @@ var dash_options_load = function() {
$(this.content).find('select.captcha_select').change(this.bind(this, function(){
var captcha_type = $(this.content).find('select.captcha_select').val();
if (captcha_type == 'recaptcha') {
$(this.content).find('.recaptcha3_inputs').hide();
$(this.content).find('.recaptcha_inputs').show();
} else if (captcha_type == 'recaptcha3') {
$(this.content).find('.recaptcha_inputs').hide();
$(this.content).find('.recaptcha3_inputs').show();
} else {
$(this.content).find('.recaptcha_inputs').hide();
$(this.content).find('.recaptcha3_inputs').hide();
}
}));
$(this.content).find('select.captcha_select').trigger('change');

View file

@ -149,12 +149,30 @@
$('body').append('<img class="new-year-theme-img new-year-theme-img-6" src="'+img6.src+'" alt="" />');
$('.new-year-theme-img-6').css('top','-'+img6.height+'px').show().animate({top:0},1000,function(){
$(this).animate({top:'-5px'},500,function(){
$(this).animate({top:'0px'},500);
$(this).stop(1,1).animate({top:'0px'},500);
});
});
var img6AnimLock = false;
$('.new-year-theme-img-6').click(function(){
img6AnimLock = false;
$(this).animate({top:'-'+img6.height+'px'},1000);
});
$('.new-year-theme-img-6').mouseover(function(){
if (img6AnimLock) return;
img6AnimLock = true;
$(this).animate({top:'-10px'},500,function(){
if (!img6AnimLock) return;
$(this).animate({top:'0px'},500, function(){
if (!img6AnimLock) return;
$(this).animate({top:'-5px'},500, function(){
if (!img6AnimLock) return;
$(this).animate({top:'0px'},500, function(){
img6AnimLock = false;
});
});
});
});
});
};
img6.src = base + '/assets/images/holiday/newyear6.png';

View file

@ -311,11 +311,17 @@
zira_init_captcha();
}
/**
* reCaptcha
* reCaptcha v2
**/
if ($('.g-recaptcha').length>0) {
zira_load_recaptcha();
}
/**
* reCaptcha v3
**/
if ($('.g-recaptcha3').length>0) {
zira_load_recaptcha3();
}
/**
* User auth popup
*/
@ -1163,6 +1169,25 @@
grecaptcha.render($('#zira-auth-dialog .g-recaptcha').get(0));
} catch(e) {}
}
} else if ($('#zira-auth-dialog .g-recaptcha3').length>0) {
if (typeof zira_load_recaptcha3.loaded == "undefined") {
zira_load_recaptcha3();
} else {
try {
var el = $('#zira-auth-dialog .g-recaptcha3');
var id = 'grecaptcha3-auth-popup';
$(el).attr('id', id);
$(el).html('<input type="hidden" name="g-recaptcha-response" id="'+id+'-hidden" />');
$(el).parent().find('.g-recaptcha3-message').attr('id',id+'-message');
var site_key = $(el).data('sitekey');
var action = $(el).data('action');
grecaptcha.execute(site_key, {action: action}).then(function(token){
$('input#'+id+'-hidden').val(token);
var msgi = $('#'+id+'-message');
$(msgi).text($(msgi).data('success'));
});
} catch(e) {}
}
} else if ($('.captcha-refresh-btn').length>0) {
zira_init_captcha();
}
@ -1495,7 +1520,7 @@
zira_init_xhr_form = function() {
$('form.xhr-form').each(function() {
if ($(this).find('.g-recaptcha').length>0 || $(this).find('.captcha-refresh-btn').length>0) return true;
if ($(this).find('.g-recaptcha').length>0 || $(this).find('.g-recaptcha3').length>0 || $(this).find('.captcha-refresh-btn').length>0) return true;
$(this).bind('xhr-submit-error', function(e, response){
if (!response) return;
if (typeof response.captcha_error != "undefined" && response.captcha_error) {
@ -1558,6 +1583,43 @@
});
};
zira_load_recaptcha3 = function() {
if (typeof zira_recaptcha3_url == "undefined") return;
if (typeof zira_load_recaptcha3.loaded != 'undefined') return;
zira_load_recaptcha3.loaded = true;
$('body').append('<script src="'+zira_recaptcha3_url+'"></script>');
};
zira_recaptcha3_onload = function() {
var grecaptcha_co = 0;
$('.g-recaptcha3').each(function(){
grecaptcha_co++;
var id = 'grecaptcha3-'+grecaptcha_co;
$(this).attr('id', id);
$(this).html('<input type="hidden" name="g-recaptcha-response" id="'+id+'-hidden" />');
$(this).parent().find('.g-recaptcha3-message').attr('id',id+'-message');
var site_key = $(this).data('sitekey');
var action = $(this).data('action');
grecaptcha.execute(site_key, {action: action}).then(function(token){
$('input#'+id+'-hidden').val(token);
var msgi = $('#'+id+'-message');
$(msgi).text($(msgi).data('success'));
});
$(this).parents('form.xhr-form').submit(function(){
var grecaptcha_el = $(this).find('.g-recaptcha3');
window.setTimeout(function(){
try {
var site_key = $(grecaptcha_el).data('sitekey');
var action = $(grecaptcha_el).data('action');
grecaptcha.execute(site_key, {action: action}).then(function(token){
$('input#'+$(grecaptcha_el).attr('id')+'-hidden').val(token);
});
} catch(err) {}
}, 3000);
});
});
};
zira_set_dashpanel_dropdown_height = function() {
var dropdown = $('#dashpanel-container').find('.open > .dropdown-menu');
if ($(dropdown).length>0) {

View file

@ -71,6 +71,12 @@ class Options extends Form
$html .= $this->input(Locale::t('Site key for reCaptcha'), 'recaptcha_site_key');
$html .= $this->input(Locale::t('Secret key for reCaptcha'), 'recaptcha_secret_key');
$html .= Zira\Helper::tag_close('div');
$html .= Zira\Helper::tag_open('div', array('class' => 'recaptcha3_inputs'));
$html .= $this->input(Locale::t('Site key for reCaptcha'), 'recaptcha3_site_key');
$html .= $this->input(Locale::t('Secret key for reCaptcha'), 'recaptcha3_secret_key');
$html .= Zira\Helper::tag_close('div');
$html .= $this->checkbox(Locale::t('Sticky top bar'), 'dash_panel_frontend', null, false);
$html .= $this->select(Locale::t('Window buttons position'), 'dashwindow_mode', array(
'0' => Locale::t('Left'),

View file

@ -39,7 +39,9 @@ class Options extends Model {
'check_updates' => 'int',
'captcha_type' => 'string',
'recaptcha_site_key' => 'string',
'recaptcha_secret_key' => 'string'
'recaptcha_secret_key' => 'string',
'recaptcha3_site_key' => 'string',
'recaptcha3_secret_key' => 'string'
);
if (count(Zira\Config::get('languages'))>1) {

View file

@ -154,5 +154,7 @@ return array(
'Anti-Bot' => 'Анти-Бот',
'Read more' => 'Подробнее',
'DD.MM.YYYY' => 'ДД.ММ.ГГГГ',
'Permission denied' => 'Нет прав доступа'
'Permission denied' => 'Нет прав доступа',
'Anti-Bot is not active.' => 'Анти-Бот не активен.',
'Anti-Bot is active.' => 'Анти-Бот активен.'
);

View file

@ -2470,6 +2470,9 @@ ul.vote-results li .vote-result {
filter: invert(65%) contrast(300%);
opacity: .6;
}
.grecaptcha-badge {
filter: invert(65%) contrast(300%);
}
/** fields **/
.record_fields_tabs_wrapper .nav-tabs {

View file

@ -80,6 +80,7 @@ class Factory {
$this->_token = Form::getToken($this->_id, $this->_is_token_unique);
$this->_validator = new Validator();
$this->_validator->setToken($this->_token);
$this->_validator->setFormId($this->_id);
if ($this->_multipart) $this->_validator->setMultipart(true);
}
@ -712,6 +713,7 @@ class Factory {
$captcha_type = Zira\Config::get('captcha_type', Zira\Models\Captcha::TYPE_DEFAULT);
if ($captcha_type == Zira\Models\Captcha::TYPE_NONE) return '';
else if ($captcha_type == Zira\Models\Captcha::TYPE_RECAPTCHA) return $this->_captcha_recaptcha();
else if ($captcha_type == Zira\Models\Captcha::TYPE_RECAPTCHA_v3) return $this->_captcha_recaptcha3();
else return $this->_captcha_default($label, $description);
}
@ -747,6 +749,15 @@ class Factory {
);
return $this->wrap($label.$this->wrap($captcha,$this->_input_wrap_class));
}
protected function _captcha_recaptcha3() {
$label = Form::label(' ', null, array('class'=>$this->_label_class));
$captcha = Form::recaptcha3(
Zira\Config::get('recaptcha3_site_key',''),
str_replace('-', '_', $this->_id)
);
return $this->wrap($label.$this->wrap($captcha,$this->_input_wrap_class));
}
public function validate() {
if (!$this->getValidator()->validate()) {

View file

@ -239,6 +239,18 @@ class Form {
$html .= Helper::tag_close('div');
return $html;
}
public static function recaptcha3($site_key, $action, $wrapper_class='recaptcha3') {
$html = Helper::tag_open('div',array('class'=>$wrapper_class));
$html .= Helper::tag('div', null, array(
'class' => 'g-recaptcha3',
'data-sitekey' => $site_key,
'data-action' => $action
));
$html .= Helper::tag('div', Zira\Locale::t('Anti-Bot is not active.').' '.Zira\Locale::t('Please wait').'...', array('data-error'=>Zira\Locale::t('Anti-Bot is not active.'),'data-success'=>Zira\Locale::t('Anti-Bot is active.'),'class'=>'g-recaptcha3-message'));
$html .= Helper::tag_close('div');
return $html;
}
public static function generateCaptcha() {
$token = Request::get('token');
@ -341,6 +353,32 @@ class Form {
}
return $result_data['success'];
}
public static function isRecaptcha3Valid($secret_key, $response_value, $action) {
if (!$secret_key || !$response_value || !$action) return false;
$data = http_build_query(array(
'secret' => $secret_key,
'response' => $response_value
));
$options = array(
'http' => array(
'method' => 'POST',
'header' => 'Content-type: application/x-www-form-urlencoded',
'content' => $data
)
);
$context = stream_context_create($options);
try {
$result = file_get_contents(Zira\Models\Captcha::RECAPTCHA_VALIDATE_URL, false, $context);
if (!$result) throw new \Exception('An error occurred');
$result_data = json_decode($result, true);
if (empty($result_data) || !array_key_exists('success', $result_data)) throw new \Exception('An error occurred');
if (!array_key_exists('score', $result_data) || !array_key_exists('action', $result_data)) throw new \Exception('An error occurred');
} catch (\Exception $e) {
return false;
}
return $result_data['success'] && $result_data['action']==$action && floatval($result_data['score'])>=Zira\Models\Captcha::RECAPTCHA_v3_MIN_SCORE;
}
public static function getValue($token,$name,$method=Request::POST) {
$_name = self::getFieldName($token, $name);

View file

@ -17,6 +17,7 @@ class Validator {
protected $_fields = array();
protected $_message = '';
protected $_error_field = '';
protected $_form_id = '';
const TYPE_STRING = 'string';
const TYPE_NUMBER = 'number';
@ -42,6 +43,14 @@ class Validator {
public function getToken() {
return $this->_token;
}
public function setFormId($id) {
$this->_form_id = $id;
}
public function getFormId() {
return $this->_form_id;
}
public function setMethod($method) {
$this->_method = $method;
@ -388,6 +397,7 @@ class Validator {
$captcha_type = Zira\Config::get('captcha_type', Zira\Models\Captcha::TYPE_DEFAULT);
if ($captcha_type == Zira\Models\Captcha::TYPE_NONE) return true;
else if ($captcha_type == Zira\Models\Captcha::TYPE_RECAPTCHA) return $this->_validateCaptchaRecaptcha($field);
else if ($captcha_type == Zira\Models\Captcha::TYPE_RECAPTCHA_v3) return $this->_validateCaptchaRecaptcha3($field);
else return $this->_validateCaptchaDefault($field);
}
@ -398,11 +408,16 @@ class Validator {
protected function _validateCaptchaRecaptcha(array $field) {
return Form::isRecaptchaValid(Zira\Config::get('recaptcha_secret_key', ''), Zira\Request::post(Zira\Models\Captcha::RECAPTCHA_RESPONSE_INPUT));
}
protected function _validateCaptchaRecaptcha3(array $field) {
return Form::isRecaptcha3Valid(Zira\Config::get('recaptcha3_secret_key', ''), Zira\Request::post(Zira\Models\Captcha::RECAPTCHA_RESPONSE_INPUT), str_replace('-', '_', $this->getFormId()));
}
public function registerCaptchaLazy($form_id, $message) {
$captcha_type = Zira\Config::get('captcha_type', Zira\Models\Captcha::TYPE_DEFAULT);
if ($captcha_type == Zira\Models\Captcha::TYPE_NONE) return;
else if ($captcha_type == Zira\Models\Captcha::TYPE_RECAPTCHA) return $this->_registerCaptchaLazyRecaptcha($form_id, $message);
else if ($captcha_type == Zira\Models\Captcha::TYPE_RECAPTCHA_v3) return $this->_registerCaptchaLazyRecaptcha3($form_id, $message);
else return $this->_registerCaptchaLazyDefault($form_id, $message);
}
@ -424,11 +439,21 @@ class Validator {
'message' => $message
);
}
protected function _registerCaptchaLazyRecaptcha3($form_id, $message) {
$this->_fields []= array(
'type' => self::TYPE_CAPTCHA_LAZY,
'name' => CAPTCHA_NAME,
'form_id' => $form_id,
'message' => $message
);
}
protected function validateCaptchaLazy(array $field) {
$captcha_type = Zira\Config::get('captcha_type', Zira\Models\Captcha::TYPE_DEFAULT);
if ($captcha_type == Zira\Models\Captcha::TYPE_NONE) return true;
else if ($captcha_type == Zira\Models\Captcha::TYPE_RECAPTCHA) return $this->_validateCaptchaLazyRecaptcha($field);
else if ($captcha_type == Zira\Models\Captcha::TYPE_RECAPTCHA_v3) return $this->_validateCaptchaLazyRecaptcha3($field);
else return $this->_validateCaptchaLazyDefault($field);
}
@ -451,6 +476,16 @@ class Validator {
return Form::isRecaptchaValid(Zira\Config::get('recaptcha_secret_key', ''), Zira\Request::post(Zira\Models\Captcha::RECAPTCHA_RESPONSE_INPUT));
}
}
protected function _validateCaptchaLazyRecaptcha3(array $field) {
if (Zira\Request::post(Zira\Models\Captcha::RECAPTCHA_RESPONSE_INPUT)===null && !Zira\Models\Captcha::isActive($field['form_id'])) {
Zira\Models\Captcha::register($field['form_id']);
return true;
} else {
Zira\Models\Captcha::register($field['form_id']);
return Form::isRecaptcha3Valid(Zira\Config::get('recaptcha3_secret_key', ''), Zira\Request::post(Zira\Models\Captcha::RECAPTCHA_RESPONSE_INPUT), str_replace('-', '_', $this->getFormId()));
}
}
public function registerExists($field,$class,$property,$message) {
$this->_fields []= array(

View file

@ -17,10 +17,12 @@ class Captcha extends Orm {
const TYPE_NONE = 'none';
const TYPE_DEFAULT = 'default';
const TYPE_RECAPTCHA = 'recaptcha';
const TYPE_RECAPTCHA_v3 = 'recaptcha3';
const RECAPTCHA_JS_URL = 'https://www.google.com/recaptcha/api.js';
const RECAPTCHA_VALIDATE_URL = 'https://www.google.com/recaptcha/api/siteverify';
const RECAPTCHA_RESPONSE_INPUT = 'g-recaptcha-response';
const RECAPTCHA_v3_MIN_SCORE = .5;
public static function getTable() {
return self::$table;
@ -67,7 +69,8 @@ class Captcha extends Orm {
return array(
self::TYPE_NONE => \Zira\Locale::t('Do not use'),
self::TYPE_DEFAULT => \Zira\Locale::t('Default'),
self::TYPE_RECAPTCHA => \Zira\Locale::t('Google reCaptcha')
self::TYPE_RECAPTCHA => \Zira\Locale::t('Google reCaptcha v2'),
self::TYPE_RECAPTCHA_v3 => \Zira\Locale::t('Google reCaptcha v3')
);
}
}

View file

@ -363,8 +363,11 @@ class View {
if (self::$_render_js_strings) {
$js_scripts .= Helper::tag_open('script', array('type'=>'text/javascript'));
$js_scripts .= 'zira_base = \''.Helper::baseUrl('').'\';';
if (Config::get('captcha_type', Models\Captcha::TYPE_DEFAULT)==Models\Captcha::TYPE_RECAPTCHA) {
$captcha_type = Config::get('captcha_type', Models\Captcha::TYPE_DEFAULT);
if ($captcha_type==Models\Captcha::TYPE_RECAPTCHA) {
$js_scripts .= 'zira_recaptcha_url = \''.Models\Captcha::RECAPTCHA_JS_URL.'?hl='.(Locale::getLanguage()).'&render=explicit&onload=zira_recaptcha_onload\';';
} else if ($captcha_type==Models\Captcha::TYPE_RECAPTCHA_v3) {
$js_scripts .= 'zira_recaptcha3_url = \''.Models\Captcha::RECAPTCHA_JS_URL.'?hl='.(Locale::getLanguage()).'&render='.Config::get('recaptcha3_site_key','').'&onload=zira_recaptcha3_onload\';';
}
$js_scripts .= 'zira_scroll_effects_enabled = '.(Config::get('site_scroll_effects',1) ? 'true' : 'false').';';
$js_scripts .= 'zira_show_images_description = '.(Config::get('site_parse_images',1) ? 'true' : 'false').';';