From b7ca41510939583109170ff1278e1772fdea7eb2 Mon Sep 17 00:00:00 2001 From: nullchinchilla Date: Wed, 27 Mar 2024 17:42:34 -0400 Subject: [PATCH] Add auth.rs with username/password validation and token management functions --- binaries/geph5-broker/src/auth.rs | 61 +++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 binaries/geph5-broker/src/auth.rs diff --git a/binaries/geph5-broker/src/auth.rs b/binaries/geph5-broker/src/auth.rs new file mode 100644 index 00000000..9dcf6f77 --- /dev/null +++ b/binaries/geph5-broker/src/auth.rs @@ -0,0 +1,61 @@ +use std::ops::Deref as _; + +use argon2::{password_hash::Encoding, Argon2, PasswordHash, PasswordVerifier}; +use geph5_broker_protocol::AuthError; + +use rand::Rng as _; + +use crate::{database::POSTGRES, log_error}; + +pub async fn validate_username_pwd(username: &str, password: &str) -> Result { + tracing::debug!(username, "validating legacy username/password"); + let res: Option<(i32, String)> = + sqlx::query_as("select user_id,pwdhash from auth_password where username = $1") + .bind(username) + .fetch_optional(POSTGRES.deref()) + .await + .inspect_err(log_error) + .map_err(|_| AuthError::RateLimited)?; + let (user_id, phc_string) = if let Some(res) = res { + res + } else { + return Err(AuthError::Forbidden); + }; + + let phc = PasswordHash::parse(&phc_string, Encoding::B64) + .inspect_err(log_error) + .map_err(|_| AuthError::Forbidden)?; + + Argon2::default() + .verify_password(password.as_bytes(), &phc) + .map_err(|_| AuthError::Forbidden)?; + + Ok(user_id) +} + +pub async fn new_auth_token(user_id: i32) -> anyhow::Result { + let token: String = std::iter::repeat(()) + .map(|()| rand::thread_rng().sample(rand::distributions::Alphanumeric)) + .map(char::from) + .take(30) + .collect(); + + match sqlx::query("INSERT INTO auth_tokens (token, user_id) VALUES ($1, $2)") + .bind(&token) + .bind(user_id) + .execute(POSTGRES.deref()) + .await + { + Ok(_) => Ok(token), + Err(e) => anyhow::bail!("database failed {e}"), // If insertion fails, return RateLimited error + } +} + +pub async fn valid_auth_token(token: &str) -> anyhow::Result { + let row: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM auth_tokens WHERE token = $1") + .bind(token) + .fetch_one(POSTGRES.deref()) + .await?; + + Ok(row.0 > 0) +}