1use std::{future::Future, marker::PhantomData, pin::Pin, sync::Arc};
8
9use http::{Request, Response, StatusCode, header};
10use rskit_security::BEARER_AUTH_SCHEME;
11use tower::{Layer, Service};
12
13use crate::{AuthClaims, AuthOutcome, MissingCredentialPolicy, traits::TokenValidator};
14
15#[derive(Clone)]
22pub struct BearerAuthLayer<V, C> {
23 validator: Arc<V>,
24 missing_policy: MissingCredentialPolicy,
25 _claims: PhantomData<fn() -> C>,
26}
27
28impl<V: 'static, C> BearerAuthLayer<V, C> {
29 #[must_use]
31 pub fn new(validator: V) -> Self {
32 Self {
33 validator: Arc::new(validator),
34 missing_policy: MissingCredentialPolicy::RejectMissing,
35 _claims: PhantomData,
36 }
37 }
38
39 #[must_use]
41 pub const fn accept_missing(mut self) -> Self {
42 self.missing_policy = MissingCredentialPolicy::AcceptMissing;
43 self
44 }
45}
46
47impl<S, V, C> Layer<S> for BearerAuthLayer<V, C>
48where
49 V: 'static,
50{
51 type Service = BearerAuthService<S, V, C>;
52
53 fn layer(&self, inner: S) -> Self::Service {
54 BearerAuthService {
55 inner,
56 validator: Arc::clone(&self.validator),
57 missing_policy: self.missing_policy,
58 _claims: PhantomData,
59 }
60 }
61}
62
63#[derive(Clone)]
65pub struct BearerAuthService<S, V, C> {
66 inner: S,
67 validator: Arc<V>,
68 missing_policy: MissingCredentialPolicy,
69 _claims: PhantomData<fn() -> C>,
70}
71
72impl<S, V, C, ReqBody, ResBody> Service<Request<ReqBody>> for BearerAuthService<S, V, C>
73where
74 S: Service<Request<ReqBody>, Response = Response<ResBody>> + Clone + Send + 'static,
75 S::Future: Send + 'static,
76 V: TokenValidator<C> + 'static,
77 C: Clone + Send + Sync + 'static,
78 ReqBody: Send + 'static,
79 ResBody: Default + Send + 'static,
80{
81 type Response = S::Response;
82 type Error = S::Error;
83 type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
84
85 fn poll_ready(
86 &mut self,
87 cx: &mut std::task::Context<'_>,
88 ) -> std::task::Poll<Result<(), Self::Error>> {
89 self.inner.poll_ready(cx)
90 }
91
92 fn call(&mut self, req: Request<ReqBody>) -> Self::Future {
93 let clone = self.inner.clone();
94 let mut inner = std::mem::replace(&mut self.inner, clone);
95 let validator = Arc::clone(&self.validator);
96 let missing_policy = self.missing_policy;
97
98 Box::pin(async move {
99 match extract_bearer_token(&req) {
100 CredentialExtraction::Missing => {
101 if missing_policy == MissingCredentialPolicy::AcceptMissing {
102 let mut req = req;
103 req.extensions_mut().remove::<AuthClaims<C>>();
104 req.extensions_mut().insert(AuthOutcome::<C>::Missing);
105 inner.call(req).await
106 } else {
107 Ok(unauthorized_bearer_response())
108 }
109 }
110 CredentialExtraction::Invalid => Ok(unauthorized_bearer_response()),
111 CredentialExtraction::Present(token) => match validator.validate(token).await {
112 Ok(claims) => {
113 let mut req = req;
114 req.extensions_mut().insert(AuthClaims(claims.clone()));
115 req.extensions_mut()
116 .insert(AuthOutcome::Authenticated(claims));
117 inner.call(req).await
118 }
119 Err(_) => Ok(unauthorized_bearer_response()),
120 },
121 }
122 })
123 }
124}
125
126enum CredentialExtraction<'a> {
127 Missing,
128 Invalid,
129 Present(&'a str),
130}
131
132fn extract_bearer_token<B>(req: &Request<B>) -> CredentialExtraction<'_> {
133 let mut values = req.headers().get_all(header::AUTHORIZATION).iter();
134 let Some(value) = values.next() else {
135 return CredentialExtraction::Missing;
136 };
137 if values.next().is_some() {
138 return CredentialExtraction::Invalid;
139 }
140 let Ok(value) = value.to_str() else {
141 return CredentialExtraction::Invalid;
142 };
143 let Some((scheme, token)) = value.split_once(' ') else {
144 return CredentialExtraction::Invalid;
145 };
146 if !scheme.eq_ignore_ascii_case(BEARER_AUTH_SCHEME) {
147 return CredentialExtraction::Invalid;
148 }
149 let token = token.trim_start_matches(' ');
150 if token.is_empty() || token.chars().any(char::is_whitespace) {
151 return CredentialExtraction::Invalid;
152 }
153 CredentialExtraction::Present(token)
154}
155
156fn unauthorized_bearer_response<ResBody: Default>() -> Response<ResBody> {
157 let mut response = Response::new(ResBody::default());
158 *response.status_mut() = StatusCode::UNAUTHORIZED;
159 response.headers_mut().insert(
160 header::WWW_AUTHENTICATE,
161 http::HeaderValue::from_static(BEARER_AUTH_SCHEME),
162 );
163 response
164}
165
166#[cfg(test)]
167mod tests {
168 use std::{convert::Infallible, future::Ready};
169
170 use async_trait::async_trait;
171 use http::{Request, Response, StatusCode};
172 use rskit_errors::{AppError, AppResult};
173 use rskit_security::BEARER_AUTH_SCHEME;
174 use serde::{Deserialize, Serialize};
175 use tower::{Layer, Service, ServiceExt};
176
177 use super::{BearerAuthLayer, CredentialExtraction, extract_bearer_token};
178 use crate::{AuthClaims, AuthOutcome, TokenValidator};
179
180 #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
181 struct Claims {
182 sub: String,
183 }
184
185 struct Validator;
186
187 #[async_trait]
188 impl TokenValidator<Claims> for Validator {
189 async fn validate(&self, token: &str) -> AppResult<Claims> {
190 if token == "good.token" {
191 Ok(Claims {
192 sub: "user-1".into(),
193 })
194 } else {
195 Err(AppError::invalid_token())
196 }
197 }
198 }
199
200 #[derive(Clone)]
201 struct ExtensionCheckingService;
202
203 impl Service<Request<()>> for ExtensionCheckingService {
204 type Response = Response<()>;
205 type Error = Infallible;
206 type Future = Ready<Result<Self::Response, Self::Error>>;
207
208 fn poll_ready(
209 &mut self,
210 _cx: &mut std::task::Context<'_>,
211 ) -> std::task::Poll<Result<(), Self::Error>> {
212 std::task::Poll::Ready(Ok(()))
213 }
214
215 fn call(&mut self, request: Request<()>) -> Self::Future {
216 let has_claims = request.extensions().get::<AuthClaims<Claims>>().is_some();
217 let has_outcome = request.extensions().get::<AuthOutcome<Claims>>().is_some();
218 let status = if has_claims && has_outcome {
219 StatusCode::OK
220 } else if matches!(
221 request.extensions().get::<AuthOutcome<Claims>>(),
222 Some(AuthOutcome::Missing)
223 ) {
224 StatusCode::NO_CONTENT
225 } else {
226 StatusCode::IM_A_TEAPOT
227 };
228 std::future::ready(Ok(Response::builder().status(status).body(()).unwrap()))
229 }
230 }
231
232 #[test]
233 fn bearer_extraction_requires_single_authorization_header() {
234 for value in [
235 "Bearer abc.def.ghi",
236 "bearer abc.def.ghi",
237 "Bearer abc.def.ghi",
238 ] {
239 let request = http::Request::builder()
240 .header(http::header::AUTHORIZATION, value)
241 .body(())
242 .unwrap();
243
244 assert!(matches!(
245 extract_bearer_token(&request),
246 CredentialExtraction::Present("abc.def.ghi")
247 ));
248 }
249
250 let request = http::Request::builder()
251 .header(http::header::AUTHORIZATION, "Bearer one")
252 .header(http::header::AUTHORIZATION, "Bearer two")
253 .body(())
254 .unwrap();
255
256 assert!(matches!(
257 extract_bearer_token(&request),
258 CredentialExtraction::Invalid
259 ));
260 }
261
262 #[test]
263 fn bearer_extraction_rejects_missing_or_malformed_values() {
264 let missing = http::Request::builder().body(()).unwrap();
265 assert!(matches!(
266 extract_bearer_token(&missing),
267 CredentialExtraction::Missing
268 ));
269
270 for value in [
271 "Basic token",
272 "Bearer ",
273 "Bearer token with-space",
274 "Bearer\ttoken",
275 " Bearer token",
276 ] {
277 let request = http::Request::builder()
278 .header(http::header::AUTHORIZATION, value)
279 .body(())
280 .unwrap();
281 assert!(matches!(
282 extract_bearer_token(&request),
283 CredentialExtraction::Invalid
284 ));
285 }
286 }
287
288 #[tokio::test]
289 async fn bearer_layer_rejects_missing_by_default_and_accepts_valid_tokens() {
290 let mut service =
291 BearerAuthLayer::<_, Claims>::new(Validator).layer(ExtensionCheckingService);
292
293 let missing = service
294 .ready()
295 .await
296 .unwrap()
297 .call(Request::builder().body(()).unwrap())
298 .await
299 .unwrap();
300 assert_eq!(missing.status(), StatusCode::UNAUTHORIZED);
301 assert_eq!(
302 missing
303 .headers()
304 .get(http::header::WWW_AUTHENTICATE)
305 .expect("missing authenticate challenge")
306 .to_str()
307 .expect("challenge should be visible ASCII"),
308 BEARER_AUTH_SCHEME
309 );
310
311 let valid = service
312 .ready()
313 .await
314 .unwrap()
315 .call(
316 Request::builder()
317 .header(http::header::AUTHORIZATION, "Bearer good.token")
318 .body(())
319 .unwrap(),
320 )
321 .await
322 .unwrap();
323 assert_eq!(valid.status(), StatusCode::OK);
324 }
325
326 #[tokio::test]
327 async fn bearer_layer_accept_missing_is_explicit_and_invalid_still_fails() {
328 let mut service = BearerAuthLayer::<_, Claims>::new(Validator)
329 .accept_missing()
330 .layer(ExtensionCheckingService);
331
332 let missing = service
333 .call(Request::builder().body(()).unwrap())
334 .await
335 .unwrap();
336 assert_eq!(missing.status(), StatusCode::NO_CONTENT);
337
338 let invalid = service
339 .call(
340 Request::builder()
341 .header(http::header::AUTHORIZATION, "Bearer bad.token")
342 .body(())
343 .unwrap(),
344 )
345 .await
346 .unwrap();
347 assert_eq!(invalid.status(), StatusCode::UNAUTHORIZED);
348 }
349
350 #[tokio::test]
351 async fn bearer_layer_accept_missing_clears_stale_claims() {
352 let mut service = BearerAuthLayer::<_, Claims>::new(Validator)
353 .accept_missing()
354 .layer(ExtensionCheckingService);
355 let mut request = Request::builder().body(()).unwrap();
356 request.extensions_mut().insert(AuthClaims(Claims {
357 sub: "stale-user".into(),
358 }));
359
360 let response = service.call(request).await.unwrap();
361
362 assert_eq!(response.status(), StatusCode::NO_CONTENT);
363 }
364}