1use codlet_core::hashing::LookupKey;
4use codlet_core::secret::{SessionId, SubjectId};
5use codlet_core::store::error::StoreError;
6use codlet_core::store::session::{ActiveSessionRecord, SessionRecord, SessionStore};
7
8use crate::SqliteStore;
9
10impl SessionStore for SqliteStore {
11 async fn find_active_session(
12 &self,
13 candidates: &[LookupKey],
14 now: u64,
15 ) -> Result<Option<ActiveSessionRecord>, StoreError> {
16 let now_i = now as i64;
17 for candidate in candidates {
18 let row: Option<(String, String, String, i64)> = sqlx::query_as(
19 "SELECT id, subject, key_version, expires_at
20 FROM codlet_sessions
21 WHERE lookup_key = ?
22 AND revoked_at IS NULL
23 AND expires_at > ?
24 LIMIT 1",
25 )
26 .bind(candidate.as_str())
27 .bind(now_i)
28 .fetch_optional(&self.pool)
29 .await
30 .map_err(|e| StoreError::Backend(e.to_string()))?;
31
32 if let Some((id, subject, _kv, exp)) = row {
33 return Ok(Some(ActiveSessionRecord {
34 id: SessionId::new(id),
35 subject: SubjectId::new(subject),
36 expires_at: exp as u64,
37 }));
38 }
39 }
40 Ok(None)
41 }
42
43 async fn insert_session(&self, record: SessionRecord) -> Result<(), StoreError> {
44 sqlx::query(
45 "INSERT INTO codlet_sessions
46 (id, lookup_key, key_version, subject, created_at, expires_at)
47 VALUES (?, ?, ?, ?, ?, ?)",
48 )
49 .bind(record.id.as_str())
50 .bind(record.lookup_key.as_str())
51 .bind(record.key_version.as_str())
52 .bind(record.subject.as_str())
53 .bind(record.created_at as i64)
54 .bind(record.expires_at as i64)
55 .execute(&self.pool)
56 .await
57 .map_err(|e| StoreError::Backend(e.to_string()))?;
58 Ok(())
59 }
60
61 async fn revoke_session(&self, session_id: &SessionId, now: u64) -> Result<(), StoreError> {
62 sqlx::query(
63 "UPDATE codlet_sessions
64 SET revoked_at = ?
65 WHERE id = ? AND revoked_at IS NULL",
66 )
67 .bind(now as i64)
68 .bind(session_id.as_str())
69 .execute(&self.pool)
70 .await
71 .map_err(|e| StoreError::Backend(e.to_string()))?;
72 Ok(())
73 }
74}