mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
Improve Authentication
This commit is contained in:
@@ -14,8 +14,10 @@ Pumpkin is currently under heavy development.
|
||||
|
||||
## What Pumpkin aims to be:
|
||||
- A fast, efficient, and scalable Minecraft server
|
||||
- Use Multi-Threading
|
||||
- Compatible with the latest Minecraft server version
|
||||
- Adherent to Vanilla game mechanics
|
||||
- Be Secure and prevent all Exploits found
|
||||
- Highly configurable with the ability to disable unnecessary features
|
||||
- A platform for plugin development
|
||||
|
||||
|
||||
@@ -33,10 +33,7 @@ impl<'a> ClientPacket for CLoginSuccess<'a> {
|
||||
bytebuf.put_list::<Property>(self.properties, |p, v| {
|
||||
p.put_string(&v.name);
|
||||
p.put_string(&v.value);
|
||||
// has signature ?
|
||||
// todo: for some reason we get "got too many bytes error when using a signature"
|
||||
p.put_bool(false);
|
||||
// p.put_option(&v.signature, |p,v| p.put_string(v));
|
||||
p.put_option(&v.signature, |p, v| p.put_string(v));
|
||||
});
|
||||
bytebuf.put_bool(self.strict_error_handling);
|
||||
}
|
||||
|
||||
@@ -34,10 +34,7 @@ impl<'a> ClientPacket for CPlayerInfoUpdate<'a> {
|
||||
p.put_list::<Property>(properties, |p, v| {
|
||||
p.put_string(&v.name);
|
||||
p.put_string(&v.value);
|
||||
// has signature ?
|
||||
// todo: for some reason we get "got too many bytes error when using a signature"
|
||||
p.put_bool(false);
|
||||
// todo signature
|
||||
p.put_option(&v.signature, |p, v| p.put_string(v));
|
||||
});
|
||||
}
|
||||
PlayerAction::InitializeChat(_) => todo!(),
|
||||
|
||||
@@ -1,19 +1,49 @@
|
||||
use std::net::IpAddr;
|
||||
use std::{collections::HashMap, net::IpAddr};
|
||||
|
||||
use base64::{engine::general_purpose, Engine};
|
||||
use num_bigint::BigInt;
|
||||
use pumpkin_protocol::Property;
|
||||
use reqwest::StatusCode;
|
||||
use serde::Deserialize;
|
||||
use reqwest::{StatusCode, Url};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::server::Server;
|
||||
use crate::{config::auth_config::TextureConfig, server::Server};
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(dead_code)]
|
||||
pub struct ProfileTextures {
|
||||
timestamp: i64,
|
||||
profileId: Uuid,
|
||||
profileName: String,
|
||||
signatureRequired: bool,
|
||||
textures: HashMap<String, Texture>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(dead_code)]
|
||||
pub struct Texture {
|
||||
url: String,
|
||||
metadata: Option<HashMap<String, String>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ProfileAction {
|
||||
#[serde(rename = "FORCED_NAME_CHANGE")]
|
||||
ForcedNameChange,
|
||||
#[serde(rename = "USING_BANNED_SKIN")]
|
||||
UsingBannedSkin,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone, Debug)]
|
||||
pub struct GameProfile {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
pub properties: Vec<Property>,
|
||||
#[serde(rename = "profileActions")]
|
||||
pub profile_actions: Option<Vec<ProfileAction>>,
|
||||
}
|
||||
|
||||
pub fn authenticate(
|
||||
@@ -22,8 +52,13 @@ pub fn authenticate(
|
||||
ip: &IpAddr,
|
||||
server: &mut Server,
|
||||
) -> Result<GameProfile, AuthError> {
|
||||
assert!(server.advanced_config.authentication.use_authentication);
|
||||
assert!(server.auth_client.is_some());
|
||||
let address = if server.base_config.prevent_proxy_connections {
|
||||
let address = if server
|
||||
.advanced_config
|
||||
.authentication
|
||||
.prevent_proxy_connections
|
||||
{
|
||||
format!("https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}&ip={ip}")
|
||||
} else {
|
||||
format!("https://sessionserver.mojang.com/session/minecraft/hasJoined?username={username}&serverId={server_hash}")
|
||||
@@ -44,10 +79,32 @@ pub fn authenticate(
|
||||
Ok(profile)
|
||||
}
|
||||
|
||||
pub fn unpack_textures(property: Property, config: &TextureConfig) {
|
||||
// todo: no unwrap
|
||||
let from64 = general_purpose::STANDARD.decode(property.value).unwrap();
|
||||
let textures: ProfileTextures = serde_json::from_slice(&from64).unwrap();
|
||||
dbg!(&textures);
|
||||
for texture in textures.textures {
|
||||
is_texture_url_valid(Url::parse(&texture.1.url).unwrap(), config);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn auth_digest(bytes: &[u8]) -> String {
|
||||
BigInt::from_signed_bytes_be(bytes).to_str_radix(16)
|
||||
}
|
||||
|
||||
pub fn is_texture_url_valid(url: Url, config: &TextureConfig) -> bool {
|
||||
let scheme = url.scheme();
|
||||
if !config.allowed_url_schemes.contains(&scheme.to_string()) {
|
||||
return false;
|
||||
}
|
||||
let domain = url.domain().unwrap_or("");
|
||||
if !config.allowed_url_domains.contains(&domain.to_string()) {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum AuthError {
|
||||
#[error("Authentication servers are down")]
|
||||
|
||||
@@ -23,7 +23,10 @@ use crate::{
|
||||
server::Server,
|
||||
};
|
||||
|
||||
use super::{authentication::auth_digest, Client, EncryptionError, PlayerConfig};
|
||||
use super::{
|
||||
authentication::{auth_digest, unpack_textures},
|
||||
Client, EncryptionError, PlayerConfig,
|
||||
};
|
||||
|
||||
/// Processes incoming Packets from the Client to the Server
|
||||
/// Implements the `Client` Packets
|
||||
@@ -45,12 +48,15 @@ impl Client {
|
||||
}
|
||||
|
||||
pub fn handle_login_start(&mut self, server: &mut Server, login_start: SLoginStart) {
|
||||
// todo: do basic name validation
|
||||
dbg!("login start");
|
||||
// default game profile, when no online mode
|
||||
// todo: make offline uuid
|
||||
self.gameprofile = Some(GameProfile {
|
||||
id: login_start.uuid,
|
||||
name: login_start.name,
|
||||
properties: vec![],
|
||||
profile_actions: None,
|
||||
});
|
||||
|
||||
// todo: check config for encryption
|
||||
@@ -91,10 +97,40 @@ impl Client {
|
||||
&ip,
|
||||
server,
|
||||
) {
|
||||
Ok(p) => self.gameprofile = Some(p),
|
||||
Ok(p) => {
|
||||
// Check if player should join
|
||||
if let Some(p) = &p.profile_actions {
|
||||
if !server
|
||||
.advanced_config
|
||||
.authentication
|
||||
.player_profile
|
||||
.allow_banned_players
|
||||
{
|
||||
if !p.is_empty() {
|
||||
self.kick("Your account can't join");
|
||||
}
|
||||
} else {
|
||||
for allowed in server
|
||||
.advanced_config
|
||||
.authentication
|
||||
.player_profile
|
||||
.allowed_actions
|
||||
.clone()
|
||||
{
|
||||
if !p.contains(&allowed) {
|
||||
self.kick("Your account can't join");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.gameprofile = Some(p);
|
||||
}
|
||||
Err(e) => self.kick(&e.to_string()),
|
||||
}
|
||||
}
|
||||
for ele in self.gameprofile.as_ref().unwrap().properties.clone() {
|
||||
unpack_textures(ele, &server.advanced_config.authentication.textures);
|
||||
}
|
||||
|
||||
if let Some(profile) = self.gameprofile.as_ref().cloned() {
|
||||
let packet = CLoginSuccess::new(profile.id, profile.name, &profile.properties, false);
|
||||
|
||||
@@ -38,7 +38,7 @@ use pumpkin_protocol::{
|
||||
use std::io::Read;
|
||||
use thiserror::Error;
|
||||
|
||||
mod authentication;
|
||||
pub mod authentication;
|
||||
mod client_packet;
|
||||
pub mod player_packet;
|
||||
|
||||
|
||||
93
pumpkin/src/config/auth_config.rs
Normal file
93
pumpkin/src/config/auth_config.rs
Normal file
@@ -0,0 +1,93 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::client::authentication::ProfileAction;
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct Authentication {
|
||||
/// Whether to use Mojang authentication.
|
||||
pub use_authentication: bool,
|
||||
|
||||
/// Prevent proxy connections.
|
||||
pub prevent_proxy_connections: bool,
|
||||
|
||||
/// Player profile handling.
|
||||
pub player_profile: PlayerProfileConfig,
|
||||
|
||||
/// Texture handling.
|
||||
pub textures: TextureConfig,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct PlayerProfileConfig {
|
||||
/// Allow players flagged by Mojang (banned, forced name change).
|
||||
pub allow_banned_players: bool,
|
||||
/// Depends on the value above
|
||||
pub allowed_actions: Vec<ProfileAction>,
|
||||
}
|
||||
|
||||
impl Default for PlayerProfileConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
allow_banned_players: false,
|
||||
allowed_actions: vec![
|
||||
ProfileAction::ForcedNameChange,
|
||||
ProfileAction::UsingBannedSkin,
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct TextureConfig {
|
||||
/// Whether to use player textures.
|
||||
pub enabled: bool,
|
||||
|
||||
pub allowed_url_schemes: Vec<String>,
|
||||
pub allowed_url_domains: Vec<String>,
|
||||
|
||||
/// Specific texture types.
|
||||
pub types: TextureTypes,
|
||||
}
|
||||
|
||||
impl Default for TextureConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: true,
|
||||
allowed_url_schemes: vec!["http".into(), "https".into()],
|
||||
allowed_url_domains: vec![".minecraft.net".into(), ".mojang.com".into()],
|
||||
types: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct TextureTypes {
|
||||
/// Use player skins.
|
||||
pub skin: bool,
|
||||
/// Use player capes.
|
||||
pub cape: bool,
|
||||
/// Use player elytras.
|
||||
/// (i didn't know myself that there are custom elytras)
|
||||
pub elytra: bool,
|
||||
}
|
||||
|
||||
impl Default for TextureTypes {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
skin: true,
|
||||
cape: true,
|
||||
elytra: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Authentication {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
use_authentication: true,
|
||||
prevent_proxy_connections: true,
|
||||
player_profile: Default::default(),
|
||||
textures: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{entity::player::GameMode, server::Difficulty};
|
||||
|
||||
use super::auth_config::Authentication;
|
||||
|
||||
/// Current Config version of the Base Config
|
||||
const CURRENT_BASE_VERSION: &str = "1.0.0";
|
||||
|
||||
@@ -12,19 +14,15 @@ const CURRENT_BASE_VERSION: &str = "1.0.0";
|
||||
/// This also allows you get some Performance or Resource boosts.
|
||||
/// Important: The Configuration should match Vanilla by default
|
||||
pub struct AdvancedConfiguration {
|
||||
/// Requires Online mode
|
||||
/// Should player have skins
|
||||
pub use_skins: bool,
|
||||
/// Should chat be enabled
|
||||
pub enable_chat: bool,
|
||||
|
||||
pub commands: Commands,
|
||||
pub authentication: Authentication,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct Commands {
|
||||
// Are commands from the Console accepted ?
|
||||
/// Are commands from the Console accepted ?
|
||||
pub use_console: bool,
|
||||
// todo commands...
|
||||
}
|
||||
|
||||
impl Default for Commands {
|
||||
@@ -37,8 +35,7 @@ impl Default for Commands {
|
||||
impl Default for AdvancedConfiguration {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
use_skins: true,
|
||||
enable_chat: true,
|
||||
authentication: Authentication::default(),
|
||||
commands: Commands::default(),
|
||||
}
|
||||
}
|
||||
@@ -74,8 +71,6 @@ pub struct BasicConfiguration {
|
||||
pub online_mode: bool,
|
||||
/// Whether packet encryption is enabled. Required when online mode is enabled.
|
||||
pub encryption: bool,
|
||||
/// Whether to prevent proxy connections.
|
||||
pub prevent_proxy_connections: bool,
|
||||
/// The server's description displayed on the status screen.
|
||||
pub motd: String,
|
||||
/// The default game mode for players.
|
||||
@@ -99,7 +94,6 @@ impl Default for BasicConfiguration {
|
||||
hardcore: false,
|
||||
online_mode: true,
|
||||
encryption: true,
|
||||
prevent_proxy_connections: true,
|
||||
motd: "A Blazing fast Pumpkin Server!".to_string(),
|
||||
default_gamemode: GameMode::Survival,
|
||||
}
|
||||
154
pumpkin/src/config/mod.rs
Normal file
154
pumpkin/src/config/mod.rs
Normal file
@@ -0,0 +1,154 @@
|
||||
use std::path::Path;
|
||||
|
||||
use auth_config::Authentication;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{entity::player::GameMode, server::Difficulty};
|
||||
|
||||
pub mod auth_config;
|
||||
|
||||
/// Current Config version of the Base Config
|
||||
const CURRENT_BASE_VERSION: &str = "1.0.0";
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
/// The idea is that Pumpkin should very customizable, You can Enable or Disable Features depning on your needs.
|
||||
/// This also allows you get some Performance or Resource boosts.
|
||||
/// Important: The Configuration should match Vanilla by default
|
||||
pub struct AdvancedConfiguration {
|
||||
pub commands: Commands,
|
||||
pub authentication: Authentication,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct Commands {
|
||||
/// Are commands from the Console accepted ?
|
||||
pub use_console: bool,
|
||||
// todo commands...
|
||||
}
|
||||
|
||||
impl Default for Commands {
|
||||
fn default() -> Self {
|
||||
Self { use_console: true }
|
||||
}
|
||||
}
|
||||
|
||||
/// Important: The Configuration should match Vanilla by default
|
||||
impl Default for AdvancedConfiguration {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
authentication: Authentication::default(),
|
||||
commands: Commands::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct BasicConfiguration {
|
||||
/// A version identifier for the configuration format.
|
||||
pub config_version: String,
|
||||
/// The address to bind the server to.
|
||||
pub server_address: String,
|
||||
/// The port to listen on.
|
||||
pub server_port: u16,
|
||||
/// The seed for world generation.
|
||||
pub seed: String,
|
||||
/// The maximum number of players allowed on the server.
|
||||
pub max_players: u32,
|
||||
/// The maximum view distance for players.
|
||||
pub view_distance: u8,
|
||||
/// The maximum simulated view distance.
|
||||
pub simulation_distance: u8,
|
||||
/// The path to the resource pack.
|
||||
pub resource_pack: String,
|
||||
/// The SHA1 hash of the resource pack.
|
||||
pub resource_pack_sha1: String,
|
||||
/// The default game difficulty.
|
||||
pub default_difficulty: Difficulty,
|
||||
/// Whether the Nether dimension is enabled.
|
||||
pub allow_nether: bool,
|
||||
/// Whether the server is in hardcore mode.
|
||||
pub hardcore: bool,
|
||||
/// Whether online mode is enabled. Requires valid Minecraft accounts.
|
||||
pub online_mode: bool,
|
||||
/// Whether packet encryption is enabled. Required when online mode is enabled.
|
||||
pub encryption: bool,
|
||||
/// The server's description displayed on the status screen.
|
||||
pub motd: String,
|
||||
/// The default game mode for players.
|
||||
pub default_gamemode: GameMode,
|
||||
}
|
||||
|
||||
impl Default for BasicConfiguration {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
config_version: CURRENT_BASE_VERSION.to_string(),
|
||||
server_address: "127.0.0.1".to_string(),
|
||||
server_port: 25565,
|
||||
seed: "".to_string(),
|
||||
max_players: 100000,
|
||||
view_distance: 10,
|
||||
simulation_distance: 10,
|
||||
resource_pack: "".to_string(),
|
||||
resource_pack_sha1: "".to_string(),
|
||||
default_difficulty: Difficulty::Normal,
|
||||
allow_nether: true,
|
||||
hardcore: false,
|
||||
online_mode: true,
|
||||
encryption: true,
|
||||
motd: "A Blazing fast Pumpkin Server!".to_string(),
|
||||
default_gamemode: GameMode::Survival,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AdvancedConfiguration {
|
||||
pub fn load<P: AsRef<Path>>(path: P) -> AdvancedConfiguration {
|
||||
if path.as_ref().exists() {
|
||||
let toml = std::fs::read_to_string(path).expect("Couldn't read configuration");
|
||||
toml::from_str(toml.as_str()).expect("Couldn't parse, Proberbly old config")
|
||||
} else {
|
||||
let config = AdvancedConfiguration::default();
|
||||
let toml = toml::to_string(&config).expect("Couldn't create toml!");
|
||||
std::fs::write(path, toml).expect("Couldn't save configuration");
|
||||
config
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BasicConfiguration {
|
||||
pub fn load<P: AsRef<Path>>(path: P) -> BasicConfiguration {
|
||||
if path.as_ref().exists() {
|
||||
let toml = std::fs::read_to_string(path).expect("Couldn't read configuration");
|
||||
toml::from_str(toml.as_str()).expect("Couldn't parse")
|
||||
} else {
|
||||
let config = BasicConfiguration::default();
|
||||
let toml = toml::to_string(&config).expect("Couldn't create toml!");
|
||||
std::fs::write(path, toml).expect("Couldn't save configuration");
|
||||
config.validate();
|
||||
config
|
||||
}
|
||||
}
|
||||
|
||||
pub fn validate(&self) {
|
||||
assert_eq!(
|
||||
self.config_version, CURRENT_BASE_VERSION,
|
||||
"Config version does not match used Config version. Please update your config"
|
||||
);
|
||||
assert!(self.view_distance >= 2, "View distance must be atleast 2");
|
||||
assert!(
|
||||
self.view_distance <= 32,
|
||||
"View distance must be less than 32"
|
||||
);
|
||||
if self.online_mode {
|
||||
assert!(
|
||||
self.encryption,
|
||||
"When Online Mode is enabled, Encryption must be enabled"
|
||||
)
|
||||
}
|
||||
assert_eq!(
|
||||
!self.resource_pack.is_empty(),
|
||||
!self.resource_pack_sha1.is_empty(),
|
||||
"Resource Pack path or Sha1 hash is missing"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,12 +4,12 @@ use std::io::{self};
|
||||
|
||||
use client::Client;
|
||||
use commands::handle_command;
|
||||
use configuration::AdvancedConfiguration;
|
||||
use config::AdvancedConfiguration;
|
||||
|
||||
use std::{collections::HashMap, rc::Rc, thread};
|
||||
|
||||
use client::interrupted;
|
||||
use configuration::BasicConfiguration;
|
||||
use config::BasicConfiguration;
|
||||
use server::Server;
|
||||
|
||||
// Setup some tokens to allow us to identify which event is for which socket.
|
||||
@@ -17,7 +17,7 @@ const SERVER: Token = Token(0);
|
||||
|
||||
pub mod client;
|
||||
pub mod commands;
|
||||
pub mod configuration;
|
||||
pub mod config;
|
||||
pub mod entity;
|
||||
pub mod server;
|
||||
pub mod util;
|
||||
|
||||
@@ -26,7 +26,7 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
client::Client,
|
||||
configuration::{AdvancedConfiguration, BasicConfiguration},
|
||||
config::{AdvancedConfiguration, BasicConfiguration},
|
||||
entity::player::{GameMode, Player},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user