1use crate::resource::{RequestContext, TenantContext};
50use sha2::{Digest, Sha256};
51use std::collections::HashMap;
52use std::marker::PhantomData;
53use std::sync::Arc;
54use tokio::sync::RwLock;
55
56pub trait AuthState: Send + Sync + 'static {}
58
59#[derive(Debug, Clone, Copy)]
61pub struct Unauthenticated;
62impl AuthState for Unauthenticated {}
63
64#[derive(Debug, Clone, Copy)]
66pub struct Authenticated;
67impl AuthState for Authenticated {}
68
69#[derive(Debug, Clone)]
71pub struct Credential<S: AuthState> {
72 pub(crate) value: String,
73 pub(crate) _phantom: PhantomData<S>,
74}
75
76impl Credential<Unauthenticated> {
77 pub fn new(value: impl Into<String>) -> Self {
79 Self {
80 value: value.into(),
81 _phantom: PhantomData,
82 }
83 }
84
85 pub fn raw_value(&self) -> &str {
87 &self.value
88 }
89}
90
91impl Credential<Authenticated> {
92 #[allow(dead_code)]
94 pub(crate) fn authenticated(value: String) -> Self {
95 Self {
96 value,
97 _phantom: PhantomData,
98 }
99 }
100
101 pub fn authenticated_value(&self) -> &str {
103 &self.value
104 }
105}
106
107#[derive(Debug, Clone)]
112pub struct AuthenticationWitness {
113 pub(crate) tenant_context: TenantContext,
114 pub(crate) credential_hash: String,
115 pub(crate) validated_at: chrono::DateTime<chrono::Utc>,
116}
117
118impl AuthenticationWitness {
119 pub(crate) fn new(tenant_context: TenantContext, credential_hash: String) -> Self {
121 Self {
122 tenant_context,
123 credential_hash,
124 validated_at: chrono::Utc::now(),
125 }
126 }
127
128 pub fn tenant_context(&self) -> &TenantContext {
130 &self.tenant_context
131 }
132
133 pub fn validated_at(&self) -> chrono::DateTime<chrono::Utc> {
135 self.validated_at
136 }
137
138 pub fn credential_hash(&self) -> &str {
140 &self.credential_hash
141 }
142}
143
144#[derive(Debug, Clone)]
149pub struct TenantAuthority {
150 witness: AuthenticationWitness,
151}
152
153impl TenantAuthority {
154 pub fn from_witness(witness: AuthenticationWitness) -> Self {
156 Self { witness }
157 }
158
159 pub fn witness(&self) -> &AuthenticationWitness {
161 &self.witness
162 }
163
164 pub fn tenant_id(&self) -> &str {
166 &self.witness.tenant_context.tenant_id
167 }
168
169 pub fn client_id(&self) -> &str {
171 &self.witness.tenant_context.client_id
172 }
173}
174
175#[derive(Debug)]
180pub struct LinearCredential {
181 inner: Option<Credential<Unauthenticated>>,
182}
183
184impl LinearCredential {
185 pub fn new(value: impl Into<String>) -> Self {
187 Self {
188 inner: Some(Credential::new(value)),
189 }
190 }
191
192 pub fn consume(mut self) -> Credential<Unauthenticated> {
194 self.inner.take().expect("Credential already consumed")
195 }
196
197 pub fn is_consumed(&self) -> bool {
199 self.inner.is_none()
200 }
201}
202
203#[derive(Debug)]
205pub struct ConsumedCredential {
206 _private: (),
207}
208
209impl ConsumedCredential {
210 pub(crate) fn new() -> Self {
212 Self { _private: () }
213 }
214}
215
216#[derive(Debug)]
218pub enum AuthenticationResult {
219 Success {
221 witness: AuthenticationWitness,
222 consumed: ConsumedCredential,
223 },
224 Failed { consumed: ConsumedCredential },
226}
227
228#[derive(Debug, Clone)]
230pub struct AuthenticatedRequestContext {
231 inner: RequestContext,
232 authority: TenantAuthority,
233}
234
235impl AuthenticatedRequestContext {
236 pub fn from_witness(witness: AuthenticationWitness) -> Self {
238 let tenant_context = witness.tenant_context().clone();
239 let authority = TenantAuthority::from_witness(witness);
240 let inner = RequestContext::with_tenant_generated_id(tenant_context);
241
242 Self { inner, authority }
243 }
244
245 pub fn with_request_id(witness: AuthenticationWitness, request_id: String) -> Self {
247 let tenant_context = witness.tenant_context().clone();
248 let authority = TenantAuthority::from_witness(witness);
249 let inner = RequestContext::with_tenant(request_id, tenant_context);
250
251 Self { inner, authority }
252 }
253
254 pub fn request_context(&self) -> &RequestContext {
256 &self.inner
257 }
258
259 pub fn authority(&self) -> &TenantAuthority {
261 &self.authority
262 }
263
264 pub fn tenant_id(&self) -> &str {
266 self.authority.tenant_id()
267 }
268
269 pub fn client_id(&self) -> &str {
271 self.authority.client_id()
272 }
273
274 pub fn request_id(&self) -> &str {
276 &self.inner.request_id
277 }
278}
279
280#[derive(Debug, Clone)]
282pub struct AuthenticatedContext {
283 authority: TenantAuthority,
284}
285
286impl AuthenticatedContext {
287 pub fn from_witness(witness: AuthenticationWitness) -> Self {
289 Self {
290 authority: TenantAuthority::from_witness(witness),
291 }
292 }
293
294 pub fn to_request_context(&self) -> AuthenticatedRequestContext {
296 let witness = self.authority.witness().clone();
297 AuthenticatedRequestContext::from_witness(witness)
298 }
299
300 pub fn authority(&self) -> &TenantAuthority {
302 &self.authority
303 }
304}
305
306#[derive(Debug, Clone)]
308pub struct AuthenticationValidator {
309 credentials: Arc<RwLock<HashMap<String, TenantContext>>>,
311}
312
313impl AuthenticationValidator {
314 pub fn new() -> Self {
316 Self {
317 credentials: Arc::new(RwLock::new(HashMap::new())),
318 }
319 }
320
321 pub async fn register_credential(&self, credential: &str, tenant_context: TenantContext) {
323 let mut creds = self.credentials.write().await;
324 creds.insert(credential.to_string(), tenant_context);
325 }
326
327 pub async fn authenticate(
332 &self,
333 credential: LinearCredential,
334 ) -> Result<AuthenticationWitness, AuthenticationError> {
335 let raw_cred = credential.consume();
337 let _consumed_proof = ConsumedCredential::new();
338
339 let creds = self.credentials.read().await;
341 if let Some(tenant_context) = creds.get(raw_cred.raw_value()) {
342 let mut hasher = Sha256::new();
344 hasher.update(raw_cred.raw_value().as_bytes());
345 let credential_hash = format!("{:x}", hasher.finalize());
346
347 Ok(AuthenticationWitness::new(
348 tenant_context.clone(),
349 credential_hash,
350 ))
351 } else {
352 Err(AuthenticationError::InvalidCredential)
353 }
354 }
355}
356
357impl Default for AuthenticationValidator {
358 fn default() -> Self {
359 Self::new()
360 }
361}
362
363#[derive(Debug, thiserror::Error)]
365pub enum AuthenticationError {
366 #[error("Invalid credential provided")]
367 InvalidCredential,
368 #[error("Credential has been revoked")]
369 CredentialRevoked,
370 #[error("Authentication system unavailable")]
371 SystemUnavailable,
372}
373
374pub trait AuthenticatedProvider {
376 type Error: std::error::Error + Send + Sync + 'static;
377
378 fn list_resources_authenticated(
380 &self,
381 resource_type: &str,
382 context: &AuthenticatedRequestContext,
383 ) -> impl std::future::Future<Output = Result<Vec<crate::resource::Resource>, Self::Error>> + Send;
384}
385
386#[cfg(test)]
387mod tests {
388 use super::*;
389 use crate::resource::TenantContext;
390
391 #[tokio::test]
392 async fn test_linear_credential_consumption() {
393 let cred = LinearCredential::new("test-key");
394 assert!(!cred.is_consumed());
395
396 let _raw = cred.consume();
397 }
399
400 #[tokio::test]
401 async fn test_authentication_flow() {
402 let validator = AuthenticationValidator::new();
403 let tenant_ctx = TenantContext::new("test-tenant".to_string(), "test-client".to_string());
404
405 validator.register_credential("valid-key", tenant_ctx).await;
406
407 let cred = LinearCredential::new("valid-key");
409
410 let witness = validator.authenticate(cred).await.unwrap();
412
413 let auth_context = AuthenticatedRequestContext::from_witness(witness);
415
416 assert_eq!(auth_context.tenant_id(), "test-tenant");
417 assert_eq!(auth_context.client_id(), "test-client");
418 }
419
420 #[tokio::test]
421 async fn test_invalid_authentication() {
422 let validator = AuthenticationValidator::new();
423 let cred = LinearCredential::new("invalid-key");
424
425 let result = validator.authenticate(cred).await;
426 assert!(result.is_err());
427 }
428
429 #[test]
430 fn test_type_level_authentication_states() {
431 let unauth = Credential::<Unauthenticated>::new("test");
433 assert_eq!(unauth.raw_value(), "test");
434
435 }
439
440 #[test]
441 fn test_witness_types() {
442 let tenant_ctx = TenantContext::new("test".to_string(), "client".to_string());
443 let witness = AuthenticationWitness::new(tenant_ctx, "hash".to_string());
444
445 let authority = TenantAuthority::from_witness(witness);
446 assert_eq!(authority.tenant_id(), "test");
447 assert_eq!(authority.client_id(), "client");
448 }
449}