1#[cfg(feature = "session-management")]
11type SessionEntry = (SessionData, Option<DateTime<Utc>>);
12
13#[cfg(feature = "session-management")]
14use crate::error::{Error, Result};
15#[cfg(feature = "session-management")]
16use crate::session::{SessionData, SessionStorage};
17#[cfg(feature = "session-management")]
18use chrono::{DateTime, Utc};
19#[cfg(feature = "session-management")]
20use std::collections::HashMap;
21#[cfg(feature = "session-management")]
22use std::sync::{Arc, RwLock};
23
24#[cfg(feature = "session-management")]
26#[derive(Debug)]
27pub struct MemoryStorage {
28 sessions: Arc<RwLock<HashMap<String, SessionEntry>>>,
29}
30
31#[cfg(feature = "session-management")]
32impl MemoryStorage {
33 pub fn new() -> Self {
34 Self {
35 sessions: Arc::new(RwLock::new(HashMap::new())),
36 }
37 }
38
39 pub fn cleanup_expired(&self) {
41 let now = Utc::now();
42 let mut sessions = self.sessions.write().unwrap();
43 sessions.retain(|_, (_, expires_at)| {
44 match expires_at {
45 Some(expiry) => *expiry > now,
46 None => true, }
48 });
49 }
50}
51
52#[cfg(feature = "session-management")]
53#[async_trait::async_trait]
54impl SessionStorage for MemoryStorage {
55 async fn store_session(
56 &self,
57 key: &str,
58 session: &SessionData,
59 expires_at: Option<DateTime<Utc>>,
60 ) -> Result<()> {
61 let mut sessions = self
62 .sessions
63 .write()
64 .map_err(|_| Error::storage("Failed to acquire write lock for memory storage"))?;
65 sessions.insert(key.to_string(), (session.clone(), expires_at));
66 Ok(())
67 }
68
69 async fn get_session(&self, key: &str) -> Result<Option<SessionData>> {
70 let sessions = self
71 .sessions
72 .read()
73 .map_err(|_| Error::storage("Failed to acquire read lock for memory storage"))?;
74
75 if let Some((session_data, expires_at)) = sessions.get(key) {
76 if let Some(expiry) = expires_at {
78 if *expiry <= Utc::now() {
79 return Ok(None);
80 }
81 }
82 Ok(Some(session_data.clone()))
83 } else {
84 Ok(None)
85 }
86 }
87
88 async fn remove_session(&self, key: &str) -> Result<()> {
89 let mut sessions = self
90 .sessions
91 .write()
92 .map_err(|_| Error::storage("Failed to acquire write lock for memory storage"))?;
93 sessions.remove(key);
94 Ok(())
95 }
96
97 async fn clear_all_sessions(&self) -> Result<()> {
98 let mut sessions = self
99 .sessions
100 .write()
101 .map_err(|_| Error::storage("Failed to acquire write lock for memory storage"))?;
102 sessions.clear();
103 Ok(())
104 }
105
106 async fn list_session_keys(&self) -> Result<Vec<String>> {
107 let sessions = self
108 .sessions
109 .read()
110 .map_err(|_| Error::storage("Failed to acquire read lock for memory storage"))?;
111 Ok(sessions.keys().cloned().collect())
112 }
113
114 fn is_available(&self) -> bool {
115 true
116 }
117}
118
119#[cfg(feature = "session-management")]
120impl Default for MemoryStorage {
121 fn default() -> Self {
122 Self::new()
123 }
124}
125
126#[cfg(all(feature = "session-management", target_arch = "wasm32"))]
128#[derive(Debug)]
129pub struct LocalStorage {
130 key_prefix: String,
131}
132
133#[cfg(all(feature = "session-management", target_arch = "wasm32"))]
134impl LocalStorage {
135 pub fn new(key_prefix: Option<String>) -> Result<Self> {
136 if !Self::is_storage_available() {
138 return Err(Error::storage(
139 "localStorage is not available in this environment",
140 ));
141 }
142
143 Ok(Self {
144 key_prefix: key_prefix.unwrap_or_else(|| "supabase_".to_string()),
145 })
146 }
147
148 fn is_storage_available() -> bool {
149 web_sys::window()
150 .and_then(|w| w.local_storage().ok().flatten())
151 .is_some()
152 }
153
154 fn get_storage() -> Result<web_sys::Storage> {
155 web_sys::window()
156 .ok_or_else(|| Error::storage("No window object available"))?
157 .local_storage()
158 .map_err(|_| Error::storage("Failed to access localStorage"))?
159 .ok_or_else(|| Error::storage("localStorage is not available"))
160 }
161
162 fn make_key(&self, key: &str) -> String {
163 format!("{}{}", self.key_prefix, key)
164 }
165}
166
167#[cfg(all(feature = "session-management", target_arch = "wasm32"))]
168#[async_trait::async_trait]
169impl SessionStorage for LocalStorage {
170 async fn store_session(
171 &self,
172 key: &str,
173 session: &SessionData,
174 _expires_at: Option<DateTime<Utc>>,
175 ) -> Result<()> {
176 let storage = Self::get_storage()?;
177 let storage_key = self.make_key(key);
178 let serialized = serde_json::to_string(session)
179 .map_err(|e| Error::storage(format!("Failed to serialize session: {}", e)))?;
180
181 storage
182 .set_item(&storage_key, &serialized)
183 .map_err(|_| Error::storage("Failed to store session in localStorage"))?;
184
185 Ok(())
186 }
187
188 async fn get_session(&self, key: &str) -> Result<Option<SessionData>> {
189 let storage = Self::get_storage()?;
190 let storage_key = self.make_key(key);
191
192 match storage.get_item(&storage_key) {
193 Ok(Some(serialized)) => {
194 let session_data: SessionData = serde_json::from_str(&serialized)
195 .map_err(|e| Error::storage(format!("Failed to deserialize session: {}", e)))?;
196
197 if session_data.session.expires_at <= Utc::now() {
199 let _ = self.remove_session(key).await;
201 Ok(None)
202 } else {
203 Ok(Some(session_data))
204 }
205 }
206 Ok(None) => Ok(None),
207 Err(_) => Err(Error::storage("Failed to read from localStorage")),
208 }
209 }
210
211 async fn remove_session(&self, key: &str) -> Result<()> {
212 let storage = Self::get_storage()?;
213 let storage_key = self.make_key(key);
214 storage
215 .remove_item(&storage_key)
216 .map_err(|_| Error::storage("Failed to remove session from localStorage"))?;
217 Ok(())
218 }
219
220 async fn clear_all_sessions(&self) -> Result<()> {
221 let storage = Self::get_storage()?;
222 let keys_to_remove: Vec<String> = (0..storage.length().unwrap_or(0))
223 .filter_map(|i| storage.key(i).ok().flatten())
224 .filter(|key| key.starts_with(&self.key_prefix))
225 .collect();
226
227 for key in keys_to_remove {
228 storage
229 .remove_item(&key)
230 .map_err(|_| Error::storage("Failed to clear session from localStorage"))?;
231 }
232
233 Ok(())
234 }
235
236 async fn list_session_keys(&self) -> Result<Vec<String>> {
237 let storage = Self::get_storage()?;
238 let keys: Vec<String> = (0..storage.length().unwrap_or(0))
239 .filter_map(|i| storage.key(i).ok().flatten())
240 .filter(|key| key.starts_with(&self.key_prefix))
241 .map(|key| {
242 key.strip_prefix(&self.key_prefix)
243 .unwrap_or(&key)
244 .to_string()
245 })
246 .collect();
247
248 Ok(keys)
249 }
250
251 fn is_available(&self) -> bool {
252 Self::is_storage_available()
253 }
254}
255
256#[cfg(all(feature = "session-management", not(target_arch = "wasm32")))]
258#[derive(Debug)]
259pub struct FileSystemStorage {
260 base_dir: std::path::PathBuf,
261}
262
263#[cfg(all(feature = "session-management", not(target_arch = "wasm32")))]
264impl FileSystemStorage {
265 pub fn new(base_dir: Option<std::path::PathBuf>) -> Result<Self> {
266 let base_dir = match base_dir {
267 Some(dir) => dir,
268 None => {
269 dirs::data_local_dir()
271 .ok_or_else(|| Error::storage("Could not determine data directory"))?
272 .join("supabase-sessions")
273 }
274 };
275
276 std::fs::create_dir_all(&base_dir)
278 .map_err(|e| Error::storage(format!("Failed to create session directory: {}", e)))?;
279
280 Ok(Self { base_dir })
281 }
282
283 fn get_session_path(&self, key: &str) -> std::path::PathBuf {
284 self.base_dir.join(format!("{}.json", key))
285 }
286}
287
288#[cfg(all(feature = "session-management", not(target_arch = "wasm32")))]
289#[async_trait::async_trait]
290impl SessionStorage for FileSystemStorage {
291 async fn store_session(
292 &self,
293 key: &str,
294 session: &SessionData,
295 _expires_at: Option<DateTime<Utc>>,
296 ) -> Result<()> {
297 let path = self.get_session_path(key);
298 let serialized = serde_json::to_string_pretty(session)
299 .map_err(|e| Error::storage(format!("Failed to serialize session: {}", e)))?;
300
301 tokio::fs::write(&path, serialized)
302 .await
303 .map_err(|e| Error::storage(format!("Failed to write session file: {}", e)))?;
304
305 Ok(())
306 }
307
308 async fn get_session(&self, key: &str) -> Result<Option<SessionData>> {
309 let path = self.get_session_path(key);
310
311 match tokio::fs::read_to_string(&path).await {
312 Ok(serialized) => {
313 let session_data: SessionData = serde_json::from_str(&serialized)
314 .map_err(|e| Error::storage(format!("Failed to deserialize session: {}", e)))?;
315
316 if session_data.session.expires_at <= Utc::now() {
318 let _ = self.remove_session(key).await;
320 Ok(None)
321 } else {
322 Ok(Some(session_data))
323 }
324 }
325 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None),
326 Err(e) => Err(Error::storage(format!(
327 "Failed to read session file: {}",
328 e
329 ))),
330 }
331 }
332
333 async fn remove_session(&self, key: &str) -> Result<()> {
334 let path = self.get_session_path(key);
335 match tokio::fs::remove_file(&path).await {
336 Ok(_) => Ok(()),
337 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(()), Err(e) => Err(Error::storage(format!(
339 "Failed to remove session file: {}",
340 e
341 ))),
342 }
343 }
344
345 async fn clear_all_sessions(&self) -> Result<()> {
346 let mut dir_entries = tokio::fs::read_dir(&self.base_dir)
347 .await
348 .map_err(|e| Error::storage(format!("Failed to read session directory: {}", e)))?;
349
350 while let Some(entry) = dir_entries
351 .next_entry()
352 .await
353 .map_err(|e| Error::storage(format!("Failed to read directory entry: {}", e)))?
354 {
355 let path = entry.path();
356 if path.extension().and_then(|s| s.to_str()) == Some("json") {
357 match tokio::fs::remove_file(&path).await {
358 Ok(_) => {}
359 Err(e) => {
360 tracing::warn!("Failed to remove session file {:?}: {}", path, e);
361 }
362 }
363 }
364 }
365
366 Ok(())
367 }
368
369 async fn list_session_keys(&self) -> Result<Vec<String>> {
370 let mut dir_entries = tokio::fs::read_dir(&self.base_dir)
371 .await
372 .map_err(|e| Error::storage(format!("Failed to read session directory: {}", e)))?;
373
374 let mut keys = Vec::new();
375
376 while let Some(entry) = dir_entries
377 .next_entry()
378 .await
379 .map_err(|e| Error::storage(format!("Failed to read directory entry: {}", e)))?
380 {
381 let path = entry.path();
382 if path.extension().and_then(|s| s.to_str()) == Some("json") {
383 if let Some(file_stem) = path.file_stem().and_then(|s| s.to_str()) {
384 keys.push(file_stem.to_string());
385 }
386 }
387 }
388
389 Ok(keys)
390 }
391
392 fn is_available(&self) -> bool {
393 self.base_dir.exists() && self.base_dir.is_dir()
394 }
395}
396
397#[cfg(all(feature = "session-management", feature = "session-encryption"))]
399pub struct EncryptedStorage {
400 inner: Arc<dyn SessionStorage>,
401 encryptor: Arc<crate::session::encryption::SessionEncryptor>,
402}
403
404#[cfg(all(feature = "session-management", feature = "session-encryption"))]
405impl std::fmt::Debug for EncryptedStorage {
406 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
407 f.debug_struct("EncryptedStorage")
408 .field("inner", &"Arc<dyn SessionStorage>")
409 .field("encryptor", &"Arc<SessionEncryptor>")
410 .finish()
411 }
412}
413
414#[cfg(all(feature = "session-management", feature = "session-encryption"))]
415impl EncryptedStorage {
416 pub fn new(inner: Arc<dyn SessionStorage>, encryption_key: [u8; 32]) -> Result<Self> {
417 let encryptor = Arc::new(crate::session::encryption::SessionEncryptor::new(
418 encryption_key,
419 )?);
420 Ok(Self { inner, encryptor })
421 }
422}
423
424#[cfg(all(feature = "session-management", feature = "session-encryption"))]
425#[async_trait::async_trait]
426impl SessionStorage for EncryptedStorage {
427 async fn store_session(
428 &self,
429 key: &str,
430 session: &SessionData,
431 expires_at: Option<DateTime<Utc>>,
432 ) -> Result<()> {
433 let encrypted_session = self.encryptor.encrypt_session(session)?;
434 self.inner
435 .store_session(key, &encrypted_session, expires_at)
436 .await
437 }
438
439 async fn get_session(&self, key: &str) -> Result<Option<SessionData>> {
440 if let Some(encrypted_session) = self.inner.get_session(key).await? {
441 let decrypted_session = self.encryptor.decrypt_session(&encrypted_session)?;
442 Ok(Some(decrypted_session))
443 } else {
444 Ok(None)
445 }
446 }
447
448 async fn remove_session(&self, key: &str) -> Result<()> {
449 self.inner.remove_session(key).await
450 }
451
452 async fn clear_all_sessions(&self) -> Result<()> {
453 self.inner.clear_all_sessions().await
454 }
455
456 async fn list_session_keys(&self) -> Result<Vec<String>> {
457 self.inner.list_session_keys().await
458 }
459
460 fn is_available(&self) -> bool {
461 self.inner.is_available()
462 }
463}
464
465#[cfg(feature = "session-management")]
467#[derive(Debug)]
468pub enum StorageBackend {
469 Memory(MemoryStorage),
470 #[cfg(target_arch = "wasm32")]
471 LocalStorage(LocalStorage),
472 #[cfg(not(target_arch = "wasm32"))]
473 FileSystem(FileSystemStorage),
474 #[cfg(feature = "session-encryption")]
475 Encrypted(EncryptedStorage),
476}
477
478#[cfg(feature = "session-management")]
479impl StorageBackend {
480 pub async fn store_session(
482 &self,
483 key: &str,
484 session: &SessionData,
485 expires_at: Option<DateTime<Utc>>,
486 ) -> Result<()> {
487 match self {
488 StorageBackend::Memory(storage) => {
489 storage.store_session(key, session, expires_at).await
490 }
491 #[cfg(target_arch = "wasm32")]
492 StorageBackend::LocalStorage(storage) => {
493 storage.store_session(key, session, expires_at).await
494 }
495 #[cfg(not(target_arch = "wasm32"))]
496 StorageBackend::FileSystem(storage) => {
497 storage.store_session(key, session, expires_at).await
498 }
499 #[cfg(feature = "session-encryption")]
500 StorageBackend::Encrypted(storage) => {
501 storage.store_session(key, session, expires_at).await
502 }
503 }
504 }
505
506 pub async fn get_session(&self, key: &str) -> Result<Option<SessionData>> {
508 match self {
509 StorageBackend::Memory(storage) => storage.get_session(key).await,
510 #[cfg(target_arch = "wasm32")]
511 StorageBackend::LocalStorage(storage) => storage.get_session(key).await,
512 #[cfg(not(target_arch = "wasm32"))]
513 StorageBackend::FileSystem(storage) => storage.get_session(key).await,
514 #[cfg(feature = "session-encryption")]
515 StorageBackend::Encrypted(storage) => storage.get_session(key).await,
516 }
517 }
518
519 pub async fn remove_session(&self, key: &str) -> Result<()> {
521 match self {
522 StorageBackend::Memory(storage) => storage.remove_session(key).await,
523 #[cfg(target_arch = "wasm32")]
524 StorageBackend::LocalStorage(storage) => storage.remove_session(key).await,
525 #[cfg(not(target_arch = "wasm32"))]
526 StorageBackend::FileSystem(storage) => storage.remove_session(key).await,
527 #[cfg(feature = "session-encryption")]
528 StorageBackend::Encrypted(storage) => storage.remove_session(key).await,
529 }
530 }
531
532 pub async fn clear_all_sessions(&self) -> Result<()> {
534 match self {
535 StorageBackend::Memory(storage) => storage.clear_all_sessions().await,
536 #[cfg(target_arch = "wasm32")]
537 StorageBackend::LocalStorage(storage) => storage.clear_all_sessions().await,
538 #[cfg(not(target_arch = "wasm32"))]
539 StorageBackend::FileSystem(storage) => storage.clear_all_sessions().await,
540 #[cfg(feature = "session-encryption")]
541 StorageBackend::Encrypted(storage) => storage.clear_all_sessions().await,
542 }
543 }
544
545 pub async fn list_session_keys(&self) -> Result<Vec<String>> {
547 match self {
548 StorageBackend::Memory(storage) => storage.list_session_keys().await,
549 #[cfg(target_arch = "wasm32")]
550 StorageBackend::LocalStorage(storage) => storage.list_session_keys().await,
551 #[cfg(not(target_arch = "wasm32"))]
552 StorageBackend::FileSystem(storage) => storage.list_session_keys().await,
553 #[cfg(feature = "session-encryption")]
554 StorageBackend::Encrypted(storage) => storage.list_session_keys().await,
555 }
556 }
557
558 pub fn is_available(&self) -> bool {
560 match self {
561 StorageBackend::Memory(storage) => storage.is_available(),
562 #[cfg(target_arch = "wasm32")]
563 StorageBackend::LocalStorage(storage) => storage.is_available(),
564 #[cfg(not(target_arch = "wasm32"))]
565 StorageBackend::FileSystem(storage) => storage.is_available(),
566 #[cfg(feature = "session-encryption")]
567 StorageBackend::Encrypted(storage) => storage.is_available(),
568 }
569 }
570}
571
572#[cfg(feature = "session-management")]
574pub fn create_default_storage() -> Result<Arc<StorageBackend>> {
575 #[cfg(target_arch = "wasm32")]
576 {
577 if let Ok(storage) = LocalStorage::new(None) {
578 Ok(Arc::new(StorageBackend::LocalStorage(storage)))
579 } else {
580 Ok(Arc::new(StorageBackend::Memory(MemoryStorage::new())))
582 }
583 }
584
585 #[cfg(not(target_arch = "wasm32"))]
586 {
587 if let Ok(storage) = FileSystemStorage::new(None) {
588 Ok(Arc::new(StorageBackend::FileSystem(storage)))
589 } else {
590 Ok(Arc::new(StorageBackend::Memory(MemoryStorage::new())))
592 }
593 }
594}