perimeter_x 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1f3a1b696f592b2f1478cc376a96475a5d5f0778
4
- data.tar.gz: e859865af132477c953d6aed1a7c70bb927b432e
3
+ metadata.gz: 3dca9964af5aa39c20643f7e5d6f00271862f0de
4
+ data.tar.gz: e0f2a81bce3aa7c8b8eb0b0b73d70cc29f272dbc
5
5
  SHA512:
6
- metadata.gz: 1f67c50466d0a347d7d5e3dc6c30f66332a3dabb27a34152e49c36a6614fa06ce81bc02d8f50ba2e061f9fb4cef8b21e9fb684d2d2b8b138ffab87298d0e1f81
7
- data.tar.gz: 35d39968545b296e04f02bed0bb2c67b40ca1ff6773e352533fbe06e2795ad630a838d159fe517a12702e79d5054d81ef828fa966e9278dec7fd6eaa37dd2e06
6
+ metadata.gz: 257d51330a1dc51e1ca2559559d7eb4e0ca6ebc891fd8bfcdd0171b4e31a5a3b4455f4c3b4227b6db7d403eeecb3bb1bb4b62e4e7cf72024d6efa89f5636bd3f
7
+ data.tar.gz: 4cd74c725090a46718716b782551e1e5bae7efb939818c1b34970bde070897d6d53334b5c54bb6df7f4a24291dd5ed608e2e39723e008647a07c4e2da8de6b07
data/changelog.md CHANGED
@@ -5,7 +5,17 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](http://keepachangelog.com/)
6
6
  and this project adheres to [Semantic Versioning](http://semver.org/).
7
7
 
8
- ## [1.3.0] - 2017-06-04
8
+ ## [1.4.0] - 2018-03-18
9
+ ### Fixed
10
+ - Incorrect assigment for s2s_call_reason
11
+ - Fixed empty token result correct s2s reason
12
+
13
+ ### Added
14
+ - Added support to captcha api v2
15
+ - Mobile sdk support for special tokens 1/2/3
16
+
17
+
18
+ ## [1.3.0] - 2017-07-27
9
19
  ### Added
10
20
  - Sending client_uuid on page_requested activities
11
21
  - Supporting mobile sdk
data/lib/perimeter_x.rb CHANGED
@@ -15,37 +15,47 @@ require 'perimeterx/internal/validators/perimeter_x_captcha_validator'
15
15
  module PxModule
16
16
  # Module expose API
17
17
  def px_verify_request
18
- verified, px_ctx = PerimeterX.instance.verify(request.env)
18
+ px_ctx = PerimeterX.instance.verify(request.env)
19
+ px_config = PerimeterX.instance.px_config
20
+ msg_title = 'PxModule[px_verify_request]'
21
+
22
+ # In case custom verification handler is in use
23
+ if px_config.key?(:custom_verification_handler)
24
+ px_config[:logger].debug("#{msg_title}: custom_verification_handler triggered")
25
+ return instance_exec(px_ctx, &px_config[:custom_verification_handler])
26
+ end
19
27
 
20
28
  # Invalidate _pxCaptcha, can be done only on the controller level
21
29
  cookies[:_pxCaptcha] = {value: "", expires: -1.minutes.from_now}
22
30
 
23
- unless verified
24
- # In case custon block handler exists
25
- if (PerimeterX.instance.px_config.key?(:custom_block_handler))
26
- PerimeterX.instance.px_config[:logger].debug('PxModule[px_verify_request]: custom_block_handler triggered')
27
- return instance_exec(px_ctx, &PerimeterX.instance.px_config[:custom_block_handler])
31
+ unless px_ctx.nil? || px_ctx.context[:verified]
32
+ # In case custom block handler exists (soon to be deprecated)
33
+ if px_config.key?(:custom_block_handler)
34
+ px_config[:logger].debug("#{msg_title}: custom_block_handler triggered")
35
+ px_config[:logger].debug(
36
+ "#{msg_title}: Please note that custom_block_handler is deprecated. Use custom_verification_handler instead.")
37
+ return instance_exec(px_ctx, &px_config[:custom_block_handler])
28
38
  else
29
39
  # Generate template
30
- PerimeterX.instance.px_config[:logger].debug('PxModule[px_verify_request]: sending default block page')
31
- html = PxTemplateFactory.get_template(px_ctx, PerimeterX.instance.px_config)
40
+ px_config[:logger].debug("#{msg_title}: sending default block page")
41
+ html = PxTemplateFactory.get_template(px_ctx, px_config)
32
42
  response.headers['Content-Type'] = 'text/html'
33
43
  response.status = 403
34
44
  # Web handler
35
45
  if px_ctx.context[:cookie_origin] == 'cookie'
36
- PerimeterX.instance.px_config[:logger].debug('PxModule[px_verify_request]: web block')
46
+ px_config[:logger].debug('#{msg_title}: web block')
37
47
  response.headers['Content-Type'] = 'text/html'
38
48
  render :html => html
39
49
  else # Mobile SDK
40
- PerimeterX.instance.px_config[:logger].debug('PxModule[px_verify_request]: mobile sdk block')
50
+ px_config[:logger].debug("#{msg_title}: mobile sdk block")
41
51
  response.headers['Content-Type'] = 'application/json'
42
52
  hash_json = {
43
53
  :action => px_ctx.context[:block_action],
44
54
  :uuid => px_ctx.context[:uuid],
45
55
  :vid => px_ctx.context[:vid],
46
- :appId => PerimeterX.instance.px_config[:app_id],
56
+ :appId => px_config[:app_id],
47
57
  :page => Base64.strict_encode64(html),
48
- :collectorUrl => "https://collector-#{PerimeterX.instance.px_config[:app_id]}.perimeterx.net"
58
+ :collectorUrl => "https://collector-#{px_config[:app_id]}.perimeterx.net"
49
59
  }
50
60
  render :json => hash_json
51
61
  end
@@ -53,7 +63,7 @@ module PxModule
53
63
  end
54
64
 
55
65
  # Request was verified
56
- return verified
66
+ return px_ctx.nil? ? true : px_ctx.context[:verified]
57
67
  end
58
68
 
59
69
  def self.configure(params)
@@ -90,34 +100,30 @@ module PxModule
90
100
  def verify(env)
91
101
  begin
92
102
  @logger.debug('PerimeterX[pxVerify]')
93
- if (!@px_config[:module_enabled])
103
+ if !@px_config[:module_enabled]
94
104
  @logger.warn('Module is disabled')
95
- return true
105
+ return nil
96
106
  end
97
107
  req = ActionDispatch::Request.new(env)
98
108
  px_ctx = PerimeterXContext.new(@px_config, req)
99
109
 
100
110
  # Captcha phase
101
111
  captcha_verified, px_ctx = @px_captcha_validator.verify(px_ctx)
102
- if (captcha_verified)
112
+ if captcha_verified
103
113
  return handle_verification(px_ctx)
104
114
  end
105
115
 
106
116
  # Cookie phase
107
117
  cookie_verified, px_ctx = @px_cookie_validator.verify(px_ctx)
108
- if (!cookie_verified)
118
+ if !cookie_verified
109
119
  @px_s2s_validator.verify(px_ctx)
110
120
  end
111
121
 
112
- if (@px_config.key?(:custom_verification_handler))
113
- return @px_config[:custom_verification_handler].call(px_ctx.context)
114
- else
115
- return handle_verification(px_ctx)
116
- end
122
+ return handle_verification(px_ctx)
117
123
  rescue Exception => e
118
124
  @logger.error("#{e.backtrace.first}: #{e.message} (#{e.class})")
119
125
  e.backtrace.drop(1).map {|s| @logger.error("\t#{s}")}
120
- return true
126
+ return nil
121
127
  end
122
128
  end
123
129
 
@@ -139,25 +145,26 @@ module PxModule
139
145
  @logger.debug("PerimeterX[handle_verification]: processing ended - score:#{px_ctx.context[:score]}, uuid:#{px_ctx.context[:uuid]}")
140
146
 
141
147
  score = px_ctx.context[:score]
148
+ px_ctx.context[:verified] = score < @px_config[:blocking_score]
142
149
  # Case PASS request
143
- if (score < @px_config[:blocking_score])
150
+ if px_ctx.context[:verified]
144
151
  @logger.debug("PerimeterX[handle_verification]: score:#{score} < blocking score, passing request")
145
152
  @px_activity_client.send_page_requested_activity(px_ctx)
146
- return true
153
+ return px_ctx
147
154
  end
148
155
 
149
156
  # Case blocking activity
150
157
  @px_activity_client.send_block_activity(px_ctx)
151
158
 
152
159
  # In case were in monitor mode, end here
153
- if (@px_config[:module_mode] == PxModule::MONITOR_MODE)
160
+ if @px_config[:module_mode] == PxModule::MONITOR_MODE
154
161
  @logger.debug('PerimeterX[handle_verification]: monitor mode is on, passing request')
155
- return true
162
+ return px_ctx
156
163
  end
157
164
 
158
165
  @logger.debug('PerimeterX[handle_verification]: verification ended, the request should be blocked')
159
166
 
160
- return false, px_ctx
167
+ return px_ctx
161
168
  end
162
169
 
163
170
  private_class_method :new
@@ -12,7 +12,7 @@ module PxModule
12
12
  :cookie_key => nil,
13
13
  :auth_token => nil,
14
14
  :module_enabled => true,
15
- :captcha_enabled => true,
15
+ :captcha_provider => "reCaptcha",
16
16
  :challenge_enabled => true,
17
17
  :encryption_enabled => true,
18
18
  :blocking_score => 70,
@@ -14,6 +14,7 @@ module PxModule
14
14
  @context[:px_cookie] = Hash.new
15
15
  @context[:headers] = Hash.new
16
16
  @context[:cookie_origin] = 'cookie'
17
+ @context[:made_s2s_risk_api_call] = false
17
18
  cookies = req.cookies
18
19
 
19
20
  # Get token from header
@@ -24,6 +25,8 @@ module PxModule
24
25
  exploded_token = token.split(':', 2)
25
26
  cookie_sym = "v#{exploded_token[0]}".to_sym
26
27
  @context[:px_cookie][cookie_sym] = exploded_token[1]
28
+ else # TOKEN_HEADER exists yet there's no ':' delimiter - may indicate an error (storing original value)
29
+ @context[:px_cookie] = req.headers[PxModule::TOKEN_HEADER]
27
30
  end
28
31
  elsif !cookies.empty? # Get cookie from jar
29
32
  # Prepare hashed cookies
@@ -7,17 +7,19 @@ module PxModule
7
7
  super(px_config, http_client)
8
8
  end
9
9
 
10
- def send_captcha_request(vid, uuid, captcha, px_ctx)
10
+ def send_captcha_request(captcha, px_ctx)
11
11
 
12
12
  request_body = {
13
13
  :request => {
14
14
  :ip => px_ctx.context[:ip],
15
15
  :headers => format_headers(px_ctx),
16
- :uri => px_ctx.context[:uri]
16
+ :uri => px_ctx.context[:uri],
17
+ :captchaType => @px_config[:captcha_provider]
18
+ },
19
+ :additional => {
20
+ :module_version => @px_config[:sdk_name]
17
21
  },
18
22
  :pxCaptcha => captcha,
19
- :vid => vid,
20
- :uuid => uuid,
21
23
  :hostname => px_ctx.context[:hostname]
22
24
  }
23
25
 
@@ -25,30 +27,28 @@ module PxModule
25
27
  headers = {
26
28
  "Authorization" => "Bearer #{@px_config[:auth_token]}" ,
27
29
  "Content-Type" => "application/json"
28
- };
30
+ }
29
31
 
30
- return @http_client.post(PxModule::API_V1_CAPTCHA, request_body, headers, @px_config[:api_timeout], @px_config[:api_timeout_connection])
32
+ return @http_client.post(PxModule::API_CAPTCHA, request_body, headers, @px_config[:api_timeout], @px_config[:api_timeout_connection])
31
33
 
32
34
  end
33
35
 
34
36
  def verify(px_ctx)
35
37
  captcha_validated = false
36
38
  begin
37
- if(!px_ctx.context.key?(:px_captcha))
39
+ if !px_ctx.context.key?(:px_captcha)
38
40
  return captcha_validated, px_ctx
39
41
  end
40
- captcha, vid, uuid = px_ctx.context[:px_captcha].split(':', 3)
41
- if captcha.nil? || vid.nil? || uuid.nil?
42
+ captcha = px_ctx.context[:px_captcha]
43
+ if captcha.nil?
42
44
  return captcha_validated, px_ctx
43
45
  end
44
46
 
45
- px_ctx.context[:vid] = vid
46
- px_ctx.context[:uuid] = uuid
47
- response = send_captcha_request(vid, uuid, captcha, px_ctx)
47
+ response = send_captcha_request(captcha, px_ctx)
48
48
 
49
- if (response.status_code == 200)
49
+ if response.success?
50
50
  response_body = eval(response.body)
51
- if ( response_body[:code] == 0 )
51
+ if response_body[:status] == 0
52
52
  captcha_validated = true
53
53
  end
54
54
  end
@@ -56,7 +56,7 @@ module PxModule
56
56
  return captcha_validated, px_ctx
57
57
 
58
58
  rescue Exception => e
59
- @logger.error("PerimeterxCaptchaValidator[verify]: failed, returning false")
59
+ @logger.error("PerimeterxCaptchaValidator[verify]: failed, returning false => #{e.message}")
60
60
  return captcha_validated, px_ctx
61
61
  end
62
62
  end
@@ -18,16 +18,34 @@ module PxModule
18
18
 
19
19
  def verify(px_ctx)
20
20
  begin
21
- # Case no cookie
22
- if px_ctx.context[:px_cookie].empty?
23
- @logger.warn("PerimeterxCookieValidator:[verify]: cookie not found")
21
+ # Mobile Error cases
22
+ if px_ctx.context[:cookie_origin] == 'header'
23
+ if px_ctx.context[:px_cookie].to_s.empty?
24
+ @logger.warn("PerimeterxCookieValidator:[verify]: Empty token value - decryption failed")
25
+ px_ctx.context[:s2s_call_reason] = PxModule::COOKIE_DECRYPTION_FAILED
26
+ return false, px_ctx
27
+ elsif px_ctx.context[:px_cookie] == "1"
28
+ @logger.warn("PerimeterxCookieValidator:[verify]: no cookie")
29
+ px_ctx.context[:s2s_call_reason] = PxModule::NO_COOKIE
30
+ return false, px_ctx
31
+ elsif px_ctx.context[:px_cookie] == "2" # Mobile SDK connection error
32
+ @logger.warn("PerimeterxCookieValidator:[verify]: mobile sdk connection error")
33
+ px_ctx.context[:s2s_call_reason] = PxModule::MOBILE_SDK_CONNECTION_ERROR
34
+ return false, px_ctx
35
+ elsif px_ctx.context[:px_cookie] == "3" # Mobile SDK pinning error
36
+ @logger.warn("PerimeterxCookieValidator:[verify]: mobile sdk pinning error")
37
+ px_ctx.context[:s2s_call_reason] = PxModule::MOBILE_SDK_PINNING_ERROR
38
+ return false, px_ctx
39
+ end
40
+ elsif px_ctx.context[:px_cookie].empty?
41
+ @logger.warn("PerimeterxCookieValidator:[verify]: no cookie")
24
42
  px_ctx.context[:s2s_call_reason] = PxModule::NO_COOKIE
25
43
  return false, px_ctx
26
44
  end
27
45
 
28
46
  # Deserialize cookie start
29
47
  cookie = PerimeterxPayload.px_cookie_factory(px_ctx, @px_config)
30
- if (!cookie.deserialize())
48
+ if !cookie.deserialize()
31
49
  @logger.warn("PerimeterxCookieValidator:[verify]: invalid cookie")
32
50
  px_ctx.context[:s2s_call_reason] = PxModule::COOKIE_DECRYPTION_FAILED
33
51
  return false, px_ctx
@@ -39,26 +57,25 @@ module PxModule
39
57
  px_ctx.context[:block_action] = px_ctx.set_block_action_type(cookie.cookie_block_action())
40
58
  px_ctx.context[:cookie_hmac] = cookie.cookie_hmac()
41
59
 
42
- if (cookie.expired?)
60
+ if cookie.expired?
43
61
  @logger.warn("PerimeterxCookieValidator:[verify]: cookie expired")
44
62
  px_ctx.context[:s2s_call_reason] = PxModule::EXPIRED_COOKIE
45
63
  return false, px_ctx
46
64
  end
47
65
 
48
- if (cookie.high_score?)
66
+ if cookie.high_score?
49
67
  @logger.warn("PerimeterxCookieValidator:[verify]: cookie high score")
50
- px_ctx.context[:s2s_call_reason] = PxModule::COOKIE_HIGH_SCORE
51
68
  px_ctx.context[:blocking_reason] = 'cookie_high_score'
52
69
  return true, px_ctx
53
70
  end
54
71
 
55
- if (!cookie.secured?)
72
+ if !cookie.secured?
56
73
  @logger.warn("PerimeterxCookieValidator:[verify]: cookie invalid hmac")
57
- px_ctx.context[:s2s_call_reason] = PxModule::COOKIE_VALIDATION_FAILED
74
+ px_ctx.context[:s2s_call_reason] = PxModule:: COOKIE_VALIDATION_FAILED
58
75
  return false, px_ctx
59
76
  end
60
77
 
61
- if (px_ctx.context[:sensitive_route])
78
+ if px_ctx.context[:sensitive_route]
62
79
  @logger.info("PerimeterxCookieValidator:[verify]: cookie was verified but route is sensitive")
63
80
  px_ctx.context[:s2s_call_reason] = PxModule::SENSITIVE_ROUTE
64
81
  return false, px_ctx
@@ -10,7 +10,7 @@ module PxModule
10
10
 
11
11
  # Routes
12
12
  API_V1_S2S = '/api/v1/collector/s2s'
13
- API_V1_CAPTCHA = '/api/v1/risk/captcha'
13
+ API_CAPTCHA = '/api/v2/risk/captcha'
14
14
  API_V2_RISK = '/api/v2/risk'
15
15
 
16
16
  # Activity Types
@@ -28,7 +28,6 @@ module PxModule
28
28
 
29
29
  # Templates
30
30
  BLOCK_TEMPLATE = 'block'
31
- CAPTCHA_TEMPLATE = 'captcha'
32
31
  TEMPLATE_EXT = '.mustache'
33
32
 
34
33
 
@@ -48,4 +47,6 @@ module PxModule
48
47
 
49
48
  # Mobile SDK
50
49
  TOKEN_HEADER = 'X-PX-AUTHORIZATION'
50
+ MOBILE_SDK_CONNECTION_ERROR = 'mobile_sdk_connection_error'
51
+ MOBILE_SDK_PINNING_ERROR = 'mobile_sdk_pinning_error'
51
52
  end
@@ -11,7 +11,7 @@ module PxModule
11
11
  end
12
12
 
13
13
  logger.debug('PxTemplateFactory[get_template]: rendering template')
14
- template_type = px_ctx.context[:block_action] == 'captcha' ? PxModule::CAPTCHA_TEMPLATE : BLOCK_TEMPLATE
14
+ template_type = px_ctx.context[:block_action] == 'captcha' ? px_config[:captcha_provider].downcase : BLOCK_TEMPLATE
15
15
 
16
16
  template_postfix = ''
17
17
  if px_ctx.context[:cookie_origin] == 'header'
@@ -119,7 +119,7 @@
119
119
  <div class="page-footer">
120
120
  <p>
121
121
  Powered by
122
- <a href="https://www.perimeterx.com">PerimeterX</a>
122
+ <a href="https://www.perimeterx.com/whywasiblocked">PerimeterX</a>
123
123
  , Inc.
124
124
  </p>
125
125
  </div>
@@ -130,4 +130,4 @@
130
130
  <script src="{{jsRef}}"></script>
131
131
  {{/ jsRef }}
132
132
  </body>
133
- </html>
133
+ </html>
@@ -119,7 +119,7 @@
119
119
  <div class="page-footer">
120
120
  <p>
121
121
  Powered by
122
- <a href="https://www.perimeterx.com">PerimeterX</a>
122
+ <a href="https://www.perimeterx.com/whywasiblocked">PerimeterX</a>
123
123
  , Inc.
124
124
  </p>
125
125
  </div>
@@ -0,0 +1,178 @@
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
+ <!-- Captcha -->
148
+ <script>
149
+
150
+ function loadFunCaptcha() {
151
+ var vid = '{{vid}}';
152
+ var uuid = '{{uuid}}';
153
+
154
+ new FunCaptcha({
155
+ public_key: "19E4B3B8-6CBE-35CC-4205-FC79ECDDA765",
156
+ target_html: "CAPTCHA",
157
+ callback: function () {
158
+ var expiryUtc = new Date(Date.now() + 1000 * 10).toUTCString();
159
+ var pxCaptcha = "_pxCaptcha=" + btoa(JSON.stringify({r: document.getElementById("FunCaptcha-Token").value, u: uuid, v: vid}));
160
+ var cookieParts = [
161
+ pxCaptcha,
162
+ "; expires=",
163
+ expiryUtc,
164
+ "; path=/"
165
+ ];
166
+
167
+ document.cookie = cookieParts.join("");
168
+ location.reload();
169
+ }
170
+ });
171
+ }
172
+ </script>
173
+ <!-- Custom Script -->
174
+ {{# jsRef }}
175
+ <script src="{{ . }}"></script>
176
+ {{/ jsRef }}
177
+ </body>
178
+ </html>