diff --git a/Cargo.lock b/Cargo.lock index ff8a4e6..5d364f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -141,9 +141,11 @@ checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" dependencies = [ "async-trait", "axum-core", + "axum-macros", "bitflags 1.3.2", "bytes", "futures-util", + "headers", "http", "http-body", "hyper", @@ -184,6 +186,18 @@ dependencies = [ "tracing", ] +[[package]] +name = "axum-macros" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdca6a10ecad987bda04e95606ef85a5417dcaac1a78455242d72e031e2b6b62" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "backtrace" version = "0.3.69" @@ -293,28 +307,6 @@ dependencies = [ "windows-targets", ] -[[package]] -name = "chrono-tz" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1369bc6b9e9a7dfdae2055f6ec151fe9c554a9d23d357c0237cee2e25eaabb7" -dependencies = [ - "chrono", - "chrono-tz-build", - "phf", -] - -[[package]] -name = "chrono-tz-build" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2f5ebdc942f57ed96d560a6d1a459bae5851102a25d5bf89dc04ae453e31ecf" -dependencies = [ - "parse-zoneinfo", - "phf", - "phf_codegen", -] - [[package]] name = "clap" version = "4.4.6" @@ -437,21 +429,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "deunicode" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71dbf1bf89c23e9cd1baf5e654f622872655f195b36588dc9dc38f7eda30758c" -dependencies = [ - "deunicode 1.4.1", -] - -[[package]] -name = "deunicode" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a1abaf4d861455be59f64fd2b55606cb151fce304ede7165f410243ce96bde6" - [[package]] name = "digest" version = "0.10.7" @@ -672,17 +649,6 @@ dependencies = [ "regex", ] -[[package]] -name = "globwalk" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" -dependencies = [ - "bitflags 1.3.2", - "ignore", - "walkdir", -] - [[package]] name = "handlebars" version = "4.4.0" @@ -718,6 +684,30 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "headers" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" +dependencies = [ + "base64", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + [[package]] name = "heck" version = "0.4.1" @@ -806,15 +796,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" -[[package]] -name = "humansize" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" -dependencies = [ - "libm", -] - [[package]] name = "hyper" version = "0.14.27" @@ -871,23 +852,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "ignore" -version = "0.4.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492" -dependencies = [ - "globset", - "lazy_static", - "log", - "memchr", - "regex", - "same-file", - "thread_local", - "walkdir", - "winapi-util", -] - [[package]] name = "imgboard" version = "0.1.0" @@ -898,10 +862,10 @@ dependencies = [ "dotenvy", "eyre", "handlebars", + "headers", "rust-embed", "serde", "sqlx", - "tera", "tokio", "tower-http", "tracing", @@ -1178,15 +1142,6 @@ dependencies = [ "windows-targets", ] -[[package]] -name = "parse-zoneinfo" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41" -dependencies = [ - "regex", -] - [[package]] name = "paste" version = "1.0.14" @@ -1253,44 +1208,6 @@ dependencies = [ "sha2", ] -[[package]] -name = "phf" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" -dependencies = [ - "phf_shared", -] - -[[package]] -name = "phf_codegen" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" -dependencies = [ - "phf_generator", - "phf_shared", -] - -[[package]] -name = "phf_generator" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" -dependencies = [ - "phf_shared", - "rand", -] - -[[package]] -name = "phf_shared" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" -dependencies = [ - "siphasher", -] - [[package]] name = "pin-project" version = "1.1.3" @@ -1703,12 +1620,6 @@ dependencies = [ "rand_core", ] -[[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - [[package]] name = "slab" version = "0.4.9" @@ -1718,15 +1629,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "slug" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3bc762e6a4b6c6fcaade73e77f9ebc6991b676f88bb2358bddb56560f073373" -dependencies = [ - "deunicode 0.4.5", -] - [[package]] name = "smallvec" version = "1.11.1" @@ -2055,28 +1957,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "tera" -version = "1.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "970dff17c11e884a4a09bc76e3a17ef71e01bb13447a11e85226e254fe6d10b8" -dependencies = [ - "chrono", - "chrono-tz", - "globwalk", - "humansize", - "lazy_static", - "percent-encoding", - "pest", - "pest_derive", - "rand", - "regex", - "serde", - "serde_json", - "slug", - "unic-segment", -] - [[package]] name = "thiserror" version = "1.0.49" @@ -2284,56 +2164,6 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" -[[package]] -name = "unic-char-property" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" -dependencies = [ - "unic-char-range", -] - -[[package]] -name = "unic-char-range" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" - -[[package]] -name = "unic-common" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" - -[[package]] -name = "unic-segment" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23" -dependencies = [ - "unic-ucd-segment", -] - -[[package]] -name = "unic-ucd-segment" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700" -dependencies = [ - "unic-char-property", - "unic-char-range", - "unic-ucd-version", -] - -[[package]] -name = "unic-ucd-version" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -dependencies = [ - "unic-common", -] - [[package]] name = "unicode-bidi" version = "0.3.13" diff --git a/Cargo.toml b/Cargo.toml index a1b363e..1c11ed2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,16 +6,16 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -axum = { version = "0.6.20", features = ["tracing"] } +axum = { version = "0.6.20", features = ["tracing", "macros", "headers"] } chrono = { version = "0.4.31", features = ["serde"] } clap = { version = "4.4.6", features = ["derive", "env"] } dotenvy = "0.15.7" eyre = "0.6.8" handlebars = { version = "4.4.0", features = ["rust-embed", "dir_source"] } +headers = "0.3.9" rust-embed = "8.0.0" serde = { version = "1.0.189", features = ["derive"] } sqlx = { version = "0.7.2", features = ["runtime-tokio", "tls-rustls", "postgres", "macros", "migrate", "chrono"] } -tera = "1.19.1" tokio = { version = "1.33.0", features = ["macros", "rt-multi-thread"] } tower-http = { version = "0.4.4", features = ["trace"] } tracing = "0.1.39" diff --git a/src/extractor.rs b/src/extractor.rs new file mode 100644 index 0000000..500c436 --- /dev/null +++ b/src/extractor.rs @@ -0,0 +1,49 @@ +use axum::{ + async_trait, + extract::{FromRef, FromRequestParts}, + http::{request::Parts, StatusCode}, + RequestPartsExt, TypedHeader, +}; +use headers::Cookie; +use tracing::error; + +use crate::{model::user::User, state::AppState}; + +pub struct ExtractUserToken(pub Option); + +#[async_trait] +impl FromRequestParts for ExtractUserToken +where + AppState: FromRef, + S: Send + Sync, +{ + type Rejection = (StatusCode, &'static str); + + async fn from_request_parts(parts: &mut Parts, state: &S) -> Result { + let state = match parts.extract_with_state::(state).await { + Ok(s) => s, + Err(why) => { + error!("Getting state: {}", why); + return Err((StatusCode::INTERNAL_SERVER_ERROR, "Internal server error")); + } + }; + + let cookie = match parts.extract::>().await { + Ok(cookie) => cookie, + Err(err) => { + error!("Getting cookie header: {}", err); + return Ok(ExtractUserToken(None)); + }, + }; + + let user = match cookie.get("imgboard-token") { + Some(_token) => { + // TODO: get from token + None + }, + None => None, + }; + + Ok(ExtractUserToken(user)) + } +} diff --git a/src/main.rs b/src/main.rs index 2641775..e595101 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ pub mod config; +pub mod extractor; pub mod model; pub mod pages; pub mod state; @@ -20,7 +21,7 @@ use crate::state::AppState; use crate::templates::handlebars; use crate::pages::index::index; -use crate::pages::user::get_user; +use crate::pages::user::{get_user, get_user_new}; #[tokio::main] async fn main() -> Result<()> { @@ -53,6 +54,7 @@ async fn main() -> Result<()> { let app = Router::new() .route("/", get(index)) .route("/users/:id", get(get_user)) + .route("/users/new", get(get_user_new)) .layer(TraceLayer::new_for_http()) .with_state(state); diff --git a/src/model/user.rs b/src/model/user.rs index c0ed2ca..1853682 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -27,7 +27,7 @@ impl Serialize for Role { S: Serializer, { serializer.serialize_str(match self { - Role::Viewer => "viwer", + Role::Viewer => "viewer", Role::Editor => "editor", Role::Manager => "manager", Role::Admin => "admin", diff --git a/src/pages/user/mod.rs b/src/pages/user/mod.rs index dfb2745..2370b73 100644 --- a/src/pages/user/mod.rs +++ b/src/pages/user/mod.rs @@ -1,14 +1,15 @@ -use std::sync::Arc; +use std::{collections::BTreeMap, sync::Arc}; use axum::{ extract::{Path, State}, response::IntoResponse, }; -use sqlx::query_as; +use sqlx::{query, query_as}; use tracing::error; use crate::{ - model::user::User, + extractor::ExtractUserToken, + model::user::{Role, User}, state::AppState, templates::{Page, PageData}, }; @@ -38,3 +39,50 @@ pub async fn get_user( .render("error.hbs", &PageData { user: Some(user) }), ) } + +pub async fn get_user_new( + State(state): State>, + ExtractUserToken(user): ExtractUserToken, +) -> impl IntoResponse { + if user.is_some() + && match user.as_ref().unwrap().role { + Role::Viewer => false, + Role::Editor => false, + Role::Manager => false, + Role::Admin => true, + } + { + // Admins can create accounts + // Handlebars expects *some* value when rendering a template, so just pass an empty BTreeMap + let map = BTreeMap::<&str, &str>::new(); + return Page::new(state.hbs.render("new_user.hbs", &map)); + } else if user.is_some() { + // No permissions to create account + return Page::new( + state + .hbs + .render::("error.hbs", &Default::default()), + ); + } + // Else, let the user create a new account if no accounts exist yet + + let count = match query!(r#"SELECT COUNT(id) FROM users"#) + .fetch_one(&state.pool) + .await + { + Ok(r) => r.count.unwrap_or(0), + Err(why) => { + error!("Getting user count: {}", why); + return Page::new( + state + .hbs + .render::("error.hbs", &Default::default()), + ); + } + }; + + let mut map = BTreeMap::new(); + map.insert("count", count); + + Page::new(state.hbs.render("new_user.hbs", &map)) +} diff --git a/src/state.rs b/src/state.rs index 455eeff..c85870b 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,6 +1,21 @@ +use axum::{extract::{FromRef, FromRequestParts}, async_trait, http::request::Parts}; use sqlx::{postgres::Postgres, Pool}; +#[derive(Clone, FromRef)] pub struct AppState { pub pool: Pool, pub hbs: handlebars::Handlebars<'static>, } + +#[async_trait] +impl FromRequestParts for AppState +where + Self: FromRef, + S: Send + Sync, +{ + type Rejection = &'static str; + + async fn from_request_parts(_parts: &mut Parts, state: &S) -> Result { + Ok(Self::from_ref(state)) + } +} diff --git a/templates/new_user.hbs b/templates/new_user.hbs new file mode 100644 index 0000000..ab37de8 --- /dev/null +++ b/templates/new_user.hbs @@ -0,0 +1,10 @@ +{{#*inline "page"}} +

New user

+
+ + + + +
+{{/inline}} +{{> root.hbs}}