use super::*;
impl RuntimePlan {
pub fn execute_request(
&self,
request: RequestInput,
cookie_secret: &[u8],
csrf_secret: &[u8],
) -> Result<RequestExecution, RequestExecutionError> {
let matched = self
.http
.resolve_match(&self.config, request.method, &request.host, &request.path)
.ok_or_else(|| RequestExecutionError::RouteNotFound {
method: request.method,
host: request.host.clone(),
path: request.path.clone(),
})?;
let trace = RequestTraceContext {
request_id: request.request_id.clone().unwrap_or_else(|| {
format!(
"req:{:?}:{}:{}",
request.method, request.host, matched.resolved.route_name
)
}),
transport_scheme: request
.forwarded_proto
.clone()
.unwrap_or_else(|| request.scheme.clone()),
};
let mut resolved_from_cookie = false;
let session_id = if let Some(session_id) = request.session_id.clone() {
Some(session_id)
} else if let Some(cookie) = request.session_cookie.as_deref() {
resolved_from_cookie = true;
Some(self.verify_session_cookie(cookie_secret, cookie)?)
} else {
None
};
let session = SessionContext {
session_id: session_id.clone(),
resolved_from_cookie,
};
let principal = PrincipalContext {
principal_id: request.principal_id.clone(),
principal_kind: request.principal_kind,
granted_capabilities: request.granted_capabilities.clone(),
};
let csrf_token = request.csrf_token.clone().or_else(|| {
request
.form_field(self.browser.csrf.field_name.as_str())
.map(str::to_string)
});
self.enforce_maintenance_mode(&matched.route, request.method, &request)?;
self.enforce_feature_flags(&matched.route, &matched.resolved)?;
self.enforce_route_auth(&matched.resolved, &session, &principal)?;
self.enforce_browser_policy(
&matched.route,
&matched.resolved,
request.method,
request.csrf_action.as_deref(),
csrf_token.as_deref(),
&session,
csrf_secret,
)?;
let response = self
.handlers
.get(&matched.resolved.route_name)
.cloned()
.map(|handler| handler.response)
.ok_or_else(|| RequestExecutionError::HandlerNotRegistered {
route: matched.resolved.route_name.clone(),
})?;
let cache = cache_disposition_for_route(request.method, &matched.resolved.auth, &session);
let cache_plan = build_execution_cache_plan(
self,
&request,
&matched.route,
&matched.resolved,
&session,
&principal,
cache,
)?;
Ok(RequestExecution {
customer_app: self.config.app.name.clone(),
site_id: matched.resolved.site_id.clone(),
site_display_name: matched
.resolved
.site_id
.as_deref()
.and_then(|site_id| self.config.site_for_id(site_id))
.map(|site| site.display_name.clone()),
brand_name: matched
.resolved
.site_id
.as_deref()
.and_then(|site_id| self.config.site_for_id(site_id))
.and_then(|site| site.brand_name.clone()),
method: request.method,
host: request.host,
path: request.path,
headers: request.headers,
query_params: request.query_params,
form_fields: request.form_fields,
content_type: request.content_type,
raw_body: request.raw_body,
route: matched.resolved.clone(),
route_area: matched.route.area,
locale: matched.resolved.locale.clone().unwrap_or_else(|| {
matched
.resolved
.site_id
.as_deref()
.and_then(|site_id| self.config.site_for_id(site_id))
.map(|site| site.default_locale.clone())
.unwrap_or_else(|| self.config.i18n.default_locale.clone())
}),
trace,
session: session.clone(),
principal,
cache,
cache_plan,
middleware: self.http.middleware.clone(),
response,
flash_messages: Vec::new(),
response_cookies: Vec::new(),
})
}
pub fn execute_browser_request(
&self,
browser: &mut BrowserHost,
mut request: RequestInput,
cookie_secret: &[u8],
csrf_secret: &[u8],
now: BrowserInstant,
) -> Result<RequestExecution, RequestExecutionError> {
let resolved = browser
.resolve_request(&request, cookie_secret, now)
.map_err(RequestExecutionError::from_browser_error)?;
request.session_id = resolved.session.session_id.clone();
request.session_cookie = None;
request.flash_cookie = None;
if request.principal_id.is_none() {
request.principal_id = resolved.principal_id.clone();
if request.principal_id.is_some() {
request.principal_kind = RequestPrincipalKind::User;
}
}
let mut execution = self.execute_request(request, cookie_secret, csrf_secret)?;
execution.session = resolved.session;
if execution.principal.principal_id.is_none() {
execution.principal.principal_id = resolved.principal_id;
if execution.principal.principal_id.is_some() {
execution.principal.principal_kind = RequestPrincipalKind::User;
}
}
execution.flash_messages = resolved.flash_messages;
execution.response_cookies = resolved.response_cookies;
Ok(execution)
}
fn verify_session_cookie(
&self,
cookie_secret: &[u8],
cookie: &str,
) -> Result<String, RequestExecutionError> {
self.browser
.sessions
.session_cookie
.unprotect(cookie_secret, cookie)
.map_err(|error| RequestExecutionError::InvalidSessionCookie(error.to_string()))
}
fn enforce_route_auth(
&self,
route: &ResolvedRoute,
session: &SessionContext,
principal: &PrincipalContext,
) -> Result<(), RequestExecutionError> {
match route.auth {
RouteAuthGate::Public => Ok(()),
RouteAuthGate::Session => {
if session.session_id.is_some() {
Ok(())
} else {
Err(RequestExecutionError::SessionRequired {
route: route.route_name.clone(),
})
}
}
RouteAuthGate::Capability(capability) => {
if session.session_id.is_none() {
return Err(RequestExecutionError::SessionRequired {
route: route.route_name.clone(),
});
}
if principal.granted_capabilities.contains(&capability) {
Ok(())
} else {
Err(RequestExecutionError::CapabilityRequired {
route: route.route_name.clone(),
capability,
})
}
}
}
}
fn enforce_feature_flags(
&self,
route: &RouteDefinition,
resolved: &ResolvedRoute,
) -> Result<(), RequestExecutionError> {
let Some(feature_flag) = route.feature_flag.as_deref() else {
return Ok(());
};
let Some(feature_flag_id) = FeatureFlagId::new(feature_flag.to_string()).ok() else {
return Err(RequestExecutionError::FeatureFlagDisabled {
route: route.name.clone(),
feature_flag: feature_flag.to_string(),
});
};
let context = FeatureFlagContext {
environment: self.config.app.environment,
customer_app: CustomerAppId::new(self.config.app.name.clone()).ok(),
site: resolved
.site_id
.as_deref()
.and_then(|site| SiteId::new(site.to_string()).ok()),
brand: resolved
.site_id
.as_deref()
.and_then(|site_id| self.config.site_for_id(site_id))
.and_then(|site| site.brand_name.as_deref())
.and_then(|brand| BrandId::new(brand.to_string()).ok()),
cohorts: BTreeSet::new(),
};
match self.observability.flags.get(&feature_flag_id) {
Some(flag) if flag.enabled_for(&context) => Ok(()),
_ => Err(RequestExecutionError::FeatureFlagDisabled {
route: route.name.clone(),
feature_flag: feature_flag.to_string(),
}),
}
}
fn enforce_maintenance_mode(
&self,
route: &RouteDefinition,
method: HttpMethod,
request: &RequestInput,
) -> Result<(), RequestExecutionError> {
let customer_app = CustomerAppId::new(self.config.app.name.clone()).ok();
let blocked = self.observability.maintenance.blocks_request(
customer_app.as_ref(),
method.is_state_changing(),
request.maintenance_bypass_token.as_deref(),
);
if blocked {
Err(RequestExecutionError::MaintenanceMode {
route: route.name.clone(),
})
} else {
Ok(())
}
}
fn enforce_browser_policy(
&self,
route: &RouteDefinition,
resolved: &ResolvedRoute,
method: HttpMethod,
csrf_action: Option<&str>,
csrf_token: Option<&str>,
session: &SessionContext,
csrf_secret: &[u8],
) -> Result<(), RequestExecutionError> {
let requires_csrf = method.is_state_changing()
&& route.area != RouteArea::Api
&& self.browser.csrf.enabled
&& !matches!(resolved.auth, RouteAuthGate::Public);
if !requires_csrf {
return Ok(());
}
let session_id = session.session_id.as_deref().ok_or_else(|| {
RequestExecutionError::MissingSessionForCsrf {
route: resolved.route_name.clone(),
}
})?;
let token = csrf_token.ok_or_else(|| RequestExecutionError::MissingCsrfToken {
route: resolved.route_name.clone(),
})?;
let action = csrf_action.unwrap_or(&resolved.route_name);
let valid = self
.browser
.csrf
.verify_token(csrf_secret, session_id, action, token)
.map_err(|_| RequestExecutionError::InvalidCsrfToken {
route: resolved.route_name.clone(),
})?;
if valid {
Ok(())
} else {
Err(RequestExecutionError::InvalidCsrfToken {
route: resolved.route_name.clone(),
})
}
}
}