metabase_api_rs/api/
auth.rs1use crate::core::models::User;
6use secrecy::{ExposeSecret, Secret, SecretString};
7use std::time::{Duration, Instant};
8use zeroize::{Zeroize, ZeroizeOnDrop};
9
10#[derive(Clone, Zeroize, ZeroizeOnDrop)]
12pub struct SecureToken {
13 #[zeroize(skip)]
14 expires_at: Option<Instant>,
15 token: String,
16}
17
18impl SecureToken {
19 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 pub fn is_expired(&self) -> bool {
27 self.expires_at
28 .is_some_and(|expiry| Instant::now() > expiry)
29 }
30
31 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#[derive(Debug, Clone)]
52pub struct AuthManager {
53 session_token: Option<SecureToken>,
54 current_user: Option<User>,
55}
56
57impl AuthManager {
58 pub fn new() -> Self {
60 Self {
61 session_token: None,
62 current_user: None,
63 }
64 }
65
66 pub fn is_authenticated(&self) -> bool {
68 self.session_token
69 .as_ref()
70 .is_some_and(|token| !token.is_expired())
71 }
72
73 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 pub fn get_session_id(&self) -> Option<String> {
82 self.session_token().map(|s| s.to_string())
83 }
84
85 pub fn current_user(&self) -> Option<&User> {
87 self.current_user.as_ref()
88 }
89
90 pub fn set_session(&mut self, token: String, user: User) {
92 self.set_session_with_ttl(token, user, None);
93 }
94
95 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 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#[derive(Clone)]
116pub enum Credentials {
117 EmailPassword {
119 email: String,
120 password: SecretString,
121 },
122 ApiKey { key: Secret<String> },
124}
125
126impl Credentials {
127 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 pub fn new_api_key(key: impl Into<String>) -> Self {
137 Self::ApiKey {
138 key: Secret::new(key.into()),
139 }
140 }
141
142 pub fn email(&self) -> Option<&str> {
144 match self {
145 Self::EmailPassword { email, .. } => Some(email),
146 _ => None,
147 }
148 }
149
150 pub fn password(&self) -> Option<&str> {
152 match self {
153 Self::EmailPassword { password, .. } => Some(password.expose_secret()),
154 _ => None,
155 }
156 }
157
158 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}