admin page
This commit is contained in:
parent
7805c730e8
commit
ac58a612a3
22 changed files with 585 additions and 4 deletions
|
@ -8,6 +8,7 @@ now with xssbook you can run as much stallman disapprovement as you want
|
||||||
- all inputs on the site are unfiltered
|
- all inputs on the site are unfiltered
|
||||||
- api calls dont care what you send them as long as they are valid strings
|
- api calls dont care what you send them as long as they are valid strings
|
||||||
- /console page to see everyones amazing api calls
|
- /console page to see everyones amazing api calls
|
||||||
|
- /admin page for adnim things
|
||||||
|
|
||||||
**installation**
|
**installation**
|
||||||
|
|
||||||
|
@ -17,6 +18,8 @@ The project is written in rust, so you can build it by running
|
||||||
|
|
||||||
Next, make sure where you are runing the binary from, that you copy the sources public folder to the same directory. The public folder is needed to server html, css, js, and font files.
|
Next, make sure where you are runing the binary from, that you copy the sources public folder to the same directory. The public folder is needed to server html, css, js, and font files.
|
||||||
|
|
||||||
|
Next, the /admin page is protected by a set secret. By default this is set to admin, but you should change it by setting the `SECRET` environment variable.
|
||||||
|
|
||||||
Finally, the site runs on port `8080`, so its recommended you put it behind a reverse proxy, or you could use a docker container and remap the outsite port (see below).
|
Finally, the site runs on port `8080`, so its recommended you put it behind a reverse proxy, or you could use a docker container and remap the outsite port (see below).
|
||||||
|
|
||||||
**docker**
|
**docker**
|
||||||
|
|
|
@ -4,6 +4,8 @@ services:
|
||||||
ritlug-discord-bot:
|
ritlug-discord-bot:
|
||||||
container_name: xssbook
|
container_name: xssbook
|
||||||
image: xssbook
|
image: xssbook
|
||||||
|
environment:
|
||||||
|
- SECRET="admin"
|
||||||
ports:
|
ports:
|
||||||
- 8080:8080
|
- 8080:8080
|
||||||
volumes:
|
volumes:
|
||||||
|
|
32
public/admin.html
Normal file
32
public/admin.html
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="stylesheet" href="/css/main.css">
|
||||||
|
<link rel="stylesheet" href="/css/header.css">
|
||||||
|
<link rel="stylesheet" href="/css/admin.css">
|
||||||
|
<title>XSSBook - Admin Panel</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script src="/js/api.js"></script>
|
||||||
|
<script src="/js/admin.js"></script>
|
||||||
|
<div id="header">
|
||||||
|
<span class="logo"><a href="/">xssbook</a></span>
|
||||||
|
</div>
|
||||||
|
<div id="login" class="hidden">
|
||||||
|
<span class="gtext desc">Admin Login</span>
|
||||||
|
<form autocomplete="off" onsubmit="auth(event)">
|
||||||
|
<input autocomplete="new-password" type="password" name="adminpassword" id="adminpassword" placeholder="Login Secret">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div id="admin" class="hidden">
|
||||||
|
<div id="queryinput">
|
||||||
|
<input type="text" name="query" id="query" placeholder="SQL Query">
|
||||||
|
<button class="submit" onclick="submit()">Submit</button>
|
||||||
|
<button class="view" onclick="posts()">View Posts</button>
|
||||||
|
<button class="view" onclick="users()">View Users</button>
|
||||||
|
<button class="view" onclick="sessions()">View Sessions</button>
|
||||||
|
</div>
|
||||||
|
<table id="table"></table>
|
||||||
|
</div>
|
||||||
|
</body>
|
133
public/css/admin.css
Normal file
133
public/css/admin.css
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: #181818;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header {
|
||||||
|
background-color: #242424;
|
||||||
|
}
|
||||||
|
|
||||||
|
#login {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#error .logo {
|
||||||
|
font-size: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desc {
|
||||||
|
font-size: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
flex: 0;
|
||||||
|
background-color: #242424;
|
||||||
|
color: white;
|
||||||
|
border: 1px solid #606770;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#admin {
|
||||||
|
margin: 1.75em;
|
||||||
|
margin-top: 5em;
|
||||||
|
width: calc(100vw - 1.75em * 2);
|
||||||
|
height: calc(100vh - 5em - 1.75em);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
#queryinput {
|
||||||
|
display: flexbox;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#queryinput #query {
|
||||||
|
width: 50em;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#queryinput .submit, .view {
|
||||||
|
all: unset;
|
||||||
|
font-family: sfpro;
|
||||||
|
margin: 0;
|
||||||
|
padding: 10px 30px;
|
||||||
|
background-color: #3bd16f;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 18px;
|
||||||
|
margin-left: 2em;
|
||||||
|
cursor: pointer;
|
||||||
|
border: 1px solid #606770;
|
||||||
|
}
|
||||||
|
|
||||||
|
#queryinput .submit:active {
|
||||||
|
background-color: #30ab5a;
|
||||||
|
}
|
||||||
|
|
||||||
|
#queryinput .view {
|
||||||
|
background-color: #242424;
|
||||||
|
color: #707882;
|
||||||
|
border: 1px solid #606770;
|
||||||
|
}
|
||||||
|
|
||||||
|
#queryinput .view:active {
|
||||||
|
background-color: #181818;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
margin-top: 3em;
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
font-family: sfpro;
|
||||||
|
color: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background-color: #242424;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
font-family: sfprobold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bool {
|
||||||
|
color: aqua;
|
||||||
|
}
|
||||||
|
|
||||||
|
.null {
|
||||||
|
color: blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.number {
|
||||||
|
color: yellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.string {
|
||||||
|
color: #4ae04a
|
||||||
|
}
|
||||||
|
|
||||||
|
.key .string {
|
||||||
|
color: white;
|
||||||
|
}
|
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 37 KiB |
59
public/js/admin.js
Normal file
59
public/js/admin.js
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
async function auth(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const text = event.target.elements.adminpassword.value;
|
||||||
|
const response = await adminauth(text);
|
||||||
|
if (response.status !== 200) {
|
||||||
|
alert(response.msg)
|
||||||
|
} else {
|
||||||
|
document.getElementById("admin").classList.remove("hidden")
|
||||||
|
document.getElementById("login").classList.add("hidden")
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submit() {
|
||||||
|
let text = document.getElementById("query").value
|
||||||
|
let response = await adminquery(text)
|
||||||
|
alert(response.msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function posts() {
|
||||||
|
let response = await adminposts();
|
||||||
|
if (response.status !== 200) {
|
||||||
|
alert(response.msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let table = document.getElementById("table")
|
||||||
|
table.innerHTML = response.msg
|
||||||
|
}
|
||||||
|
|
||||||
|
async function users() {
|
||||||
|
let response = await adminusers();
|
||||||
|
if (response.status !== 200) {
|
||||||
|
alert(response.msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let table = document.getElementById("table")
|
||||||
|
table.innerHTML = response.msg
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sessions() {
|
||||||
|
let response = await adminsessions();
|
||||||
|
if (response.status !== 200) {
|
||||||
|
alert(response.msg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let table = document.getElementById("table")
|
||||||
|
table.innerHTML = response.msg
|
||||||
|
}
|
||||||
|
|
||||||
|
async function load() {
|
||||||
|
let check = await admincheck();
|
||||||
|
if (check.msg === "true") {
|
||||||
|
document.getElementById("admin").classList.remove("hidden")
|
||||||
|
} else {
|
||||||
|
document.getElementById("login").classList.remove("hidden")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
load()
|
|
@ -64,4 +64,28 @@ const postlike = async (post_id, state) => {
|
||||||
|
|
||||||
const createpost = async (content) => {
|
const createpost = async (content) => {
|
||||||
return await request('/posts/create', {content})
|
return await request('/posts/create', {content})
|
||||||
|
}
|
||||||
|
|
||||||
|
const adminauth = async (secret) => {
|
||||||
|
return await request('/admin/auth', {secret})
|
||||||
|
}
|
||||||
|
|
||||||
|
const admincheck = async () => {
|
||||||
|
return await request('/admin/check', {})
|
||||||
|
}
|
||||||
|
|
||||||
|
const adminquery = async (query) => {
|
||||||
|
return await request('/admin/query', {query})
|
||||||
|
}
|
||||||
|
|
||||||
|
const adminposts = async () => {
|
||||||
|
return await request('/admin/posts', {})
|
||||||
|
}
|
||||||
|
|
||||||
|
const adminusers = async () => {
|
||||||
|
return await request('/admin/users', {})
|
||||||
|
}
|
||||||
|
|
||||||
|
const adminsessions = async () => {
|
||||||
|
return await request('/admin/sessions', {})
|
||||||
}
|
}
|
|
@ -164,7 +164,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<footer>
|
<footer>
|
||||||
Metashit © 2023 | This website does not care about you
|
Tyler Murphy © 2023 | tylerm.dev
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
125
src/admin.rs
Normal file
125
src/admin.rs
Normal file
|
@ -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)
|
||||||
|
}
|
83
src/api/admin.rs
Normal file
83
src/api/admin.rs
Normal file
|
@ -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))
|
||||||
|
}
|
|
@ -2,3 +2,4 @@ pub mod auth;
|
||||||
pub mod pages;
|
pub mod pages;
|
||||||
pub mod posts;
|
pub mod posts;
|
||||||
pub mod users;
|
pub mod users;
|
||||||
|
pub mod admin;
|
|
@ -53,6 +53,10 @@ async fn console() -> Response {
|
||||||
console::generate().await
|
console::generate().await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn admin() -> Response {
|
||||||
|
ResponseCode::Success.file("/admin.html").await
|
||||||
|
}
|
||||||
|
|
||||||
async fn wordpress(_: Log) -> Response {
|
async fn wordpress(_: Log) -> Response {
|
||||||
ResponseCode::ImATeapot.text("Hello i am a teapot owo")
|
ResponseCode::ImATeapot.text("Hello i am a teapot owo")
|
||||||
}
|
}
|
||||||
|
@ -66,4 +70,5 @@ pub fn router() -> Router {
|
||||||
.route("/profile", get(profile))
|
.route("/profile", get(profile))
|
||||||
.route("/console", get(console))
|
.route("/console", get(console))
|
||||||
.route("/wp-admin", get(wordpress))
|
.route("/wp-admin", get(wordpress))
|
||||||
|
.route("/admin", get(admin))
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,8 @@ impl ToString for LogMessage {
|
||||||
Method::OPTIONS => "#423fe0",
|
Method::OPTIONS => "#423fe0",
|
||||||
_ => "white",
|
_ => "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() {
|
if body.is_empty() {
|
||||||
return String::new();
|
return String::new();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use tracing::instrument;
|
||||||
|
|
||||||
pub mod posts;
|
pub mod posts;
|
||||||
pub mod sessions;
|
pub mod sessions;
|
||||||
pub mod users;
|
pub mod users;
|
||||||
|
@ -12,3 +14,10 @@ pub fn init() -> Result<(), rusqlite::Error> {
|
||||||
sessions::init()?;
|
sessions::init()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[instrument()]
|
||||||
|
pub fn query(query: String) -> Result<usize, rusqlite::Error> {
|
||||||
|
tracing::trace!("Running custom query");
|
||||||
|
let conn = connect()?;
|
||||||
|
conn.execute(&query, [])
|
||||||
|
}
|
|
@ -77,6 +77,18 @@ pub fn get_post_page(page: u64) -> Result<Vec<Post>, rusqlite::Error> {
|
||||||
Ok(row.into_iter().flatten().collect())
|
Ok(row.into_iter().flatten().collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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()]
|
#[instrument()]
|
||||||
pub fn get_users_posts(user_id: u64) -> Result<Vec<Post>, rusqlite::Error> {
|
pub fn get_users_posts(user_id: u64) -> Result<Vec<Post>, rusqlite::Error> {
|
||||||
tracing::trace!("Retrieving users posts");
|
tracing::trace!("Retrieving users posts");
|
||||||
|
|
|
@ -32,6 +32,20 @@ pub fn get_session(token: &str) -> Result<Option<Session>, rusqlite::Error> {
|
||||||
Ok(row)
|
Ok(row)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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()]
|
#[instrument()]
|
||||||
pub fn set_session(user_id: u64, token: &str) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn set_session(user_id: u64, token: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
tracing::trace!("Setting new session");
|
tracing::trace!("Setting new session");
|
||||||
|
|
|
@ -117,6 +117,18 @@ pub fn get_user_page(page: u64, hide_password: bool) -> Result<Vec<User>, rusqli
|
||||||
Ok(row.into_iter().flatten().collect())
|
Ok(row.into_iter().flatten().collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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()]
|
#[instrument()]
|
||||||
pub fn add_user(request: RegistrationRequet) -> Result<User, rusqlite::Error> {
|
pub fn add_user(request: RegistrationRequet) -> Result<User, rusqlite::Error> {
|
||||||
tracing::trace!("Adding new user");
|
tracing::trace!("Adding new user");
|
||||||
|
|
|
@ -23,6 +23,7 @@ mod api;
|
||||||
mod console;
|
mod console;
|
||||||
mod database;
|
mod database;
|
||||||
mod types;
|
mod types;
|
||||||
|
mod admin;
|
||||||
|
|
||||||
async fn serve<B>(req: Request<B>, next: Next<B>) -> Response
|
async fn serve<B>(req: Request<B>, next: Next<B>) -> Response
|
||||||
where
|
where
|
||||||
|
@ -76,6 +77,10 @@ async fn main() {
|
||||||
.layer(middleware::from_fn(log))
|
.layer(middleware::from_fn(log))
|
||||||
.layer(middleware::from_fn(serve))
|
.layer(middleware::from_fn(serve))
|
||||||
.nest("/", pages::router())
|
.nest("/", pages::router())
|
||||||
|
.nest(
|
||||||
|
"/api/admin",
|
||||||
|
api::admin::router().layer(Extension(RouterURI("/admin"))),
|
||||||
|
)
|
||||||
.nest(
|
.nest(
|
||||||
"/api/auth",
|
"/api/auth",
|
||||||
auth::router().layer(Extension(RouterURI("/api/auth"))),
|
auth::router().layer(Extension(RouterURI("/api/auth"))),
|
||||||
|
|
|
@ -19,7 +19,7 @@ use crate::{
|
||||||
http::{ResponseCode, Result},
|
http::{ResponseCode, Result},
|
||||||
session::Session,
|
session::Session,
|
||||||
user::User,
|
user::User,
|
||||||
},
|
}, admin,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct AuthorizedUser(pub User);
|
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;
|
pub struct Log;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
|
|
|
@ -50,6 +50,14 @@ impl Post {
|
||||||
Ok(posts)
|
Ok(posts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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()]
|
#[instrument()]
|
||||||
pub fn new(user_id: u64, content: String) -> Result<Self> {
|
pub fn new(user_id: u64, content: String) -> Result<Self> {
|
||||||
let Ok(post) = database::posts::add_post(user_id, &content) else {
|
let Ok(post) = database::posts::add_post(user_id, &content) else {
|
||||||
|
|
|
@ -21,6 +21,14 @@ impl Session {
|
||||||
Ok(session)
|
Ok(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()]
|
#[instrument()]
|
||||||
pub fn new(user_id: u64) -> Result<Self> {
|
pub fn new(user_id: u64) -> Result<Self> {
|
||||||
let token: String = rand::thread_rng()
|
let token: String = rand::thread_rng()
|
||||||
|
|
|
@ -68,6 +68,14 @@ impl User {
|
||||||
Ok(user)
|
Ok(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()]
|
#[instrument()]
|
||||||
pub fn new(request: RegistrationRequet) -> Result<Self> {
|
pub fn new(request: RegistrationRequet) -> Result<Self> {
|
||||||
if Self::from_email(&request.email).is_ok() {
|
if Self::from_email(&request.email).is_ok() {
|
||||||
|
|
Loading…
Reference in a new issue