camel_auth/
token_authenticator.rs1use async_trait::async_trait;
2use camel_api::CamelError;
3use camel_api::security_policy::Principal;
4
5use crate::jwt::JwtValidator;
6
7#[async_trait]
13pub trait TokenAuthenticator: Send + Sync {
14 async fn authenticate_bearer(&self, token: &str) -> Result<Principal, CamelError>;
16}
17
18#[async_trait]
19impl<T: JwtValidator> TokenAuthenticator for T {
20 async fn authenticate_bearer(&self, token: &str) -> Result<Principal, CamelError> {
21 self.validate(token).await.map_err(CamelError::from)
22 }
23}
24
25#[cfg(test)]
26mod tests {
27 use super::*;
28 use crate::types::AuthError;
29 use serde_json::json;
30
31 struct MockValidator {
32 principal: Option<Principal>,
33 should_fail: bool,
34 }
35
36 #[async_trait]
37 impl JwtValidator for MockValidator {
38 async fn validate(&self, _token: &str) -> Result<Principal, AuthError> {
39 if self.should_fail {
40 return Err(AuthError::TokenInvalid("bad token".into()));
41 }
42 self.principal
43 .clone()
44 .ok_or_else(|| AuthError::TokenInvalid("no principal".into()))
45 }
46 }
47
48 fn test_principal() -> Principal {
49 Principal {
50 subject: "user1".into(),
51 issuer: "test-issuer".into(),
52 audience: vec!["api".into()],
53 scopes: vec!["read".into()],
54 roles: vec!["admin".into()],
55 claims: json!({"sub": "user1"}),
56 }
57 }
58
59 #[tokio::test]
60 async fn test_authenticate_bearer_success() {
61 let validator = MockValidator {
62 principal: Some(test_principal()),
63 should_fail: false,
64 };
65 let result = validator.authenticate_bearer("valid-token").await;
66 assert!(result.is_ok());
67 assert_eq!(result.unwrap().subject, "user1");
68 }
69
70 #[tokio::test]
71 async fn test_authenticate_bearer_invalid_token() {
72 let validator = MockValidator {
73 principal: None,
74 should_fail: true,
75 };
76 let err = validator.authenticate_bearer("bad").await.unwrap_err();
77 match err {
78 CamelError::Unauthenticated(msg) => assert!(msg.contains("bad token")),
79 _ => panic!("expected Unauthenticated, got: {err:?}"),
80 }
81 }
82
83 #[tokio::test]
84 async fn test_authenticate_bearer_provider_unavailable() {
85 struct UnavailableValidator;
86 #[async_trait]
87 impl JwtValidator for UnavailableValidator {
88 async fn validate(&self, _token: &str) -> Result<Principal, AuthError> {
89 Err(AuthError::ProviderUnavailable("connection refused".into()))
90 }
91 }
92 let err = UnavailableValidator
93 .authenticate_bearer("token")
94 .await
95 .unwrap_err();
96 match err {
97 CamelError::ProcessorError(msg) => assert!(msg.contains("auth provider unavailable")),
98 _ => panic!("expected ProcessorError, got: {err:?}"),
99 }
100 }
101
102 #[tokio::test]
103 async fn test_authenticate_bearer_token_expired() {
104 struct ExpiredValidator;
105 #[async_trait]
106 impl JwtValidator for ExpiredValidator {
107 async fn validate(&self, _token: &str) -> Result<Principal, AuthError> {
108 Err(AuthError::TokenExpired)
109 }
110 }
111 let err = ExpiredValidator
112 .authenticate_bearer("expired-token")
113 .await
114 .unwrap_err();
115 match err {
116 CamelError::Unauthenticated(msg) => assert!(msg.contains("token expired")),
117 _ => panic!("expected Unauthenticated, got: {err:?}"),
118 }
119 }
120
121 #[tokio::test]
122 async fn test_authenticate_bearer_unauthorized() {
123 struct UnauthorizedValidator;
124 #[async_trait]
125 impl JwtValidator for UnauthorizedValidator {
126 async fn validate(&self, _token: &str) -> Result<Principal, AuthError> {
127 Err(AuthError::Unauthorized("insufficient permissions".into()))
128 }
129 }
130 let err = UnauthorizedValidator
131 .authenticate_bearer("token")
132 .await
133 .unwrap_err();
134 match err {
135 CamelError::Unauthorized(msg) => assert!(msg.contains("insufficient permissions")),
136 _ => panic!("expected Unauthorized, got: {err:?}"),
137 }
138 }
139
140 #[tokio::test]
141 async fn test_authenticate_bearer_config_error() {
142 struct ConfigErrorValidator;
143 #[async_trait]
144 impl JwtValidator for ConfigErrorValidator {
145 async fn validate(&self, _token: &str) -> Result<Principal, AuthError> {
146 Err(AuthError::ConfigError("missing issuer".into()))
147 }
148 }
149 let err = ConfigErrorValidator
150 .authenticate_bearer("token")
151 .await
152 .unwrap_err();
153 match err {
154 CamelError::Config(msg) => assert!(msg.contains("missing issuer")),
155 _ => panic!("expected Config, got: {err:?}"),
156 }
157 }
158}