Now that I've rolled my own crappy auth
This commit is contained in:
parent
0879a84df1
commit
305c1bd011
6 changed files with 283 additions and 29 deletions
157
Cargo.lock
generated
157
Cargo.lock
generated
|
@ -26,6 +26,21 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.14"
|
||||
|
@ -205,6 +220,15 @@ version = "1.6.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "1.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
|
@ -238,6 +262,12 @@ dependencies = [
|
|||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
|
@ -262,6 +292,21 @@ version = "1.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"windows-targets 0.52.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.4"
|
||||
|
@ -384,6 +429,12 @@ dependencies = [
|
|||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.12"
|
||||
|
@ -766,6 +817,29 @@ dependencies = [
|
|||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indenter"
|
||||
version = "0.3.3"
|
||||
|
@ -823,6 +897,15 @@ version = "1.0.11"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "json5"
|
||||
version = "0.4.1"
|
||||
|
@ -894,6 +977,8 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"argon2",
|
||||
"axum",
|
||||
"bincode",
|
||||
"chrono",
|
||||
"clap",
|
||||
"color-eyre",
|
||||
"config",
|
||||
|
@ -1039,6 +1124,15 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.16.0"
|
||||
|
@ -1918,6 +2012,60 @@ version = "0.11.0+wasi-snapshot-preview1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
|
@ -1949,6 +2097,15 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
|
|
|
@ -20,6 +20,8 @@ panic = "abort"
|
|||
|
||||
[dependencies]
|
||||
argon2 = { version = "0.5.3", features = ["std"] }
|
||||
bincode = "1.3.3"
|
||||
chrono = { version = "0.4.38", features = ["serde"] }
|
||||
clap = { version = "4.5.4", features = ["derive", "env"] }
|
||||
color-eyre = "0.6.3"
|
||||
config = "0.14.0"
|
||||
|
|
48
src/db.rs
48
src/db.rs
|
@ -1,3 +1,4 @@
|
|||
use serde::de::DeserializeOwned;
|
||||
use sled::{Db, IVec};
|
||||
use thiserror::Error;
|
||||
|
||||
|
@ -10,6 +11,9 @@ pub struct Data {
|
|||
pub enum Error {
|
||||
#[error("sled error: {0}")]
|
||||
Sled(#[from] sled::Error),
|
||||
|
||||
#[error("bincode error: {0}")]
|
||||
Binccode(#[from] Box<bincode::ErrorKind>),
|
||||
}
|
||||
|
||||
impl Data {
|
||||
|
@ -19,16 +23,15 @@ impl Data {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn get<K: AsRef<[u8]>, V: From<IVec>>(
|
||||
&self,
|
||||
tree_name: &str,
|
||||
key: K,
|
||||
) -> Result<Option<V>, Error> {
|
||||
Ok(self
|
||||
.db
|
||||
.open_tree(tree_name)?
|
||||
.get(key.as_ref())?
|
||||
.map(V::from))
|
||||
pub fn get<K, V>(&self, tree_name: &str, key: K) -> Result<Option<V>, Error>
|
||||
where
|
||||
K: AsRef<[u8]>,
|
||||
V: DeserializeOwned,
|
||||
{
|
||||
match self.db.open_tree(tree_name)?.get(key.as_ref())? {
|
||||
Some(v) => Ok(Some(bincode::deserialize::<V>(&v)?)),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert<K: AsRef<[u8]>, V: Into<IVec>>(
|
||||
|
@ -39,4 +42,29 @@ impl Data {
|
|||
) -> Result<Option<IVec>, Error> {
|
||||
Ok(self.db.open_tree(tree_name)?.insert(key, value.into())?)
|
||||
}
|
||||
|
||||
pub fn all<'de, K, V>(
|
||||
&self,
|
||||
tree_name: &str,
|
||||
) -> Result<impl Iterator<Item = Result<(K, V), Error>>, Error>
|
||||
where
|
||||
V: DeserializeOwned,
|
||||
K: From<IVec>,
|
||||
{
|
||||
Ok(self
|
||||
.db
|
||||
.open_tree(tree_name)?
|
||||
.scan_prefix([])
|
||||
.map(|r| match r {
|
||||
Ok((k, v)) => {
|
||||
let key = K::from(k);
|
||||
match bincode::deserialize::<V>(&v).map_err(Error::from) {
|
||||
Ok(v) => Ok((key, v)),
|
||||
Err(err) => Err(Error::from(err)),
|
||||
}
|
||||
}
|
||||
Err(err) => Err(Error::from(err)),
|
||||
})
|
||||
.into_iter())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
use crate::partials::page;
|
||||
use crate::user::User;
|
||||
use crate::{db, user};
|
||||
use crate::{
|
||||
file_watcher::FileWatcher,
|
||||
prelude::*,
|
||||
service::{auth, static_files},
|
||||
state::State as AppState,
|
||||
};
|
||||
use axum::extract::State;
|
||||
use axum::{
|
||||
http::StatusCode,
|
||||
response::{Html, IntoResponse},
|
||||
|
@ -12,6 +15,7 @@ use axum::{
|
|||
Router,
|
||||
};
|
||||
use maud::html;
|
||||
use sled::IVec;
|
||||
use thiserror::Error;
|
||||
use tower_http::trace::TraceLayer;
|
||||
use tower_livereload::LiveReloadLayer;
|
||||
|
@ -26,6 +30,15 @@ pub enum NewRouterError {
|
|||
pub enum ReqError {
|
||||
#[error("argon2 error: {0}")]
|
||||
Argon2(#[from] argon2::password_hash::Error),
|
||||
|
||||
#[error("bincode error: {0}")]
|
||||
Bincode(#[from] bincode::Error),
|
||||
|
||||
#[error("database error: {0}")]
|
||||
Database(#[from] db::Error),
|
||||
|
||||
#[error("user error: {0}")]
|
||||
User(#[from] user::Error),
|
||||
}
|
||||
|
||||
impl IntoResponse for ReqError {
|
||||
|
@ -61,11 +74,12 @@ pub async fn router(
|
|||
};
|
||||
|
||||
let (static_file_service, static_file_watcher) = static_files::router(orl())?;
|
||||
let auth_service = auth::router().unwrap();
|
||||
let auth_service = auth::router(state.clone()).unwrap();
|
||||
|
||||
let mut result = Router::new()
|
||||
.route("/", get(index))
|
||||
.route("/about", get(about))
|
||||
.route("/users", get(users))
|
||||
.nest_service("/auth", auth_service)
|
||||
.nest_service("/static", static_file_service)
|
||||
.layer(TraceLayer::new_for_http())
|
||||
|
@ -87,3 +101,12 @@ async fn index() -> ReqResult<Html<String>> {
|
|||
async fn about() -> ReqResult<Html<String>> {
|
||||
page("index", html! { "About" })
|
||||
}
|
||||
|
||||
async fn users(State(state): State<AppState>) -> ReqResult<String> {
|
||||
let mut s = String::new();
|
||||
let mut users = state.db.all::<IVec, User>(User::tree())?;
|
||||
while let Some(Ok((_, user))) = users.next() {
|
||||
s.push_str(&format!("{}: {:?}", user.username, user.registered_at))
|
||||
}
|
||||
Ok(s)
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ use axum::{
|
|||
Router,
|
||||
};
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::{prelude::*, user};
|
||||
|
||||
pub fn router(state: AppState) -> Result<Router, Infallible> {
|
||||
Ok(Router::new()
|
||||
|
@ -37,7 +37,7 @@ async fn login() -> ReqResult<Html<String>> {
|
|||
let subaction = html! {
|
||||
small class="mt-4" {
|
||||
"Need an account? "
|
||||
a href="/register" {"Get one"}
|
||||
a href="/auth/register" {"Get one"}
|
||||
"."
|
||||
}
|
||||
};
|
||||
|
@ -56,7 +56,7 @@ async fn register() -> ReqResult<Html<String>> {
|
|||
let subaction = html! {
|
||||
small class="mt-4" {
|
||||
"Already have an account? "
|
||||
a href="/login" {"Login"}
|
||||
a href="/auth/login" {"Login"}
|
||||
"."
|
||||
}
|
||||
};
|
||||
|
@ -69,15 +69,34 @@ struct Creds {
|
|||
password: Secret<String>,
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
async fn authenticate(Form(creds): Form<Creds>) -> ReqResult<Html<String>> {
|
||||
info!("login attempt");
|
||||
Ok(Html(
|
||||
html! {
|
||||
"no"
|
||||
}
|
||||
.into_string(),
|
||||
))
|
||||
#[instrument(skip(state))]
|
||||
async fn authenticate(
|
||||
State(state): State<AppState>,
|
||||
Form(creds): Form<Creds>,
|
||||
) -> ReqResult<Html<String>> {
|
||||
let existing_user: Option<User> = state.db.get(User::tree(), &creds.username)?;
|
||||
|
||||
if existing_user.is_none() {
|
||||
// timing/enumeration attacks or something
|
||||
return Err(user::Error::UsernameNotFound(Box::new(creds.username)).into());
|
||||
}
|
||||
|
||||
let existing_user = existing_user.unwrap();
|
||||
if let Err(err) = existing_user.verify(creds.password.expose_secret()) {
|
||||
Ok(Html(
|
||||
html! {
|
||||
"failed to login: " ({err})
|
||||
}
|
||||
.into_string(),
|
||||
))
|
||||
} else {
|
||||
Ok(Html(
|
||||
html! {
|
||||
"logged in"
|
||||
}
|
||||
.into_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip(state))]
|
||||
|
@ -86,9 +105,21 @@ async fn create_user(
|
|||
Form(creds): Form<Creds>,
|
||||
) -> ReqResult<Html<String>> {
|
||||
let user = User::try_new(&creds.username, creds.password.expose_secret())?;
|
||||
let existing_user: Option<User> = state.db.get(User::tree(), &creds.username)?;
|
||||
|
||||
// TODO: fail2ban?
|
||||
if existing_user.is_some() {
|
||||
// timing/enumeration attacks or something
|
||||
return Err(user::Error::UsernameExists(Box::new(creds.username)).into());
|
||||
}
|
||||
|
||||
state
|
||||
.db
|
||||
.insert(User::tree(), &user.username, bincode::serialize(&user)?)?;
|
||||
|
||||
Ok(Html(
|
||||
html! {
|
||||
({user.username}) " has been registered"
|
||||
({&user.username}) " has been registered"
|
||||
}
|
||||
.into_string(),
|
||||
))
|
||||
|
|
23
src/user.rs
23
src/user.rs
|
@ -1,14 +1,19 @@
|
|||
use crate::prelude::*;
|
||||
use redact::Secret;
|
||||
use serde::Deserialize;
|
||||
use chrono::Utc;
|
||||
use redact::{expose_secret, Secret};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
||||
pub const USER_TREE: &str = "user";
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct User {
|
||||
pub username: String,
|
||||
password_digest: Secret<String>,
|
||||
|
||||
#[serde(serialize_with = "expose_secret")]
|
||||
password_digest: Secret<Vec<u8>>,
|
||||
|
||||
pub registered_at: chrono::DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
|
@ -28,7 +33,8 @@ impl User {
|
|||
pub fn try_new(username: &str, password: &str) -> Result<Self, argon2::password_hash::Error> {
|
||||
Ok(Self {
|
||||
username: username.to_owned(),
|
||||
password_digest: Secret::new(crate::auth::password_digest(password)?.to_string()),
|
||||
registered_at: Utc::now(),
|
||||
password_digest: Secret::new(crate::auth::password_digest(password)?.into()),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -36,3 +42,10 @@ impl User {
|
|||
crate::auth::verified_password(password, self.password_digest.expose_secret())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<sled::IVec> for User {
|
||||
type Error = bincode::Error;
|
||||
fn try_from(value: sled::IVec) -> Result<Self, Self::Error> {
|
||||
bincode::deserialize(&value)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue