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 session ID (same as session token)
81    pub fn get_session_id(&self) -> Option<String> {
82        self.session_token().map(|s| s.to_string())
83    }
84
85    /// Gets the current user information
86    pub fn current_user(&self) -> Option<&User> {
87        self.current_user.as_ref()
88    }
89
90    /// Sets the session information with secure token storage
91    pub fn set_session(&mut self, token: String, user: User) {
92        self.set_session_with_ttl(token, user, None);
93    }
94
95    /// Sets the session information with token expiry
96    pub fn set_session_with_ttl(&mut self, token: String, user: User, ttl: Option<Duration>) {
97        self.session_token = Some(SecureToken::new(token, ttl));
98        self.current_user = Some(user);
99    }
100
101    /// Clears the session information
102    pub fn clear_session(&mut self) {
103        self.session_token = None;
104        self.current_user = None;
105    }
106}
107
108impl Default for AuthManager {
109    fn default() -> Self {
110        Self::new()
111    }
112}
113
114/// Authentication credentials with secure password handling
115#[derive(Clone)]
116pub enum Credentials {
117    /// Email and password authentication
118    EmailPassword {
119        email: String,
120        password: SecretString,
121    },
122    /// API key authentication  
123    ApiKey { key: Secret<String> },
124}
125
126impl Credentials {
127    /// Creates new email/password credentials
128    pub fn email_password(email: impl Into<String>, password: impl Into<String>) -> Self {
129        Self::EmailPassword {
130            email: email.into(),
131            password: SecretString::new(password.into()),
132        }
133    }
134
135    /// Creates new API key credentials
136    pub fn new_api_key(key: impl Into<String>) -> Self {
137        Self::ApiKey {
138            key: Secret::new(key.into()),
139        }
140    }
141
142    /// Gets email if this is email/password credentials
143    pub fn email(&self) -> Option<&str> {
144        match self {
145            Self::EmailPassword { email, .. } => Some(email),
146            _ => None,
147        }
148    }
149
150    /// Gets password if this is email/password credentials
151    pub fn password(&self) -> Option<&str> {
152        match self {
153            Self::EmailPassword { password, .. } => Some(password.expose_secret()),
154            _ => None,
155        }
156    }
157
158    /// Gets API key if this is API key credentials
159    pub fn api_key(&self) -> Option<&str> {
160        match self {
161            Self::ApiKey { key } => Some(key.expose_secret()),
162            _ => None,
163        }
164    }
165}
166
167impl std::fmt::Debug for Credentials {
168    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169        match self {
170            Self::EmailPassword { email, .. } => f
171                .debug_struct("EmailPassword")
172                .field("email", email)
173                .field("password", &"[REDACTED]")
174                .finish(),
175            Self::ApiKey { .. } => f
176                .debug_struct("ApiKey")
177                .field("key", &"[REDACTED]")
178                .finish(),
179        }
180    }
181}