use crate::{
error::{FraiseQLError, Result},
runtime::{classify_field_access, field_filter::FieldAccessResult},
schema::{CompiledSchema, SessionVariableSource, SessionVariablesConfig},
security::SecurityContext,
};
#[must_use]
pub(in super::super) fn resolve_session_variables(
config: &SessionVariablesConfig,
security_context: &SecurityContext,
) -> Vec<(String, String)> {
let mut vars: Vec<(String, String)> = Vec::new();
if config.inject_started_at {
vars.push((
fraiseql_db::STARTED_AT_VAR.to_string(),
fraiseql_db::CLOCK_TIMESTAMP_DIRECTIVE.to_string(),
));
}
for mapping in &config.variables {
let value: Option<String> = match &mapping.source {
SessionVariableSource::Jwt { claim } => {
if let Some(v) = security_context.attributes.get(claim.as_str()) {
Some(if let serde_json::Value::String(s) = v {
s.clone()
} else {
v.to_string()
})
} else if claim == "sub" || claim == "user_id" {
Some(security_context.user_id.0.clone())
} else if claim == "tenant_id" {
security_context.tenant_id.as_ref().map(|t| t.0.clone())
} else if claim == "email" {
security_context.email.clone()
} else if claim == "name" || claim == "display_name" {
security_context.display_name.clone()
} else {
None
}
},
SessionVariableSource::Header { header } => {
security_context.attributes.get(header.as_str()).map(|v| {
if let serde_json::Value::String(s) = v {
s.clone()
} else {
v.to_string()
}
})
},
SessionVariableSource::Literal { value } => Some(value.clone()),
};
if let Some(v) = value {
vars.push((mapping.name.clone(), v));
}
}
vars
}
pub(in super::super) fn apply_field_rbac_filtering(
schema: &CompiledSchema,
return_type: &str,
projection_fields: Vec<String>,
security_context: &SecurityContext,
) -> Result<FieldAccessResult> {
if let Some(security_config) = schema.security.as_ref() {
if let Some(type_def) = schema.types.iter().find(|t| t.name == return_type) {
return classify_field_access(
security_context,
security_config,
&type_def.fields,
projection_fields,
)
.map_err(|rejected_field| FraiseQLError::Authorization {
message: format!(
"Access denied: field '{rejected_field}' on type '{return_type}' \
requires a scope you do not have"
),
action: Some("read".to_string()),
resource: Some(format!("{return_type}.{rejected_field}")),
});
}
}
Ok(FieldAccessResult {
allowed: projection_fields,
masked: Vec::new(),
})
}