scim_server/auth/
mod.rs

1//! Compile-time authentication system with type-level proofs.
2//!
3//! This module provides a zero-cost abstraction for authentication that leverages
4//! Rust's type system to ensure only authenticated clients can access resources.
5//! Authentication state is tracked at compile time, preventing runtime security bugs.
6//!
7//! # Type-Level Authentication Design
8//!
9//! The system uses phantom types and witness types to encode authentication state:
10//!
11//! * **Unauthenticated State**: Raw credentials that haven't been validated
12//! * **Authenticated State**: Credentials that have passed validation with type-level proof
13//! * **Authorized Context**: Request contexts that can only be created with valid authentication
14//! * **Linear Credentials**: Authentication tokens that can only be consumed once
15//!
16//! # Key Principles
17//!
18//! 1. **Impossible States**: Unauthenticated access is unrepresentable
19//! 2. **Zero Runtime Cost**: All validation happens at compile time where possible
20//! 3. **Linear Resources**: Credentials are consumed during authentication
21//! 4. **Proof Carrying**: Authenticated contexts carry evidence of validation
22//! 5. **Type Safety**: Operations require specific authentication levels
23//!
24//! # Example Usage
25//!
26//! ```rust
27//! use scim_server::auth::{
28//!     AuthenticationValidator, AuthenticatedRequestContext,
29//!     LinearCredential, Credential, Unauthenticated
30//! };
31//!
32//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
33//! // Raw credential (unauthenticated)
34//! let raw_cred = LinearCredential::new("api-key-123");
35//!
36//! // Validation consumes the raw credential
37//! let validator = AuthenticationValidator::new();
38//! let witness = validator.authenticate(raw_cred).await?;
39//!
40//! // Only validated credentials can create authenticated contexts
41//! let auth_context = AuthenticatedRequestContext::from_witness(witness);
42//!
43//! // Only authenticated contexts can access resources
44//! // provider.list_resources(&auth_context).await;
45//! # Ok(())
46//! # }
47//! ```
48
49use 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
56/// Type-level authentication states using phantom types
57pub trait AuthState: Send + Sync + 'static {}
58
59/// Phantom type for unauthenticated state
60#[derive(Debug, Clone, Copy)]
61pub struct Unauthenticated;
62impl AuthState for Unauthenticated {}
63
64/// Phantom type for authenticated state
65#[derive(Debug, Clone, Copy)]
66pub struct Authenticated;
67impl AuthState for Authenticated {}
68
69/// Credential with compile-time authentication state
70#[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    /// Create a new unauthenticated credential
78    pub fn new(value: impl Into<String>) -> Self {
79        Self {
80            value: value.into(),
81            _phantom: PhantomData,
82        }
83    }
84
85    /// Get the raw credential value (only available for unauthenticated)
86    pub fn raw_value(&self) -> &str {
87        &self.value
88    }
89}
90
91impl Credential<Authenticated> {
92    /// Create an authenticated credential (internal use only)
93    #[allow(dead_code)]
94    pub(crate) fn authenticated(value: String) -> Self {
95        Self {
96            value,
97            _phantom: PhantomData,
98        }
99    }
100
101    /// Get the credential value (only available after authentication)
102    pub fn authenticated_value(&self) -> &str {
103        &self.value
104    }
105}
106
107/// Witness type proving successful authentication
108///
109/// This type can only be constructed by the authentication system
110/// and serves as compile-time proof that validation occurred.
111#[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    /// Create a new authentication witness (internal use only)
120    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    /// Get the tenant context (only available with witness)
129    pub fn tenant_context(&self) -> &TenantContext {
130        &self.tenant_context
131    }
132
133    /// Get validation timestamp
134    pub fn validated_at(&self) -> chrono::DateTime<chrono::Utc> {
135        self.validated_at
136    }
137
138    /// Get credential hash for audit purposes
139    pub fn credential_hash(&self) -> &str {
140        &self.credential_hash
141    }
142}
143
144/// Witness type proving tenant-level authority
145///
146/// This type can only be created from an AuthenticationWitness
147/// and proves the holder has authority within a specific tenant.
148#[derive(Debug, Clone)]
149pub struct TenantAuthority {
150    witness: AuthenticationWitness,
151}
152
153impl TenantAuthority {
154    /// Create tenant authority from authentication witness
155    pub fn from_witness(witness: AuthenticationWitness) -> Self {
156        Self { witness }
157    }
158
159    /// Get the underlying authentication witness
160    pub fn witness(&self) -> &AuthenticationWitness {
161        &self.witness
162    }
163
164    /// Get tenant ID (compile-time guaranteed to exist)
165    pub fn tenant_id(&self) -> &str {
166        &self.witness.tenant_context.tenant_id
167    }
168
169    /// Get client ID (compile-time guaranteed to exist)
170    pub fn client_id(&self) -> &str {
171        &self.witness.tenant_context.client_id
172    }
173}
174
175/// Linear credential that can only be consumed once
176///
177/// This prevents credential reuse and ensures authentication
178/// happens exactly once per credential.
179#[derive(Debug)]
180pub struct LinearCredential {
181    inner: Option<Credential<Unauthenticated>>,
182}
183
184impl LinearCredential {
185    /// Create a new linear credential
186    pub fn new(value: impl Into<String>) -> Self {
187        Self {
188            inner: Some(Credential::new(value)),
189        }
190    }
191
192    /// Consume this credential for authentication (can only be called once)
193    pub fn consume(mut self) -> Credential<Unauthenticated> {
194        self.inner.take().expect("Credential already consumed")
195    }
196
197    /// Check if credential has been consumed
198    pub fn is_consumed(&self) -> bool {
199        self.inner.is_none()
200    }
201}
202
203/// Marker type proving a credential was consumed
204#[derive(Debug)]
205pub struct ConsumedCredential {
206    _private: (),
207}
208
209impl ConsumedCredential {
210    /// Create proof of consumption (internal use only)
211    pub(crate) fn new() -> Self {
212        Self { _private: () }
213    }
214}
215
216/// Result of authentication that either succeeds with proof or fails
217#[derive(Debug)]
218pub enum AuthenticationResult {
219    /// Authentication succeeded with witness
220    Success {
221        /// Proof of successful authentication
222        witness: AuthenticationWitness,
223        /// The credential that was consumed during authentication
224        consumed: ConsumedCredential,
225    },
226    /// Authentication failed
227    Failed {
228        /// The credential that was consumed during failed authentication
229        consumed: ConsumedCredential
230    },
231}
232
233/// Request context that can only be created with authentication proof
234#[derive(Debug, Clone)]
235pub struct AuthenticatedRequestContext {
236    inner: RequestContext,
237    authority: TenantAuthority,
238}
239
240impl AuthenticatedRequestContext {
241    /// Create authenticated context from witness (consuming it)
242    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    /// Create authenticated context with specific request ID
251    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    /// Get the underlying request context
260    pub fn request_context(&self) -> &RequestContext {
261        &self.inner
262    }
263
264    /// Get tenant authority proof
265    pub fn authority(&self) -> &TenantAuthority {
266        &self.authority
267    }
268
269    /// Get tenant ID (compile-time guaranteed)
270    pub fn tenant_id(&self) -> &str {
271        self.authority.tenant_id()
272    }
273
274    /// Get client ID (compile-time guaranteed)
275    pub fn client_id(&self) -> &str {
276        self.authority.client_id()
277    }
278
279    /// Get request ID
280    pub fn request_id(&self) -> &str {
281        &self.inner.request_id
282    }
283}
284
285/// Simplified authenticated context for common operations
286#[derive(Debug, Clone)]
287pub struct AuthenticatedContext {
288    authority: TenantAuthority,
289}
290
291impl AuthenticatedContext {
292    /// Create from authentication witness
293    pub fn from_witness(witness: AuthenticationWitness) -> Self {
294        Self {
295            authority: TenantAuthority::from_witness(witness),
296        }
297    }
298
299    /// Convert to full request context
300    pub fn to_request_context(&self) -> AuthenticatedRequestContext {
301        let witness = self.authority.witness().clone();
302        AuthenticatedRequestContext::from_witness(witness)
303    }
304
305    /// Get tenant authority
306    pub fn authority(&self) -> &TenantAuthority {
307        &self.authority
308    }
309}
310
311/// Compile-time authentication validator
312#[derive(Debug, Clone)]
313pub struct AuthenticationValidator {
314    // Runtime credential store for validation
315    credentials: Arc<RwLock<HashMap<String, TenantContext>>>,
316}
317
318impl AuthenticationValidator {
319    /// Create a new authentication validator
320    pub fn new() -> Self {
321        Self {
322            credentials: Arc::new(RwLock::new(HashMap::new())),
323        }
324    }
325
326    /// Register a credential (for testing/setup)
327    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    /// Authenticate a credential with compile-time proof
333    ///
334    /// This method consumes the credential and either returns an authentication
335    /// witness (proving successful validation) or an error with proof of consumption.
336    pub async fn authenticate(
337        &self,
338        credential: LinearCredential,
339    ) -> Result<AuthenticationWitness, AuthenticationError> {
340        // Consume the credential (can only happen once)
341        let raw_cred = credential.consume();
342        let _consumed_proof = ConsumedCredential::new();
343
344        // Runtime validation
345        let creds = self.credentials.read().await;
346        if let Some(tenant_context) = creds.get(raw_cred.raw_value()) {
347            // Create secure hash for witness using SHA-256
348            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/// Authentication errors
369#[derive(Debug, thiserror::Error)]
370pub enum AuthenticationError {
371    #[error("Invalid credential provided")]
372    /// The provided credential is invalid or malformed
373    InvalidCredential,
374    #[error("Credential has been revoked")]
375    /// The credential has been revoked and is no longer valid
376    CredentialRevoked,
377    #[error("Authentication system unavailable")]
378    /// The authentication system is temporarily unavailable
379    SystemUnavailable,
380}
381
382/// Type-safe authentication traits for providers
383pub trait AuthenticatedProvider {
384    /// Error type returned by authenticated operations
385    type Error: std::error::Error + Send + Sync + 'static;
386
387    /// List resources with authenticated context (compile-time guaranteed)
388    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        // Credential is now consumed - cannot use again
407    }
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        // Create linear credential
417        let cred = LinearCredential::new("valid-key");
418
419        // Authenticate (consumes credential)
420        let witness = validator.authenticate(cred).await.unwrap();
421
422        // Create authenticated context
423        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        // Can create unauthenticated credential
441        let unauth = Credential::<Unauthenticated>::new("test");
442        assert_eq!(unauth.raw_value(), "test");
443
444        // Cannot create authenticated credential directly
445        // This would not compile:
446        // let auth = Credential::<Authenticated>::new("test");
447    }
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}