Begin creating logic layer in web UI.
This commit is contained in:
parent
9e0fd44448
commit
b9381eecf4
|
@ -3578,6 +3578,7 @@ dependencies = [
|
||||||
"graphql_client",
|
"graphql_client",
|
||||||
"graphql_client_web",
|
"graphql_client_web",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
|
"jsonwebtoken",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tenebrous-api",
|
"tenebrous-api",
|
||||||
|
|
|
@ -25,6 +25,7 @@ graphql_client_web = { git = "https://github.com/graphql-rust/graphql-client", b
|
||||||
serde = { version = "1.0.67", features = ["derive"] }
|
serde = { version = "1.0.67", features = ["derive"] }
|
||||||
serde_json = {version = "1.0" }
|
serde_json = {version = "1.0" }
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
|
jsonwebtoken = "7.2"
|
||||||
|
|
||||||
[dependencies.web-sys]
|
[dependencies.web-sys]
|
||||||
version = "0.3"
|
version = "0.3"
|
||||||
|
|
|
@ -83,7 +83,6 @@ pub async fn refresh_jwt() -> Result<String, UiError> {
|
||||||
request.headers().set("Content-Type", "application/json")?;
|
request.headers().set("Content-Type", "application/json")?;
|
||||||
request.headers().set("Accept", "application/json")?;
|
request.headers().set("Accept", "application/json")?;
|
||||||
|
|
||||||
//TODO don't unwrap the response. OR... change it so we have a standard response.
|
|
||||||
let response: LoginResponse = make_request(request).await?;
|
let response: LoginResponse = make_request(request).await?;
|
||||||
Ok(response.jwt_token)
|
Ok(response.jwt_token)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,9 @@ pub enum UiError {
|
||||||
|
|
||||||
#[error("(de)serialization error: {0}")]
|
#[error("(de)serialization error: {0}")]
|
||||||
SerdeError(#[from] serde_json::Error),
|
SerdeError(#[from] serde_json::Error),
|
||||||
|
|
||||||
|
#[error("JWT validation error: {0}")]
|
||||||
|
JwtError(#[from] jsonwebtoken::errors::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<wasm_bindgen::JsValue> for UiError {
|
impl From<wasm_bindgen::JsValue> for UiError {
|
||||||
|
|
|
@ -16,6 +16,7 @@ pub mod api;
|
||||||
pub mod components;
|
pub mod components;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod grpc;
|
pub mod grpc;
|
||||||
|
pub mod logic;
|
||||||
pub mod rooms;
|
pub mod rooms;
|
||||||
pub mod state;
|
pub mod state;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
use crate::{
|
||||||
|
api,
|
||||||
|
error::UiError,
|
||||||
|
state::{Action, Room, WebUiDispatcher},
|
||||||
|
};
|
||||||
|
use jsonwebtoken::{
|
||||||
|
dangerous_insecure_decode_with_validation as decode_without_verify, Validation,
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
struct Claims {
|
||||||
|
exp: usize,
|
||||||
|
sub: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_to_vec(action: Option<Action>) -> Vec<Action> {
|
||||||
|
action.map(|a| vec![a]).unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn ensure_jwt(dispatch: &WebUiDispatcher) -> Result<(String, Option<Action>), UiError> {
|
||||||
|
//TODO we should add a logout action and return it from here if there's an error when refreshing.
|
||||||
|
//TODO somehow have to handle actions on an error!
|
||||||
|
|
||||||
|
//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 =
|
||||||
|
decode_without_verify::<Claims>(token, &Validation::default()).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) => {
|
||||||
|
match api::auth::refresh_jwt().await {
|
||||||
|
Ok(new_jwt) => (new_jwt.clone(), Some(Action::UpdateJwt(new_jwt))),
|
||||||
|
Err(e) => return Err(e.into()), //TODO logout action
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => return Err(e.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(token_and_action)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn fetch_rooms(dispatch: &WebUiDispatcher) -> Result<Vec<Action>, UiError> {
|
||||||
|
let (jwt, jwt_update) = ensure_jwt(dispatch)
|
||||||
|
.await
|
||||||
|
.map(|(token, update)| (token, map_to_vec(update)))?;
|
||||||
|
|
||||||
|
//Use new JWT to list rooms from graphql.
|
||||||
|
//TODO get username from state.
|
||||||
|
let rooms: Vec<Action> = api::dicebot::rooms_for_user(&jwt, "@projectmoon:agnos.is")
|
||||||
|
.await?
|
||||||
|
.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())
|
||||||
|
}
|
Loading…
Reference in New Issue