From eeacf01cbe38eb90fb08bbbbf68e068129cf5388 Mon Sep 17 00:00:00 2001 From: projectmoon Date: Fri, 11 Jun 2021 13:46:05 +0000 Subject: [PATCH] Add login API call for web UI --- api/src/auth.rs | 9 +++++-- web-ui/crate/Cargo.toml | 12 +++++++++- web-ui/crate/src/api/auth.rs | 46 ++++++++++++++++++++++++++++++++++++ web-ui/crate/src/api/mod.rs | 5 ++-- web-ui/crate/src/error.rs | 13 ++++++++++ web-ui/crate/src/rooms.rs | 16 +++++++++++++ web-ui/crate/src/state.rs | 17 ++++++++----- 7 files changed, 107 insertions(+), 11 deletions(-) create mode 100644 web-ui/crate/src/api/auth.rs diff --git a/api/src/auth.rs b/api/src/auth.rs index c29c0d1..3bd1df8 100644 --- a/api/src/auth.rs +++ b/api/src/auth.rs @@ -96,15 +96,20 @@ fn create_token<'a>( Ok(token) } +#[derive(Serialize)] +struct LoginResponse { + jwt_token: String, +} + #[rocket::post("/login", data = "")] async fn login( request: Json>, config: &State, cookies: &CookieJar<'_>, -) -> Result> { +) -> Result, Custom> { let token = create_token(request.username, Duration::minutes(1), &config.jwt_secret)?; let refresh_token = create_token(request.username, Duration::weeks(1), &config.jwt_secret)?; cookies.add_private(Cookie::new("refresh_token", refresh_token)); - Ok(token) + Ok(Json(LoginResponse { jwt_token: token })) } diff --git a/web-ui/crate/Cargo.toml b/web-ui/crate/Cargo.toml index 390b2a2..73d07b0 100644 --- a/web-ui/crate/Cargo.toml +++ b/web-ui/crate/Cargo.toml @@ -27,12 +27,22 @@ wasm-bindgen = { version = "0.2" } wasm-bindgen-futures = "0.4" js-sys = "0.3" #grpc-web-client = "0.1" -web-sys = "0.3" graphql_client = { git = "https://github.com/graphql-rust/graphql-client", branch = "master" } graphql_client_web = { git = "https://github.com/graphql-rust/graphql-client", branch = "master" } serde = { version = "1.0.67", features = ["derive"] } thiserror = "1.0" +[dependencies.web-sys] +version = "0.3" +features = [ + 'Headers', + 'Request', + 'RequestInit', + 'RequestMode', + 'Response', + 'Window', +] + # hopefully we can add grpc-web later instead of graphql. # prost = { version = "0.7.0", default-features = false } # tonic = { git = "https://github.com/hyperium/tonic", branch = "master", default-features = false, features = ["codegen", "prost"] } diff --git a/web-ui/crate/src/api/auth.rs b/web-ui/crate/src/api/auth.rs new file mode 100644 index 0000000..db0c319 --- /dev/null +++ b/web-ui/crate/src/api/auth.rs @@ -0,0 +1,46 @@ +use crate::error::UiError; +use serde::{Deserialize, Serialize}; +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; +use wasm_bindgen_futures::JsFuture; +use web_sys::{console, Request, RequestInit, RequestMode, Response}; + +#[derive(Debug, Serialize, Deserialize)] +struct LoginResponse { + jwt_token: String, +} + +pub async fn fetch_jwt() -> Result { + let mut opts = RequestInit::new(); + opts.method("POST"); + opts.mode(RequestMode::Cors); + opts.body(Some(&JsValue::from_str( + r#" + { "username": "@projectmoon:agnos.is", "password": "lolol" } + "#, + ))); + + let url = format!("http://localhost:10000/login"); + + let request = Request::new_with_str_and_init(&url, &opts)?; + request.headers().set("Content-Type", "application/json")?; + request.headers().set("Accept", "application/json")?; + + let window = web_sys::window().unwrap(); + let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?; + + // `resp_value` is a `Response` object. + assert!(resp_value.is_instance_of::()); + let resp: Response = resp_value.dyn_into().unwrap(); + + // Convert this other `Promise` into a rust `Future`. + let json = JsFuture::from(resp.json()?).await?; + + console::log_1(&json); + + // Use serde to parse the JSON into a struct. + let login_response: LoginResponse = json.into_serde().unwrap(); + + // Send the `Branch` struct back to JS as an `Object`. + Ok(login_response.jwt_token) +} diff --git a/web-ui/crate/src/api/mod.rs b/web-ui/crate/src/api/mod.rs index a73ca1a..0d0ccad 100644 --- a/web-ui/crate/src/api/mod.rs +++ b/web-ui/crate/src/api/mod.rs @@ -2,6 +2,9 @@ use graphql_client_web::Response; use crate::error::UiError; +pub mod auth; +pub mod dicebot; + /// Extensions to the GraphQL web response type to add convenience, /// particularly when working with errors. trait ResponseExt { @@ -32,5 +35,3 @@ impl ResponseExt for Response { Ok(data) } } - -pub mod dicebot; diff --git a/web-ui/crate/src/error.rs b/web-ui/crate/src/error.rs index 1d52213..aa01416 100644 --- a/web-ui/crate/src/error.rs +++ b/web-ui/crate/src/error.rs @@ -9,4 +9,17 @@ pub enum UiError { /// General API error, collecting errors from graphql server. #[error("error: {0}")] ApiError(String), + + #[error("error: {0}")] + JsError(String), +} + +impl From for UiError { + fn from(js_error: wasm_bindgen::JsValue) -> UiError { + UiError::JsError( + js_error + .as_string() + .unwrap_or("unknown JS error".to_string()), + ) + } } diff --git a/web-ui/crate/src/rooms.rs b/web-ui/crate/src/rooms.rs index 3d501e3..58b96a1 100644 --- a/web-ui/crate/src/rooms.rs +++ b/web-ui/crate/src/rooms.rs @@ -38,6 +38,12 @@ async fn load_rooms(dispatch: &WebUiDispatcher) -> Result<(), UiError> { Ok(()) } +async fn do_jwt_stuff(dispatch: &WebUiDispatcher) -> Result<(), UiError> { + let jwt = api::auth::fetch_jwt().await?; + dispatch.send(Action::UpdateJwt(jwt)); + Ok(()) +} + impl Component for YewduxRoomList { type Message = (); type Properties = WebUiDispatcher; @@ -72,9 +78,19 @@ impl Component for YewduxRoomList { fn view(&self) -> Html { let the_future = self.link.callback(move |_| {}); + let dispatch = Arc::new(self.dispatch.clone()); + let jwt_update = self.link.callback(move |_| { + let dispatch = dispatch.clone(); + spawn_local(async move { + do_jwt_stuff(&*dispatch).await; + }); + }); + html! {
+ +
{ "Current JWT: " } { self.dispatch.state().jwt_token.as_ref().unwrap_or(&"[not set]".to_string()) }
    { for self.dispatch.state().rooms.iter().map(|room| { diff --git a/web-ui/crate/src/state.rs b/web-ui/crate/src/state.rs index eb83fba..12d0b14 100644 --- a/web-ui/crate/src/state.rs +++ b/web-ui/crate/src/state.rs @@ -8,10 +8,12 @@ pub(crate) struct Room { #[derive(Default, Clone)] pub(crate) struct WebUiState { + pub jwt_token: Option, pub rooms: Vec, } pub(crate) enum Action { + UpdateJwt(String), AddRoom(Room), } @@ -19,16 +21,19 @@ impl Reducer for WebUiState { type Action = Action; fn new() -> Self { - Self { rooms: vec![] } + Self { + jwt_token: None, + rooms: vec![], + } } fn reduce(&mut self, action: Self::Action) -> bool { match action { - Action::AddRoom(room) => { - self.rooms.push(room.clone()); - true - } - } + Action::UpdateJwt(jwt_token) => self.jwt_token = Some(jwt_token), + Action::AddRoom(room) => self.rooms.push(room.clone()), + }; + + true } }