1pub mod bearer;
18pub mod mtls;
19pub mod oauth;
20
21#[cfg(feature = "jwt")]
22pub mod jwt;
23
24#[cfg(feature = "oauth-pkce")]
25pub mod pkce;
26
27#[cfg(feature = "oauth-pkce-server")]
28pub mod pkce_server;
29
30use std::collections::BTreeMap;
31use std::sync::Arc;
32
33use crate::errors::RpcError;
34
35#[derive(Clone, Debug, Default)]
41pub struct AuthContext {
42 pub domain: String,
45 pub authenticated: bool,
47 pub principal: String,
49 pub claims: BTreeMap<String, String>,
51}
52
53impl AuthContext {
54 pub fn anonymous() -> Self {
56 Self::default()
57 }
58
59 pub fn for_principal(domain: impl Into<String>, principal: impl Into<String>) -> Self {
61 Self {
62 domain: domain.into(),
63 authenticated: true,
64 principal: principal.into(),
65 claims: BTreeMap::new(),
66 }
67 }
68
69 pub fn require_authenticated(&self) -> crate::errors::Result<()> {
71 if self.authenticated {
72 Ok(())
73 } else {
74 Err(RpcError::permission_error("authentication required"))
75 }
76 }
77
78 pub fn with_claim(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
80 self.claims.insert(key.into(), value.into());
81 self
82 }
83}
84
85#[derive(Debug)]
90pub struct AuthRequest<'a> {
91 pub method: &'a str,
92 pub headers: &'a [(String, String)],
93 pub peer_addr: Option<&'a str>,
94}
95
96impl<'a> AuthRequest<'a> {
97 pub fn anonymous_pipe(method: &'a str) -> Self {
99 Self {
100 method,
101 headers: &[],
102 peer_addr: None,
103 }
104 }
105
106 pub fn header(&self, name: &str) -> Option<&str> {
108 self.headers
109 .iter()
110 .find(|(k, _)| k.eq_ignore_ascii_case(name))
111 .map(|(_, v)| v.as_str())
112 }
113}
114
115pub type AuthResult = std::result::Result<AuthContext, RpcError>;
120
121pub type Authenticate = Arc<dyn Fn(&AuthRequest<'_>) -> AuthResult + Send + Sync>;
126
127pub fn chain_authenticate(a: Authenticate, b: Authenticate) -> Authenticate {
133 Arc::new(move |req| {
134 let first = (a)(req)?;
135 if first.authenticated {
136 return Ok(first);
137 }
138 (b)(req)
139 })
140}
141
142pub(crate) fn extract_bearer<'a>(req: &'a AuthRequest<'a>) -> Option<&'a str> {
147 let h = req.header("authorization")?;
148 let prefix = "Bearer ";
149 if h.len() > prefix.len() && h[..prefix.len()].eq_ignore_ascii_case(prefix) {
150 let tok = h[prefix.len()..].trim();
151 (!tok.is_empty()).then_some(tok)
152 } else {
153 None
154 }
155}
156
157pub fn chain_all<I: IntoIterator<Item = Authenticate>>(cbs: I) -> Option<Authenticate> {
159 let mut it = cbs.into_iter();
160 let mut acc = it.next()?;
161 for next in it {
162 acc = chain_authenticate(acc, next);
163 }
164 Some(acc)
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170
171 #[test]
172 fn require_authenticated_rejects_anonymous() {
173 let anon = AuthContext::anonymous();
174 assert!(anon.require_authenticated().is_err());
175 let authd = AuthContext::for_principal("bearer", "alice");
176 assert!(authd.require_authenticated().is_ok());
177 }
178
179 #[test]
180 fn chain_tries_second_when_first_anonymous() {
181 let a: Authenticate = Arc::new(|_| Ok(AuthContext::anonymous()));
182 let b: Authenticate = Arc::new(|_| Ok(AuthContext::for_principal("bearer", "alice")));
183 let chain = chain_authenticate(a, b);
184 let req = AuthRequest::anonymous_pipe("echo");
185 let ctx = chain(&req).unwrap();
186 assert_eq!(ctx.principal, "alice");
187 }
188
189 #[test]
190 fn chain_uses_first_when_authenticated() {
191 let a: Authenticate = Arc::new(|_| Ok(AuthContext::for_principal("mtls", "bob")));
192 let b: Authenticate = Arc::new(|_| Ok(AuthContext::for_principal("bearer", "alice")));
193 let chain = chain_authenticate(a, b);
194 let req = AuthRequest::anonymous_pipe("echo");
195 assert_eq!(chain(&req).unwrap().principal, "bob");
196 }
197}