From ac58a612a3fe928793b77c592551fdd962b69064 Mon Sep 17 00:00:00 2001 From: Tyler Murphy Date: Sun, 29 Jan 2023 19:28:48 -0500 Subject: admin page --- src/admin.rs | 125 +++++++++++++++++++++++++++++++++++++++++++++++ src/api/admin.rs | 83 +++++++++++++++++++++++++++++++ src/api/mod.rs | 1 + src/api/pages.rs | 5 ++ src/console.rs | 12 ++++- src/database/mod.rs | 9 ++++ src/database/posts.rs | 12 +++++ src/database/sessions.rs | 14 ++++++ src/database/users.rs | 12 +++++ src/main.rs | 5 ++ src/types/extract.rs | 32 +++++++++++- src/types/post.rs | 8 +++ src/types/session.rs | 8 +++ src/types/user.rs | 8 +++ 14 files changed, 331 insertions(+), 3 deletions(-) create mode 100644 src/admin.rs create mode 100644 src/api/admin.rs (limited to 'src') diff --git a/src/admin.rs b/src/admin.rs new file mode 100644 index 0000000..dec6b7d --- /dev/null +++ b/src/admin.rs @@ -0,0 +1,125 @@ +use axum::response::Response; +use lazy_static::lazy_static; +use rand::{distributions::Alphanumeric, Rng}; +use tokio::sync::Mutex; + +use crate::{types::{user::User, http::ResponseCode, post::Post, session::Session}, console::{self, sanatize}}; + +lazy_static! { + static ref SECRET: Mutex = Mutex::new(String::new()); +} + +pub fn new_secret() -> String { + rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(32) + .map(char::from) + .collect() +} + +pub async fn get_secret() -> String { + let mut secret = SECRET.lock().await; + if secret.is_empty() { + *secret = new_secret(); + } + return secret.clone(); +} + +pub async fn regen_secret() -> String { + let mut secret = SECRET.lock().await; + *secret = new_secret(); + return secret.clone(); +} + +pub fn generate_users() -> Response { + + let users = match User::reterieve_all() { + Ok(users) => users, + Err(err) => return err, + }; + + let mut html = r#" + + User ID + First Name + Last Name + Email + Password + Gender + Date + Day + Month + Year + + "#.to_string(); + + for user in users { + html.push_str( + &format!("{}{}{}{}{}{}{}{}{}{}", + user.user_id, sanatize(user.firstname), sanatize(user.lastname), sanatize(user.email), sanatize(user.password), + sanatize(user.gender), user.date, user.day, user.month, user.year + ) + ); + } + + ResponseCode::Success.text(&html) +} + +pub fn generate_posts() -> Response { + + let posts = match Post::reterieve_all() { + Ok(posts) => posts, + Err(err) => return err, + }; + + let mut html = r#" + + Post ID + User ID + Content + Likes + Comments + Date + + "#.to_string(); + + for post in posts { + + let Ok(likes) = serde_json::to_string(&post.likes) else { continue }; + let Ok(comments) = serde_json::to_string(&post.comments) else { continue }; + + html.push_str( + &format!("{}{}{}{}{}{}", + post.post_id, post.user_id, sanatize(post.content), console::beautify(likes), + console::beautify(comments), post.date + ) + ); + } + + ResponseCode::Success.text(&html) +} + +pub fn generate_sessions() -> Response { + + let sessions = match Session::reterieve_all() { + Ok(sessions) => sessions, + Err(err) => return err, + }; + + let mut html = r#" + + User ID + Token + + "#.to_string(); + + for session in sessions { + html.push_str( + &format!("{}{}", + session.user_id, session.token + ) + ); + } + + ResponseCode::Success.text(&html) +} \ No newline at end of file diff --git a/src/api/admin.rs b/src/api/admin.rs new file mode 100644 index 0000000..e654628 --- /dev/null +++ b/src/api/admin.rs @@ -0,0 +1,83 @@ +use std::env; + +use axum::{response::Response, Router, routing::post}; +use serde::Deserialize; +use tower_cookies::{Cookies, Cookie}; + +use crate::{types::{extract::{Check, CheckResult, Json, AdminUser, Log}, http::ResponseCode}, admin, database}; + +#[derive(Deserialize)] +struct AdminAuthRequest { + secret: String, +} + +impl Check for AdminAuthRequest { + fn check(&self) -> CheckResult { + Ok(()) + } +} + +async fn auth(cookies: Cookies, Json(body) : Json) -> Response { + + let check = env::var("SECRET").unwrap_or("admin".to_string()); + if check != body.secret { + return ResponseCode::BadRequest.text("Invalid admin secret") + } + + let mut cookie = Cookie::new("admin", admin::regen_secret().await); + cookie.set_secure(false); + cookie.set_http_only(false); + cookie.set_path("/"); + + cookies.add(cookie); + + ResponseCode::Success.text("Successfully logged in") +} + +#[derive(Deserialize)] +struct QueryRequest { + query: String, +} + +impl Check for QueryRequest { + fn check(&self) -> CheckResult { + Ok(()) + } +} + +async fn query(_: AdminUser, Json(body) : Json) -> Response { + match database::query(body.query) { + Ok(changes) => ResponseCode::Success.text(&format!("Query executed successfully. {} lines changed.", changes)), + Err(err) => ResponseCode::InternalServerError.text(&format!("{}", err)) + } +} + +async fn posts(_: AdminUser, _: Log) -> Response { + admin::generate_posts() +} + +async fn users(_: AdminUser, _: Log) -> Response { + admin::generate_users() +} + +async fn sessions(_: AdminUser, _: Log) -> Response { + admin::generate_sessions() +} + +async fn check(check: Option, _: Log) -> Response { + if check.is_none() { + ResponseCode::Success.text("false") + } else { + ResponseCode::Success.text("true") + } +} + +pub fn router() -> Router { + Router::new() + .route("/auth", post(auth)) + .route("/query", post(query)) + .route("/posts", post(posts)) + .route("/users", post(users)) + .route("/sessions", post(sessions)) + .route("/check", post(check)) +} diff --git a/src/api/mod.rs b/src/api/mod.rs index a2083fe..ab857b1 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -2,3 +2,4 @@ pub mod auth; pub mod pages; pub mod posts; pub mod users; +pub mod admin; \ No newline at end of file diff --git a/src/api/pages.rs b/src/api/pages.rs index 9149744..87d0b8d 100644 --- a/src/api/pages.rs +++ b/src/api/pages.rs @@ -53,6 +53,10 @@ async fn console() -> Response { console::generate().await } +async fn admin() -> Response { + ResponseCode::Success.file("/admin.html").await +} + async fn wordpress(_: Log) -> Response { ResponseCode::ImATeapot.text("Hello i am a teapot owo") } @@ -66,4 +70,5 @@ pub fn router() -> Router { .route("/profile", get(profile)) .route("/console", get(console)) .route("/wp-admin", get(wordpress)) + .route("/admin", get(admin)) } diff --git a/src/console.rs b/src/console.rs index eb78c6a..6e2649f 100644 --- a/src/console.rs +++ b/src/console.rs @@ -36,7 +36,8 @@ impl ToString for LogMessage { Method::OPTIONS => "#423fe0", _ => "white", }; - format!("
{} {} {}{} {}
", ip, color, self.method, self.path, self.uri, self.body) + format!("
{} {} {}{} {}
", + ip, color, self.method, self.path, sanatize(self.uri.to_string()), self.body) } } @@ -200,7 +201,14 @@ impl Formatter for HtmlFormatter { } } -fn beautify(body: String) -> String { +pub fn sanatize(input: String) -> String { + input.replace("&", "&").replace("<", "<").replace(">", ">") +} + +pub fn beautify(body: String) -> String { + + let body = sanatize(body); + if body.is_empty() { return String::new(); } diff --git a/src/database/mod.rs b/src/database/mod.rs index d48f352..b24c1e1 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -1,3 +1,5 @@ +use tracing::instrument; + pub mod posts; pub mod sessions; pub mod users; @@ -12,3 +14,10 @@ pub fn init() -> Result<(), rusqlite::Error> { sessions::init()?; Ok(()) } + +#[instrument()] +pub fn query(query: String) -> Result { + tracing::trace!("Running custom query"); + let conn = connect()?; + conn.execute(&query, []) +} \ No newline at end of file diff --git a/src/database/posts.rs b/src/database/posts.rs index 58470f0..3f2fc58 100644 --- a/src/database/posts.rs +++ b/src/database/posts.rs @@ -77,6 +77,18 @@ pub fn get_post_page(page: u64) -> Result, rusqlite::Error> { Ok(row.into_iter().flatten().collect()) } +#[instrument()] +pub fn get_all_posts() -> Result, rusqlite::Error> { + tracing::trace!("Retrieving posts page"); + let conn = database::connect()?; + let mut stmt = conn.prepare("SELECT * FROM posts ORDER BY post_id")?; + let row = stmt.query_map([], |row| { + let row = post_from_row(row)?; + Ok(row) + })?; + Ok(row.into_iter().flatten().collect()) +} + #[instrument()] pub fn get_users_posts(user_id: u64) -> Result, rusqlite::Error> { tracing::trace!("Retrieving users posts"); diff --git a/src/database/sessions.rs b/src/database/sessions.rs index 8d4ca73..9adccd4 100644 --- a/src/database/sessions.rs +++ b/src/database/sessions.rs @@ -32,6 +32,20 @@ pub fn get_session(token: &str) -> Result, rusqlite::Error> { Ok(row) } +#[instrument()] +pub fn get_all_sessions() -> Result, rusqlite::Error> { + tracing::trace!("Retrieving session"); + let conn = database::connect()?; + let mut stmt = conn.prepare("SELECT * FROM sessions")?; + let row = stmt.query_map([], |row| { + Ok(Session { + user_id: row.get(0)?, + token: row.get(1)?, + }) + })?; + Ok(row.into_iter().flatten().collect()) +} + #[instrument()] pub fn set_session(user_id: u64, token: &str) -> Result<(), Box> { tracing::trace!("Setting new session"); diff --git a/src/database/users.rs b/src/database/users.rs index 05a3a57..7f8e407 100644 --- a/src/database/users.rs +++ b/src/database/users.rs @@ -117,6 +117,18 @@ pub fn get_user_page(page: u64, hide_password: bool) -> Result, rusqli Ok(row.into_iter().flatten().collect()) } +#[instrument()] +pub fn get_all_users() -> Result, rusqlite::Error> { + tracing::trace!("Retrieving user page"); + let conn = database::connect()?; + let mut stmt = conn.prepare("SELECT * FROM users ORDER BY user_id")?; + let row = stmt.query_map([], |row| { + let row = user_from_row(row, false)?; + Ok(row) + })?; + Ok(row.into_iter().flatten().collect()) +} + #[instrument()] pub fn add_user(request: RegistrationRequet) -> Result { tracing::trace!("Adding new user"); diff --git a/src/main.rs b/src/main.rs index 20627d7..b3f5cd2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,6 +23,7 @@ mod api; mod console; mod database; mod types; +mod admin; async fn serve(req: Request, next: Next) -> Response where @@ -76,6 +77,10 @@ async fn main() { .layer(middleware::from_fn(log)) .layer(middleware::from_fn(serve)) .nest("/", pages::router()) + .nest( + "/api/admin", + api::admin::router().layer(Extension(RouterURI("/admin"))), + ) .nest( "/api/auth", auth::router().layer(Extension(RouterURI("/api/auth"))), diff --git a/src/types/extract.rs b/src/types/extract.rs index 4d92a3b..64a3e73 100644 --- a/src/types/extract.rs +++ b/src/types/extract.rs @@ -19,7 +19,7 @@ use crate::{ http::{ResponseCode, Result}, session::Session, user::User, - }, + }, admin, }; pub struct AuthorizedUser(pub User); @@ -53,6 +53,36 @@ where } } +pub struct AdminUser; + +#[async_trait] +impl FromRequestParts for AdminUser +where + S: Send + Sync, +{ + type Rejection = Response; + + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + let Ok(Some(cookies)) = Option::>::from_request_parts(parts, state).await else { + return Err(ResponseCode::Forbidden.text("No cookies provided")) + }; + + let Some(secret) = cookies.get("admin") else { + return Err(ResponseCode::Forbidden.text("No admin secret provided")) + }; + + println!("{}", secret); + + let check = admin::get_secret().await; + + if check != secret { + return Err(ResponseCode::Unauthorized.text("Auth token invalid")) + } + + Ok(Self) + } +} + pub struct Log; #[async_trait] diff --git a/src/types/post.rs b/src/types/post.rs index 90eada2..7397009 100644 --- a/src/types/post.rs +++ b/src/types/post.rs @@ -50,6 +50,14 @@ impl Post { Ok(posts) } + #[instrument()] + pub fn reterieve_all() -> Result> { + let Ok(posts) = database::posts::get_all_posts() else { + return Err(ResponseCode::InternalServerError.text("Failed to fetch posts")) + }; + Ok(posts) + } + #[instrument()] pub fn new(user_id: u64, content: String) -> Result { let Ok(post) = database::posts::add_post(user_id, &content) else { diff --git a/src/types/session.rs b/src/types/session.rs index e704ac7..a9073aa 100644 --- a/src/types/session.rs +++ b/src/types/session.rs @@ -21,6 +21,14 @@ impl Session { Ok(session) } + #[instrument()] + pub fn reterieve_all() -> Result> { + let Ok(sessions) = database::sessions::get_all_sessions() else { + return Err(ResponseCode::InternalServerError.text("Failed to fetch sessions")) + }; + Ok(sessions) + } + #[instrument()] pub fn new(user_id: u64) -> Result { let token: String = rand::thread_rng() diff --git a/src/types/user.rs b/src/types/user.rs index fcfbe91..2bffa52 100644 --- a/src/types/user.rs +++ b/src/types/user.rs @@ -68,6 +68,14 @@ impl User { Ok(user) } + #[instrument()] + pub fn reterieve_all() -> Result> { + let Ok(users) = database::users::get_all_users() else { + return Err(ResponseCode::InternalServerError.text("Failed to fetch users")) + }; + Ok(users) + } + #[instrument()] pub fn new(request: RegistrationRequet) -> Result { if Self::from_email(&request.email).is_ok() { -- cgit v1.2.3-freya