Added auth all across admin

This commit is contained in:
Franklin 2023-04-26 08:23:20 -04:00
parent 7177823b14
commit 5acf61c4f8
15 changed files with 173 additions and 36 deletions

View File

@ -16,16 +16,26 @@ use jl_types::{
project::{NewProjectPayload, UpdateProjectPayload}, project::{NewProjectPayload, UpdateProjectPayload},
unit::{UpdateUnitPayload, NewUnitPayload}, unit::{UpdateUnitPayload, NewUnitPayload},
}, },
project_card::ProjectCardDto, project_card::ProjectCardDto, auth::AuthDto,
}, },
}; };
use reqwest::Method; use reqwest::Method;
use uuid::Uuid; use uuid::Uuid;
use crate::utils::admin_panel;
use super::base::{perform_multipart_request_without_client, perform_request_without_client}; use super::base::{perform_multipart_request_without_client, perform_request_without_client};
const BASE_URL: &str = "http://localhost:8095/"; 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<HashSet<String>, err::Error> { pub async fn get_all_cities() -> Result<HashSet<String>, err::Error> {
perform_request_without_client::<String, HashSet<String>>( perform_request_without_client::<String, HashSet<String>>(
BASE_URL.into(), BASE_URL.into(),
@ -145,7 +155,7 @@ pub async fn get_all_page_visits_count() -> Result<Count, err::Error> {
format!("admin/visits/count"), format!("admin/visits/count"),
None, None,
200, 200,
Vec::new(), Vec::from([get_auth_header()]),
None, None,
) )
.await .await
@ -158,7 +168,7 @@ pub async fn get_all_contacts_count() -> Result<Count, err::Error> {
format!("admin/contacts/count"), format!("admin/contacts/count"),
None, None,
200, 200,
Vec::new(), Vec::from([get_auth_header()]),
None, None,
) )
.await .await
@ -171,7 +181,7 @@ pub async fn get_all_contacts() -> Result<Vec<Contact>, err::Error> {
format!("admin/contacts"), format!("admin/contacts"),
None, None,
200, 200,
Vec::new(), Vec::from([get_auth_header()]),
None, None,
) )
.await .await
@ -213,7 +223,7 @@ pub async fn create_new_agent(agent: NewAgentPayload) -> Result<Agent, err::Erro
format!("admin/agent"), format!("admin/agent"),
Some(agent), Some(agent),
200, 200,
Vec::new(), Vec::from([get_auth_header()]),
None, None,
) )
.await .await
@ -226,7 +236,7 @@ pub async fn create_new_unit(unit: NewUnitPayload) -> Result<Unit, err::Error> {
format!("admin/unit"), format!("admin/unit"),
Some(unit), Some(unit),
200, 200,
Vec::new(), Vec::from([get_auth_header()]),
None, None,
) )
.await .await
@ -239,7 +249,7 @@ pub async fn create_new_project(project: NewProjectPayload) -> Result<Project, e
format!("admin/project"), format!("admin/project"),
Some(project), Some(project),
200, 200,
Vec::new(), Vec::from([get_auth_header()]),
None, None,
) )
.await .await
@ -252,7 +262,7 @@ pub async fn create_location(location: NewLocationPayload) -> Result<Location, e
format!("admin/location"), format!("admin/location"),
Some(location), Some(location),
200, 200,
Vec::new(), Vec::from([get_auth_header()]),
None, None,
) )
.await .await
@ -264,7 +274,7 @@ pub async fn update_agent(agent: UpdateAgentPayload) -> Result<Agent, err::Error
format!("admin/agent"), format!("admin/agent"),
Some(agent), Some(agent),
200, 200,
Vec::new(), Vec::from([get_auth_header()]),
None, None,
) )
.await .await
@ -277,7 +287,7 @@ pub async fn update_project(project: UpdateProjectPayload) -> Result<Project, er
format!("admin/project"), format!("admin/project"),
Some(project), Some(project),
200, 200,
Vec::new(), Vec::from([get_auth_header()]),
None, None,
) )
.await .await
@ -290,7 +300,7 @@ pub async fn update_unit(unit: UpdateUnitPayload) -> Result<Unit, err::Error> {
format!("admin/unit"), format!("admin/unit"),
Some(unit), Some(unit),
200, 200,
Vec::new(), Vec::from([get_auth_header()]),
None, None,
) )
.await .await
@ -303,7 +313,7 @@ pub async fn delete_project(project_id: &Uuid) -> Result<(), err::Error> {
format!("admin/project/{project_id}"), format!("admin/project/{project_id}"),
None, None,
200, 200,
Vec::new(), Vec::from([get_auth_header()]),
None, None,
) )
.await .await
@ -316,7 +326,7 @@ pub async fn delete_agent(agent_id: &Uuid) -> Result<(), err::Error> {
format!("admin/agent/{agent_id}"), format!("admin/agent/{agent_id}"),
None, None,
200, 200,
Vec::new(), Vec::from([get_auth_header()]),
None, None,
) )
.await .await
@ -329,7 +339,7 @@ pub async fn delete_unit(unit_id: &Uuid) -> Result<(), err::Error> {
format!("admin/unit/{unit_id}"), format!("admin/unit/{unit_id}"),
None, None,
200, 200,
Vec::new(), Vec::from([get_auth_header()]),
None, None,
) )
.await .await
@ -342,6 +352,19 @@ pub async fn upload_image(item: Item, body: Vec<u8>) -> Result<String, err::Erro
format!("admin/images/{item}"), format!("admin/images/{item}"),
body, body,
200, 200,
Vec::from([get_auth_header()]),
None,
)
.await
}
pub async fn authenticate(auth_dto: AuthDto) -> Result<String, err::Error> {
perform_request_without_client(
BASE_URL.into(),
Method::POST,
format!("admin/auth"),
Some(auth_dto),
200,
Vec::new(), Vec::new(),
None, None,
) )

View File

@ -1,15 +1,19 @@
use yew::prelude::*; use yew::prelude::*;
use yew_router::prelude::use_navigator;
use crate::{ use crate::{
api::backend::get_all_agents, api::backend::get_all_agents,
components::{ components::{
admin_agent::AdminAgent, admin_nav_bar::AdminNavigationBar, new_widget::NewThingWidget, admin_agent::AdminAgent, admin_nav_bar::AdminNavigationBar, new_widget::NewThingWidget,
}, },
pages::admin::edit::EditItem, pages::admin::edit::EditItem, auth_present,
}; };
#[function_component(AdminAgents)] #[function_component(AdminAgents)]
pub fn admin_agents() -> Html { pub fn admin_agents() -> Html {
let navigator = use_navigator().unwrap();
auth_present!(navigator);
let agents = use_state(|| Vec::new()); let agents = use_state(|| Vec::new());
use_state(|| { use_state(|| {

View File

@ -1,9 +1,12 @@
use yew::prelude::*; 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)] #[function_component(AdminContacts)]
pub fn admin_contacts() -> Html { pub fn admin_contacts() -> Html {
let navigator = use_navigator().unwrap();
auth_present!(navigator);
let contacts = use_state(|| Vec::new()); let contacts = use_state(|| Vec::new());
use_state(|| { use_state(|| {

View File

@ -7,13 +7,14 @@ use yew_router::prelude::use_navigator;
use crate::{ use crate::{
api::backend::{get_agent_with_id, get_project_listing}, api::backend::{get_agent_with_id, get_project_listing},
components::admin_nav_bar::AdminNavigationBar, 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. /// 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)] #[function_component(AdminEditPage)]
pub fn edit_page(props: &AdminEditPageProps) -> Html { 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 listing = use_state(|| None);
let agent = use_state(|| None); let agent = use_state(|| None);
use_state(|| { use_state(|| {

View File

@ -11,7 +11,7 @@ use crate::{
dropdown::DropDown, single_media_picker::SingleMediaPicker, textfield::TextField, dropdown::DropDown, single_media_picker::SingleMediaPicker, textfield::TextField,
}, },
pages::admin::edit::EditType, pages::admin::edit::EditType,
routes::main_router::Route, routes::main_router::Route, auth_present,
}; };
#[derive(Properties, PartialEq, Clone)] #[derive(Properties, PartialEq, Clone)]
@ -23,6 +23,7 @@ pub struct AgentFieldsProps {
#[function_component(AgentFields)] #[function_component(AgentFields)]
pub fn agent_fields(props: &AgentFieldsProps) -> Html { pub fn agent_fields(props: &AgentFieldsProps) -> Html {
let navigator = use_navigator().unwrap(); let navigator = use_navigator().unwrap();
auth_present!(navigator);
let user_typed = use_state(|| false); let user_typed = use_state(|| false);
let agent_opt = props.agent.clone(); let agent_opt = props.agent.clone();

View File

@ -32,7 +32,7 @@ use crate::{
edit::{EditItem, EditType}, edit::{EditItem, EditType},
units::AdminUnits, units::AdminUnits,
}, },
routes::main_router::Route, routes::main_router::Route, auth_present,
}; };
#[derive(Properties, PartialEq, Clone)] #[derive(Properties, PartialEq, Clone)]
@ -44,6 +44,7 @@ pub struct ProjectFieldsProps {
#[function_component(ProjectFields)] #[function_component(ProjectFields)]
pub fn generate_fields_for_project(props: &ProjectFieldsProps) -> Html { pub fn generate_fields_for_project(props: &ProjectFieldsProps) -> Html {
let navigator = use_navigator().unwrap(); let navigator = use_navigator().unwrap();
auth_present!(navigator);
let user_typed = use_state(|| false); let user_typed = use_state(|| false);
let listing_opt = props.listing.clone(); let listing_opt = props.listing.clone();

View File

@ -5,7 +5,7 @@ use yew_router::prelude::use_navigator;
use crate::{ use crate::{
components::{dropdown::DropDown, number_textfield::NumberTextField, media_picker::MediaPicker, textfield::{TextField, TextFieldType}}, 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)] #[derive(Properties, PartialEq, Clone)]
@ -38,6 +38,7 @@ pub fn unit_fields(props: &UnitFieldsProps) -> Html {
let navigator = use_navigator().unwrap(); let navigator = use_navigator().unwrap();
auth_present!(navigator);
let user_typed = use_state(|| false); let user_typed = use_state(|| false);
let edit_type = props.edittype.clone(); let edit_type = props.edittype.clone();
let project_id = props.projectid.clone(); let project_id = props.projectid.clone();

View File

@ -1,15 +1,23 @@
use jl_types::dto::auth::AuthDto;
use yew::prelude::*; use yew::prelude::*;
use yew_router::prelude::use_navigator; 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)] #[function_component(AdminLoginPage)]
pub fn login_page() -> Html { pub fn login_page() -> Html {
// TODO: If logged in go to start
let navigator = use_navigator().unwrap(); let navigator = use_navigator().unwrap();
let error = use_state(|| false); let error = use_state(|| false);
let username = use_state(|| String::new()); let username = use_state(|| String::new());
let password = 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 on_username_input_changed = {
let username = username.clone(); let username = username.clone();
@ -25,14 +33,24 @@ pub fn login_page() -> Html {
}; };
let onclick = { let onclick = {
let error = error.clone(); let error = error.clone();
let username = username.clone();
let password = password.clone();
Callback::from(move |_| { Callback::from(move |_| {
let error = error.clone(); let error = error.clone();
let navigator = navigator.clone(); let navigator = navigator.clone();
let username = username.clone();
let password = password.clone();
wasm_bindgen_futures::spawn_local(async move { wasm_bindgen_futures::spawn_local(async move {
error.set(false); match authenticate(AuthDto { email: (*username).clone(), password: (*password).clone() }).await {
// Call backend login Ok(token) => {
navigator.push(&Route::AdminStart); save_admin_token(token);
navigator.push(&Route::AdminStart);
},
Err(error_message) => {
log::error!("Incorrect user/pass...: {error_message}");
error.set(true);
}
};
}); });
}) })
}; };

View File

@ -1,20 +1,24 @@
use yew::prelude::*; use yew::prelude::*;
use yew_router::prelude::use_navigator;
use crate::{ use crate::{
api::backend::get_all_projects_with_filters_paged, api::backend::get_all_projects_with_filters_paged,
components::{ components::{
admin_nav_bar::AdminNavigationBar, admin_project::AdminProject, new_widget::NewThingWidget, 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)] #[function_component(AdminProjects)]
pub fn admin_projects() -> Html { pub fn admin_projects() -> Html {
let navigator = use_navigator().unwrap();
auth_present!(navigator);
let projects = use_state(|| Vec::new()); let projects = use_state(|| Vec::new());
use_state(|| { use_state(|| {
let navigator = navigator.clone();
let projects_handle = projects.clone(); let projects_handle = projects.clone();
wasm_bindgen_futures::spawn_local(async move { 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) => { Ok(projects) => {
projects_handle.set(projects); projects_handle.set(projects);
} }

View File

@ -1,9 +1,12 @@
use yew::prelude::*; 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)] #[function_component(AdminStart)]
pub fn admin_start() -> Html { pub fn admin_start() -> Html {
let navigator = use_navigator().unwrap();
auth_present!(navigator);
html! { html! {
<> <>
<AdminNavigationBar/> <AdminNavigationBar/>

View File

@ -1,10 +1,13 @@
use jl_types::domain::unit::Unit; use jl_types::domain::unit::Unit;
use yew::prelude::*; 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)] #[function_component(AdminUnits)]
pub fn admin_units(props: &AdminUnitProps) -> Html { pub fn admin_units(props: &AdminUnitProps) -> Html {
let navigator = use_navigator().unwrap();
auth_present!(navigator);
let units_handle = props.units.clone(); let units_handle = props.units.clone();
html! { html! {
<div class={"admin-start-container"} style={"min-height: 10vh; margin-top: 10vh;"}> <div class={"admin-start-container"} style={"min-height: 10vh; margin-top: 10vh;"}>

View File

@ -1,13 +1,35 @@
use super::storage; use super::storage;
pub fn get_admin_token_from_storage() -> Option<String> { pub fn get_admin_token_from_storage() -> Option<String> {
match storage::get_from_local_storage(storage::StorageKey::AdminUser) { match storage::get_from_local_storage(storage::StorageKey::AdminToken) {
Ok(opt) => opt, Ok(opt) => opt,
Err(_) => { Err(_) => {
log::error!( 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 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."
);
}
}
}

35
src/utils/macros.rs Normal file
View File

@ -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),
};
};
}

View File

@ -2,3 +2,4 @@ pub mod admin_panel;
pub mod get_value; pub mod get_value;
pub mod input; pub mod input;
pub mod storage; pub mod storage;
pub mod macros;

View File

@ -5,13 +5,14 @@ use web_sys::window;
pub enum StorageKey { pub enum StorageKey {
AgentShortcode, AgentShortcode,
AdminUser, AdminToken,
} }
impl Display for StorageKey { impl Display for StorageKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
StorageKey::AgentShortcode => write!(f, "agentshortcode"), 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<Option<String>, Error>
None => Err(Error::IO(MessageResource::new_from_string(format!("Error accessing Window object.")))), 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.")))),
}
}