From cc1219db631070e28b88eee0dfbb70abe7154854 Mon Sep 17 00:00:00 2001 From: SirLynix Date: Sun, 14 Jul 2024 22:59:45 +0200 Subject: [PATCH] Add game connect route --- src/game_connection.rs | 47 ++++++++++++++++++++++--- src/main.rs | 17 ++++----- src/players.rs | 78 +++++++++++++++++++++++++++++------------- src/version.rs | 2 +- 4 files changed, 108 insertions(+), 36 deletions(-) diff --git a/src/game_connection.rs b/src/game_connection.rs index bf5d076..df38a72 100644 --- a/src/game_connection.rs +++ b/src/game_connection.rs @@ -1,17 +1,56 @@ use actix_web::{post, web, HttpResponse, Responder}; +use deadpool_postgres::tokio_postgres::types::Type; +use serde::{Deserialize, Serialize}; use uuid::Uuid; use crate::{ app_data::AppData, - errors::api::RouteError, + errors::api::{ErrorCode, RequestError, RouteError}, game_connection_token::{ GameConnectionToken, GameConnectionTokenPrivate, GamePlayerData, GameServerAddress, }, + players::validate_player_token, }; -#[post("/v1/player/test")] -async fn player_test(app_data: web::Data) -> Result { - let player_data = GamePlayerData::generate(Uuid::new_v4(), "SirLynix".into()); +#[derive(Deserialize)] +struct GameConnectionParams { + token: String, +} + +#[derive(Serialize)] +struct GameConnectionResponse { + uuid: String, + nickname: String, +} + +#[post("/v1/game/connect")] +async fn route_game_connect( + app_data: web::Data, + pg_pool: web::Data, + params: web::Json, +) -> Result { + let pg_client = pg_pool.get().await?; + let player_id = validate_player_token(&pg_client, ¶ms.token).await?; + + let find_player_info = pg_client + .prepare_typed_cached( + "SELECT uuid, nickname FROM players WHERE id = $1", + &[Type::INT4], + ) + .await?; + + let player_result = pg_client.query(&find_player_info, &[&player_id]).await?; + if player_result.is_empty() { + return Err(RouteError::InvalidRequest(RequestError::new( + ErrorCode::AuthenticationInvalidToken, + "Invalid token".to_string(), + ))); + } + + let uuid: Uuid = player_result[0].get(0); + let nickname: String = player_result[0].get(1); + + let player_data = GamePlayerData::generate(uuid, nickname); let server_address = GameServerAddress { address: app_data.config.game_server_address.clone(), diff --git a/src/main.rs b/src/main.rs index f4c3c81..ef60df4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,14 +2,15 @@ use actix_governor::{Governor, GovernorConfig, GovernorConfigBuilder}; use actix_web::{middleware, web, App, HttpServer}; use cached::TimedCache; use confy::ConfyError; -use game_connection::player_test; +use game_connection::route_game_connect; +use players::route_player_auth; use std::sync::Mutex; use crate::app_data::AppData; use crate::config::ApiConfig; use crate::fetcher::Fetcher; -use crate::players::{player_authenticate, player_create}; -use crate::version::game_version; +use crate::players::route_player_create; +use crate::version::route_game_version; mod app_data; mod config; @@ -62,7 +63,7 @@ async fn main() -> Result<(), std::io::Error> { let test_client = pg_pool.get().await.expect("failed to connect to database"); drop(test_client); - std::env::set_var("RUST_LOG", "debug,actix_web=debug"); + std::env::set_var("RUST_LOG", "info,actix_web=info"); env_logger::init(); let bind_address = format!("{}:{}", config.listen_address, config.listen_port); @@ -87,13 +88,13 @@ async fn main() -> Result<(), std::io::Error> { .wrap(Governor::new(&governor_conf)) .app_data(data_config.clone()) .app_data(pg_pool.clone()) - .service(game_version) - .service(player_authenticate) - .service(player_test) + .service(route_game_version) + .service(route_player_auth) + .service(route_game_connect) .service( web::scope("") .wrap(Governor::new(&player_create_governor_conf)) - .service(player_create), + .service(route_player_create), ) }) .bind(bind_address)? diff --git a/src/players.rs b/src/players.rs index 2105f04..dea3b4f 100644 --- a/src/players.rs +++ b/src/players.rs @@ -10,6 +10,35 @@ use uuid::Uuid; use crate::app_data::AppData; use crate::errors::api::{ErrorCode, RequestError, RouteError}; +pub async fn validate_player_token( + pg_client: &deadpool_postgres::Client, + token: &str, +) -> Result { + if token.is_empty() || token.len() > 64 { + return Err(RouteError::InvalidRequest(RequestError::new( + ErrorCode::AuthenticationInvalidToken, + "Invalid token".to_string(), + ))); + } + + let find_token_statement = pg_client + .prepare_typed_cached( + "SELECT player_id FROM player_tokens WHERE token = $1", + &[Type::VARCHAR], + ) + .await?; + + let token_result = pg_client.query(&find_token_statement, &[&token]).await?; + if token_result.is_empty() { + return Err(RouteError::InvalidRequest(RequestError::new( + ErrorCode::AuthenticationInvalidToken, + "Invalid token".to_string(), + ))); + } + + Ok(token_result[0].get(0)) +} + #[derive(Deserialize)] struct CreatePlayerParams { nickname: String, @@ -22,7 +51,7 @@ struct CreatePlayerResponse { } #[post("/v1/players")] -async fn player_create( +async fn route_player_create( app_data: web::Data, pg_pool: web::Data, params: web::Json, @@ -110,28 +139,13 @@ struct AuthenticationResponse { nickname: String, } -#[post("/v1/player/authenticate")] -async fn player_authenticate( +#[post("/v1/player/auth")] +async fn route_player_auth( pg_pool: web::Data, params: web::Json, ) -> Result { - let token = ¶ms.token; - - if token.is_empty() || token.len() > 64 { - return Err(RouteError::InvalidRequest(RequestError::new( - ErrorCode::AuthenticationInvalidToken, - "Invalid token".to_string(), - ))); - } - let pg_client = pg_pool.get().await?; - - let find_token_statement = pg_client - .prepare_typed_cached( - "SELECT player_id FROM player_tokens WHERE token = $1", - &[Type::VARCHAR], - ) - .await?; + let player_id = validate_player_token(&pg_client, ¶ms.token).await?; let find_player_info = pg_client .prepare_typed_cached( @@ -140,16 +154,34 @@ async fn player_authenticate( ) .await?; - let token_result = pg_client.query(&find_token_statement, &[&token]).await?; - if token_result.is_empty() { + let player_result = pg_client.query(&find_player_info, &[&player_id]).await?; + if player_result.is_empty() { return Err(RouteError::InvalidRequest(RequestError::new( ErrorCode::AuthenticationInvalidToken, "Invalid token".to_string(), ))); } - let player_id: i32 = token_result[0].get(0); - let player_result = pg_client.query(&find_player_info, &[&player_id]).await?; + // Update last connection time in a separate task as its result won't affect the route + tokio::spawn(async move { + match pg_client + .prepare_typed_cached( + "UPDATE players SET last_connection_time = NOW() WHERE id = $1", + &[Type::INT4], + ) + .await + { + Ok(statement) => { + let res = pg_client.query(&statement, &[&player_id]).await; + if let Err(err) = res { + eprintln!("failed to update player {player_id} connection time: {err}"); + } + } + Err(err) => { + eprintln!("failed to update player {player_id} connection time (failed to prepare query): {err}"); + } + } + }); let uuid: Uuid = player_result[0].get(0); let nickname: String = player_result[0].get(1); diff --git a/src/version.rs b/src/version.rs index cbd3ea4..b8714ab 100644 --- a/src/version.rs +++ b/src/version.rs @@ -20,7 +20,7 @@ pub(crate) enum CachedReleased { } #[get("/game_version")] -async fn game_version( +async fn route_game_version( app_data: web::Data, ver_query: web::Query, ) -> impl Responder {