diff --git a/backend/Cargo.lock b/backend/Cargo.lock index bddd351..ac402f6 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -665,19 +665,6 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" -[[package]] -name = "bcrypt" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e65938ed058ef47d92cf8b346cc76ef48984572ade631927e9937b5ffc7662c7" -dependencies = [ - "base64 0.22.1", - "blowfish", - "getrandom 0.2.17", - "subtle", - "zeroize", -] - [[package]] name = "bigdecimal" version = "0.4.10" @@ -750,16 +737,6 @@ dependencies = [ "piper", ] -[[package]] -name = "blowfish" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" -dependencies = [ - "byteorder", - "cipher", -] - [[package]] name = "borsh" version = "1.6.1" @@ -941,16 +918,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", -] - [[package]] name = "clap" version = "4.6.1" @@ -2012,15 +1979,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "inout" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" -dependencies = [ - "generic-array", -] - [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -3556,7 +3514,6 @@ version = "0.1.0" dependencies = [ "argon2", "base64 0.22.1", - "bcrypt", "chess", "chrono", "db", diff --git a/backend/modules/api/src/auth.rs b/backend/modules/api/src/auth.rs index 29da899..8290f22 100644 --- a/backend/modules/api/src/auth.rs +++ b/backend/modules/api/src/auth.rs @@ -210,13 +210,14 @@ pub async fn refresh( }; // Extract Bearer token - let token = if auth_header.starts_with("Bearer ") { - &auth_header[7..] - } else { - return HttpResponse::Unauthorized().json(ErrorResponse { - message: "Invalid authorization format".to_string(), - code: "INVALID_AUTH_FORMAT".to_string(), - }); + let token = match auth_header.strip_prefix("Bearer ") { + Some(t) => t, + None => { + return HttpResponse::Unauthorized().json(ErrorResponse { + message: "Invalid authorization format".to_string(), + code: "INVALID_AUTH_FORMAT".to_string(), + }); + } }; // Validate access token and get user info @@ -351,13 +352,14 @@ pub async fn logout( } }; - let token = if auth_header.starts_with("Bearer ") { - &auth_header[7..] - } else { - return HttpResponse::Unauthorized().json(ErrorResponse { - message: "Invalid authorization format".to_string(), - code: "INVALID_AUTH_FORMAT".to_string(), - }); + let token = match auth_header.strip_prefix("Bearer ") { + Some(t) => t, + None => { + return HttpResponse::Unauthorized().json(ErrorResponse { + message: "Invalid authorization format".to_string(), + code: "INVALID_AUTH_FORMAT".to_string(), + }); + } }; // Validate the token and extract the actual user ID diff --git a/backend/modules/api/src/test/rate_limit.rs b/backend/modules/api/src/test/rate_limit.rs index e36e992..b60fa76 100644 --- a/backend/modules/api/src/test/rate_limit.rs +++ b/backend/modules/api/src/test/rate_limit.rs @@ -1,7 +1,5 @@ use actix_governor::{Governor, GovernorConfigBuilder}; use actix_web::{test, web, App, HttpResponse, Responder}; -use std::thread; -use std::time::Duration; async fn mock_handler() -> impl Responder { HttpResponse::Ok().body("OK") diff --git a/backend/modules/api/src/ws.rs b/backend/modules/api/src/ws.rs index d8586be..687fa5d 100644 --- a/backend/modules/api/src/ws.rs +++ b/backend/modules/api/src/ws.rs @@ -367,7 +367,6 @@ fn validate_reconnect_token(token: &str) -> Result { #[cfg(test)] mod tests { use super::*; - use actix::prelude::*; use tokio::sync::mpsc::unbounded_channel; struct TestRecipient { diff --git a/backend/modules/chess/src/bitboard/bitboard.rs b/backend/modules/chess/src/bitboard/bitboard.rs index 16321ed..6fe201e 100644 --- a/backend/modules/chess/src/bitboard/bitboard.rs +++ b/backend/modules/chess/src/bitboard/bitboard.rs @@ -26,6 +26,7 @@ impl Bitboard { (self.0 & (1 << square)) != 0 } + #[allow(clippy::should_implement_trait)] pub fn add(self, square: u64) -> Bitboard { Bitboard(self.0 | (1 << square)) } diff --git a/backend/modules/chess/src/bitboard/mod.rs b/backend/modules/chess/src/bitboard/mod.rs index edf4055..77cfd3b 100644 --- a/backend/modules/chess/src/bitboard/mod.rs +++ b/backend/modules/chess/src/bitboard/mod.rs @@ -1,2 +1,3 @@ +#[allow(clippy::module_inception)] pub mod bitboard; pub mod board; diff --git a/backend/modules/chess/src/pgn.rs b/backend/modules/chess/src/pgn.rs index b2b5d35..26cc7b3 100644 --- a/backend/modules/chess/src/pgn.rs +++ b/backend/modules/chess/src/pgn.rs @@ -45,7 +45,6 @@ pub enum GameResult { Ongoing, } - impl GameResult { /// Parse a result string from PGN format pub fn from_pgn_string(s: &str) -> Result { diff --git a/backend/modules/db/src/db.rs b/backend/modules/db/src/db.rs index 0a12d65..151ce9c 100644 --- a/backend/modules/db/src/db.rs +++ b/backend/modules/db/src/db.rs @@ -1,3 +1,4 @@ +#[allow(clippy::module_inception)] pub mod db { use sea_orm::{ConnectOptions, Database, DatabaseConnection}; diff --git a/backend/modules/dto/src/ai.rs b/backend/modules/dto/src/ai.rs index c61ddbf..ebbdf93 100644 --- a/backend/modules/dto/src/ai.rs +++ b/backend/modules/dto/src/ai.rs @@ -2,21 +2,31 @@ use once_cell::sync::Lazy; use regex::Regex; use serde::{Deserialize, Serialize}; use utoipa::ToSchema; -use validator::Validate; +use validator::{Validate, ValidationError}; -// Define a regex for validating FEN chess position notation -static FEN_REGEX: Lazy = Lazy::new(|| { +static FEN_STRUCTURE: Lazy = Lazy::new(|| { Regex::new( - r"^(?=\S*K)(?=\S*k)([rnbqkpRNBQKP1-8]+/){7}[rnbqkpRNBQKP1-8]+\s[bw]\s(-|[KQkq]+)\s(-|[a-h][36])\s\d+\s\d+$" - ).unwrap() + r"^([rnbqkpRNBQKP1-8]+/){7}[rnbqkpRNBQKP1-8]+\s[bw]\s(-|[KQkq]+)\s(-|[a-h][36])\s\d+\s\d+$", + ) + .unwrap() }); +fn validate_fen(fen: &str) -> Result<(), ValidationError> { + if !FEN_STRUCTURE.is_match(fen) { + return Err(ValidationError::new("Must be a valid FEN string")); + } + if !fen.contains('K') { + return Err(ValidationError::new("FEN must contain a white king")); + } + if !fen.contains('k') { + return Err(ValidationError::new("FEN must contain a black king")); + } + Ok(()) +} + #[derive(Debug, Serialize, Deserialize, ToSchema, Validate)] pub struct AiSuggestionRequest { - #[validate(regex( - path = "FEN_REGEX", - message = "Must be a valid FEN string in format: [piece placement] [active color] [castling] [en passant] [halfmove clock] [fullmove number]" - ))] + #[validate(custom(function = "validate_fen"))] #[schema(example = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")] pub fen: String, @@ -52,10 +62,7 @@ pub struct AiSuggestionResponse { #[derive(Debug, Serialize, Deserialize, ToSchema, Validate)] pub struct PositionAnalysisRequest { - #[validate(regex( - path = "FEN_REGEX", - message = "Must be a valid FEN string in format: [piece placement] [active color] [castling] [en passant] [halfmove clock] [fullmove number]" - ))] + #[validate(custom(function = "validate_fen"))] #[schema(example = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")] pub fen: String, diff --git a/backend/modules/matchmaking/models.rs b/backend/modules/matchmaking/models.rs index b8a826c..5404e89 100644 --- a/backend/modules/matchmaking/models.rs +++ b/backend/modules/matchmaking/models.rs @@ -32,7 +32,7 @@ impl TimeControl { "bullet" => 60, "blitz" => 180, "rapid" => 480, - "standard" | _ => 600, + _ => 600, } } diff --git a/backend/modules/security/src/jwt.rs b/backend/modules/security/src/jwt.rs index 96400b8..9c8e015 100644 --- a/backend/modules/security/src/jwt.rs +++ b/backend/modules/security/src/jwt.rs @@ -138,11 +138,7 @@ impl JwtService { /// Extract token from Authorization header pub fn extract_token_from_header(auth_header: &str) -> Option { - if auth_header.starts_with("Bearer ") { - Some(auth_header[7..].to_string()) - } else { - None - } + auth_header.strip_prefix("Bearer ").map(|s| s.to_string()) } } diff --git a/backend/modules/service/Cargo.toml b/backend/modules/service/Cargo.toml index 9443d20..f44eed2 100644 --- a/backend/modules/service/Cargo.toml +++ b/backend/modules/service/Cargo.toml @@ -6,7 +6,6 @@ edition = "2021" [dependencies] sea-orm = { version = "1.1.0", features = [ "sqlx-postgres", "runtime-tokio-native-tls", "macros", "mock" ] } uuid = { version = "1", features = ["v4", "serde"] } -bcrypt = "0.15" argon2 = "0.5" rand = "0.8" chrono = { version = "0.4", features = ["serde"] } diff --git a/backend/modules/service/src/engine_service.rs b/backend/modules/service/src/engine_service.rs index d887a87..748ce86 100644 --- a/backend/modules/service/src/engine_service.rs +++ b/backend/modules/service/src/engine_service.rs @@ -5,6 +5,7 @@ use tokio::sync::Mutex; use uuid::Uuid; pub struct EngineService { + #[allow(dead_code)] engines: Arc>>>, engine_path: String, } diff --git a/backend/modules/service/src/lib.rs b/backend/modules/service/src/lib.rs index 547859e..22dd26a 100644 --- a/backend/modules/service/src/lib.rs +++ b/backend/modules/service/src/lib.rs @@ -2,3 +2,4 @@ pub mod engine_service; pub mod games; pub mod helper; pub mod players; +pub mod user; diff --git a/backend/modules/service/src/players.rs b/backend/modules/service/src/players.rs index aec8bfb..bc679bc 100644 --- a/backend/modules/service/src/players.rs +++ b/backend/modules/service/src/players.rs @@ -156,6 +156,31 @@ pub async fn update_player(id: Uuid, payload: UpdatePlayer) -> Result Result { + let db = get_db().await; + + let user = player::Entity::find() + .filter(player::Column::Username.eq(username)) + .filter(player::Column::IsEnabled.eq(true)) + .one(&db) + .await?; + + match user { + Some(usr) => { + let stored_hash = String::from_utf8(usr.password_hash.clone()) + .map_err(|_| ApiError::InvalidCredentials)?; + match password::verify_password(password, &stored_hash) { + Ok(()) => Ok(usr), + Err(_) => Err(ApiError::InvalidCredentials), + } + } + None => Err(ApiError::InvalidCredentials), + } +} + pub async fn delete_player(id: Uuid) -> Result<(), ApiError> { let db = get_db().await; let existing_player = find_player_by_id(id).await?; diff --git a/backend/modules/service/src/user.rs b/backend/modules/service/src/user.rs index 21c0a11..553ccbb 100644 --- a/backend/modules/service/src/user.rs +++ b/backend/modules/service/src/user.rs @@ -1,10 +1,9 @@ +use crate::helper::password; +use chrono::Utc; +use db_entity::user; use sea_orm::{ - ActiveModelTrait, ColumnTrait, DatabaseConnection, DbErr, EntityTrait, ActiveValue, + ActiveModelTrait, ActiveValue, ColumnTrait, DatabaseConnection, DbErr, EntityTrait, QueryFilter, }; -use db_entity::user::{self, Entity as UserEntity}; -use chrono::Utc; -use bcrypt::{hash, verify, DEFAULT_COST}; - /// User service for authentication and user management pub struct UserService; @@ -37,8 +36,7 @@ impl UserService { return Err(DbErr::Custom("Email already exists".to_string())); } - // Hash password - let password_hash = hash(password, DEFAULT_COST) + let password_hash = password::hash_password(password) .map_err(|_| DbErr::Custom("Failed to hash password".to_string()))?; let now = Utc::now(); @@ -70,16 +68,9 @@ impl UserService { match user { Some(user_model) => { - // Verify password - match verify(password, &user_model.password_hash) { - Ok(is_valid) => { - if is_valid { - Ok(user_model) - } else { - Err(DbErr::Custom("Invalid password".to_string())) - } - } - Err(_) => Err(DbErr::Custom("Authentication failed".to_string())), + match password::verify_password(password, &user_model.password_hash) { + Ok(()) => Ok(user_model), + Err(_) => Err(DbErr::Custom("Invalid password".to_string())), } } None => Err(DbErr::Custom("User not found".to_string())), @@ -87,7 +78,10 @@ impl UserService { } /// Get user by ID - pub async fn get_by_id(db: &DatabaseConnection, user_id: i32) -> Result, DbErr> { + pub async fn get_by_id( + db: &DatabaseConnection, + user_id: i32, + ) -> Result, DbErr> { user::Entity::find_by_id(user_id).one(db).await } @@ -103,7 +97,10 @@ impl UserService { } /// Get user by email - pub async fn get_by_email(db: &DatabaseConnection, email: &str) -> Result, DbErr> { + pub async fn get_by_email( + db: &DatabaseConnection, + email: &str, + ) -> Result, DbErr> { user::Entity::find() .filter(user::Column::Email.eq(email)) .one(db) diff --git a/backend/modules/st_core/src/models.rs b/backend/modules/st_core/src/models.rs index cba6aec..15cde2e 100644 --- a/backend/modules/st_core/src/models.rs +++ b/backend/modules/st_core/src/models.rs @@ -44,4 +44,3 @@ pub struct StellarAssetInfo { pub fixed_number: u32, pub display_decimals: u8, } - diff --git a/backend/modules/st_core/src/nft.rs b/backend/modules/st_core/src/nft.rs index f8ce6df..5a0b877 100644 --- a/backend/modules/st_core/src/nft.rs +++ b/backend/modules/st_core/src/nft.rs @@ -177,7 +177,7 @@ mod tests { #[test] fn test_format_ai_metadata() { - let mut metadata = AIMetadata { + let metadata = AIMetadata { name: " Test AI ".to_string(), description: " Test Description ".to_string(), code: "testai".to_string(), diff --git a/backend/modules/st_core/tests/integration_test.rs b/backend/modules/st_core/tests/integration_test.rs index a89c531..2ee69e4 100644 --- a/backend/modules/st_core/tests/integration_test.rs +++ b/backend/modules/st_core/tests/integration_test.rs @@ -37,7 +37,7 @@ mod tests { #[tokio::test] async fn test_ai_metadata_formatting() { - let mut metadata = AIMetadata { + let metadata = AIMetadata { name: " Test AI ".to_string(), description: " Test Description ".to_string(), url: "ipfs://QmTest123".to_string(), diff --git a/backend/modules/tournament/src/swiss/pairer.rs b/backend/modules/tournament/src/swiss/pairer.rs index b2fb479..6b6d53a 100644 --- a/backend/modules/tournament/src/swiss/pairer.rs +++ b/backend/modules/tournament/src/swiss/pairer.rs @@ -2,6 +2,7 @@ use super::*; use std::collections::HashMap; pub struct SwissPairer { + #[allow(dead_code)] config: SwissConfig, } @@ -230,7 +231,6 @@ impl SwissPairer { } // Color balance preference - self.check_color_preference(player1, player2) } diff --git a/contracts/game_contract/src/lib.rs b/contracts/game_contract/src/lib.rs index c73cfc7..929d889 100644 --- a/contracts/game_contract/src/lib.rs +++ b/contracts/game_contract/src/lib.rs @@ -501,7 +501,12 @@ impl GameContract { Ok(()) } - pub fn claim_draw(env: Env, game_id: u64, player: Address, signature: BytesN<64>) -> Result<(), ContractError> { + pub fn claim_draw( + env: Env, + game_id: u64, + player: Address, + signature: BytesN<64>, + ) -> Result<(), ContractError> { let mut games: Map = env .storage() .instance() @@ -1233,10 +1238,8 @@ impl GameContract { env.storage().instance().set(&BALANCES, &balances); env.storage().instance().set(&TREASURY, &treasury); - env.events().publish( - (symbol_short!("pzlbatch"),), - (proofs.len(), total_claimed), - ); + env.events() + .publish((symbol_short!("pzlbatch"),), (proofs.len(), total_claimed)); Ok(()) } @@ -1751,10 +1754,8 @@ impl GameContract { verified.set(account.clone(), true); env.storage().instance().set(&SEP10_VERIFIED, &verified); - env.events().publish( - (symbol_short!("sep10"), symbol_short!("verified")), - account, - ); + env.events() + .publish((symbol_short!("sep10"), symbol_short!("verified")), account); Ok(()) } @@ -1808,7 +1809,9 @@ impl GameContract { } env.storage().instance().set(&MULTISIG_SIGNERS, &signers); - env.storage().instance().set(&MULTISIG_THRESHOLD, &threshold); + env.storage() + .instance() + .set(&MULTISIG_THRESHOLD, &threshold); Ok(()) } @@ -1952,11 +1955,7 @@ impl GameContract { return Err(ContractError::NotASigner); } - if !env - .storage() - .instance() - .has(&PENDING_FEE_PROPOSAL) - { + if !env.storage().instance().has(&PENDING_FEE_PROPOSAL) { return Err(ContractError::NoProposal); } @@ -1965,10 +1964,8 @@ impl GameContract { env.storage().instance().remove(&PENDING_FEE_PROPOSAL); env.storage().instance().remove(&FEE_PROPOSAL_APPROVALS); - env.events().publish( - (symbol_short!("multisig"), symbol_short!("cancel")), - signer, - ); + env.events() + .publish((symbol_short!("multisig"), symbol_short!("cancel")), signer); Ok(()) } @@ -1995,7 +1992,11 @@ impl GameContract { // to get a trusted timestamp for game clock synchronization. /// Configure the SEP-40 oracle contract address for clock sync. - pub fn configure_oracle(env: Env, admin: Address, oracle: Address) -> Result<(), ContractError> { + pub fn configure_oracle( + env: Env, + admin: Address, + oracle: Address, + ) -> Result<(), ContractError> { let current_admin: Address = env .storage() .instance() @@ -2060,7 +2061,9 @@ impl GameContract { if duration == 0 { panic!("Timelock duration must be greater than 0"); } - env.storage().instance().set(&TOURNAMENT_TIMELOCK, &duration); + env.storage() + .instance() + .set(&TOURNAMENT_TIMELOCK, &duration); Ok(()) } @@ -2068,10 +2071,7 @@ impl GameContract { /// /// Locks the total prize pool until `current_ledger + timelock_duration`. /// Returns the escrow ID. - pub fn create_tournament_escrow( - env: Env, - game_id: u64, - ) -> Result { + pub fn create_tournament_escrow(env: Env, game_id: u64) -> Result { let games: Map = env .storage() .instance() @@ -2125,33 +2125,35 @@ impl GameContract { Ok(escrow_id) } - /// Release a time-locked tournament escrow to the specified winners. -/// -/// Can only be called after the lock period has expired, and only by the -/// contract admin. -pub fn release_tournament_escrow( - env: Env, - admin: Address, - escrow_id: u64, - winners: Vec
, - percentages: Vec, -) -> Result<(), ContractError> { - let current_admin: Address = env - .storage() - .instance() - .get(&CONTRACT_ADMIN) - .expect("Not initialized"); - current_admin.require_auth(); - if admin != current_admin { - return Err(ContractError::Unauthorized); - } + /// Release a time-locked tournament escrow to the specified winners. + /// + /// Can only be called after the lock period has expired, and only by the + /// contract admin. + pub fn release_tournament_escrow( + env: Env, + admin: Address, + escrow_id: u64, + winners: Vec
, + percentages: Vec, + ) -> Result<(), ContractError> { + let current_admin: Address = env + .storage() + .instance() + .get(&CONTRACT_ADMIN) + .expect("Not initialized"); + current_admin.require_auth(); + if admin != current_admin { + return Err(ContractError::Unauthorized); + } let mut escrows: Map = env .storage() .instance() .get(&TOURNAMENT_ESCROWS) .ok_or(ContractError::EscrowNotFound)?; - let escrow = escrows.get(escrow_id).ok_or(ContractError::EscrowNotFound)?; + let escrow = escrows + .get(escrow_id) + .ok_or(ContractError::EscrowNotFound)?; if escrow.released { return Err(ContractError::EscrowAlreadyReleased); @@ -2213,7 +2215,10 @@ pub fn release_tournament_escrow( } /// Query a tournament escrow by ID. - pub fn get_tournament_escrow(env: Env, escrow_id: u64) -> Result { + pub fn get_tournament_escrow( + env: Env, + escrow_id: u64, + ) -> Result { let escrows: Map = env .storage() .instance()