#[cfg(test)]
use crate::passkey::CredentialId;
use crate::passkey::{CredentialSearchField, PasskeyStore};
use crate::userdb::{User as DbUser, UserStore};
use super::errors::CoordinationError;
use crate::session::{SessionId, UserId, get_user_from_session};
pub async fn update_user_account(
session_id: SessionId,
user_id: UserId,
account: Option<String>,
label: Option<String>,
) -> Result<DbUser, CoordinationError> {
let session_cookie = crate::SessionCookie::new(session_id.as_str().to_string())
.map_err(|_| CoordinationError::Unauthorized.log())?;
let session_user = get_user_from_session(&session_cookie)
.await
.map_err(|_| CoordinationError::Unauthorized.log())?;
if session_user.id != user_id.as_str() {
tracing::debug!(
session_user_id = %session_user.id,
target_user_id = %user_id.as_str(),
"User is not authorized (not resource owner)"
);
return Err(CoordinationError::Unauthorized.log());
}
let user = DbUser::from(session_user);
let updated_user = DbUser {
account: account.unwrap_or(user.account),
label: label.unwrap_or(user.label),
..user
};
let user = UserStore::upsert_user(updated_user).await?;
Ok(user)
}
pub async fn delete_user_account(
session_id: SessionId,
user_id: UserId,
) -> Result<Vec<String>, CoordinationError> {
let session_cookie = crate::SessionCookie::new(session_id.as_str().to_string())
.map_err(|_| CoordinationError::Unauthorized.log())?;
let session_user = get_user_from_session(&session_cookie)
.await
.map_err(|_| CoordinationError::Unauthorized.log())?;
if !session_user.has_admin_privileges() && session_user.id != user_id.as_str() {
tracing::debug!(
session_user_id = %session_user.id,
target_user_id = %user_id.as_str(),
has_admin_privileges = %session_user.has_admin_privileges(),
"User is not authorized (neither admin nor resource owner)"
);
return Err(CoordinationError::Unauthorized.log());
}
let user = if session_user.id == user_id.as_str() {
DbUser::from(session_user)
} else {
UserStore::get_user(user_id.clone()).await?.ok_or_else(|| {
CoordinationError::ResourceNotFound {
resource_type: "User".to_string(),
resource_id: user_id.as_str().to_string(),
}
.log()
})?
};
tracing::debug!("Deleting user account: {:#?}", user);
let credentials =
PasskeyStore::get_credentials_by(CredentialSearchField::UserId(user_id.clone())).await?;
let credential_ids: Vec<String> = credentials
.iter()
.map(|c| c.credential_id.clone())
.collect();
if user.has_admin_privileges() {
let deleted = UserStore::delete_user_if_not_last_admin(user_id)
.await
.map_err(|e| CoordinationError::Database(e.to_string()))?;
if !deleted {
return Err(CoordinationError::Conflict(
"Cannot delete the last admin user".to_string(),
)
.log());
}
} else {
UserStore::delete_user(user_id).await?;
}
Ok(credential_ids)
}
pub(super) async fn gen_new_user_id() -> Result<String, CoordinationError> {
for _ in 0..3 {
let id = uuid::Uuid::new_v4().to_string();
let user_id = UserId::new(id.clone()).map_err(|e| {
CoordinationError::Internal(format!("Failed to create user ID from UUID: {e}"))
})?;
match UserStore::get_user(user_id).await {
Ok(None) => return Ok(id), Ok(Some(_)) => continue, Err(e) => {
return Err(
CoordinationError::Database(format!("Failed to check user ID: {e}")).log(),
);
}
}
}
Err(CoordinationError::Coordination(
"Failed to generate a unique user ID after multiple attempts".to_string(),
)
.log())
}
#[cfg(test)]
mod tests;