edit users

This commit is contained in:
2025-07-21 09:37:53 +02:00
parent 6e0880eb3d
commit 50e066f794
46 changed files with 1284 additions and 232 deletions

View File

@@ -145,6 +145,13 @@ impl AuthMetrics for MetricsDebugLogger {
tracing::debug!("[Metrics] User creation failed");
}
async fn record_user_edit_success(&self) {
tracing::debug!("[Metrics] User edit succeeded");
}
async fn record_user_edit_failure(&self) {
tracing::debug!("[Metrics] User edit failed");
}
async fn record_user_deletion_success(&self) {
tracing::debug!("[Metrics] User deletion succeeded");
}
@@ -152,13 +159,20 @@ impl AuthMetrics for MetricsDebugLogger {
tracing::debug!("[Metrics] User deletion failed");
}
async fn record_user_list_success(&self) -> () {
async fn record_user_list_success(&self) {
tracing::debug!("[Metrics] User list succeeded");
}
async fn record_user_list_failure(&self) -> () {
async fn record_user_list_failure(&self) {
tracing::debug!("[Metrics] User list failed");
}
async fn record_list_all_users_and_warrens_success(&self) {
tracing::debug!("[Metrics] Users, warrens and user warrens list succeeded");
}
async fn record_list_all_users_and_warrens_failure(&self) {
tracing::debug!("[Metrics] Users, warrens and user warrens list failed");
}
async fn record_auth_session_creation_success(&self) {
tracing::debug!("[Metrics] Auth session creation succeeded");
}
@@ -180,10 +194,10 @@ impl AuthMetrics for MetricsDebugLogger {
tracing::debug!("[Metrics] Auth warren list failed");
}
async fn record_auth_fetch_user_warrens_success(&self) -> () {
async fn record_auth_fetch_user_warrens_success(&self) {
tracing::debug!("[Metrics] Auth user warren id fetch succeeded");
}
async fn record_auth_fetch_user_warrens_failure(&self) -> () {
async fn record_auth_fetch_user_warrens_failure(&self) {
tracing::debug!("[Metrics] Auth user warren id fetch failed");
}

View File

@@ -4,7 +4,7 @@ use crate::domain::warren::{
models::{
auth_session::requests::FetchAuthSessionResponse,
file::{File, FilePath},
user::{LoginUserResponse, User},
user::{ListAllUsersAndWarrensResponse, LoginUserResponse, User},
user_warren::UserWarren,
warren::{
CreateWarrenDirectoryResponse, DeleteWarrenDirectoryResponse, DeleteWarrenFileResponse,
@@ -129,6 +129,14 @@ impl AuthNotifier for NotifierDebugLogger {
);
}
async fn user_edited(&self, editor: &User, user: &User) {
tracing::debug!(
"[Notifier] Admin user {} edited user {}",
editor.name(),
user.id()
);
}
async fn user_deleted(&self, deleter: &User, user: &User) {
tracing::debug!(
"[Notifier] Admin user {} deleted user {}",
@@ -145,6 +153,20 @@ impl AuthNotifier for NotifierDebugLogger {
);
}
async fn all_users_and_warrens_listed(
&self,
user: &User,
response: &ListAllUsersAndWarrensResponse,
) {
tracing::debug!(
"[Notifier] Admin user {} listed {} user(s), {} warren(s) and {} user warren(s)",
user.name(),
response.users().len(),
response.warrens().len(),
response.user_warrens().len(),
);
}
async fn user_logged_in(&self, response: &LoginUserResponse) {
tracing::debug!("[Notifier] Logged in user {}", response.user().name());
}

View File

@@ -25,22 +25,23 @@ use crate::domain::warren::{
},
},
user::{
CreateUserError, CreateUserRequest, DeleteUserError, DeleteUserRequest, ListUsersError,
ListUsersRequest, User, UserEmail, UserName, UserPassword, VerifyUserPasswordError,
VerifyUserPasswordRequest,
CreateUserError, CreateUserRequest, DeleteUserError, DeleteUserRequest, EditUserError,
EditUserRequest, ListAllUsersAndWarrensError, ListAllUsersAndWarrensRequest,
ListAllUsersAndWarrensResponse, ListUsersError, ListUsersRequest, User, UserEmail,
UserName, UserPassword, VerifyUserPasswordError, VerifyUserPasswordRequest,
},
user_warren::{
UserWarren,
requests::{
FetchUserWarrenError, FetchUserWarrenRequest, FetchUserWarrensError,
FetchUserWarrensRequest,
FetchUserWarrensRequest, ListUserWarrensError, ListUserWarrensRequest,
},
},
warren::{
FetchWarrenError, FetchWarrenRequest, FetchWarrensError, FetchWarrensRequest, Warren,
},
},
ports::{AuthRepository, WarrenRepository},
ports::{AuthRepository, WarrenRepository, WarrenService},
};
#[derive(Debug, Clone)]
@@ -138,12 +139,8 @@ impl Postgres {
password: &UserPassword,
is_admin: bool,
) -> anyhow::Result<User> {
let salt = SaltString::generate(&mut OsRng);
let argon2 = Argon2::default();
let password_hash = argon2
.hash_password(password.as_str().as_bytes(), &salt)
.map_err(|_| anyhow!("Failed to hash password"))?
.to_string();
let password_hash =
hash_password(password).map_err(|e| anyhow!("Failed to hash password: {e:?}"))?;
let mut tx = connection.begin().await?;
@@ -176,6 +173,73 @@ impl Postgres {
Ok(user)
}
async fn edit_user(
&self,
connection: &mut PgConnection,
id: &Uuid,
name: &UserName,
email: &UserEmail,
password: Option<&UserPassword>,
is_admin: bool,
) -> anyhow::Result<User> {
let password_hash = if let Some(password) = password {
Some(hash_password(password).map_err(|e| anyhow!("Failed to hash password: {e:?}"))?)
} else {
None
};
let mut tx = connection.begin().await?;
let user: User = sqlx::query_as(
"UPDATE
users
SET
name = $2,
email = $3,
hash = COALESCE($4, hash),
admin = $5
WHERE
id = $1
RETURNING
*
",
)
.bind(id)
.bind(name)
.bind(email)
.bind(password_hash)
.bind(is_admin)
.fetch_one(&mut *tx)
.await?;
self.delete_user_sessions(&mut *tx, id).await?;
tx.commit().await?;
Ok(user)
}
async fn delete_user_sessions(
&self,
connection: &mut PgConnection,
user_id: &Uuid,
) -> Result<u64, sqlx::Error> {
let rows_affected = sqlx::query(
"
DELETE FROM
auth_sessions
WHERE
user_id = $1
",
)
.bind(user_id)
.execute(connection)
.await?
.rows_affected();
Ok(rows_affected)
}
async fn delete_user_from_database(
&self,
connection: &mut PgConnection,
@@ -307,7 +371,7 @@ impl Postgres {
&self,
connection: &mut PgConnection,
session_id: &AuthSessionId,
) -> anyhow::Result<AuthSession> {
) -> Result<AuthSession, sqlx::Error> {
let session: AuthSession = sqlx::query_as(
"
SELECT
@@ -330,7 +394,7 @@ impl Postgres {
connection: &mut PgConnection,
user_id: &Uuid,
) -> Result<Vec<UserWarren>, sqlx::Error> {
let ids: Vec<UserWarren> = sqlx::query_as(
let user_warrens: Vec<UserWarren> = sqlx::query_as(
"
SELECT
*
@@ -344,7 +408,25 @@ impl Postgres {
.fetch_all(connection)
.await?;
Ok(ids)
Ok(user_warrens)
}
async fn get_all_user_warrens(
&self,
connection: &mut PgConnection,
) -> Result<Vec<UserWarren>, sqlx::Error> {
let user_warrens: Vec<UserWarren> = sqlx::query_as(
"
SELECT
*
FROM
user_warrens
",
)
.fetch_all(connection)
.await?;
Ok(user_warrens)
}
async fn get_user_warren(
@@ -379,6 +461,8 @@ impl Postgres {
*
FROM
users
ORDER BY
created_at ASC
",
)
.fetch_all(connection)
@@ -453,6 +537,28 @@ impl AuthRepository for Postgres {
Ok(user)
}
async fn edit_user(&self, request: EditUserRequest) -> Result<User, EditUserError> {
let mut connection = self
.pool
.acquire()
.await
.context("Failed to get a PostgreSQL connection")?;
let user = self
.edit_user(
&mut connection,
request.user_id(),
request.name(),
request.email(),
request.password(),
request.admin(),
)
.await
.context(format!("Failed to edit user"))?;
Ok(user)
}
async fn delete_user(&self, request: DeleteUserRequest) -> Result<User, DeleteUserError> {
let mut connection = self
.pool
@@ -528,7 +634,13 @@ impl AuthRepository for Postgres {
let session = self
.get_auth_session(&mut connection, request.session_id())
.await
.context("Failed to get auth session")?;
.map_err(|e| {
if is_not_found_error(&e) {
FetchAuthSessionError::NotFound
} else {
anyhow!("Failed to get auth session: {e:?}").into()
}
})?;
let user = self
.get_user_from_id(&mut connection, session.user_id())
.await
@@ -547,12 +659,30 @@ impl AuthRepository for Postgres {
.await
.context("Failed to get a PostgreSQL connection")?;
let warren_ids = self
let user_warrens = self
.get_user_warrens(&mut connection, request.user_id())
.await
.context("Failed to get user warrens")?;
Ok(warren_ids)
Ok(user_warrens)
}
async fn list_user_warrens(
&self,
_request: ListUserWarrensRequest,
) -> Result<Vec<UserWarren>, ListUserWarrensError> {
let mut connection = self
.pool
.acquire()
.await
.context("Failed to get a PostgreSQL connection")?;
let user_warrens = self
.get_all_user_warrens(&mut connection)
.await
.context("Failed to get all user warrens")?;
Ok(user_warrens)
}
async fn fetch_user_warren(
@@ -590,8 +720,53 @@ impl AuthRepository for Postgres {
Ok(users)
}
async fn list_all_users_and_warrens<WS: WarrenService>(
&self,
_request: ListAllUsersAndWarrensRequest,
warren_service: &WS,
) -> Result<ListAllUsersAndWarrensResponse, ListAllUsersAndWarrensError> {
let mut connection = self
.pool
.acquire()
.await
.context("Failed to get a PostgreSQL connection")?;
let users = self
.fetch_users(&mut connection)
.await
.context("Failed to fetch all users")?;
let user_warrens = self
.get_all_user_warrens(&mut connection)
.await
.context("Failed to fetch all user warrens")?;
let warrens = warren_service
.list_warrens(FetchWarrensRequest::new(
user_warrens
.iter()
.map(|uw| uw.warren_id().clone())
.collect(),
))
.await
.context("Failed to get warrens")?;
Ok(ListAllUsersAndWarrensResponse::new(
users,
user_warrens,
warrens,
))
}
}
fn is_not_found_error(err: &sqlx::Error) -> bool {
matches!(err, sqlx::Error::RowNotFound)
}
fn hash_password(password: &UserPassword) -> Result<String, argon2::password_hash::Error> {
let salt = SaltString::generate(&mut OsRng);
let argon2 = Argon2::default();
argon2
.hash_password(password.as_str().as_bytes(), &salt)
.map(|h| h.to_string())
}