Web API, Web UI #86
|
@ -96,15 +96,20 @@ fn create_token<'a>(
|
||||||
Ok(token)
|
Ok(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct LoginResponse {
|
||||||
|
jwt_token: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[rocket::post("/login", data = "<request>")]
|
#[rocket::post("/login", data = "<request>")]
|
||||||
async fn login(
|
async fn login(
|
||||||
request: Json<LoginRequest<'_>>,
|
request: Json<LoginRequest<'_>>,
|
||||||
config: &State<Config>,
|
config: &State<Config>,
|
||||||
cookies: &CookieJar<'_>,
|
cookies: &CookieJar<'_>,
|
||||||
) -> Result<String, Custom<String>> {
|
) -> Result<Json<LoginResponse>, Custom<String>> {
|
||||||
let token = create_token(request.username, Duration::minutes(1), &config.jwt_secret)?;
|
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)?;
|
let refresh_token = create_token(request.username, Duration::weeks(1), &config.jwt_secret)?;
|
||||||
|
|
||||||
cookies.add_private(Cookie::new("refresh_token", refresh_token));
|
cookies.add_private(Cookie::new("refresh_token", refresh_token));
|
||||||
Ok(token)
|
Ok(Json(LoginResponse { jwt_token: token }))
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,12 +27,22 @@ wasm-bindgen = { version = "0.2" }
|
||||||
wasm-bindgen-futures = "0.4"
|
wasm-bindgen-futures = "0.4"
|
||||||
js-sys = "0.3"
|
js-sys = "0.3"
|
||||||
#grpc-web-client = "0.1"
|
#grpc-web-client = "0.1"
|
||||||
web-sys = "0.3"
|
|
||||||
graphql_client = { git = "https://github.com/graphql-rust/graphql-client", branch = "master" }
|
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" }
|
graphql_client_web = { git = "https://github.com/graphql-rust/graphql-client", branch = "master" }
|
||||||
serde = { version = "1.0.67", features = ["derive"] }
|
serde = { version = "1.0.67", features = ["derive"] }
|
||||||
thiserror = "1.0"
|
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.
|
# hopefully we can add grpc-web later instead of graphql.
|
||||||
# prost = { version = "0.7.0", default-features = false }
|
# prost = { version = "0.7.0", default-features = false }
|
||||||
# tonic = { git = "https://github.com/hyperium/tonic", branch = "master", default-features = false, features = ["codegen", "prost"] }
|
# tonic = { git = "https://github.com/hyperium/tonic", branch = "master", default-features = false, features = ["codegen", "prost"] }
|
||||||
|
|
|
@ -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<String, UiError> {
|
||||||
|
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::<Response>());
|
||||||
|
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)
|
||||||
|
}
|
|
@ -2,6 +2,9 @@ use graphql_client_web::Response;
|
||||||
|
|
||||||
use crate::error::UiError;
|
use crate::error::UiError;
|
||||||
|
|
||||||
|
pub mod auth;
|
||||||
|
pub mod dicebot;
|
||||||
|
|
||||||
/// Extensions to the GraphQL web response type to add convenience,
|
/// Extensions to the GraphQL web response type to add convenience,
|
||||||
/// particularly when working with errors.
|
/// particularly when working with errors.
|
||||||
trait ResponseExt<T> {
|
trait ResponseExt<T> {
|
||||||
|
@ -32,5 +35,3 @@ impl<T> ResponseExt<T> for Response<T> {
|
||||||
Ok(data)
|
Ok(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod dicebot;
|
|
||||||
|
|
|
@ -9,4 +9,17 @@ pub enum UiError {
|
||||||
/// General API error, collecting errors from graphql server.
|
/// General API error, collecting errors from graphql server.
|
||||||
#[error("error: {0}")]
|
#[error("error: {0}")]
|
||||||
ApiError(String),
|
ApiError(String),
|
||||||
|
|
||||||
|
#[error("error: {0}")]
|
||||||
|
JsError(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<wasm_bindgen::JsValue> for UiError {
|
||||||
|
fn from(js_error: wasm_bindgen::JsValue) -> UiError {
|
||||||
|
UiError::JsError(
|
||||||
|
js_error
|
||||||
|
.as_string()
|
||||||
|
.unwrap_or("unknown JS error".to_string()),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,12 @@ async fn load_rooms(dispatch: &WebUiDispatcher) -> Result<(), UiError> {
|
||||||
Ok(())
|
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 {
|
impl Component for YewduxRoomList {
|
||||||
type Message = ();
|
type Message = ();
|
||||||
type Properties = WebUiDispatcher;
|
type Properties = WebUiDispatcher;
|
||||||
|
@ -72,9 +78,19 @@ impl Component for YewduxRoomList {
|
||||||
fn view(&self) -> Html {
|
fn view(&self) -> Html {
|
||||||
let the_future = self.link.callback(move |_| {});
|
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! {
|
html! {
|
||||||
<div>
|
<div>
|
||||||
<button onclick=the_future>{ "Add Room" }</button>
|
<button onclick=the_future>{ "Add Room" }</button>
|
||||||
|
<button onclick=jwt_update>{ "Fetch JWT" }</button>
|
||||||
|
<div> { "Current JWT: " } { self.dispatch.state().jwt_token.as_ref().unwrap_or(&"[not set]".to_string()) }</div>
|
||||||
<ul>
|
<ul>
|
||||||
{
|
{
|
||||||
for self.dispatch.state().rooms.iter().map(|room| {
|
for self.dispatch.state().rooms.iter().map(|room| {
|
||||||
|
|
|
@ -8,10 +8,12 @@ pub(crate) struct Room {
|
||||||
|
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
pub(crate) struct WebUiState {
|
pub(crate) struct WebUiState {
|
||||||
|
pub jwt_token: Option<String>,
|
||||||
pub rooms: Vec<Room>,
|
pub rooms: Vec<Room>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) enum Action {
|
pub(crate) enum Action {
|
||||||
|
UpdateJwt(String),
|
||||||
AddRoom(Room),
|
AddRoom(Room),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,17 +21,20 @@ impl Reducer for WebUiState {
|
||||||
type Action = Action;
|
type Action = Action;
|
||||||
|
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Self { rooms: vec![] }
|
Self {
|
||||||
|
jwt_token: None,
|
||||||
|
rooms: vec![],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reduce(&mut self, action: Self::Action) -> bool {
|
fn reduce(&mut self, action: Self::Action) -> bool {
|
||||||
match action {
|
match action {
|
||||||
Action::AddRoom(room) => {
|
Action::UpdateJwt(jwt_token) => self.jwt_token = Some(jwt_token),
|
||||||
self.rooms.push(room.clone());
|
Action::AddRoom(room) => self.rooms.push(room.clone()),
|
||||||
|
};
|
||||||
|
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) type WebUiDispatcher = DispatchProps<ReducerStore<WebUiState>>;
|
pub(crate) type WebUiDispatcher = DispatchProps<ReducerStore<WebUiState>>;
|
||||||
|
|
Loading…
Reference in New Issue