diff options
author | Tyler Murphy <tylermurphy534@gmail.com> | 2023-01-29 19:28:48 -0500 |
---|---|---|
committer | Tyler Murphy <tylermurphy534@gmail.com> | 2023-01-29 19:28:48 -0500 |
commit | ac58a612a3fe928793b77c592551fdd962b69064 (patch) | |
tree | c746d9325a88447e3149891a2435bcb1f3ece67a /src | |
parent | no mass rerendering html plus logging fix (diff) | |
download | xssbook-ac58a612a3fe928793b77c592551fdd962b69064.tar.gz xssbook-ac58a612a3fe928793b77c592551fdd962b69064.tar.bz2 xssbook-ac58a612a3fe928793b77c592551fdd962b69064.zip |
admin page
Diffstat (limited to 'src')
-rw-r--r-- | src/admin.rs | 125 | ||||
-rw-r--r-- | src/api/admin.rs | 83 | ||||
-rw-r--r-- | src/api/mod.rs | 1 | ||||
-rw-r--r-- | src/api/pages.rs | 5 | ||||
-rw-r--r-- | src/console.rs | 12 | ||||
-rw-r--r-- | src/database/mod.rs | 9 | ||||
-rw-r--r-- | src/database/posts.rs | 12 | ||||
-rw-r--r-- | src/database/sessions.rs | 14 | ||||
-rw-r--r-- | src/database/users.rs | 12 | ||||
-rw-r--r-- | src/main.rs | 5 | ||||
-rw-r--r-- | src/types/extract.rs | 32 | ||||
-rw-r--r-- | src/types/post.rs | 8 | ||||
-rw-r--r-- | src/types/session.rs | 8 | ||||
-rw-r--r-- | src/types/user.rs | 8 |
14 files changed, 331 insertions, 3 deletions
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<String> = 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#" + <tr> + <th>User ID</th> + <th>First Name</th> + <th>Last Name</th> + <th>Email</th> + <th>Password</th> + <th>Gender</th> + <th>Date</th> + <th>Day</th> + <th>Month</th> + <th>Year</th> + </tr> + "#.to_string(); + + for user in users { + html.push_str( + &format!("<tr><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td></tr>", + 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#" + <tr> + <th>Post ID</th> + <th>User ID</th> + <th>Content</th> + <th>Likes</th> + <th>Comments</th> + <th>Date</th> + </tr> + "#.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!("<tr><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td></tr>", + 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#" + <tr> + <th>User ID</th> + <th>Token</th> + </tr> + "#.to_string(); + + for session in sessions { + html.push_str( + &format!("<tr><td>{}</td><td>{}</td></tr>", + 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<AdminAuthRequest>) -> 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<QueryRequest>) -> 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<AdminUser>, _: 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!("<div><span class='ip'>{}</span> <span class='method' style='color: {};'>{}</span> <span class='path'>{}{}</span> <span class='body'>{}</span></div>", ip, color, self.method, self.path, self.uri, self.body) + format!("<div><span class='ip'>{}</span> <span class='method' style='color: {};'>{}</span> <span class='path'>{}{}</span> <span class='body'>{}</span></div>", + 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<usize, rusqlite::Error> { + 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 @@ -78,6 +78,18 @@ pub fn get_post_page(page: u64) -> Result<Vec<Post>, rusqlite::Error> { } #[instrument()] +pub fn get_all_posts() -> Result<Vec<Post>, 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<Vec<Post>, rusqlite::Error> { tracing::trace!("Retrieving users posts"); let conn = database::connect()?; 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 @@ -33,6 +33,20 @@ pub fn get_session(token: &str) -> Result<Option<Session>, rusqlite::Error> { } #[instrument()] +pub fn get_all_sessions() -> Result<Vec<Session>, 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<dyn std::error::Error>> { tracing::trace!("Setting new session"); let conn = database::connect()?; 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 @@ -118,6 +118,18 @@ pub fn get_user_page(page: u64, hide_password: bool) -> Result<Vec<User>, rusqli } #[instrument()] +pub fn get_all_users() -> Result<Vec<User>, 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<User, rusqlite::Error> { tracing::trace!("Adding new user"); let date = u64::try_from( 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<B>(req: Request<B>, next: Next<B>) -> Response where @@ -77,6 +78,10 @@ async fn main() { .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<S> FromRequestParts<S> for AdminUser +where + S: Send + Sync, +{ + type Rejection = Response; + + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self> { + let Ok(Some(cookies)) = Option::<TypedHeader<Cookie>>::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 @@ -51,6 +51,14 @@ impl Post { } #[instrument()] + pub fn reterieve_all() -> Result<Vec<Self>> { + 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<Self> { let Ok(post) = database::posts::add_post(user_id, &content) else { tracing::error!("Failed to create post"); 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 @@ -22,6 +22,14 @@ impl Session { } #[instrument()] + pub fn reterieve_all() -> Result<Vec<Self>> { + 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<Self> { let token: String = rand::thread_rng() .sample_iter(&Alphanumeric) 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 @@ -69,6 +69,14 @@ impl User { } #[instrument()] + pub fn reterieve_all() -> Result<Vec<Self>> { + 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<Self> { if Self::from_email(&request.email).is_ok() { return Err(ResponseCode::BadRequest |