perimeterx-fastly-enforcer 2.2.2

PerimeterX Fastly Compute@Edge Rust Enforcer
Documentation
use crate::modules::pxutils::{decode_jwt_payload, get_value_at_path, is_truthy_json_value};
use crate::px_debug;
use crate::pxconfig::PXConfig;
use crate::pxcontext::PXContext;
use fastly::Request;
use serde_json::{Map, Value};

/// Extract JWT user identifiers from configured cookie/header into `PXContext`.
pub(crate) fn enrich_context_from_request(req: &Request, conf: &PXConfig, ctx: &mut PXContext) {
    let (jwt_token, user_id_field_name, additional_field_names) =
        match resolve_jwt_source(req, conf, ctx) {
            Some(source) => source,
            None => return,
        };

    if user_id_field_name.is_empty() {
        return;
    }

    let payload = match decode_jwt_payload(&jwt_token) {
        Some(p) => p,
        None => {
            px_debug!("unable to decode user identifiers JWT token");
            return;
        }
    };

    let extracted = extract_jwt_fields(&payload, user_id_field_name, additional_field_names);

    if let Some(app_user_id) = extracted.app_user_id {
        ctx.app_user_id = Some(app_user_id);
    }
    if !extracted.jwt_additional_fields.is_empty() {
        ctx.jwt_additional_fields = Some(extracted.jwt_additional_fields);
    }
}

fn resolve_jwt_source<'a>(
    req: &Request,
    conf: &'a PXConfig,
    ctx: &'a PXContext,
) -> Option<(String, &'a str, &'a [String])> {
    if !conf.jwt_cookie_name.is_empty() && !conf.jwt_cookie_user_id_field_name.is_empty() {
        if let Some(token) = ctx.cookies.get(conf.jwt_cookie_name.as_str()) {
            if !token.is_empty() {
                return Some((
                    token.clone(),
                    conf.jwt_cookie_user_id_field_name.as_str(),
                    conf.jwt_cookie_additional_field_names.as_slice(),
                ));
            }
        }
    }

    if !conf.jwt_header_name.is_empty() && !conf.jwt_header_user_id_field_name.is_empty() {
        if let Some(token) = req
            .get_header_str_lossy(conf.jwt_header_name.as_str())
            .map(|v| v.into_owned())
            .filter(|t| !t.is_empty())
        {
            return Some((
                token,
                conf.jwt_header_user_id_field_name.as_str(),
                conf.jwt_header_additional_field_names.as_slice(),
            ));
        }
    }

    None
}

pub(crate) struct ExtractedJwtFields {
    pub app_user_id: Option<String>,
    pub jwt_additional_fields: Map<String, Value>,
}

/// Pull configured user ID and additional JWT payload fields.
pub(crate) fn extract_jwt_fields(
    payload: &Value,
    user_id_field_name: &str,
    additional_field_names: &[String],
) -> ExtractedJwtFields {
    let mut jwt_additional_fields = Map::new();

    let app_user_id = get_value_at_path(payload, user_id_field_name)
        .and_then(|value| value.as_str().filter(|s| !s.is_empty()).map(str::to_owned));

    for field_name in additional_field_names {
        if field_name.is_empty() {
            continue;
        }
        if let Some(value) = get_value_at_path(payload, field_name) {
            if is_truthy_json_value(value) {
                jwt_additional_fields.insert(field_name.clone(), value.clone());
            }
        }
    }

    ExtractedJwtFields {
        app_user_id,
        jwt_additional_fields,
    }
}