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,
223 consumed: ConsumedCredential,
225 },
226 Failed {
228 consumed: ConsumedCredential,
230 },
231}
232
233#[derive(Debug, Clone)]
235pub struct AuthenticatedRequestContext {
236 inner: RequestContext,
237 authority: TenantAuthority,
238}
239
240impl AuthenticatedRequestContext {
241 pub fn from_witness(witness: AuthenticationWitness) -> Self {
243 let tenant_context = witness.tenant_context().clone();
244 let authority = TenantAuthority::from_witness(witness);
245 let inner = RequestContext::with_tenant_generated_id(tenant_context);
246
247 Self { inner, authority }
248 }
249
250 pub fn with_request_id(witness: AuthenticationWitness, request_id: String) -> Self {
252 let tenant_context = witness.tenant_context().clone();
253 let authority = TenantAuthority::from_witness(witness);
254 let inner = RequestContext::with_tenant(request_id, tenant_context);
255
256 Self { inner, authority }
257 }
258
259 pub fn request_context(&self) -> &RequestContext {
261 &self.inner
262 }
263
264 pub fn authority(&self) -> &TenantAuthority {
266 &self.authority
267 }
268
269 pub fn tenant_id(&self) -> &str {
271 self.authority.tenant_id()
272 }
273
274 pub fn client_id(&self) -> &str {
276 self.authority.client_id()
277 }
278
279 pub fn request_id(&self) -> &str {
281 &self.inner.request_id
282 }
283}
284
285#[derive(Debug, Clone)]
287pub struct AuthenticatedContext {
288 authority: TenantAuthority,
289}
290
291impl AuthenticatedContext {
292 pub fn from_witness(witness: AuthenticationWitness) -> Self {
294 Self {
295 authority: TenantAuthority::from_witness(witness),
296 }
297 }
298
299 pub fn to_request_context(&self) -> AuthenticatedRequestContext {
301 let witness = self.authority.witness().clone();
302 AuthenticatedRequestContext::from_witness(witness)
303 }
304
305 pub fn authority(&self) -> &TenantAuthority {
307 &self.authority
308 }
309}
310
311#[derive(Debug, Clone)]
313pub struct AuthenticationValidator {
314 credentials: Arc<RwLock<HashMap<String, TenantContext>>>,
316}
317
318impl AuthenticationValidator {
319 pub fn new() -> Self {
321 Self {
322 credentials: Arc::new(RwLock::new(HashMap::new())),
323 }
324 }
325
326 pub async fn register_credential(&self, credential: &str, tenant_context: TenantContext) {
328 let mut creds = self.credentials.write().await;
329 creds.insert(credential.to_string(), tenant_context);
330 }
331
332 pub async fn authenticate(
337 &self,
338 credential: LinearCredential,
339 ) -> Result<AuthenticationWitness, AuthenticationError> {
340 let raw_cred = credential.consume();
342 let _consumed_proof = ConsumedCredential::new();
343
344 let creds = self.credentials.read().await;
346 if let Some(tenant_context) = creds.get(raw_cred.raw_value()) {
347 let mut hasher = Sha256::new();
349 hasher.update(raw_cred.raw_value().as_bytes());
350 let credential_hash = format!("{:x}", hasher.finalize());
351
352 Ok(AuthenticationWitness::new(
353 tenant_context.clone(),
354 credential_hash,
355 ))
356 } else {
357 Err(AuthenticationError::InvalidCredential)
358 }
359 }
360}
361
362impl Default for AuthenticationValidator {
363 fn default() -> Self {
364 Self::new()
365 }
366}
367
368#[derive(Debug, thiserror::Error)]
370pub enum AuthenticationError {
371 #[error("Invalid credential provided")]
372 InvalidCredential,
374 #[error("Credential has been revoked")]
375 CredentialRevoked,
377 #[error("Authentication system unavailable")]
378 SystemUnavailable,
380}
381
382pub trait AuthenticatedProvider {
384 type Error: std::error::Error + Send + Sync + 'static;
386
387 fn list_resources_authenticated(
389 &self,
390 resource_type: &str,
391 context: &AuthenticatedRequestContext,
392 ) -> impl std::future::Future<Output = Result<Vec<crate::resource::Resource>, Self::Error>> + Send;
393}
394
395#[cfg(test)]
396mod tests {
397 use super::*;
398 use crate::resource::TenantContext;
399
400 #[tokio::test]
401 async fn test_linear_credential_consumption() {
402 let cred = LinearCredential::new("test-key");
403 assert!(!cred.is_consumed());
404
405 let _raw = cred.consume();
406 }
408
409 #[tokio::test]
410 async fn test_authentication_flow() {
411 let validator = AuthenticationValidator::new();
412 let tenant_ctx = TenantContext::new("test-tenant".to_string(), "test-client".to_string());
413
414 validator.register_credential("valid-key", tenant_ctx).await;
415
416 let cred = LinearCredential::new("valid-key");
418
419 let witness = validator.authenticate(cred).await.unwrap();
421
422 let auth_context = AuthenticatedRequestContext::from_witness(witness);
424
425 assert_eq!(auth_context.tenant_id(), "test-tenant");
426 assert_eq!(auth_context.client_id(), "test-client");
427 }
428
429 #[tokio::test]
430 async fn test_invalid_authentication() {
431 let validator = AuthenticationValidator::new();
432 let cred = LinearCredential::new("invalid-key");
433
434 let result = validator.authenticate(cred).await;
435 assert!(result.is_err());
436 }
437
438 #[test]
439 fn test_type_level_authentication_states() {
440 let unauth = Credential::<Unauthenticated>::new("test");
442 assert_eq!(unauth.raw_value(), "test");
443
444 }
448
449 #[test]
450 fn test_witness_types() {
451 let tenant_ctx = TenantContext::new("test".to_string(), "client".to_string());
452 let witness = AuthenticationWitness::new(tenant_ctx, "hash".to_string());
453
454 let authority = TenantAuthority::from_witness(witness);
455 assert_eq!(authority.tenant_id(), "test");
456 assert_eq!(authority.client_id(), "client");
457 }
458}