use jacquard_common::IntoStatic; use jacquard_common::session::SessionStoreError; use jacquard_common::types::did::Did; use jacquard_oauth::authstore::ClientAuthStore; use jacquard_oauth::session::{AuthRequestData, ClientSessionData}; use sqlx::SqlitePool; fn sqlx_to_session_err(e: sqlx::Error) -> SessionStoreError { SessionStoreError::Other(Box::new(e)) } #[derive(Clone)] pub struct SqlAuthStore { pool: SqlitePool, } impl SqlAuthStore { pub fn new(pool: SqlitePool) -> Self { Self { pool } } } impl ClientAuthStore for SqlAuthStore { async fn get_session( &self, did: &Did<'_>, session_id: &str, ) -> Result>, SessionStoreError> { let key = format!("{}_{}", did, session_id); let row: Option<(String,)> = sqlx::query_as("SELECT data FROM oauth_client_sessions WHERE session_key = ?") .bind(&key) .fetch_optional(&self.pool) .await .map_err(sqlx_to_session_err)?; match row { Some((json_data,)) => { let session: ClientSessionData<'_> = serde_json::from_str(&json_data)?; Ok(Some(session.into_static())) } None => Ok(None), } } async fn upsert_session( &self, session: ClientSessionData<'_>, ) -> Result<(), SessionStoreError> { let static_session = session.into_static(); let did = static_session.account_did.to_string(); let session_id = static_session.session_id.to_string(); let key = format!("{}_{}", did, session_id); let json_data = serde_json::to_string(&static_session)?; let now = chrono::Utc::now().to_rfc3339(); sqlx::query( "INSERT INTO oauth_client_sessions (session_key, did, session_id, data, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT(session_key) DO UPDATE SET data = excluded.data, updated_at = excluded.updated_at", ) .bind(&key) .bind(&did) .bind(&session_id) .bind(&json_data) .bind(&now) .bind(&now) .execute(&self.pool) .await .map_err(sqlx_to_session_err)?; Ok(()) } async fn delete_session( &self, did: &Did<'_>, session_id: &str, ) -> Result<(), SessionStoreError> { let key = format!("{}_{}", did, session_id); sqlx::query("DELETE FROM oauth_client_sessions WHERE session_key = ?") .bind(&key) .execute(&self.pool) .await .map_err(sqlx_to_session_err)?; Ok(()) } async fn get_auth_req_info( &self, state: &str, ) -> Result>, SessionStoreError> { let row: Option<(String,)> = sqlx::query_as("SELECT data FROM oauth_auth_requests WHERE state = ?") .bind(state) .fetch_optional(&self.pool) .await .map_err(sqlx_to_session_err)?; match row { Some((json_data,)) => { let auth_req: AuthRequestData<'_> = serde_json::from_str(&json_data)?; Ok(Some(auth_req.into_static())) } None => Ok(None), } } async fn save_auth_req_info( &self, auth_req_info: &AuthRequestData<'_>, ) -> Result<(), SessionStoreError> { let static_info = auth_req_info.clone().into_static(); let state = static_info.state.to_string(); let json_data = serde_json::to_string(&static_info)?; let now = chrono::Utc::now().to_rfc3339(); sqlx::query("INSERT INTO oauth_auth_requests (state, data, created_at) VALUES (?, ?, ?)") .bind(&state) .bind(&json_data) .bind(&now) .execute(&self.pool) .await .map_err(sqlx_to_session_err)?; Ok(()) } async fn delete_auth_req_info(&self, state: &str) -> Result<(), SessionStoreError> { sqlx::query("DELETE FROM oauth_auth_requests WHERE state = ?") .bind(state) .execute(&self.pool) .await .map_err(sqlx_to_session_err)?; Ok(()) } } /// Delete auth requests older than the given number of minutes. pub async fn cleanup_stale_auth_requests( pool: &SqlitePool, max_age_minutes: i64, ) -> Result { let cutoff = (chrono::Utc::now() - chrono::Duration::minutes(max_age_minutes)).to_rfc3339(); let result = sqlx::query("DELETE FROM oauth_auth_requests WHERE created_at < ?") .bind(&cutoff) .execute(pool) .await?; Ok(result.rows_affected()) }