use super::DbContext; use rand::Rng; use redis::Commands; use serde::{Deserialize, Serialize}; #[derive(Deserialize, Serialize)] pub struct OauthState { pub state_id: String, pub from_url: String, } const STATE_CHAR_LENGTH: usize = 4; const STATE_EXPIRATION_SECONDS: u64 = 10 * 60; const SESSION_FOLDER: &'static str = "bingo:sessions"; impl DbContext { pub async fn set_oauth_state(&mut self, from_url: &String) -> Option { let mut conn = match self.get_redis_connection().await { Some(x) => x, None => return None, }; let state_id: String = rand::rng() .sample_iter(&rand::distr::Alphabetic) .take(STATE_CHAR_LENGTH) .map(char::from) .collect(); let state = OauthState { state_id: state_id.to_uppercase(), from_url: from_url.clone(), }; let data = match serde_json::to_string(&state) { Ok(x) => x, Err(e) => { log::error!("Given state cannot be serialized to a string: {e}"); return None; } }; let _: () = match conn.set_ex( format!("{SESSION_FOLDER}:{}", state.state_id), data, STATE_EXPIRATION_SECONDS, ) { Ok(x) => x, Err(e) => { log::error!("{e}"); return None; } }; Some(state) } pub async fn fetch_oauth_state(&mut self, id: &str) -> Option { let mut conn = match self.get_redis_connection().await { Some(x) => x, None => return None, }; let data: String = match conn.get(format!("{SESSION_FOLDER}:{}", id)) { Ok(x) => x, Err(e) => { log::error!("{e}"); return None; } }; let state: OauthState = match serde_json::from_str(&data) { Ok(x) => x, Err(e) => { log::error!("Failed to parse OauthState: {e}"); return None; } }; let _: () = match conn.del(format!("{SESSION_FOLDER}:{}", id)) { Ok(x) => x, Err(e) => { log::warn!("Failed to delete state: {e}") } }; Some(state) } }