fix: resolve all remaining audit failures across rust, svelte, and java

This commit is contained in:
scoped committed 2026-09-29 15:51:15 -04:00
1 parent bc12a25a01
commit 590178e41a
35 files changed
+958 -300

No files matched your search

+81 -16
View File
@@ -10,6 +10,62 @@ use scopenet_shared::{BaltopEntry, EconomyTransaction, MarketListing, ServerEcon
use serde::Deserialize;
use serde_json::Value;
async fn begin_operation(state: &AppState, server_id: i64, operation_id: &str) -> AppResult<(sqlx::Transaction<'static, sqlx::Sqlite>, Option<Value>)> {
if operation_id.is_empty() || operation_id.len() > 100 { return Err(AppError::bad_request("Invalid operation ID")); }
let mut tx = state.db.begin().await?;
// The first statement obtains the write lock before reading any balances.
let inserted = sqlx::query("INSERT OR IGNORE INTO economy_operations(server_id, operation_id, response) VALUES (?, ?, '')")
.bind(server_id).bind(operation_id).execute(&mut *tx).await?.rows_affected();
let previous = if inserted == 0 {
let raw: String = sqlx::query_scalar("SELECT response FROM economy_operations WHERE server_id = ? AND operation_id = ?")
.bind(server_id).bind(operation_id).fetch_one(&mut *tx).await?;
Some(serde_json::from_str(&raw)?)
} else { None };
Ok((tx, previous))
}
async fn finish_operation(mut tx: sqlx::Transaction<'_, sqlx::Sqlite>, server_id: i64, operation_id: &str, response: Value) -> AppResult<Json<Value>> {
sqlx::query("UPDATE economy_operations SET response = ? WHERE server_id = ? AND operation_id = ?")
.bind(response.to_string()).bind(server_id).bind(operation_id).execute(&mut *tx).await?;
tx.commit().await?;
Ok(Json(response))
}
async fn ensure_balance(tx: &mut sqlx::Transaction<'_, sqlx::Sqlite>, server_id: i64, uuid: &str, name: &str) -> AppResult<()> {
sqlx::query("INSERT INTO server_economy(server_id, uuid, username, balance, updated_at) VALUES (?, ?, ?, 1000, ?) ON CONFLICT(server_id, uuid) DO NOTHING")
.bind(server_id).bind(uuid).bind(name).bind(chrono::Utc::now().to_rfc3339()).execute(&mut **tx).await?;
Ok(())
}
#[derive(Deserialize)]
pub struct AdjustBalancePayload {
pub uuid: String,
pub username: String,
pub delta: f64,
pub operation_id: String,
pub description: String,
}
pub async fn server_adjust_balance(GameServer(server): GameServer, State(state): State<AppState>, Json(p): Json<AdjustBalancePayload>) -> AppResult<Json<Value>> {
if !p.delta.is_finite() || p.delta == 0.0 || p.delta.abs() > 1e12 { return Err(AppError::bad_request("Invalid amount")); }
let (mut tx, previous) = begin_operation(&state, server.id, &p.operation_id).await?;
if let Some(previous) = previous { return Ok(Json(previous)); }
ensure_balance(&mut tx, server.id, &p.uuid, &p.username).await?;
let balance: Option<f64> = sqlx::query_scalar("UPDATE server_economy SET balance = balance + ?, updated_at = ? WHERE server_id = ? AND uuid = ? AND balance + ? >= 0 RETURNING balance")
.bind(p.delta).bind(chrono::Utc::now().to_rfc3339()).bind(server.id).bind(&p.uuid).bind(p.delta).fetch_optional(&mut *tx).await?;
let Some(balance) = balance else { return Err(AppError::bad_request("Insufficient funds")); };
let (from, from_name, to, to_name) = if p.delta > 0.0 { ("server", "Server Shop", p.uuid.as_str(), p.username.as_str()) } else { (p.uuid.as_str(), p.username.as_str(), "server", "Server Shop") };
sqlx::query("INSERT INTO economy_transactions(server_id, from_uuid, from_name, to_uuid, to_name, amount, description, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)")
.bind(server.id).bind(from).bind(from_name).bind(to).bind(to_name).bind(p.delta.abs()).bind(&p.description).bind(chrono::Utc::now().to_rfc3339()).execute(&mut *tx).await?;
finish_operation(tx, server.id, &p.operation_id, serde_json::json!({"ok": true, "balance": balance})).await
}
pub async fn server_market_read(GameServer(server): GameServer, State(state): State<AppState>) -> AppResult<Json<Vec<Value>>> {
let rows: Vec<(i64, String, String, String, i32, f64, Option<String>)> = sqlx::query_as("SELECT id, seller_name, item_id, item_name, amount, price, item_data FROM server_market WHERE server_id = ? ORDER BY id DESC LIMIT 45")
.bind(server.id).fetch_all(&state.db).await?;
Ok(Json(rows.into_iter().map(|(id, seller, item_id, item_name, amount, price, data)| serde_json::json!({"id":id,"seller_name":seller,"item_id":item_id,"item_name":item_name,"amount":amount,"price":price,"item_data":data})).collect()))
}
// ---------------------------------------------------------------------------
// Launcher & Public Economy API
// ---------------------------------------------------------------------------
@@ -213,7 +269,7 @@ pub async fn server_transfer(
State(state): State<AppState>,
Json(payload): Json<ServerTransferPayload>,
) -> AppResult<Json<Value>> {
if payload.amount <= 0.0 {
if !payload.amount.is_finite() || payload.amount <= 0.0 {
return Err(AppError::bad_request("Transfer amount must be positive"));
}
@@ -347,6 +403,8 @@ pub async fn server_baltop(
#[derive(Deserialize)]
pub struct MarketListPayload {
pub operation_id: String,
pub item_data: Option<String>,
pub seller_uuid: String,
pub seller_name: String,
pub item_id: String,
@@ -360,10 +418,13 @@ pub async fn server_market_list(
State(state): State<AppState>,
Json(payload): Json<MarketListPayload>,
) -> AppResult<Json<Value>> {
if !payload.price.is_finite() || payload.price <= 0.0 || payload.amount <= 0 || payload.amount > 64 { return Err(AppError::bad_request("Invalid listing")); }
let (mut tx, previous) = begin_operation(&state, server.id, &payload.operation_id).await?;
if let Some(previous) = previous { return Ok(Json(previous)); }
let now = chrono::Utc::now().to_rfc3339();
let id: i64 = sqlx::query_scalar(
"INSERT INTO server_market (server_id, seller_uuid, seller_name, item_id, item_name, amount, price, created_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
"INSERT INTO server_market (server_id, seller_uuid, seller_name, item_id, item_name, amount, price, created_at, item_data)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
RETURNING id",
)
.bind(server.id)
@@ -374,14 +435,16 @@ pub async fn server_market_list(
.bind(payload.amount)
.bind(payload.price)
.bind(&now)
.fetch_one(&state.db)
.bind(&payload.item_data)
.fetch_one(&mut *tx)
.await?;
Ok(Json(serde_json::json!({ "id": id, "ok": true })))
finish_operation(tx, server.id, &payload.operation_id, serde_json::json!({ "id": id, "ok": true })).await
}
#[derive(Deserialize)]
pub struct MarketBuyPayload {
pub operation_id: String,
pub listing_id: i64,
pub buyer_uuid: String,
pub buyer_name: String,
@@ -392,21 +455,24 @@ pub async fn server_market_buy(
State(state): State<AppState>,
Json(payload): Json<MarketBuyPayload>,
) -> AppResult<Json<Value>> {
let listing: Option<(String, String, String, String, i32, f64)> = sqlx::query_as(
"SELECT seller_uuid, seller_name, item_id, item_name, amount, price
FROM server_market
WHERE id = ? AND server_id = ?",
let (mut tx, previous) = begin_operation(&state, server.id, &payload.operation_id).await?;
if let Some(previous) = previous { return Ok(Json(previous)); }
let listing: Option<(String, String, String, String, i32, f64, Option<String>)> = sqlx::query_as(
"DELETE FROM server_market
WHERE id = ? AND server_id = ?
RETURNING seller_uuid, seller_name, item_id, item_name, amount, price, item_data
",
)
.bind(payload.listing_id)
.bind(server.id)
.fetch_optional(&state.db)
.fetch_optional(&mut *tx)
.await?;
let Some((seller_uuid, seller_name, item_id, item_name, amount, price)) = listing else {
let Some((seller_uuid, seller_name, item_id, item_name, amount, price, item_data)) = listing else {
return Err(AppError::not_found("Listing not found"));
};
let mut tx = state.db.begin().await?;
ensure_balance(&mut tx, server.id, &payload.buyer_uuid, &payload.buyer_name).await?;
let now = chrono::Utc::now().to_rfc3339();
// Deduct buyer
@@ -471,13 +537,12 @@ pub async fn server_market_buy(
.execute(&mut *tx)
.await?;
tx.commit().await?;
Ok(Json(serde_json::json!({
finish_operation(tx, server.id, &payload.operation_id, serde_json::json!({
"ok": true,
"item_id": item_id,
"item_data": item_data,
"item_name": item_name,
"amount": amount,
"price": price
})))
})).await
}