Skip to content

Commit

Permalink
Merge pull request #52 from PocketRelay/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
jacobtread authored Sep 11, 2023
2 parents 3f1b168 + f0a4ab9 commit 34e2b16
Show file tree
Hide file tree
Showing 49 changed files with 4,550 additions and 3,705 deletions.
1,125 changes: 736 additions & 389 deletions Cargo.lock

Large diffs are not rendered by default.

12 changes: 4 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pocket-relay"
version = "0.5.8"
version = "0.5.9"
description = "Pocket Relay Server"
readme = "README.md"
keywords = ["EA", "PocketRelay", "MassEffect"]
Expand All @@ -21,8 +21,7 @@ serde_json = "1"
log = { version = "0.4", features = ["serde"] }
log-panics = { version = "2", features = ["with-backtrace"] }

# Blaze packet system & SSLv3 async impl
blaze-pk = "1.3"
# SSLv3 async impl
blaze-ssl-async = "^0.3"

# Resource embedding
Expand Down Expand Up @@ -55,6 +54,8 @@ hyper = "0.14.25"
tower = "0.4"

bitflags = "2.3.1"
tdf = { version = "0.1" }
bytes = "1.4.0"


# SeaORM
Expand Down Expand Up @@ -111,11 +112,6 @@ version = "0.4"
default-features = false
features = ["std", "serde"]


# Patch which updates the windows-sys dep
[patch.crates-io]
local-ip-address = { git = "https://github.com/jacobtread/local-ip-address.git" }

[profile.release]
strip = true
lto = true
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ RUN apk add curl
WORKDIR /app

# Download server executable
RUN curl -LJ -o pocket-relay-linux https://github.com/PocketRelay/Server/releases/download/v0.5.8/pocket-relay-linux
RUN curl -LJ -o pocket-relay-linux https://github.com/PocketRelay/Server/releases/download/v0.5.9/pocket-relay-linux

# Make the server executable
RUN chmod +x ./pocket-relay-linux
Expand Down
7 changes: 3 additions & 4 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ use log::LevelFilter;
use serde::Deserialize;
use std::{env, fs::read_to_string, path::Path};

/// The server version extracted from the Cargo.toml
pub const VERSION: &str = env!("CARGO_PKG_VERSION");

pub struct RuntimeConfig {
pub reverse_proxy: bool,
pub galaxy_at_war: GalaxyAtWarConfig,
Expand Down Expand Up @@ -51,10 +54,6 @@ pub fn load_config() -> Option<Config> {
Some(config)
}

pub struct ServicesConfig {
pub retriever: RetrieverConfig,
}

#[derive(Deserialize)]
#[serde(default)]
pub struct Config {
Expand Down
11 changes: 5 additions & 6 deletions src/database/entities/players.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! SeaORM Entity. Generated by sea-orm-codegen 0.9.3
use crate::config::RuntimeConfig;
use crate::database::DbResult;
use crate::state::App;
use crate::utils::hashing::hash_password;
use sea_orm::prelude::*;
use sea_orm::{
Expand Down Expand Up @@ -68,14 +68,13 @@ impl Model {
/// `display_name` The player display name
/// `password` The hashed player password
/// `origin` Whether the account is an origin account
pub fn create(
db: &DatabaseConnection,
pub fn create<'db>(
db: &'db DatabaseConnection,
email: String,
display_name: String,
mut password: Option<String>,
) -> DbFuture<Self> {
let config = App::config();

config: &RuntimeConfig,
) -> DbFuture<'db, Self> {
let mut role = PlayerRole::Default;

if config
Expand Down
69 changes: 59 additions & 10 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
use axum::Server;
use crate::{
config::{RuntimeConfig, VERSION},
services::{
game::manager::GameManager, leaderboard::Leaderboard, retriever::Retriever,
sessions::Sessions,
},
};
use axum::{Extension, Server};
use config::load_config;
use log::{error, info};
use state::App;
use std::net::{Ipv4Addr, SocketAddr};
use tokio::{select, signal};
use log::{error, info, LevelFilter};
use std::{
net::{Ipv4Addr, SocketAddr},
sync::Arc,
};
use tokio::{join, select, signal};
use utils::logging;

mod config;
Expand All @@ -12,31 +21,71 @@ mod middleware;
mod routes;
mod services;
mod session;
mod state;
mod utils;

#[tokio::main]
async fn main() {
// Load configuration
let config = load_config().unwrap_or_default();

if config.logging == LevelFilter::Debug {
utils::components::initialize();
}

// Initialize logging
logging::setup(config.logging);

// Create the server socket address while the port is still available
let addr: SocketAddr = (Ipv4Addr::UNSPECIFIED, config.port).into();

// Initialize global state
App::init(config).await;
// Config data persisted to runtime
let runtime_config = RuntimeConfig {
reverse_proxy: config.reverse_proxy,
galaxy_at_war: config.galaxy_at_war,
menu_message: config.menu_message,
dashboard: config.dashboard,
};

// This step may take longer than expected so its spawned instead of joined
tokio::spawn(logging::log_connection_urls(config.port));

let (db, retriever, sessions) = join!(
database::init(&runtime_config),
Retriever::start(config.retriever),
Sessions::start()
);
let game_manager = GameManager::start();
let leaderboard = Leaderboard::start();
let config = Arc::new(runtime_config);

// Initialize session router
let mut router = session::routes::router();

router.add_extension(db.clone());
router.add_extension(config.clone());
router.add_extension(retriever.clone());
router.add_extension(game_manager.clone());
router.add_extension(leaderboard.clone());
router.add_extension(sessions.clone());

let router = router.build();

// Create the HTTP router
let router = routes::router().into_make_service_with_connect_info::<SocketAddr>();
let router = routes::router()
// Apply data extensions
.layer(Extension(db))
.layer(Extension(config))
.layer(Extension(router))
.layer(Extension(game_manager))
.layer(Extension(leaderboard))
.layer(Extension(sessions))
.into_make_service_with_connect_info::<SocketAddr>();

// Create futures for server and shutdown signal
let server_future = Server::bind(&addr).serve(router);
let close_future = signal::ctrl_c();

info!("Started server on {} (v{})", addr, state::VERSION);
info!("Started server on {} (v{})", addr, VERSION);

// Await server termination or shutdown signal
select! {
Expand Down
107 changes: 57 additions & 50 deletions src/middleware/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ use crate::{
entities::{players::PlayerRole, Player},
DbErr,
},
services::tokens::{Tokens, VerifyError},
state::App,
services::sessions::{Sessions, VerifyError, VerifyTokenMessage},
utils::types::BoxFuture,
};
use axum::{
Expand All @@ -13,50 +12,40 @@ use axum::{
http::StatusCode,
response::{IntoResponse, Response},
};
use std::marker::PhantomData;
use interlink::prelude::{Link, LinkError};
use sea_orm::DatabaseConnection;
use thiserror::Error;

/// Extractor for extracting authentication from a request
/// authorization header Bearer token
pub struct Auth<V: AuthVerifier = ()>(pub Player, PhantomData<V>);
pub struct Auth(pub Player);
pub struct AdminAuth(pub Player);

impl<V: AuthVerifier> Auth<V> {
/// Converts the auth guard into its inner player
pub fn into_inner(self) -> Player {
self.0
}
}

/// Alias for an auth gaurd using admin verification
pub type AdminAuth = Auth<AdminVerify>;

pub trait AuthVerifier {
/// Verify function for checking that the provided
/// player meets the requirements
fn verify(player: &Player) -> bool;
}

/// Unit auth verifier type for accepting any player
impl AuthVerifier for () {
fn verify(_player: &Player) -> bool {
true
}
}

/// Auth verifier implementation requiring a role of
/// Admin or higher
pub struct AdminVerify;
impl<S> FromRequestParts<S> for AdminAuth {
type Rejection = TokenError;

impl AuthVerifier for AdminVerify {
fn verify(player: &Player) -> bool {
player.role >= PlayerRole::Admin
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 = Auth::from_request_parts(parts, state);
Box::pin(async move {
let Auth(player) = auth.await?;
if player.role < PlayerRole::Admin {
return Err(TokenError::MissingRole);
}
Ok(AdminAuth(player))
})
}
}

/// The HTTP header that contains the authentication token
const TOKEN_HEADER: &str = "X-Token";

impl<V: AuthVerifier, S> FromRequestParts<S> for Auth<V> {
impl<S> FromRequestParts<S> for Auth {
type Rejection = TokenError;

fn from_request_parts<'a, 'b, 'c>(
Expand All @@ -68,6 +57,17 @@ impl<V: AuthVerifier, S> FromRequestParts<S> for Auth<V> {
'b: 'c,
Self: 'c,
{
let db = parts
.extensions
.get::<DatabaseConnection>()
.expect("Database connection extension missing")
.clone();
let sessions = parts
.extensions
.get::<Link<Sessions>>()
.expect("Database connection extension missing")
.clone();

Box::pin(async move {
// Extract the token from the headers
let token = parts
Expand All @@ -76,11 +76,20 @@ impl<V: AuthVerifier, S> FromRequestParts<S> for Auth<V> {
.and_then(|value| value.to_str().ok())
.ok_or(TokenError::MissingToken)?;

// Verify the token claim
let db = App::database();
let player: Player = Tokens::service_verify(db, token).await?;
let player_id = sessions
.send(VerifyTokenMessage(token.to_string()))
.await
.map_err(TokenError::SessionService)?
.map_err(|err| match err {
VerifyError::Expired => TokenError::ExpiredToken,
VerifyError::Invalid => TokenError::InvalidToken,
})?;

let player = Player::by_id(&db, player_id)
.await?
.ok_or(TokenError::InvalidToken)?;

Ok(Self(player, PhantomData))
Ok(Self(player))
})
}
}
Expand All @@ -98,18 +107,15 @@ pub enum TokenError {
/// The provided token was not a valid token
#[error("Invalid token")]
InvalidToken,
/// Authentication is not high enough role
#[error("Missing required role")]
MissingRole,
/// Database error
#[error("Internal server error")]
Database(#[from] DbErr),
}

impl From<VerifyError> for TokenError {
fn from(value: VerifyError) -> Self {
match value {
VerifyError::Expired => Self::ExpiredToken,
_ => Self::InvalidToken,
}
}
/// Session service error
#[error("Session service unavailable")]
SessionService(LinkError),
}

/// IntoResponse implementation for TokenError to allow it to be
Expand All @@ -120,7 +126,8 @@ impl IntoResponse for TokenError {
let status = match &self {
Self::MissingToken => StatusCode::BAD_REQUEST,
Self::InvalidToken | Self::ExpiredToken => StatusCode::UNAUTHORIZED,
Self::Database(_) => StatusCode::INTERNAL_SERVER_ERROR,
Self::MissingRole => StatusCode::FORBIDDEN,
Self::Database(_) | Self::SessionService(_) => StatusCode::INTERNAL_SERVER_ERROR,
};

(status, boxed(self.to_string())).into_response()
Expand Down
Loading

0 comments on commit 34e2b16

Please sign in to comment.