auth_patterns/
auth_patterns.rs

1#![allow(clippy::uninlined_format_args)]
2//! Authentication patterns and configuration examples.
3//!
4//! This example demonstrates:
5//! - Environment variable configuration
6//! - API key authentication
7//! - Organization ID configuration
8//! - Project ID configuration
9//! - Custom headers
10//! - Proxy configuration
11//! - Multiple client configurations
12//!
13//! Run with: `cargo run --example auth_patterns`
14
15use openai_ergonomic::{Client, Config, Result};
16use std::env;
17
18#[tokio::main]
19async fn main() -> Result<()> {
20    println!("=== Authentication Patterns ===\n");
21
22    // Example 1: Environment variable authentication
23    println!("1. Environment Variable Authentication:");
24    env_var_auth().await?;
25
26    // Example 2: Direct API key
27    println!("\n2. Direct API Key:");
28    direct_api_key().await?;
29
30    // Example 3: Organization configuration
31    println!("\n3. Organization Configuration:");
32    organization_config()?;
33
34    // Example 4: Project configuration
35    println!("\n4. Project Configuration:");
36    project_config()?;
37
38    // Example 5: Custom headers
39    println!("\n5. Custom Headers:");
40    custom_headers()?;
41
42    // Example 6: Proxy configuration
43    println!("\n6. Proxy Configuration:");
44    proxy_config()?;
45
46    // Example 7: Multiple client configurations
47    println!("\n7. Multiple Client Configurations:");
48    multiple_clients()?;
49
50    // Example 8: Configuration validation
51    println!("\n8. Configuration Validation:");
52    config_validation()?;
53
54    // Example 9: Secure key management
55    println!("\n9. Secure Key Management:");
56    secure_key_management();
57
58    Ok(())
59}
60
61async fn env_var_auth() -> Result<()> {
62    // Standard environment variables:
63    // - OPENAI_API_KEY: Your API key
64    // - OPENAI_ORG_ID: Optional organization ID
65    // - OPENAI_PROJECT_ID: Optional project ID
66    // - OPENAI_BASE_URL: Optional custom base URL
67
68    // Check if environment variables are set
69    if env::var("OPENAI_API_KEY").is_err() {
70        println!("Warning: OPENAI_API_KEY not set");
71        println!("Set it with: export OPENAI_API_KEY=your-key-here");
72        return Ok(());
73    }
74
75    // Create client from environment
76    let client = Client::from_env()?.build();
77    println!("Client created from environment variables");
78
79    // Test the client
80    match client.send_chat(client.chat_simple("Hello")).await {
81        Ok(response) => {
82            if let Some(content) = response.content() {
83                println!("Response: {}", content);
84            } else {
85                println!("Response: (no content)");
86            }
87        }
88        Err(e) => println!("Error: {}", e),
89    }
90
91    Ok(())
92}
93
94async fn direct_api_key() -> Result<()> {
95    // Create client with direct API key
96    let api_key = "sk-your-api-key-here"; // Replace with actual key
97    let config = Config::builder().api_key(api_key).build();
98    let client = Client::builder(config)?.build();
99
100    println!("Client created with direct API key");
101
102    // Note: This will fail with invalid key
103    match client.send_chat(client.chat_simple("Hello")).await {
104        Ok(response) => {
105            if let Some(content) = response.content() {
106                println!("Response: {}", content);
107            } else {
108                println!("Response: (no content)");
109            }
110        }
111        Err(e) => println!("Expected error with demo key: {}", e),
112    }
113
114    Ok(())
115}
116
117fn organization_config() -> Result<()> {
118    // Configure client with organization ID
119    let config = Config::builder()
120        .api_key("your-api-key")
121        .organization("org-123456789")
122        .build();
123
124    let _client = Client::builder(config)?.build();
125    println!("Client configured with organization ID");
126
127    // Organization ID is sent in headers with all requests
128    // Useful for:
129    // - Usage tracking per organization
130    // - Access control
131    // - Billing segregation
132
133    Ok(())
134}
135
136fn project_config() -> Result<()> {
137    // Configure client with project ID
138    let config = Config::builder()
139        .api_key("your-api-key")
140        .project("proj-abc123")
141        .build();
142
143    let _client = Client::builder(config)?.build();
144    println!("Client configured with project ID");
145
146    // Project ID helps with:
147    // - Fine-grained usage tracking
148    // - Project-specific rate limits
149    // - Cost allocation
150
151    Ok(())
152}
153
154fn custom_headers() -> Result<()> {
155    // Note: Custom headers are not yet supported in the current API
156    // This would typically be used for:
157    // - Request tracing
158    // - A/B testing
159    // - Custom routing
160
161    let config = Config::builder().api_key("your-api-key").build();
162
163    let _client = Client::builder(config)?.build();
164    println!("Client configured (custom headers not yet supported)");
165
166    // TODO: Add support for custom headers in the future
167    println!("Custom headers feature planned for future implementation");
168
169    Ok(())
170}
171
172fn proxy_config() -> Result<()> {
173    // Note: Proxy configuration is not yet supported in the current API
174    // This would typically be used for:
175    // - Enterprise security policies
176    // - Request monitoring
177    // - Network isolation
178
179    let config = Config::builder().api_key("your-api-key").build();
180
181    let _client = Client::builder(config)?.build();
182    println!("Client configured (proxy support not yet available)");
183
184    // TODO: Add proxy support in the future
185    println!("Proxy configuration feature planned for future implementation");
186
187    Ok(())
188}
189
190fn multiple_clients() -> Result<()> {
191    use reqwest_middleware::ClientBuilder;
192    use std::time::Duration;
193
194    // Create multiple clients for different use cases
195
196    // Production client with retries and longer timeout
197    let prod_http_client = ClientBuilder::new(
198        reqwest::Client::builder()
199            .timeout(Duration::from_secs(60))
200            .build()
201            .expect("Failed to build reqwest client"),
202    )
203    .build();
204
205    let prod_config = Config::builder()
206        .api_key("prod-api-key")
207        .organization("org-prod")
208        .http_client(prod_http_client)
209        .max_retries(5)
210        .build();
211    let prod_client = Client::builder(prod_config)?.build();
212
213    // Development client with debug logging and shorter timeout
214    let dev_http_client = ClientBuilder::new(
215        reqwest::Client::builder()
216            .timeout(Duration::from_secs(10))
217            .build()
218            .expect("Failed to build reqwest client"),
219    )
220    .build();
221
222    let dev_config = Config::builder()
223        .api_key("dev-api-key")
224        .organization("org-dev")
225        .api_base("https://api.openai-dev.com") // Custom endpoint
226        .http_client(dev_http_client)
227        .build();
228    let dev_client = Client::builder(dev_config)?.build();
229
230    // Test client with mocked responses
231    let test_config = Config::builder()
232        .api_key("test-api-key")
233        .api_base("http://localhost:8080") // Local mock server
234        .build();
235    let _test_client = Client::builder(test_config)?.build();
236
237    println!("Created multiple clients:");
238    println!("- Production client with retries");
239    println!("- Development client with custom endpoint");
240    println!("- Test client with mock server");
241
242    // Use appropriate client based on context
243    let _client = if cfg!(debug_assertions) {
244        &dev_client
245    } else {
246        &prod_client
247    };
248
249    println!(
250        "Using {} client",
251        if cfg!(debug_assertions) {
252            "dev"
253        } else {
254            "prod"
255        }
256    );
257
258    Ok(())
259}
260
261fn config_validation() -> Result<()> {
262    // Validate configuration before use
263
264    fn validate_api_key(key: &str) -> bool {
265        // OpenAI API keys typically start with "sk-"
266        key.starts_with("sk-") && key.len() > 20
267    }
268
269    fn validate_org_id(org: &str) -> bool {
270        // Organization IDs typically start with "org-"
271        org.starts_with("org-") && org.len() > 4
272    }
273
274    let api_key = "sk-test-key-123456789";
275    let org_id = "org-12345";
276
277    if !validate_api_key(api_key) {
278        println!("Warning: API key format appears invalid");
279    }
280
281    if !validate_org_id(org_id) {
282        println!("Warning: Organization ID format appears invalid");
283    }
284
285    // Build config only if validation passes
286    if validate_api_key(api_key) {
287        let config = Config::builder()
288            .api_key(api_key)
289            .organization(org_id)
290            .build();
291
292        let _client = Client::builder(config)?.build();
293        println!("Configuration validated and client created");
294    }
295
296    Ok(())
297}
298
299fn secure_key_management() {
300    println!("Secure Key Management Best Practices:");
301    println!();
302
303    // 1. Never hardcode keys
304    println!("1. Never hardcode API keys in source code");
305    // BAD: let api_key = "sk-abc123...";
306    // GOOD: let api_key = env::var("OPENAI_API_KEY")?;
307
308    // 2. Use environment variables
309    println!("2. Use environment variables for local development");
310    if let Ok(key) = env::var("OPENAI_API_KEY") {
311        println!("   API key loaded from environment (length: {})", key.len());
312    }
313
314    // 3. Use secrets management in production
315    println!("3. Use proper secrets management in production:");
316    println!("   - AWS Secrets Manager");
317    println!("   - Azure Key Vault");
318    println!("   - HashiCorp Vault");
319    println!("   - Kubernetes Secrets");
320
321    // 4. Rotate keys regularly
322    println!("4. Rotate API keys regularly");
323
324    // 5. Use different keys per environment
325    println!("5. Use different API keys for each environment:");
326    let keys_by_env = vec![
327        ("development", "OPENAI_API_KEY_DEV"),
328        ("staging", "OPENAI_API_KEY_STAGING"),
329        ("production", "OPENAI_API_KEY_PROD"),
330    ];
331
332    for (env_name, env_var) in keys_by_env {
333        if env::var(env_var).is_ok() {
334            println!("    {} key configured", env_name);
335        } else {
336            println!("    {} key not found", env_name);
337        }
338    }
339
340    // 6. Monitor key usage
341    println!("6. Monitor API key usage:");
342    println!("   - Set up usage alerts");
343    println!("   - Track per-project costs");
344    println!("   - Audit access logs");
345
346    // Example: Loading from a secrets file (for demonstration)
347    #[cfg(unix)]
348    {
349        use std::fs;
350        use std::os::unix::fs::PermissionsExt;
351
352        let secret_file = "/tmp/openai_secret";
353
354        // Check file permissions (should be 600)
355        if let Ok(metadata) = fs::metadata(secret_file) {
356            let permissions = metadata.permissions();
357            if permissions.mode() & 0o777 == 0o600 {
358                println!("7. Secret file has correct permissions (600)");
359            } else {
360                println!("7. Warning: Secret file permissions too open!");
361            }
362        }
363    }
364}