diff --git a/migrations/2024-05-18-231505_add_user_admin/down.sql b/migrations/2024-05-18-231505_add_user_admin/down.sql new file mode 100644 index 0000000..8c2de0f --- /dev/null +++ b/migrations/2024-05-18-231505_add_user_admin/down.sql @@ -0,0 +1 @@ +ALTER TABLE users DROP COLUMN admin; diff --git a/migrations/2024-05-18-231505_add_user_admin/up.sql b/migrations/2024-05-18-231505_add_user_admin/up.sql new file mode 100644 index 0000000..58a6e49 --- /dev/null +++ b/migrations/2024-05-18-231505_add_user_admin/up.sql @@ -0,0 +1 @@ +ALTER TABLE users ADD COLUMN admin BOOLEAN DEFAULT FALSE NOT NULL; diff --git a/src/auth.rs b/src/auth.rs index 2eda7cf..8c33347 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -21,9 +21,10 @@ use crate::users::UserCredentials; pub async fn signup(new_user: User) -> Result<(), ServerFnError> { use crate::users::create_user; - // Ensure the user has no id + // Ensure the user has no id, and is not a self-proclaimed admin let new_user = User { id: None, + admin: false, ..new_user }; @@ -120,3 +121,37 @@ pub async fn require_auth() -> Result<(), ServerFnError> { } }) } + +/// Check if a user is an admin +/// Returns a Result with a boolean indicating if the user is logged in and an admin +#[server(endpoint = "check_admin")] +pub async fn check_admin() -> Result { + let auth_session = extract::>().await + .map_err(|e| ServerFnError::::ServerError(format!("Error getting auth session: {}", e)))?; + + Ok(auth_session.user.as_ref().map(|u| u.admin).unwrap_or(false)) +} + +/// Require that a user is logged in and an admin +/// Returns a Result with the error message if the user is not logged in or is not an admin +/// Intended to be used at the start of a protected route, to ensure the user is logged in and an admin: +/// ```rust +/// use leptos::*; +/// use libretunes::auth::require_admin; +/// #[server(endpoint = "protected_admin_route")] +/// pub async fn protected_admin_route() -> Result<(), ServerFnError> { +/// require_admin().await?; +/// // Continue with protected route +/// Ok(()) +/// } +/// ``` +#[cfg(feature = "ssr")] +pub async fn require_admin() -> Result<(), ServerFnError> { + check_admin().await.and_then(|is_admin| { + if is_admin { + Ok(()) + } else { + Err(ServerFnError::::ServerError(format!("Unauthorized"))) + } + }) +} diff --git a/src/models.rs b/src/models.rs index 442713e..349ed74 100644 --- a/src/models.rs +++ b/src/models.rs @@ -41,6 +41,8 @@ pub struct User { /// The time the user was created #[cfg_attr(feature = "ssr", diesel(deserialize_as = SystemTime))] pub created_at: Option, + /// Whether the user is an admin + pub admin: bool, } /// Model for an artist diff --git a/src/pages/signup.rs b/src/pages/signup.rs index 69f68c7..f02dfab 100644 --- a/src/pages/signup.rs +++ b/src/pages/signup.rs @@ -25,6 +25,7 @@ pub fn Signup() -> impl IntoView { email: email.get(), password: Some(password.get()), created_at: None, + admin: false, }; log!("new user: {:?}", new_user); diff --git a/src/schema.rs b/src/schema.rs index 97de324..e4964b9 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -50,6 +50,7 @@ diesel::table! { email -> Varchar, password -> Varchar, created_at -> Timestamp, + admin -> Bool, } }