auth_framework/
builders.rs

1//! Builder patterns and ergonomic helpers for the Auth Framework
2//!
3//! This module provides fluent builder APIs and helper functions to make
4//! common authentication setup tasks easier and more discoverable.
5//!
6//! # Quick Start Builders
7//!
8//! For the most common setups, use the quick start builders:
9//!
10//! ```rust,no_run
11//! use auth_framework::prelude::*;
12//!
13//! // Simple JWT auth with environment variables
14//! let auth = AuthFramework::quick_start()
15//!     .jwt_auth_from_env()
16//!     .build().await?;
17//!
18//! // Web app with database
19//! let auth = AuthFramework::quick_start()
20//!     .jwt_auth("your-secret-key")
21//!     .with_postgres("postgresql://...")
22//!     .with_axum()
23//!     .build().await?;
24//! ```
25//!
26//! # Preset Configurations
27//!
28//! Use presets for common security and performance configurations:
29//!
30//! ```rust,no_run
31//! use auth_framework::prelude::*;
32//!
33//! let auth = AuthFramework::new()
34//!     .security_preset(SecurityPreset::HighSecurity)
35//!     .performance_preset(PerformancePreset::LowLatency)
36//!     .build().await?;
37//! ```
38//!
39//! # Use Case Templates
40//!
41//! Get started quickly with templates for common use cases:
42//!
43//! ```rust,no_run
44//! use auth_framework::prelude::*;
45//!
46//! // Configure for web application
47//! let auth = AuthFramework::for_use_case(UseCasePreset::WebApp)
48//!     .customize(|config| {
49//!         config.token_lifetime(hours(24))
50//!               .enable_sessions(true)
51//!     })
52//!     .build().await?;
53//! ```
54
55use crate::{
56    AuthConfig, AuthError, AuthFramework,
57    config::{RateLimitConfig, SecurityConfig, StorageConfig},
58    prelude::{PerformancePreset, UseCasePreset, days, hours, minutes},
59    security::SecurityPreset,
60};
61use std::time::Duration;
62
63/// Main builder for AuthFramework with fluent API
64pub struct AuthBuilder {
65    config: AuthConfig,
66    security_preset: Option<SecurityPreset>,
67    performance_preset: Option<PerformancePreset>,
68    use_case_preset: Option<UseCasePreset>,
69    /// Optional custom storage instance supplied by caller (Arc<dyn AuthStorage>)
70    custom_storage: Option<std::sync::Arc<dyn crate::storage::AuthStorage>>,
71}
72
73/// Quick start builder for common authentication setups
74#[derive(Debug)]
75pub struct QuickStartBuilder {
76    auth_method: Option<QuickStartAuth>,
77    storage: Option<QuickStartStorage>,
78    framework: Option<QuickStartFramework>,
79    security_level: SecurityPreset,
80}
81
82/// Authentication method configuration for quick start
83#[derive(Debug)]
84pub enum QuickStartAuth {
85    Jwt {
86        secret: String,
87    },
88    JwtFromEnv,
89    OAuth2 {
90        client_id: String,
91        client_secret: String,
92    },
93    Combined {
94        jwt_secret: String,
95        oauth_client_id: String,
96        oauth_client_secret: String,
97    },
98}
99
100/// Storage configuration for quick start
101#[derive(Debug)]
102pub enum QuickStartStorage {
103    Memory,
104    Postgres(String),
105    PostgresFromEnv,
106    Redis(String),
107    RedisFromEnv,
108}
109
110/// Web framework integration for quick start
111#[derive(Debug)]
112pub enum QuickStartFramework {
113    Axum,
114    ActixWeb,
115    Warp,
116}
117
118impl AuthFramework {
119    /// Create a new builder for the authentication framework
120    pub fn builder() -> AuthBuilder {
121        AuthBuilder::new()
122    }
123
124    /// Quick start builder for common setups
125    pub fn quick_start() -> QuickStartBuilder {
126        QuickStartBuilder::new()
127    }
128
129    /// Create a builder for a specific use case
130    pub fn for_use_case(use_case: UseCasePreset) -> AuthBuilder {
131        AuthBuilder::new().use_case_preset(use_case)
132    }
133
134    /// Create an authentication framework with preset configuration
135    pub fn preset(preset: SecurityPreset) -> AuthBuilder {
136        AuthBuilder::new().security_preset(preset)
137    }
138}
139
140impl AuthBuilder {
141    /// Create a new builder with default configuration
142    pub fn new() -> Self {
143        Self {
144            config: AuthConfig::default(),
145            security_preset: None,
146            performance_preset: None,
147            use_case_preset: None,
148            custom_storage: None,
149        }
150    }
151
152    /// Apply a security preset
153    pub fn security_preset(mut self, preset: SecurityPreset) -> Self {
154        self.security_preset = Some(preset);
155        self
156    }
157
158    /// Apply a performance preset
159    pub fn performance_preset(mut self, preset: PerformancePreset) -> Self {
160        self.performance_preset = Some(preset);
161        self
162    }
163
164    /// Apply a use case preset
165    pub fn use_case_preset(mut self, preset: UseCasePreset) -> Self {
166        self.use_case_preset = Some(preset);
167        self
168    }
169
170    /// Configure JWT authentication
171    pub fn with_jwt(self) -> JwtBuilder {
172        JwtBuilder::new(self)
173    }
174
175    /// Configure OAuth2 authentication
176    pub fn with_oauth2(self) -> OAuth2Builder {
177        OAuth2Builder::new(self)
178    }
179
180    /// Configure storage backend
181    pub fn with_storage(self) -> StorageBuilder {
182        StorageBuilder::new(self)
183    }
184
185    /// Configure rate limiting
186    pub fn with_rate_limiting(self) -> RateLimitBuilder {
187        RateLimitBuilder::new(self)
188    }
189
190    /// Configure security settings
191    pub fn with_security(self) -> SecurityBuilder {
192        SecurityBuilder::new(self)
193    }
194
195    /// Configure audit logging
196    pub fn with_audit(self) -> AuditBuilder {
197        AuditBuilder::new(self)
198    }
199
200    /// Customize configuration with a closure
201    pub fn customize<F>(mut self, f: F) -> Self
202    where
203        F: FnOnce(&mut AuthConfig) -> &mut AuthConfig,
204    {
205        f(&mut self.config);
206        self
207    }
208
209    /// Build the authentication framework
210    pub async fn build(mut self) -> Result<AuthFramework, AuthError> {
211        // Apply presets before building
212        if let Some(preset) = self.security_preset.take() {
213            self.config.security = self.apply_security_preset(preset);
214        }
215
216        if let Some(preset) = self.performance_preset.take() {
217            self.apply_performance_preset(preset);
218        }
219
220        if let Some(preset) = self.use_case_preset.take() {
221            self.apply_use_case_preset(preset);
222        }
223
224        // Validate configuration
225        self.config.validate()?;
226
227        // Create and initialize framework
228        // If a custom storage was provided via the builder, we'll construct a framework
229        // and replace its storage before initialization so managers use the custom storage.
230        let mut framework = AuthFramework::new(self.config);
231        if let Some(storage) = self.custom_storage.take() {
232            framework.replace_storage(storage);
233        }
234        framework.initialize().await?;
235
236        Ok(framework)
237    }
238
239    fn apply_security_preset(&self, preset: SecurityPreset) -> SecurityConfig {
240        match preset {
241            SecurityPreset::Development => SecurityConfig::development(),
242            SecurityPreset::Balanced => SecurityConfig::default(),
243            SecurityPreset::HighSecurity | SecurityPreset::Paranoid => SecurityConfig::secure(),
244        }
245    }
246
247    fn apply_performance_preset(&mut self, preset: PerformancePreset) {
248        match preset {
249            PerformancePreset::HighThroughput => {
250                // Optimize for throughput
251                self.config.rate_limiting.max_requests = 1000;
252                self.config.rate_limiting.window = Duration::from_secs(60);
253            }
254            PerformancePreset::LowLatency => {
255                // Optimize for latency
256                self.config.token_lifetime = hours(1);
257                self.config.rate_limiting.max_requests = 100;
258                self.config.rate_limiting.window = Duration::from_secs(60);
259            }
260            PerformancePreset::LowMemory => {
261                // Optimize for memory usage
262                self.config.token_lifetime = minutes(15);
263                self.config.refresh_token_lifetime = hours(2);
264            }
265            PerformancePreset::Balanced => {
266                // Keep defaults
267            }
268        }
269    }
270
271    fn apply_use_case_preset(&mut self, preset: UseCasePreset) {
272        match preset {
273            UseCasePreset::WebApp => {
274                self.config.token_lifetime = hours(24);
275                self.config.refresh_token_lifetime = days(7);
276                self.config.security.secure_cookies = true;
277                self.config.security.csrf_protection = true;
278            }
279            UseCasePreset::ApiService => {
280                self.config.token_lifetime = hours(1);
281                self.config.refresh_token_lifetime = hours(24);
282                self.config.rate_limiting.enabled = true;
283                self.config.rate_limiting.max_requests = 1000;
284            }
285            UseCasePreset::Microservices => {
286                self.config.token_lifetime = minutes(15);
287                self.config.refresh_token_lifetime = hours(1);
288                self.config.audit.enabled = true;
289            }
290            UseCasePreset::MobileBackend => {
291                self.config.token_lifetime = hours(1);
292                self.config.refresh_token_lifetime = days(30);
293                self.config.security.secure_cookies = false; // Mobile doesn't use cookies
294            }
295            UseCasePreset::Enterprise => {
296                self.config.enable_multi_factor = true;
297                self.config.security = SecurityConfig::secure();
298                self.config.audit.enabled = true;
299                self.config.audit.log_success = true;
300                self.config.audit.log_failures = true;
301            }
302        }
303    }
304}
305
306impl QuickStartBuilder {
307    fn new() -> Self {
308        Self {
309            auth_method: None,
310            storage: None,
311            framework: None,
312            security_level: SecurityPreset::Balanced,
313        }
314    }
315
316    /// Configure JWT authentication with a secret key
317    pub fn jwt_auth(mut self, secret: impl Into<String>) -> Self {
318        self.auth_method = Some(QuickStartAuth::Jwt {
319            secret: secret.into(),
320        });
321        self
322    }
323
324    /// Configure JWT authentication from JWT_SECRET environment variable
325    pub fn jwt_auth_from_env(mut self) -> Self {
326        self.auth_method = Some(QuickStartAuth::JwtFromEnv);
327        self
328    }
329
330    /// Configure OAuth2 authentication
331    pub fn oauth2_auth(
332        mut self,
333        client_id: impl Into<String>,
334        client_secret: impl Into<String>,
335    ) -> Self {
336        self.auth_method = Some(QuickStartAuth::OAuth2 {
337            client_id: client_id.into(),
338            client_secret: client_secret.into(),
339        });
340        self
341    }
342
343    /// Configure both JWT and OAuth2 authentication
344    pub fn combined_auth(
345        mut self,
346        jwt_secret: impl Into<String>,
347        oauth_client_id: impl Into<String>,
348        oauth_client_secret: impl Into<String>,
349    ) -> Self {
350        self.auth_method = Some(QuickStartAuth::Combined {
351            jwt_secret: jwt_secret.into(),
352            oauth_client_id: oauth_client_id.into(),
353            oauth_client_secret: oauth_client_secret.into(),
354        });
355        self
356    }
357
358    /// Use PostgreSQL storage with connection string
359    pub fn with_postgres(mut self, connection_string: impl Into<String>) -> Self {
360        self.storage = Some(QuickStartStorage::Postgres(connection_string.into()));
361        self
362    }
363
364    /// Use PostgreSQL storage from DATABASE_URL environment variable
365    pub fn with_postgres_from_env(mut self) -> Self {
366        self.storage = Some(QuickStartStorage::PostgresFromEnv);
367        self
368    }
369
370    /// Use Redis storage with connection string
371    pub fn with_redis(mut self, connection_string: impl Into<String>) -> Self {
372        self.storage = Some(QuickStartStorage::Redis(connection_string.into()));
373        self
374    }
375
376    /// Use Redis storage from REDIS_URL environment variable
377    pub fn with_redis_from_env(mut self) -> Self {
378        self.storage = Some(QuickStartStorage::RedisFromEnv);
379        self
380    }
381
382    /// Use in-memory storage (development only)
383    pub fn with_memory_storage(mut self) -> Self {
384        self.storage = Some(QuickStartStorage::Memory);
385        self
386    }
387
388    /// Configure for Axum web framework
389    pub fn with_axum(mut self) -> Self {
390        self.framework = Some(QuickStartFramework::Axum);
391        self
392    }
393
394    /// Configure for Actix Web framework
395    pub fn with_actix(mut self) -> Self {
396        self.framework = Some(QuickStartFramework::ActixWeb);
397        self
398    }
399
400    /// Configure for Warp web framework
401    pub fn with_warp(mut self) -> Self {
402        self.framework = Some(QuickStartFramework::Warp);
403        self
404    }
405
406    /// Set security level
407    pub fn security_level(mut self, level: SecurityPreset) -> Self {
408        self.security_level = level;
409        self
410    }
411
412    /// Build the authentication framework
413    pub async fn build(self) -> Result<AuthFramework, AuthError> {
414        let mut builder = AuthBuilder::new().security_preset(self.security_level);
415
416        // Configure authentication method
417        match self.auth_method {
418            Some(QuickStartAuth::Jwt { secret }) => {
419                builder = builder.with_jwt().secret(secret).done();
420            }
421            Some(QuickStartAuth::JwtFromEnv) => {
422                let secret = std::env::var("JWT_SECRET").map_err(|_| {
423                    AuthError::config("JWT_SECRET environment variable is required")
424                })?;
425                builder = builder.with_jwt().secret(secret).done();
426            }
427            Some(QuickStartAuth::OAuth2 {
428                client_id,
429                client_secret,
430            }) => {
431                builder = builder
432                    .with_oauth2()
433                    .client_id(client_id)
434                    .client_secret(client_secret)
435                    .done();
436            }
437            Some(QuickStartAuth::Combined {
438                jwt_secret,
439                oauth_client_id,
440                oauth_client_secret,
441            }) => {
442                builder = builder
443                    .with_jwt()
444                    .secret(jwt_secret)
445                    .done()
446                    .with_oauth2()
447                    .client_id(oauth_client_id)
448                    .client_secret(oauth_client_secret)
449                    .done();
450            }
451            None => {
452                return Err(AuthError::config("Authentication method is required"));
453            }
454        }
455
456        // Configure storage
457        match self.storage {
458            Some(QuickStartStorage::Memory) => {
459                builder = builder.with_storage().memory().done();
460            }
461            Some(QuickStartStorage::Postgres(conn_str)) => {
462                builder = builder.with_storage().postgres(conn_str).done();
463            }
464            Some(QuickStartStorage::PostgresFromEnv) => {
465                let conn_str = std::env::var("DATABASE_URL").map_err(|_| {
466                    AuthError::config("DATABASE_URL environment variable is required")
467                })?;
468                builder = builder.with_storage().postgres(conn_str).done();
469            }
470            Some(QuickStartStorage::Redis(_conn_str)) => {
471                // Redis storage not yet implemented, fallback to memory
472                builder = builder.with_storage().memory().done();
473            }
474            Some(QuickStartStorage::RedisFromEnv) => {
475                // Redis storage not yet implemented, fallback to memory
476                builder = builder.with_storage().memory().done();
477            }
478            None => {
479                // Default to memory storage for quick start
480                builder = builder.with_storage().memory().done();
481            }
482        }
483
484        builder.build().await
485    }
486}
487
488/// JWT configuration builder
489pub struct JwtBuilder {
490    parent: AuthBuilder,
491    secret: Option<String>,
492    issuer: Option<String>,
493    audience: Option<String>,
494    token_lifetime: Option<Duration>,
495}
496
497impl JwtBuilder {
498    fn new(parent: AuthBuilder) -> Self {
499        Self {
500            parent,
501            secret: None,
502            issuer: None,
503            audience: None,
504            token_lifetime: None,
505        }
506    }
507
508    /// Set JWT secret key
509    pub fn secret(mut self, secret: impl Into<String>) -> Self {
510        self.secret = Some(secret.into());
511        self
512    }
513
514    /// Load JWT secret from environment variable
515    pub fn secret_from_env(mut self, env_var: &str) -> Self {
516        if let Ok(secret) = std::env::var(env_var) {
517            self.secret = Some(secret);
518        }
519        self
520    }
521
522    /// Set JWT issuer
523    pub fn issuer(mut self, issuer: impl Into<String>) -> Self {
524        self.issuer = Some(issuer.into());
525        self
526    }
527
528    /// Set JWT audience
529    pub fn audience(mut self, audience: impl Into<String>) -> Self {
530        self.audience = Some(audience.into());
531        self
532    }
533
534    /// Set token lifetime
535    pub fn token_lifetime(mut self, lifetime: Duration) -> Self {
536        self.token_lifetime = Some(lifetime);
537        self
538    }
539
540    /// Complete JWT configuration and return to main builder
541    pub fn done(mut self) -> AuthBuilder {
542        if let Some(secret) = self.secret {
543            self.parent.config.secret = Some(secret);
544        }
545        if let Some(issuer) = self.issuer {
546            self.parent.config.issuer = issuer;
547        }
548        if let Some(audience) = self.audience {
549            self.parent.config.audience = audience;
550        }
551        if let Some(lifetime) = self.token_lifetime {
552            self.parent.config.token_lifetime = lifetime;
553        }
554        self.parent
555    }
556}
557
558/// OAuth2 configuration builder
559pub struct OAuth2Builder {
560    parent: AuthBuilder,
561    client_id: Option<String>,
562    client_secret: Option<String>,
563    redirect_uri: Option<String>,
564}
565
566impl OAuth2Builder {
567    fn new(parent: AuthBuilder) -> Self {
568        Self {
569            parent,
570            client_id: None,
571            client_secret: None,
572            redirect_uri: None,
573        }
574    }
575
576    /// Set OAuth2 client ID
577    pub fn client_id(mut self, client_id: impl Into<String>) -> Self {
578        self.client_id = Some(client_id.into());
579        self
580    }
581
582    /// Set OAuth2 client secret
583    pub fn client_secret(mut self, client_secret: impl Into<String>) -> Self {
584        self.client_secret = Some(client_secret.into());
585        self
586    }
587
588    /// Set redirect URI
589    pub fn redirect_uri(mut self, redirect_uri: impl Into<String>) -> Self {
590        self.redirect_uri = Some(redirect_uri.into());
591        self
592    }
593
594    /// Configure Google OAuth2
595    pub fn google_client_id(self, client_id: impl Into<String>) -> Self {
596        self.client_id(client_id)
597    }
598
599    /// Configure GitHub OAuth2
600    pub fn github_client_id(self, client_id: impl Into<String>) -> Self {
601        self.client_id(client_id)
602    }
603
604    /// Complete OAuth2 configuration and return to main builder
605    pub fn done(self) -> AuthBuilder {
606        // OAuth2 configuration would be stored in method_configs
607        // This is a simplified version
608        self.parent
609    }
610}
611
612/// Storage configuration builder
613pub struct StorageBuilder {
614    parent: AuthBuilder,
615}
616
617impl StorageBuilder {
618    fn new(parent: AuthBuilder) -> Self {
619        Self { parent }
620    }
621
622    /// Use a custom storage instance (already initialized) instead of configuring via enums.
623    ///
624    /// Example:
625    ///
626    /// let storage = Arc::new(MySurrealStorage::connect(...).await?);
627    /// let auth = AuthFramework::builder()
628    ///     .with_storage()
629    ///     .custom(storage)
630    ///     .done()
631    ///     .build()
632    ///     .await?;
633    pub fn custom(mut self, storage: std::sync::Arc<dyn crate::storage::AuthStorage>) -> Self {
634        self.parent.custom_storage = Some(storage);
635        self
636    }
637
638    /// Configure in-memory storage
639    pub fn memory(mut self) -> Self {
640        self.parent.config.storage = StorageConfig::Memory;
641        self
642    }
643
644    /// Configure PostgreSQL storage
645    #[cfg(feature = "postgres-storage")]
646    pub fn postgres(mut self, connection_string: impl Into<String>) -> Self {
647        self.parent.config.storage = StorageConfig::Postgres {
648            connection_string: connection_string.into(),
649            table_prefix: "auth_".to_string(),
650        };
651        self
652    }
653
654    /// Configure PostgreSQL storage from environment
655    #[cfg(feature = "postgres-storage")]
656    pub fn postgres_from_env(mut self) -> Self {
657        if let Ok(conn_str) = std::env::var("DATABASE_URL") {
658            self = self.postgres(conn_str);
659        }
660        self
661    }
662
663    /// Configure Redis storage
664    #[cfg(feature = "redis-storage")]
665    pub fn redis(mut self, url: impl Into<String>) -> Self {
666        self.parent.config.storage = StorageConfig::Redis {
667            url: url.into(),
668            key_prefix: "auth:".to_string(),
669        };
670        self
671    }
672
673    /// Configure Redis storage from environment
674    #[cfg(feature = "redis-storage")]
675    pub fn redis_from_env(mut self) -> Self {
676        if let Ok(url) = std::env::var("REDIS_URL") {
677            self = self.redis(url);
678        }
679        self
680    }
681
682    /// Set connection pool size
683    pub fn connection_pool_size(self, _size: u32) -> Self {
684        // This would be implemented when storage supports connection pooling
685        self
686    }
687
688    /// Complete storage configuration and return to main builder
689    pub fn done(self) -> AuthBuilder {
690        self.parent
691    }
692}
693
694/// Rate limiting configuration builder
695pub struct RateLimitBuilder {
696    parent: AuthBuilder,
697}
698
699impl RateLimitBuilder {
700    fn new(parent: AuthBuilder) -> Self {
701        Self { parent }
702    }
703
704    /// Configure rate limiting per IP
705    pub fn per_ip(mut self, (requests, window): (u32, Duration)) -> Self {
706        self.parent.config.rate_limiting = RateLimitConfig {
707            enabled: true,
708            max_requests: requests,
709            window,
710            burst: requests / 10,
711            per_user_enabled: false,
712            max_requests_per_user: requests,
713            per_user_window: window,
714        };
715        self
716    }
717
718    /// Disable rate limiting
719    pub fn disabled(mut self) -> Self {
720        self.parent.config.rate_limiting.enabled = false;
721        self
722    }
723
724    /// Complete rate limiting configuration and return to main builder
725    pub fn done(self) -> AuthBuilder {
726        self.parent
727    }
728}
729
730/// Security configuration builder
731pub struct SecurityBuilder {
732    parent: AuthBuilder,
733}
734
735impl SecurityBuilder {
736    fn new(parent: AuthBuilder) -> Self {
737        Self { parent }
738    }
739
740    /// Set minimum password length
741    pub fn min_password_length(mut self, length: usize) -> Self {
742        self.parent.config.security.min_password_length = length;
743        self
744    }
745
746    /// Enable/disable password complexity requirements
747    pub fn require_password_complexity(mut self, required: bool) -> Self {
748        self.parent.config.security.require_password_complexity = required;
749        self
750    }
751
752    /// Enable/disable secure cookies
753    pub fn secure_cookies(mut self, enabled: bool) -> Self {
754        self.parent.config.security.secure_cookies = enabled;
755        self
756    }
757
758    /// Complete security configuration and return to main builder
759    pub fn done(self) -> AuthBuilder {
760        self.parent
761    }
762}
763
764/// Audit configuration builder
765pub struct AuditBuilder {
766    parent: AuthBuilder,
767}
768
769impl AuditBuilder {
770    fn new(parent: AuthBuilder) -> Self {
771        Self { parent }
772    }
773
774    /// Enable audit logging
775    pub fn enabled(mut self, enabled: bool) -> Self {
776        self.parent.config.audit.enabled = enabled;
777        self
778    }
779
780    /// Log successful authentications
781    pub fn log_success(mut self, enabled: bool) -> Self {
782        self.parent.config.audit.log_success = enabled;
783        self
784    }
785
786    /// Log failed authentications
787    pub fn log_failures(mut self, enabled: bool) -> Self {
788        self.parent.config.audit.log_failures = enabled;
789        self
790    }
791
792    /// Complete audit configuration and return to main builder
793    pub fn done(self) -> AuthBuilder {
794        self.parent
795    }
796}
797
798impl Default for AuthBuilder {
799    fn default() -> Self {
800        Self::new()
801    }
802}