perimeter_x 1.3.0 → 2.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,26 @@
1
+ def validate_hash_schema(hash, schema)
2
+ hash.each do |key, value|
3
+ if schema.key?(key) && value != nil
4
+ # validate value types in hash are according to schema
5
+ if !schema[key][:types].include?(value.class)
6
+ raise PxConfigurationException.new("PerimeterX: Type of #{key} should be one of #{schema[key][:types]} but instead is #{value.class}")
7
+ end
8
+
9
+ # validate arrays elments types are according to schema
10
+ if value.class == Array
11
+ value.each do |element|
12
+ if !schema[key][:allowed_element_types].include?(element.class)
13
+ raise PxConfigurationException.new("PerimeterX: #{key} may only contain elements of the following types: #{schema[key][:allowed_element_types]} but includes element of type #{element.class}")
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ # validate required fields exist in hash
21
+ schema.each do |key, value|
22
+ if value[:required] && hash[key].nil?
23
+ raise PxConfigurationException.new("PerimeterX: #{key} configuration is missing")
24
+ end
25
+ end
26
+ end
@@ -18,47 +18,51 @@ module PxModule
18
18
 
19
19
  def verify(px_ctx)
20
20
  begin
21
- # Case no cookie
22
21
  if px_ctx.context[:px_cookie].empty?
23
- @logger.warn("PerimeterxCookieValidator:[verify]: cookie not found")
22
+ @logger.warn("PerimeterxCookieValidator:[verify]: no cookie")
24
23
  px_ctx.context[:s2s_call_reason] = PxModule::NO_COOKIE
25
24
  return false, px_ctx
26
25
  end
27
-
26
+
28
27
  # Deserialize cookie start
29
28
  cookie = PerimeterxPayload.px_cookie_factory(px_ctx, @px_config)
30
- if (!cookie.deserialize())
29
+ if !cookie.deserialize()
31
30
  @logger.warn("PerimeterxCookieValidator:[verify]: invalid cookie")
31
+ px_ctx.context[:px_orig_cookie] = px_ctx.get_px_cookie
32
32
  px_ctx.context[:s2s_call_reason] = PxModule::COOKIE_DECRYPTION_FAILED
33
33
  return false, px_ctx
34
34
  end
35
35
  px_ctx.context[:decoded_cookie] = cookie.decoded_cookie
36
36
  px_ctx.context[:score] = cookie.cookie_score()
37
37
  px_ctx.context[:uuid] = cookie.decoded_cookie[:u]
38
- px_ctx.context[:vid] = cookie.decoded_cookie[:v]
39
38
  px_ctx.context[:block_action] = px_ctx.set_block_action_type(cookie.cookie_block_action())
40
39
  px_ctx.context[:cookie_hmac] = cookie.cookie_hmac()
41
40
 
42
- if (cookie.expired?)
41
+ vid = cookie.decoded_cookie[:v]
42
+ if vid.is_a?(String) && vid.match(PxModule::VID_REGEX)
43
+ px_ctx.context[:vid_source] = "risk_cookie"
44
+ px_ctx.context[:vid] = vid
45
+ end
46
+
47
+ if cookie.expired?
43
48
  @logger.warn("PerimeterxCookieValidator:[verify]: cookie expired")
44
49
  px_ctx.context[:s2s_call_reason] = PxModule::EXPIRED_COOKIE
45
50
  return false, px_ctx
46
51
  end
47
52
 
48
- if (cookie.high_score?)
53
+ if cookie.high_score?
49
54
  @logger.warn("PerimeterxCookieValidator:[verify]: cookie high score")
50
- px_ctx.context[:s2s_call_reason] = PxModule::COOKIE_HIGH_SCORE
51
55
  px_ctx.context[:blocking_reason] = 'cookie_high_score'
52
56
  return true, px_ctx
53
57
  end
54
58
 
55
- if (!cookie.secured?)
59
+ if !cookie.secured?
56
60
  @logger.warn("PerimeterxCookieValidator:[verify]: cookie invalid hmac")
57
- px_ctx.context[:s2s_call_reason] = PxModule::COOKIE_VALIDATION_FAILED
61
+ px_ctx.context[:s2s_call_reason] = PxModule:: COOKIE_VALIDATION_FAILED
58
62
  return false, px_ctx
59
63
  end
60
64
 
61
- if (px_ctx.context[:sensitive_route])
65
+ if px_ctx.context[:sensitive_route]
62
66
  @logger.info("PerimeterxCookieValidator:[verify]: cookie was verified but route is sensitive")
63
67
  px_ctx.context[:s2s_call_reason] = PxModule::SENSITIVE_ROUTE
64
68
  return false, px_ctx
@@ -66,10 +70,11 @@ module PxModule
66
70
 
67
71
  @logger.debug("PerimeterxCookieValidator:[verify]: cookie validation passed succesfully")
68
72
 
73
+ px_ctx.context[:pass_reason] = 'cookie'
69
74
  return true, px_ctx
70
75
  rescue Exception => e
71
76
  @logger.error("PerimeterxCookieValidator:[verify]: exception while verifying cookie => #{e.message}")
72
- px_ctx.context[:px_orig_cookie] = cookie.px_cookie
77
+ px_ctx.context[:px_orig_cookie] = px_ctx.context[:px_cookie]
73
78
  px_ctx.context[:s2s_call_reason] = PxModule::COOKIE_DECRYPTION_FAILED
74
79
  return false, px_ctx
75
80
  end
@@ -26,12 +26,22 @@ module PxModule
26
26
  :additional => {
27
27
  :s2s_call_reason => px_ctx.context[:s2s_call_reason],
28
28
  :module_version => @px_config[:sdk_name],
29
- :cookie_origin => px_ctx.context[:cookie_origin],
30
29
  :http_method => px_ctx.context[:http_method],
31
30
  :http_version => px_ctx.context[:http_version],
32
31
  :risk_mode => risk_mode
33
32
  }
34
33
  }
34
+
35
+ #Check for cookie_origin
36
+ if !px_ctx.context[:px_cookie].empty?
37
+ request_body[:additional][:cookie_origin] = px_ctx.context[:cookie_origin]
38
+ end
39
+
40
+ #Override s2s_call_reason in case of mobile error
41
+ if !px_ctx.context[:mobile_error].nil?
42
+ request_body[:additional][:s2s_call_reason] = "mobile_error_#{px_ctx.context[:mobile_error]}"
43
+ end
44
+
35
45
  #Check for hmac
36
46
  @logger.debug("px_ctx cookie_hmac key = #{px_ctx.context.key?(:cookie_hmac)}, value is: #{px_ctx.context[:cookie_hmac]}")
37
47
  if px_ctx.context.key?(:cookie_hmac)
@@ -67,11 +77,19 @@ module PxModule
67
77
  };
68
78
 
69
79
  # Custom risk handler
80
+ risk_start = Time.now
70
81
  if (risk_mode == PxModule::ACTIVE_MODE && @px_config.key?(:custom_risk_handler))
71
- response = @px_config[:custom_risk_handler].call(PxModule::API_V2_RISK, request_body, headers, @px_config[:api_timeout], @px_config[:api_timeout_connection])
82
+ response = @px_config[:custom_risk_handler].call(PxModule::API_V3_RISK, request_body, headers, @px_config[:api_timeout], @px_config[:api_timeout_connection])
72
83
  else
73
- response = @http_client.post(PxModule::API_V2_RISK , request_body, headers, @px_config[:api_timeout], @px_config[:api_timeout_connection])
84
+ response = @http_client.post(PxModule::API_V3_RISK , request_body, headers, @px_config[:api_timeout], @px_config[:api_timeout_connection])
85
+ end
86
+
87
+ # Set risk_rtt
88
+ if(response)
89
+ risk_end = Time.now
90
+ px_ctx.context[:risk_rtt] = ((risk_end-risk_start)*1000).round
74
91
  end
92
+
75
93
  return response
76
94
  end
77
95
 
@@ -79,6 +97,7 @@ module PxModule
79
97
  @logger.debug("PerimeterxS2SValidator[verify]")
80
98
  response = send_risk_request(px_ctx)
81
99
  if (!response)
100
+ px_ctx.context[:pass_reason] = "s2s_timeout"
82
101
  return px_ctx
83
102
  end
84
103
  px_ctx.context[:made_s2s_risk_api_call] = true
@@ -86,7 +105,7 @@ module PxModule
86
105
  # From here response should be valid, if success or error
87
106
  response_body = eval(response.body);
88
107
  # When success
89
- if (response.code == 200 && response_body.key?(:score) && response_body.key?(:action))
108
+ if (response.code == 200 && response_body.key?(:score) && response_body.key?(:action) && response_body.key?(:status) && response_body[:status] == 0 )
90
109
  @logger.debug("PerimeterxS2SValidator[verify]: response ok")
91
110
  score = response_body[:score]
92
111
  px_ctx.context[:score] = score
@@ -97,13 +116,17 @@ module PxModule
97
116
  px_ctx.context[:blocking_reason] = 'challenge'
98
117
  elsif (score >= @px_config[:blocking_score])
99
118
  px_ctx.context[:blocking_reason] = 's2s_high_score'
119
+ else
120
+ px_ctx.context[:pass_reason] = 's2s'
100
121
  end #end challange or blocking score
101
122
  end #end success response
102
123
 
103
124
  # When error
104
- if(response.code != 200)
105
- @logger.warn("PerimeterxS2SValidator[verify]: bad response, return code #{response.code}")
106
- px_ctx.context[:uuid] = ""
125
+ risk_error_status = response_body && response_body.key?(:status) && response_body[:status] == -1
126
+ if(response.code != 200 || risk_error_status)
127
+ @logger.warn("PerimeterxS2SValidator[verify]: bad response, returned code #{response.code} #{risk_error_status ? "risk status: -1" : ""}")
128
+ px_ctx.context[:pass_reason] = 'request_failed'
129
+ px_ctx.context[:uuid] = (!response_body || response_body[:uuid].nil?)? "" : response_body[:uuid]
107
130
  px_ctx.context[:s2s_error_msg] = !response_body || response_body[:message].nil? ? 'unknown' : response_body[:message]
108
131
  end
109
132
 
@@ -10,8 +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'
14
- API_V2_RISK = '/api/v2/risk'
13
+ API_V3_RISK = '/api/v3/risk'
15
14
 
16
15
  # Activity Types
17
16
  BLOCK_ACTIVITY = 'block'
@@ -27,9 +26,9 @@ module PxModule
27
26
  SENSITIVE_ROUTE = 'sensitive_route'
28
27
 
29
28
  # Templates
30
- BLOCK_TEMPLATE = 'block'
31
- CAPTCHA_TEMPLATE = 'captcha'
29
+ CHALLENGE_TEMPLATE = 'block_template'
32
30
  TEMPLATE_EXT = '.mustache'
31
+ RATELIMIT_TEMPLATE = 'ratelimit'
33
32
 
34
33
 
35
34
  # Template Props
@@ -41,11 +40,24 @@ module PxModule
41
40
  PROP_CUSTOM_LOGO = :customLogo
42
41
  PROP_CSS_REF = :cssRef
43
42
  PROP_JS_REF = :jsRef
44
- HOST_URL = :hostUrl
43
+ PROP_BLOCK_SCRIPT = :blockScript
44
+ PROP_JS_CLIENT_SRC = :jsClientSrc
45
+ PROP_HOST_URL = :hostUrl
46
+ PROP_FIRST_PARTY_ENABLED = :firstPartyEnabled
47
+
48
+ # Hosts
49
+ CLIENT_HOST = 'client.perimeterx.net'
50
+ CAPTCHA_HOST = 'captcha.px-cdn.net'
45
51
 
46
52
  VISIBLE = 'visible'
47
53
  HIDDEN = 'hidden'
48
54
 
49
55
  # Mobile SDK
50
56
  TOKEN_HEADER = 'X-PX-AUTHORIZATION'
57
+ ORIGINAL_TOKEN_HEADER = 'X-PX-ORIGINAL-TOKEN'
58
+
59
+ # Regular Expressions
60
+ VID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/
61
+ MOBILE_TOKEN_V3_REGEX = /\A3:(.*)\z/
62
+ MOBILE_ERROR_REGEX = /\A[0-9]\z/
51
63
  end
@@ -1,6 +1,7 @@
1
1
  require 'perimeterx/utils/px_logger'
2
2
  require 'typhoeus'
3
3
  require 'concurrent'
4
+ require 'net/http'
4
5
 
5
6
  module PxModule
6
7
  class PxHttpClient
@@ -12,7 +13,7 @@ module PxModule
12
13
  def initialize(px_config)
13
14
  @px_config = px_config
14
15
  @logger = px_config[:logger]
15
- @logger.debug("PxHttpClient[initialize]: HTTP client is being initilized with base_uri: #{px_config[:perimeterx_server_host]}")
16
+ @logger.debug("PxHttpClient[initialize]: HTTP client is being initilized with base_uri: #{px_config[:backend_url]}")
16
17
  end
17
18
 
18
19
  # Runs a POST command to Perimeter X servers
@@ -28,7 +29,7 @@ module PxModule
28
29
  begin
29
30
  @logger.debug("PxHttpClient[post]: posting to #{path} headers {#{headers.to_json()}} body: {#{body.to_json()}} ")
30
31
  response = Typhoeus.post(
31
- "#{px_config[:perimeterx_server_host]}#{path}",
32
+ "#{px_config[:backend_url]}#{path}",
32
33
  headers: headers,
33
34
  body: body.to_json,
34
35
  timeout: api_timeout,
@@ -45,5 +46,61 @@ module PxModule
45
46
  return response
46
47
  end
47
48
 
49
+
50
+ def post_xhr(url, body, headers)
51
+ s = Time.now
52
+ begin
53
+ @logger.debug("PxHttpClient[post]: sending xhr post request to #{url} with headers {#{headers.to_json()}}")
54
+
55
+ #set url
56
+ uri = URI(url)
57
+ req = Net::HTTP::Post.new(uri)
58
+
59
+ # set body
60
+ req.body=body
61
+
62
+ # set headers
63
+ headers.each do |key, value|
64
+ req[key] = value
65
+ end
66
+
67
+ # send request
68
+ response = Net::HTTP.start(uri.hostname, uri.port) {|http|
69
+ http.request(req)
70
+ }
71
+
72
+ ensure
73
+ e = Time.now
74
+ @logger.debug("PxHttpClient[get]: runtime: #{(e-s) * 1000.0}")
75
+ end
76
+ return response
77
+ end
78
+
79
+
80
+ def get(url, headers)
81
+ s = Time.now
82
+ begin
83
+ @logger.debug("PxHttpClient[get]: sending get request to #{url} with headers {#{headers.to_json()}}")
84
+
85
+ #set url
86
+ uri = URI(url)
87
+ req = Net::HTTP::Get.new(uri)
88
+
89
+ # set headers
90
+ headers.each do |key, value|
91
+ req[key] = value
92
+ end
93
+
94
+ # send request
95
+ response = Net::HTTP.start(uri.hostname, uri.port) {|http|
96
+ http.request(req)
97
+ }
98
+
99
+ ensure
100
+ e = Time.now
101
+ @logger.debug("PxHttpClient[get]: runtime: #{(e-s) * 1000.0}")
102
+ end
103
+ return response
104
+ end
48
105
  end
49
106
  end
@@ -3,23 +3,24 @@ require 'perimeterx/utils/px_constants'
3
3
  module PxModule
4
4
  module PxTemplateFactory
5
5
 
6
- def self.get_template(px_ctx, px_config)
6
+ def self.get_template(px_ctx, px_config, px_template_object)
7
7
  logger = px_config[:logger]
8
8
  if (px_config[:challenge_enabled] && px_ctx.context[:block_action] == 'challenge')
9
9
  logger.debug('PxTemplateFactory[get_template]: px challange triggered')
10
10
  return px_ctx.context[:block_action_data].html_safe
11
11
  end
12
12
 
13
- logger.debug('PxTemplateFactory[get_template]: rendering template')
14
- template_type = px_ctx.context[:block_action] == 'captcha' ? PxModule::CAPTCHA_TEMPLATE : BLOCK_TEMPLATE
13
+ view = Mustache.new
15
14
 
16
- template_postfix = ''
17
- if px_ctx.context[:cookie_origin] == 'header'
18
- template_postfix = '.mobile'
15
+ if (px_ctx.context[:block_action] == 'rate_limit')
16
+ logger.debug('PxTemplateFactory[get_template]: rendering ratelimit template')
17
+ template_type = RATELIMIT_TEMPLATE
18
+ else
19
+ logger.debug('PxTemplateFactory[get_template]: rendering template')
20
+ template_type = CHALLENGE_TEMPLATE
19
21
  end
20
22
 
21
- Mustache.template_file = "#{File.dirname(__FILE__) }/templates/#{template_type}#{template_postfix}#{PxModule::TEMPLATE_EXT}"
22
- view = Mustache.new
23
+ Mustache.template_file = "#{File.dirname(__FILE__) }/templates/#{template_type}#{PxModule::TEMPLATE_EXT}"
23
24
 
24
25
  view[PxModule::PROP_APP_ID] = px_config[:app_id]
25
26
  view[PxModule::PROP_REF_ID] = px_ctx.context[:uuid]
@@ -28,8 +29,11 @@ module PxModule
28
29
  view[PxModule::PROP_CUSTOM_LOGO] = px_config[:custom_logo]
29
30
  view[PxModule::PROP_CSS_REF] = px_config[:css_ref]
30
31
  view[PxModule::PROP_JS_REF] = px_config[:js_ref]
31
- view[PxModule::HOST_URL] = "https://collector-#{px_config[:app_id]}.perimeterx.net"
32
+ view[PxModule::PROP_HOST_URL] = px_template_object[:host_url]
32
33
  view[PxModule::PROP_LOGO_VISIBILITY] = px_config[:custom_logo] ? PxModule::VISIBLE : PxModule::HIDDEN
34
+ view[PxModule::PROP_BLOCK_SCRIPT] = px_template_object[:block_script]
35
+ view[PxModule::PROP_JS_CLIENT_SRC] = px_template_object[:js_client_src]
36
+ view[PxModule::PROP_FIRST_PARTY_ENABLED] = px_ctx.context[:first_party_enabled]
33
37
 
34
38
  return view.render.html_safe
35
39
  end
@@ -6,19 +6,19 @@
6
6
  <title>Access to this page has been denied.</title>
7
7
  <link href="https://fonts.googleapis.com/css?family=Open+Sans:300" rel="stylesheet">
8
8
  <style>
9
- html,body{
9
+ html, body {
10
10
  margin: 0;
11
11
  padding: 0;
12
12
  font-family: 'Open Sans', sans-serif;
13
13
  color: #000;
14
14
  }
15
15
 
16
- a{
16
+ a {
17
17
  color: #c5c5c5;
18
18
  text-decoration: none;
19
19
  }
20
20
 
21
- .container{
21
+ .container {
22
22
  align-items: center;
23
23
  display: flex;
24
24
  flex: 1;
@@ -30,7 +30,7 @@
30
30
  .container > div {
31
31
  width: 100%;
32
32
  display: flex;
33
- justify-content:center;
33
+ justify-content: center;
34
34
  }
35
35
 
36
36
  .container > div > div {
@@ -38,38 +38,40 @@
38
38
  width: 80%;
39
39
  }
40
40
 
41
- .customer-logo-wrapper{
41
+ .customer-logo-wrapper {
42
42
  padding-top: 2rem;
43
43
  flex-grow: 0;
44
44
  background-color: #fff;
45
45
  visibility: {{logoVisibility}};
46
46
  }
47
47
 
48
- .customer-logo{
48
+ .customer-logo {
49
49
  border-bottom: 1px solid #000;
50
50
  }
51
51
 
52
- .customer-logo > img{
52
+ .customer-logo > img {
53
53
  padding-bottom: 1rem;
54
54
  max-height: 50px;
55
- max-width: auto;
55
+ max-width: 100%;
56
56
  }
57
57
 
58
- .page-title-wrapper{
58
+ .page-title-wrapper {
59
59
  flex-grow: 2;
60
60
  }
61
+
61
62
  .page-title {
62
63
  flex-direction: column-reverse;
63
64
  }
64
65
 
65
- .content-wrapper{
66
+ .content-wrapper {
66
67
  flex-grow: 5;
67
68
  }
68
- .content{
69
+
70
+ .content {
69
71
  flex-direction: column;
70
72
  }
71
73
 
72
- .page-footer-wrapper{
74
+ .page-footer-wrapper {
73
75
  align-items: center;
74
76
  flex-grow: 0.2;
75
77
  background-color: #000;
@@ -77,17 +79,16 @@
77
79
  font-size: 70%;
78
80
  }
79
81
 
80
- @media (min-width:768px){
81
- html,body{
82
+ @media (min-width: 768px) {
83
+ html, body {
82
84
  height: 100%;
83
85
  }
84
86
  }
85
87
  </style>
86
88
  <!-- Custom CSS -->
87
89
  {{#cssRef}}
88
- <link rel="stylesheet" type="text/css" href="{{cssRef}}" />
90
+ <link rel="stylesheet" type="text/css" href="{{{cssRef}}}"/>
89
91
  {{/cssRef}}
90
- <script src="https://www.google.com/recaptcha/api.js" async defer></script>
91
92
  </head>
92
93
 
93
94
  <body>
@@ -104,13 +105,12 @@
104
105
  </div>
105
106
  <div class="content-wrapper">
106
107
  <div class="content">
107
- <p>
108
- Please click "I am not a robot" to continue
109
- </p>
110
- <div class="g-recaptcha" data-sitekey="6Lcj-R8TAAAAABs3FrRPuQhLMbp5QrHsHufzLf7b" data-callback="handleCaptcha" data-theme="dark">
108
+
109
+ <div id="px-captcha">
111
110
  </div>
112
111
  <p>
113
- Access to this page has been denied because we believe you are using automation tools to browse the website.
112
+ Access to this page has been denied because we believe you are using automation tools to browse the
113
+ website.
114
114
  </p>
115
115
  <p>
116
116
  This may happen as a result of the following:
@@ -124,7 +124,8 @@
124
124
  </li>
125
125
  </ul>
126
126
  <p>
127
- Please make sure that Javascript and cookies are enabled on your browser and that you are not blocking them from loading.
127
+ Please make sure that Javascript and cookies are enabled on your browser and that you are not blocking
128
+ them from loading.
128
129
  </p>
129
130
  <p>
130
131
  Reference ID: #{{refId}}
@@ -135,62 +136,40 @@
135
136
  <div class="page-footer">
136
137
  <p>
137
138
  Powered by
138
- <a href="https://www.perimeterx.com">PerimeterX</a>
139
+ <a href="https://www.perimeterx.com/whywasiblocked">PerimeterX</a>
139
140
  , Inc.
140
141
  </p>
141
142
  </div>
142
143
  </div>
143
144
  </section>
144
- <!-- Captcha -->
145
+ <!-- Px -->
145
146
  <script>
146
- function captchaSolved(res) {
147
- window.location.href = '/px/captcha_callback?status=' + res.status;
148
- }
149
-
150
- function handleCaptcha(response) {
151
- var appId = '{{appId}}';
152
- var vid = '{{vid}}';
153
- var uuid = '{{uuid}}';
154
- var collectorUrl = '{{{hostUrl}}}';
155
- var req = new XMLHttpRequest();
156
- req.open('POST', collectorUrl + '/api/v1/collector/captcha');
157
- req.setRequestHeader('Content-Type', 'application/json');
158
- req.addEventListener('error', function() {
159
- captchaSolved({
160
- status: 1
161
- });
162
- });
163
- req.addEventListener('cancel', function() {
164
- captchaSolved({
165
- status: 2
166
- });
167
- });
168
- req.addEventListener('load', function() {
169
- if (req.status == 200) {
170
- try {
171
- var responseJSON = JSON.parse(req.responseText);
172
- return captchaSolved(responseJSON);
173
- } catch (ex) {}
174
- }
175
- captchaSolved({
176
- status: 3
177
- });
178
- });
179
- req.send(JSON.stringify({
180
- appId: appId,
181
- uuid: uuid,
182
- vid: vid,
183
- pxCaptcha: response,
184
- hostname: window.location.hostname,
185
- request: {
186
- url: window.location.href
187
- }
188
- }));
147
+ window._pxAppId = '{{appId}}';
148
+ window._pxJsClientSrc = '{{{jsClientSrc}}}';
149
+ window._pxFirstPartyEnabled = {{firstPartyEnabled}};
150
+ window._pxVid = '{{vid}}';
151
+ window._pxUuid = '{{uuid}}';
152
+ window._pxHostUrl = '{{{hostUrl}}}';
153
+ </script>
154
+ <script>
155
+ var s = document.createElement('script');
156
+ s.src = '{{{blockScript}}}';
157
+ var p = document.getElementsByTagName('head')[0];
158
+ p.insertBefore(s, null);
159
+ if ({{firstPartyEnabled}}) {
160
+ s.onerror = function () {
161
+ s = document.createElement('script');
162
+ var suffixIndex = '{{{blockScript}}}'.indexOf('captcha.js');
163
+ var temperedBlockScript = '{{{blockScript}}}'.substring(suffixIndex);
164
+ s.src = '//captcha.px-cdn.net/{{appId}}/' + temperedBlockScript;
165
+ p.parentNode.insertBefore(s, p);
166
+ };
189
167
  }
190
168
  </script>
169
+
191
170
  <!-- Custom Script -->
192
171
  {{#jsRef}}
193
- <script src="{{jsRef}}"></script>
172
+ <script src="{{{jsRef}}}"></script>
194
173
  {{/jsRef}}
195
174
  </body>
196
- </html>
175
+ </html>