feat(user-avatar): users can now edit there image (image is taken from OIDC picture)
This commit is contained in:
@@ -17,6 +17,8 @@ pub struct UserDto {
|
||||
pub last_login_at: Option<DateTime<Utc>>,
|
||||
pub active: bool,
|
||||
pub auth_provider: String,
|
||||
pub image: Option<String>,
|
||||
pub can_edit_image: bool,
|
||||
}
|
||||
|
||||
impl From<User> for UserDto {
|
||||
@@ -33,6 +35,8 @@ impl From<User> for UserDto {
|
||||
last_login_at: user.last_login_at(),
|
||||
active: user.is_active(),
|
||||
auth_provider: user.oidc_provider().unwrap_or("local").to_string(),
|
||||
image: user.image().map(|s| s.to_string()),
|
||||
can_edit_image: !user.is_oidc_user(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -151,6 +151,7 @@ pub struct OidcIdClaims {
|
||||
pub preferred_username: Option<String>,
|
||||
pub name: Option<String>,
|
||||
pub groups: Vec<String>,
|
||||
pub picture: Option<String>,
|
||||
}
|
||||
|
||||
/// Port for OIDC operations — implemented in infrastructure layer
|
||||
|
||||
@@ -647,6 +647,53 @@ impl AuthApplicationService {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update the profile image for a non-OIDC user.
|
||||
pub async fn update_user_image(
|
||||
&self,
|
||||
caller_id: Uuid,
|
||||
image: Option<String>,
|
||||
) -> Result<(), DomainError> {
|
||||
let user = self.user_storage.get_user_by_id(caller_id).await?;
|
||||
|
||||
if user.is_oidc_user() {
|
||||
return Err(DomainError::new(
|
||||
ErrorKind::AccessDenied,
|
||||
"User",
|
||||
"Avatar is managed by your identity provider and cannot be changed here",
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(ref img) = image {
|
||||
const MAX_BYTES: usize = 524_288; // 512 KiB
|
||||
if img.len() > MAX_BYTES {
|
||||
return Err(DomainError::new(
|
||||
ErrorKind::InvalidInput,
|
||||
"User",
|
||||
"Image exceeds maximum allowed size (512 KiB)",
|
||||
));
|
||||
}
|
||||
let valid = img.starts_with("https://")
|
||||
|| img.starts_with("http://")
|
||||
|| img.starts_with("data:image/png;base64,")
|
||||
|| img.starts_with("data:image/webp;base64,")
|
||||
|| img.starts_with("data:image/jpeg;base64,");
|
||||
if !valid {
|
||||
return Err(DomainError::new(
|
||||
ErrorKind::InvalidInput,
|
||||
"User",
|
||||
"Image must be an https/http URL or a data URI (png, webp, jpeg)",
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
self.user_storage
|
||||
.update_image(caller_id, image)
|
||||
.await
|
||||
.map_err(DomainError::from)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_user(&self, user_id: Uuid) -> Result<UserDto, DomainError> {
|
||||
let user = self.user_storage.get_user_by_id(user_id).await?;
|
||||
Ok(UserDto::from(user))
|
||||
@@ -1128,8 +1175,9 @@ impl AuthApplicationService {
|
||||
.await
|
||||
{
|
||||
Ok(mut existing_user) => {
|
||||
// User exists — update last login
|
||||
// User exists — update last login and sync avatar from IdP
|
||||
existing_user.register_login();
|
||||
existing_user.set_image(claims.picture.clone());
|
||||
self.user_storage.update_user(existing_user.clone()).await?;
|
||||
existing_user
|
||||
}
|
||||
@@ -1206,7 +1254,7 @@ impl AuthApplicationService {
|
||||
username = format!("{}_{}", &username[..username.len().min(27)], suffix);
|
||||
}
|
||||
|
||||
let new_user = User::new_oidc(
|
||||
let mut new_user = User::new_oidc(
|
||||
username.clone(),
|
||||
oidc_email,
|
||||
role,
|
||||
@@ -1221,6 +1269,7 @@ impl AuthApplicationService {
|
||||
format!("Failed to create OIDC user: {}", e),
|
||||
)
|
||||
})?;
|
||||
new_user.set_image(claims.picture.clone());
|
||||
|
||||
let created_user = self.user_storage.create_user(new_user).await?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user