diff --git a/Cargo.lock b/Cargo.lock index b102f95..0929654 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,41 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "aho-corasick" version = "1.1.2" @@ -32,6 +67,18 @@ version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +[[package]] +name = "argon2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ba4cac0a46bc1d2912652a751c47f2a9f3a7fe89bcae2275d418f5270402f9" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures", + "password-hash", +] + [[package]] name = "async-trait" version = "0.1.74" @@ -99,6 +146,26 @@ dependencies = [ "tower-service", ] +[[package]] +name = "axum_csrf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c0ccdf64283e19f1ee0c4180f216ba28eedef4696dc3ee947e1dc01b3486960" +dependencies = [ + "async-trait", + "axum-core", + "base64ct", + "cookie", + "hmac", + "http", + "rand", + "sha2", + "thiserror", + "time", + "tower-layer", + "tower-service", +] + [[package]] name = "backtrace" version = "0.3.69" @@ -120,6 +187,12 @@ version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + [[package]] name = "bitflags" version = "1.3.2" @@ -132,6 +205,15 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -162,6 +244,33 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "cookie" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cd91cf61412820176e137621345ee43b3f4423e589e7ae4e50d601d93e35ef8" +dependencies = [ + "aes-gcm", + "base64", + "hmac", + "percent-encoding", + "rand", + "sha2", + "subtle", + "time", + "version_check", +] + [[package]] name = "cpufeatures" version = "0.2.11" @@ -197,9 +306,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core", "typenum", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "deadpool" version = "0.10.0" @@ -261,6 +380,7 @@ dependencies = [ "diesel_derives", "libsqlite3-sys", "time", + "uuid", ] [[package]] @@ -303,6 +423,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -396,6 +517,27 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "ghash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "gimli" version = "0.28.0" @@ -438,6 +580,15 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "http" version = "0.2.10" @@ -531,6 +682,15 @@ dependencies = [ "libc", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "itoa" version = "1.0.9" @@ -600,7 +760,11 @@ name = "lyrs" version = "0.1.0" dependencies = [ "anyhow", + "argon2", "axum", + "axum_csrf", + "base64", + "cookie", "deadpool", "deadpool-diesel", "diesel", @@ -608,6 +772,7 @@ dependencies = [ "maud", "notify", "serde", + "thiserror", "tokio", "tower-http", "tower-livereload", @@ -770,6 +935,12 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "overload" version = "0.1.1" @@ -799,6 +970,17 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + [[package]] name = "percent-encoding" version = "2.3.0" @@ -843,12 +1025,30 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +[[package]] +name = "polyval" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -891,6 +1091,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "redox_syscall" version = "0.3.5" @@ -1059,6 +1289,17 @@ dependencies = [ "digest", ] +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -1103,6 +1344,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + [[package]] name = "syn" version = "1.0.109" @@ -1131,6 +1378,26 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "thiserror" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + [[package]] name = "thread_local" version = "1.1.7" @@ -1403,6 +1670,22 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "uuid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc" + [[package]] name = "valuable" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index f15e4c0..8b448fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,16 +5,30 @@ edition = "2021" [dependencies] anyhow = "1.0.75" + +# web axum = { version = "0.6.20", features = ["headers"] } -deadpool = "0.10.0" -deadpool-diesel = { version = "0.5.0", features = ["rt_tokio_1", "sqlite", "tracing", "serde"] } -diesel = { version = "2.1.3", features = ["sqlite"] } -diesel_migrations = { version = "2.1.0", features = ["sqlite"] } +axum_csrf = { version = "0.8.0", features = ["layer"] } +base64 = "0.21.5" +cookie = "0.18.0" maud = "0.25.0" -notify = "6.1.1" serde = { version = "1.0.192", features = ["derive"] } tokio = { version = "1.34.0", features = ["full", "macros", "rt-multi-thread"] } tower-http = { version = "0.4.4", features = ["fs"] } -tower-livereload = "0.8.2" + +# instrumentation tracing = "0.1.40" tracing-subscriber = { version = "0.3.17", features = ["env-filter"] } + +# database +deadpool = "0.10.0" +deadpool-diesel = { version = "0.5.0", features = ["rt_tokio_1", "sqlite", "tracing", "serde"] } +diesel = { version = "2.1.3", features = ["sqlite", "uuid"] } +diesel_migrations = { version = "2.1.0", features = ["sqlite"] } + +# fancy during-development stuff +notify = "6.1.1" +tower-livereload = "0.8.2" +argon2 = { version = "0.5.2", features = ["std"] } +thiserror = "1.0.50" + diff --git a/assets/styles.css b/assets/styles.css index a5b1c3d..4257baa 100644 --- a/assets/styles.css +++ b/assets/styles.css @@ -98,6 +98,7 @@ textarea:not([rows]) { :root { --font: monospace; + --br: 3px; /* catppuccin latte */ --Rosewater: #dc8a78; @@ -171,6 +172,32 @@ ol { padding-left: 2ex; } +input { + padding: 1ex 2ex; + background-color: var(--Surface0); + border: solid 1px var(--Surface2); + border-radius: var(--br); + color: inherit; +} + +input:hover { + border: solid 1px var(--Overlay0); + /* background-color: var(--Surface0); */ +} + +button { + cursor: pointer; + padding: 1ex 2ex; + background-color: var(--Surface0); + border: 0; + border-radius: var(--br); + color: inherit; +} + +button:hover { + background-color: var(--Surface1); +} + html { background-color: var(--bg); color: var(--text); @@ -197,7 +224,7 @@ body>header a:not([class]) { display: flex; align-items: center; padding: 1ex 2ex; - transition: color 0.1s ease-out, background-color 0.1s ease-out; + /* transition: color 0.1s ease-out, background-color 0.1s ease-out; */ text-decoration: none; color: var(--primary); gap: 0.5ex; @@ -214,7 +241,7 @@ main.prose { text-decoration: none; color: currentColor; box-shadow: 0 -1px 1px var(--Mantle); - border-radius: 2px; + border-radius: var(--br); } body>header a:hover { @@ -225,4 +252,16 @@ body>header a:hover { .bg-primary { background-color: var(--primary); color: var(--bg); +} + +form { + display: flex; + flex-direction: column; + gap: 2ex; +} + +form>label { + display: flex; + flex-direction: column; + gap: 0.5ex; } \ No newline at end of file diff --git a/flake.nix b/flake.nix index fef5085..400ce22 100644 --- a/flake.nix +++ b/flake.nix @@ -59,6 +59,7 @@ RUST_SRC_PATH = rustPlatform.rustLibSrc; shellHook = '' export DATABASE_URL="sqlite://./data/lyrs.sqlitedb"; + export COOKIE_KEY="2z49_8yfKUkoTOo0cjzzjwufCfhKvfOIc1CGleuTXC5zRqY4U0Xhkd34ipREQN5iHRH62tt5O7y6U5mmFBH3MA" ''; }; }); diff --git a/migrations/2023-11-11-073646_create_users/up.sql b/migrations/2023-11-11-073646_create_users/up.sql index 7db81c8..3b79939 100644 --- a/migrations/2023-11-11-073646_create_users/up.sql +++ b/migrations/2023-11-11-073646_create_users/up.sql @@ -1,5 +1,7 @@ create table users ( id text primary key, + username text name text, - hashed_password binary -) + password_digest binary +); +create unique index users_username on users(username); diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..fb3678e --- /dev/null +++ b/src/error.rs @@ -0,0 +1,25 @@ +use axum::{ + http::StatusCode, + response::{IntoResponse, Response}, +}; + +pub struct AppError(anyhow::Error); + +impl IntoResponse for AppError { + fn into_response(self) -> Response { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Something went wrong: {}", self.0), + ) + .into_response() + } +} + +impl From for AppError +where + E: Into, +{ + fn from(err: E) -> Self { + Self(err.into()) + } +} diff --git a/src/main.rs b/src/main.rs index 02262c3..16d4efa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,8 +5,10 @@ mod app; mod database; +mod error; mod feather_icons; mod instrumentation; +mod models; mod partials; mod router; mod schema; diff --git a/src/models.rs b/src/models.rs new file mode 100644 index 0000000..e411e12 --- /dev/null +++ b/src/models.rs @@ -0,0 +1,23 @@ +use diesel::prelude::*; + +#[derive(Queryable, Selectable)] +#[diesel(table_name = crate::schema::users)] +#[diesel(check_for_backend(diesel::sqlite::Sqlite))] +pub struct User { + // pub id: Text, + // pub username: sql_types::Text, + // pub name: sql_types::Text, + // pub password_digest: sql_types::Binary, + pub id: Vec, + pub username: String, + pub name: Option, + pub password_digest: Vec, +} + +#[derive(Insertable)] +#[diesel(table_name = crate::schema::users)] +pub struct NewUser<'a> { + pub username: &'a str, + pub name: Option<&'a str>, + pub password_digest: &'a [u8], +} diff --git a/src/router.rs b/src/router.rs index cee8719..c220127 100644 --- a/src/router.rs +++ b/src/router.rs @@ -1,12 +1,26 @@ -use std::{path::Path, sync::Arc}; +use std::{env, path::Path, sync::Arc}; -use axum::{http::StatusCode, routing::get, Router}; +use argon2::{ + password_hash::{rand_core::OsRng, SaltString}, + Argon2, PasswordHasher, +}; +use axum::{ + http::StatusCode, + response::{Html, IntoResponse, Response}, + routing::get, + Form, Router, +}; +use axum_csrf::{CsrfConfig, CsrfLayer, CsrfToken}; +use maud::html; use notify::Watcher; +use serde::Deserialize; use tower_http::services::ServeDir; use tower_livereload::LiveReloadLayer; use tracing::instrument; -use crate::{state::State, views}; +use base64::prelude::*; + +use crate::{error::AppError, models::NewUser, state::State, views}; #[instrument] pub async fn new() -> Result { @@ -23,14 +37,73 @@ pub async fn new() -> Result { let mut watcher = notify::recommended_watcher(move |_| reloader.reload())?; watcher.watch(Path::new("assets"), notify::RecursiveMode::Recursive)?; + let cookie_key_bytes: Vec = BASE64_URL_SAFE_NO_PAD + .decode(env::var("COOKIE_KEY").expect("COOKIE_KEY not set")) + .expect("COOKIE_KEY not base64 URL-safe encoded"); + let cookie_key = cookie::Key::from(&cookie_key_bytes); + let csrf_config = CsrfConfig::default().with_key(Some(cookie_key)); + let router = Router::new() .fallback(|| async { (StatusCode::NOT_FOUND, "404 page not found") }) .nest("/app", app_router) .nest_service("/assets", assets_dir) .route("/", get(views::index)) - .route("/register", get(views::register)) + .route("/register", get(views::register).post(register)) .with_state(state) + .layer(CsrfLayer::new(csrf_config)) .layer(live_reload_layer); Ok(router) } + +#[derive(Deserialize)] +struct Register { + authenticity_token: String, + username: String, + password: String, +} + +impl<'a> TryInto> for Register { + type Error = (); + + fn try_into(self) -> Result, Self::Error> { + todo!() + } +} + +async fn register( + csrf_token: CsrfToken, + Form(register): Form, +) -> Result { + // TODO: https://docs.rs/axum_csrf/latest/axum_csrf/#prevent-post-replay-attacks-with-csrf + + let v = csrf_token.verify(®ister.authenticity_token); + println!("{:?} {:?}", register.authenticity_token, v); + if v.is_err() { + return Ok(( + StatusCode::BAD_REQUEST, + Html(html! { "invalid request" }.into_string()), + )); + } + + let salt = SaltString::generate(&mut OsRng); + let argon2 = Argon2::default(); + let password_digest = argon2.hash_password(register.password.as_bytes(), &salt)?; + + let new_user = NewUser { + username: ®ister.username, + name: None, + password_digest: password_digest.to_string().as_bytes(), + }; + + Ok(( + StatusCode::CREATED, + Html( + html! { + h1 { (register.username) } + h1 { (register.password) } + } + .into_string(), + ), + )) +} diff --git a/src/schema.rs b/src/schema.rs index 439e88c..5ccddd5 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -2,8 +2,9 @@ diesel::table! { users (id) { - id -> Nullable, + id -> Binary, + username -> Text, name -> Nullable, - hashed_password -> Nullable, + password_digest -> Binary, } } diff --git a/src/state.rs b/src/state.rs index 530143e..7cae4c1 100644 --- a/src/state.rs +++ b/src/state.rs @@ -10,6 +10,7 @@ impl State { pub async fn new() -> Result { let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); let database = database::Database::new(database_url).await?; + Ok(State { database }) } } diff --git a/src/views.rs b/src/views.rs index 2319d47..4defefb 100644 --- a/src/views.rs +++ b/src/views.rs @@ -1,4 +1,5 @@ -use axum::response::Html; +use axum::response::{Html, IntoResponse}; +use axum_csrf::CsrfToken; use maud::html; use tracing::instrument; @@ -21,7 +22,7 @@ pub async fn index() -> Html { li { "Lightweight and fast" } } section class="flex gap" { - button hx-get="/register" hx-target="body>main" hx-push-url="true" class="button bg-primary" { "Try now" } + a href="/register" class="button bg-primary" { "Try now" } a class="button" href="/login" { "Login" } } } @@ -31,27 +32,61 @@ pub async fn index() -> Html { ) } -#[instrument] -pub async fn register() -> Html { - Html( - html! { - h1 { "Register an account" } - p { "Stop struggling to share the same messy set of PowerPoint files or Google Presentations. Make editing and controlling your live lyrics and music displays easy and simple." } - ul { - li { "Live collaboration with your team" } - li { "Fully compatible with any device" } - li { "Simple workflow" } - li { "Dark theme and light theme" } - li { "Generous free plan" } - li { "Lightweight and fast" } +pub async fn register(csrf: CsrfToken) -> impl IntoResponse { + let token = csrf.authenticity_token().unwrap(); + ( + csrf, + Html( + html! { + (header()) + main class="prose" { + h1 { "Register an account" } + form method="post" { + input type="hidden" name="authenticity_token" value=(token) {} + label { + "Username:" + input name="username" {} + } + label { + "Password:" + input type="password" name="password" {} + } + button type="submit" { "Create Account" } + } + } + (footer()) } - section class="flex gap" { - a class="button bg-primary" href="/register/anonymous" { "Try now" } - a class="button" href="/login" { "Login" } - } - } - .into_string(), + .into_string(), + ), ) + .into_response() +} + +pub async fn login(csrf: CsrfToken) -> impl IntoResponse { + let token = csrf.authenticity_token().unwrap(); + ( + csrf, + Html( + html! { + (header()) + main class="prose" { + h1 { "Login" } + form method="post" { + input type="hidden" name="authenticity_token" value=(token) {} + label { + input {} + } + label { + input {} + } + } + } + (footer()) + } + .into_string(), + ), + ) + .into_response() } #[instrument]