1use std::collections::HashSet;
38use std::future::Future;
39use std::sync::Arc;
40
41use tower::Layer;
42#[cfg(feature = "http")]
43use tower::ServiceExt;
44
45#[derive(Debug, Clone)]
47#[non_exhaustive]
48pub enum AuthResult {
49 Authenticated(Option<AuthInfo>),
51 Failed(AuthError),
53}
54
55#[derive(Debug, Clone)]
57pub struct AuthInfo {
58 pub client_id: String,
60 pub claims: Option<serde_json::Value>,
62}
63
64#[derive(Debug, Clone)]
66pub struct AuthError {
67 pub code: String,
69 pub message: String,
71}
72
73impl std::fmt::Display for AuthError {
74 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
75 write!(f, "{}: {}", self.code, self.message)
76 }
77}
78
79impl std::error::Error for AuthError {}
80
81pub trait Validate: Clone + Send + Sync + 'static {
119 fn validate(&self, credential: &str) -> impl Future<Output = AuthResult> + Send;
121}
122
123#[derive(Debug, Clone)]
134pub struct ApiKeyValidator {
135 valid_keys: Arc<HashSet<String>>,
136}
137
138impl ApiKeyValidator {
139 pub fn new(keys: impl IntoIterator<Item = String>) -> Self {
141 Self {
142 valid_keys: Arc::new(keys.into_iter().collect()),
143 }
144 }
145
146 pub fn add_key(&mut self, key: String) {
148 Arc::make_mut(&mut self.valid_keys).insert(key);
149 }
150
151 pub fn is_valid(&self, key: &str) -> bool {
153 self.valid_keys.contains(key)
154 }
155}
156
157impl Validate for ApiKeyValidator {
158 async fn validate(&self, key: &str) -> AuthResult {
159 if self.valid_keys.contains(key) {
160 AuthResult::Authenticated(Some(AuthInfo {
161 client_id: format!("api_key:{}", &key[..8.min(key.len())]),
162 claims: None,
163 }))
164 } else {
165 AuthResult::Failed(AuthError {
166 code: "invalid_api_key".to_string(),
167 message: "The provided API key is not valid".to_string(),
168 })
169 }
170 }
171}
172
173#[derive(Debug, Clone)]
184pub struct StaticBearerValidator {
185 valid_tokens: Arc<HashSet<String>>,
186}
187
188impl StaticBearerValidator {
189 pub fn new(tokens: impl IntoIterator<Item = String>) -> Self {
191 Self {
192 valid_tokens: Arc::new(tokens.into_iter().collect()),
193 }
194 }
195}
196
197impl Validate for StaticBearerValidator {
198 async fn validate(&self, token: &str) -> AuthResult {
199 if self.valid_tokens.contains(token) {
200 AuthResult::Authenticated(Some(AuthInfo {
201 client_id: format!("bearer:{}", &token[..8.min(token.len())]),
202 claims: None,
203 }))
204 } else {
205 AuthResult::Failed(AuthError {
206 code: "invalid_token".to_string(),
207 message: "The provided bearer token is not valid".to_string(),
208 })
209 }
210 }
211}
212
213pub fn extract_api_key(auth_header: &str) -> Option<&str> {
224 let auth_header = auth_header.trim();
225
226 if let Some(key) = auth_header.strip_prefix("Bearer ") {
227 Some(key.trim())
228 } else if let Some(key) = auth_header.strip_prefix("ApiKey ") {
229 Some(key.trim())
230 } else if !auth_header.contains(' ') {
231 Some(auth_header)
233 } else {
234 None
235 }
236}
237
238pub fn extract_bearer_token(auth_header: &str) -> Option<&str> {
240 auth_header.trim().strip_prefix("Bearer ").map(|t| t.trim())
241}
242
243#[derive(Clone)]
252pub struct AuthLayer<V> {
253 validator: V,
254 header_name: String,
255}
256
257impl<V> AuthLayer<V> {
258 pub fn new(validator: V) -> Self {
262 Self {
263 validator,
264 header_name: "Authorization".to_string(),
265 }
266 }
267
268 pub fn header_name(mut self, name: impl Into<String>) -> Self {
270 self.header_name = name.into();
271 self
272 }
273}
274
275impl<S, V: Clone> Layer<S> for AuthLayer<V> {
276 type Service = AuthService<S, V>;
277
278 fn layer(&self, inner: S) -> Self::Service {
279 AuthService {
280 inner,
281 validator: self.validator.clone(),
282 header_name: self.header_name.clone(),
283 }
284 }
285}
286
287#[derive(Clone)]
308#[cfg_attr(not(feature = "http"), allow(dead_code))]
309pub struct AuthService<S, V> {
310 inner: S,
311 validator: V,
312 header_name: String,
313}
314
315#[cfg(feature = "http")]
316impl<S, V> tower_service::Service<axum::http::Request<axum::body::Body>> for AuthService<S, V>
317where
318 S: tower_service::Service<
319 axum::http::Request<axum::body::Body>,
320 Response = axum::response::Response,
321 > + Clone
322 + Send
323 + 'static,
324 S::Future: Send,
325 S::Error: Into<crate::BoxError> + Send,
326 V: Validate,
327{
328 type Response = axum::response::Response;
329 type Error = S::Error;
330 type Future =
331 std::pin::Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
332
333 fn poll_ready(
334 &mut self,
335 cx: &mut std::task::Context<'_>,
336 ) -> std::task::Poll<Result<(), Self::Error>> {
337 self.inner.poll_ready(cx)
338 }
339
340 fn call(&mut self, req: axum::http::Request<axum::body::Body>) -> Self::Future {
341 let credential = req
342 .headers()
343 .get(&self.header_name)
344 .and_then(|v| v.to_str().ok())
345 .and_then(extract_api_key)
346 .map(|s| s.to_owned());
347
348 let inner = self.inner.clone();
349 let validator = self.validator.clone();
350
351 Box::pin(async move {
352 let Some(credential) = credential else {
353 return Ok(unauthorized_response(
354 "Missing authentication credentials. Provide via Authorization header.",
355 ));
356 };
357
358 match validator.validate(&credential).await {
359 AuthResult::Authenticated(info) => {
360 let mut req = req;
361 if let Some(info) = info {
362 req.extensions_mut().insert(info);
363 }
364 inner.oneshot(req).await
365 }
366 AuthResult::Failed(err) => Ok(unauthorized_response(&err.message)),
367 }
368 })
369 }
370}
371
372#[cfg(feature = "http")]
378fn unauthorized_response(message: &str) -> axum::response::Response {
379 use axum::http::StatusCode;
380 use axum::response::IntoResponse;
381
382 let body = serde_json::json!({
383 "jsonrpc": "2.0",
384 "error": {
385 "code": tower_mcp_types::McpErrorCode::Forbidden.code(),
386 "message": message
387 },
388 "id": null
389 });
390
391 (StatusCode::UNAUTHORIZED, axum::Json(body)).into_response()
392}
393
394#[derive(Clone)]
400pub struct AuthConfig {
401 pub allow_anonymous: bool,
403 pub public_paths: Vec<String>,
405 pub header_name: String,
407}
408
409impl Default for AuthConfig {
410 fn default() -> Self {
411 Self {
412 allow_anonymous: false,
413 public_paths: Vec::new(),
414 header_name: "Authorization".to_string(),
415 }
416 }
417}
418
419impl AuthConfig {
420 pub fn new() -> Self {
422 Self::default()
423 }
424
425 pub fn allow_anonymous(mut self, allow: bool) -> Self {
427 self.allow_anonymous = allow;
428 self
429 }
430
431 pub fn public_path(mut self, path: impl Into<String>) -> Self {
433 self.public_paths.push(path.into());
434 self
435 }
436
437 pub fn header_name(mut self, name: impl Into<String>) -> Self {
439 self.header_name = name.into();
440 self
441 }
442
443 pub fn is_public(&self, path: &str) -> bool {
445 self.public_paths.iter().any(|p| path.starts_with(p))
446 }
447}
448
449#[cfg(test)]
450mod tests {
451 use super::*;
452
453 #[test]
454 fn test_extract_api_key_bearer() {
455 assert_eq!(extract_api_key("Bearer sk-123"), Some("sk-123"));
456 assert_eq!(extract_api_key("Bearer sk-123 "), Some("sk-123"));
457 }
458
459 #[test]
460 fn test_extract_api_key_apikey_prefix() {
461 assert_eq!(extract_api_key("ApiKey sk-123"), Some("sk-123"));
462 }
463
464 #[test]
465 fn test_extract_api_key_raw() {
466 assert_eq!(extract_api_key("sk-123"), Some("sk-123"));
467 }
468
469 #[test]
470 fn test_extract_api_key_invalid() {
471 assert_eq!(extract_api_key("Basic user:pass"), None);
472 }
473
474 #[test]
475 fn test_extract_bearer_token() {
476 assert_eq!(extract_bearer_token("Bearer abc123"), Some("abc123"));
477 assert_eq!(extract_bearer_token("bearer abc123"), None); assert_eq!(extract_bearer_token("abc123"), None);
479 }
480
481 #[tokio::test]
482 async fn test_api_key_validator() {
483 let validator = ApiKeyValidator::new(vec!["valid-key".to_string()]);
484
485 match validator.validate("valid-key").await {
486 AuthResult::Authenticated(info) => {
487 assert!(info.is_some());
488 }
489 AuthResult::Failed(_) => panic!("Expected authentication to succeed"),
490 }
491
492 match validator.validate("invalid-key").await {
493 AuthResult::Authenticated(_) => panic!("Expected authentication to fail"),
494 AuthResult::Failed(err) => {
495 assert_eq!(err.code, "invalid_api_key");
496 }
497 }
498 }
499
500 #[tokio::test]
501 async fn test_bearer_validator() {
502 let validator = StaticBearerValidator::new(vec!["token123".to_string()]);
503
504 match validator.validate("token123").await {
505 AuthResult::Authenticated(info) => {
506 assert!(info.is_some());
507 }
508 AuthResult::Failed(_) => panic!("Expected authentication to succeed"),
509 }
510
511 match validator.validate("bad-token").await {
512 AuthResult::Authenticated(_) => panic!("Expected authentication to fail"),
513 AuthResult::Failed(err) => {
514 assert_eq!(err.code, "invalid_token");
515 }
516 }
517 }
518
519 #[test]
520 fn test_auth_config() {
521 let config = AuthConfig::new()
522 .allow_anonymous(false)
523 .public_path("/health")
524 .public_path("/metrics")
525 .header_name("X-API-Key");
526
527 assert!(!config.allow_anonymous);
528 assert!(config.is_public("/health"));
529 assert!(config.is_public("/metrics/cpu"));
530 assert!(!config.is_public("/api/tools"));
531 assert_eq!(config.header_name, "X-API-Key");
532 }
533
534 #[test]
535 fn test_auth_layer_creates_service() {
536 let validator = ApiKeyValidator::new(vec!["key".to_string()]);
537 let layer = AuthLayer::new(validator);
538 let _service: AuthService<(), ApiKeyValidator> = layer.layer(());
540 }
541
542 #[cfg(feature = "http")]
543 mod http_tests {
544 use super::*;
545 use std::pin::Pin;
546 use std::task::{Context, Poll};
547
548 use axum::body::Body;
549 use axum::http::{Request, StatusCode};
550 use tower::ServiceExt;
551 use tower_service::Service;
552
553 #[derive(Clone)]
555 struct OkService;
556
557 impl Service<Request<Body>> for OkService {
558 type Response = axum::response::Response;
559 type Error = std::convert::Infallible;
560 type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
561
562 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
563 Poll::Ready(Ok(()))
564 }
565
566 fn call(&mut self, _req: Request<Body>) -> Self::Future {
567 Box::pin(async {
568 Ok(axum::response::Response::builder()
569 .status(StatusCode::OK)
570 .body(Body::empty())
571 .unwrap())
572 })
573 }
574 }
575
576 #[tokio::test]
577 async fn test_auth_service_rejects_missing_credentials() {
578 let validator = ApiKeyValidator::new(vec!["sk-test-123".to_string()]);
579 let layer = AuthLayer::new(validator);
580 let mut service = layer.layer(OkService);
581
582 let req = Request::builder().uri("/").body(Body::empty()).unwrap();
583
584 let resp = service.ready().await.unwrap().call(req).await.unwrap();
585 assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
586 }
587
588 #[tokio::test]
589 async fn test_auth_service_rejects_invalid_key() {
590 let validator = ApiKeyValidator::new(vec!["sk-test-123".to_string()]);
591 let layer = AuthLayer::new(validator);
592 let mut service = layer.layer(OkService);
593
594 let req = Request::builder()
595 .uri("/")
596 .header("Authorization", "Bearer sk-wrong-key")
597 .body(Body::empty())
598 .unwrap();
599
600 let resp = service.ready().await.unwrap().call(req).await.unwrap();
601 assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
602 }
603
604 #[tokio::test]
605 async fn test_auth_service_accepts_valid_key() {
606 let validator = ApiKeyValidator::new(vec!["sk-test-123".to_string()]);
607 let layer = AuthLayer::new(validator);
608 let mut service = layer.layer(OkService);
609
610 let req = Request::builder()
611 .uri("/")
612 .header("Authorization", "Bearer sk-test-123")
613 .body(Body::empty())
614 .unwrap();
615
616 let resp = service.ready().await.unwrap().call(req).await.unwrap();
617 assert_eq!(resp.status(), StatusCode::OK);
618 }
619
620 #[tokio::test]
621 async fn test_auth_service_injects_auth_info() {
622 let validator = ApiKeyValidator::new(vec!["sk-test-123".to_string()]);
623 let layer = AuthLayer::new(validator);
624
625 #[derive(Clone)]
627 struct CheckAuthInfo;
628
629 impl Service<Request<Body>> for CheckAuthInfo {
630 type Response = axum::response::Response;
631 type Error = std::convert::Infallible;
632 type Future =
633 Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
634
635 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
636 Poll::Ready(Ok(()))
637 }
638
639 fn call(&mut self, req: Request<Body>) -> Self::Future {
640 let has_auth = req.extensions().get::<AuthInfo>().is_some();
641 Box::pin(async move {
642 let status = if has_auth {
643 StatusCode::OK
644 } else {
645 StatusCode::INTERNAL_SERVER_ERROR
646 };
647 Ok(axum::response::Response::builder()
648 .status(status)
649 .body(Body::empty())
650 .unwrap())
651 })
652 }
653 }
654
655 let mut service = layer.layer(CheckAuthInfo);
656
657 let req = Request::builder()
658 .uri("/")
659 .header("Authorization", "Bearer sk-test-123")
660 .body(Body::empty())
661 .unwrap();
662
663 let resp = service.ready().await.unwrap().call(req).await.unwrap();
664 assert_eq!(resp.status(), StatusCode::OK);
665 }
666
667 #[tokio::test]
668 async fn test_auth_service_custom_header() {
669 let validator = ApiKeyValidator::new(vec!["my-key".to_string()]);
670 let layer = AuthLayer::new(validator).header_name("X-API-Key");
671 let mut service = layer.layer(OkService);
672
673 let req = Request::builder()
675 .uri("/")
676 .header("Authorization", "Bearer my-key")
677 .body(Body::empty())
678 .unwrap();
679 let resp = service.ready().await.unwrap().call(req).await.unwrap();
680 assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
681
682 let req = Request::builder()
684 .uri("/")
685 .header("X-API-Key", "my-key")
686 .body(Body::empty())
687 .unwrap();
688 let resp = service.ready().await.unwrap().call(req).await.unwrap();
689 assert_eq!(resp.status(), StatusCode::OK);
690 }
691 }
692}