From 5acf61c4f830ee4a4f04b35ef8590c4a1388ba8f Mon Sep 17 00:00:00 2001 From: Franklin Date: Wed, 26 Apr 2023 08:23:20 -0400 Subject: [PATCH] Added auth all across admin --- src/api/backend/mod.rs | 53 ++++++++++++++++++++++--------- src/pages/admin/agents.rs | 6 +++- src/pages/admin/contacts.rs | 5 ++- src/pages/admin/edit.rs | 5 +-- src/pages/admin/fields/agent.rs | 3 +- src/pages/admin/fields/project.rs | 3 +- src/pages/admin/fields/unit.rs | 3 +- src/pages/admin/login.rs | 30 +++++++++++++---- src/pages/admin/projects.rs | 8 +++-- src/pages/admin/start.rs | 5 ++- src/pages/admin/units.rs | 5 ++- src/utils/admin_panel.rs | 26 +++++++++++++-- src/utils/macros.rs | 35 ++++++++++++++++++++ src/utils/mod.rs | 1 + src/utils/storage.rs | 21 ++++++++++-- 15 files changed, 173 insertions(+), 36 deletions(-) create mode 100644 src/utils/macros.rs diff --git a/src/api/backend/mod.rs b/src/api/backend/mod.rs index 62a50b0..66de62a 100644 --- a/src/api/backend/mod.rs +++ b/src/api/backend/mod.rs @@ -16,16 +16,26 @@ use jl_types::{ project::{NewProjectPayload, UpdateProjectPayload}, unit::{UpdateUnitPayload, NewUnitPayload}, }, - project_card::ProjectCardDto, + project_card::ProjectCardDto, auth::AuthDto, }, }; use reqwest::Method; use uuid::Uuid; +use crate::utils::admin_panel; + use super::base::{perform_multipart_request_without_client, perform_request_without_client}; const BASE_URL: &str = "http://localhost:8095/"; +pub fn get_auth_header() -> (String, String) { + let token = admin_panel::get_admin_token_from_storage(); + match token { + Some(token) => (String::from("auth_token"), token), + None => (String::from("noauth"), String::new()), + } +} + pub async fn get_all_cities() -> Result, err::Error> { perform_request_without_client::>( BASE_URL.into(), @@ -145,7 +155,7 @@ pub async fn get_all_page_visits_count() -> Result { format!("admin/visits/count"), None, 200, - Vec::new(), + Vec::from([get_auth_header()]), None, ) .await @@ -158,7 +168,7 @@ pub async fn get_all_contacts_count() -> Result { format!("admin/contacts/count"), None, 200, - Vec::new(), + Vec::from([get_auth_header()]), None, ) .await @@ -171,7 +181,7 @@ pub async fn get_all_contacts() -> Result, err::Error> { format!("admin/contacts"), None, 200, - Vec::new(), + Vec::from([get_auth_header()]), None, ) .await @@ -213,7 +223,7 @@ pub async fn create_new_agent(agent: NewAgentPayload) -> Result Result { format!("admin/unit"), Some(unit), 200, - Vec::new(), + Vec::from([get_auth_header()]), None, ) .await @@ -239,7 +249,7 @@ pub async fn create_new_project(project: NewProjectPayload) -> Result Result Result Result Result { format!("admin/unit"), Some(unit), 200, - Vec::new(), + Vec::from([get_auth_header()]), None, ) .await @@ -303,7 +313,7 @@ pub async fn delete_project(project_id: &Uuid) -> Result<(), err::Error> { format!("admin/project/{project_id}"), None, 200, - Vec::new(), + Vec::from([get_auth_header()]), None, ) .await @@ -316,7 +326,7 @@ pub async fn delete_agent(agent_id: &Uuid) -> Result<(), err::Error> { format!("admin/agent/{agent_id}"), None, 200, - Vec::new(), + Vec::from([get_auth_header()]), None, ) .await @@ -329,7 +339,7 @@ pub async fn delete_unit(unit_id: &Uuid) -> Result<(), err::Error> { format!("admin/unit/{unit_id}"), None, 200, - Vec::new(), + Vec::from([get_auth_header()]), None, ) .await @@ -342,8 +352,21 @@ pub async fn upload_image(item: Item, body: Vec) -> Result Result { + perform_request_without_client( + BASE_URL.into(), + Method::POST, + format!("admin/auth"), + Some(auth_dto), + 200, + Vec::new(), + None, + ) + .await +} \ No newline at end of file diff --git a/src/pages/admin/agents.rs b/src/pages/admin/agents.rs index eb8876d..7f360ea 100644 --- a/src/pages/admin/agents.rs +++ b/src/pages/admin/agents.rs @@ -1,15 +1,19 @@ use yew::prelude::*; +use yew_router::prelude::use_navigator; use crate::{ api::backend::get_all_agents, components::{ admin_agent::AdminAgent, admin_nav_bar::AdminNavigationBar, new_widget::NewThingWidget, }, - pages::admin::edit::EditItem, + pages::admin::edit::EditItem, auth_present, }; #[function_component(AdminAgents)] pub fn admin_agents() -> Html { + let navigator = use_navigator().unwrap(); + auth_present!(navigator); + let agents = use_state(|| Vec::new()); use_state(|| { diff --git a/src/pages/admin/contacts.rs b/src/pages/admin/contacts.rs index 6684832..700d4d4 100644 --- a/src/pages/admin/contacts.rs +++ b/src/pages/admin/contacts.rs @@ -1,9 +1,12 @@ use yew::prelude::*; +use yew_router::prelude::use_navigator; -use crate::{api::backend::get_all_contacts, components::admin_nav_bar::AdminNavigationBar}; +use crate::{api::backend::get_all_contacts, components::admin_nav_bar::AdminNavigationBar, auth_present}; #[function_component(AdminContacts)] pub fn admin_contacts() -> Html { + let navigator = use_navigator().unwrap(); + auth_present!(navigator); let contacts = use_state(|| Vec::new()); use_state(|| { diff --git a/src/pages/admin/edit.rs b/src/pages/admin/edit.rs index 293c342..8349d8a 100644 --- a/src/pages/admin/edit.rs +++ b/src/pages/admin/edit.rs @@ -7,13 +7,14 @@ use yew_router::prelude::use_navigator; use crate::{ api::backend::{get_agent_with_id, get_project_listing}, components::admin_nav_bar::AdminNavigationBar, - pages::admin::fields::{agent::AgentFields, project::ProjectFields, unit::UnitFields}, + pages::admin::fields::{agent::AgentFields, project::ProjectFields, unit::UnitFields}, auth_present, }; /// All of the editing actions of the admin panel will lead to here. This should take an id of anything. A unit, a project, an agent. And its corresponding ID. #[function_component(AdminEditPage)] pub fn edit_page(props: &AdminEditPageProps) -> Html { - let _navigator = use_navigator().unwrap(); + let navigator = use_navigator().unwrap(); + auth_present!(navigator); let listing = use_state(|| None); let agent = use_state(|| None); use_state(|| { diff --git a/src/pages/admin/fields/agent.rs b/src/pages/admin/fields/agent.rs index 917ce3c..1597b13 100644 --- a/src/pages/admin/fields/agent.rs +++ b/src/pages/admin/fields/agent.rs @@ -11,7 +11,7 @@ use crate::{ dropdown::DropDown, single_media_picker::SingleMediaPicker, textfield::TextField, }, pages::admin::edit::EditType, - routes::main_router::Route, + routes::main_router::Route, auth_present, }; #[derive(Properties, PartialEq, Clone)] @@ -23,6 +23,7 @@ pub struct AgentFieldsProps { #[function_component(AgentFields)] pub fn agent_fields(props: &AgentFieldsProps) -> Html { let navigator = use_navigator().unwrap(); + auth_present!(navigator); let user_typed = use_state(|| false); let agent_opt = props.agent.clone(); diff --git a/src/pages/admin/fields/project.rs b/src/pages/admin/fields/project.rs index 3370843..577dc0c 100644 --- a/src/pages/admin/fields/project.rs +++ b/src/pages/admin/fields/project.rs @@ -32,7 +32,7 @@ use crate::{ edit::{EditItem, EditType}, units::AdminUnits, }, - routes::main_router::Route, + routes::main_router::Route, auth_present, }; #[derive(Properties, PartialEq, Clone)] @@ -44,6 +44,7 @@ pub struct ProjectFieldsProps { #[function_component(ProjectFields)] pub fn generate_fields_for_project(props: &ProjectFieldsProps) -> Html { let navigator = use_navigator().unwrap(); + auth_present!(navigator); let user_typed = use_state(|| false); let listing_opt = props.listing.clone(); diff --git a/src/pages/admin/fields/unit.rs b/src/pages/admin/fields/unit.rs index 550cbce..cd394ae 100644 --- a/src/pages/admin/fields/unit.rs +++ b/src/pages/admin/fields/unit.rs @@ -5,7 +5,7 @@ use yew_router::prelude::use_navigator; use crate::{ components::{dropdown::DropDown, number_textfield::NumberTextField, media_picker::MediaPicker, textfield::{TextField, TextFieldType}}, - pages::admin::edit::EditType, api::backend::{get_unit_with_id, create_new_unit, update_unit}, routes::main_router::Route, + pages::admin::edit::EditType, api::backend::{get_unit_with_id, create_new_unit, update_unit}, routes::main_router::Route, auth_present, }; #[derive(Properties, PartialEq, Clone)] @@ -38,6 +38,7 @@ pub fn unit_fields(props: &UnitFieldsProps) -> Html { let navigator = use_navigator().unwrap(); + auth_present!(navigator); let user_typed = use_state(|| false); let edit_type = props.edittype.clone(); let project_id = props.projectid.clone(); diff --git a/src/pages/admin/login.rs b/src/pages/admin/login.rs index b32a9cb..ec2830b 100644 --- a/src/pages/admin/login.rs +++ b/src/pages/admin/login.rs @@ -1,15 +1,23 @@ +use jl_types::dto::auth::AuthDto; use yew::prelude::*; use yew_router::prelude::use_navigator; -use crate::{routes::main_router::Route, utils::input::get_value_from_input_event}; +use crate::{routes::main_router::Route, utils::{input::get_value_from_input_event, admin_panel::{self, save_admin_token}}, api::backend::authenticate}; #[function_component(AdminLoginPage)] pub fn login_page() -> Html { - // TODO: If logged in go to start let navigator = use_navigator().unwrap(); let error = use_state(|| false); let username = use_state(|| String::new()); let password = use_state(|| String::new()); + let saved_token = use_state(|| admin_panel::get_admin_token_from_storage()); + + match (*saved_token).clone() { + Some(_) => { + navigator.push(&Route::AdminStart); + }, + None => {} + }; let on_username_input_changed = { let username = username.clone(); @@ -25,14 +33,24 @@ pub fn login_page() -> Html { }; let onclick = { let error = error.clone(); - + let username = username.clone(); + let password = password.clone(); Callback::from(move |_| { let error = error.clone(); let navigator = navigator.clone(); + let username = username.clone(); + let password = password.clone(); wasm_bindgen_futures::spawn_local(async move { - error.set(false); - // Call backend login - navigator.push(&Route::AdminStart); + match authenticate(AuthDto { email: (*username).clone(), password: (*password).clone() }).await { + Ok(token) => { + save_admin_token(token); + navigator.push(&Route::AdminStart); + }, + Err(error_message) => { + log::error!("Incorrect user/pass...: {error_message}"); + error.set(true); + } + }; }); }) }; diff --git a/src/pages/admin/projects.rs b/src/pages/admin/projects.rs index ae26e9c..848e0bf 100644 --- a/src/pages/admin/projects.rs +++ b/src/pages/admin/projects.rs @@ -1,20 +1,24 @@ use yew::prelude::*; +use yew_router::prelude::use_navigator; use crate::{ api::backend::get_all_projects_with_filters_paged, components::{ admin_nav_bar::AdminNavigationBar, admin_project::AdminProject, new_widget::NewThingWidget, }, - pages::admin::edit::EditItem, + pages::admin::edit::EditItem, authed_call, auth_present, }; #[function_component(AdminProjects)] pub fn admin_projects() -> Html { + let navigator = use_navigator().unwrap(); + auth_present!(navigator); let projects = use_state(|| Vec::new()); use_state(|| { + let navigator = navigator.clone(); let projects_handle = projects.clone(); wasm_bindgen_futures::spawn_local(async move { - match get_all_projects_with_filters_paged(&1, Vec::new()).await { + match authed_call!(get_all_projects_with_filters_paged(&1, Vec::new()), navigator) { Ok(projects) => { projects_handle.set(projects); } diff --git a/src/pages/admin/start.rs b/src/pages/admin/start.rs index f24b2b5..1f03188 100644 --- a/src/pages/admin/start.rs +++ b/src/pages/admin/start.rs @@ -1,9 +1,12 @@ use yew::prelude::*; +use yew_router::prelude::use_navigator; -use crate::components::admin_nav_bar::AdminNavigationBar; +use crate::{components::admin_nav_bar::AdminNavigationBar, auth_present}; #[function_component(AdminStart)] pub fn admin_start() -> Html { + let navigator = use_navigator().unwrap(); + auth_present!(navigator); html! { <> diff --git a/src/pages/admin/units.rs b/src/pages/admin/units.rs index 5d71d93..c100cf4 100644 --- a/src/pages/admin/units.rs +++ b/src/pages/admin/units.rs @@ -1,10 +1,13 @@ use jl_types::domain::unit::Unit; use yew::prelude::*; +use yew_router::prelude::use_navigator; -use crate::components::admin_unit::AdminUnit; +use crate::{components::admin_unit::AdminUnit, auth_present}; #[function_component(AdminUnits)] pub fn admin_units(props: &AdminUnitProps) -> Html { + let navigator = use_navigator().unwrap(); + auth_present!(navigator); let units_handle = props.units.clone(); html! {
diff --git a/src/utils/admin_panel.rs b/src/utils/admin_panel.rs index fddff55..8aa1b3a 100644 --- a/src/utils/admin_panel.rs +++ b/src/utils/admin_panel.rs @@ -1,13 +1,35 @@ use super::storage; pub fn get_admin_token_from_storage() -> Option { - match storage::get_from_local_storage(storage::StorageKey::AdminUser) { + match storage::get_from_local_storage(storage::StorageKey::AdminToken) { Ok(opt) => opt, Err(_) => { log::error!( - "No user stored when attempting to use admin panel. Redirect to admin panel." + "No user stored when attempting to use admin panel. Redirect to admin authentication." ); None } } } + +pub fn save_admin_token(token: String) { + match storage::store_in_local_storage(storage::StorageKey::AdminToken, &token) { + Ok(_) => {}, + Err(_) => { + log::error!( + "Something wrong happened while storing token." + ); + } + } +} + +pub fn remove_admin_token() { + match storage::delete_from_local_storage(storage::StorageKey::AdminToken) { + Ok(_) => {}, + Err(_) => { + log::error!( + "Something wrong happened while deleting token." + ); + } + } +} \ No newline at end of file diff --git a/src/utils/macros.rs b/src/utils/macros.rs new file mode 100644 index 0000000..16ff803 --- /dev/null +++ b/src/utils/macros.rs @@ -0,0 +1,35 @@ + + +/// This macro calls the api call you give it, then if it's a 401 unauthorized it will navigate you to the admin panel login. +#[macro_export] +macro_rules! authed_call { + ($e:expr, $navigator:expr) => { + match $e.await { + Ok(value) => Ok(value), + Err(error) => { + log::error!("Error happened inside auth call. {error}"); + match error.clone() { + err::Error::UnexpectedStatusCode(_, actual, _) => { + if actual == 401 { + $navigator.push(&crate::routes::main_router::Route::Admin); + } + Err(error) + }, + _ => Err(error) + } + } + } + }; +} + +/// This macro checks if there's an authtoken present in localstorage. If not, send user back to login. +#[macro_export] +macro_rules! auth_present { + ($navigator:expr) => { + match crate::utils::admin_panel::get_admin_token_from_storage() { + Some(_) => {}, + None => $navigator.push(&crate::routes::main_router::Route::Admin), + }; + + }; +} \ No newline at end of file diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 79ede19..306c0d5 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -2,3 +2,4 @@ pub mod admin_panel; pub mod get_value; pub mod input; pub mod storage; +pub mod macros; \ No newline at end of file diff --git a/src/utils/storage.rs b/src/utils/storage.rs index 4fab35a..7673652 100644 --- a/src/utils/storage.rs +++ b/src/utils/storage.rs @@ -5,13 +5,14 @@ use web_sys::window; pub enum StorageKey { AgentShortcode, - AdminUser, + AdminToken, + } impl Display for StorageKey { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { StorageKey::AgentShortcode => write!(f, "agentshortcode"), - StorageKey::AdminUser => write!(f, "adminuser"), + StorageKey::AdminToken => write!(f, "adminuser"), } } } @@ -53,3 +54,19 @@ pub fn get_from_local_storage(key: StorageKey) -> Result, Error> None => Err(Error::IO(MessageResource::new_from_string(format!("Error accessing Window object.")))), } } + +pub fn delete_from_local_storage(key: StorageKey) -> Result<(), Error> { + match window() { + Some(window) => match window.local_storage() { + Ok(local_storage_opt) => match local_storage_opt { + Some(local_storage) => match local_storage.remove_item(key.to_string().as_str()) { + Ok(value) => Ok(value), + Err(js_err) => Err(Error::IO(MessageResource::new_from_string(format!("JsValue: {:#?}", js_err.as_string())))), + }, + None => Err(Error::IO(MessageResource::new_from_str("Error accessing local storage instance from window object. Result was fine, option came back as none."))), + }, + Err(e) => Err(Error::IO(MessageResource::new_from_string(format!("Error accessing local storage instance from window object. Resulting error: {:#?}", e.as_string())))), + }, + None => Err(Error::IO(MessageResource::new_from_string(format!("Error accessing Window object.")))), + } +} \ No newline at end of file