1use super::*;
2
3impl RuntimePlan {
4 pub fn execute_request(
5 &self,
6 request: RequestInput,
7 cookie_secret: &[u8],
8 csrf_secret: &[u8],
9 ) -> Result<RequestExecution, RequestExecutionError> {
10 let matched = self
11 .http
12 .resolve_match(&self.config, request.method, &request.host, &request.path)
13 .ok_or_else(|| RequestExecutionError::RouteNotFound {
14 method: request.method,
15 host: request.host.clone(),
16 path: request.path.clone(),
17 })?;
18
19 let trace = RequestTraceContext {
20 request_id: request.request_id.clone().unwrap_or_else(|| {
21 format!(
22 "req:{:?}:{}:{}",
23 request.method, request.host, matched.resolved.route_name
24 )
25 }),
26 transport_scheme: request
27 .forwarded_proto
28 .clone()
29 .unwrap_or_else(|| request.scheme.clone()),
30 };
31
32 let mut resolved_from_cookie = false;
33 let session_id = if let Some(session_id) = request.session_id.clone() {
34 Some(session_id)
35 } else if let Some(cookie) = request.session_cookie.as_deref() {
36 resolved_from_cookie = true;
37 Some(self.verify_session_cookie(cookie_secret, cookie)?)
38 } else {
39 None
40 };
41
42 let session = SessionContext {
43 session_id: session_id.clone(),
44 resolved_from_cookie,
45 };
46 let principal = PrincipalContext {
47 principal_id: request.principal_id.clone(),
48 principal_kind: request.principal_kind,
49 granted_capabilities: request.granted_capabilities.clone(),
50 };
51 let csrf_token = request.csrf_token.clone().or_else(|| {
52 request
53 .form_field(self.browser.csrf.field_name.as_str())
54 .map(str::to_string)
55 });
56
57 self.enforce_maintenance_mode(&matched.route, request.method, &request)?;
58 self.enforce_feature_flags(&matched.route, &matched.resolved)?;
59 self.enforce_route_auth(&matched.resolved, &session, &principal)?;
60 self.enforce_browser_policy(
61 &matched.route,
62 &matched.resolved,
63 request.method,
64 request.csrf_action.as_deref(),
65 csrf_token.as_deref(),
66 &session,
67 csrf_secret,
68 )?;
69 let response = self
70 .handlers
71 .get(&matched.resolved.route_name)
72 .cloned()
73 .map(|handler| handler.response)
74 .ok_or_else(|| RequestExecutionError::HandlerNotRegistered {
75 route: matched.resolved.route_name.clone(),
76 })?;
77 let cache = cache_disposition_for_route(request.method, &matched.resolved.auth, &session);
78 let cache_plan = build_execution_cache_plan(
79 self,
80 &request,
81 &matched.route,
82 &matched.resolved,
83 &session,
84 &principal,
85 cache,
86 )?;
87
88 Ok(RequestExecution {
89 customer_app: self.config.app.name.clone(),
90 site_id: matched.resolved.site_id.clone(),
91 site_display_name: matched
92 .resolved
93 .site_id
94 .as_deref()
95 .and_then(|site_id| self.config.site_for_id(site_id))
96 .map(|site| site.display_name.clone()),
97 brand_name: matched
98 .resolved
99 .site_id
100 .as_deref()
101 .and_then(|site_id| self.config.site_for_id(site_id))
102 .and_then(|site| site.brand_name.clone()),
103 method: request.method,
104 host: request.host,
105 path: request.path,
106 headers: request.headers,
107 query_params: request.query_params,
108 form_fields: request.form_fields,
109 content_type: request.content_type,
110 raw_body: request.raw_body,
111 route: matched.resolved.clone(),
112 route_area: matched.route.area,
113 locale: matched.resolved.locale.clone().unwrap_or_else(|| {
114 matched
115 .resolved
116 .site_id
117 .as_deref()
118 .and_then(|site_id| self.config.site_for_id(site_id))
119 .map(|site| site.default_locale.clone())
120 .unwrap_or_else(|| self.config.i18n.default_locale.clone())
121 }),
122 trace,
123 session: session.clone(),
124 principal,
125 cache,
126 cache_plan,
127 middleware: self.http.middleware.clone(),
128 response,
129 flash_messages: Vec::new(),
130 response_cookies: Vec::new(),
131 })
132 }
133
134 pub fn execute_browser_request(
135 &self,
136 browser: &mut BrowserHost,
137 mut request: RequestInput,
138 cookie_secret: &[u8],
139 csrf_secret: &[u8],
140 now: BrowserInstant,
141 ) -> Result<RequestExecution, RequestExecutionError> {
142 let resolved = browser
143 .resolve_request(&request, cookie_secret, now)
144 .map_err(RequestExecutionError::from_browser_error)?;
145
146 request.session_id = resolved.session.session_id.clone();
147 request.session_cookie = None;
148 request.flash_cookie = None;
149
150 if request.principal_id.is_none() {
151 request.principal_id = resolved.principal_id.clone();
152 if request.principal_id.is_some() {
153 request.principal_kind = RequestPrincipalKind::User;
154 }
155 }
156
157 let mut execution = self.execute_request(request, cookie_secret, csrf_secret)?;
158 execution.session = resolved.session;
159 if execution.principal.principal_id.is_none() {
160 execution.principal.principal_id = resolved.principal_id;
161 if execution.principal.principal_id.is_some() {
162 execution.principal.principal_kind = RequestPrincipalKind::User;
163 }
164 }
165 execution.flash_messages = resolved.flash_messages;
166 execution.response_cookies = resolved.response_cookies;
167 Ok(execution)
168 }
169
170 fn verify_session_cookie(
171 &self,
172 cookie_secret: &[u8],
173 cookie: &str,
174 ) -> Result<String, RequestExecutionError> {
175 self.browser
176 .sessions
177 .session_cookie
178 .unprotect(cookie_secret, cookie)
179 .map_err(|error| RequestExecutionError::InvalidSessionCookie(error.to_string()))
180 }
181
182 fn enforce_route_auth(
183 &self,
184 route: &ResolvedRoute,
185 session: &SessionContext,
186 principal: &PrincipalContext,
187 ) -> Result<(), RequestExecutionError> {
188 match route.auth {
189 RouteAuthGate::Public => Ok(()),
190 RouteAuthGate::Session => {
191 if session.session_id.is_some() {
192 Ok(())
193 } else {
194 Err(RequestExecutionError::SessionRequired {
195 route: route.route_name.clone(),
196 })
197 }
198 }
199 RouteAuthGate::Capability(capability) => {
200 if session.session_id.is_none() {
201 return Err(RequestExecutionError::SessionRequired {
202 route: route.route_name.clone(),
203 });
204 }
205
206 if principal.granted_capabilities.contains(&capability) {
207 Ok(())
208 } else {
209 Err(RequestExecutionError::CapabilityRequired {
210 route: route.route_name.clone(),
211 capability,
212 })
213 }
214 }
215 }
216 }
217
218 fn enforce_feature_flags(
219 &self,
220 route: &RouteDefinition,
221 resolved: &ResolvedRoute,
222 ) -> Result<(), RequestExecutionError> {
223 let Some(feature_flag) = route.feature_flag.as_deref() else {
224 return Ok(());
225 };
226
227 let Some(feature_flag_id) = FeatureFlagId::new(feature_flag.to_string()).ok() else {
228 return Err(RequestExecutionError::FeatureFlagDisabled {
229 route: route.name.clone(),
230 feature_flag: feature_flag.to_string(),
231 });
232 };
233 let context = FeatureFlagContext {
234 environment: self.config.app.environment,
235 customer_app: CustomerAppId::new(self.config.app.name.clone()).ok(),
236 site: resolved
237 .site_id
238 .as_deref()
239 .and_then(|site| SiteId::new(site.to_string()).ok()),
240 brand: resolved
241 .site_id
242 .as_deref()
243 .and_then(|site_id| self.config.site_for_id(site_id))
244 .and_then(|site| site.brand_name.as_deref())
245 .and_then(|brand| BrandId::new(brand.to_string()).ok()),
246 cohorts: BTreeSet::new(),
247 };
248
249 match self.observability.flags.get(&feature_flag_id) {
250 Some(flag) if flag.enabled_for(&context) => Ok(()),
251 _ => Err(RequestExecutionError::FeatureFlagDisabled {
252 route: route.name.clone(),
253 feature_flag: feature_flag.to_string(),
254 }),
255 }
256 }
257
258 fn enforce_maintenance_mode(
259 &self,
260 route: &RouteDefinition,
261 method: HttpMethod,
262 request: &RequestInput,
263 ) -> Result<(), RequestExecutionError> {
264 let customer_app = CustomerAppId::new(self.config.app.name.clone()).ok();
265 let blocked = self.observability.maintenance.blocks_request(
266 customer_app.as_ref(),
267 method.is_state_changing(),
268 request.maintenance_bypass_token.as_deref(),
269 );
270
271 if blocked {
272 Err(RequestExecutionError::MaintenanceMode {
273 route: route.name.clone(),
274 })
275 } else {
276 Ok(())
277 }
278 }
279
280 fn enforce_browser_policy(
281 &self,
282 route: &RouteDefinition,
283 resolved: &ResolvedRoute,
284 method: HttpMethod,
285 csrf_action: Option<&str>,
286 csrf_token: Option<&str>,
287 session: &SessionContext,
288 csrf_secret: &[u8],
289 ) -> Result<(), RequestExecutionError> {
290 let requires_csrf = method.is_state_changing()
291 && route.area != RouteArea::Api
292 && self.browser.csrf.enabled
293 && !matches!(resolved.auth, RouteAuthGate::Public);
294
295 if !requires_csrf {
296 return Ok(());
297 }
298
299 let session_id = session.session_id.as_deref().ok_or_else(|| {
300 RequestExecutionError::MissingSessionForCsrf {
301 route: resolved.route_name.clone(),
302 }
303 })?;
304 let token = csrf_token.ok_or_else(|| RequestExecutionError::MissingCsrfToken {
305 route: resolved.route_name.clone(),
306 })?;
307 let action = csrf_action.unwrap_or(&resolved.route_name);
308 let valid = self
309 .browser
310 .csrf
311 .verify_token(csrf_secret, session_id, action, token)
312 .map_err(|_| RequestExecutionError::InvalidCsrfToken {
313 route: resolved.route_name.clone(),
314 })?;
315
316 if valid {
317 Ok(())
318 } else {
319 Err(RequestExecutionError::InvalidCsrfToken {
320 route: resolved.route_name.clone(),
321 })
322 }
323 }
324}