metabase_api_rs/api/
auth.rs

1//! Authentication management module
2//!
3//! Handles session management and authentication state with secure memory management
4
5use crate::core::models::User;
6use secrecy::{ExposeSecret, Secret, SecretString};
7use std::time::{Duration, Instant};
8use zeroize::{Zeroize, ZeroizeOnDrop};
9
10/// Secure session token wrapper with automatic memory clearing
11#[derive(Clone, Zeroize, ZeroizeOnDrop)]
12pub struct SecureToken {
13    #[zeroize(skip)]
14    expires_at: Option<Instant>,
15    token: String,
16}
17
18impl SecureToken {
19    /// Creates a new secure token with optional expiry
20    pub fn new(token: String, ttl: Option<Duration>) -> Self {
21        let expires_at = ttl.map(|duration| Instant::now() + duration);
22        Self { token, expires_at }
23    }
24
25    /// Checks if the token is expired
26    pub fn is_expired(&self) -> bool {
27        self.expires_at
28            .is_some_and(|expiry| Instant::now() > expiry)
29    }
30
31    /// Gets the token if not expired
32    pub fn get_if_valid(&self) -> Option<&str> {
33        if self.is_expired() {
34            None
35        } else {
36            Some(&self.token)
37        }
38    }
39}
40
41impl std::fmt::Debug for SecureToken {
42    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43        f.debug_struct("SecureToken")
44            .field("token", &"[REDACTED]")
45            .field("expires_at", &self.expires_at)
46            .finish()
47    }
48}
49
50/// Manages authentication state and session tokens with enhanced security
51#[derive(Debug, Clone)]
52pub struct AuthManager {
53    session_token: Option<SecureToken>,
54    current_user: Option<User>,
55}
56
57impl AuthManager {
58    /// Creates a new AuthManager instance
59    pub fn new() -> Self {
60        Self {
61            session_token: None,
62            current_user: None,
63        }
64    }
65
66    /// Checks if the user is authenticated with a valid, non-expired token
67    pub fn is_authenticated(&self) -> bool {
68        self.session_token
69            .as_ref()
70            .is_some_and(|token| !token.is_expired())
71    }
72
73    /// Gets the current session token if valid and not expired
74    pub fn session_token(&self) -> Option<&str> {
75        self.session_token
76            .as_ref()
77            .and_then(|token| token.get_if_valid())
78    }
79
80    /// Gets the current user information
81    pub fn current_user(&self) -> Option<&User> {
82        self.current_user.as_ref()
83    }
84
85    /// Sets the session information with secure token storage
86    pub fn set_session(&mut self, token: String, user: User) {
87        self.set_session_with_ttl(token, user, None);
88    }
89
90    /// Sets the session information with token expiry
91    pub fn set_session_with_ttl(&mut self, token: String, user: User, ttl: Option<Duration>) {
92        self.session_token = Some(SecureToken::new(token, ttl));
93        self.current_user = Some(user);
94    }
95
96    /// Clears the session information
97    pub fn clear_session(&mut self) {
98        self.session_token = None;
99        self.current_user = None;
100    }
101}
102
103impl Default for AuthManager {
104    fn default() -> Self {
105        Self::new()
106    }
107}
108
109/// Authentication credentials with secure password handling
110#[derive(Clone)]
111pub enum Credentials {
112    /// Email and password authentication
113    EmailPassword {
114        email: String,
115        password: SecretString,
116    },
117    /// API key authentication  
118    ApiKey { key: Secret<String> },
119}
120
121impl Credentials {
122    /// Creates new email/password credentials
123    pub fn email_password(email: impl Into<String>, password: impl Into<String>) -> Self {
124        Self::EmailPassword {
125            email: email.into(),
126            password: SecretString::new(password.into()),
127        }
128    }
129
130    /// Creates new API key credentials
131    pub fn new_api_key(key: impl Into<String>) -> Self {
132        Self::ApiKey {
133            key: Secret::new(key.into()),
134        }
135    }
136
137    /// Gets email if this is email/password credentials
138    pub fn email(&self) -> Option<&str> {
139        match self {
140            Self::EmailPassword { email, .. } => Some(email),
141            _ => None,
142        }
143    }
144
145    /// Gets password if this is email/password credentials
146    pub fn password(&self) -> Option<&str> {
147        match self {
148            Self::EmailPassword { password, .. } => Some(password.expose_secret()),
149            _ => None,
150        }
151    }
152
153    /// Gets API key if this is API key credentials
154    pub fn api_key(&self) -> Option<&str> {
155        match self {
156            Self::ApiKey { key } => Some(key.expose_secret()),
157            _ => None,
158        }
159    }
160}
161
162impl std::fmt::Debug for Credentials {
163    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164        match self {
165            Self::EmailPassword { email, .. } => f
166                .debug_struct("EmailPassword")
167                .field("email", email)
168                .field("password", &"[REDACTED]")
169                .finish(),
170            Self::ApiKey { .. } => f
171                .debug_struct("ApiKey")
172                .field("key", &"[REDACTED]")
173                .finish(),
174        }
175    }
176}