cratestack_core/
context.rs1mod identity;
6mod principal;
7
8#[cfg(test)]
9mod tests;
10
11use std::collections::BTreeMap;
12
13use serde::{Deserialize, Serialize};
14
15use crate::error::CoolError;
16use crate::value::Value;
17
18pub use identity::CoolAuthIdentity;
19pub use principal::{PrincipalContext, PrincipalFacet};
20
21use principal::lookup_value_path_in_map;
22
23#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
24pub struct CoolContext {
25 pub auth: Option<CoolAuthIdentity>,
26 pub principal: Option<PrincipalContext>,
27 pub extensions: BTreeMap<String, Value>,
28}
29
30#[derive(Debug, Clone, Copy)]
31pub struct RequestContext<'a> {
32 pub method: &'a str,
33 pub path: &'a str,
34 pub query: Option<&'a str>,
35 pub headers: &'a http::HeaderMap,
36 pub body: &'a [u8],
37}
38
39pub trait AuthProvider: Clone + Send + Sync + 'static {
40 type Error: Into<CoolError> + Send;
41
42 fn authenticate(
43 &self,
44 request: &RequestContext<'_>,
45 ) -> impl ::core::future::Future<Output = Result<CoolContext, Self::Error>> + Send;
46}
47
48impl<F, E> AuthProvider for F
49where
50 F: Clone + Send + Sync + 'static + for<'a> Fn(&'a http::HeaderMap) -> Result<CoolContext, E>,
51 E: Into<CoolError> + Send,
52{
53 type Error = E;
54
55 fn authenticate(
56 &self,
57 request: &RequestContext<'_>,
58 ) -> impl ::core::future::Future<Output = Result<CoolContext, Self::Error>> + Send {
59 let result = (self)(request.headers);
60 ::core::future::ready(result)
61 }
62}
63
64impl CoolContext {
65 pub fn anonymous() -> Self {
66 Self::default()
67 }
68
69 pub fn authenticated(fields: impl IntoIterator<Item = (String, Value)>) -> Self {
70 let fields = fields.into_iter().collect::<BTreeMap<_, _>>();
71 Self {
72 auth: Some(CoolAuthIdentity {
73 fields: fields.clone(),
74 }),
75 principal: Some(PrincipalContext::from_claims(fields)),
76 extensions: BTreeMap::new(),
77 }
78 }
79
80 pub fn is_authenticated(&self) -> bool {
81 self.auth.is_some() || self.principal.is_some()
82 }
83
84 pub fn auth_field(&self, name: &str) -> Option<&Value> {
85 if let Some(auth) = self.auth.as_ref()
86 && let Some(value) = auth
87 .fields
88 .get(name)
89 .or_else(|| lookup_value_path_in_map(&auth.fields, name))
90 {
91 return Some(value);
92 }
93
94 self.principal
95 .as_ref()
96 .and_then(|principal| principal.field(name))
97 }
98
99 pub fn from_principal<P: Serialize>(principal: Option<P>) -> Result<Self, CoolError> {
100 let Some(principal) = principal else {
101 return Ok(Self::anonymous());
102 };
103
104 let principal = PrincipalContext::from_principal(principal)?;
105 let auth = principal.as_auth_identity();
106 Ok(Self {
107 auth: Some(auth),
108 principal: Some(principal),
109 extensions: BTreeMap::new(),
110 })
111 }
112
113 pub fn with_principal(principal: PrincipalContext) -> Self {
114 Self {
115 auth: Some(principal.as_auth_identity()),
116 principal: Some(principal),
117 extensions: BTreeMap::new(),
118 }
119 }
120
121 pub fn principal_actor_id(&self) -> Option<&str> {
126 let from_facet = self
127 .principal
128 .as_ref()
129 .and_then(|p| p.actor.as_ref())
130 .and_then(|facet| facet.fields.get("id"));
131 let from_claims = self.principal.as_ref().and_then(|p| p.claims.get("id"));
132 let from_auth = self.auth.as_ref().and_then(|auth| auth.fields.get("id"));
133 from_facet
134 .or(from_claims)
135 .or(from_auth)
136 .and_then(|v| match v {
137 Value::String(s) => Some(s.as_str()),
138 _ => None,
139 })
140 }
141
142 pub fn tenant_id(&self) -> Option<&str> {
144 self.principal
145 .as_ref()
146 .and_then(|p| p.tenant.as_ref())
147 .and_then(|facet| facet.fields.get("id"))
148 .and_then(|v| match v {
149 Value::String(s) => Some(s.as_str()),
150 _ => None,
151 })
152 }
153
154 pub fn client_ip(&self) -> Option<&str> {
157 self.extensions.get("client_ip").and_then(|v| match v {
158 Value::String(s) => Some(s.as_str()),
159 _ => None,
160 })
161 }
162
163 pub fn request_id(&self) -> Option<&str> {
166 self.extensions.get("request_id").and_then(|v| match v {
167 Value::String(s) => Some(s.as_str()),
168 _ => None,
169 })
170 }
171
172 pub fn audit_claims_snapshot(&self) -> BTreeMap<String, Value> {
175 self.principal
176 .as_ref()
177 .map(|p| p.claims.clone())
178 .unwrap_or_default()
179 }
180
181 pub fn with_request_id(mut self, request_id: impl Into<String>) -> Self {
185 self.extensions
186 .insert("request_id".to_owned(), Value::String(request_id.into()));
187 self
188 }
189
190 pub fn with_client_ip(mut self, ip: impl Into<String>) -> Self {
195 self.extensions
196 .insert("client_ip".to_owned(), Value::String(ip.into()));
197 self
198 }
199}