use crate::{ api, state::{Action, Claims, Room, WebUiDispatcher}, }; use jsonwebtoken::dangerous_insecure_decode; pub(crate) type LogicResult = Result, Action>; pub(crate) trait LogicResultExt { /// Consumes self into the vec of Actions to apply to state, /// either the list of successful actions, or a list containing /// the error action. fn actions(self) -> Vec; } impl LogicResultExt for LogicResult { fn actions(self) -> Vec { self.unwrap_or_else(|err_action| vec![err_action]) } } fn map_to_vec(action: Option) -> Vec { action.map(|a| vec![a]).unwrap_or_default() } async fn refresh_ensured_jwt() -> Result<(String, Option), Action> { api::auth::refresh_jwt() .await .map(|new_jwt| (new_jwt.clone(), Some(Action::UpdateJwt(new_jwt)))) .map_err(|_| Action::Logout) } async fn ensure_jwt(dispatch: &WebUiDispatcher) -> Result<(String, Option), Action> { //TODO lots of clones here. can we avoid? use jsonwebtoken::errors::ErrorKind; let token = dispatch.state().jwt_token.as_deref().unwrap_or_default(); let validation: Result = dangerous_insecure_decode(token).map(|data| data.claims); //If valid, simply return token. If expired, attempt to refresh. //Otherwise, bubble error. let token_and_action = match validation { Ok(_) => (token.to_owned(), None), Err(e) if matches!(e.kind(), ErrorKind::ExpiredSignature) => refresh_ensured_jwt().await?, Err(_) => return Err(Action::Logout), //TODO carry error inside Logout? }; Ok(token_and_action) } pub(crate) async fn fetch_rooms(dispatch: &WebUiDispatcher) -> LogicResult { let (jwt, jwt_update) = ensure_jwt(dispatch) .await .map(|(token, update)| (token, map_to_vec(update)))?; let rooms: Vec = api::dicebot::rooms_for_user(&jwt, &dispatch.state().username) .await .map_err(|e| Action::ErrorMessage(e))? .into_iter() .map(|room| { Action::AddRoom(Room { room_id: room.room_id, display_name: room.display_name, }) }) .collect(); Ok(rooms.into_iter().chain(jwt_update).collect()) }