#[cfg(feature = "kv_store")]
use crate::modules::kvstore_ext::KVStoreGetExt;
use crate::modules::pxconstants::{
CUSTOM_COOKIE_HEADER, GRAPHQL_BODY_MAX_LENGTH, MCP_ENDPOINT_PATH, WHITELIST_EXT,
};
use crate::px_error;
use crate::pxcontext::{PXContext, PXModuleMode, TokenVersion};
#[cfg(target_arch = "wasm32")]
use fastly::secret_store::SecretStore;
#[cfg(not(feature = "kv_store"))]
use fastly::ConfigStore;
#[cfg(feature = "kv_store")]
use fastly::KVStore;
use fastly::{Request, Response};
use regex::Regex;
use serde::{
de::{self, Deserializer, MapAccess, Visitor},
ser::{SerializeMap, Serializer},
Deserialize, Serialize,
};
use std::collections::HashSet;
use std::fmt;
use strum::{EnumProperty, IntoEnumIterator};
use strum_macros::{AsRefStr, Display, EnumIter, EnumProperty as EnumPropertyMacro};
#[derive(Default, Clone)]
pub struct PXCustomParams {
pub custom_param1: String,
pub custom_param2: String,
pub custom_param3: String,
pub custom_param4: String,
pub custom_param5: String,
pub custom_param6: String,
pub custom_param7: String,
pub custom_param8: String,
pub custom_param9: String,
pub custom_param10: String,
}
pub type PXEnrichCustomParamsFn = fn(req: &Request, conf: &PXConfig, params: &mut PXCustomParams);
pub type PXIsSensitiveRequestFn = fn(req: &Request, conf: &PXConfig) -> bool;
pub type PXIsFilteredRequestFn = fn(req: &Request, conf: &PXConfig) -> bool;
pub type PXIsEnforcedRequestFn = fn(req: &Request, conf: &PXConfig) -> bool;
pub type PXIsMonitoredRequestFn = fn(req: &Request, conf: &PXConfig) -> bool;
pub type PXAdditionalActivityHandlerFn = fn(req: &Request, conf: &PXConfig, ctx: &PXContext);
pub type PXCorsCustomPreflightHandlerFn = fn(req: &Request, conf: &PXConfig) -> Option<Response>;
pub type PXCorsCustomBlockResponseHeadersFn =
fn(req: &Request, conf: &PXConfig) -> Vec<(String, String)>;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct PXRawCredentials {
pub user: Option<String>,
pub pass: Option<String>,
}
impl PXRawCredentials {
pub fn without_empty_fields(mut self) -> Self {
if self.user.as_deref().is_some_and(str::is_empty) {
self.user = None;
}
if self.pass.as_deref().is_some_and(str::is_empty) {
self.pass = None;
}
self
}
}
pub type PXExtractCredentialsFn =
fn(req: &Request, endpoint_index: usize) -> Option<PXRawCredentials>;
pub type PXLoginSuccessfulFn = fn(resp: &Response, endpoint_index: usize) -> Option<bool>;
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq, Eq)]
pub struct PXCredentialEndpointConfig {
pub path: String,
#[serde(default)]
pub path_type: String,
pub method: String,
#[serde(default)]
pub sent_through: String,
#[serde(default)]
pub user_field: String,
#[serde(default)]
pub pass_field: String,
#[serde(default)]
pub protocol: String,
#[serde(default)]
pub login_successful_reporting_method: String,
#[serde(default)]
pub login_successful_statuses: Vec<u16>,
#[serde(default)]
pub login_successful_body_regex: String,
#[serde(default)]
pub login_successful_header_name: String,
#[serde(default)]
pub login_successful_header_value: String,
}
#[derive(Clone)]
pub struct PXPreparedCredentialEndpoint {
pub config: PXCredentialEndpointConfig,
pub path_regex: Option<Regex>,
}
#[derive(
Display, AsRefStr, EnumIter, EnumPropertyMacro, Clone, Copy, PartialEq, Eq, Hash, Debug,
)]
#[strum(serialize_all = "snake_case")]
#[strum(prefix = "px_")]
#[doc(hidden)]
pub enum PXConfigKey {
#[strum(props(static = "true", required = "true"))]
AppId,
#[strum(props(sensitive = "true", static = "true", required = "true"))]
CookieSecret,
#[strum(props(sensitive = "true", static = "true", required = "true"))]
AuthToken,
Debug,
BlockingScore,
ModuleEnabled,
ModuleMode,
SensitiveHeaders,
SensitiveRoutes,
SensitiveRoutesRegex,
FilterByRoute,
FilterByExtension,
FilterByUserAgent,
FilterByIp,
FilterByHttpMethod,
CustomCookieHeader,
EnforcedRoutes,
MonitoredRoutes,
BypassMonitorHeader,
FirstPartyEnabled,
CustomLogo,
JsRef,
CssRef,
#[strum(props(local = "true"))]
HumanSapiHost,
#[strum(props(local = "true"))]
HumanSapiBackend,
#[strum(props(local = "true"))]
HumanCollectorHost,
#[strum(props(local = "true"))]
HumanCollectorBackend,
#[strum(props(local = "true"))]
HumanClientHost,
#[strum(props(local = "true"))]
HumanClientBackend,
#[strum(props(local = "true"))]
HumanCaptchaHost,
#[strum(props(local = "true"))]
HumanCaptchaBackend,
IpHeaders,
LogEndpoint,
DataEnrichmentHeaderName,
ExtractedCookies,
CorsSupportEnabled,
CorsPreflightRequestFilterEnabled,
GraphqlEnabled,
GraphqlRoutes,
SensitiveGraphqlOperationNames,
SensitiveGraphqlOperationTypes,
GraphqlBodyMaxLength,
GraphqlKeywords,
S2sTimeout,
TokenVersion,
CustomFirstPartyCaptchaEndpoint,
CustomFirstPartySensorEndpoint,
CustomFirstPartyXhrEndpoint,
UserAgentMaxLength,
RiskCookieMaxLength,
RiskCookieMinIterations,
RiskCookieMaxIterations,
AgenticTrustEnabled,
AgenticTrustMcpEndpointPath,
SecuredPxhdEnabled,
PxhdDomain,
JwtCookieName,
JwtCookieUserIdFieldName,
JwtCookieAdditionalFieldNames,
JwtHeaderName,
JwtHeaderUserIdFieldName,
JwtHeaderAdditionalFieldNames,
#[strum(props(sensitive = "true", static = "true"))]
LoggerAuthToken,
LoginCredentialsExtractionEnabled,
LoginCredentialsExtraction,
CredentialsIntelligenceVersion,
CompromisedCredentialsHeader,
SendRawUsernameOnAdditionalS2sActivity,
AdditionalS2sActivityEnabled,
AdditionalS2sActivityHeaderEnabled,
LoginSuccessfulReportingMethod,
LoginSuccessfulBodyRegex,
LoginSuccessfulHeaderName,
LoginSuccessfulHeaderValue,
LoginSuccessfulStatus,
}
impl PXConfigKey {
pub fn is_sensitive(&self) -> bool {
self.get_str("sensitive") == Some("true")
}
pub fn is_static(&self) -> bool {
self.get_str("static") == Some("true")
}
pub fn is_required(&self) -> bool {
self.get_str("required") == Some("true")
}
pub fn is_local(&self) -> bool {
self.get_str("local") == Some("true")
}
}
pub enum PXConfigValue<'a> {
Bool(bool),
U8(u8),
U16(u16),
U16Vec(&'a [u16]),
U32(u32),
USize(usize),
Str(&'a str),
StrVec(&'a [String]),
RegexVec(&'a [Regex]),
ModuleMode(PXModuleMode),
TokenVersion(TokenVersion),
CiEndpoints(&'a [PXCredentialEndpointConfig]),
}
impl fmt::Display for PXConfigValue<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Bool(v) => write!(f, "Bool({v})"),
Self::U8(v) => write!(f, "U8({v})"),
Self::U16(v) => write!(f, "U16({v})"),
Self::U16Vec(v) => write!(f, "U16Vec({v:?})"),
Self::U32(v) => write!(f, "U32({v})"),
Self::USize(v) => write!(f, "USize({v})"),
Self::Str(v) => write!(f, "Str({v})"),
Self::StrVec(v) => write!(f, "StrVec({v:?})"),
Self::RegexVec(v) => {
let pats: Vec<&str> = v.iter().map(Regex::as_str).collect();
write!(f, "RegexVec({pats:?})")
}
Self::ModuleMode(v) => write!(f, "ModuleMode({})", v.as_ref()),
Self::TokenVersion(v) => write!(f, "TokenVersion({})", v.as_ref()),
Self::CiEndpoints(v) => write!(f, "CiEndpoints({v:?})"),
}
}
}
impl Serialize for PXConfigValue<'_> {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
match self {
Self::Bool(v) => v.serialize(s),
Self::U8(v) => v.serialize(s),
Self::U16(v) => v.serialize(s),
Self::U16Vec(v) => v.serialize(s),
Self::U32(v) => v.serialize(s),
Self::USize(v) => v.serialize(s),
Self::Str(v) => v.serialize(s),
Self::StrVec(v) => v.serialize(s),
Self::RegexVec(v) => {
let pats: Vec<String> = v
.iter()
.map(|r| format!("{REGEX_PREFIX}{}", r.as_str()))
.collect();
pats.serialize(s)
}
Self::ModuleMode(v) => v.serialize(s),
Self::TokenVersion(v) => v.serialize(s),
Self::CiEndpoints(v) => v.serialize(s),
}
}
}
const REDACTED: &str = "***REDACTED***";
const REDACT_THRESHOLD: usize = 50;
const REDACT_TAIL_LEN: usize = 5;
pub(crate) const REGEX_PREFIX: &str = "_REGEXP ";
fn redact(s: &str) -> String {
let char_count = s.chars().count();
if char_count >= REDACT_THRESHOLD {
let tail: String = s.chars().skip(char_count - REDACT_TAIL_LEN).collect();
format!("{REDACTED}{tail}")
} else if char_count > 0 {
REDACTED.to_owned()
} else {
s.to_owned()
}
}
struct Redacted<'a>(&'a PXConfigValue<'a>);
impl Serialize for Redacted<'_> {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
match self.0 {
PXConfigValue::Bool(v) => redact(&v.to_string()).serialize(s),
PXConfigValue::U8(v) => redact(&v.to_string()).serialize(s),
PXConfigValue::U16(v) => redact(&v.to_string()).serialize(s),
PXConfigValue::U16Vec(v) => v
.iter()
.map(|n| redact(&n.to_string()))
.collect::<Vec<_>>()
.serialize(s),
PXConfigValue::U32(v) => redact(&v.to_string()).serialize(s),
PXConfigValue::USize(v) => redact(&v.to_string()).serialize(s),
PXConfigValue::Str(v) => redact(v).serialize(s),
PXConfigValue::StrVec(v) => v
.iter()
.map(|item| redact(item))
.collect::<Vec<_>>()
.serialize(s),
PXConfigValue::RegexVec(v) => v
.iter()
.map(|item| redact(item.as_str()))
.collect::<Vec<_>>()
.serialize(s),
PXConfigValue::ModuleMode(v) => redact(v.as_ref()).serialize(s),
PXConfigValue::TokenVersion(v) => redact(v.as_ref()).serialize(s),
PXConfigValue::CiEndpoints(v) => v.serialize(s),
}
}
}
impl Serialize for PXModuleMode {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(self.as_ref())
}
}
impl<'de> Deserialize<'de> for PXModuleMode {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let s = String::deserialize(d)?;
s.parse().map_err(de::Error::custom)
}
}
impl Serialize for TokenVersion {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_str(self.as_ref())
}
}
impl<'de> Deserialize<'de> for TokenVersion {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let s = String::deserialize(d)?;
s.parse().map_err(de::Error::custom)
}
}
pub struct PXConfig {
#[cfg(not(feature = "kv_store"))]
pub store: Option<ConfigStore>,
#[cfg(feature = "kv_store")]
pub store: Option<KVStore>,
pub app_id: String,
pub cookie_secret: String,
pub auth_token: String,
pub debug: bool,
pub blocking_score: u8,
pub module_enabled: bool,
pub module_mode: PXModuleMode,
pub sensitive_headers: Vec<String>,
pub sensitive_routes: Vec<String>,
pub sensitive_routes_regex: Vec<Regex>,
pub filter_by_route: Vec<String>,
pub filter_by_extension: Vec<String>,
pub filter_by_user_agent: Vec<String>,
pub filter_by_ip: Vec<String>,
pub filter_by_http_method: Vec<String>,
pub custom_cookie_header: String,
pub enforced_routes: Vec<String>,
pub monitored_routes: Vec<String>,
pub bypass_monitor_header: String,
pub first_party_enabled: bool,
pub custom_logo: String,
pub js_ref: String,
pub css_ref: String,
pub ip_headers: Vec<String>,
pub log_endpoint: String,
pub data_enrichment_header_name: String,
pub extracted_cookies: Vec<String>,
pub cors_support_enabled: bool,
pub cors_preflight_request_filter_enabled: bool,
pub graphql_enabled: bool,
pub graphql_routes: Vec<Regex>,
pub sensitive_graphql_operation_names: Vec<String>,
pub sensitive_graphql_operation_types: Vec<String>,
pub graphql_body_max_length: usize,
pub graphql_keywords: Vec<Regex>,
pub s2s_timeout: u32,
pub token_version: TokenVersion,
pub custom_first_party_captcha_endpoint: String,
pub custom_first_party_sensor_endpoint: String,
pub custom_first_party_xhr_endpoint: String,
pub user_agent_max_length: usize,
pub risk_cookie_max_length: usize,
pub risk_cookie_min_iterations: usize,
pub risk_cookie_max_iterations: usize,
pub agentic_trust_enabled: bool,
pub agentic_trust_mcp_endpoint_path: String,
pub logger_auth_token: String,
pub secured_pxhd_enabled: bool,
pub pxhd_domain: String,
pub jwt_cookie_name: String,
pub jwt_cookie_user_id_field_name: String,
pub jwt_cookie_additional_field_names: Vec<String>,
pub jwt_header_name: String,
pub jwt_header_user_id_field_name: String,
pub jwt_header_additional_field_names: Vec<String>,
pub human_sapi_host: String,
pub human_sapi_backend: String,
pub human_collector_host: String,
pub human_collector_backend: String,
pub human_client_host: String,
pub human_client_backend: String,
pub human_captcha_host: String,
pub human_captcha_backend: String,
pub login_credentials_extraction_enabled: bool,
pub login_credentials_extraction: Vec<PXCredentialEndpointConfig>,
pub prepared_ci_endpoints: Vec<PXPreparedCredentialEndpoint>,
pub credentials_intelligence_version: String,
pub compromised_credentials_header: String,
pub send_raw_username_on_additional_s2s_activity: bool,
pub additional_s2s_activity_enabled: bool,
pub additional_s2s_activity_header_enabled: bool,
pub login_successful_reporting_method: String,
pub login_successful_body_regex: String,
pub login_successful_header_name: String,
pub login_successful_header_value: String,
pub login_successful_status: Vec<u16>,
pub enrich_params_fn: Option<PXEnrichCustomParamsFn>,
pub is_sensitive_request_fn: Option<PXIsSensitiveRequestFn>,
pub is_enforced_request_fn: Option<PXIsEnforcedRequestFn>,
pub is_monitored_request_fn: Option<PXIsMonitoredRequestFn>,
pub is_filtered_request_fn: Option<PXIsFilteredRequestFn>,
pub additional_activity_handler_fn: Option<PXAdditionalActivityHandlerFn>,
pub cors_custom_preflight_handler_fn: Option<PXCorsCustomPreflightHandlerFn>,
pub cors_create_custom_block_response_headers_fn: Option<PXCorsCustomBlockResponseHeadersFn>,
pub ci_extract_credentials_fn: Option<PXExtractCredentialsFn>,
pub ci_login_successful_fn: Option<PXLoginSuccessfulFn>,
}
impl Default for PXConfig {
fn default() -> Self {
Self {
store: None,
app_id: String::new(),
cookie_secret: String::new(),
auth_token: String::new(),
debug: false,
blocking_score: 100,
module_enabled: false,
module_mode: PXModuleMode::Monitor,
sensitive_headers: vec!["Cookie".to_owned(), "Cookies".to_owned()],
sensitive_routes: Vec::new(),
sensitive_routes_regex: Vec::new(),
filter_by_route: Vec::new(),
filter_by_extension: WHITELIST_EXT.iter().map(|ext| (*ext).to_owned()).collect(),
filter_by_user_agent: Vec::new(),
filter_by_ip: Vec::new(),
filter_by_http_method: Vec::new(),
custom_cookie_header: CUSTOM_COOKIE_HEADER.to_owned(),
enforced_routes: Vec::new(),
monitored_routes: Vec::new(),
bypass_monitor_header: "x-px-block".to_owned(),
first_party_enabled: true,
custom_logo: String::new(),
js_ref: String::new(),
css_ref: String::new(),
human_sapi_host: default_human_sapi_host(""),
human_sapi_backend: "human_sapi".to_owned(),
human_collector_host: default_human_collector_host(""),
human_collector_backend: "human_collector".to_owned(),
human_client_host: "client.perimeterx.net".to_owned(),
human_client_backend: "human_client".to_owned(),
human_captcha_host: "captcha.px-cdn.net".to_owned(),
human_captcha_backend: "human_captcha".to_owned(),
ip_headers: Vec::new(),
log_endpoint: String::new(),
data_enrichment_header_name: String::new(),
extracted_cookies: Vec::new(),
cors_support_enabled: false,
cors_preflight_request_filter_enabled: false,
graphql_enabled: false,
graphql_routes: default_graphql_routes(),
sensitive_graphql_operation_names: Vec::new(),
sensitive_graphql_operation_types: Vec::new(),
graphql_body_max_length: GRAPHQL_BODY_MAX_LENGTH,
graphql_keywords: Vec::new(),
s2s_timeout: 2000,
token_version: TokenVersion::V3,
custom_first_party_captcha_endpoint: String::new(),
custom_first_party_sensor_endpoint: String::new(),
custom_first_party_xhr_endpoint: String::new(),
user_agent_max_length: 8528,
risk_cookie_max_length: 2048,
risk_cookie_min_iterations: 500,
risk_cookie_max_iterations: 5000,
agentic_trust_enabled: false,
agentic_trust_mcp_endpoint_path: MCP_ENDPOINT_PATH.to_owned(),
logger_auth_token: String::new(),
secured_pxhd_enabled: false,
pxhd_domain: String::new(),
jwt_cookie_name: String::new(),
jwt_cookie_user_id_field_name: String::new(),
jwt_cookie_additional_field_names: Vec::new(),
jwt_header_name: String::new(),
jwt_header_user_id_field_name: String::new(),
jwt_header_additional_field_names: Vec::new(),
login_credentials_extraction_enabled: false,
login_credentials_extraction: Vec::new(),
prepared_ci_endpoints: Vec::new(),
credentials_intelligence_version: "both".to_owned(),
compromised_credentials_header:
crate::modules::pxconstants::DEFAULT_COMPROMISED_CREDENTIALS_HEADER.to_owned(),
send_raw_username_on_additional_s2s_activity: false,
additional_s2s_activity_enabled: true,
additional_s2s_activity_header_enabled: false,
login_successful_reporting_method: "status".to_owned(),
login_successful_body_regex: String::new(),
login_successful_header_name: String::new(),
login_successful_header_value: String::new(),
login_successful_status: vec![200],
enrich_params_fn: None,
is_sensitive_request_fn: None,
is_enforced_request_fn: None,
is_monitored_request_fn: None,
is_filtered_request_fn: None,
additional_activity_handler_fn: None,
cors_custom_preflight_handler_fn: None,
cors_create_custom_block_response_headers_fn: None,
ci_extract_credentials_fn: None,
ci_login_successful_fn: None,
}
}
}
fn compile_ci_path_regex(path: &str) -> Option<Regex> {
let raw = path.strip_prefix(REGEX_PREFIX).unwrap_or(path);
match Regex::new(raw) {
Ok(re) => Some(re),
Err(e) => {
px_error!("Invalid CI endpoint regex {:?}: {}", raw, e);
None
}
}
}
pub(crate) fn rebuild_prepared_ci_endpoints(conf: &mut PXConfig) {
conf.prepared_ci_endpoints = conf
.login_credentials_extraction
.iter()
.map(|config| {
let path_regex = if config.path_type.eq_ignore_ascii_case("regex") {
compile_ci_path_regex(&config.path)
} else {
None
};
PXPreparedCredentialEndpoint {
config: config.clone(),
path_regex,
}
})
.collect();
}
fn is_empty_ci_endpoints_value(v: &serde_json::Value) -> bool {
match v {
serde_json::Value::Object(map) => map.is_empty(),
serde_json::Value::Array(arr) => {
arr.is_empty()
|| arr
.iter()
.all(|item| item.as_object().is_some_and(serde_json::Map::is_empty))
}
_ => false,
}
}
fn parse_ci_endpoints_from_value(
v: &serde_json::Value,
) -> Result<Vec<PXCredentialEndpointConfig>, serde_json::Error> {
if is_empty_ci_endpoints_value(v) {
return Ok(Vec::new());
}
if let Some(raw) = v.as_str() {
if raw.trim().is_empty() {
return Ok(Vec::new());
}
if let Ok(parsed) = serde_json::from_str::<serde_json::Value>(raw) {
return parse_ci_endpoints_from_value(&parsed);
}
serde_json::from_str(raw)
} else {
serde_json::from_value(v.clone())
}
}
fn parse_ci_endpoints(raw: &str) -> Vec<PXCredentialEndpointConfig> {
match parse_ci_endpoints_from_value(&serde_json::Value::String(raw.to_owned())) {
Ok(endpoints) => endpoints,
Err(e) => {
px_error!(
"failed to parse px_login_credentials_extraction from store: {}",
e
);
Vec::new()
}
}
}
fn parse_status_vec(raw: &str) -> Vec<u16> {
if let Ok(vec) = serde_json::from_str::<Vec<u16>>(raw) {
return vec;
}
raw.split(',')
.filter_map(|s| s.trim().parse::<u16>().ok())
.collect()
}
fn default_graphql_routes() -> Vec<Regex> {
Regex::new("^/graphql$")
.map(|r| vec![r])
.unwrap_or_default()
}
pub(crate) fn parse_vec(input: &str) -> Vec<String> {
if let Ok(vec) = serde_json::from_str::<Vec<String>>(input) {
return vec.into_iter().filter(|s| !s.is_empty()).collect();
}
input
.split(',')
.map(|s| s.trim())
.filter(|s| !s.is_empty())
.map(|s| s.to_owned())
.collect()
}
fn parse_regex_vec(key_name: &str, input: &str) -> Vec<Regex> {
let items: Vec<String> = if let Ok(vec) = serde_json::from_str::<Vec<String>>(input) {
vec.into_iter().filter(|s| !s.is_empty()).collect()
} else {
input
.split(',')
.map(|s| s.trim())
.filter(|s| !s.is_empty())
.map(|s| s.to_owned())
.collect()
};
items
.iter()
.filter_map(|s| match Regex::new(s) {
Ok(r) => Some(r),
Err(_) => {
px_error!("[{}] Invalid regex {:?}", key_name, s);
None
}
})
.collect()
}
#[cfg(feature = "kv_store")]
pub(crate) fn json_value_to_raw_store(v: &serde_json::Value) -> String {
match v {
serde_json::Value::String(s) => s.clone(),
serde_json::Value::Array(arr) => {
let all_strings = arr.iter().all(serde_json::Value::is_string);
if all_strings {
arr.iter()
.filter_map(|item| item.as_str())
.collect::<Vec<_>>()
.join(",")
} else {
v.to_string()
}
}
other => other.to_string(),
}
}
pub(crate) fn strip_json_quotes(s: &str) -> &str {
s.strip_prefix('"')
.and_then(|inner| inner.strip_suffix('"'))
.unwrap_or(s)
}
fn parse_string(input: &str) -> String {
if input.eq_ignore_ascii_case("none") {
String::new()
} else {
input.to_owned()
}
}
fn default_human_sapi_host(app_id: &str) -> String {
format!("sapi-{app_id}.perimeterx.net")
}
fn default_human_collector_host(app_id: &str) -> String {
format!("collector-{app_id}.perimeterx.net")
}
fn compile_regex_vec<I, S>(patterns: I, key_name: &str) -> Vec<Regex>
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
patterns
.into_iter()
.filter_map(|p| {
let raw = p.as_ref().strip_prefix(REGEX_PREFIX).unwrap_or(p.as_ref());
match Regex::new(raw) {
Ok(r) => Some(r),
Err(_) => {
px_error!("Invalid regex {:?} for key {}", raw, key_name);
None
}
}
})
.collect()
}
#[cfg(any(target_arch = "wasm32", test))]
const SECRET_STORE_KEYS: &[PXConfigKey] = &[
PXConfigKey::AppId,
PXConfigKey::CookieSecret,
PXConfigKey::AuthToken,
PXConfigKey::LoggerAuthToken,
];
#[cfg(any(target_arch = "wasm32", test))]
pub(crate) fn secret_store_keys() -> &'static [PXConfigKey] {
SECRET_STORE_KEYS
}
#[cfg(any(target_arch = "wasm32", test))]
pub(crate) fn apply_secret_values(conf: &mut PXConfig, secrets: &[(PXConfigKey, &str)]) {
for (key, raw) in secrets {
conf.set_from_raw(*key, raw);
}
}
fn config_value_is_set(value: &PXConfigValue<'_>) -> bool {
match value {
PXConfigValue::Str(v) => !v.is_empty(),
PXConfigValue::StrVec(v) => !v.is_empty(),
PXConfigValue::RegexVec(v) => !v.is_empty(),
PXConfigValue::U16Vec(v) => !v.is_empty(),
PXConfigValue::CiEndpoints(v) => !v.is_empty(),
PXConfigValue::Bool(_)
| PXConfigValue::U8(_)
| PXConfigValue::U16(_)
| PXConfigValue::U32(_)
| PXConfigValue::USize(_)
| PXConfigValue::ModuleMode(_)
| PXConfigValue::TokenVersion(_) => true,
}
}
pub(crate) fn load_secrets_from_store(conf: &mut PXConfig, secret_store_name: &str) {
if secret_store_name.is_empty() {
return;
}
#[cfg(not(target_arch = "wasm32"))]
{
let _ = conf;
}
#[cfg(target_arch = "wasm32")]
let secret_store = match SecretStore::open(secret_store_name) {
Ok(store) => store,
Err(e) => {
px_error!("Failed to open Secret Store {:?}: {}", secret_store_name, e);
return;
}
};
#[cfg(target_arch = "wasm32")]
{
let mut overlay = Vec::with_capacity(secret_store_keys().len());
for key in secret_store_keys() {
let secret_name = key.as_ref();
match secret_store.try_get(secret_name) {
Ok(Some(secret)) => match secret.try_plaintext() {
Ok(plaintext) => {
if let Ok(value) = std::str::from_utf8(plaintext.as_ref()) {
overlay.push((*key, value.to_owned()));
} else {
px_error!(
"Secret {:?} in Secret Store {:?} is not valid UTF-8",
secret_name,
secret_store_name
);
}
}
Err(e) => {
px_error!(
"Failed to decrypt secret {:?} in Secret Store {:?}: {}",
secret_name,
secret_store_name,
e
);
}
},
Ok(None) => {}
Err(e) => {
px_error!(
"Failed to lookup secret {:?} in Secret Store {:?}: {}",
secret_name,
secret_store_name,
e
);
}
}
}
let overlay_refs: Vec<(PXConfigKey, &str)> = overlay
.iter()
.map(|(key, value)| (*key, value.as_str()))
.collect();
apply_secret_values(conf, &overlay_refs);
}
}
pub(crate) fn recompute_derived(conf: &mut PXConfig, previous_app_id: &str) {
let previous_sapi_host = default_human_sapi_host(previous_app_id);
if conf.human_sapi_host.is_empty() || conf.human_sapi_host == previous_sapi_host {
conf.human_sapi_host = default_human_sapi_host(&conf.app_id);
}
let previous_collector_host = default_human_collector_host(previous_app_id);
if conf.human_collector_host.is_empty() || conf.human_collector_host == previous_collector_host
{
conf.human_collector_host = default_human_collector_host(&conf.app_id);
}
}
impl Serialize for PXConfig {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
let entries: Vec<_> = self.fields().filter(|(k, _)| !k.is_local()).collect();
let mut map = s.serialize_map(Some(entries.len()))?;
for (key, value) in entries {
if key.is_sensitive() {
map.serialize_entry(key.as_ref(), &Redacted(&value))?;
} else {
map.serialize_entry(key.as_ref(), &value)?;
}
}
map.end()
}
}
impl<'de> Deserialize<'de> for PXConfig {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
struct PXConfigVisitor;
impl<'de> Visitor<'de> for PXConfigVisitor {
type Value = PXConfig;
fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("a PXConfig map keyed by PXConfigKey names")
}
fn visit_map<M: MapAccess<'de>>(self, mut map: M) -> Result<PXConfig, M::Error> {
let mut out = PXConfig::default();
let mut seen: HashSet<PXConfigKey> = HashSet::new();
while let Some(key) = map.next_key::<String>()? {
let variant = PXConfigKey::iter()
.find(|v| v.as_ref() == key)
.ok_or_else(|| de::Error::custom(format!("unknown field `{key}`")))?;
if !seen.insert(variant) {
return Err(de::Error::custom(format!("duplicate field `{key}`")));
}
out.set_from(variant, &mut map)?;
}
if let Some(missing) =
PXConfigKey::iter().find(|v| !v.is_local() && !seen.contains(v))
{
return Err(de::Error::custom(format!(
"missing field `{}`",
missing.as_ref()
)));
}
rebuild_prepared_ci_endpoints(&mut out);
recompute_derived(&mut out, "");
Ok(out)
}
}
d.deserialize_map(PXConfigVisitor)
}
}
impl PXConfig {
pub fn get_module_mode(&self) -> PXModuleMode {
self.module_mode
}
pub fn new(config_store_name: &str, secret_store_name: &str) -> Self {
let mut conf = PXConfig::default();
#[cfg(feature = "kv_store")]
let confg_store = {
match KVStore::open(config_store_name) {
Ok(Some(kv)) => kv,
Ok(None) => {
px_error!(
"KVStore {:?} not found; falling back to default PXConfig",
config_store_name
);
return Self::default();
}
Err(e) => {
px_error!(
"Failed to open KVStore {:?}: {:?}; falling back to default PXConfig",
config_store_name,
e
);
return Self::default();
}
}
};
#[cfg(not(feature = "kv_store"))]
let confg_store = ConfigStore::open(config_store_name);
for key in PXConfigKey::iter() {
if let Some(raw) = confg_store.get(key.as_ref()) {
conf.set_from_raw(key, &raw);
}
}
recompute_derived(&mut conf, "");
conf.store = Some(confg_store);
let app_id_before_secrets = conf.app_id.clone();
load_secrets_from_store(&mut conf, secret_store_name);
rebuild_prepared_ci_endpoints(&mut conf);
recompute_derived(&mut conf, &app_id_before_secrets);
conf.validate_required_fields();
conf
}
#[doc(hidden)]
pub fn update_from_json(&mut self, cfg: &serde_json::Value) {
let config_obj = cfg.get("configValue").unwrap_or(cfg);
let previous_app_id = self.app_id.clone();
let mut config_found = false;
for key in PXConfigKey::iter() {
let Some(v) = config_obj.get(key.as_ref()).filter(|v| !v.is_null()) else {
continue;
};
config_found = true;
if let Err(e) = self.set_from_value(key, v) {
px_error!("update_from_json: bad value for {}: {}", key.as_ref(), e);
}
#[cfg(feature = "kv_store")]
if let Some(store) = self.store.as_mut() {
let store_value = json_value_to_raw_store(v);
let _ = store.insert(key.as_ref(), store_value);
}
}
if !config_found {
px_error!("update_from_json: config not found in body");
}
rebuild_prepared_ci_endpoints(self);
recompute_derived(self, &previous_app_id);
self.validate_required_fields();
}
pub fn get(&self, key: PXConfigKey) -> PXConfigValue<'_> {
self.get_value(key)
}
pub fn fields(&self) -> impl ExactSizeIterator<Item = (PXConfigKey, PXConfigValue<'_>)> {
PXConfigKey::iter().map(|k| (k, self.get_value(k)))
}
pub fn get_field(&self, name: impl AsRef<str>) -> Option<PXConfigValue<'_>> {
let name = name.as_ref();
self.fields()
.find_map(|(k, v)| (k.as_ref() == name).then_some(v))
}
pub(crate) fn build_static_json(&self) -> serde_json::Value {
let static_fields: Vec<(PXConfigKey, PXConfigValue<'_>)> =
self.fields().filter(|(k, _)| k.is_static()).collect();
let mut map = serde_json::Map::with_capacity(static_fields.len());
for (key, value) in static_fields {
let json_value = if key.is_sensitive() {
serde_json::to_value(Redacted(&value)).unwrap_or_default()
} else {
serde_json::to_value(&value).unwrap_or_default()
};
map.insert(key.as_ref().to_owned(), json_value);
}
serde_json::Value::Object(map)
}
pub(crate) fn build_config_json(&self) -> serde_json::Value {
let entries: Vec<(PXConfigKey, PXConfigValue<'_>)> =
self.fields().filter(|(k, _)| !k.is_local()).collect();
let mut map = serde_json::Map::with_capacity(entries.len());
for (key, value) in entries {
let json_value = if key.is_sensitive() {
serde_json::to_value(Redacted(&value)).unwrap_or_default()
} else {
serde_json::to_value(&value).unwrap_or_default()
};
map.insert(key.as_ref().to_owned(), json_value);
}
serde_json::Value::Object(map)
}
fn missing_required_fields(&self) -> Vec<PXConfigKey> {
self.fields()
.filter_map(|(key, value)| {
(key.is_required() && !config_value_is_set(&value)).then_some(key)
})
.collect()
}
fn validate_required_fields(&self) {
for key in self.missing_required_fields() {
px_error!(
"Required PX configuration key {} is missing or empty",
key.as_ref()
);
}
}
fn get_value(&self, key: PXConfigKey) -> PXConfigValue<'_> {
match key {
PXConfigKey::AppId => PXConfigValue::Str(&self.app_id),
PXConfigKey::CookieSecret => PXConfigValue::Str(&self.cookie_secret),
PXConfigKey::AuthToken => PXConfigValue::Str(&self.auth_token),
PXConfigKey::Debug => PXConfigValue::Bool(self.debug),
PXConfigKey::BlockingScore => PXConfigValue::U8(self.blocking_score),
PXConfigKey::ModuleEnabled => PXConfigValue::Bool(self.module_enabled),
PXConfigKey::ModuleMode => PXConfigValue::ModuleMode(self.module_mode),
PXConfigKey::SensitiveHeaders => PXConfigValue::StrVec(&self.sensitive_headers),
PXConfigKey::SensitiveRoutes => PXConfigValue::StrVec(&self.sensitive_routes),
PXConfigKey::SensitiveRoutesRegex => {
PXConfigValue::RegexVec(&self.sensitive_routes_regex)
}
PXConfigKey::FilterByRoute => PXConfigValue::StrVec(&self.filter_by_route),
PXConfigKey::FilterByExtension => PXConfigValue::StrVec(&self.filter_by_extension),
PXConfigKey::FilterByUserAgent => PXConfigValue::StrVec(&self.filter_by_user_agent),
PXConfigKey::FilterByIp => PXConfigValue::StrVec(&self.filter_by_ip),
PXConfigKey::FilterByHttpMethod => PXConfigValue::StrVec(&self.filter_by_http_method),
PXConfigKey::CustomCookieHeader => PXConfigValue::Str(&self.custom_cookie_header),
PXConfigKey::EnforcedRoutes => PXConfigValue::StrVec(&self.enforced_routes),
PXConfigKey::MonitoredRoutes => PXConfigValue::StrVec(&self.monitored_routes),
PXConfigKey::BypassMonitorHeader => PXConfigValue::Str(&self.bypass_monitor_header),
PXConfigKey::FirstPartyEnabled => PXConfigValue::Bool(self.first_party_enabled),
PXConfigKey::CustomLogo => PXConfigValue::Str(&self.custom_logo),
PXConfigKey::JsRef => PXConfigValue::Str(&self.js_ref),
PXConfigKey::CssRef => PXConfigValue::Str(&self.css_ref),
PXConfigKey::HumanSapiHost => PXConfigValue::Str(&self.human_sapi_host),
PXConfigKey::HumanSapiBackend => PXConfigValue::Str(&self.human_sapi_backend),
PXConfigKey::HumanCollectorHost => PXConfigValue::Str(&self.human_collector_host),
PXConfigKey::HumanCollectorBackend => PXConfigValue::Str(&self.human_collector_backend),
PXConfigKey::HumanClientHost => PXConfigValue::Str(&self.human_client_host),
PXConfigKey::HumanClientBackend => PXConfigValue::Str(&self.human_client_backend),
PXConfigKey::HumanCaptchaHost => PXConfigValue::Str(&self.human_captcha_host),
PXConfigKey::HumanCaptchaBackend => PXConfigValue::Str(&self.human_captcha_backend),
PXConfigKey::IpHeaders => PXConfigValue::StrVec(&self.ip_headers),
PXConfigKey::LogEndpoint => PXConfigValue::Str(&self.log_endpoint),
PXConfigKey::DataEnrichmentHeaderName => {
PXConfigValue::Str(&self.data_enrichment_header_name)
}
PXConfigKey::ExtractedCookies => PXConfigValue::StrVec(&self.extracted_cookies),
PXConfigKey::CorsSupportEnabled => PXConfigValue::Bool(self.cors_support_enabled),
PXConfigKey::CorsPreflightRequestFilterEnabled => {
PXConfigValue::Bool(self.cors_preflight_request_filter_enabled)
}
PXConfigKey::GraphqlEnabled => PXConfigValue::Bool(self.graphql_enabled),
PXConfigKey::GraphqlRoutes => PXConfigValue::RegexVec(&self.graphql_routes),
PXConfigKey::SensitiveGraphqlOperationNames => {
PXConfigValue::StrVec(&self.sensitive_graphql_operation_names)
}
PXConfigKey::SensitiveGraphqlOperationTypes => {
PXConfigValue::StrVec(&self.sensitive_graphql_operation_types)
}
PXConfigKey::GraphqlBodyMaxLength => PXConfigValue::USize(self.graphql_body_max_length),
PXConfigKey::GraphqlKeywords => PXConfigValue::RegexVec(&self.graphql_keywords),
PXConfigKey::S2sTimeout => PXConfigValue::U32(self.s2s_timeout),
PXConfigKey::TokenVersion => PXConfigValue::TokenVersion(self.token_version),
PXConfigKey::CustomFirstPartyCaptchaEndpoint => {
PXConfigValue::Str(&self.custom_first_party_captcha_endpoint)
}
PXConfigKey::CustomFirstPartySensorEndpoint => {
PXConfigValue::Str(&self.custom_first_party_sensor_endpoint)
}
PXConfigKey::CustomFirstPartyXhrEndpoint => {
PXConfigValue::Str(&self.custom_first_party_xhr_endpoint)
}
PXConfigKey::UserAgentMaxLength => PXConfigValue::USize(self.user_agent_max_length),
PXConfigKey::RiskCookieMaxLength => PXConfigValue::USize(self.risk_cookie_max_length),
PXConfigKey::RiskCookieMinIterations => {
PXConfigValue::USize(self.risk_cookie_min_iterations)
}
PXConfigKey::RiskCookieMaxIterations => {
PXConfigValue::USize(self.risk_cookie_max_iterations)
}
PXConfigKey::AgenticTrustEnabled => PXConfigValue::Bool(self.agentic_trust_enabled),
PXConfigKey::AgenticTrustMcpEndpointPath => {
PXConfigValue::Str(&self.agentic_trust_mcp_endpoint_path)
}
PXConfigKey::SecuredPxhdEnabled => PXConfigValue::Bool(self.secured_pxhd_enabled),
PXConfigKey::PxhdDomain => PXConfigValue::Str(&self.pxhd_domain),
PXConfigKey::JwtCookieName => PXConfigValue::Str(&self.jwt_cookie_name),
PXConfigKey::JwtCookieUserIdFieldName => {
PXConfigValue::Str(&self.jwt_cookie_user_id_field_name)
}
PXConfigKey::JwtCookieAdditionalFieldNames => {
PXConfigValue::StrVec(&self.jwt_cookie_additional_field_names)
}
PXConfigKey::JwtHeaderName => PXConfigValue::Str(&self.jwt_header_name),
PXConfigKey::JwtHeaderUserIdFieldName => {
PXConfigValue::Str(&self.jwt_header_user_id_field_name)
}
PXConfigKey::JwtHeaderAdditionalFieldNames => {
PXConfigValue::StrVec(&self.jwt_header_additional_field_names)
}
PXConfigKey::LoggerAuthToken => PXConfigValue::Str(&self.logger_auth_token),
PXConfigKey::LoginCredentialsExtractionEnabled => {
PXConfigValue::Bool(self.login_credentials_extraction_enabled)
}
PXConfigKey::LoginCredentialsExtraction => {
PXConfigValue::CiEndpoints(&self.login_credentials_extraction)
}
PXConfigKey::CredentialsIntelligenceVersion => {
PXConfigValue::Str(&self.credentials_intelligence_version)
}
PXConfigKey::CompromisedCredentialsHeader => {
PXConfigValue::Str(&self.compromised_credentials_header)
}
PXConfigKey::SendRawUsernameOnAdditionalS2sActivity => {
PXConfigValue::Bool(self.send_raw_username_on_additional_s2s_activity)
}
PXConfigKey::AdditionalS2sActivityEnabled => {
PXConfigValue::Bool(self.additional_s2s_activity_enabled)
}
PXConfigKey::AdditionalS2sActivityHeaderEnabled => {
PXConfigValue::Bool(self.additional_s2s_activity_header_enabled)
}
PXConfigKey::LoginSuccessfulReportingMethod => {
PXConfigValue::Str(&self.login_successful_reporting_method)
}
PXConfigKey::LoginSuccessfulBodyRegex => {
PXConfigValue::Str(&self.login_successful_body_regex)
}
PXConfigKey::LoginSuccessfulHeaderName => {
PXConfigValue::Str(&self.login_successful_header_name)
}
PXConfigKey::LoginSuccessfulHeaderValue => {
PXConfigValue::Str(&self.login_successful_header_value)
}
PXConfigKey::LoginSuccessfulStatus => {
PXConfigValue::U16Vec(&self.login_successful_status)
}
}
}
fn set_from<'de, M: MapAccess<'de>>(
&mut self,
key: PXConfigKey,
map: &mut M,
) -> Result<(), M::Error> {
match key {
PXConfigKey::AppId => self.app_id = map.next_value()?,
PXConfigKey::CookieSecret => self.cookie_secret = map.next_value()?,
PXConfigKey::AuthToken => self.auth_token = map.next_value()?,
PXConfigKey::Debug => self.debug = map.next_value()?,
PXConfigKey::BlockingScore => self.blocking_score = map.next_value()?,
PXConfigKey::ModuleEnabled => self.module_enabled = map.next_value()?,
PXConfigKey::ModuleMode => self.module_mode = map.next_value()?,
PXConfigKey::SensitiveHeaders => self.sensitive_headers = map.next_value()?,
PXConfigKey::SensitiveRoutes => self.sensitive_routes = map.next_value()?,
PXConfigKey::SensitiveRoutesRegex => {
let pats: Vec<String> = map.next_value()?;
self.sensitive_routes_regex = compile_regex_vec(pats, key.as_ref());
}
PXConfigKey::FilterByRoute => self.filter_by_route = map.next_value()?,
PXConfigKey::FilterByExtension => self.filter_by_extension = map.next_value()?,
PXConfigKey::FilterByUserAgent => self.filter_by_user_agent = map.next_value()?,
PXConfigKey::FilterByIp => self.filter_by_ip = map.next_value()?,
PXConfigKey::FilterByHttpMethod => self.filter_by_http_method = map.next_value()?,
PXConfigKey::CustomCookieHeader => self.custom_cookie_header = map.next_value()?,
PXConfigKey::EnforcedRoutes => self.enforced_routes = map.next_value()?,
PXConfigKey::MonitoredRoutes => self.monitored_routes = map.next_value()?,
PXConfigKey::BypassMonitorHeader => self.bypass_monitor_header = map.next_value()?,
PXConfigKey::FirstPartyEnabled => self.first_party_enabled = map.next_value()?,
PXConfigKey::CustomLogo => self.custom_logo = map.next_value()?,
PXConfigKey::JsRef => self.js_ref = map.next_value()?,
PXConfigKey::CssRef => self.css_ref = map.next_value()?,
PXConfigKey::HumanSapiHost => self.human_sapi_host = map.next_value()?,
PXConfigKey::HumanSapiBackend => self.human_sapi_backend = map.next_value()?,
PXConfigKey::HumanCollectorHost => self.human_collector_host = map.next_value()?,
PXConfigKey::HumanCollectorBackend => {
self.human_collector_backend = map.next_value()?
}
PXConfigKey::HumanClientHost => self.human_client_host = map.next_value()?,
PXConfigKey::HumanClientBackend => self.human_client_backend = map.next_value()?,
PXConfigKey::HumanCaptchaHost => self.human_captcha_host = map.next_value()?,
PXConfigKey::HumanCaptchaBackend => self.human_captcha_backend = map.next_value()?,
PXConfigKey::IpHeaders => self.ip_headers = map.next_value()?,
PXConfigKey::LogEndpoint => self.log_endpoint = map.next_value()?,
PXConfigKey::DataEnrichmentHeaderName => {
self.data_enrichment_header_name = map.next_value()?
}
PXConfigKey::ExtractedCookies => self.extracted_cookies = map.next_value()?,
PXConfigKey::CorsSupportEnabled => self.cors_support_enabled = map.next_value()?,
PXConfigKey::CorsPreflightRequestFilterEnabled => {
self.cors_preflight_request_filter_enabled = map.next_value()?
}
PXConfigKey::GraphqlEnabled => self.graphql_enabled = map.next_value()?,
PXConfigKey::GraphqlRoutes => {
let pats: Vec<String> = map.next_value()?;
self.graphql_routes = compile_regex_vec(pats, key.as_ref());
}
PXConfigKey::SensitiveGraphqlOperationNames => {
self.sensitive_graphql_operation_names = map.next_value()?
}
PXConfigKey::SensitiveGraphqlOperationTypes => {
self.sensitive_graphql_operation_types = map.next_value()?
}
PXConfigKey::GraphqlBodyMaxLength => self.graphql_body_max_length = map.next_value()?,
PXConfigKey::GraphqlKeywords => {
let pats: Vec<String> = map.next_value()?;
self.graphql_keywords = compile_regex_vec(pats, key.as_ref());
}
PXConfigKey::S2sTimeout => self.s2s_timeout = map.next_value()?,
PXConfigKey::TokenVersion => self.token_version = map.next_value()?,
PXConfigKey::CustomFirstPartyCaptchaEndpoint => {
self.custom_first_party_captcha_endpoint = map.next_value()?
}
PXConfigKey::CustomFirstPartySensorEndpoint => {
self.custom_first_party_sensor_endpoint = map.next_value()?
}
PXConfigKey::CustomFirstPartyXhrEndpoint => {
self.custom_first_party_xhr_endpoint = map.next_value()?
}
PXConfigKey::UserAgentMaxLength => self.user_agent_max_length = map.next_value()?,
PXConfigKey::RiskCookieMaxLength => self.risk_cookie_max_length = map.next_value()?,
PXConfigKey::RiskCookieMinIterations => {
self.risk_cookie_min_iterations = map.next_value()?
}
PXConfigKey::RiskCookieMaxIterations => {
self.risk_cookie_max_iterations = map.next_value()?
}
PXConfigKey::AgenticTrustEnabled => self.agentic_trust_enabled = map.next_value()?,
PXConfigKey::AgenticTrustMcpEndpointPath => {
self.agentic_trust_mcp_endpoint_path = map.next_value()?
}
PXConfigKey::SecuredPxhdEnabled => self.secured_pxhd_enabled = map.next_value()?,
PXConfigKey::PxhdDomain => self.pxhd_domain = map.next_value()?,
PXConfigKey::JwtCookieName => self.jwt_cookie_name = map.next_value()?,
PXConfigKey::JwtCookieUserIdFieldName => {
self.jwt_cookie_user_id_field_name = map.next_value()?
}
PXConfigKey::JwtCookieAdditionalFieldNames => {
self.jwt_cookie_additional_field_names = map.next_value()?
}
PXConfigKey::JwtHeaderName => self.jwt_header_name = map.next_value()?,
PXConfigKey::JwtHeaderUserIdFieldName => {
self.jwt_header_user_id_field_name = map.next_value()?
}
PXConfigKey::JwtHeaderAdditionalFieldNames => {
self.jwt_header_additional_field_names = map.next_value()?
}
PXConfigKey::LoggerAuthToken => self.logger_auth_token = map.next_value()?,
PXConfigKey::LoginCredentialsExtractionEnabled => {
self.login_credentials_extraction_enabled = map.next_value()?
}
PXConfigKey::LoginCredentialsExtraction => {
let v: serde_json::Value = map.next_value()?;
self.login_credentials_extraction =
parse_ci_endpoints_from_value(&v).map_err(serde::de::Error::custom)?;
rebuild_prepared_ci_endpoints(self);
}
PXConfigKey::CredentialsIntelligenceVersion => {
self.credentials_intelligence_version = map.next_value()?
}
PXConfigKey::CompromisedCredentialsHeader => {
self.compromised_credentials_header = map.next_value()?
}
PXConfigKey::SendRawUsernameOnAdditionalS2sActivity => {
self.send_raw_username_on_additional_s2s_activity = map.next_value()?
}
PXConfigKey::AdditionalS2sActivityEnabled => {
self.additional_s2s_activity_enabled = map.next_value()?
}
PXConfigKey::AdditionalS2sActivityHeaderEnabled => {
self.additional_s2s_activity_header_enabled = map.next_value()?
}
PXConfigKey::LoginSuccessfulReportingMethod => {
self.login_successful_reporting_method = map.next_value()?
}
PXConfigKey::LoginSuccessfulBodyRegex => {
self.login_successful_body_regex = map.next_value()?
}
PXConfigKey::LoginSuccessfulHeaderName => {
self.login_successful_header_name = map.next_value()?
}
PXConfigKey::LoginSuccessfulHeaderValue => {
self.login_successful_header_value = map.next_value()?
}
PXConfigKey::LoginSuccessfulStatus => {
self.login_successful_status = map.next_value()?
}
}
Ok(())
}
pub(crate) fn set_from_value(
&mut self,
key: PXConfigKey,
v: &serde_json::Value,
) -> Result<(), serde_json::Error> {
match key {
PXConfigKey::AppId => self.app_id = serde_json::from_value(v.clone())?,
PXConfigKey::CookieSecret => self.cookie_secret = serde_json::from_value(v.clone())?,
PXConfigKey::AuthToken => self.auth_token = serde_json::from_value(v.clone())?,
PXConfigKey::Debug => self.debug = serde_json::from_value(v.clone())?,
PXConfigKey::BlockingScore => self.blocking_score = serde_json::from_value(v.clone())?,
PXConfigKey::ModuleEnabled => self.module_enabled = serde_json::from_value(v.clone())?,
PXConfigKey::ModuleMode => self.module_mode = serde_json::from_value(v.clone())?,
PXConfigKey::SensitiveHeaders => {
self.sensitive_headers = serde_json::from_value(v.clone())?
}
PXConfigKey::SensitiveRoutes => {
self.sensitive_routes = serde_json::from_value(v.clone())?
}
PXConfigKey::SensitiveRoutesRegex => {
let pats: Vec<String> = serde_json::from_value(v.clone())?;
self.sensitive_routes_regex = compile_regex_vec(pats, key.as_ref());
}
PXConfigKey::FilterByRoute => self.filter_by_route = serde_json::from_value(v.clone())?,
PXConfigKey::FilterByExtension => {
self.filter_by_extension = serde_json::from_value(v.clone())?
}
PXConfigKey::FilterByUserAgent => {
self.filter_by_user_agent = serde_json::from_value(v.clone())?
}
PXConfigKey::FilterByIp => self.filter_by_ip = serde_json::from_value(v.clone())?,
PXConfigKey::FilterByHttpMethod => {
self.filter_by_http_method = serde_json::from_value(v.clone())?
}
PXConfigKey::CustomCookieHeader => {
self.custom_cookie_header = serde_json::from_value(v.clone())?
}
PXConfigKey::EnforcedRoutes => {
self.enforced_routes = serde_json::from_value(v.clone())?
}
PXConfigKey::MonitoredRoutes => {
self.monitored_routes = serde_json::from_value(v.clone())?
}
PXConfigKey::BypassMonitorHeader => {
self.bypass_monitor_header = serde_json::from_value(v.clone())?
}
PXConfigKey::FirstPartyEnabled => {
self.first_party_enabled = serde_json::from_value(v.clone())?
}
PXConfigKey::CustomLogo => self.custom_logo = serde_json::from_value(v.clone())?,
PXConfigKey::JsRef => self.js_ref = serde_json::from_value(v.clone())?,
PXConfigKey::CssRef => self.css_ref = serde_json::from_value(v.clone())?,
PXConfigKey::HumanSapiHost => self.human_sapi_host = serde_json::from_value(v.clone())?,
PXConfigKey::HumanSapiBackend => {
self.human_sapi_backend = serde_json::from_value(v.clone())?
}
PXConfigKey::HumanCollectorHost => {
self.human_collector_host = serde_json::from_value(v.clone())?
}
PXConfigKey::HumanCollectorBackend => {
self.human_collector_backend = serde_json::from_value(v.clone())?
}
PXConfigKey::HumanClientHost => {
self.human_client_host = serde_json::from_value(v.clone())?
}
PXConfigKey::HumanClientBackend => {
self.human_client_backend = serde_json::from_value(v.clone())?
}
PXConfigKey::HumanCaptchaHost => {
self.human_captcha_host = serde_json::from_value(v.clone())?
}
PXConfigKey::HumanCaptchaBackend => {
self.human_captcha_backend = serde_json::from_value(v.clone())?
}
PXConfigKey::IpHeaders => self.ip_headers = serde_json::from_value(v.clone())?,
PXConfigKey::LogEndpoint => self.log_endpoint = serde_json::from_value(v.clone())?,
PXConfigKey::DataEnrichmentHeaderName => {
self.data_enrichment_header_name = serde_json::from_value(v.clone())?
}
PXConfigKey::ExtractedCookies => {
self.extracted_cookies = serde_json::from_value(v.clone())?
}
PXConfigKey::CorsSupportEnabled => {
self.cors_support_enabled = serde_json::from_value(v.clone())?
}
PXConfigKey::CorsPreflightRequestFilterEnabled => {
self.cors_preflight_request_filter_enabled = serde_json::from_value(v.clone())?
}
PXConfigKey::GraphqlEnabled => {
self.graphql_enabled = serde_json::from_value(v.clone())?
}
PXConfigKey::GraphqlRoutes => {
let pats: Vec<String> = serde_json::from_value(v.clone())?;
self.graphql_routes = compile_regex_vec(pats, key.as_ref());
}
PXConfigKey::SensitiveGraphqlOperationNames => {
self.sensitive_graphql_operation_names = serde_json::from_value(v.clone())?
}
PXConfigKey::SensitiveGraphqlOperationTypes => {
self.sensitive_graphql_operation_types = serde_json::from_value(v.clone())?
}
PXConfigKey::GraphqlBodyMaxLength => {
self.graphql_body_max_length = serde_json::from_value(v.clone())?
}
PXConfigKey::GraphqlKeywords => {
let pats: Vec<String> = serde_json::from_value(v.clone())?;
self.graphql_keywords = compile_regex_vec(pats, key.as_ref());
}
PXConfigKey::S2sTimeout => self.s2s_timeout = serde_json::from_value(v.clone())?,
PXConfigKey::TokenVersion => self.token_version = serde_json::from_value(v.clone())?,
PXConfigKey::CustomFirstPartyCaptchaEndpoint => {
self.custom_first_party_captcha_endpoint = serde_json::from_value(v.clone())?
}
PXConfigKey::CustomFirstPartySensorEndpoint => {
self.custom_first_party_sensor_endpoint = serde_json::from_value(v.clone())?
}
PXConfigKey::CustomFirstPartyXhrEndpoint => {
self.custom_first_party_xhr_endpoint = serde_json::from_value(v.clone())?
}
PXConfigKey::UserAgentMaxLength => {
self.user_agent_max_length = serde_json::from_value(v.clone())?
}
PXConfigKey::RiskCookieMaxLength => {
self.risk_cookie_max_length = serde_json::from_value(v.clone())?
}
PXConfigKey::RiskCookieMinIterations => {
self.risk_cookie_min_iterations = serde_json::from_value(v.clone())?
}
PXConfigKey::RiskCookieMaxIterations => {
self.risk_cookie_max_iterations = serde_json::from_value(v.clone())?
}
PXConfigKey::AgenticTrustEnabled => {
self.agentic_trust_enabled = serde_json::from_value(v.clone())?
}
PXConfigKey::AgenticTrustMcpEndpointPath => {
self.agentic_trust_mcp_endpoint_path = serde_json::from_value(v.clone())?
}
PXConfigKey::SecuredPxhdEnabled => {
self.secured_pxhd_enabled = serde_json::from_value(v.clone())?
}
PXConfigKey::PxhdDomain => self.pxhd_domain = serde_json::from_value(v.clone())?,
PXConfigKey::JwtCookieName => self.jwt_cookie_name = serde_json::from_value(v.clone())?,
PXConfigKey::JwtCookieUserIdFieldName => {
self.jwt_cookie_user_id_field_name = serde_json::from_value(v.clone())?
}
PXConfigKey::JwtCookieAdditionalFieldNames => {
self.jwt_cookie_additional_field_names = serde_json::from_value(v.clone())?
}
PXConfigKey::JwtHeaderName => self.jwt_header_name = serde_json::from_value(v.clone())?,
PXConfigKey::JwtHeaderUserIdFieldName => {
self.jwt_header_user_id_field_name = serde_json::from_value(v.clone())?
}
PXConfigKey::JwtHeaderAdditionalFieldNames => {
self.jwt_header_additional_field_names = serde_json::from_value(v.clone())?
}
PXConfigKey::LoggerAuthToken => {
self.logger_auth_token = serde_json::from_value(v.clone())?
}
PXConfigKey::LoginCredentialsExtractionEnabled => {
self.login_credentials_extraction_enabled = serde_json::from_value(v.clone())?
}
PXConfigKey::LoginCredentialsExtraction => {
self.login_credentials_extraction = parse_ci_endpoints_from_value(v)?;
rebuild_prepared_ci_endpoints(self);
}
PXConfigKey::CredentialsIntelligenceVersion => {
self.credentials_intelligence_version = serde_json::from_value(v.clone())?
}
PXConfigKey::CompromisedCredentialsHeader => {
self.compromised_credentials_header = serde_json::from_value(v.clone())?
}
PXConfigKey::SendRawUsernameOnAdditionalS2sActivity => {
self.send_raw_username_on_additional_s2s_activity =
serde_json::from_value(v.clone())?
}
PXConfigKey::AdditionalS2sActivityEnabled => {
self.additional_s2s_activity_enabled = serde_json::from_value(v.clone())?
}
PXConfigKey::AdditionalS2sActivityHeaderEnabled => {
self.additional_s2s_activity_header_enabled = serde_json::from_value(v.clone())?
}
PXConfigKey::LoginSuccessfulReportingMethod => {
self.login_successful_reporting_method = serde_json::from_value(v.clone())?
}
PXConfigKey::LoginSuccessfulBodyRegex => {
self.login_successful_body_regex = serde_json::from_value(v.clone())?
}
PXConfigKey::LoginSuccessfulHeaderName => {
self.login_successful_header_name = serde_json::from_value(v.clone())?
}
PXConfigKey::LoginSuccessfulHeaderValue => {
self.login_successful_header_value = serde_json::from_value(v.clone())?
}
PXConfigKey::LoginSuccessfulStatus => {
self.login_successful_status = serde_json::from_value(v.clone())?
}
}
Ok(())
}
pub(crate) fn set_from_raw(&mut self, key: PXConfigKey, raw: &str) {
let raw = strip_json_quotes(raw);
match key {
PXConfigKey::AppId => self.app_id = raw.to_owned(),
PXConfigKey::CookieSecret => self.cookie_secret = raw.to_owned(),
PXConfigKey::AuthToken => self.auth_token = raw.to_owned(),
PXConfigKey::Debug => self.debug = raw.parse().unwrap_or(self.debug),
PXConfigKey::BlockingScore => {
self.blocking_score = raw.parse().unwrap_or(self.blocking_score)
}
PXConfigKey::ModuleEnabled => {
self.module_enabled = raw.parse().unwrap_or(self.module_enabled)
}
PXConfigKey::ModuleMode => self.module_mode = raw.parse().unwrap_or(self.module_mode),
PXConfigKey::SensitiveHeaders => self.sensitive_headers = parse_vec(raw),
PXConfigKey::SensitiveRoutes => self.sensitive_routes = parse_vec(raw),
PXConfigKey::SensitiveRoutesRegex => {
self.sensitive_routes_regex = parse_regex_vec(key.as_ref(), raw)
}
PXConfigKey::FilterByRoute => self.filter_by_route = parse_vec(raw),
PXConfigKey::FilterByExtension => self.filter_by_extension = parse_vec(raw),
PXConfigKey::FilterByUserAgent => self.filter_by_user_agent = parse_vec(raw),
PXConfigKey::FilterByIp => self.filter_by_ip = parse_vec(raw),
PXConfigKey::FilterByHttpMethod => self.filter_by_http_method = parse_vec(raw),
PXConfigKey::CustomCookieHeader => self.custom_cookie_header = parse_string(raw),
PXConfigKey::EnforcedRoutes => self.enforced_routes = parse_vec(raw),
PXConfigKey::MonitoredRoutes => self.monitored_routes = parse_vec(raw),
PXConfigKey::BypassMonitorHeader => self.bypass_monitor_header = parse_string(raw),
PXConfigKey::FirstPartyEnabled => {
self.first_party_enabled = raw.parse().unwrap_or(self.first_party_enabled)
}
PXConfigKey::CustomLogo => self.custom_logo = parse_string(raw),
PXConfigKey::JsRef => self.js_ref = parse_string(raw),
PXConfigKey::CssRef => self.css_ref = parse_string(raw),
PXConfigKey::HumanSapiHost => self.human_sapi_host = parse_string(raw),
PXConfigKey::HumanSapiBackend => self.human_sapi_backend = parse_string(raw),
PXConfigKey::HumanCollectorHost => self.human_collector_host = parse_string(raw),
PXConfigKey::HumanCollectorBackend => self.human_collector_backend = parse_string(raw),
PXConfigKey::HumanClientHost => self.human_client_host = parse_string(raw),
PXConfigKey::HumanClientBackend => self.human_client_backend = parse_string(raw),
PXConfigKey::HumanCaptchaHost => self.human_captcha_host = parse_string(raw),
PXConfigKey::HumanCaptchaBackend => self.human_captcha_backend = parse_string(raw),
PXConfigKey::IpHeaders => self.ip_headers = parse_vec(raw),
PXConfigKey::LogEndpoint => self.log_endpoint = raw.to_owned(),
PXConfigKey::DataEnrichmentHeaderName => {
self.data_enrichment_header_name = parse_string(raw)
}
PXConfigKey::ExtractedCookies => self.extracted_cookies = parse_vec(raw),
PXConfigKey::CorsSupportEnabled => {
self.cors_support_enabled = raw.parse().unwrap_or(self.cors_support_enabled)
}
PXConfigKey::CorsPreflightRequestFilterEnabled => {
self.cors_preflight_request_filter_enabled = raw
.parse()
.unwrap_or(self.cors_preflight_request_filter_enabled)
}
PXConfigKey::GraphqlEnabled => {
self.graphql_enabled = raw.parse().unwrap_or(self.graphql_enabled)
}
PXConfigKey::GraphqlRoutes => self.graphql_routes = parse_regex_vec(key.as_ref(), raw),
PXConfigKey::SensitiveGraphqlOperationNames => {
self.sensitive_graphql_operation_names = parse_vec(raw)
}
PXConfigKey::SensitiveGraphqlOperationTypes => {
self.sensitive_graphql_operation_types = parse_vec(raw)
}
PXConfigKey::GraphqlBodyMaxLength => {
self.graphql_body_max_length = raw.parse().unwrap_or(self.graphql_body_max_length)
}
PXConfigKey::GraphqlKeywords => {
self.graphql_keywords = parse_regex_vec(key.as_ref(), raw)
}
PXConfigKey::S2sTimeout => self.s2s_timeout = raw.parse().unwrap_or(self.s2s_timeout),
PXConfigKey::TokenVersion => {
self.token_version = raw.parse().unwrap_or(self.token_version)
}
PXConfigKey::CustomFirstPartyCaptchaEndpoint => {
self.custom_first_party_captcha_endpoint = parse_string(raw)
}
PXConfigKey::CustomFirstPartySensorEndpoint => {
self.custom_first_party_sensor_endpoint = parse_string(raw)
}
PXConfigKey::CustomFirstPartyXhrEndpoint => {
self.custom_first_party_xhr_endpoint = parse_string(raw)
}
PXConfigKey::UserAgentMaxLength => {
self.user_agent_max_length = raw.parse().unwrap_or(self.user_agent_max_length)
}
PXConfigKey::RiskCookieMaxLength => {
self.risk_cookie_max_length = raw.parse().unwrap_or(self.risk_cookie_max_length)
}
PXConfigKey::RiskCookieMinIterations => {
self.risk_cookie_min_iterations =
raw.parse().unwrap_or(self.risk_cookie_min_iterations)
}
PXConfigKey::RiskCookieMaxIterations => {
self.risk_cookie_max_iterations =
raw.parse().unwrap_or(self.risk_cookie_max_iterations)
}
PXConfigKey::AgenticTrustEnabled => {
self.agentic_trust_enabled = raw.parse().unwrap_or(self.agentic_trust_enabled)
}
PXConfigKey::AgenticTrustMcpEndpointPath => {
self.agentic_trust_mcp_endpoint_path = parse_string(raw)
}
PXConfigKey::SecuredPxhdEnabled => {
self.secured_pxhd_enabled = raw.parse().unwrap_or(self.secured_pxhd_enabled)
}
PXConfigKey::PxhdDomain => self.pxhd_domain = parse_string(raw),
PXConfigKey::JwtCookieName => self.jwt_cookie_name = parse_string(raw),
PXConfigKey::JwtCookieUserIdFieldName => {
self.jwt_cookie_user_id_field_name = parse_string(raw)
}
PXConfigKey::JwtCookieAdditionalFieldNames => {
self.jwt_cookie_additional_field_names = parse_vec(raw)
}
PXConfigKey::JwtHeaderName => self.jwt_header_name = parse_string(raw),
PXConfigKey::JwtHeaderUserIdFieldName => {
self.jwt_header_user_id_field_name = parse_string(raw)
}
PXConfigKey::JwtHeaderAdditionalFieldNames => {
self.jwt_header_additional_field_names = parse_vec(raw)
}
PXConfigKey::LoggerAuthToken => self.logger_auth_token = raw.to_owned(),
PXConfigKey::LoginCredentialsExtractionEnabled => {
self.login_credentials_extraction_enabled = raw
.parse()
.unwrap_or(self.login_credentials_extraction_enabled)
}
PXConfigKey::LoginCredentialsExtraction => {
self.login_credentials_extraction = parse_ci_endpoints(raw);
rebuild_prepared_ci_endpoints(self);
}
PXConfigKey::CredentialsIntelligenceVersion => {
self.credentials_intelligence_version = parse_string(raw)
}
PXConfigKey::CompromisedCredentialsHeader => {
self.compromised_credentials_header = parse_string(raw)
}
PXConfigKey::SendRawUsernameOnAdditionalS2sActivity => {
self.send_raw_username_on_additional_s2s_activity = raw
.parse()
.unwrap_or(self.send_raw_username_on_additional_s2s_activity)
}
PXConfigKey::AdditionalS2sActivityEnabled => {
self.additional_s2s_activity_enabled =
raw.parse().unwrap_or(self.additional_s2s_activity_enabled)
}
PXConfigKey::AdditionalS2sActivityHeaderEnabled => {
self.additional_s2s_activity_header_enabled = raw
.parse()
.unwrap_or(self.additional_s2s_activity_header_enabled)
}
PXConfigKey::LoginSuccessfulReportingMethod => {
self.login_successful_reporting_method = parse_string(raw)
}
PXConfigKey::LoginSuccessfulBodyRegex => {
self.login_successful_body_regex = parse_string(raw)
}
PXConfigKey::LoginSuccessfulHeaderName => {
self.login_successful_header_name = parse_string(raw)
}
PXConfigKey::LoginSuccessfulHeaderValue => {
self.login_successful_header_value = parse_string(raw)
}
PXConfigKey::LoginSuccessfulStatus => {
self.login_successful_status = parse_status_vec(raw)
}
}
}
pub fn set_ci_extract_credentials_fn(&mut self, f: PXExtractCredentialsFn) {
self.ci_extract_credentials_fn = Some(f);
}
pub fn set_ci_login_successful_fn(&mut self, f: PXLoginSuccessfulFn) {
self.ci_login_successful_fn = Some(f);
}
}