perimeter_x 1.3.0 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/changelog.md +11 -1
- data/lib/perimeter_x.rb +35 -28
- data/lib/perimeterx/configuration.rb +1 -1
- data/lib/perimeterx/internal/perimeter_x_context.rb +3 -0
- data/lib/perimeterx/internal/validators/perimeter_x_captcha_validator.rb +15 -15
- data/lib/perimeterx/internal/validators/perimeter_x_cookie_validator.rb +27 -10
- data/lib/perimeterx/utils/px_constants.rb +3 -2
- data/lib/perimeterx/utils/px_template_factory.rb +1 -1
- data/lib/perimeterx/utils/templates/block.mobile.mustache +2 -2
- data/lib/perimeterx/utils/templates/block.mustache +1 -1
- data/lib/perimeterx/utils/templates/funcaptcha.mobile.mustache +178 -0
- data/lib/perimeterx/utils/templates/funcaptcha.mustache +191 -0
- data/lib/perimeterx/utils/templates/{captcha.mobile.mustache → recaptcha.mobile.mustache} +2 -2
- data/lib/perimeterx/utils/templates/recaptcha.mustache +176 -0
- data/lib/perimeterx/version.rb +1 -1
- data/readme.md +47 -26
- metadata +6 -4
- data/lib/perimeterx/utils/templates/captcha.mustache +0 -185
@@ -0,0 +1,191 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="utf-8">
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
6
|
+
<title>Access to this page has been denied.</title>
|
7
|
+
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300" rel="stylesheet">
|
8
|
+
<style>
|
9
|
+
html, body {
|
10
|
+
margin: 0;
|
11
|
+
padding: 0;
|
12
|
+
font-family: 'Open Sans', sans-serif;
|
13
|
+
color: #000;
|
14
|
+
}
|
15
|
+
|
16
|
+
a {
|
17
|
+
color: #c5c5c5;
|
18
|
+
text-decoration: none;
|
19
|
+
}
|
20
|
+
|
21
|
+
.container {
|
22
|
+
align-items: center;
|
23
|
+
display: flex;
|
24
|
+
flex: 1;
|
25
|
+
justify-content: space-between;
|
26
|
+
flex-direction: column;
|
27
|
+
height: 100%;
|
28
|
+
}
|
29
|
+
|
30
|
+
.container > div {
|
31
|
+
width: 100%;
|
32
|
+
display: flex;
|
33
|
+
justify-content: center;
|
34
|
+
}
|
35
|
+
|
36
|
+
.container > div > div {
|
37
|
+
display: flex;
|
38
|
+
width: 80%;
|
39
|
+
}
|
40
|
+
|
41
|
+
.customer-logo-wrapper {
|
42
|
+
padding-top: 2rem;
|
43
|
+
flex-grow: 0;
|
44
|
+
background-color: #fff;
|
45
|
+
visibility: {{logoVisibility}};
|
46
|
+
}
|
47
|
+
|
48
|
+
.customer-logo {
|
49
|
+
border-bottom: 1px solid #000;
|
50
|
+
}
|
51
|
+
|
52
|
+
.customer-logo > img {
|
53
|
+
padding-bottom: 1rem;
|
54
|
+
max-height: 50px;
|
55
|
+
max-width: 100%;
|
56
|
+
}
|
57
|
+
|
58
|
+
.page-title-wrapper {
|
59
|
+
flex-grow: 2;
|
60
|
+
}
|
61
|
+
|
62
|
+
.page-title {
|
63
|
+
flex-direction: column-reverse;
|
64
|
+
}
|
65
|
+
|
66
|
+
.content-wrapper {
|
67
|
+
flex-grow: 5;
|
68
|
+
}
|
69
|
+
|
70
|
+
.content {
|
71
|
+
flex-direction: column;
|
72
|
+
}
|
73
|
+
|
74
|
+
.page-footer-wrapper {
|
75
|
+
align-items: center;
|
76
|
+
flex-grow: 0.2;
|
77
|
+
background-color: #000;
|
78
|
+
color: #c5c5c5;
|
79
|
+
font-size: 70%;
|
80
|
+
}
|
81
|
+
|
82
|
+
@media (min-width: 768px) {
|
83
|
+
html, body {
|
84
|
+
height: 100%;
|
85
|
+
}
|
86
|
+
}
|
87
|
+
</style>
|
88
|
+
<!-- Custom CSS -->
|
89
|
+
{{# cssRef }}
|
90
|
+
<link rel="stylesheet" type="text/css" href="{{ . }}"/>
|
91
|
+
{{/ cssRef }}
|
92
|
+
<script src="https://funcaptcha.com/fc/api/?onload=loadFunCaptcha" async defer></script>
|
93
|
+
</head>
|
94
|
+
|
95
|
+
<body>
|
96
|
+
<section class="container">
|
97
|
+
<div class="customer-logo-wrapper">
|
98
|
+
<div class="customer-logo">
|
99
|
+
<img src="{{customLogo}}" alt="Logo"/>
|
100
|
+
</div>
|
101
|
+
</div>
|
102
|
+
<div class="page-title-wrapper">
|
103
|
+
<div class="page-title">
|
104
|
+
<h1>Please verify you are a human</h1>
|
105
|
+
</div>
|
106
|
+
</div>
|
107
|
+
<div class="content-wrapper">
|
108
|
+
<div class="content">
|
109
|
+
<p>
|
110
|
+
Please click "Verify" to continue
|
111
|
+
</p>
|
112
|
+
<div id="CAPTCHA"></div>
|
113
|
+
<p>
|
114
|
+
Access to this page has been denied because we believe you are using automation tools to browse the
|
115
|
+
website.
|
116
|
+
</p>
|
117
|
+
<p>
|
118
|
+
This may happen as a result of the following:
|
119
|
+
</p>
|
120
|
+
<ul>
|
121
|
+
<li>
|
122
|
+
Javascript is disabled or blocked by an extension (ad blockers for example)
|
123
|
+
</li>
|
124
|
+
<li>
|
125
|
+
Your browser does not support cookies
|
126
|
+
</li>
|
127
|
+
</ul>
|
128
|
+
<p>
|
129
|
+
Please make sure that Javascript and cookies are enabled on your browser and that you are not blocking
|
130
|
+
them from loading.
|
131
|
+
</p>
|
132
|
+
<p>
|
133
|
+
Reference ID: #{{refId}}
|
134
|
+
</p>
|
135
|
+
</div>
|
136
|
+
</div>
|
137
|
+
<div class="page-footer-wrapper">
|
138
|
+
<div class="page-footer">
|
139
|
+
<p>
|
140
|
+
Powered by
|
141
|
+
<a href="https://www.perimeterx.com/whywasiblocked">PerimeterX</a>
|
142
|
+
, Inc.
|
143
|
+
</p>
|
144
|
+
</div>
|
145
|
+
</div>
|
146
|
+
</section>
|
147
|
+
<!-- Px -->
|
148
|
+
<script>
|
149
|
+
(
|
150
|
+
function () {
|
151
|
+
window._pxAppId = '{{appId}}';
|
152
|
+
var p = document.getElementsByTagName("script")[0], s = document.createElement("script");
|
153
|
+
|
154
|
+
s.async = 1;
|
155
|
+
s.src = '//client.perimeterx.net/{{appId}}/main.min.js';
|
156
|
+
p.parentNode.insertBefore(s, p);
|
157
|
+
}()
|
158
|
+
);
|
159
|
+
</script>
|
160
|
+
<!-- Captcha -->
|
161
|
+
<script>
|
162
|
+
|
163
|
+
function loadFunCaptcha() {
|
164
|
+
var vid = '{{vid}}';
|
165
|
+
var uuid = '{{uuid}}';
|
166
|
+
|
167
|
+
new FunCaptcha({
|
168
|
+
public_key: "19E4B3B8-6CBE-35CC-4205-FC79ECDDA765",
|
169
|
+
target_html: "CAPTCHA",
|
170
|
+
callback: function () {
|
171
|
+
var expiryUtc = new Date(Date.now() + 1000 * 10).toUTCString();
|
172
|
+
var pxCaptcha = "_pxCaptcha=" + btoa(JSON.stringify({r: document.getElementById("FunCaptcha-Token").value, u: uuid, v: vid}));
|
173
|
+
var cookieParts = [
|
174
|
+
pxCaptcha,
|
175
|
+
"; expires=",
|
176
|
+
expiryUtc,
|
177
|
+
"; path=/"
|
178
|
+
];
|
179
|
+
|
180
|
+
document.cookie = cookieParts.join("");
|
181
|
+
location.reload();
|
182
|
+
}
|
183
|
+
});
|
184
|
+
}
|
185
|
+
</script>
|
186
|
+
<!-- Custom Script -->
|
187
|
+
{{# jsRef }}
|
188
|
+
<script src="{{ . }}"></script>
|
189
|
+
{{/ jsRef }}
|
190
|
+
</body>
|
191
|
+
</html>
|
@@ -135,7 +135,7 @@
|
|
135
135
|
<div class="page-footer">
|
136
136
|
<p>
|
137
137
|
Powered by
|
138
|
-
<a href="https://www.perimeterx.com">PerimeterX</a>
|
138
|
+
<a href="https://www.perimeterx.com/whywasiblocked">PerimeterX</a>
|
139
139
|
, Inc.
|
140
140
|
</p>
|
141
141
|
</div>
|
@@ -193,4 +193,4 @@
|
|
193
193
|
<script src="{{jsRef}}"></script>
|
194
194
|
{{/jsRef}}
|
195
195
|
</body>
|
196
|
-
</html>
|
196
|
+
</html>
|
@@ -0,0 +1,176 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="utf-8">
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
6
|
+
<title>Access to this page has been denied.</title>
|
7
|
+
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300" rel="stylesheet">
|
8
|
+
<style>
|
9
|
+
html, body {
|
10
|
+
margin: 0;
|
11
|
+
padding: 0;
|
12
|
+
font-family: 'Open Sans', sans-serif;
|
13
|
+
color: #000;
|
14
|
+
}
|
15
|
+
a {
|
16
|
+
color: #c5c5c5;
|
17
|
+
text-decoration: none;
|
18
|
+
}
|
19
|
+
.container {
|
20
|
+
align-items: center;
|
21
|
+
display: flex;
|
22
|
+
flex: 1;
|
23
|
+
justify-content: space-between;
|
24
|
+
flex-direction: column;
|
25
|
+
height: 100%;
|
26
|
+
}
|
27
|
+
.container > div {
|
28
|
+
width: 100%;
|
29
|
+
display: flex;
|
30
|
+
justify-content: center;
|
31
|
+
}
|
32
|
+
.container > div > div {
|
33
|
+
display: flex;
|
34
|
+
width: 80%;
|
35
|
+
}
|
36
|
+
.customer-logo-wrapper {
|
37
|
+
padding-top: 2rem;
|
38
|
+
flex-grow: 0;
|
39
|
+
background-color: #fff;
|
40
|
+
visibility: {{logoVisibility}};
|
41
|
+
}
|
42
|
+
.customer-logo {
|
43
|
+
border-bottom: 1px solid #000;
|
44
|
+
}
|
45
|
+
.customer-logo > img {
|
46
|
+
padding-bottom: 1rem;
|
47
|
+
max-height: 50px;
|
48
|
+
max-width: 100%;
|
49
|
+
}
|
50
|
+
.page-title-wrapper {
|
51
|
+
flex-grow: 2;
|
52
|
+
}
|
53
|
+
.page-title {
|
54
|
+
flex-direction: column-reverse;
|
55
|
+
}
|
56
|
+
.content-wrapper {
|
57
|
+
flex-grow: 5;
|
58
|
+
}
|
59
|
+
.content {
|
60
|
+
flex-direction: column;
|
61
|
+
}
|
62
|
+
.page-footer-wrapper {
|
63
|
+
align-items: center;
|
64
|
+
flex-grow: 0.2;
|
65
|
+
background-color: #000;
|
66
|
+
color: #c5c5c5;
|
67
|
+
font-size: 70%;
|
68
|
+
}
|
69
|
+
@media (min-width: 768px) {
|
70
|
+
html, body {
|
71
|
+
height: 100%;
|
72
|
+
}
|
73
|
+
}
|
74
|
+
</style>
|
75
|
+
<!-- Custom CSS -->
|
76
|
+
{{#cssRef}}
|
77
|
+
<link rel="stylesheet" type="text/css" href="{{cssRef}}" />
|
78
|
+
{{/cssRef}}
|
79
|
+
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
|
80
|
+
</head>
|
81
|
+
|
82
|
+
<body>
|
83
|
+
<section class="container">
|
84
|
+
<div class="customer-logo-wrapper">
|
85
|
+
<div class="customer-logo">
|
86
|
+
<img src="{{customLogo}}" alt="Logo"/>
|
87
|
+
</div>
|
88
|
+
</div>
|
89
|
+
<div class="page-title-wrapper">
|
90
|
+
<div class="page-title">
|
91
|
+
<h1>Please verify you are a human</h1>
|
92
|
+
</div>
|
93
|
+
</div>
|
94
|
+
<div class="content-wrapper">
|
95
|
+
<div class="content">
|
96
|
+
<p>
|
97
|
+
Please click "I am not a robot" to continue
|
98
|
+
</p>
|
99
|
+
<div class="g-recaptcha" data-sitekey="6Lcj-R8TAAAAABs3FrRPuQhLMbp5QrHsHufzLf7b"
|
100
|
+
data-callback="handleCaptcha" data-theme="dark">
|
101
|
+
</div>
|
102
|
+
<p>
|
103
|
+
Access to this page has been denied because we believe you are using automation tools to browse the
|
104
|
+
website.
|
105
|
+
</p>
|
106
|
+
<p>
|
107
|
+
This may happen as a result of the following:
|
108
|
+
</p>
|
109
|
+
<ul>
|
110
|
+
<li>
|
111
|
+
Javascript is disabled or blocked by an extension (ad blockers for example)
|
112
|
+
</li>
|
113
|
+
<li>
|
114
|
+
Your browser does not support cookies
|
115
|
+
</li>
|
116
|
+
</ul>
|
117
|
+
<p>
|
118
|
+
Please make sure that Javascript and cookies are enabled on your browser and that you are not blocking
|
119
|
+
them from loading.
|
120
|
+
</p>
|
121
|
+
<p>
|
122
|
+
Reference ID: #{{refId}}
|
123
|
+
</p>
|
124
|
+
</div>
|
125
|
+
</div>
|
126
|
+
<div class="page-footer-wrapper">
|
127
|
+
<div class="page-footer">
|
128
|
+
<p>
|
129
|
+
Powered by
|
130
|
+
<a href="https://www.perimeterx.com/whywasiblocked">PerimeterX</a>
|
131
|
+
, Inc.
|
132
|
+
</p>
|
133
|
+
</div>
|
134
|
+
</div>
|
135
|
+
</section>
|
136
|
+
<!-- Px -->
|
137
|
+
<script>
|
138
|
+
(
|
139
|
+
function () {
|
140
|
+
window._pxAppId = '{{appId}}';
|
141
|
+
var p = document.getElementsByTagName("script")[0], s = document.createElement("script");
|
142
|
+
s.async = 1;
|
143
|
+
s.src = '//client.perimeterx.net/{{appId}}/main.min.js';
|
144
|
+
p.parentNode.insertBefore(s, p);
|
145
|
+
}()
|
146
|
+
);
|
147
|
+
</script>
|
148
|
+
<!-- Captcha -->
|
149
|
+
<script>
|
150
|
+
window.px_vid = '{{vid}}';
|
151
|
+
function handleCaptcha(response){
|
152
|
+
var vid = '{{vid}}';
|
153
|
+
var uuid = '{{uuid}}';
|
154
|
+
var name = "_pxCaptcha";
|
155
|
+
|
156
|
+
var expiryUtc = new Date(Date.now() + 1000 * 10).toUTCString();
|
157
|
+
|
158
|
+
var cookieParts = [
|
159
|
+
name,
|
160
|
+
"=",
|
161
|
+
btoa(JSON.stringify({r: response, v: vid, u: uuid})),
|
162
|
+
"; expires=",
|
163
|
+
expiryUtc,
|
164
|
+
"; path=/"
|
165
|
+
];
|
166
|
+
|
167
|
+
document.cookie = cookieParts.join("");
|
168
|
+
location.reload();
|
169
|
+
}
|
170
|
+
</script>
|
171
|
+
<!-- Custom Script -->
|
172
|
+
{{#jsRef}}
|
173
|
+
<script src="{{jsRef}}"></script>
|
174
|
+
{{/jsRef}}
|
175
|
+
</body>
|
176
|
+
</html>
|
data/lib/perimeterx/version.rb
CHANGED
data/readme.md
CHANGED
@@ -1,20 +1,26 @@
|
|
1
|
+
[![Build Status](https://travis-ci.org/PerimeterX/perimeterx-ruby-sdk.svg?branch=master)](https://travis-ci.org/PerimeterX/perimeterx-ruby-sdk)
|
2
|
+
|
1
3
|
![image](http://media.marketwire.com/attachments/201604/34215_PerimeterX_logo.jpg)
|
2
4
|
#
|
3
5
|
[PerimeterX](http://www.perimeterx.com) Ruby SDK
|
4
6
|
=============================================================
|
5
7
|
|
8
|
+
> Latest stable version: [v1.3.0](https://rubygems.org/gems/perimeter_x/versions/1.3.0)
|
9
|
+
|
6
10
|
Table of Contents
|
7
11
|
-----------------
|
8
|
-
|
12
|
+
**[Usage](#usage)**
|
9
13
|
* [Dependencies](#dependencies)
|
10
14
|
* [Installation](#installation)
|
11
15
|
* [Basic Usage Example](#basic-usage)
|
12
|
-
|
16
|
+
|
17
|
+
**[Configuration](#configuration)**
|
13
18
|
* [Configuring Required Parameters](#requireied-params)
|
14
19
|
* [Blocking Score](#blocking-score)
|
20
|
+
* [Custom Verification Action](#custom-verification-action)
|
15
21
|
* [Custom Block Page](#custom-block-page)
|
16
|
-
* [Custom Block Action](#custom-block-action)
|
17
22
|
* [Enable/Disable Captcha](#captcha-support)
|
23
|
+
* [Select Captcha Provider](#captcha-provider)
|
18
24
|
* [Extracting Real IP Address](#real-ip)
|
19
25
|
* [Custom URI](#custom-uri)
|
20
26
|
* [Filter Sensitive Headers](#sensitive-headers)
|
@@ -23,7 +29,8 @@ Table of Contents
|
|
23
29
|
* [Additional Page Activity Handler](#additional-page-activity-handler)
|
24
30
|
* [Monitor Only](#logging)
|
25
31
|
* [Debug Mode](#debug-mode)
|
26
|
-
|
32
|
+
|
33
|
+
**[Contributing](#contributing)**
|
27
34
|
|
28
35
|
<a name="Usage"></a>
|
29
36
|
<a name="dependencies"></a> Dependencies
|
@@ -90,42 +97,46 @@ params = {
|
|
90
97
|
}
|
91
98
|
```
|
92
99
|
|
100
|
+
<a name="custom-verification-action"></a>**Custom Verification Handler**
|
93
101
|
|
102
|
+
> Note: This handler replaces the now deprecated `custom_block_handler`.
|
94
103
|
|
95
|
-
|
96
|
-
|
97
|
-
A custom verification handler is being executed inside ``px_verify_request`` instead of the the default behavior and allows a user to use a custom action based on the risk score returned by PerimeterX.
|
104
|
+
A custom verification handler is being executed inside `px_verify_request` instead of the the default behavior and allows a user to use a custom action based on the risk score returned by PerimeterX.
|
98
105
|
|
99
|
-
When implemented, this method receives
|
106
|
+
When implemented, this method receives a hash variable as input which represents data from the PerimeterX context of the request (px_ctx).
|
100
107
|
|
101
|
-
- `px_ctx[:score]
|
102
|
-
- `px_ctx[:uuid]
|
108
|
+
- `px_ctx.context[:score]` - contains the risk score
|
109
|
+
- `px_ctx.context[:uuid]` - contains the request UUID
|
110
|
+
- `px_ctx.context[:verified]` - contains indication whether the request passed verification or was blocked (inspect `px_ctx.context[:block_reason]` for block reason)
|
103
111
|
|
104
|
-
|
112
|
+
> Note: to determine whether to return a captcha/block page (HTML) or block JSON payload a reference key on the context will be available: ```px_ctx.context[:format]```
|
105
113
|
|
106
114
|
To replace the default verification behavior, add the configuration a lambda member as shown in the example below.
|
107
115
|
|
108
|
-
The method must return boolen value.
|
109
|
-
|
110
116
|
```ruby
|
111
117
|
params = {
|
112
118
|
:app_id => <APP_ID>,
|
113
119
|
:auth_token => <AUTH_TOKEN>,
|
114
|
-
:
|
120
|
+
:custom_verification_handler => -> (px_ctx) {
|
115
121
|
if px_ctx.context[:score] >= 60
|
116
|
-
|
122
|
+
# take your action and render an html page or JSON with applicable status code.
|
123
|
+
render json: { :score => px_ctx.context[:score] }
|
117
124
|
end
|
118
|
-
return true
|
119
125
|
}
|
120
126
|
}
|
121
127
|
```
|
122
128
|
|
129
|
+
> Note: Unlike previous versions, the method no longer needs to return a boolean value.
|
130
|
+
|
123
131
|
**Example**
|
124
|
-
|
132
|
+
#### Serving a Custom HTML Page ####
|
125
133
|
```ruby
|
126
134
|
|
127
|
-
params
|
128
|
-
|
135
|
+
params = {
|
136
|
+
:app_id => <APP_ID>,
|
137
|
+
:auth_token => <AUTH_TOKEN>,
|
138
|
+
...
|
139
|
+
:custom_verification_handler => -> (px_ctx) {
|
129
140
|
block_score = px_ctx.context[:score];
|
130
141
|
block_uuid = px_ctx.context[:uuid];
|
131
142
|
full_url = px_ctx.context[:full_url];
|
@@ -140,19 +151,17 @@ params[:custom_block_handler] = -> (px_ctx)
|
|
140
151
|
response.headers["Content-Type"] = "text/html"
|
141
152
|
response.status = 403
|
142
153
|
render :html => html
|
143
|
-
|
144
|
-
}
|
145
|
-
|
146
|
-
PxModule.configure(params)
|
154
|
+
}
|
155
|
+
}
|
147
156
|
```
|
148
157
|
|
149
|
-
<a name="real-ip"></a>**
|
158
|
+
<a name="real-ip"></a>**Custom User IP**
|
150
159
|
|
151
160
|
> Note: IP extraction, according to your network setup, is very important. It is common to have a load balancer/proxy on top of your applications, in which case the PerimeterX module will send the system's internal IP as the user's. In order to properly perform processing and detection on server-to-server calls, PerimeterX module needs the real user's IP.
|
152
161
|
|
153
162
|
By default the clients IP is taken from the ``REMOTE_ADDR`` header, in case the user decides to use different header or custom function that extract the header the following key should be added to the configuration
|
154
163
|
|
155
|
-
***
|
164
|
+
***Custom header***
|
156
165
|
```ruby
|
157
166
|
configuration = {
|
158
167
|
"app_id" => <APP_ID>,
|
@@ -160,7 +169,7 @@ configuration = {
|
|
160
169
|
"custom_user_ip" => <HTTP_HEADER_NAME>,
|
161
170
|
```
|
162
171
|
|
163
|
-
***
|
172
|
+
***Custom Function***
|
164
173
|
> Note: the function receive as a first parameter the controller request and must return the ip at the end as string
|
165
174
|
|
166
175
|
```ruby
|
@@ -222,6 +231,18 @@ By enabling CAPTCHA support, a CAPTCHA will be served as part of the block page,
|
|
222
231
|
params[:captcha_enabled] = false
|
223
232
|
```
|
224
233
|
|
234
|
+
<a name="captcha-provider"></a>**Select CAPTCHA Provider**
|
235
|
+
|
236
|
+
The CAPTCHA part of the block page can use one of the following:
|
237
|
+
* [reCAPTCHA](https://www.google.com/recaptcha)
|
238
|
+
* [FunCaptcha](https://www.funcaptcha.com/)
|
239
|
+
|
240
|
+
Default: 'reCaptcha'
|
241
|
+
|
242
|
+
```ruby
|
243
|
+
captchaProvider = "funCaptcha"
|
244
|
+
```
|
245
|
+
|
225
246
|
<a name="custom-uri"></a>**Custom URI**
|
226
247
|
|
227
248
|
Default: 'REQUEST_URI'
|