mirror of
				https://github.com/nitnelave/lldap.git
				synced 2023-04-12 14:25:13 +00:00 
			
		
		
		
	Implement logout
Also introduce a library to handle cookies
This commit is contained in:
		
							parent
							
								
									d57cd1230c
								
							
						
					
					
						commit
						4d9f554fe6
					
				@ -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<Result<String>>| {
 | 
			
		||||
            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<Result<String>>| {
 | 
			
		||||
            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::<web_sys::HtmlDocument>()
 | 
			
		||||
                                    .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")
 | 
			
		||||
 | 
			
		||||
@ -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<Self>,
 | 
			
		||||
@ -11,30 +12,7 @@ pub struct App {
 | 
			
		||||
 | 
			
		||||
pub enum Msg {
 | 
			
		||||
    Login(String),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn extract_user_id_cookie() -> Result<String> {
 | 
			
		||||
    let document = web_sys::window()
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .document()
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .dyn_into::<web_sys::HtmlDocument>()
 | 
			
		||||
        .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>) -> 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 {
 | 
			
		||||
            <div>
 | 
			
		||||
                <h1>{ "LLDAP" }</h1>
 | 
			
		||||
                {if self.user_name.is_some() {
 | 
			
		||||
                    html! {<UserTable />}
 | 
			
		||||
                    html! {
 | 
			
		||||
                      <div>
 | 
			
		||||
                        <LogoutButton on_logged_out=self.link.callback(|_| Msg::Logout) />
 | 
			
		||||
                        <UserTable />
 | 
			
		||||
                      </div>
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    html! {<LoginForm on_logged_in=self.link.callback(|u| { Msg::Login(u) })/>}
 | 
			
		||||
                    html! {<LoginForm on_logged_in=self.link.callback(|u| Msg::Login(u))/>}
 | 
			
		||||
                }}
 | 
			
		||||
            </div>
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										52
									
								
								app/src/cookies.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								app/src/cookies.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,52 @@
 | 
			
		||||
use anyhow::{anyhow, Result};
 | 
			
		||||
use chrono::prelude::*;
 | 
			
		||||
use wasm_bindgen::JsCast;
 | 
			
		||||
use web_sys::HtmlDocument;
 | 
			
		||||
 | 
			
		||||
fn get_document() -> Result<HtmlDocument> {
 | 
			
		||||
    web_sys::window()
 | 
			
		||||
        .map(|w| w.document())
 | 
			
		||||
        .flatten()
 | 
			
		||||
        .ok_or(anyhow!("Could not get window document"))
 | 
			
		||||
        .and_then(|d| {
 | 
			
		||||
            d.dyn_into::<web_sys::HtmlDocument>()
 | 
			
		||||
                .map_err(|_| anyhow!("Document is not an HTMLDocument"))
 | 
			
		||||
        })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn set_cookie(cookie_name: &str, value: &str, expiration: &DateTime<Utc>) -> 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::<web_sys::HtmlDocument>()
 | 
			
		||||
                .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<Option<String>> {
 | 
			
		||||
    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(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -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};
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										54
									
								
								app/src/logout.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								app/src/logout.rs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,54 @@
 | 
			
		||||
use crate::cookies::delete_cookie;
 | 
			
		||||
use yew::prelude::*;
 | 
			
		||||
use yew::services::ConsoleService;
 | 
			
		||||
 | 
			
		||||
pub struct LogoutButton {
 | 
			
		||||
    link: ComponentLink<Self>,
 | 
			
		||||
    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>) -> 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! {
 | 
			
		||||
            <button onclick=self.link.callback(|_| { Msg::Logout })>{"Logout"}</button>
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user