Skip to content

Commit 04895c1

Browse files
vsilentCopilot
andcommitted
security: defense-in-depth — add user_id to DB delete/fetch functions
- project::fetch_one_by_name: add user_id filter (prevents cross-user name lookup) - project::delete: add user_id to WHERE clause - cloud::delete: add user_id to WHERE clause - server::delete: add user_id to WHERE clause - All callers updated to pass user.id - Returns rows_affected > 0 instead of always true - Added .sqlx/ cache for updated fetch_one_by_name query Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 0bf301a commit 04895c1

File tree

9 files changed

+98
-16
lines changed

9 files changed

+98
-16
lines changed

.sqlx/query-3fd71974a7948b85a0fa72d2c583e29118c63af715e14d9b0a50ef672b8b4d97.json

Lines changed: 77 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/db/cloud.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,13 +158,14 @@ pub async fn update(pool: &PgPool, mut cloud: models::Cloud) -> Result<models::C
158158
}
159159

160160
#[tracing::instrument(name = "Delete cloud of a user.")]
161-
pub async fn delete(pool: &PgPool, id: i32) -> Result<bool, String> {
161+
pub async fn delete(pool: &PgPool, id: i32, user_id: &str) -> Result<bool, String> {
162162
tracing::info!("Delete cloud {}", id);
163-
sqlx::query::<sqlx::Postgres>("DELETE FROM cloud WHERE id = $1;")
163+
sqlx::query::<sqlx::Postgres>("DELETE FROM cloud WHERE id = $1 AND user_id = $2;")
164164
.bind(id)
165+
.bind(user_id)
165166
.execute(pool)
166167
.await
167-
.map(|_| true)
168+
.map(|r| r.rows_affected() > 0)
168169
.map_err(|err| {
169170
tracing::error!("Failed to delete cloud: {:?}", err);
170171
"Failed to delete cloud".to_string()

src/db/project.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ pub async fn fetch_by_user(pool: &PgPool, user_id: &str) -> Result<Vec<models::P
5151
pub async fn fetch_one_by_name(
5252
pool: &PgPool,
5353
name: &str,
54+
user_id: &str,
5455
) -> Result<Option<models::Project>, String> {
5556
let query_span = tracing::info_span!("Fetch one project by name.");
5657
sqlx::query_as!(
@@ -59,10 +60,11 @@ pub async fn fetch_one_by_name(
5960
SELECT
6061
*
6162
FROM project
62-
WHERE name=$1
63+
WHERE name=$1 AND user_id=$2
6364
LIMIT 1
6465
"#,
65-
name
66+
name,
67+
user_id
6668
)
6769
.fetch_one(pool)
6870
.instrument(query_span)
@@ -150,13 +152,14 @@ pub async fn update(
150152
}
151153

152154
#[tracing::instrument(name = "Delete user's project.")]
153-
pub async fn delete(pool: &PgPool, id: i32) -> Result<bool, String> {
155+
pub async fn delete(pool: &PgPool, id: i32, user_id: &str) -> Result<bool, String> {
154156
tracing::info!("Delete project {}", id);
155-
sqlx::query::<sqlx::Postgres>("DELETE FROM project WHERE id = $1;")
157+
sqlx::query::<sqlx::Postgres>("DELETE FROM project WHERE id = $1 AND user_id = $2;")
156158
.bind(id)
159+
.bind(user_id)
157160
.execute(pool)
158161
.await
159-
.map(|_| true)
162+
.map(|r| r.rows_affected() > 0)
160163
.map_err(|err| {
161164
tracing::error!("Failed to delete project: {:?}", err);
162165
"Failed to delete project".to_string()

src/db/server.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -325,13 +325,14 @@ pub async fn update_srv_ip(
325325
}
326326

327327
#[tracing::instrument(name = "Delete user's server.")]
328-
pub async fn delete(pool: &PgPool, id: i32) -> Result<bool, String> {
328+
pub async fn delete(pool: &PgPool, id: i32, user_id: &str) -> Result<bool, String> {
329329
tracing::info!("Delete server {}", id);
330-
sqlx::query::<sqlx::Postgres>("DELETE FROM server WHERE id = $1;")
330+
sqlx::query::<sqlx::Postgres>("DELETE FROM server WHERE id = $1 AND user_id = $2;")
331331
.bind(id)
332+
.bind(user_id)
332333
.execute(pool)
333334
.await
334-
.map(|_| true)
335+
.map(|r| r.rows_affected() > 0)
335336
.map_err(|err| {
336337
tracing::error!("Failed to delete server: {:?}", err);
337338
"Failed to delete server".to_string()

src/mcp/tools/cloud.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ impl ToolHandler for DeleteCloudTool {
169169
.map_err(|e| format!("Cloud error: {}", e))?
170170
.ok_or_else(|| "Cloud not found".to_string())?;
171171

172-
db::cloud::delete(&context.pg_pool, args.id)
172+
db::cloud::delete(&context.pg_pool, args.id, &context.user.id)
173173
.await
174174
.map_err(|e| format!("Failed to delete cloud: {}", e))?;
175175

src/mcp/tools/compose.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ impl ToolHandler for DeleteProjectTool {
3030
return Err("Unauthorized: You do not own this project".to_string());
3131
}
3232

33-
db::project::delete(&context.pg_pool, args.project_id)
33+
db::project::delete(&context.pg_pool, args.project_id, &context.user.id)
3434
.await
3535
.map_err(|e| format!("Failed to delete project: {}", e))?;
3636

src/routes/cloud/delete.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ pub async fn item(
2727
None => Err(JsonResponse::<models::Cloud>::build().not_found("not found")),
2828
})?;
2929

30-
db::cloud::delete(pg_pool.get_ref(), cloud.id)
30+
db::cloud::delete(pg_pool.get_ref(), cloud.id, &user.id)
3131
.await
3232
.map_err(|err| JsonResponse::<Cloud>::build().internal_server_error(err))
3333
.and_then(|result| match result {

src/routes/project/delete.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ pub async fn item(
2727
None => Err(JsonResponse::<models::Project>::build().not_found("")),
2828
})?;
2929

30-
db::project::delete(pg_pool.get_ref(), project.id)
30+
db::project::delete(pg_pool.get_ref(), project.id, &user.id)
3131
.await
3232
.map_err(|err| JsonResponse::<Project>::build().internal_server_error(err))
3333
.and_then(|result| match result {

src/routes/server/delete.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ pub async fn item(
152152
}
153153

154154
// 4. Delete server record from DB
155-
db::server::delete(pg_pool.get_ref(), server.id)
155+
db::server::delete(pg_pool.get_ref(), server.id, &user.id)
156156
.await
157157
.map_err(|err| JsonResponse::<Server>::build().internal_server_error(err))
158158
.and_then(|result| match result {

0 commit comments

Comments
 (0)