This commit is contained in:
2025-07-17 16:36:36 +02:00
parent a4cf1064d4
commit 8d707535fd
29 changed files with 751 additions and 26 deletions

View File

@@ -115,10 +115,10 @@ impl FileSystemMetrics for MetricsDebugLogger {
tracing::debug!("[Metrics] File deletion failed");
}
async fn record_entry_rename_success(&self) -> () {
async fn record_entry_rename_success(&self) {
tracing::debug!("[Metrics] Entry rename succeeded");
}
async fn record_entry_rename_failure(&self) -> () {
async fn record_entry_rename_failure(&self) {
tracing::debug!("[Metrics] Entry rename failed");
}
}
@@ -130,4 +130,18 @@ impl AuthMetrics for MetricsDebugLogger {
async fn record_user_registration_failure(&self) {
tracing::debug!("[Metrics] User registration failed");
}
async fn record_user_login_success(&self) {
tracing::debug!("[Metrics] User login succeeded");
}
async fn record_user_login_failure(&self) {
tracing::debug!("[Metrics] User login failed");
}
async fn record_auth_session_creation_success(&self) {
tracing::debug!("[Metrics] Auth session creation succeeded");
}
async fn record_auth_session_creation_failure(&self) {
tracing::debug!("[Metrics] Auth session creation failed");
}
}

View File

@@ -1,3 +1,5 @@
use uuid::Uuid;
use crate::domain::warren::{
models::{
file::{File, FilePath},
@@ -118,4 +120,12 @@ impl AuthNotifier for NotifierDebugLogger {
async fn user_registered(&self, user: &User) {
tracing::debug!("[Notifier] Registered user {}", user.name());
}
async fn user_logged_in(&self, user: &User) {
tracing::debug!("[Notifier] Logged in user {}", user.name());
}
async fn auth_session_created(&self, user_id: &Uuid) {
tracing::debug!("[Notifier] Created auth session for user {}", user_id);
}
}

View File

@@ -2,9 +2,13 @@ use std::str::FromStr;
use anyhow::{Context, anyhow};
use argon2::{
Argon2,
password_hash::{PasswordHasher, SaltString, rand_core::OsRng},
Argon2, PasswordHash, PasswordVerifier,
password_hash::{
PasswordHasher, SaltString,
rand_core::{OsRng, RngCore as _},
},
};
use chrono::Utc;
use sqlx::{
ConnectOptions as _, Connection as _, PgConnection, PgPool,
postgres::{PgConnectOptions, PgPoolOptions},
@@ -13,7 +17,14 @@ use uuid::Uuid;
use crate::domain::warren::{
models::{
user::{RegisterUserError, RegisterUserRequest, User, UserEmail, UserName, UserPassword},
auth_session::{
AuthSession,
requests::{CreateAuthSessionError, CreateAuthSessionRequest, SessionExpirationTime},
},
user::{
RegisterUserError, RegisterUserRequest, User, UserEmail, UserName, UserPassword,
VerifyUserPasswordError, VerifyUserPasswordRequest,
},
warren::{
FetchWarrenError, FetchWarrenRequest, ListWarrensError, ListWarrensRequest, Warren,
},
@@ -151,6 +162,89 @@ impl Postgres {
Ok(user)
}
async fn get_user_from_email(
&self,
connection: &mut PgConnection,
email: &UserEmail,
) -> Result<User, sqlx::Error> {
let user: User = sqlx::query_as(
"
SELECT
*
FROM
users
WHERE
email = $1
",
)
.bind(email)
.fetch_one(connection)
.await?;
Ok(user)
}
fn check_user_password_against_hash(
&self,
password: &UserPassword,
hash: &str,
) -> Result<(), VerifyUserPasswordError> {
let argon = Argon2::default();
let hash =
PasswordHash::new(hash).map_err(|e| VerifyUserPasswordError::Unknown(anyhow!(e)))?;
argon
.verify_password(password.as_str().as_bytes(), &hash)
.map_err(|e| match e {
argon2::password_hash::Error::Password => {
VerifyUserPasswordError::IncorrectPassword
}
_ => VerifyUserPasswordError::Unknown(anyhow!(e)),
})
}
async fn create_session(
&self,
connection: &mut PgConnection,
user: &User,
expiration: &SessionExpirationTime,
) -> anyhow::Result<AuthSession> {
let mut rng = OsRng;
let mut bytes = vec![0_u8; 32];
rng.fill_bytes(&mut bytes);
let session_id = hex::encode(bytes);
let expiration_time = Utc::now().timestamp_millis() + i64::try_from(expiration.millis())?;
let mut tx = connection.begin().await?;
let session: AuthSession = sqlx::query_as(
"
INSERT INTO auth_sessions (
session_id,
user_id,
expires_at
) VALUES (
$1,
$2,
TO_TIMESTAMP($3::double precision / 1000)
)
RETURNING
*
",
)
.bind(session_id)
.bind(user.id())
.bind(expiration_time)
.fetch_one(&mut *tx)
.await?;
tx.commit().await?;
Ok(session)
}
}
impl WarrenRepository for Postgres {
@@ -217,6 +311,50 @@ impl AuthRepository for Postgres {
Ok(user)
}
async fn verify_user_password(
&self,
request: VerifyUserPasswordRequest,
) -> Result<User, VerifyUserPasswordError> {
let mut connection = self
.pool
.acquire()
.await
.context("Failed to get a PostgreSQL connection")?;
let user = self
.get_user_from_email(&mut connection, request.email())
.await
.map_err(|e| {
if is_not_found_error(&e) {
VerifyUserPasswordError::NotFound(request.email().clone())
} else {
VerifyUserPasswordError::Unknown(anyhow!(e))
}
})?;
self.check_user_password_against_hash(request.password(), user.password_hash())?;
Ok(user)
}
async fn create_auth_session(
&self,
request: CreateAuthSessionRequest,
) -> Result<AuthSession, CreateAuthSessionError> {
let mut connection = self
.pool
.acquire()
.await
.context("Failed to get a PostgreSQL connection")?;
let session = self
.create_session(&mut connection, request.user(), request.expiration())
.await
.context("Failed to create session")?;
Ok(session)
}
}
fn is_not_found_error(err: &sqlx::Error) -> bool {