Microservice to bring 2FA to self hosted PDSes
at feature/admin-rbac 154 lines 4.8 kB view raw
1use jacquard_common::IntoStatic; 2use jacquard_common::session::SessionStoreError; 3use jacquard_common::types::did::Did; 4use jacquard_oauth::authstore::ClientAuthStore; 5use jacquard_oauth::session::{AuthRequestData, ClientSessionData}; 6use sqlx::SqlitePool; 7 8fn sqlx_to_session_err(e: sqlx::Error) -> SessionStoreError { 9 SessionStoreError::Other(Box::new(e)) 10} 11 12#[derive(Clone)] 13pub struct SqlAuthStore { 14 pool: SqlitePool, 15} 16 17impl SqlAuthStore { 18 pub fn new(pool: SqlitePool) -> Self { 19 Self { pool } 20 } 21} 22 23impl ClientAuthStore for SqlAuthStore { 24 async fn get_session( 25 &self, 26 did: &Did<'_>, 27 session_id: &str, 28 ) -> Result<Option<ClientSessionData<'_>>, SessionStoreError> { 29 let key = format!("{}_{}", did, session_id); 30 31 let row: Option<(String,)> = 32 sqlx::query_as("SELECT data FROM oauth_client_sessions WHERE session_key = ?") 33 .bind(&key) 34 .fetch_optional(&self.pool) 35 .await 36 .map_err(sqlx_to_session_err)?; 37 38 match row { 39 Some((json_data,)) => { 40 let session: ClientSessionData<'_> = serde_json::from_str(&json_data)?; 41 Ok(Some(session.into_static())) 42 } 43 None => Ok(None), 44 } 45 } 46 47 async fn upsert_session( 48 &self, 49 session: ClientSessionData<'_>, 50 ) -> Result<(), SessionStoreError> { 51 let static_session = session.into_static(); 52 let did = static_session.account_did.to_string(); 53 let session_id = static_session.session_id.to_string(); 54 let key = format!("{}_{}", did, session_id); 55 let json_data = serde_json::to_string(&static_session)?; 56 let now = chrono::Utc::now().to_rfc3339(); 57 58 sqlx::query( 59 "INSERT INTO oauth_client_sessions (session_key, did, session_id, data, created_at, updated_at) 60 VALUES (?, ?, ?, ?, ?, ?) 61 ON CONFLICT(session_key) DO UPDATE SET data = excluded.data, updated_at = excluded.updated_at", 62 ) 63 .bind(&key) 64 .bind(&did) 65 .bind(&session_id) 66 .bind(&json_data) 67 .bind(&now) 68 .bind(&now) 69 .execute(&self.pool) 70 .await 71 .map_err(sqlx_to_session_err)?; 72 73 Ok(()) 74 } 75 76 async fn delete_session( 77 &self, 78 did: &Did<'_>, 79 session_id: &str, 80 ) -> Result<(), SessionStoreError> { 81 let key = format!("{}_{}", did, session_id); 82 83 sqlx::query("DELETE FROM oauth_client_sessions WHERE session_key = ?") 84 .bind(&key) 85 .execute(&self.pool) 86 .await 87 .map_err(sqlx_to_session_err)?; 88 89 Ok(()) 90 } 91 92 async fn get_auth_req_info( 93 &self, 94 state: &str, 95 ) -> Result<Option<AuthRequestData<'_>>, SessionStoreError> { 96 let row: Option<(String,)> = 97 sqlx::query_as("SELECT data FROM oauth_auth_requests WHERE state = ?") 98 .bind(state) 99 .fetch_optional(&self.pool) 100 .await 101 .map_err(sqlx_to_session_err)?; 102 103 match row { 104 Some((json_data,)) => { 105 let auth_req: AuthRequestData<'_> = serde_json::from_str(&json_data)?; 106 Ok(Some(auth_req.into_static())) 107 } 108 None => Ok(None), 109 } 110 } 111 112 async fn save_auth_req_info( 113 &self, 114 auth_req_info: &AuthRequestData<'_>, 115 ) -> Result<(), SessionStoreError> { 116 let static_info = auth_req_info.clone().into_static(); 117 let state = static_info.state.to_string(); 118 let json_data = serde_json::to_string(&static_info)?; 119 let now = chrono::Utc::now().to_rfc3339(); 120 121 sqlx::query("INSERT INTO oauth_auth_requests (state, data, created_at) VALUES (?, ?, ?)") 122 .bind(&state) 123 .bind(&json_data) 124 .bind(&now) 125 .execute(&self.pool) 126 .await 127 .map_err(sqlx_to_session_err)?; 128 129 Ok(()) 130 } 131 132 async fn delete_auth_req_info(&self, state: &str) -> Result<(), SessionStoreError> { 133 sqlx::query("DELETE FROM oauth_auth_requests WHERE state = ?") 134 .bind(state) 135 .execute(&self.pool) 136 .await 137 .map_err(sqlx_to_session_err)?; 138 139 Ok(()) 140 } 141} 142 143/// Delete auth requests older than the given number of minutes. 144pub async fn cleanup_stale_auth_requests( 145 pool: &SqlitePool, 146 max_age_minutes: i64, 147) -> Result<u64, sqlx::Error> { 148 let cutoff = (chrono::Utc::now() - chrono::Duration::minutes(max_age_minutes)).to_rfc3339(); 149 let result = sqlx::query("DELETE FROM oauth_auth_requests WHERE created_at < ?") 150 .bind(&cutoff) 151 .execute(pool) 152 .await?; 153 Ok(result.rows_affected()) 154}