diff --git a/app/src/api.rs b/app/src/api.rs index 9ba6973..8856568 100644 --- a/app/src/api.rs +++ b/app/src/api.rs @@ -1,6 +1,6 @@ +use crate::cookies::set_cookie; use anyhow::{anyhow, Result}; use lldap_model::*; -use wasm_bindgen::JsCast; use yew::callback::Callback; use yew::format::Json; @@ -30,19 +30,17 @@ impl HostService { let url = "/api/users"; let handler = move |response: Response>| { let (meta, maybe_data) = response.into_parts(); - match maybe_data { - Ok(data) => { + let message = maybe_data + .map_err(|e| anyhow!("Could not fetch: {}", e)) + .and_then(|data| { if meta.status.is_success() { - callback.emit( - serde_json::from_str(&data) - .map_err(|e| anyhow!("Could not parse response: {}", e)), - ) + serde_json::from_str(&data) + .map_err(|e| anyhow!("Could not parse response: {}", e)) } else { - callback.emit(Err(anyhow!("[{}]: {}", meta.status, data))) + Err(anyhow!("[{}]: {}", meta.status, data)) } - } - Err(e) => callback.emit(Err(anyhow!("Could not fetch: {}", e))), - } + }); + callback.emit(message) }; let request = Request::post(url) .header("Content-Type", "application/json") @@ -57,43 +55,28 @@ impl HostService { let url = "/api/authorize"; let handler = move |response: Response>| { let (meta, maybe_data) = response.into_parts(); - match maybe_data { - Ok(data) => { + let message = maybe_data + .map_err(|e| anyhow!("Could not reach authentication server: {}", e)) + .and_then(|data| { if meta.status.is_success() { - match get_claims_from_jwt(&data) { - Ok(jwt_claims) => { - let document = web_sys::window() - .unwrap() - .document() - .unwrap() - .dyn_into::() - .unwrap(); - document - .set_cookie(&format!( - "user_id={}; expires={}", - &jwt_claims.user, &jwt_claims.exp - )) - .unwrap(); - callback.emit(Ok(jwt_claims.user.clone())) - } - Err(e) => { - callback.emit(Err(anyhow!("Could not parse response: {}", e))) - } - } + get_claims_from_jwt(&data) + .map_err(|e| anyhow!("Could not parse response: {}", e)) + .and_then(|jwt_claims| { + set_cookie("user_id", &jwt_claims.user, &jwt_claims.exp) + .map(|_| jwt_claims.user.clone()) + .map_err(|e| anyhow!("Error clearing cookie: {}", e)) + }) } else if meta.status == 401 { - callback.emit(Err(anyhow!("Invalid username or password"))) + Err(anyhow!("Invalid username or password")) } else { - callback.emit(Err(anyhow!( + Err(anyhow!( "Could not authenticate: [{}]: {}", meta.status, data - ))) + )) } - } - Err(e) => { - callback.emit(Err(anyhow!("Could not reach authentication server: {}", e))) - } - } + }); + callback.emit(message) }; let request = Request::post(url) .header("Content-Type", "application/json") diff --git a/app/src/app.rs b/app/src/app.rs index ba9015d..12722f1 100644 --- a/app/src/app.rs +++ b/app/src/app.rs @@ -1,8 +1,9 @@ +use crate::cookies::get_cookie; use crate::login::LoginForm; +use crate::logout::LogoutButton; use crate::user_table::UserTable; -use anyhow::{anyhow, Result}; -use wasm_bindgen::JsCast; use yew::prelude::*; +use yew::services::ConsoleService; pub struct App { link: ComponentLink, @@ -11,30 +12,7 @@ pub struct App { pub enum Msg { Login(String), -} - -fn extract_user_id_cookie() -> Result { - let document = web_sys::window() - .unwrap() - .document() - .unwrap() - .dyn_into::() - .unwrap(); - let cookies = document.cookie().unwrap(); - yew::services::ConsoleService::info(&cookies); - cookies - .split(";") - .filter_map(|c| c.split_once('=')) - .map(|(name, value)| { - if name == "user_id" { - Ok(value.into()) - } else { - Err(anyhow!("Wrong cookie")) - } - }) - .filter(Result::is_ok) - .next() - .unwrap_or(Err(anyhow!("User ID cookie not found in response"))) + Logout, } impl Component for App { @@ -44,7 +22,10 @@ impl Component for App { fn create(_: Self::Properties, link: ComponentLink) -> Self { App { link: link.clone(), - user_name: extract_user_id_cookie().ok(), + user_name: get_cookie("user_id").unwrap_or_else(|e| { + ConsoleService::error(&e.to_string()); + None + }), } } @@ -53,6 +34,9 @@ impl Component for App { Msg::Login(user_name) => { self.user_name = Some(user_name); } + Msg::Logout => { + self.user_name = None; + } } true } @@ -66,9 +50,14 @@ impl Component for App {

{ "LLDAP" }

{if self.user_name.is_some() { - html! {} + html! { +
+ + +
+ } } else { - html! {} + html! {} }}
} diff --git a/app/src/cookies.rs b/app/src/cookies.rs new file mode 100644 index 0000000..1cd9e83 --- /dev/null +++ b/app/src/cookies.rs @@ -0,0 +1,52 @@ +use anyhow::{anyhow, Result}; +use chrono::prelude::*; +use wasm_bindgen::JsCast; +use web_sys::HtmlDocument; + +fn get_document() -> Result { + web_sys::window() + .map(|w| w.document()) + .flatten() + .ok_or(anyhow!("Could not get window document")) + .and_then(|d| { + d.dyn_into::() + .map_err(|_| anyhow!("Document is not an HTMLDocument")) + }) +} + +pub fn set_cookie(cookie_name: &str, value: &str, expiration: &DateTime) -> Result<()> { + let doc = web_sys::window() + .map(|w| w.document()) + .flatten() + .ok_or(anyhow!("Could not get window document")) + .and_then(|d| { + d.dyn_into::() + .map_err(|_| anyhow!("Document is not an HTMLDocument")) + })?; + doc.set_cookie(&format!("{}={};expires={}", cookie_name, value, expiration)) + .map_err(|_| anyhow!("Could not set cookie")) +} + +pub fn get_cookie(cookie_name: &str) -> Result> { + let cookies = get_document()? + .cookie() + .map_err(|_| anyhow!("Could not access cookies"))?; + Ok(cookies + .split(";") + .filter_map(|c| c.split_once('=')) + .find_map(|(name, value)| { + if name == cookie_name { + Some(value.to_string()) + } else { + None + } + })) +} + +pub fn delete_cookie(cookie_name: &str) -> Result<()> { + if let Some(_) = get_cookie(cookie_name)? { + set_cookie(cookie_name, "", &Utc.ymd(1970, 1, 1).and_hms(0, 0, 0)) + } else { + Ok(()) + } +} diff --git a/app/src/lib.rs b/app/src/lib.rs index f883baa..a3d8d5f 100644 --- a/app/src/lib.rs +++ b/app/src/lib.rs @@ -1,7 +1,9 @@ #![recursion_limit = "256"] mod api; mod app; +mod cookies; mod login; +mod logout; mod user_table; use wasm_bindgen::prelude::{wasm_bindgen, JsValue}; diff --git a/app/src/logout.rs b/app/src/logout.rs new file mode 100644 index 0000000..a47f55f --- /dev/null +++ b/app/src/logout.rs @@ -0,0 +1,54 @@ +use crate::cookies::delete_cookie; +use yew::prelude::*; +use yew::services::ConsoleService; + +pub struct LogoutButton { + link: ComponentLink, + on_logged_out: Callback<()>, +} + +#[derive(Clone, PartialEq, Properties)] +pub struct Props { + pub on_logged_out: Callback<()>, +} + +pub enum Msg { + Logout, +} + +impl Component for LogoutButton { + type Message = Msg; + type Properties = Props; + + fn create(props: Self::Properties, link: ComponentLink) -> Self { + LogoutButton { + link: link.clone(), + on_logged_out: props.on_logged_out, + } + } + + fn update(&mut self, msg: Self::Message) -> ShouldRender { + match msg { + Msg::Logout => match delete_cookie("user_id") { + Err(e) => { + ConsoleService::error(&e.to_string()); + false + } + Ok(()) => { + self.on_logged_out.emit(()); + true + } + }, + } + } + + fn change(&mut self, _: Self::Properties) -> ShouldRender { + false + } + + fn view(&self) -> Html { + html! { + + } + } +}