1use http::{HeaderValue, header::AUTHORIZATION};
10use secrecy::{ExposeSecret, SecretString};
11use toolkit_security::SecurityContext;
12use toolkit_security::constants::INTERNAL_TOKEN_HEADER;
13
14pub const ERROR_BODY_PREVIEW_LIMIT: usize = 8 * 1024;
20
21pub fn attach_bearer_http<B>(request: &mut http::Request<B>, secctx: &SecurityContext) {
29 let Some(token) = secctx.bearer_token() else {
30 return;
31 };
32 let Ok(mut value) = HeaderValue::from_str(&format!("Bearer {}", token.expose_secret())) else {
34 tracing::warn!(
35 "bearer token contains invalid HTTP header characters; outgoing request sent without Authorization header"
36 );
37 return;
38 };
39 value.set_sensitive(true);
40 request.headers_mut().insert(AUTHORIZATION, value);
41}
42
43pub fn attach_internal_token_http<B>(request: &mut http::Request<B>, token: &SecretString) {
53 let Ok(mut value) = HeaderValue::from_str(token.expose_secret()) else {
55 tracing::warn!(
56 "internal token contains invalid HTTP header characters; outgoing request sent without X-ToolKit-Internal-Token header"
57 );
58 return;
59 };
60 value.set_sensitive(true);
61 request.headers_mut().insert(INTERNAL_TOKEN_HEADER, value);
62}
63
64#[cfg(test)]
65#[cfg_attr(coverage_nightly, coverage(off))]
66mod tests {
67 use super::*;
68
69 fn secctx_with_token(token: &str) -> SecurityContext {
70 SecurityContext::builder()
71 .subject_id(uuid::Uuid::nil())
72 .subject_tenant_id(uuid::Uuid::nil())
73 .bearer_token(token.to_owned())
74 .build()
75 .expect("valid security context")
76 }
77
78 #[test]
79 fn attach_sets_bearer_header() {
80 let secctx = secctx_with_token("header.payload.signature");
81 let mut request = http::Request::new(());
82 attach_bearer_http(&mut request, &secctx);
83
84 let value = request.headers().get(AUTHORIZATION).expect("header set");
85 assert_eq!(value.to_str().unwrap(), "Bearer header.payload.signature");
86 }
87
88 #[test]
89 fn attach_marks_header_sensitive() {
90 let secctx = secctx_with_token("abc.def.ghi");
91 let mut request = http::Request::new(());
92 attach_bearer_http(&mut request, &secctx);
93
94 let value = request.headers().get(AUTHORIZATION).expect("header set");
95 assert!(value.is_sensitive());
96 }
97
98 #[test]
99 fn attach_noop_when_no_token() {
100 let secctx = SecurityContext::anonymous();
101 let mut request = http::Request::new(());
102 attach_bearer_http(&mut request, &secctx);
103
104 assert!(request.headers().get(AUTHORIZATION).is_none());
105 }
106
107 #[test]
108 fn internal_token_uses_dedicated_header_not_authorization() {
109 let mut request = http::Request::new(());
110 attach_internal_token_http(&mut request, &SecretString::from("sa.jwt.token"));
111
112 assert!(request.headers().get(INTERNAL_TOKEN_HEADER).is_some());
113 assert!(request.headers().get(AUTHORIZATION).is_none());
114 }
115
116 #[test]
117 fn internal_token_header_is_sensitive() {
118 let mut request = http::Request::new(());
119 attach_internal_token_http(&mut request, &SecretString::from("sa.jwt.token"));
120
121 let value = request
122 .headers()
123 .get(INTERNAL_TOKEN_HEADER)
124 .expect("header set");
125 assert!(value.is_sensitive());
126 }
127}