summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/admin.rs125
-rw-r--r--src/api/admin.rs83
-rw-r--r--src/api/mod.rs1
-rw-r--r--src/api/pages.rs5
-rw-r--r--src/console.rs12
-rw-r--r--src/database/mod.rs9
-rw-r--r--src/database/posts.rs12
-rw-r--r--src/database/sessions.rs14
-rw-r--r--src/database/users.rs12
-rw-r--r--src/main.rs5
-rw-r--r--src/types/extract.rs32
-rw-r--r--src/types/post.rs8
-rw-r--r--src/types/session.rs8
-rw-r--r--src/types/user.rs8
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("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
+}
+
+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