Store a salted and hashed password.

Lots of changes for this to happen. Passwords are now to be stored separately
from the players in a separate table in the database, and will only be
queried when necessary. Random salts are generated, and a hash of the
salted password is stored along with the salt. Some functions and structs
to do all this were added. If I did it right, this should make the password
storage secure, even if nothing else is.
master
rasul 4 years ago
parent b38d96dd5c
commit 64114b6d68

@ -2,22 +2,21 @@ use mio::Token;
use crate::command::CommandSetPlayer; use crate::command::CommandSetPlayer;
use crate::database::Db; use crate::database::Db;
use crate::password::Password;
use crate::queue::SendQueue; use crate::queue::SendQueue;
use crate::{try_option_send_error, try_send_error}; use crate::{try_option_send_error, try_send_error};
impl CommandSetPlayer { impl CommandSetPlayer {
/// Set the player's password /// Set the player's password
pub fn dispatch_password(&self, args: String, token: Token, db: &mut Db) -> SendQueue { pub fn dispatch_password(&self, args: String, token: Token, db: &mut Db) -> SendQueue {
let mut player = try_option_send_error!(token, db.get_connected_player(token)); let player = try_option_send_error!(token, db.get_connected_player(token));
if args.is_empty() { if args.is_empty() {
return (token, "Password can't be empty").into(); return (token, "Password can't be empty").into();
} }
player.password = args; let password = Password::new(args);
let _ = try_send_error!(token, db.save_password(player.id, &password));
let _ = try_send_error!(token, db.save_player(&player));
let _ = try_send_error!(token, db.save_connected_player(token, &player));
SendQueue::ok(token) SendQueue::ok(token)
} }

@ -17,7 +17,7 @@ impl Db {
) -> RudeResult<Vec<(Token, Player)>> { ) -> RudeResult<Vec<(Token, Player)>> {
let mut statement = try_log!( let mut statement = try_log!(
self.0.prepare( self.0.prepare(
"select connected_players.token, players.id, players.name, players.password, players.created, players.location from connected_players, players where players.location = ? and connected_players.player = players.id;" "select connected_players.token, players.id, players.name, players.created, players.location from connected_players, players where players.location = ? and connected_players.player = players.id;"
), ),
"Unable to prepare sql statement" "Unable to prepare sql statement"
); );
@ -42,7 +42,7 @@ impl Db {
pub fn get_connected_player(&self, token: Token) -> RudeResult<Option<Player>> { pub fn get_connected_player(&self, token: Token) -> RudeResult<Option<Player>> {
let mut statement = try_log!( let mut statement = try_log!(
self.0.prepare( self.0.prepare(
"select connected_players.token, players.id, players.name, players.password, players.created, players.location from connected_players, players where connected_players.token = ? and connected_players.player = players.id;" "select connected_players.token, players.id, players.name, players.created, players.location from connected_players, players where connected_players.token = ? and connected_players.player = players.id;"
), ),
"Unable to prepare sql statement" "Unable to prepare sql statement"
); );

@ -2,6 +2,7 @@
mod connected_players; mod connected_players;
mod db; mod db;
mod password;
mod players; mod players;
mod rooms; mod rooms;
mod zones; mod zones;

@ -0,0 +1,55 @@
use std::convert::TryFrom;
use rusqlite::params;
use crate::database::Db;
use crate::id::Id;
use crate::password::Password;
use crate::result::RudeResult;
use crate::try_log;
impl Db {
/// Get a password
pub fn get_password(&self, id: Id) -> RudeResult<Option<Password>> {
let mut statement = try_log!(
self.0
.prepare("select salt, hash from passwords where player = ?"),
"Unable to prepare sql statement"
);
let mut rows = try_log!(statement.query(params![id]), "Unable to perform query");
if let Some(row) = try_log!(rows.next(), "Unable to retrieve row") {
Ok(Some(try_log!(
Password::try_from(row),
"Unable to get Password from Row"
)))
} else {
Ok(None)
}
}
/// Save a password
pub fn save_password(&self, id: Id, password: &Password) -> RudeResult<()> {
let mut statement = try_log!(
self.0.prepare(
"insert into passwords (player, salt, hash) values (?, ?, ?) on conflict(player) do update set player=?, salt=?, hash=?;"
),
"Unable to prepare statement"
);
let _ = try_log!(
statement.execute(params![
id,
password.salt,
password.hash,
id,
password.salt,
password.hash,
]),
"Unable to perform query"
);
Ok(())
}
}

@ -13,7 +13,7 @@ impl Db {
pub fn _load_player(&self, id: Id) -> RudeResult<Option<Player>> { pub fn _load_player(&self, id: Id) -> RudeResult<Option<Player>> {
let mut statement = try_log!( let mut statement = try_log!(
self.0 self.0
.prepare("select id, name, password, created, location from players where id = ?"), .prepare("select id, name, created, location from players where id = ?"),
"Unable to prepare sql statement" "Unable to prepare sql statement"
); );
@ -34,9 +34,8 @@ impl Db {
/// Find a player by the name /// Find a player by the name
pub fn find_player_by_name<S: Into<String>>(&self, name: S) -> RudeResult<Option<Player>> { pub fn find_player_by_name<S: Into<String>>(&self, name: S) -> RudeResult<Option<Player>> {
let mut statement = try_log!( let mut statement = try_log!(
self.0.prepare( self.0
"select id, name, password, created, location from players where name = ?" .prepare("select id, name, created, location from players where name = ?"),
),
"Unable to prepare sql statement" "Unable to prepare sql statement"
); );
@ -61,7 +60,7 @@ impl Db {
pub fn save_player(&self, player: &Player) -> RudeResult<()> { pub fn save_player(&self, player: &Player) -> RudeResult<()> {
let mut statement = try_log!( let mut statement = try_log!(
self.0.prepare( self.0.prepare(
"insert into players (id, name, password, created, location) values (?, ?, ?, ?, ?) on conflict(id) do update set id=?, name=?, password=?, created=?, location=?;" "insert into players (id, name, created, location) values (?, ?, ?, ?) on conflict(id) do update set id=?, name=?, created=?, location=?;"
), ),
"Unable to prepare statement" "Unable to prepare statement"
); );
@ -70,12 +69,10 @@ impl Db {
statement.execute(params![ statement.execute(params![
player.id, player.id,
player.name, player.name,
player.password,
player.created, player.created,
player.location, player.location,
player.id, player.id,
player.name, player.name,
player.password,
player.created, player.created,
player.location, player.location,
]), ]),

@ -3,6 +3,7 @@ use mio::Token;
use crate::command::Command; use crate::command::Command;
use crate::game::{Game, PlayerCheck}; use crate::game::{Game, PlayerCheck};
use crate::password::Password;
use crate::player::Player; use crate::player::Player;
use crate::queue::SendQueue; use crate::queue::SendQueue;
use crate::state::*; use crate::state::*;
@ -87,7 +88,7 @@ impl Game {
false, false,
Some(State::Login(Login::CreatePassword2(( Some(State::Login(Login::CreatePassword2((
username.to_owned(), username.to_owned(),
pass, Password::new(pass),
)))), )))),
); );
} }
@ -103,8 +104,8 @@ impl Game {
} }
Login::CreatePassword2((username, pass)) => { Login::CreatePassword2((username, pass)) => {
let pass = pass.to_owned(); if !pass.check(message) {
if message.is_empty() || message != pass { send_queue.push(token, "\n\nPasswords don't match", false, None);
send_queue.push( send_queue.push(
token, token,
"\n\nUsername: ", "\n\nUsername: ",
@ -116,12 +117,13 @@ impl Game {
let player = Player { let player = Player {
id, id,
name: username.clone(), name: username.clone(),
password: pass,
created: Utc::now(), created: Utc::now(),
location: self.config.player.starting_location.clone(), location: self.config.player.starting_location.clone(),
}; };
if self.db.single_save_player(token, &player).is_ok() { if self.db.single_save_player(token, &player).is_ok()
&& self.db.save_password(player.id, pass).is_ok()
{
send_queue.push( send_queue.push(
token, token,
format!("Welcome, {}\n", username), format!("Welcome, {}\n", username),
@ -155,24 +157,34 @@ impl Game {
); );
} else { } else {
match self.db.find_player_by_name(username) { match self.db.find_player_by_name(username) {
Ok(Some(player)) => { Ok(Some(player)) => match self.db.get_password(player.id) {
if message == player.password { Ok(Some(password)) => {
if self.db.save_connected_player(token, &player).is_ok() { if password.check(message) {
send_queue.push( if self.db.save_connected_player(token, &player).is_ok() {
token, send_queue.push(
format!("Welcome back, {}\n\n", username), token,
false, format!("Welcome back, {}\n\n", username),
Some(State::Action), false,
); Some(State::Action),
);
send_queue.append(&mut Command::dispatch_look( send_queue.append(&mut Command::dispatch_look(
&Command::default(), &Command::default(),
String::new(), String::new(),
token, token,
&mut self.db, &mut self.db,
)); ));
} else {
send_queue.push(token, "Unable to login\n", false, None);
send_queue.push(
token,
"\n\nUsername: ",
false,
Some(State::Login(Login::Username)),
);
}
} else { } else {
send_queue.push(token, "Unable to login\n", false, None); send_queue.push(token, "Incorrect password\n", false, None);
send_queue.push( send_queue.push(
token, token,
"\n\nUsername: ", "\n\nUsername: ",
@ -180,8 +192,10 @@ impl Game {
Some(State::Login(Login::Username)), Some(State::Login(Login::Username)),
); );
} }
} else { }
send_queue.push(token, "Incorrect password\n", false, None); Ok(None) => {
log::error!("Player has no password: {}", player.id);
send_queue.push(token, "Error\n", false, None);
send_queue.push( send_queue.push(
token, token,
"\n\nUsername: ", "\n\nUsername: ",
@ -189,7 +203,17 @@ impl Game {
Some(State::Login(Login::Username)), Some(State::Login(Login::Username)),
); );
} }
} Err(e) => {
log::error!("Database error: {}", e);
send_queue.push(token, "Error\n", false, None);
send_queue.push(
token,
"\n\nUsername: ",
false,
Some(State::Login(Login::Username)),
);
}
},
Ok(None) | Err(_) => { Ok(None) | Err(_) => {
send_queue.push(token, "Error\n", false, None); send_queue.push(token, "Error\n", false, None);
send_queue.push( send_queue.push(

@ -11,6 +11,7 @@ pub mod logger;
#[macro_use] #[macro_use]
#[doc(hidden)] #[doc(hidden)]
pub mod macros; pub mod macros;
pub mod password;
pub mod player; pub mod player;
pub mod queue; pub mod queue;
pub mod result; pub mod result;

@ -8,6 +8,7 @@ mod id;
mod logger; mod logger;
#[macro_use] #[macro_use]
mod macros; mod macros;
mod password;
mod player; mod player;
mod queue; mod queue;
mod result; mod result;

@ -0,0 +1,55 @@
//! Player information
use std::cmp::PartialEq;
use std::convert::TryFrom;
use std::error::Error;
use rusqlite::Row;
use serde_derive::{Deserialize, Serialize};
use crate::hash::{hash, salt};
/// Containing object for the password
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Password {
/// Salt
pub salt: String,
/// Salted and hashed password
pub hash: String,
}
impl Password {
/// Create a new password object from a string password.
pub fn new<S: Into<String>>(s: S) -> Self {
let salt = salt();
let hash = hash(s, &salt);
Self { salt, hash }
}
/// Check the password against the provided password.
pub fn check<S: Into<String>>(&self, s: S) -> bool {
let s = s.into();
let hash = hash(s, &self.salt);
self.hash == hash
}
}
impl<'a> TryFrom<&Row<'a>> for Password {
type Error = Box<dyn Error>;
fn try_from(row: &Row) -> Result<Self, Self::Error> {
Ok(Self {
salt: try_log!(row.get("salt"), "salt"),
hash: try_log!(row.get("hash"), "hash"),
})
}
}
impl PartialEq for Password {
fn eq(&self, other: &Self) -> bool {
self.hash == other.hash
}
}

@ -18,9 +18,6 @@ pub struct Player {
/// Player name /// Player name
pub name: String, pub name: String,
/// Player's password (this needs to be properly salted and hashed but isn't)
pub password: String,
/// Creation DateTime /// Creation DateTime
pub created: DateTime<Utc>, pub created: DateTime<Utc>,
@ -35,7 +32,6 @@ impl<'a> TryFrom<&Row<'a>> for Player {
Ok(Self { Ok(Self {
id: try_log!(row.get("id"), "id"), id: try_log!(row.get("id"), "id"),
name: try_log!(row.get("name"), "name"), name: try_log!(row.get("name"), "name"),
password: try_log!(row.get("password"), "password"),
created: try_log!(row.get("created"), "created"), created: try_log!(row.get("created"), "created"),
location: try_log!(row.get("location"), "location"), location: try_log!(row.get("location"), "location"),
}) })

@ -1,5 +1,7 @@
//! State information for a connected `Client`. //! State information for a connected `Client`.
use crate::password::Password;
/// Play state for a conected `Client`. /// Play state for a conected `Client`.
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum State { pub enum State {
@ -26,7 +28,7 @@ pub enum Login {
CreatePassword(String), CreatePassword(String),
/// New user password again /// New user password again
CreatePassword2((String, String)), CreatePassword2((String, Password)),
/// Password for existing user /// Password for existing user
Password(String), Password(String),

Loading…
Cancel
Save