Added admin field to prevent a normal user fetching admin data
This commit is contained in:
parent
e7edea5c85
commit
1aa7a0565d
@ -2,7 +2,7 @@ pub mod migration;
|
|||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
use crate::util::model::member::Member;
|
use crate::util::model::{member::Member, session::Session};
|
||||||
|
|
||||||
pub fn Members() -> Element {
|
pub fn Members() -> Element {
|
||||||
let members = use_resource(fetch_members);
|
let members = use_resource(fetch_members);
|
||||||
@ -58,13 +58,27 @@ fn MemberRow(props: MemberRowProps) -> Element {
|
|||||||
class: "hover hover:cursor-pointer",
|
class: "hover hover:cursor-pointer",
|
||||||
th { "{props.member.id}" }
|
th { "{props.member.id}" }
|
||||||
td { "{props.member.name.full}" }
|
td { "{props.member.name.full}" }
|
||||||
td { "{registration_token} {props.member.hours:?}" }
|
td { "{registration_token}" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[server]
|
#[server]
|
||||||
async fn fetch_members() -> Result<Vec<Member>, ServerFnError> {
|
async fn fetch_members() -> Result<Vec<Member>, ServerFnError> {
|
||||||
|
let user = Session::fetch_current_user().await?;
|
||||||
|
|
||||||
|
if !user.admin {
|
||||||
|
return Err(crate::Error::NoPermissions.into());
|
||||||
|
}
|
||||||
|
|
||||||
let members = Member::fetch_all().await?;
|
let members = Member::fetch_all().await?;
|
||||||
Ok(members)
|
Ok(members)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn MemberModal(props: MemberRowProps) -> Element {
|
||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
"BOe"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,4 +1,7 @@
|
|||||||
use crate::util::model::member::{Member, MembersMigration};
|
use crate::util::model::{
|
||||||
|
member::{Member, MembersMigration},
|
||||||
|
session::Session,
|
||||||
|
};
|
||||||
use dioxus::prelude::{dioxus_elements::FileEngine, *};
|
use dioxus::prelude::{dioxus_elements::FileEngine, *};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@ -209,6 +212,12 @@ fn MembersTable(members: Vec<Member>) -> Element {
|
|||||||
|
|
||||||
#[server]
|
#[server]
|
||||||
async fn upload_members_list(input: String) -> Result<(u16, MembersMigration), ServerFnError> {
|
async fn upload_members_list(input: String) -> Result<(u16, MembersMigration), ServerFnError> {
|
||||||
|
let user = Session::fetch_current_user().await?;
|
||||||
|
|
||||||
|
if !user.admin {
|
||||||
|
return Err(crate::Error::NoPermissions.into());
|
||||||
|
}
|
||||||
|
|
||||||
match MembersMigration::migrate_proposal(input).await {
|
match MembersMigration::migrate_proposal(input).await {
|
||||||
Ok(r) => Ok(r),
|
Ok(r) => Ok(r),
|
||||||
Err(err) => Err(ServerFnError::new(err.to_string())),
|
Err(err) => Err(ServerFnError::new(err.to_string())),
|
||||||
@ -217,6 +226,12 @@ async fn upload_members_list(input: String) -> Result<(u16, MembersMigration), S
|
|||||||
|
|
||||||
#[server]
|
#[server]
|
||||||
async fn migration_response(correct: bool, id: u16) -> Result<(), ServerFnError> {
|
async fn migration_response(correct: bool, id: u16) -> Result<(), ServerFnError> {
|
||||||
|
let user = Session::fetch_current_user().await?;
|
||||||
|
|
||||||
|
if !user.admin {
|
||||||
|
return Err(crate::Error::NoPermissions.into());
|
||||||
|
}
|
||||||
|
|
||||||
if correct {
|
if correct {
|
||||||
match MembersMigration::migrate(id).await {
|
match MembersMigration::migrate(id).await {
|
||||||
Err(err) => Err(ServerFnError::new(err.to_string())),
|
Err(err) => Err(ServerFnError::new(err.to_string())),
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
use crate::util::model::user::User;
|
use crate::util::model::{session::Session, user::User};
|
||||||
|
|
||||||
pub fn Users() -> Element {
|
pub fn Users() -> Element {
|
||||||
let users = use_resource(fetch_users);
|
let users = use_resource(fetch_users);
|
||||||
@ -56,6 +56,13 @@ fn UserRow(props: UserRowProps) -> Element {
|
|||||||
|
|
||||||
#[server]
|
#[server]
|
||||||
async fn fetch_users() -> Result<Vec<User>, ServerFnError> {
|
async fn fetch_users() -> Result<Vec<User>, ServerFnError> {
|
||||||
|
let user = Session::fetch_current_user().await?;
|
||||||
|
|
||||||
|
if !user.admin {
|
||||||
|
return Err(crate::Error::NoPermissions.into());
|
||||||
|
}
|
||||||
|
|
||||||
let users = User::fetch_all().await?;
|
let users = User::fetch_all().await?;
|
||||||
|
|
||||||
Ok(users)
|
Ok(users)
|
||||||
}
|
}
|
||||||
|
@ -51,3 +51,17 @@ async fn get_user_from_cookie() -> Result<User, ServerFnError> {
|
|||||||
|
|
||||||
Ok(user)
|
Ok(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn AdminLayout() -> Element {
|
||||||
|
let user_state = use_context::<Signal<Option<User>>>();
|
||||||
|
let user = user_state().unwrap();
|
||||||
|
|
||||||
|
rsx! {
|
||||||
|
if user.admin {
|
||||||
|
Outlet::<Route> {}
|
||||||
|
} else {
|
||||||
|
div { "No Permissions" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -2,9 +2,13 @@ use crate::Route;
|
|||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
use super::icons;
|
use super::icons;
|
||||||
|
use crate::util::model::user::User;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Topbar() -> Element {
|
pub fn Topbar() -> Element {
|
||||||
|
let user_state = use_context::<Signal<Option<User>>>();
|
||||||
|
let user = user_state().unwrap();
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div {
|
||||||
class: "navbar bg-base-200 px-3",
|
class: "navbar bg-base-200 px-3",
|
||||||
@ -14,10 +18,12 @@ pub fn Topbar() -> Element {
|
|||||||
}
|
}
|
||||||
div {
|
div {
|
||||||
class: "flex-none gap-2",
|
class: "flex-none gap-2",
|
||||||
Link {
|
if user.admin {
|
||||||
class: "btn btn-ghost font-normal",
|
Link {
|
||||||
to: Route::Admin {},
|
class: "btn btn-ghost font-normal",
|
||||||
"Administration"
|
to: Route::Admin {},
|
||||||
|
"Administration"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Link {
|
Link {
|
||||||
class: "btn btn-square btn-ghost",
|
class: "btn btn-square btn-ghost",
|
||||||
|
@ -20,4 +20,7 @@ pub enum Error {
|
|||||||
|
|
||||||
#[error("Could not get cookie jar")]
|
#[error("Could not get cookie jar")]
|
||||||
CookieJar(String),
|
CookieJar(String),
|
||||||
|
|
||||||
|
#[error("No permissions")]
|
||||||
|
NoPermissions,
|
||||||
}
|
}
|
||||||
|
36
src/main.rs
36
src/main.rs
@ -17,29 +17,33 @@ use components::admin::users::Users;
|
|||||||
use components::admin::Admin;
|
use components::admin::Admin;
|
||||||
use components::agenda::Agenda;
|
use components::agenda::Agenda;
|
||||||
use components::home::Home;
|
use components::home::Home;
|
||||||
|
use components::layout::AdminLayout;
|
||||||
use components::layout::Global;
|
use components::layout::Global;
|
||||||
use components::news::News;
|
use components::news::News;
|
||||||
use components::settings::Settings;
|
use components::settings::Settings;
|
||||||
|
|
||||||
#[derive(Clone, Routable, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
#[derive(Clone, Routable, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||||
|
#[rustfmt::skip]
|
||||||
pub enum Route {
|
pub enum Route {
|
||||||
#[layout(Global)]
|
#[layout(Global)]
|
||||||
#[route("/")]
|
#[route("/")]
|
||||||
Home {},
|
Home {},
|
||||||
#[route("/agenda")]
|
#[route("/agenda")]
|
||||||
Agenda {},
|
Agenda {},
|
||||||
#[route("/news")]
|
#[route("/news")]
|
||||||
News {},
|
News {},
|
||||||
#[route("/settings")]
|
#[route("/settings")]
|
||||||
Settings {},
|
Settings {},
|
||||||
#[route("/admin")]
|
|
||||||
Admin {},
|
#[layout(AdminLayout)]
|
||||||
#[route("/admin/users")]
|
#[route("/admin")]
|
||||||
Users {},
|
Admin {},
|
||||||
#[route("/admin/members")]
|
#[route("/admin/users")]
|
||||||
Members {},
|
Users {},
|
||||||
#[route("/admin/members/migration")]
|
#[route("/admin/members")]
|
||||||
Migration {},
|
Members {},
|
||||||
|
#[route("/admin/members/migration")]
|
||||||
|
Migration {},
|
||||||
}
|
}
|
||||||
|
|
||||||
const _TAILWIND_URL: &str = manganis::mg!(file("assets/tailwind.css"));
|
const _TAILWIND_URL: &str = manganis::mg!(file("assets/tailwind.css"));
|
||||||
|
@ -64,7 +64,7 @@ impl Session {
|
|||||||
pub async fn fetch_user_from_token(session_token: String) -> Result<User, crate::Error> {
|
pub async fn fetch_user_from_token(session_token: String) -> Result<User, crate::Error> {
|
||||||
let mut res = DB
|
let mut res = DB
|
||||||
.query(
|
.query(
|
||||||
"SELECT user as id, user.email as email FROM session WHERE token = $session_token",
|
"SELECT user as id, user.email as email, user.admin as admin FROM session WHERE token = $session_token",
|
||||||
)
|
)
|
||||||
.bind(("session_token", session_token))
|
.bind(("session_token", session_token))
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -13,13 +13,14 @@ pub struct User {
|
|||||||
#[cfg_attr(feature = "server", serde(deserialize_with = "thing_to_string"))]
|
#[cfg_attr(feature = "server", serde(deserialize_with = "thing_to_string"))]
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub email: String,
|
pub email: String,
|
||||||
|
pub admin: bool,
|
||||||
password: Option<String>,
|
password: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
impl User {
|
impl User {
|
||||||
pub async fn fetch_all() -> Result<Vec<Self>, surrealdb::Error> {
|
pub async fn fetch_all() -> Result<Vec<Self>, surrealdb::Error> {
|
||||||
let mut res = DB.query("SELECT id, email FROM user").await?;
|
let mut res = DB.query("SELECT id, email, admin FROM user").await?;
|
||||||
|
|
||||||
res = res.check()?;
|
res = res.check()?;
|
||||||
|
|
||||||
@ -35,7 +36,7 @@ impl User {
|
|||||||
) -> Result<Self, crate::Error> {
|
) -> Result<Self, crate::Error> {
|
||||||
// Create user record
|
// Create user record
|
||||||
let mut transaction = DB.query(BeginStatement::default())
|
let mut transaction = DB.query(BeginStatement::default())
|
||||||
.query("let $user = CREATE ONLY user SET email = $email, password = crypto::argon2::generate($password) RETURN id, email;")
|
.query("let $user = CREATE ONLY user SET email = $email, password = crypto::argon2::generate($password) RETURN id, email, admin;")
|
||||||
.bind(("email", email))
|
.bind(("email", email))
|
||||||
.bind(("password", password));
|
.bind(("password", password));
|
||||||
|
|
||||||
@ -103,7 +104,7 @@ impl User {
|
|||||||
password: String,
|
password: String,
|
||||||
) -> Result<String, crate::Error> {
|
) -> Result<String, crate::Error> {
|
||||||
let mut res = DB
|
let mut res = DB
|
||||||
.query("SELECT id, email FROM user WHERE email = $email AND crypto::argon2::compare(password, $password)")
|
.query("SELECT id, email, admin FROM user WHERE email = $email AND crypto::argon2::compare(password, $password)")
|
||||||
.bind(("email", email))
|
.bind(("email", email))
|
||||||
.bind(("password", password))
|
.bind(("password", password))
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -41,6 +41,8 @@ async fn apply_queries() -> surrealdb::Result<()> {
|
|||||||
VALUE string::lowercase($value)
|
VALUE string::lowercase($value)
|
||||||
ASSERT string::is::email($value);
|
ASSERT string::is::email($value);
|
||||||
DEFINE FIELD password ON TABLE user TYPE string;
|
DEFINE FIELD password ON TABLE user TYPE string;
|
||||||
|
DEFINE FIELD admin ON TABLE user TYPE bool
|
||||||
|
DEFAULT false;
|
||||||
|
|
||||||
DEFINE INDEX userEmailIndex ON TABLE user COLUMNS email UNIQUE;
|
DEFINE INDEX userEmailIndex ON TABLE user COLUMNS email UNIQUE;
|
||||||
",
|
",
|
||||||
|
Loading…
Reference in New Issue
Block a user