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 anyhow::{anyhow, Result};
 | 
				
			||||||
use lldap_model::*;
 | 
					use lldap_model::*;
 | 
				
			||||||
use wasm_bindgen::JsCast;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
use yew::callback::Callback;
 | 
					use yew::callback::Callback;
 | 
				
			||||||
use yew::format::Json;
 | 
					use yew::format::Json;
 | 
				
			||||||
@ -30,19 +30,17 @@ impl HostService {
 | 
				
			|||||||
        let url = "/api/users";
 | 
					        let url = "/api/users";
 | 
				
			||||||
        let handler = move |response: Response<Result<String>>| {
 | 
					        let handler = move |response: Response<Result<String>>| {
 | 
				
			||||||
            let (meta, maybe_data) = response.into_parts();
 | 
					            let (meta, maybe_data) = response.into_parts();
 | 
				
			||||||
            match maybe_data {
 | 
					            let message = maybe_data
 | 
				
			||||||
                Ok(data) => {
 | 
					                .map_err(|e| anyhow!("Could not fetch: {}", e))
 | 
				
			||||||
 | 
					                .and_then(|data| {
 | 
				
			||||||
                    if meta.status.is_success() {
 | 
					                    if meta.status.is_success() {
 | 
				
			||||||
                        callback.emit(
 | 
					 | 
				
			||||||
                        serde_json::from_str(&data)
 | 
					                        serde_json::from_str(&data)
 | 
				
			||||||
                                .map_err(|e| anyhow!("Could not parse response: {}", e)),
 | 
					                            .map_err(|e| anyhow!("Could not parse response: {}", e))
 | 
				
			||||||
                        )
 | 
					 | 
				
			||||||
                    } else {
 | 
					                    } 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)
 | 
					        let request = Request::post(url)
 | 
				
			||||||
            .header("Content-Type", "application/json")
 | 
					            .header("Content-Type", "application/json")
 | 
				
			||||||
@ -57,43 +55,28 @@ impl HostService {
 | 
				
			|||||||
        let url = "/api/authorize";
 | 
					        let url = "/api/authorize";
 | 
				
			||||||
        let handler = move |response: Response<Result<String>>| {
 | 
					        let handler = move |response: Response<Result<String>>| {
 | 
				
			||||||
            let (meta, maybe_data) = response.into_parts();
 | 
					            let (meta, maybe_data) = response.into_parts();
 | 
				
			||||||
            match maybe_data {
 | 
					            let message = maybe_data
 | 
				
			||||||
                Ok(data) => {
 | 
					                .map_err(|e| anyhow!("Could not reach authentication server: {}", e))
 | 
				
			||||||
 | 
					                .and_then(|data| {
 | 
				
			||||||
                    if meta.status.is_success() {
 | 
					                    if meta.status.is_success() {
 | 
				
			||||||
                        match get_claims_from_jwt(&data) {
 | 
					                        get_claims_from_jwt(&data)
 | 
				
			||||||
                            Ok(jwt_claims) => {
 | 
					                            .map_err(|e| anyhow!("Could not parse response: {}", e))
 | 
				
			||||||
                                let document = web_sys::window()
 | 
					                            .and_then(|jwt_claims| {
 | 
				
			||||||
                                    .unwrap()
 | 
					                                set_cookie("user_id", &jwt_claims.user, &jwt_claims.exp)
 | 
				
			||||||
                                    .document()
 | 
					                                    .map(|_| jwt_claims.user.clone())
 | 
				
			||||||
                                    .unwrap()
 | 
					                                    .map_err(|e| anyhow!("Error clearing cookie: {}", e))
 | 
				
			||||||
                                    .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)))
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    } else if meta.status == 401 {
 | 
					                    } else if meta.status == 401 {
 | 
				
			||||||
                        callback.emit(Err(anyhow!("Invalid username or password")))
 | 
					                        Err(anyhow!("Invalid username or password"))
 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        callback.emit(Err(anyhow!(
 | 
					                        Err(anyhow!(
 | 
				
			||||||
                            "Could not authenticate: [{}]: {}",
 | 
					                            "Could not authenticate: [{}]: {}",
 | 
				
			||||||
                            meta.status,
 | 
					                            meta.status,
 | 
				
			||||||
                            data
 | 
					                            data
 | 
				
			||||||
                        )))
 | 
					                        ))
 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                Err(e) => {
 | 
					 | 
				
			||||||
                    callback.emit(Err(anyhow!("Could not reach authentication server: {}", e)))
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					            callback.emit(message)
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
        let request = Request::post(url)
 | 
					        let request = Request::post(url)
 | 
				
			||||||
            .header("Content-Type", "application/json")
 | 
					            .header("Content-Type", "application/json")
 | 
				
			||||||
 | 
				
			|||||||
@ -1,8 +1,9 @@
 | 
				
			|||||||
 | 
					use crate::cookies::get_cookie;
 | 
				
			||||||
use crate::login::LoginForm;
 | 
					use crate::login::LoginForm;
 | 
				
			||||||
 | 
					use crate::logout::LogoutButton;
 | 
				
			||||||
use crate::user_table::UserTable;
 | 
					use crate::user_table::UserTable;
 | 
				
			||||||
use anyhow::{anyhow, Result};
 | 
					 | 
				
			||||||
use wasm_bindgen::JsCast;
 | 
					 | 
				
			||||||
use yew::prelude::*;
 | 
					use yew::prelude::*;
 | 
				
			||||||
 | 
					use yew::services::ConsoleService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub struct App {
 | 
					pub struct App {
 | 
				
			||||||
    link: ComponentLink<Self>,
 | 
					    link: ComponentLink<Self>,
 | 
				
			||||||
@ -11,30 +12,7 @@ pub struct App {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
pub enum Msg {
 | 
					pub enum Msg {
 | 
				
			||||||
    Login(String),
 | 
					    Login(String),
 | 
				
			||||||
}
 | 
					    Logout,
 | 
				
			||||||
 | 
					 | 
				
			||||||
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")))
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl Component for App {
 | 
					impl Component for App {
 | 
				
			||||||
@ -44,7 +22,10 @@ impl Component for App {
 | 
				
			|||||||
    fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
 | 
					    fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
 | 
				
			||||||
        App {
 | 
					        App {
 | 
				
			||||||
            link: link.clone(),
 | 
					            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) => {
 | 
					            Msg::Login(user_name) => {
 | 
				
			||||||
                self.user_name = Some(user_name);
 | 
					                self.user_name = Some(user_name);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            Msg::Logout => {
 | 
				
			||||||
 | 
					                self.user_name = None;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        true
 | 
					        true
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -66,9 +50,14 @@ impl Component for App {
 | 
				
			|||||||
            <div>
 | 
					            <div>
 | 
				
			||||||
                <h1>{ "LLDAP" }</h1>
 | 
					                <h1>{ "LLDAP" }</h1>
 | 
				
			||||||
                {if self.user_name.is_some() {
 | 
					                {if self.user_name.is_some() {
 | 
				
			||||||
                    html! {<UserTable />}
 | 
					                    html! {
 | 
				
			||||||
 | 
					                      <div>
 | 
				
			||||||
 | 
					                        <LogoutButton on_logged_out=self.link.callback(|_| Msg::Logout) />
 | 
				
			||||||
 | 
					                        <UserTable />
 | 
				
			||||||
 | 
					                      </div>
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                } else {
 | 
					                } 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>
 | 
					            </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"]
 | 
					#![recursion_limit = "256"]
 | 
				
			||||||
mod api;
 | 
					mod api;
 | 
				
			||||||
mod app;
 | 
					mod app;
 | 
				
			||||||
 | 
					mod cookies;
 | 
				
			||||||
mod login;
 | 
					mod login;
 | 
				
			||||||
 | 
					mod logout;
 | 
				
			||||||
mod user_table;
 | 
					mod user_table;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use wasm_bindgen::prelude::{wasm_bindgen, JsValue};
 | 
					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