Check user/pw against list of known users w/hashed pw

This commit is contained in:
pjht 2024-10-17 12:18:45 -05:00
parent c21054814c
commit cbd2792ad3
Signed by: pjht
GPG Key ID: CA239FC6934E6F3A
6 changed files with 129 additions and 29 deletions

46
Cargo.lock generated
View File

@ -102,6 +102,18 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d301b3b94cb4b2f23d7917810addbbaff90738e0ca2be692bd027e70d7e0330c"
[[package]]
name = "argon2"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072"
dependencies = [
"base64ct",
"blake2",
"cpufeatures",
"password-hash",
]
[[package]]
name = "async-compression"
version = "0.4.13"
@ -234,6 +246,12 @@ version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "base64ct"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]]
name = "bincode"
version = "1.3.3"
@ -249,6 +267,15 @@ version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[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"
@ -1096,6 +1123,13 @@ dependencies = [
"crunchy",
]
[[package]]
name = "hash_pw"
version = "0.1.0"
dependencies = [
"argon2",
]
[[package]]
name = "hashbrown"
version = "0.15.0"
@ -1471,6 +1505,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.1"
@ -1769,6 +1814,7 @@ dependencies = [
name = "server"
version = "0.1.0"
dependencies = [
"argon2",
"axum",
"axum-login",
"ciborium",

View File

@ -2,6 +2,6 @@
resolver = "2"
members = [ "common",
"frontend",
"frontend", "hash_pw",
"server",
]

7
hash_pw/Cargo.toml Normal file
View File

@ -0,0 +1,7 @@
[package]
name = "hash_pw"
version = "0.1.0"
edition = "2021"
[dependencies]
argon2 = "0.5.3"

16
hash_pw/src/main.rs Normal file
View File

@ -0,0 +1,16 @@
use std::io::{stdin, stdout, Write};
use argon2::{password_hash::{rand_core::OsRng, SaltString}, Argon2, PasswordHasher};
fn main() {
print!("Enter password:");
stdout().flush().unwrap();
let mut pw_buf = String::new();
stdin().read_line(&mut pw_buf).unwrap();
let password = pw_buf.trim_end();
let salt = SaltString::generate(&mut OsRng);
let argon2 = Argon2::default();
let pw_hash = argon2.hash_password(password.as_bytes(), &salt).unwrap().to_string();
println!("{pw_hash}");
}

View File

@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2021"
[dependencies]
argon2 = "0.5.3"
axum = { version = "0.7.7", features = ["ws"] }
axum-login = "0.16.0"
ciborium = "0.2.2"

View File

@ -1,3 +1,4 @@
use argon2::{Argon2, PasswordHash, PasswordVerifier};
use axum::body::Body;
use axum::extract::ws::{self, CloseFrame, Message, WebSocket};
use axum::extract::{Request, State, WebSocketUpgrade};
@ -13,6 +14,7 @@ use common::{ChatMessage, LoggedInResponse, LoginRequest, LoginResponse};
use futures::stream::SplitSink;
use futures::{SinkExt, StreamExt};
use slab::Slab;
use std::collections::HashMap;
use std::net::{IpAddr, Ipv6Addr, SocketAddr};
use std::path::{Path, PathBuf};
use std::str::FromStr;
@ -66,43 +68,67 @@ impl ServState {
}
}
#[derive(Clone, Debug)]
struct DummyAuthUser;
//const USERS: HashMap<String, User> = {
// let mut map = HashMap::new();
// map.insert("pjht".to_string(), User {
// username: "pjht".to_string(),
// pw_hash: "$argon2id$v=19$m=19456,t=2,p=1$miwLkAOUyJa7NxWX9ueBJQ$w43pjFVBqnqRvWWiPaW2cbhlxk0Dq5sdYjmy4I+Yh+U".to_string(),
// id: 0,
// }).unwrap();
//map
//};
impl AuthUser for DummyAuthUser {
struct UserCreds {
username: String,
password: String,
}
#[derive(Clone, Debug)]
struct User {
username: String,
pw_hash: String,
}
impl AuthUser for User {
type Id = String;
fn id(&self) -> Self::Id {
"pjht".to_string()
self.username.clone()
}
fn session_auth_hash(&self) -> &[u8] {
&[0]
self.pw_hash.as_bytes()
}
}
#[derive(Clone, Debug)]
struct DummyAuthBackend;
struct AuthBackend {
users: HashMap<String, User>
}
#[async_trait]
impl AuthnBackend for DummyAuthBackend {
type User = DummyAuthUser;
type Credentials = ();
impl AuthnBackend for AuthBackend {
type User = User;
type Credentials = UserCreds;
type Error = std::convert::Infallible;
async fn authenticate(
&self,
_creds: Self::Credentials,
creds: Self::Credentials,
) -> Result<Option<Self::User>, Self::Error> {
Ok(Some(DummyAuthUser))
Ok((|| {
let user = self.users.get(&creds.username)?;
let pw_hash = PasswordHash::new(&user.pw_hash).unwrap();
if Argon2::default().verify_password(creds.password.as_bytes(), &pw_hash).is_ok() {
Some(user.clone())
} else {
None
}
})())
}
async fn get_user(&self, user_id: &UserId<Self>) -> Result<Option<Self::User>, Self::Error> {
if user_id == "pjht" {
Ok(Some(DummyAuthUser))
} else {
Ok(None)
}
Ok(self.users.get(user_id).cloned())
}
}
@ -125,29 +151,26 @@ async fn serve_index(static_dir: &Path) -> Response {
}
async fn login(
mut auth_session: AuthSession<DummyAuthBackend>,
mut auth_session: AuthSession<AuthBackend>,
Json(req): Json<LoginRequest>,
) -> Response {
if req.username != "pjht" || req.password != "123456" {
return Json(LoginResponse(Err(
"Invalid username or password".to_string()
)))
.into_response();
}
if auth_session.login(&DummyAuthUser).await.is_err() {
let Some(user) = auth_session.authenticate(UserCreds {username: req.username, password: req.password}).await.unwrap() else {
return Json(LoginResponse(Err("Invalid username or password".to_string()))).into_response();
};
if auth_session.login(&user).await.is_err() {
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
}
Json(LoginResponse(Ok(()))).into_response()
}
async fn logout(mut auth_session: AuthSession<DummyAuthBackend>) -> Response {
async fn logout(mut auth_session: AuthSession<AuthBackend>) -> Response {
if auth_session.logout().await.is_err() {
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
}
StatusCode::OK.into_response()
}
async fn logged_in(auth_session: AuthSession<DummyAuthBackend>) -> Response {
async fn logged_in(auth_session: AuthSession<AuthBackend>) -> Response {
let resp = LoggedInResponse {
logged_in: auth_session.user.is_some(),
};
@ -174,7 +197,14 @@ async fn main() {
.with_expiry(Expiry::OnInactivity(Duration::days(1)))
.with_signed(signing_key);
let auth_backend = DummyAuthBackend;
let mut users = HashMap::new();
users.insert("pjht".to_string(), User {
username: "pjht".to_string(),
pw_hash: "$argon2id$v=19$m=19456,t=2,p=1$aI7n6SgiTcVhSBjk7pXo+Q$wcJriSwcIj5Al/oNlZJdxMVOA/15e13t2AaWs4VQmVM".to_string(),
});
let auth_backend = AuthBackend {
users
};
let auth_layer = AuthManagerLayerBuilder::new(auth_backend, session_layer).build();
let api = Router::new()
@ -231,7 +261,7 @@ async fn main() {
async fn chat_ws(
State(state): State<Arc<Mutex<ServState>>>,
auth_session: AuthSession<DummyAuthBackend>,
auth_session: AuthSession<AuthBackend>,
ws: WebSocketUpgrade,
) -> Response {
ws.on_upgrade(move |socket| async move {