mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'telastrus/auth' into 'master'
Server-side authentication See merge request veloren/veloren!419
This commit is contained in:
commit
2191921721
@ -30,6 +30,9 @@ fn main() {
|
||||
println!("Enter the server address");
|
||||
let server_addr = read_input();
|
||||
|
||||
println!("Enter your password");
|
||||
let password = read_input();
|
||||
|
||||
// Create a client.
|
||||
let mut client = Client::new(
|
||||
server_addr
|
||||
@ -45,7 +48,9 @@ fn main() {
|
||||
|
||||
println!("Players online: {:?}", client.get_players());
|
||||
|
||||
client.register(comp::Player::new(username, None));
|
||||
client
|
||||
.register(comp::Player::new(username, None), password)
|
||||
.unwrap();
|
||||
|
||||
let (tx, rx) = mpsc::channel();
|
||||
thread::spawn(move || loop {
|
||||
|
@ -7,6 +7,7 @@ pub enum Error {
|
||||
ServerTimeout,
|
||||
ServerShutdown,
|
||||
TooManyPlayers,
|
||||
InvalidAuth,
|
||||
//TODO: InvalidAlias,
|
||||
Other(String),
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ pub use specs::{join::Join, saveload::Marker, Entity as EcsEntity, ReadStorage};
|
||||
|
||||
use common::{
|
||||
comp,
|
||||
msg::{ClientMsg, ClientState, ServerError, ServerInfo, ServerMsg},
|
||||
msg::{ClientMsg, ClientState, RequestStateError, ServerError, ServerInfo, ServerMsg},
|
||||
net::PostBox,
|
||||
state::{State, Uid},
|
||||
terrain::{block::Block, chonk::ChonkMetrics, TerrainChunk, TerrainChunkSize},
|
||||
@ -132,9 +132,19 @@ impl Client {
|
||||
}
|
||||
|
||||
/// Request a state transition to `ClientState::Registered`.
|
||||
pub fn register(&mut self, player: comp::Player) {
|
||||
self.postbox.send_message(ClientMsg::Register { player });
|
||||
pub fn register(&mut self, player: comp::Player, password: String) -> Result<(), Error> {
|
||||
self.postbox
|
||||
.send_message(ClientMsg::Register { player, password });
|
||||
self.client_state = ClientState::Pending;
|
||||
loop {
|
||||
match self.postbox.next_message() {
|
||||
Some(ServerMsg::StateAnswer(Err((RequestStateError::Denied, _)))) => {
|
||||
break Err(Error::InvalidAuth)
|
||||
}
|
||||
Some(ServerMsg::StateAnswer(Ok(ClientState::Registered))) => break Ok(()),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Request a state transition to `ClientState::Character`.
|
||||
@ -397,6 +407,7 @@ impl Client {
|
||||
match msg {
|
||||
ServerMsg::Error(e) => match e {
|
||||
ServerError::TooManyPlayers => return Err(Error::ServerWentMad),
|
||||
ServerError::InvalidAuth => return Err(Error::InvalidAuth),
|
||||
//TODO: ServerError::InvalidAlias => return Err(Error::InvalidAlias),
|
||||
},
|
||||
ServerMsg::Shutdown => return Err(Error::ServerShutdown),
|
||||
@ -453,6 +464,10 @@ impl Client {
|
||||
self.client_state = state;
|
||||
}
|
||||
ServerMsg::StateAnswer(Err((error, state))) => {
|
||||
if error == RequestStateError::Denied {
|
||||
warn!("Connection denied!");
|
||||
return Err(Error::InvalidAuth);
|
||||
}
|
||||
warn!(
|
||||
"StateAnswer: {:?}. Server thinks client is in state {:?}.",
|
||||
error, state
|
||||
|
@ -7,6 +7,7 @@ use vek::*;
|
||||
pub enum ClientMsg {
|
||||
Register {
|
||||
player: comp::Player,
|
||||
password: String,
|
||||
},
|
||||
Character {
|
||||
name: String,
|
||||
|
@ -7,7 +7,7 @@ use crate::{
|
||||
use fxhash::FxHashMap;
|
||||
use vek::*;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
pub enum RequestStateError {
|
||||
Denied,
|
||||
Already,
|
||||
@ -69,6 +69,7 @@ pub enum ServerMsg {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum ServerError {
|
||||
TooManyPlayers,
|
||||
InvalidAuth,
|
||||
//TODO: InvalidAlias,
|
||||
}
|
||||
|
||||
|
32
server/src/auth_provider.rs
Normal file
32
server/src/auth_provider.rs
Normal file
@ -0,0 +1,32 @@
|
||||
use log::{info, warn};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct AuthProvider {
|
||||
accounts: HashMap<String, String>,
|
||||
}
|
||||
|
||||
impl AuthProvider {
|
||||
pub fn new() -> Self {
|
||||
AuthProvider {
|
||||
accounts: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn query(&mut self, username: String, password: String) -> bool {
|
||||
let pwd = password.clone();
|
||||
if self.accounts.entry(username.clone()).or_insert_with(|| {
|
||||
info!("Registered new user '{}'", &username);
|
||||
pwd
|
||||
}) == &password
|
||||
{
|
||||
info!("User '{}' successfully authenticated", username);
|
||||
true
|
||||
} else {
|
||||
warn!(
|
||||
"User '{}' attempted to log in with invalid password '{}'!",
|
||||
username, password
|
||||
);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
#![feature(drain_filter, bind_by_move_pattern_guards)]
|
||||
|
||||
pub mod auth_provider;
|
||||
pub mod client;
|
||||
pub mod cmd;
|
||||
pub mod error;
|
||||
@ -10,6 +11,7 @@ pub mod settings;
|
||||
pub use crate::{error::Error, input::Input, settings::ServerSettings};
|
||||
|
||||
use crate::{
|
||||
auth_provider::AuthProvider,
|
||||
client::{Client, Clients},
|
||||
cmd::CHAT_COMMANDS,
|
||||
};
|
||||
@ -68,6 +70,9 @@ pub struct Server {
|
||||
|
||||
server_settings: ServerSettings,
|
||||
server_info: ServerInfo,
|
||||
|
||||
// TODO: anything but this
|
||||
accounts: AuthProvider,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
@ -107,6 +112,7 @@ impl Server {
|
||||
description: settings.server_description.clone(),
|
||||
git_hash: common::util::GIT_HASH.to_string(),
|
||||
},
|
||||
accounts: AuthProvider::new(),
|
||||
server_settings: settings,
|
||||
};
|
||||
|
||||
@ -468,6 +474,8 @@ impl Server {
|
||||
fn handle_new_messages(&mut self) -> Result<Vec<Event>, Error> {
|
||||
let mut frontend_events = Vec::new();
|
||||
|
||||
let accounts = &mut self.accounts;
|
||||
|
||||
let state = &mut self.state;
|
||||
let mut new_chat_msgs = Vec::new();
|
||||
let mut disconnected_clients = Vec::new();
|
||||
@ -522,7 +530,11 @@ impl Server {
|
||||
ClientState::Pending => {}
|
||||
},
|
||||
// Valid player
|
||||
ClientMsg::Register { player } if player.is_valid() => {
|
||||
ClientMsg::Register { player, password } if player.is_valid() => {
|
||||
if !accounts.query(player.alias.clone(), password) {
|
||||
client.error_state(RequestStateError::Denied);
|
||||
break;
|
||||
}
|
||||
match client.client_state {
|
||||
ClientState::Connected => {
|
||||
Self::initialize_player(state, entity, client, player);
|
||||
@ -530,6 +542,7 @@ impl Server {
|
||||
// Use RequestState instead (No need to send `player` again).
|
||||
_ => client.error_state(RequestStateError::Impossible),
|
||||
}
|
||||
//client.allow_state(ClientState::Registered);
|
||||
}
|
||||
// Invalid player
|
||||
ClientMsg::Register { .. } => {
|
||||
|
@ -19,6 +19,7 @@ pub enum Error {
|
||||
NoAddress,
|
||||
// Parsing/host name resolution successful but could not connect.
|
||||
ConnectionFailed(ClientError),
|
||||
InvalidAuth,
|
||||
ClientCrashed,
|
||||
ServerIsFull,
|
||||
}
|
||||
@ -29,7 +30,12 @@ pub struct ClientInit {
|
||||
rx: Receiver<Result<Client, Error>>,
|
||||
}
|
||||
impl ClientInit {
|
||||
pub fn new(connection_args: (String, u16, bool), player: comp::Player, wait: bool) -> Self {
|
||||
pub fn new(
|
||||
connection_args: (String, u16, bool),
|
||||
player: comp::Player,
|
||||
password: String,
|
||||
wait: bool,
|
||||
) -> Self {
|
||||
let (server_address, default_port, prefer_ipv6) = connection_args;
|
||||
|
||||
let (tx, rx) = channel();
|
||||
@ -56,7 +62,13 @@ impl ClientInit {
|
||||
for socket_addr in first_addrs.into_iter().chain(second_addrs) {
|
||||
match Client::new(socket_addr, player.view_distance) {
|
||||
Ok(mut client) => {
|
||||
client.register(player);
|
||||
if let Err(ClientError::InvalidAuth) =
|
||||
client.register(player, password)
|
||||
{
|
||||
last_err = Some(Error::InvalidAuth);
|
||||
break;
|
||||
}
|
||||
//client.register(player, password);
|
||||
let _ = tx.send(Ok(client));
|
||||
|
||||
#[cfg(feature = "discord")]
|
||||
@ -81,6 +93,9 @@ impl ClientInit {
|
||||
last_err = Some(Error::ServerIsFull);
|
||||
break;
|
||||
}
|
||||
ClientError::InvalidAuth => {
|
||||
last_err = Some(Error::InvalidAuth);
|
||||
}
|
||||
// TODO: Handle errors?
|
||||
_ => panic!(
|
||||
"Unexpected non-network error when creating client: {:?}",
|
||||
|
@ -64,6 +64,7 @@ impl PlayState for MainMenuState {
|
||||
self.main_menu_ui.login_error(
|
||||
match err {
|
||||
InitError::BadAddress(_) | InitError::NoAddress => "Server not found",
|
||||
InitError::InvalidAuth => "Invalid credentials",
|
||||
InitError::ServerIsFull => "Server is Full!",
|
||||
InitError::ConnectionFailed(_) => "Connection failed",
|
||||
InitError::ClientCrashed => "Client crashed",
|
||||
@ -82,10 +83,12 @@ impl PlayState for MainMenuState {
|
||||
match event {
|
||||
MainMenuEvent::LoginAttempt {
|
||||
username,
|
||||
password,
|
||||
server_address,
|
||||
} => {
|
||||
let mut net_settings = &mut global_state.settings.networking;
|
||||
net_settings.username = username.clone();
|
||||
net_settings.password = password.clone();
|
||||
if !net_settings.servers.contains(&server_address) {
|
||||
net_settings.servers.push(server_address.clone());
|
||||
}
|
||||
@ -103,11 +106,12 @@ impl PlayState for MainMenuState {
|
||||
client_init = client_init.or(Some(ClientInit::new(
|
||||
(server_address, DEFAULT_PORT, false),
|
||||
player,
|
||||
password,
|
||||
false,
|
||||
)));
|
||||
} else {
|
||||
self.main_menu_ui
|
||||
.login_error("Invalid username".to_string());
|
||||
.login_error("Invalid username or password".to_string());
|
||||
}
|
||||
}
|
||||
MainMenuEvent::StartSingleplayer => {
|
||||
|
@ -38,6 +38,7 @@ impl PlayState for StartSingleplayerState {
|
||||
username.clone(),
|
||||
Some(global_state.settings.graphics.view_distance),
|
||||
),
|
||||
String::default(),
|
||||
true,
|
||||
);
|
||||
|
||||
|
@ -38,10 +38,14 @@ widget_ids! {
|
||||
username_text,
|
||||
username_bg,
|
||||
username_field,
|
||||
password_text,
|
||||
password_bg,
|
||||
password_field,
|
||||
singleplayer_button,
|
||||
singleplayer_text,
|
||||
usrnm_bg,
|
||||
srvr_bg,
|
||||
passwd_bg,
|
||||
// Server list
|
||||
servers_button,
|
||||
servers_frame,
|
||||
@ -87,6 +91,7 @@ font_ids! {
|
||||
pub enum Event {
|
||||
LoginAttempt {
|
||||
username: String,
|
||||
password: String,
|
||||
server_address: String,
|
||||
},
|
||||
StartSingleplayer,
|
||||
@ -101,6 +106,7 @@ pub struct MainMenuUi {
|
||||
imgs: Imgs,
|
||||
fonts: Fonts,
|
||||
username: String,
|
||||
password: String,
|
||||
server_address: String,
|
||||
login_error: Option<String>,
|
||||
connecting: Option<std::time::Instant>,
|
||||
@ -129,6 +135,7 @@ impl MainMenuUi {
|
||||
imgs,
|
||||
fonts,
|
||||
username: networking.username.clone(),
|
||||
password: "".to_owned(),
|
||||
server_address: networking.servers[networking.default_server].clone(),
|
||||
login_error: None,
|
||||
connecting: None,
|
||||
@ -227,6 +234,7 @@ impl MainMenuUi {
|
||||
self.connecting = Some(std::time::Instant::now());
|
||||
events.push(Event::LoginAttempt {
|
||||
username: self.username.clone(),
|
||||
password: self.password.clone(),
|
||||
server_address: self.server_address.clone(),
|
||||
});
|
||||
};
|
||||
@ -240,6 +248,7 @@ impl MainMenuUi {
|
||||
events.push(Event::StartSingleplayer);
|
||||
events.push(Event::LoginAttempt {
|
||||
username: "singleplayer".to_string(),
|
||||
password: String::default(),
|
||||
server_address: "localhost".to_string(),
|
||||
});
|
||||
};
|
||||
@ -274,6 +283,35 @@ impl MainMenuUi {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Password
|
||||
Rectangle::fill_with([320.0, 50.0], color::rgba(0.0, 0.0, 0.0, 0.97))
|
||||
.down_from(self.ids.usrnm_bg, 30.0)
|
||||
.set(self.ids.passwd_bg, ui_widgets);
|
||||
Image::new(self.imgs.input_bg)
|
||||
.w_h(337.0, 67.0)
|
||||
.middle_of(self.ids.passwd_bg)
|
||||
.set(self.ids.password_bg, ui_widgets);
|
||||
for event in TextBox::new(&self.password)
|
||||
.w_h(290.0, 30.0)
|
||||
.mid_bottom_with_margin_on(self.ids.password_bg, 44.0 / 2.0)
|
||||
.font_size(22)
|
||||
.font_id(self.fonts.opensans)
|
||||
.text_color(TEXT_COLOR)
|
||||
// transparent background
|
||||
.color(TRANSPARENT)
|
||||
.border_color(TRANSPARENT)
|
||||
.set(self.ids.password_field, ui_widgets)
|
||||
{
|
||||
match event {
|
||||
TextBoxEvent::Update(password) => {
|
||||
// Note: TextBox limits the input string length to what fits in it
|
||||
self.password = password;
|
||||
}
|
||||
TextBoxEvent::Enter => {
|
||||
login!();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Login error
|
||||
if let Some(msg) = &self.login_error {
|
||||
let text = Text::new(&msg)
|
||||
@ -369,7 +407,7 @@ impl MainMenuUi {
|
||||
}
|
||||
// Server address
|
||||
Rectangle::fill_with([320.0, 50.0], color::rgba(0.0, 0.0, 0.0, 0.97))
|
||||
.down_from(self.ids.usrnm_bg, 30.0)
|
||||
.down_from(self.ids.passwd_bg, 30.0)
|
||||
.set(self.ids.srvr_bg, ui_widgets);
|
||||
Image::new(self.imgs.input_bg)
|
||||
.w_h(337.0, 67.0)
|
||||
|
@ -100,6 +100,7 @@ impl Default for GameplaySettings {
|
||||
#[serde(default)]
|
||||
pub struct NetworkingSettings {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
pub servers: Vec<String>,
|
||||
pub default_server: usize,
|
||||
}
|
||||
@ -108,6 +109,7 @@ impl Default for NetworkingSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
username: "Username".to_string(),
|
||||
password: String::default(),
|
||||
servers: vec!["server.veloren.net".to_string()],
|
||||
default_server: 0,
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user