1use async_trait::async_trait;
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4use std::str::FromStr;
5
6use crate::{
7 Error, OAuthAccount, Session, User, UserId, error::utilities::RequiredFieldExt,
8 session::SessionToken,
9};
10
11#[async_trait]
12pub trait StoragePlugin: Send + Sync + 'static {
13 type Config;
14
15 async fn initialize(&self, config: Self::Config) -> Result<(), Error>;
17
18 async fn health_check(&self) -> Result<(), Error>;
20
21 async fn cleanup(&self) -> Result<(), Error>;
23}
24
25#[async_trait]
26pub trait UserStorage: Send + Sync + 'static {
27 async fn create_user(&self, user: &NewUser) -> Result<User, Error>;
28 async fn get_user(&self, id: &UserId) -> Result<Option<User>, Error>;
29 async fn get_user_by_email(&self, email: &str) -> Result<Option<User>, Error>;
30 async fn get_or_create_user_by_email(&self, email: &str) -> Result<User, Error>;
31 async fn update_user(&self, user: &User) -> Result<User, Error>;
32 async fn delete_user(&self, id: &UserId) -> Result<(), Error>;
33 async fn set_user_email_verified(&self, user_id: &UserId) -> Result<(), Error>;
34}
35
36#[async_trait]
37pub trait SessionStorage: Send + Sync + 'static {
38 async fn create_session(&self, session: &Session) -> Result<Session, Error>;
39 async fn get_session(&self, token: &SessionToken) -> Result<Option<Session>, Error>;
40 async fn delete_session(&self, token: &SessionToken) -> Result<(), Error>;
41 async fn cleanup_expired_sessions(&self) -> Result<(), Error>;
42 async fn delete_sessions_for_user(&self, user_id: &UserId) -> Result<(), Error>;
43}
44
45#[async_trait]
50pub trait PasswordStorage: UserStorage {
51 async fn set_password_hash(&self, user_id: &UserId, hash: &str) -> Result<(), Error>;
53
54 async fn get_password_hash(&self, user_id: &UserId) -> Result<Option<String>, Error>;
56}
57
58#[async_trait]
63pub trait OAuthStorage: UserStorage {
64 async fn create_oauth_account(
66 &self,
67 provider: &str,
68 subject: &str,
69 user_id: &UserId,
70 ) -> Result<OAuthAccount, Error>;
71
72 async fn get_user_by_provider_and_subject(
74 &self,
75 provider: &str,
76 subject: &str,
77 ) -> Result<Option<User>, Error>;
78
79 async fn get_oauth_account_by_provider_and_subject(
81 &self,
82 provider: &str,
83 subject: &str,
84 ) -> Result<Option<OAuthAccount>, Error>;
85
86 async fn link_oauth_account(
88 &self,
89 user_id: &UserId,
90 provider: &str,
91 subject: &str,
92 ) -> Result<(), Error>;
93
94 async fn store_pkce_verifier(
96 &self,
97 csrf_state: &str,
98 pkce_verifier: &str,
99 expires_in: chrono::Duration,
100 ) -> Result<(), Error>;
101
102 async fn get_pkce_verifier(&self, csrf_state: &str) -> Result<Option<String>, Error>;
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize)]
107pub struct NewUser {
108 pub id: UserId,
109 pub email: String,
110 pub name: Option<String>,
111 pub email_verified_at: Option<DateTime<Utc>>,
112}
113
114impl NewUser {
115 pub fn builder() -> NewUserBuilder {
116 NewUserBuilder::default()
117 }
118
119 pub fn new(email: String) -> Self {
120 NewUserBuilder::default()
121 .email(email)
122 .build()
123 .expect("Default builder should never fail")
124 }
125
126 pub fn with_id(id: UserId, email: String) -> Self {
127 NewUserBuilder::default()
128 .id(id)
129 .email(email)
130 .build()
131 .expect("Default builder should never fail")
132 }
133}
134
135#[derive(Default)]
136pub struct NewUserBuilder {
137 id: Option<UserId>,
138 email: Option<String>,
139 name: Option<String>,
140 email_verified_at: Option<DateTime<Utc>>,
141}
142
143impl NewUserBuilder {
144 pub fn id(mut self, id: UserId) -> Self {
145 self.id = Some(id);
146 self
147 }
148
149 pub fn email(mut self, email: String) -> Self {
150 self.email = Some(email);
151 self
152 }
153
154 pub fn name(mut self, name: String) -> Self {
155 self.name = Some(name);
156 self
157 }
158
159 pub fn email_verified_at(mut self, email_verified_at: Option<DateTime<Utc>>) -> Self {
160 self.email_verified_at = email_verified_at;
161 self
162 }
163
164 pub fn build(self) -> Result<NewUser, Error> {
165 Ok(NewUser {
166 id: self.id.unwrap_or_default(),
167 email: self.email.require_field("Email")?,
168 name: self.name,
169 email_verified_at: self.email_verified_at,
170 })
171 }
172}
173
174#[async_trait]
179pub trait PasskeyStorage: UserStorage {
180 async fn add_passkey(
182 &self,
183 user_id: &UserId,
184 credential_id: &str,
185 passkey_json: &str,
186 ) -> Result<(), Error>;
187
188 async fn get_passkey_by_credential_id(
190 &self,
191 credential_id: &str,
192 ) -> Result<Option<String>, Error>;
193
194 async fn get_passkeys(&self, user_id: &UserId) -> Result<Vec<String>, Error>;
196
197 async fn set_passkey_challenge(
199 &self,
200 challenge_id: &str,
201 challenge: &str,
202 expires_in: chrono::Duration,
203 ) -> Result<(), Error>;
204
205 async fn get_passkey_challenge(&self, challenge_id: &str) -> Result<Option<String>, Error>;
207}
208
209#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
211pub enum TokenPurpose {
212 MagicLink,
214 PasswordReset,
216 EmailVerification,
218}
219
220impl TokenPurpose {
221 pub fn as_str(&self) -> &'static str {
223 match self {
224 TokenPurpose::MagicLink => "magic_link",
225 TokenPurpose::PasswordReset => "password_reset",
226 TokenPurpose::EmailVerification => "email_verification",
227 }
228 }
229}
230
231impl FromStr for TokenPurpose {
232 type Err = Error;
233
234 fn from_str(s: &str) -> Result<Self, Self::Err> {
235 use crate::error::StorageError;
236 match s {
237 "magic_link" => Ok(TokenPurpose::MagicLink),
238 "password_reset" => Ok(TokenPurpose::PasswordReset),
239 "email_verification" => Ok(TokenPurpose::EmailVerification),
240 _ => Err(Error::Storage(StorageError::Database(format!(
241 "Invalid token purpose: {s}"
242 )))),
243 }
244 }
245}
246
247#[derive(Debug, Clone)]
249pub struct SecureToken {
250 pub user_id: UserId,
251 pub token: String,
252 pub purpose: TokenPurpose,
253 pub used_at: Option<DateTime<Utc>>,
254 pub expires_at: DateTime<Utc>,
255 pub created_at: DateTime<Utc>,
256 pub updated_at: DateTime<Utc>,
257}
258
259impl SecureToken {
260 pub fn new(
261 user_id: UserId,
262 token: String,
263 purpose: TokenPurpose,
264 used_at: Option<DateTime<Utc>>,
265 expires_at: DateTime<Utc>,
266 created_at: DateTime<Utc>,
267 updated_at: DateTime<Utc>,
268 ) -> Self {
269 Self {
270 user_id,
271 token,
272 purpose,
273 used_at,
274 expires_at,
275 created_at,
276 updated_at,
277 }
278 }
279
280 pub fn used(&self) -> bool {
281 self.used_at.is_some()
282 }
283}
284
285impl PartialEq for SecureToken {
286 fn eq(&self, other: &Self) -> bool {
287 self.user_id == other.user_id
288 && self.token == other.token
289 && self.purpose == other.purpose
290 && self.used_at == other.used_at
291 && self.expires_at.timestamp() == other.expires_at.timestamp()
293 && self.created_at.timestamp() == other.created_at.timestamp()
294 && self.updated_at.timestamp() == other.updated_at.timestamp()
295 }
296}
297
298#[async_trait]
303pub trait TokenStorage: UserStorage {
304 async fn save_secure_token(&self, token: &SecureToken) -> Result<(), Error>;
306
307 async fn get_secure_token(
309 &self,
310 token: &str,
311 purpose: TokenPurpose,
312 ) -> Result<Option<SecureToken>, Error>;
313
314 async fn set_secure_token_used(&self, token: &str, purpose: TokenPurpose) -> Result<(), Error>;
316
317 async fn cleanup_expired_secure_tokens(&self) -> Result<(), Error>;
319}