1use crate::marker::{MaybeSend, MaybeSync};
47use alloc::collections::BTreeMap;
48use alloc::string::{String, ToString};
49use alloc::vec;
50use alloc::vec::Vec;
51use core::fmt;
52use core::future::Future;
53use serde::{Deserialize, Serialize};
54
55#[derive(Debug, Clone, PartialEq, Eq)]
60pub enum Credential {
61 Bearer(String),
64
65 ApiKey(String),
68
69 Basic {
72 username: String,
74 password: String,
76 },
77
78 Custom {
81 scheme: String,
83 value: String,
85 },
86}
87
88impl Credential {
89 pub fn bearer(token: impl Into<String>) -> Self {
91 Self::Bearer(token.into())
92 }
93
94 pub fn api_key(key: impl Into<String>) -> Self {
96 Self::ApiKey(key.into())
97 }
98
99 pub fn basic(username: impl Into<String>, password: impl Into<String>) -> Self {
101 Self::Basic {
102 username: username.into(),
103 password: password.into(),
104 }
105 }
106
107 pub fn custom(scheme: impl Into<String>, value: impl Into<String>) -> Self {
109 Self::Custom {
110 scheme: scheme.into(),
111 value: value.into(),
112 }
113 }
114
115 pub fn is_bearer(&self) -> bool {
117 matches!(self, Self::Bearer(_))
118 }
119
120 pub fn as_bearer(&self) -> Option<&str> {
122 match self {
123 Self::Bearer(token) => Some(token),
124 _ => None,
125 }
126 }
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct Principal {
135 pub subject: String,
137
138 #[serde(skip_serializing_if = "Option::is_none")]
140 pub issuer: Option<String>,
141
142 #[serde(skip_serializing_if = "Option::is_none")]
144 pub audience: Option<String>,
145
146 #[serde(skip_serializing_if = "Option::is_none")]
148 pub expires_at: Option<u64>,
149
150 #[serde(skip_serializing_if = "Option::is_none")]
152 pub email: Option<String>,
153
154 #[serde(skip_serializing_if = "Option::is_none")]
156 pub name: Option<String>,
157
158 #[serde(default, skip_serializing_if = "Vec::is_empty")]
160 pub roles: Vec<String>,
161
162 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
164 pub claims: BTreeMap<String, serde_json::Value>,
165}
166
167impl Principal {
168 pub fn new(subject: impl Into<String>) -> Self {
170 Self {
171 subject: subject.into(),
172 issuer: None,
173 audience: None,
174 expires_at: None,
175 email: None,
176 name: None,
177 roles: Vec::new(),
178 claims: BTreeMap::new(),
179 }
180 }
181
182 pub fn with_issuer(mut self, issuer: impl Into<String>) -> Self {
184 self.issuer = Some(issuer.into());
185 self
186 }
187
188 pub fn with_audience(mut self, audience: impl Into<String>) -> Self {
190 self.audience = Some(audience.into());
191 self
192 }
193
194 pub fn with_expires_at(mut self, expires_at: u64) -> Self {
196 self.expires_at = Some(expires_at);
197 self
198 }
199
200 pub fn with_email(mut self, email: impl Into<String>) -> Self {
202 self.email = Some(email.into());
203 self
204 }
205
206 pub fn with_name(mut self, name: impl Into<String>) -> Self {
208 self.name = Some(name.into());
209 self
210 }
211
212 pub fn with_role(mut self, role: impl Into<String>) -> Self {
214 self.roles.push(role.into());
215 self
216 }
217
218 pub fn with_roles(mut self, roles: impl IntoIterator<Item = impl Into<String>>) -> Self {
220 self.roles.extend(roles.into_iter().map(Into::into));
221 self
222 }
223
224 pub fn with_claim(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
226 self.claims.insert(key.into(), value);
227 self
228 }
229
230 pub fn has_role(&self, role: &str) -> bool {
232 self.roles.iter().any(|r| r == role)
233 }
234
235 pub fn has_any_role(&self, roles: &[&str]) -> bool {
237 roles.iter().any(|r| self.has_role(r))
238 }
239
240 pub fn is_expired(&self) -> bool {
246 #[cfg(feature = "std")]
247 {
248 if let Some(exp) = self.expires_at {
249 use std::time::{SystemTime, UNIX_EPOCH};
250 let now = SystemTime::now()
251 .duration_since(UNIX_EPOCH)
252 .map(|d| d.as_secs())
253 .unwrap_or(0);
254 now > exp
255 } else {
256 false
257 }
258 }
259 #[cfg(not(feature = "std"))]
260 {
261 let _ = self.expires_at;
264 false
265 }
266 }
267
268 pub fn get_claim(&self, key: &str) -> Option<&serde_json::Value> {
270 self.claims.get(key)
271 }
272}
273
274impl fmt::Display for Principal {
275 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
276 write!(f, "Principal({})", self.subject)
277 }
278}
279
280#[derive(Debug, Clone)]
282pub enum AuthError {
283 MissingCredentials,
285
286 InvalidCredentialFormat(String),
288
289 UnsupportedCredentialType,
291
292 TokenExpired,
294
295 InvalidSignature,
297
298 InvalidClaims(String),
300
301 InvalidIssuer {
303 expected: String,
305 actual: String,
307 },
308
309 InvalidAudience {
311 expected: String,
313 actual: String,
315 },
316
317 KeyNotFound(String),
319
320 KeyFetchError(String),
322
323 Internal(String),
325}
326
327impl fmt::Display for AuthError {
328 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
329 match self {
330 Self::MissingCredentials => write!(f, "No credentials provided"),
331 Self::InvalidCredentialFormat(msg) => write!(f, "Invalid credential format: {}", msg),
332 Self::UnsupportedCredentialType => write!(f, "Unsupported credential type"),
333 Self::TokenExpired => write!(f, "Token has expired"),
334 Self::InvalidSignature => write!(f, "Invalid token signature"),
335 Self::InvalidClaims(msg) => write!(f, "Invalid claims: {}", msg),
336 Self::InvalidIssuer { expected, actual } => {
337 write!(
338 f,
339 "Invalid issuer: expected '{}', got '{}'",
340 expected, actual
341 )
342 }
343 Self::InvalidAudience { expected, actual } => {
344 write!(
345 f,
346 "Invalid audience: expected '{}', got '{}'",
347 expected, actual
348 )
349 }
350 Self::KeyNotFound(kid) => write!(f, "Key not found: {}", kid),
351 Self::KeyFetchError(msg) => write!(f, "Failed to fetch keys: {}", msg),
352 Self::Internal(msg) => write!(f, "Internal auth error: {}", msg),
353 }
354 }
355}
356
357#[cfg(feature = "std")]
358impl std::error::Error for AuthError {}
359
360pub trait Authenticator: MaybeSend + MaybeSync + Clone {
399 type Error: fmt::Debug + fmt::Display + MaybeSend;
401
402 fn authenticate(
413 &self,
414 credential: &Credential,
415 ) -> impl Future<Output = Result<Principal, Self::Error>> + MaybeSend;
416}
417
418pub trait CredentialExtractor: MaybeSend + MaybeSync {
430 fn extract<F>(&self, get_header: F) -> Option<Credential>
441 where
442 F: Fn(&str) -> Option<String>;
443}
444
445#[derive(Debug, Clone, Copy, Default)]
447pub struct HeaderExtractor;
448
449impl CredentialExtractor for HeaderExtractor {
450 fn extract<F>(&self, get_header: F) -> Option<Credential>
451 where
452 F: Fn(&str) -> Option<String>,
453 {
454 if let Some(auth) = get_header("authorization") {
456 let auth = auth.trim();
457
458 if let Some(token) = auth
460 .strip_prefix("Bearer ")
461 .or_else(|| auth.strip_prefix("bearer "))
462 {
463 return Some(Credential::Bearer(token.trim().to_string()));
464 }
465
466 #[cfg(feature = "std")]
468 if let Some(encoded) = auth
469 .strip_prefix("Basic ")
470 .or_else(|| auth.strip_prefix("basic "))
471 {
472 use base64::Engine;
473 if let Ok(decoded) =
474 base64::engine::general_purpose::STANDARD.decode(encoded.trim())
475 && let Ok(decoded_str) = String::from_utf8(decoded)
476 && let Some((username, password)) = decoded_str.split_once(':')
477 {
478 return Some(Credential::Basic {
479 username: username.to_string(),
480 password: password.to_string(),
481 });
482 }
483 }
484
485 if let Some(key) = auth
487 .strip_prefix("ApiKey ")
488 .or_else(|| auth.strip_prefix("apikey "))
489 {
490 return Some(Credential::ApiKey(key.trim().to_string()));
491 }
492 }
493
494 if let Some(key) = get_header("x-api-key") {
496 return Some(Credential::ApiKey(key.trim().to_string()));
497 }
498
499 None
500 }
501}
502
503#[derive(Debug, Clone)]
526pub struct JwtConfig {
527 pub issuer: Option<String>,
529
530 pub audience: Option<String>,
532
533 pub algorithms: Vec<JwtAlgorithm>,
539
540 pub leeway_seconds: u64,
542
543 pub validate_exp: bool,
545
546 pub validate_nbf: bool,
548}
549
550impl JwtConfig {
551 #[allow(clippy::new_without_default)]
566 pub fn new() -> Self {
567 Self {
568 issuer: None,
569 audience: None,
570 algorithms: vec![JwtAlgorithm::RS256, JwtAlgorithm::ES256],
571 leeway_seconds: 60,
572 validate_exp: true,
573 validate_nbf: true,
574 }
575 }
576
577 pub fn issuer(mut self, issuer: impl Into<String>) -> Self {
579 self.issuer = Some(issuer.into());
580 self
581 }
582
583 pub fn audience(mut self, audience: impl Into<String>) -> Self {
585 self.audience = Some(audience.into());
586 self
587 }
588
589 pub fn algorithms(mut self, algorithms: Vec<JwtAlgorithm>) -> Self {
591 self.algorithms = algorithms;
592 self
593 }
594
595 pub fn leeway_seconds(mut self, seconds: u64) -> Self {
597 self.leeway_seconds = seconds;
598 self
599 }
600
601 pub fn skip_exp_validation(mut self) -> Self {
603 self.validate_exp = false;
604 self
605 }
606
607 pub fn skip_nbf_validation(mut self) -> Self {
609 self.validate_nbf = false;
610 self
611 }
612}
613
614#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
618#[serde(rename_all = "UPPERCASE")]
619pub enum JwtAlgorithm {
620 HS256,
622 HS384,
624 HS512,
626 RS256,
628 RS384,
630 RS512,
632 ES256,
634 ES384,
636}
637
638impl JwtAlgorithm {
639 pub fn as_str(&self) -> &'static str {
641 match self {
642 Self::HS256 => "HS256",
643 Self::HS384 => "HS384",
644 Self::HS512 => "HS512",
645 Self::RS256 => "RS256",
646 Self::RS384 => "RS384",
647 Self::RS512 => "RS512",
648 Self::ES256 => "ES256",
649 Self::ES384 => "ES384",
650 }
651 }
652
653 pub fn parse(s: &str) -> Option<Self> {
658 s.parse().ok()
659 }
660
661 pub fn is_asymmetric(&self) -> bool {
663 matches!(
664 self,
665 Self::RS256 | Self::RS384 | Self::RS512 | Self::ES256 | Self::ES384
666 )
667 }
668
669 pub fn is_symmetric(&self) -> bool {
671 matches!(self, Self::HS256 | Self::HS384 | Self::HS512)
672 }
673}
674
675impl fmt::Display for JwtAlgorithm {
676 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
677 write!(f, "{}", self.as_str())
678 }
679}
680
681impl core::str::FromStr for JwtAlgorithm {
682 type Err = ();
683
684 fn from_str(s: &str) -> Result<Self, Self::Err> {
685 match s.to_uppercase().as_str() {
686 "HS256" => Ok(Self::HS256),
687 "HS384" => Ok(Self::HS384),
688 "HS512" => Ok(Self::HS512),
689 "RS256" => Ok(Self::RS256),
690 "RS384" => Ok(Self::RS384),
691 "RS512" => Ok(Self::RS512),
692 "ES256" => Ok(Self::ES256),
693 "ES384" => Ok(Self::ES384),
694 _ => Err(()),
695 }
696 }
697}
698
699#[derive(Debug, Clone, Default, Serialize, Deserialize)]
703pub struct StandardClaims {
704 #[serde(skip_serializing_if = "Option::is_none")]
706 pub sub: Option<String>,
707
708 #[serde(skip_serializing_if = "Option::is_none")]
710 pub iss: Option<String>,
711
712 #[serde(skip_serializing_if = "Option::is_none")]
714 pub aud: Option<Audience>,
715
716 #[serde(skip_serializing_if = "Option::is_none")]
718 pub exp: Option<u64>,
719
720 #[serde(skip_serializing_if = "Option::is_none")]
722 pub nbf: Option<u64>,
723
724 #[serde(skip_serializing_if = "Option::is_none")]
726 pub iat: Option<u64>,
727
728 #[serde(skip_serializing_if = "Option::is_none")]
730 pub jti: Option<String>,
731}
732
733#[derive(Debug, Clone, Serialize, Deserialize)]
735#[serde(untagged)]
736pub enum Audience {
737 Single(String),
739 Multiple(Vec<String>),
741}
742
743impl Audience {
744 pub fn contains(&self, expected: &str) -> bool {
746 match self {
747 Self::Single(s) => s == expected,
748 Self::Multiple(v) => v.iter().any(|s| s == expected),
749 }
750 }
751
752 pub fn to_vec(&self) -> Vec<String> {
754 match self {
755 Self::Single(s) => vec![s.clone()],
756 Self::Multiple(v) => v.clone(),
757 }
758 }
759}
760
761#[cfg(test)]
762mod tests {
763 use super::*;
764
765 #[test]
766 fn test_credential_constructors() {
767 let bearer = Credential::bearer("token123");
768 assert!(bearer.is_bearer());
769 assert_eq!(bearer.as_bearer(), Some("token123"));
770
771 let api_key = Credential::api_key("key456");
772 assert!(!api_key.is_bearer());
773 assert_eq!(api_key.as_bearer(), None);
774
775 let basic = Credential::basic("user", "pass");
776 assert!(!basic.is_bearer());
777 }
778
779 #[test]
780 fn test_principal_builder() {
781 let principal = Principal::new("user123")
782 .with_issuer("https://auth.example.com")
783 .with_audience("my-api")
784 .with_email("user@example.com")
785 .with_role("admin")
786 .with_role("user");
787
788 assert_eq!(principal.subject, "user123");
789 assert_eq!(
790 principal.issuer,
791 Some("https://auth.example.com".to_string())
792 );
793 assert!(principal.has_role("admin"));
794 assert!(principal.has_role("user"));
795 assert!(!principal.has_role("guest"));
796 assert!(principal.has_any_role(&["admin", "guest"]));
797 }
798
799 #[test]
800 fn test_header_extractor_bearer() {
801 let extractor = HeaderExtractor;
802
803 let cred = extractor.extract(|name| {
804 if name == "authorization" {
805 Some("Bearer my-token".to_string())
806 } else {
807 None
808 }
809 });
810
811 assert_eq!(cred, Some(Credential::Bearer("my-token".to_string())));
812 }
813
814 #[test]
815 fn test_header_extractor_api_key() {
816 let extractor = HeaderExtractor;
817
818 let cred = extractor.extract(|name| {
820 if name == "x-api-key" {
821 Some("my-api-key".to_string())
822 } else {
823 None
824 }
825 });
826
827 assert_eq!(cred, Some(Credential::ApiKey("my-api-key".to_string())));
828
829 let cred2 = extractor.extract(|name| {
831 if name == "authorization" {
832 Some("ApiKey another-key".to_string())
833 } else {
834 None
835 }
836 });
837
838 assert_eq!(cred2, Some(Credential::ApiKey("another-key".to_string())));
839 }
840
841 #[test]
842 fn test_jwt_algorithm() {
843 assert_eq!(JwtAlgorithm::RS256.as_str(), "RS256");
844 assert!(JwtAlgorithm::RS256.is_asymmetric());
845 assert!(!JwtAlgorithm::RS256.is_symmetric());
846
847 assert!(JwtAlgorithm::HS256.is_symmetric());
848 assert!(!JwtAlgorithm::HS256.is_asymmetric());
849
850 assert_eq!(JwtAlgorithm::parse("es256"), Some(JwtAlgorithm::ES256));
851 assert_eq!(JwtAlgorithm::parse("unknown"), None);
852 }
853
854 #[test]
855 fn test_audience() {
856 let single = Audience::Single("my-api".to_string());
857 assert!(single.contains("my-api"));
858 assert!(!single.contains("other"));
859
860 let multiple = Audience::Multiple(vec!["api1".to_string(), "api2".to_string()]);
861 assert!(multiple.contains("api1"));
862 assert!(multiple.contains("api2"));
863 assert!(!multiple.contains("api3"));
864 }
865
866 #[test]
867 fn test_jwt_config_builder() {
868 let config = JwtConfig::new()
869 .issuer("https://auth.example.com")
870 .audience("my-api")
871 .algorithms(vec![JwtAlgorithm::RS256])
872 .leeway_seconds(120);
873
874 assert_eq!(config.issuer, Some("https://auth.example.com".to_string()));
875 assert_eq!(config.audience, Some("my-api".to_string()));
876 assert_eq!(config.algorithms, vec![JwtAlgorithm::RS256]);
877 assert_eq!(config.leeway_seconds, 120);
878 }
879}