gitdataai/libs/service/workspace/settings.rs
ZhenYi bdb5393835 fix: resolve 30+ bugs from security audit
Critical:
- CORS: replace allow_any_origin + credentials with env-configured origins
- XSS: escape HTML before dangerouslySetInnerHTML in search results
- Path traversal: sanitize storage keys to reject ".." components
- Auth missing: add Session requirement to git init/open/is-repo endpoints
- Transaction: wrap issue cascade delete in DB transaction

High:
- Mutex poisoning: replace unwrap() with poison-recovering guards
- Drop tokio::spawn: use runtime handle or fallback thread for lock release
- Redis KEYS: replace with non-blocking SCAN for typing events
- SSH panic: handle missing stdin/stdout/stderr gracefully
- LFS auth: remove x-user-uid header injection vector, generate per-request tokens

Medium:
- Memory leak: remove Box::leak in provider normalization
- Race conditions: query closed count directly instead of subtraction
- Silent failures: add tracing::warn for AI tasks, room events, activity logs
- Frontend nav: sync activeRoomId when initialRoomId prop changes
- Duplicate nav: remove redundant setActiveRoom in delete handler
- Callback conflict: skip undefined values in updateCallbacks merge
- Stale closure: use wsClient state instead of wsClientRef.current in useMemo

Low:
- Captcha: validate captcha not empty before login submission
- Broadcast capacity: reduce from 100K to 1000
- Error handling: add try/catch for removeMember and updateMemberRole
- Loading state: show placeholder instead of null in RepositoryContextProvider
- WebSocket: add heartbeat ping and jitter to reconnect backoff
2026-04-27 10:57:23 +08:00

84 lines
2.6 KiB
Rust

use crate::AppService;
use crate::error::AppError;
use chrono::Utc;
use models::workspaces::{WorkspaceRole, workspace};
use sea_orm::*;
use serde::{Deserialize, Serialize};
use session::Session;
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct WorkspaceUpdateParams {
pub name: Option<String>,
pub description: Option<String>,
pub avatar_url: Option<String>,
pub billing_email: Option<String>,
}
impl AppService {
pub async fn workspace_update(
&self,
ctx: &Session,
workspace_slug: String,
params: WorkspaceUpdateParams,
) -> Result<workspace::Model, AppError> {
let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
let ws = self
.utils_find_workspace_by_slug(workspace_slug.clone())
.await?;
self.utils_check_workspace_permission(ws.id, user_uid, &[WorkspaceRole::Admin])
.await?;
let ws_id = ws.id;
let mut m: workspace::ActiveModel = ws.into();
if let Some(name) = params.name {
// Check name uniqueness
if workspace::Entity::find()
.filter(workspace::Column::Name.eq(&name))
.filter(workspace::Column::DeletedAt.is_null())
.filter(workspace::Column::Id.ne(ws_id))
.one(&self.db)
.await?
.is_some()
{
return Err(AppError::WorkspaceNameAlreadyExists);
}
m.name = Set(name);
}
if let Some(description) = params.description {
m.description = Set(Some(description));
}
if let Some(avatar_url) = params.avatar_url {
m.avatar_url = Set(Some(avatar_url));
}
if let Some(billing_email) = params.billing_email {
m.billing_email = Set(Some(billing_email));
}
m.updated_at = Set(Utc::now());
m.update(&self.db).await.map_err(Into::into)
}
/// Soft-delete a workspace. Only owner can delete.
pub async fn workspace_delete(
&self,
ctx: &Session,
workspace_slug: String,
) -> Result<(), AppError> {
let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
let ws = self.utils_find_workspace_by_slug(workspace_slug).await?;
self.utils_check_workspace_permission(ws.id, user_uid, &[WorkspaceRole::Owner])
.await?;
let mut m: workspace::ActiveModel = ws.into();
m.deleted_at = Set(Some(Utc::now()));
m.updated_at = Set(Utc::now());
m.update(&self.db).await?;
Ok(())
}
}