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