fix: harden security, reduce duplication, and improve robustness
- Fix SQL injection in data.rs by wrapping get_table_data in READ ONLY transaction - Fix SQL injection in docker.rs CREATE DATABASE via escape_ident - Fix command injection in docker.rs by validating pg_version/container_name and escaping shell-interpolated values - Fix UTF-8 panic on stderr truncation with char_indices - Wrap delete_rows in a transaction for atomicity - Replace .expect() with proper error propagation in lib.rs - Cache AI settings in AppState to avoid repeated disk reads - Cap JSONB column discovery at 50 to prevent unbounded queries - Fix ERD colorMode to respect system theme via useTheme() - Extract AppState::get_pool() replacing ~19 inline pool patterns - Extract shared AiSettingsFields component (DRY popover + sheet) - Make get_connections_path pub(crate) and reuse from docker.rs - Deduplicate check_docker by delegating to check_docker_internal Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -14,17 +14,14 @@ pub async fn list_databases(
|
||||
state: State<'_, Arc<AppState>>,
|
||||
connection_id: String,
|
||||
) -> TuskResult<Vec<String>> {
|
||||
let pools = state.pools.read().await;
|
||||
let pool = pools
|
||||
.get(&connection_id)
|
||||
.ok_or(TuskError::NotConnected(connection_id))?;
|
||||
let pool = state.get_pool(&connection_id).await?;
|
||||
|
||||
let rows = sqlx::query(
|
||||
"SELECT datname FROM pg_database \
|
||||
WHERE datistemplate = false \
|
||||
ORDER BY datname",
|
||||
)
|
||||
.fetch_all(pool)
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.map_err(TuskError::Database)?;
|
||||
|
||||
@@ -35,10 +32,7 @@ pub async fn list_schemas_core(
|
||||
state: &AppState,
|
||||
connection_id: &str,
|
||||
) -> TuskResult<Vec<String>> {
|
||||
let pools = state.pools.read().await;
|
||||
let pool = pools
|
||||
.get(connection_id)
|
||||
.ok_or_else(|| TuskError::NotConnected(connection_id.to_string()))?;
|
||||
let pool = state.get_pool(connection_id).await?;
|
||||
|
||||
let flavor = state.get_flavor(connection_id).await;
|
||||
let sql = if flavor == DbFlavor::Greenplum {
|
||||
@@ -52,7 +46,7 @@ pub async fn list_schemas_core(
|
||||
};
|
||||
|
||||
let rows = sqlx::query(sql)
|
||||
.fetch_all(pool)
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.map_err(TuskError::Database)?;
|
||||
|
||||
@@ -72,10 +66,7 @@ pub async fn list_tables_core(
|
||||
connection_id: &str,
|
||||
schema: &str,
|
||||
) -> TuskResult<Vec<SchemaObject>> {
|
||||
let pools = state.pools.read().await;
|
||||
let pool = pools
|
||||
.get(connection_id)
|
||||
.ok_or_else(|| TuskError::NotConnected(connection_id.to_string()))?;
|
||||
let pool = state.get_pool(connection_id).await?;
|
||||
|
||||
let rows = sqlx::query(
|
||||
"SELECT t.table_name, \
|
||||
@@ -88,7 +79,7 @@ pub async fn list_tables_core(
|
||||
ORDER BY t.table_name",
|
||||
)
|
||||
.bind(schema)
|
||||
.fetch_all(pool)
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.map_err(TuskError::Database)?;
|
||||
|
||||
@@ -119,10 +110,7 @@ pub async fn list_views(
|
||||
connection_id: String,
|
||||
schema: String,
|
||||
) -> TuskResult<Vec<SchemaObject>> {
|
||||
let pools = state.pools.read().await;
|
||||
let pool = pools
|
||||
.get(&connection_id)
|
||||
.ok_or(TuskError::NotConnected(connection_id))?;
|
||||
let pool = state.get_pool(&connection_id).await?;
|
||||
|
||||
let rows = sqlx::query(
|
||||
"SELECT table_name FROM information_schema.views \
|
||||
@@ -130,7 +118,7 @@ pub async fn list_views(
|
||||
ORDER BY table_name",
|
||||
)
|
||||
.bind(&schema)
|
||||
.fetch_all(pool)
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.map_err(TuskError::Database)?;
|
||||
|
||||
@@ -152,10 +140,7 @@ pub async fn list_functions(
|
||||
connection_id: String,
|
||||
schema: String,
|
||||
) -> TuskResult<Vec<SchemaObject>> {
|
||||
let pools = state.pools.read().await;
|
||||
let pool = pools
|
||||
.get(&connection_id)
|
||||
.ok_or(TuskError::NotConnected(connection_id))?;
|
||||
let pool = state.get_pool(&connection_id).await?;
|
||||
|
||||
let rows = sqlx::query(
|
||||
"SELECT routine_name FROM information_schema.routines \
|
||||
@@ -163,7 +148,7 @@ pub async fn list_functions(
|
||||
ORDER BY routine_name",
|
||||
)
|
||||
.bind(&schema)
|
||||
.fetch_all(pool)
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.map_err(TuskError::Database)?;
|
||||
|
||||
@@ -185,10 +170,7 @@ pub async fn list_indexes(
|
||||
connection_id: String,
|
||||
schema: String,
|
||||
) -> TuskResult<Vec<SchemaObject>> {
|
||||
let pools = state.pools.read().await;
|
||||
let pool = pools
|
||||
.get(&connection_id)
|
||||
.ok_or(TuskError::NotConnected(connection_id))?;
|
||||
let pool = state.get_pool(&connection_id).await?;
|
||||
|
||||
let rows = sqlx::query(
|
||||
"SELECT indexname FROM pg_indexes \
|
||||
@@ -196,7 +178,7 @@ pub async fn list_indexes(
|
||||
ORDER BY indexname",
|
||||
)
|
||||
.bind(&schema)
|
||||
.fetch_all(pool)
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.map_err(TuskError::Database)?;
|
||||
|
||||
@@ -218,10 +200,7 @@ pub async fn list_sequences(
|
||||
connection_id: String,
|
||||
schema: String,
|
||||
) -> TuskResult<Vec<SchemaObject>> {
|
||||
let pools = state.pools.read().await;
|
||||
let pool = pools
|
||||
.get(&connection_id)
|
||||
.ok_or(TuskError::NotConnected(connection_id))?;
|
||||
let pool = state.get_pool(&connection_id).await?;
|
||||
|
||||
let rows = sqlx::query(
|
||||
"SELECT sequence_name FROM information_schema.sequences \
|
||||
@@ -229,7 +208,7 @@ pub async fn list_sequences(
|
||||
ORDER BY sequence_name",
|
||||
)
|
||||
.bind(&schema)
|
||||
.fetch_all(pool)
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.map_err(TuskError::Database)?;
|
||||
|
||||
@@ -251,10 +230,7 @@ pub async fn get_table_columns_core(
|
||||
schema: &str,
|
||||
table: &str,
|
||||
) -> TuskResult<Vec<ColumnInfo>> {
|
||||
let pools = state.pools.read().await;
|
||||
let pool = pools
|
||||
.get(connection_id)
|
||||
.ok_or_else(|| TuskError::NotConnected(connection_id.to_string()))?;
|
||||
let pool = state.get_pool(connection_id).await?;
|
||||
|
||||
let rows = sqlx::query(
|
||||
"SELECT \
|
||||
@@ -287,7 +263,7 @@ pub async fn get_table_columns_core(
|
||||
)
|
||||
.bind(schema)
|
||||
.bind(table)
|
||||
.fetch_all(pool)
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.map_err(TuskError::Database)?;
|
||||
|
||||
@@ -323,10 +299,7 @@ pub async fn get_table_constraints(
|
||||
schema: String,
|
||||
table: String,
|
||||
) -> TuskResult<Vec<ConstraintInfo>> {
|
||||
let pools = state.pools.read().await;
|
||||
let pool = pools
|
||||
.get(&connection_id)
|
||||
.ok_or(TuskError::NotConnected(connection_id))?;
|
||||
let pool = state.get_pool(&connection_id).await?;
|
||||
|
||||
let rows = sqlx::query(
|
||||
"SELECT \
|
||||
@@ -376,7 +349,7 @@ pub async fn get_table_constraints(
|
||||
)
|
||||
.bind(&schema)
|
||||
.bind(&table)
|
||||
.fetch_all(pool)
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.map_err(TuskError::Database)?;
|
||||
|
||||
@@ -402,10 +375,7 @@ pub async fn get_table_indexes(
|
||||
schema: String,
|
||||
table: String,
|
||||
) -> TuskResult<Vec<IndexInfo>> {
|
||||
let pools = state.pools.read().await;
|
||||
let pool = pools
|
||||
.get(&connection_id)
|
||||
.ok_or(TuskError::NotConnected(connection_id))?;
|
||||
let pool = state.get_pool(&connection_id).await?;
|
||||
|
||||
let rows = sqlx::query(
|
||||
"SELECT \
|
||||
@@ -422,7 +392,7 @@ pub async fn get_table_indexes(
|
||||
)
|
||||
.bind(&schema)
|
||||
.bind(&table)
|
||||
.fetch_all(pool)
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.map_err(TuskError::Database)?;
|
||||
|
||||
@@ -443,10 +413,7 @@ pub async fn get_completion_schema(
|
||||
connection_id: String,
|
||||
) -> TuskResult<HashMap<String, HashMap<String, Vec<String>>>> {
|
||||
let flavor = state.get_flavor(&connection_id).await;
|
||||
let pools = state.pools.read().await;
|
||||
let pool = pools
|
||||
.get(&connection_id)
|
||||
.ok_or(TuskError::NotConnected(connection_id))?;
|
||||
let pool = state.get_pool(&connection_id).await?;
|
||||
|
||||
let sql = if flavor == DbFlavor::Greenplum {
|
||||
"SELECT table_schema, table_name, column_name \
|
||||
@@ -461,7 +428,7 @@ pub async fn get_completion_schema(
|
||||
};
|
||||
|
||||
let rows = sqlx::query(sql)
|
||||
.fetch_all(pool)
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.map_err(TuskError::Database)?;
|
||||
|
||||
@@ -490,10 +457,7 @@ pub async fn get_column_details(
|
||||
table: String,
|
||||
) -> TuskResult<Vec<ColumnDetail>> {
|
||||
let flavor = state.get_flavor(&connection_id).await;
|
||||
let pools = state.pools.read().await;
|
||||
let pool = pools
|
||||
.get(&connection_id)
|
||||
.ok_or(TuskError::NotConnected(connection_id))?;
|
||||
let pool = state.get_pool(&connection_id).await?;
|
||||
|
||||
let sql = if flavor == DbFlavor::Greenplum {
|
||||
"SELECT c.column_name, c.data_type, \
|
||||
@@ -516,7 +480,7 @@ pub async fn get_column_details(
|
||||
let rows = sqlx::query(sql)
|
||||
.bind(&schema)
|
||||
.bind(&table)
|
||||
.fetch_all(pool)
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.map_err(TuskError::Database)?;
|
||||
|
||||
@@ -539,10 +503,7 @@ pub async fn get_table_triggers(
|
||||
schema: String,
|
||||
table: String,
|
||||
) -> TuskResult<Vec<TriggerInfo>> {
|
||||
let pools = state.pools.read().await;
|
||||
let pool = pools
|
||||
.get(&connection_id)
|
||||
.ok_or(TuskError::NotConnected(connection_id))?;
|
||||
let pool = state.get_pool(&connection_id).await?;
|
||||
|
||||
let rows = sqlx::query(
|
||||
"SELECT \
|
||||
@@ -571,7 +532,7 @@ pub async fn get_table_triggers(
|
||||
)
|
||||
.bind(&schema)
|
||||
.bind(&table)
|
||||
.fetch_all(pool)
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.map_err(TuskError::Database)?;
|
||||
|
||||
@@ -595,10 +556,7 @@ pub async fn get_schema_erd(
|
||||
connection_id: String,
|
||||
schema: String,
|
||||
) -> TuskResult<ErdData> {
|
||||
let pools = state.pools.read().await;
|
||||
let pool = pools
|
||||
.get(&connection_id)
|
||||
.ok_or(TuskError::NotConnected(connection_id))?;
|
||||
let pool = state.get_pool(&connection_id).await?;
|
||||
|
||||
// Get all tables with columns
|
||||
let col_rows = sqlx::query(
|
||||
@@ -627,7 +585,7 @@ pub async fn get_schema_erd(
|
||||
ORDER BY c.table_name, c.ordinal_position",
|
||||
)
|
||||
.bind(&schema)
|
||||
.fetch_all(pool)
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.map_err(TuskError::Database)?;
|
||||
|
||||
@@ -690,7 +648,7 @@ pub async fn get_schema_erd(
|
||||
ORDER BY c.conname",
|
||||
)
|
||||
.bind(&schema)
|
||||
.fetch_all(pool)
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.map_err(TuskError::Database)?;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user