Skip to content

Commit

Permalink
feat: new public games api configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
jacobtread committed May 10, 2024
1 parent 18f8708 commit fec1449
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 19 deletions.
23 changes: 22 additions & 1 deletion src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub struct RuntimeConfig {
pub menu_message: String,
pub dashboard: DashboardConfig,
pub tunnel: TunnelConfig,
pub api: APIConfig,
}

/// Environment variable key to load the config from
Expand Down Expand Up @@ -78,6 +79,7 @@ pub struct Config {
pub logging: LevelFilter,
pub retriever: RetrieverConfig,
pub tunnel: TunnelConfig,
pub api: APIConfig,
}

impl Default for Config {
Expand All @@ -92,7 +94,8 @@ impl Default for Config {
galaxy_at_war: Default::default(),
logging: LevelFilter::Info,
retriever: Default::default(),
tunnel: Default::default()
tunnel: Default::default(),
api: Default::default()
}
}
}
Expand Down Expand Up @@ -133,6 +136,24 @@ pub enum QosServerConfig {
},
}

#[derive(Deserialize)]
#[serde(default)]
pub struct APIConfig {
/// Allow games data to be requested from the API without auth
pub public_games: bool,
/// Hide players from API response when no auth is provided
pub public_games_hide_players: bool,
}

impl Default for APIConfig {
fn default() -> Self {
Self {
public_games: false,
public_games_hide_players: true,
}
}
}

#[derive(Deserialize)]
#[serde(default)]
pub struct GalaxyAtWarConfig {
Expand Down
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ async fn main() {
dashboard: config.dashboard,
qos: config.qos,
tunnel: config.tunnel,
api: config.api,
};

debug!("QoS server: {:?}", &runtime_config.qos);
Expand Down
28 changes: 27 additions & 1 deletion src/middleware/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,36 @@ use axum::{
};
use futures_util::future::BoxFuture;
use sea_orm::DatabaseConnection;
use std::future::Future;
use std::sync::Arc;
use thiserror::Error;

pub struct MaybeAuth(pub Option<Player>);

impl<S> FromRequestParts<S> for MaybeAuth {
type Rejection = TokenError;

fn from_request_parts<'a, 'b, 'c>(
parts: &'a mut axum::http::request::Parts,
state: &'b S,
) -> BoxFuture<'c, Result<Self, Self::Rejection>>
where
'a: 'c,
'b: 'c,
Self: 'c,
{
let auth: std::pin::Pin<Box<dyn Future<Output = Result<Auth, TokenError>> + Send>> =
Auth::from_request_parts(parts, state);
Box::pin(async move {
match auth.await {
Ok(Auth(value)) => Ok(MaybeAuth(Some(value))),
Err(TokenError::MissingToken) => Ok(MaybeAuth(None)),
Err(err) => Err(err),
}
})
}
}

pub struct Auth(pub Player);
pub struct AdminAuth(pub Player);

Expand Down Expand Up @@ -47,7 +74,6 @@ const TOKEN_HEADER: &str = "X-Token";

impl<S> FromRequestParts<S> for Auth {
type Rejection = TokenError;

fn from_request_parts<'a, 'b, 'c>(
parts: &'a mut axum::http::request::Parts,
_state: &'b S,
Expand Down
34 changes: 28 additions & 6 deletions src/routes/games.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::{
config::RuntimeConfig,
database::entities::players::PlayerRole,
middleware::auth::Auth,
middleware::auth::MaybeAuth,
services::game::{manager::GameManager, GameSnapshot},
utils::types::GameID,
};
Expand All @@ -20,6 +21,8 @@ pub enum GamesError {
/// The requested game could not be found (For specific game lookup)
#[error("Game not found")]
NotFound,
#[error("Missing required access")]
NoPermission,
}

/// The query structure for a players query
Expand Down Expand Up @@ -55,17 +58,25 @@ pub struct GamesResponse {
/// Player networking information is included for requesting
/// players with admin level or greater access.
pub async fn get_games(
Auth(auth): Auth,
MaybeAuth(auth): MaybeAuth,
Query(GamesRequest { offset, count }): Query<GamesRequest>,
Extension(game_manager): Extension<Arc<GameManager>>,
Extension(config): Extension<Arc<RuntimeConfig>>,
) -> Result<Json<GamesResponse>, GamesError> {
if let (None, false) = (&auth, config.api.public_games) {
return Err(GamesError::NoPermission);
}

let count: usize = count.unwrap_or(20) as usize;
let offset: usize = offset * count;
let include_net = auth.role >= PlayerRole::Admin;
let include_net = auth
.as_ref()
.is_some_and(|player| player.role >= PlayerRole::Admin);
let include_players = auth.is_some() || !config.api.public_games_hide_players;

// Retrieve the game snapshots
let (games, more) = game_manager
.create_snapshot(offset, count, include_net)
.create_snapshot(offset, count, include_net, include_players)
.await;

// Get the total number of games
Expand All @@ -86,16 +97,26 @@ pub async fn get_games(
/// Player networking information is included for requesting
/// players with admin level or greater access.
pub async fn get_game(
Auth(auth): Auth,
MaybeAuth(auth): MaybeAuth,
Path(game_id): Path<GameID>,
Extension(game_manager): Extension<Arc<GameManager>>,
Extension(config): Extension<Arc<RuntimeConfig>>,
) -> Result<Json<GameSnapshot>, GamesError> {
if let (None, false) = (&auth, config.api.public_games) {
return Err(GamesError::NoPermission);
}

let include_net = auth
.as_ref()
.is_some_and(|player| player.role >= PlayerRole::Admin);
let include_players = auth.is_some() || !config.api.public_games_hide_players;

let game = game_manager
.get_game(game_id)
.await
.ok_or(GamesError::NotFound)?;
let game = &*game.read().await;
let snapshot = game.snapshot(auth.role >= PlayerRole::Admin);
let snapshot = game.snapshot(include_net, include_players);

Ok(Json(snapshot))
}
Expand All @@ -105,6 +126,7 @@ impl IntoResponse for GamesError {
fn into_response(self) -> Response {
let status_code = match &self {
Self::NotFound => StatusCode::NOT_FOUND,
Self::NoPermission => StatusCode::FORBIDDEN,
};

(status_code, self.to_string()).into_response()
Expand Down
4 changes: 2 additions & 2 deletions src/routes/gaw.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//! Routes for the Galaxy At War API used by the Mass Effect 3 client in order
//! to retrieve and increase the Galxay At War values for a player.
//! to retrieve and increase the Galaxy At War values for a player.
//!
//! This API is not documented as it is not intended to be used by anyone
//! other than the Mass Effect 3 client itself.
Expand Down Expand Up @@ -90,7 +90,7 @@ pub async fn shared_token_login(Query(AuthQuery { auth }): Query<AuthQuery>) ->
/// GET /galaxyatwar/getRatings/:id
///
/// Route for retrieving the galaxy at war ratings for the player
/// with the provied ID
/// with the provided ID
///
/// `id` The hex encoded ID of the player
pub async fn get_ratings(
Expand Down
3 changes: 2 additions & 1 deletion src/services/game/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ impl GameManager {
offset: usize,
count: usize,
include_net: bool,
include_players: bool,
) -> (Vec<GameSnapshot>, bool) {
// Create the futures using the handle action before passing
// them to a future to be awaited
Expand Down Expand Up @@ -111,7 +112,7 @@ impl GameManager {
.for_each(|game| {
join_set.spawn(async move {
let game = &*game.read().await;
game.snapshot(include_net)
game.snapshot(include_net, include_players)
});
});

Expand Down
20 changes: 12 additions & 8 deletions src/services/game/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ pub struct GameSnapshot {
/// The game attributes
pub attributes: AttrMap,
/// Snapshots of the game players
pub players: Box<[GamePlayerSnapshot]>,
pub players: Option<Box<[GamePlayerSnapshot]>>,
}

/// Attributes map type
Expand Down Expand Up @@ -423,13 +423,17 @@ impl Game {
GameJoinableState::Joinable
}

pub fn snapshot(&self, include_net: bool) -> GameSnapshot {
let players = self
.players
.iter()
.map(|value| value.snapshot(include_net))
.collect();

pub fn snapshot(&self, include_net: bool, include_players: bool) -> GameSnapshot {
let players = if include_players {
let players = self
.players
.iter()
.map(|value| value.snapshot(include_net))
.collect();
Some(players)
} else {
None
};
GameSnapshot {
id: self.id,
state: self.state,
Expand Down

0 comments on commit fec1449

Please sign in to comment.