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 current_user(&self) -> Option<&User> {
82 self.current_user.as_ref()
83 }
84
85 pub fn set_session(&mut self, token: String, user: User) {
87 self.set_session_with_ttl(token, user, None);
88 }
89
90 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 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#[derive(Clone)]
111pub enum Credentials {
112 EmailPassword {
114 email: String,
115 password: SecretString,
116 },
117 ApiKey { key: Secret<String> },
119}
120
121impl Credentials {
122 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 pub fn new_api_key(key: impl Into<String>) -> Self {
132 Self::ApiKey {
133 key: Secret::new(key.into()),
134 }
135 }
136
137 pub fn email(&self) -> Option<&str> {
139 match self {
140 Self::EmailPassword { email, .. } => Some(email),
141 _ => None,
142 }
143 }
144
145 pub fn password(&self) -> Option<&str> {
147 match self {
148 Self::EmailPassword { password, .. } => Some(password.expose_secret()),
149 _ => None,
150 }
151 }
152
153 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}