Compare commits
8 Commits
915483c465
...
2c625a3329
Author | SHA1 | Date |
---|---|---|
ayham | 2c625a3329 | |
ayham | 661e3709fb | |
ayham | edf212de7e | |
ayham | ca6f0677ed | |
ayham | 888b2cbe80 | |
ayham | 30a20873ea | |
ayham | 27e68e3eeb | |
ayham | 4f420925cf |
|
@ -27,6 +27,13 @@ steps:
|
||||||
- sleep 75
|
- sleep 75
|
||||||
- ./scripts/deploy_sandbox_client.sh server:4000
|
- ./scripts/deploy_sandbox_client.sh server:4000
|
||||||
|
|
||||||
|
- name: test_server
|
||||||
|
image: rust
|
||||||
|
commands:
|
||||||
|
- sleep 75
|
||||||
|
- ./scripts/deploy_sandbox_client.sh server:4000
|
||||||
|
- cargo test --no-default-features --features "client,tls_no_verify"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
- name: database
|
- name: database
|
||||||
image: postgres
|
image: postgres
|
||||||
|
|
24
Cargo.toml
24
Cargo.toml
|
@ -18,13 +18,13 @@ test = false
|
||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["server", "client"]
|
default = ["server"]
|
||||||
server = []
|
server = []
|
||||||
client = []
|
client = []
|
||||||
tls_no_verify = ["tokio-rustls/dangerous_configuration"]
|
tls_no_verify = ["tokio-rustls/dangerous_configuration"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
argh = "*"
|
argh = "0.1.5"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
tokio = { version = "1.6.1", features = [ "full" ] }
|
tokio = { version = "1.6.1", features = [ "full" ] }
|
||||||
tokio-io = { version = "0.1.13" }
|
tokio-io = { version = "0.1.13" }
|
||||||
|
@ -32,23 +32,19 @@ tokio-rustls = { version = "0.22.0" }
|
||||||
tokio-util = { version = "0.6.7" }
|
tokio-util = { version = "0.6.7" }
|
||||||
tokio-postgres = { version = "0.7.2" }
|
tokio-postgres = { version = "0.7.2" }
|
||||||
webpki-roots = { version = "0.21" }
|
webpki-roots = { version = "0.21" }
|
||||||
futures = "*"
|
futures = "0.3.16"
|
||||||
bytes = "*"
|
|
||||||
#postgres = { version = "0.4.0" }
|
#postgres = { version = "0.4.0" }
|
||||||
postgres-types = { version = "0.2.1", features = ["derive"] }
|
postgres-types = { version = "0.2.1", features = ["derive"] }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
fern = { version = "0.6.0", features = ["colored"] }
|
fern = { version = "0.6.0", features = ["colored"] }
|
||||||
enum_primitive = "*"
|
|
||||||
os_type="2.2"
|
os_type="2.2"
|
||||||
ring="*"
|
ring="0.16.20"
|
||||||
data-encoding="*"
|
bincode="1.3.3"
|
||||||
bincode="*"
|
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
ct-logs="0.7"
|
ct-logs="0.7"
|
||||||
either="*"
|
|
||||||
arrayref="*"
|
|
||||||
rust-crypto="0.2.36"
|
rust-crypto="0.2.36"
|
||||||
jsonwebtoken="*"
|
jsonwebtoken="7.2.0"
|
||||||
json="*"
|
json="0.12.4"
|
||||||
bitflags="*"
|
rand="0.8.4"
|
||||||
rand="*"
|
rand_core = { version = "0.6", features = ["std"] }
|
||||||
|
argon2= { version = "0.2", features = ["password-hash"] }
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
. ./scripts/env.sh && cargo run --no-default-features --features "server" -- ${1:-0.0.0.0:4000} &
|
||||||
|
|
||||||
|
PID_server=$!
|
||||||
|
echo $PID_server
|
||||||
|
|
||||||
|
while kill -0 $PID_server > /dev/null 2>&1
|
||||||
|
do
|
||||||
|
[[ -f "/tmp/paper/running" ]] && break
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -f "/tmp/paper/running" ]]; then
|
||||||
|
cargo test --no-default-features --features "client,tls_no_verify"
|
||||||
|
rm /tmp/paper/running
|
||||||
|
kill $PID_server
|
||||||
|
pkill sandbox
|
||||||
|
fi
|
|
@ -1,9 +1,9 @@
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
|
||||||
#[cfg(feature = "client")]
|
#[cfg(feature = "client")]
|
||||||
use libtrader::client::initializer::libtrader_init_client;
|
use libtrader::client;
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
use libtrader::server::initializer::{libtrader_init_server, IP};
|
use libtrader::server;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
|
@ -18,18 +18,17 @@ fn main() {
|
||||||
|
|
||||||
// Spawn server
|
// Spawn server
|
||||||
rt.block_on(async move {
|
rt.block_on(async move {
|
||||||
IP.scope("0.0.0.0:0000".parse().unwrap(), async move {
|
server::IP
|
||||||
// for main task logging
|
.scope("0.0.0.0:0000".parse().unwrap(), async move {
|
||||||
libtrader_init_server()
|
// for main task logging
|
||||||
.await
|
server::initialize().await.expect("failed running server");
|
||||||
.expect("failed running server");
|
})
|
||||||
})
|
.await;
|
||||||
.await;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* this is a sandbox, we should try to atleast
|
/* this is a sandbox, we should try to atleast
|
||||||
* implement a testing method */
|
* implement a testing method */
|
||||||
#[cfg(feature = "client")]
|
#[cfg(feature = "client")]
|
||||||
libtrader_init_client().expect("failed running client");
|
client::initialize().expect("failed running client");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,12 @@
|
||||||
use data_encoding::HEXUPPER;
|
|
||||||
use ring::digest;
|
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use crate::common::account::hash::hash;
|
use argon2::password_hash::PasswordHash;
|
||||||
use crate::common::message::assert_msg::assert_msg;
|
|
||||||
use crate::common::message::inst::CommandInst;
|
|
||||||
use crate::common::message::message::Message;
|
|
||||||
use crate::common::message::message_builder::message_builder;
|
|
||||||
use crate::common::message::message_type::MessageType;
|
|
||||||
use crate::common::misc::return_flags::ReturnFlags;
|
|
||||||
|
|
||||||
use crate::client::network::cmd::req_server_salt::req_server_salt;
|
use crate::common::message::*;
|
||||||
|
|
||||||
|
use crate::client::account::hash::*;
|
||||||
|
use crate::client::network::cmd::req_server_salt::*;
|
||||||
|
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use tokio_rustls::client::TlsStream;
|
use tokio_rustls::client::TlsStream;
|
||||||
|
|
||||||
|
@ -39,91 +33,76 @@ use tokio_rustls::client::TlsStream;
|
||||||
/// Err(err) => panic!("panik! {}", err), /* unauth OR invalid JWT token */
|
/// Err(err) => panic!("panik! {}", err), /* unauth OR invalid JWT token */
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub async fn acc_auth(
|
pub async fn authorize(
|
||||||
socket: &mut TlsStream<TcpStream>,
|
socket: &mut TlsStream<TcpStream>,
|
||||||
username: &str,
|
username: &str,
|
||||||
email: &str,
|
email: &str,
|
||||||
password: &str,
|
passw: &str,
|
||||||
) -> io::Result<String> {
|
) -> std::io::Result<String> {
|
||||||
/*
|
/*
|
||||||
* get email salt
|
* get email salt
|
||||||
* */
|
* */
|
||||||
let email_salt: [u8; digest::SHA512_OUTPUT_LEN] =
|
let email_salt = req_server_salt(socket, username, Command::GetEmailSalt).await?;
|
||||||
req_server_salt(socket, username, CommandInst::GetEmailSalt as i64).await?;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* get password salt
|
* get password salt
|
||||||
* */
|
* */
|
||||||
let password_salt: [u8; digest::SHA512_OUTPUT_LEN] =
|
let passw_salt = req_server_salt(socket, username, Command::GetPasswordSalt).await?;
|
||||||
req_server_salt(socket, username, CommandInst::GetPasswordSalt as i64).await?;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* hash the email
|
* hash the email
|
||||||
*/
|
*/
|
||||||
let hashed_email = hash(&email.as_bytes().to_vec(), &email_salt.to_vec(), 175_000);
|
let hashed_email = hash(email, &email_salt, false);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* hash the password
|
* hash the password
|
||||||
*/
|
*/
|
||||||
let hashed_password = hash(
|
let hashed_passw = hash(passw, &passw_salt, false);
|
||||||
&password.as_bytes().to_vec(),
|
|
||||||
&password_salt.to_vec(),
|
/*
|
||||||
250_000,
|
* only send hash without other PHC information
|
||||||
);
|
* */
|
||||||
|
let parsed_email = PasswordHash::new(&hashed_email).unwrap();
|
||||||
|
let parsed_passw = PasswordHash::new(&hashed_passw).unwrap();
|
||||||
|
let parsed_email_hash = parsed_email.hash.unwrap().to_string();
|
||||||
|
let parsed_passw_hash = parsed_passw.hash.unwrap().to_string();
|
||||||
|
|
||||||
/* generate message to be sent to the server */
|
/* generate message to be sent to the server */
|
||||||
let data = object! {
|
let data = object! {
|
||||||
hashed_email: HEXUPPER.encode(&hashed_email),
|
hashed_email: parsed_email_hash,
|
||||||
hashed_password: HEXUPPER.encode(&hashed_password),
|
hashed_passw: parsed_passw_hash,
|
||||||
username: username
|
username: username
|
||||||
};
|
};
|
||||||
let message = message_builder(
|
/* build message request */
|
||||||
MessageType::Command,
|
Message::new()
|
||||||
CommandInst::LoginMethod1 as i64,
|
.command(Command::LoginMethod1)
|
||||||
3,
|
.data(data.to_string())
|
||||||
0,
|
.send(socket)
|
||||||
0,
|
|
||||||
data.dump().as_bytes().to_vec(),
|
|
||||||
);
|
|
||||||
socket
|
|
||||||
.write_all(&bincode::serialize(&message).unwrap())
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
/* decode response */
|
/* decode response */
|
||||||
let mut buf = Vec::with_capacity(4096);
|
let ret_msg = Message::receive(socket).await?;
|
||||||
socket.read_buf(&mut buf).await?;
|
|
||||||
let response: Message = bincode::deserialize(&buf).map_err(|_| {
|
|
||||||
io::Error::new(
|
|
||||||
io::ErrorKind::InvalidInput,
|
|
||||||
format!("{}", ReturnFlags::ClientAccUnauthorized),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if assert_msg(
|
/* decode response */
|
||||||
&response,
|
if !ret_msg.assert_command(Command::Success) || !ret_msg.assert_data() {
|
||||||
MessageType::ServerReturn,
|
Err(io::Error::new(
|
||||||
true,
|
io::ErrorKind::ConnectionRefused,
|
||||||
1,
|
format!(
|
||||||
false,
|
"Failed authorizing account, {}, server returned error: {}.",
|
||||||
0,
|
username,
|
||||||
false,
|
ret_msg.get_err()
|
||||||
0,
|
),
|
||||||
false,
|
))
|
||||||
0,
|
} else {
|
||||||
) && response.data.len() != 0
|
|
||||||
&& response.instruction == 1
|
|
||||||
{
|
|
||||||
/* authorized */
|
/* authorized */
|
||||||
return Ok(String::from_utf8(response.data).map_err(|_| {
|
Ok(ret_msg.get_data().map_err(|_| {
|
||||||
io::Error::new(
|
io::Error::new(
|
||||||
io::ErrorKind::InvalidData,
|
io::ErrorKind::InvalidData,
|
||||||
format!("{}", ReturnFlags::ClientAccInvalidSessionId),
|
format!(
|
||||||
|
"Failed authorizing account, {}, server returned invalid data.",
|
||||||
|
username
|
||||||
|
),
|
||||||
)
|
)
|
||||||
})?);
|
})?)
|
||||||
} else {
|
|
||||||
return Err(io::Error::new(
|
|
||||||
io::ErrorKind::ConnectionRefused,
|
|
||||||
format!("{}", ReturnFlags::ClientAccUnauthorized),
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,11 @@
|
||||||
use data_encoding::HEXUPPER;
|
|
||||||
use ring::digest;
|
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use crate::client::account::hash_email::hash_email;
|
use crate::common::message::*;
|
||||||
use crate::client::account::hash_pwd::hash_pwd;
|
|
||||||
|
|
||||||
use crate::common::message::assert_msg::assert_msg;
|
use crate::client::account::hash::*;
|
||||||
use crate::common::message::inst::CommandInst;
|
|
||||||
use crate::common::message::message::Message;
|
|
||||||
use crate::common::message::message_builder::message_builder;
|
|
||||||
use crate::common::message::message_type::MessageType;
|
|
||||||
use crate::common::misc::return_flags::ReturnFlags;
|
|
||||||
|
|
||||||
use crate::client::network::cmd::get_server_salt::get_server_salt;
|
use crate::client::network::cmd::get_server_salt::get_server_salt;
|
||||||
|
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use tokio_rustls::client::TlsStream;
|
use tokio_rustls::client::TlsStream;
|
||||||
|
|
||||||
|
@ -41,73 +32,53 @@ use tokio_rustls::client::TlsStream;
|
||||||
/// Err(err) => panic!("panik {}", err),
|
/// Err(err) => panic!("panik {}", err),
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub async fn acc_create(
|
pub async fn create(
|
||||||
socket: &mut TlsStream<TcpStream>,
|
socket: &mut TlsStream<TcpStream>,
|
||||||
username: &str,
|
username: &str,
|
||||||
email: &str,
|
email: &str,
|
||||||
password: &str,
|
passw: &str,
|
||||||
) -> io::Result<()> {
|
) -> std::io::Result<()> {
|
||||||
/*
|
/*
|
||||||
* get two server salts for email, and password
|
* get two server salts for email, and password
|
||||||
* */
|
* */
|
||||||
let email_server_salt: [u8; digest::SHA512_OUTPUT_LEN / 2] = get_server_salt(socket).await?;
|
let email_server_salt = get_server_salt(socket).await?;
|
||||||
let password_server_salt: [u8; digest::SHA512_OUTPUT_LEN / 2] = get_server_salt(socket).await?;
|
let passw_server_salt = get_server_salt(socket).await?;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* generate hashes for email, password
|
* generate hashes for email, password
|
||||||
* */
|
* */
|
||||||
let email_hash = hash_email(&email.as_bytes().to_vec(), email_server_salt);
|
let email_hash_phc = hash(email, &email_server_salt, true);
|
||||||
let password_hash = hash_pwd(&password.as_bytes().to_vec(), password_server_salt);
|
let passw_hash_phc = hash(passw, &passw_server_salt, true);
|
||||||
|
|
||||||
/* generate message to be sent to the server */
|
/* generate message to be sent to the server */
|
||||||
let data = object! {
|
let data = object! {
|
||||||
email_hash: HEXUPPER.encode(&email_hash.0),
|
email_client_hash_phc: email_hash_phc,
|
||||||
email_client_salt: HEXUPPER.encode(&email_hash.1),
|
passw_client_hash_phc: passw_hash_phc,
|
||||||
password_hash: HEXUPPER.encode(&password_hash.0),
|
|
||||||
password_client_salt: HEXUPPER.encode(&password_hash.1),
|
|
||||||
username: username
|
username: username
|
||||||
};
|
};
|
||||||
let message = message_builder(
|
/* build message request */
|
||||||
MessageType::Command,
|
Message::new()
|
||||||
CommandInst::Register as i64,
|
.command(Command::Register)
|
||||||
5,
|
.data(data.to_string())
|
||||||
0,
|
.send(socket)
|
||||||
0,
|
|
||||||
data.dump().as_bytes().to_vec(),
|
|
||||||
);
|
|
||||||
socket
|
|
||||||
.write_all(&bincode::serialize(&message).unwrap())
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
/* decode response */
|
/* decode response */
|
||||||
let mut buf = Vec::with_capacity(4096);
|
let ret_msg = Message::receive(socket).await?;
|
||||||
socket.read_buf(&mut buf).await?;
|
|
||||||
let response: Message = bincode::deserialize(&buf).map_err(|_| {
|
/* assert received message */
|
||||||
io::Error::new(
|
if !ret_msg.assert_command(Command::Success) || !ret_msg.assert_data() {
|
||||||
io::ErrorKind::InvalidInput,
|
|
||||||
format!("{}", ReturnFlags::ClientTlsReadError),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
if !assert_msg(
|
|
||||||
&response,
|
|
||||||
MessageType::ServerReturn,
|
|
||||||
true,
|
|
||||||
1,
|
|
||||||
false,
|
|
||||||
0,
|
|
||||||
false,
|
|
||||||
0,
|
|
||||||
false,
|
|
||||||
0,
|
|
||||||
) && response.instruction == 1
|
|
||||||
{
|
|
||||||
/* created successfully */
|
/* created successfully */
|
||||||
return Ok(());
|
return Ok(());
|
||||||
} else {
|
} else {
|
||||||
/* server rejected account creation */
|
/* server rejected account creation */
|
||||||
return Err(io::Error::new(
|
return Err(io::Error::new(
|
||||||
io::ErrorKind::ConnectionRefused,
|
io::ErrorKind::ConnectionRefused,
|
||||||
format!("{}", ReturnFlags::ClientAccCreationFailed),
|
format!(
|
||||||
|
"Failed creating account for user, {}, server reason: {}",
|
||||||
|
username,
|
||||||
|
ret_msg.get_err()
|
||||||
|
),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
use argon2::{
|
||||||
|
password_hash::{PasswordHasher, SaltString},
|
||||||
|
Argon2,
|
||||||
|
};
|
||||||
|
use rand_core::OsRng;
|
||||||
|
|
||||||
|
/// Generates a client email hash from a raw email.
|
||||||
|
///
|
||||||
|
/// Takes in a raw email, outputs a hashed version of the client email to be sent to the server
|
||||||
|
/// with the returned client random bits that make up the whole client salt. This function is to be
|
||||||
|
/// used on client random bits that make up the whole client salt. This function is to be used on
|
||||||
|
/// client side account creation. The result from this function is not be stored directly on the
|
||||||
|
/// database, result must be run through the server side hashing again.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// email - The raw user email to be hashed.
|
||||||
|
/// server_salt - The server's part sent of the salt.
|
||||||
|
///
|
||||||
|
/// Returns: a tuple containing the client hash and full salt, nothing on failure.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```rust
|
||||||
|
/// let enc = hash_email("totallyrealemail@anemail.c0m", server_salt).unwrap();
|
||||||
|
/// ```
|
||||||
|
pub fn hash(email: &str, server_salt: &SaltString, resalt: bool) -> String {
|
||||||
|
let mut raw_salt: String = server_salt.as_str().to_string();
|
||||||
|
if resalt {
|
||||||
|
raw_salt.push_str(SaltString::generate(&mut OsRng).as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
let salt = SaltString::new(&raw_salt).unwrap();
|
||||||
|
|
||||||
|
let argon2id = Argon2::default();
|
||||||
|
let phc = argon2id
|
||||||
|
.hash_password_simple(&email.as_bytes(), &salt)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
phc.to_string()
|
||||||
|
}
|
|
@ -1,84 +0,0 @@
|
||||||
use ring::rand::SecureRandom;
|
|
||||||
use ring::{digest, rand};
|
|
||||||
|
|
||||||
use crate::common::account::hash::hash;
|
|
||||||
|
|
||||||
/// Generates a client email hash from a raw email.
|
|
||||||
///
|
|
||||||
/// Takes in a raw email, outputs a hashed version of the client email to be sent to the server
|
|
||||||
/// with the returned client random bits that make up the whole client salt. This function is to be
|
|
||||||
/// used on client random bits that make up the whole client salt. This function is to be used on
|
|
||||||
/// client side account creation. The result from this function is not be stored directly on the
|
|
||||||
/// database, result must be run through the server side hashing again.
|
|
||||||
///
|
|
||||||
/// Arguments:
|
|
||||||
/// email - The raw user email to be hashed.
|
|
||||||
/// server_salt - The server's part sent of the salt.
|
|
||||||
///
|
|
||||||
/// Returns: a tuple containing the client hash and full salt, nothing on failure.
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
/// ```rust
|
|
||||||
/// let enc = hash_email("totallyrealemail@anemail.c0m", server_salt).unwrap();
|
|
||||||
/// println!("Client Email Hash: {}", HEXUPPER.encode(&enc.0));
|
|
||||||
/// println!("Client Email Salt: {}", HEXUPPER.encode(&enc.1));
|
|
||||||
/// ```
|
|
||||||
pub fn hash_email(
|
|
||||||
email: &Vec<u8>,
|
|
||||||
server_salt: [u8; digest::SHA512_OUTPUT_LEN / 2],
|
|
||||||
) -> (
|
|
||||||
[u8; digest::SHA512_OUTPUT_LEN],
|
|
||||||
[u8; digest::SHA512_OUTPUT_LEN],
|
|
||||||
) {
|
|
||||||
// client hash, full salt
|
|
||||||
let rng = rand::SystemRandom::new();
|
|
||||||
|
|
||||||
let mut client_salt = [0u8; digest::SHA512_OUTPUT_LEN / 2];
|
|
||||||
rng.fill(&mut client_salt).unwrap();
|
|
||||||
|
|
||||||
let salt = [server_salt, client_salt].concat();
|
|
||||||
|
|
||||||
let hash = hash(email, &salt, 175_000);
|
|
||||||
(hash, *array_ref!(salt, 0, digest::SHA512_OUTPUT_LEN))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
use data_encoding::HEXUPPER;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_account_hash_email_client() {
|
|
||||||
let email = "totallyrealemail@anemail.c0m";
|
|
||||||
|
|
||||||
/* generate server salt */
|
|
||||||
let rng = rand::SystemRandom::new();
|
|
||||||
let mut server_salt = [0u8; digest::SHA512_OUTPUT_LEN / 2];
|
|
||||||
rng.fill(&mut server_salt).unwrap();
|
|
||||||
|
|
||||||
/* ensure that hash_email_client() works */
|
|
||||||
let output = hash_email(&email.as_bytes().to_vec(), server_salt);
|
|
||||||
assert_ne!(output.0.len(), 0);
|
|
||||||
assert_ne!(output.1.len(), 0);
|
|
||||||
|
|
||||||
/* ensure that hash_email_client() doesn't generate same output
|
|
||||||
* with the same server salt.
|
|
||||||
* */
|
|
||||||
let mut enc0 = hash_email(&email.as_bytes().to_vec(), server_salt);
|
|
||||||
let mut enc1 = hash_email(&email.as_bytes().to_vec(), server_salt);
|
|
||||||
assert_ne!(HEXUPPER.encode(&enc0.0), HEXUPPER.encode(&enc1.0));
|
|
||||||
assert_ne!(HEXUPPER.encode(&enc0.1), HEXUPPER.encode(&enc1.1));
|
|
||||||
|
|
||||||
/* ensure that hash_email_client() generates a different output
|
|
||||||
* with different server salts
|
|
||||||
* */
|
|
||||||
// Generate new server salt.
|
|
||||||
let mut server_salt2 = [0u8; digest::SHA512_OUTPUT_LEN / 2];
|
|
||||||
rng.fill(&mut server_salt2).unwrap();
|
|
||||||
|
|
||||||
enc0 = hash_email(&email.as_bytes().to_vec(), server_salt);
|
|
||||||
enc1 = hash_email(&email.as_bytes().to_vec(), server_salt);
|
|
||||||
assert_ne!(HEXUPPER.encode(&enc0.0), HEXUPPER.encode(&enc1.0));
|
|
||||||
assert_ne!(HEXUPPER.encode(&enc0.1), HEXUPPER.encode(&enc1.1));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,84 +0,0 @@
|
||||||
use ring::rand::SecureRandom;
|
|
||||||
use ring::{digest, rand};
|
|
||||||
|
|
||||||
use crate::common::account::hash::hash;
|
|
||||||
|
|
||||||
/// Generates a client password hash from a raw password.
|
|
||||||
///
|
|
||||||
/// Takes in a raw password, outputs a hashed version of the client password to be sent to the
|
|
||||||
/// server with the returned client random bits that make up the whole client salt. This function
|
|
||||||
/// is to be used on client side account creation. The result from this function is not be stored
|
|
||||||
/// directly on the database, result must be run through the server side hashing again.
|
|
||||||
///
|
|
||||||
/// Arguments:
|
|
||||||
/// pass - The raw user password to be hashed.
|
|
||||||
/// server_salt - The server's part sent of the salt.
|
|
||||||
///
|
|
||||||
/// Returns: a tuple containing the client hash and full salt, nothing on failure.
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
/// ```rust
|
|
||||||
/// let enc = hash_pwd("this is my real password!", server_salt).unwrap();
|
|
||||||
/// println!("Client Pass Hash: {}", HEXUPPER.encode(&enc.0));
|
|
||||||
/// println!("Client Pass Salt: {}", HEXUPPER.encode(&enc.1));
|
|
||||||
/// ```
|
|
||||||
pub fn hash_pwd(
|
|
||||||
pass: &Vec<u8>,
|
|
||||||
server_salt: [u8; digest::SHA512_OUTPUT_LEN / 2],
|
|
||||||
) -> (
|
|
||||||
[u8; digest::SHA512_OUTPUT_LEN],
|
|
||||||
[u8; digest::SHA512_OUTPUT_LEN],
|
|
||||||
) {
|
|
||||||
// client hash, full salt
|
|
||||||
let rng = rand::SystemRandom::new();
|
|
||||||
|
|
||||||
let mut client_salt = [0u8; digest::SHA512_OUTPUT_LEN / 2];
|
|
||||||
rng.fill(&mut client_salt).unwrap();
|
|
||||||
|
|
||||||
let salt = [server_salt, client_salt].concat();
|
|
||||||
|
|
||||||
let hash = hash(pass, &salt, 250_000);
|
|
||||||
|
|
||||||
(hash, *array_ref!(salt, 0, digest::SHA512_OUTPUT_LEN))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
use data_encoding::HEXUPPER;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_account_hash_pwd_client() {
|
|
||||||
let pass = "goodlilpassword";
|
|
||||||
|
|
||||||
/* generate server salt */
|
|
||||||
let rng = rand::SystemRandom::new();
|
|
||||||
let mut server_salt = [0u8; digest::SHA512_OUTPUT_LEN / 2];
|
|
||||||
rng.fill(&mut server_salt).unwrap();
|
|
||||||
|
|
||||||
/* ensure that hash_pwd_client() works */
|
|
||||||
let output = hash_pwd(&pass.as_bytes().to_vec(), server_salt);
|
|
||||||
assert_ne!(output.0.len(), 0);
|
|
||||||
assert_ne!(output.1.len(), 0);
|
|
||||||
|
|
||||||
/* ensure that hash_pwd_client() doesn't generate same output
|
|
||||||
* with the same server salt.
|
|
||||||
* */
|
|
||||||
let mut enc0 = hash_pwd(&pass.as_bytes().to_vec(), server_salt);
|
|
||||||
let mut enc1 = hash_pwd(&pass.as_bytes().to_vec(), server_salt);
|
|
||||||
assert_ne!(HEXUPPER.encode(&enc0.0), HEXUPPER.encode(&enc1.0));
|
|
||||||
assert_ne!(HEXUPPER.encode(&enc0.1), HEXUPPER.encode(&enc1.1));
|
|
||||||
|
|
||||||
/* ensure that hash_pwd_client() generates different output
|
|
||||||
* with different server salts.
|
|
||||||
* */
|
|
||||||
// Generate new server salt.
|
|
||||||
let mut server_salt2 = [0u8; digest::SHA512_OUTPUT_LEN / 2];
|
|
||||||
rng.fill(&mut server_salt2).unwrap();
|
|
||||||
|
|
||||||
enc0 = hash_pwd(&pass.as_bytes().to_vec(), server_salt);
|
|
||||||
enc1 = hash_pwd(&pass.as_bytes().to_vec(), server_salt);
|
|
||||||
assert_ne!(HEXUPPER.encode(&enc0.0), HEXUPPER.encode(&enc1.0));
|
|
||||||
assert_ne!(HEXUPPER.encode(&enc0.1), HEXUPPER.encode(&enc1.1));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,7 @@
|
||||||
pub mod authorization;
|
pub mod authorization;
|
||||||
pub mod creation;
|
pub mod creation;
|
||||||
pub mod hash_email;
|
pub mod hash;
|
||||||
pub mod hash_pwd;
|
|
||||||
pub mod retrieval_portfolio;
|
pub use crate::client::account::authorization::authorize;
|
||||||
pub mod retrieval_transaction;
|
pub use crate::client::account::creation::create;
|
||||||
|
pub use crate::client::account::hash::hash;
|
||||||
|
|
|
@ -1,91 +0,0 @@
|
||||||
use std::io;
|
|
||||||
|
|
||||||
use crate::common::account::portfolio::Portfolio;
|
|
||||||
use crate::common::message::assert_msg::assert_msg;
|
|
||||||
use crate::common::message::inst::DataTransferInst;
|
|
||||||
use crate::common::message::message::Message;
|
|
||||||
use crate::common::message::message_builder::message_builder;
|
|
||||||
use crate::common::message::message_type::MessageType;
|
|
||||||
use crate::common::misc::return_flags::ReturnFlags;
|
|
||||||
|
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
|
||||||
use tokio::net::TcpStream;
|
|
||||||
use tokio_rustls::client::TlsStream;
|
|
||||||
|
|
||||||
/// Retrieves from the connected TLS server an authorized portfolio.
|
|
||||||
///
|
|
||||||
/// Sends a request for portfolio with the JWT token of the client connection. Handles any response
|
|
||||||
/// and returns.
|
|
||||||
/// Should be used in contexts that return ```io::Result```.
|
|
||||||
/// Should be used in Async context.
|
|
||||||
///
|
|
||||||
/// Arguments:
|
|
||||||
/// socket - TLS socket to use.
|
|
||||||
/// auth_jwt - JWT token to authenticate with.
|
|
||||||
///
|
|
||||||
/// Returns: ```io::Result``` wraps ```Portfolio```.
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
/// ```rust
|
|
||||||
/// let mut portfolio = acc_retrieve_portfolio(&mut tls_client, &mut poll)?;
|
|
||||||
/// ```
|
|
||||||
pub async fn acc_retrieve_portfolio(
|
|
||||||
socket: &mut TlsStream<TcpStream>,
|
|
||||||
auth_jwt: String,
|
|
||||||
) -> io::Result<Portfolio> {
|
|
||||||
if auth_jwt.is_empty() == true {
|
|
||||||
return Err(io::Error::new(
|
|
||||||
io::ErrorKind::PermissionDenied,
|
|
||||||
"ACC_RETRIEVE_PORTFOLIO: JWT TOKEN EMPTY",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* build message request */
|
|
||||||
let message = message_builder(
|
|
||||||
MessageType::Command,
|
|
||||||
DataTransferInst::GetUserPortfolio as i64,
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
bincode::serialize(&auth_jwt).unwrap(),
|
|
||||||
);
|
|
||||||
socket
|
|
||||||
.write_all(&bincode::serialize(&message).unwrap())
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
/* decode response */
|
|
||||||
let mut buf = Vec::with_capacity(4096);
|
|
||||||
socket.read_buf(&mut buf).await?;
|
|
||||||
|
|
||||||
let response: Message = bincode::deserialize(&buf).map_err(|_| {
|
|
||||||
io::Error::new(
|
|
||||||
io::ErrorKind::InvalidInput,
|
|
||||||
format!("{}", ReturnFlags::ClientAccRetrievePortfolioError),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if assert_msg(
|
|
||||||
&response,
|
|
||||||
MessageType::DataTransfer,
|
|
||||||
true,
|
|
||||||
1,
|
|
||||||
false,
|
|
||||||
0,
|
|
||||||
false,
|
|
||||||
0,
|
|
||||||
false,
|
|
||||||
0,
|
|
||||||
) && response.instruction == 1
|
|
||||||
&& response.data.len() != 0
|
|
||||||
{
|
|
||||||
/* returned data */
|
|
||||||
let portfolio: Portfolio = bincode::deserialize(&response.data).unwrap();
|
|
||||||
return Ok(portfolio);
|
|
||||||
} else {
|
|
||||||
/* could not get data */
|
|
||||||
return Err(io::Error::new(
|
|
||||||
io::ErrorKind::InvalidData,
|
|
||||||
format!("{}", ReturnFlags::ClientAccRetrievePortfolioError),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,98 +0,0 @@
|
||||||
use std::io;
|
|
||||||
|
|
||||||
use crate::common::account::transaction::Transaction;
|
|
||||||
|
|
||||||
use crate::common::message::assert_msg::assert_msg;
|
|
||||||
use crate::common::message::inst::DataTransferInst;
|
|
||||||
use crate::common::message::message::Message;
|
|
||||||
use crate::common::message::message_builder::message_builder;
|
|
||||||
use crate::common::message::message_type::MessageType;
|
|
||||||
use crate::common::misc::return_flags::ReturnFlags;
|
|
||||||
|
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
|
||||||
use tokio::net::TcpStream;
|
|
||||||
use tokio_rustls::client::TlsStream;
|
|
||||||
|
|
||||||
/// Retrieves from the connected TLS server an authorized transaction history.
|
|
||||||
///
|
|
||||||
/// Sends a request for a transaction history with the JWT token of the client connection. Handles
|
|
||||||
/// any response and returns.
|
|
||||||
/// Should be used in contexts that return ```io::Result```.
|
|
||||||
/// Should be used in Async contexts.
|
|
||||||
///
|
|
||||||
/// Arguments:
|
|
||||||
/// socket - TLS socket to use.
|
|
||||||
/// auth_jwt - JWT Token to authenticate with
|
|
||||||
///
|
|
||||||
/// Returns: ```io::Result``` wrapped ```Vec<Transaction>```
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
/// ```rust
|
|
||||||
/// let mut transaction = acc_retrieve_transaction(&mut tls_client, &mut poll)?
|
|
||||||
/// ```
|
|
||||||
pub async fn acc_retrieve_transaction(
|
|
||||||
socket: &mut TlsStream<TcpStream>,
|
|
||||||
auth_jwt: String,
|
|
||||||
) -> io::Result<Vec<Transaction>> {
|
|
||||||
if auth_jwt.is_empty() == true {
|
|
||||||
return Err(io::Error::new(
|
|
||||||
io::ErrorKind::PermissionDenied,
|
|
||||||
"ACC_RETRIEVE_TRANSACTION: JWT TOKEN EMPTY",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* build message request */
|
|
||||||
let message = message_builder(
|
|
||||||
MessageType::DataTransfer,
|
|
||||||
DataTransferInst::GetUserTransactionHist as i64,
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
bincode::serialize(&auth_jwt).unwrap(),
|
|
||||||
);
|
|
||||||
socket
|
|
||||||
.write_all(&bincode::serialize(&message).unwrap())
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
/* decode response */
|
|
||||||
let mut buf = Vec::with_capacity(4096);
|
|
||||||
socket.read_buf(&mut buf).await?;
|
|
||||||
|
|
||||||
let response: Message = bincode::deserialize(&buf).map_err(|_| {
|
|
||||||
io::Error::new(
|
|
||||||
io::ErrorKind::InvalidInput,
|
|
||||||
format!("{}", ReturnFlags::ClientAccRetrieveTransactionError),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if assert_msg(
|
|
||||||
&response,
|
|
||||||
MessageType::ServerReturn,
|
|
||||||
true,
|
|
||||||
1,
|
|
||||||
false,
|
|
||||||
0,
|
|
||||||
false,
|
|
||||||
0,
|
|
||||||
false,
|
|
||||||
0,
|
|
||||||
) && response.data.len() != 0
|
|
||||||
&& response.instruction == 1
|
|
||||||
{
|
|
||||||
/* returned data*/
|
|
||||||
let transactions: Vec<Transaction> =
|
|
||||||
bincode::deserialize(&response.data).map_err(|_| {
|
|
||||||
io::Error::new(
|
|
||||||
io::ErrorKind::InvalidInput,
|
|
||||||
format!("{}", ReturnFlags::ClientAccRetrievePortfolioError),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
return Ok(transactions);
|
|
||||||
} else {
|
|
||||||
/* could not get data */
|
|
||||||
return Err(io::Error::new(
|
|
||||||
io::ErrorKind::InvalidData,
|
|
||||||
format!("{}", ReturnFlags::ClientAccRetrieveTransactionError),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,13 +5,15 @@ use tokio::net::TcpStream;
|
||||||
use tokio_rustls::webpki::DNSNameRef;
|
use tokio_rustls::webpki::DNSNameRef;
|
||||||
use tokio_rustls::TlsConnector;
|
use tokio_rustls::TlsConnector;
|
||||||
|
|
||||||
use crate::client::network::gen_tls_client_config::gen_tls_client_config;
|
use crate::client::network::tls::*;
|
||||||
|
|
||||||
use rand::distributions::Alphanumeric;
|
use rand::distributions::Alphanumeric;
|
||||||
use rand::{thread_rng, Rng};
|
use rand::{thread_rng, Rng};
|
||||||
|
|
||||||
use argh::FromArgs;
|
use argh::FromArgs;
|
||||||
|
|
||||||
|
use crate::client::account;
|
||||||
|
|
||||||
/// Client Options
|
/// Client Options
|
||||||
#[derive(FromArgs)]
|
#[derive(FromArgs)]
|
||||||
struct Options {
|
struct Options {
|
||||||
|
@ -35,7 +37,7 @@ struct Options {
|
||||||
/// libtrader_init_client().expect("failed running client");
|
/// libtrader_init_client().expect("failed running client");
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
fn libtrader_init_log() -> io::Result<()> {
|
fn initialize_log() -> io::Result<()> {
|
||||||
use fern::colors::{Color, ColoredLevelConfig};
|
use fern::colors::{Color, ColoredLevelConfig};
|
||||||
|
|
||||||
let mut dispatch = fern::Dispatch::new().format(|out, message, record| {
|
let mut dispatch = fern::Dispatch::new().format(|out, message, record| {
|
||||||
|
@ -97,12 +99,12 @@ fn libtrader_init_log() -> io::Result<()> {
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// libtrader_init_client()?;
|
/// initialize()?;
|
||||||
/// ```
|
/// ```
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
pub async fn libtrader_init_client() -> std::io::Result<()> {
|
pub async fn initialize() -> std::io::Result<()> {
|
||||||
// Initialize log.
|
// Initialize log.
|
||||||
libtrader_init_log()?;
|
initialize_log()?;
|
||||||
|
|
||||||
// Initialize arguments
|
// Initialize arguments
|
||||||
let options: Options = argh::from_env();
|
let options: Options = argh::from_env();
|
||||||
|
@ -140,17 +142,15 @@ pub async fn libtrader_init_client() -> std::io::Result<()> {
|
||||||
.map(char::from)
|
.map(char::from)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
use crate::client::account::creation::acc_create;
|
match account::create(&mut socket, &username, &email, &password).await {
|
||||||
match acc_create(&mut socket, &username, &email, &password).await {
|
|
||||||
Ok(_) => println!("we created it"),
|
Ok(_) => println!("we created it"),
|
||||||
Err(err) => panic!("panik! {}", err),
|
Err(err) => panic!("panik! {}", err),
|
||||||
}
|
}
|
||||||
|
|
||||||
use crate::client::account::authorization::acc_auth;
|
|
||||||
let mut jwt: String = String::new();
|
let mut jwt: String = String::new();
|
||||||
println!("{}", jwt); // this is for removing pisky warnings,
|
println!("{}", jwt); // this is for removing pisky warnings,
|
||||||
// this is fine as long as this code is sandbox
|
// this is fine as long as this code is sandbox
|
||||||
match acc_auth(&mut socket, &username, &email, &password).await {
|
match account::authorize(&mut socket, &username, &email, &password).await {
|
||||||
Ok(auth) => {
|
Ok(auth) => {
|
||||||
jwt = auth;
|
jwt = auth;
|
||||||
println!("we accessed it, the token: {}", jwt);
|
println!("we accessed it, the token: {}", jwt);
|
||||||
|
@ -158,17 +158,5 @@ pub async fn libtrader_init_client() -> std::io::Result<()> {
|
||||||
Err(err) => panic!("panik! {}", err),
|
Err(err) => panic!("panik! {}", err),
|
||||||
}
|
}
|
||||||
|
|
||||||
use crate::client::account::retrieval_portfolio::acc_retrieve_portfolio;
|
|
||||||
match acc_retrieve_portfolio(&mut socket, String::from(jwt.as_str())).await {
|
|
||||||
Ok(portfolio) => println!("we got portfolio {:#?}", portfolio),
|
|
||||||
Err(err) => panic!("panik! {}", err),
|
|
||||||
}
|
|
||||||
|
|
||||||
use crate::client::account::retrieval_transaction::acc_retrieve_transaction;
|
|
||||||
match acc_retrieve_transaction(&mut socket, jwt).await {
|
|
||||||
Ok(transaction) => println!("we got the transactions {:#?}", transaction),
|
|
||||||
Err(err) => panic!("panik! {}", err),
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,3 +2,5 @@ pub mod account;
|
||||||
pub mod ds;
|
pub mod ds;
|
||||||
pub mod initializer;
|
pub mod initializer;
|
||||||
pub mod network;
|
pub mod network;
|
||||||
|
|
||||||
|
pub use crate::client::initializer::initialize;
|
||||||
|
|
|
@ -1,15 +1,9 @@
|
||||||
use ring::digest;
|
|
||||||
|
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use crate::common::message::assert_msg::assert_msg;
|
use argon2::password_hash::SaltString;
|
||||||
use crate::common::message::inst::CommandInst;
|
|
||||||
use crate::common::message::message::Message;
|
use crate::common::message::*;
|
||||||
use crate::common::message::message_builder::message_builder;
|
|
||||||
use crate::common::message::message_type::MessageType;
|
|
||||||
use crate::common::misc::return_flags::ReturnFlags;
|
|
||||||
|
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use tokio_rustls::client::TlsStream;
|
use tokio_rustls::client::TlsStream;
|
||||||
|
|
||||||
|
@ -28,51 +22,37 @@ use tokio_rustls::client::TlsStream;
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// let server_salt: [u8; digest::SHA512_OUTPUT_LEN/2] = get_server_salt(tls_client)?;
|
/// let server_salt: [u8; digest::SHA512_OUTPUT_LEN/2] = get_server_salt(tls_client)?;
|
||||||
/// ```
|
/// ```
|
||||||
pub async fn get_server_salt(
|
pub async fn get_server_salt(socket: &mut TlsStream<TcpStream>) -> std::io::Result<SaltString> {
|
||||||
socket: &mut TlsStream<TcpStream>,
|
|
||||||
) -> io::Result<[u8; digest::SHA512_OUTPUT_LEN / 2]> {
|
|
||||||
/*
|
/*
|
||||||
* request to generate a salt from the server.
|
* request to generate a salt from the server.
|
||||||
* */
|
* */
|
||||||
let message = message_builder(
|
Message::new()
|
||||||
MessageType::Command,
|
.command(Command::GenHashSalt)
|
||||||
CommandInst::GenHashSalt as i64,
|
.send(socket)
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
Vec::new(),
|
|
||||||
);
|
|
||||||
socket
|
|
||||||
.write_all(&bincode::serialize(&message).unwrap())
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut buf = Vec::with_capacity(4096);
|
let ret_msg = Message::receive(socket).await?;
|
||||||
socket.read_buf(&mut buf).await?;
|
|
||||||
|
|
||||||
let ret_msg: Message = bincode::deserialize(&buf).map_err(|_| {
|
/* assert received message */
|
||||||
io::Error::new(
|
if !ret_msg.assert_command(Command::Success) || !ret_msg.assert_data() {
|
||||||
io::ErrorKind::InvalidInput,
|
|
||||||
format!("{}", ReturnFlags::ClientGenSaltFailed),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
if assert_msg(
|
|
||||||
&ret_msg,
|
|
||||||
MessageType::DataTransfer,
|
|
||||||
true,
|
|
||||||
1,
|
|
||||||
false,
|
|
||||||
0,
|
|
||||||
true,
|
|
||||||
1,
|
|
||||||
true,
|
|
||||||
digest::SHA512_OUTPUT_LEN / 2,
|
|
||||||
) {
|
|
||||||
Ok(*array_ref!(ret_msg.data, 0, digest::SHA512_OUTPUT_LEN / 2))
|
|
||||||
} else {
|
|
||||||
Err(io::Error::new(
|
Err(io::Error::new(
|
||||||
io::ErrorKind::InvalidData,
|
io::ErrorKind::InvalidData,
|
||||||
format!("{}", ReturnFlags::ClientReqSaltInvMsg),
|
"Failed getting generated server Salt, received an invalid message.",
|
||||||
))
|
))
|
||||||
|
} else {
|
||||||
|
let salt_raw: String = ret_msg.get_data().map_err(|_| {
|
||||||
|
io::Error::new(
|
||||||
|
io::ErrorKind::InvalidData,
|
||||||
|
"Failed getting generated server Salt, received an invalid message.",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
/* verify that the salt is actually valid */
|
||||||
|
SaltString::new(salt_raw.as_str()).map_err(|_| {
|
||||||
|
io::Error::new(
|
||||||
|
io::ErrorKind::InvalidData,
|
||||||
|
"Failed getting generated server Salt, received an invalid message.",
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,9 @@
|
||||||
use ring::digest;
|
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
use crate::common::message::inst::CommandInst;
|
use argon2::password_hash::SaltString;
|
||||||
use crate::common::message::message::Message;
|
|
||||||
use crate::common::message::message_builder::message_builder;
|
use crate::common::message::*;
|
||||||
use crate::common::message::message_type::MessageType;
|
|
||||||
use crate::common::misc::return_flags::ReturnFlags;
|
|
||||||
|
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use tokio_rustls::client::TlsStream;
|
use tokio_rustls::client::TlsStream;
|
||||||
|
|
||||||
|
@ -32,64 +28,43 @@ use tokio_rustls::client::TlsStream;
|
||||||
pub async fn req_server_salt(
|
pub async fn req_server_salt(
|
||||||
socket: &mut TlsStream<TcpStream>,
|
socket: &mut TlsStream<TcpStream>,
|
||||||
username: &str,
|
username: &str,
|
||||||
salt_type: i64,
|
salt_type: Command,
|
||||||
) -> io::Result<[u8; digest::SHA512_OUTPUT_LEN]> {
|
) -> std::io::Result<SaltString> {
|
||||||
/* enforce salt_type to be either email or password */
|
/* enforce salt_type to be either email or password */
|
||||||
assert_eq!(salt_type >= CommandInst::GetEmailSalt as i64, true);
|
assert_eq!(
|
||||||
assert_eq!(salt_type <= CommandInst::GetPasswordSalt as i64, true);
|
(salt_type == Command::GetEmailSalt) || (salt_type == Command::GetPasswordSalt),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
/* generate message to send */
|
/* generate message to send */
|
||||||
let message = message_builder(
|
Message::new()
|
||||||
MessageType::Command,
|
.command(salt_type)
|
||||||
salt_type,
|
.data(username)
|
||||||
1,
|
.send(socket)
|
||||||
0,
|
|
||||||
0,
|
|
||||||
username.as_bytes().to_vec(),
|
|
||||||
);
|
|
||||||
socket
|
|
||||||
.write_all(&bincode::serialize(&message).unwrap())
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut buf = Vec::with_capacity(4096);
|
let ret_msg = Message::receive(socket).await?;
|
||||||
socket.read_buf(&mut buf).await?;
|
|
||||||
|
|
||||||
let ret_msg: Message = bincode::deserialize(&buf).map_err(|_| {
|
/* assert received message */
|
||||||
|
if !ret_msg.assert_command(Command::Success) || !ret_msg.assert_data() {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidData,
|
||||||
|
"Recieved invalid Salt from server.",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let salt_raw: String = ret_msg.get_data().map_err(|_| {
|
||||||
io::Error::new(
|
io::Error::new(
|
||||||
io::ErrorKind::InvalidInput,
|
io::ErrorKind::InvalidData,
|
||||||
format!("{}", ReturnFlags::ClientReqSaltInvMsg),
|
"Could not get server salt, received invalid data.",
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
match ret_msg.msgtype {
|
/* verify that the salt is actually valid */
|
||||||
MessageType::Command => Err(io::Error::new(
|
SaltString::new(salt_raw.as_str()).map_err(|_| {
|
||||||
|
io::Error::new(
|
||||||
io::ErrorKind::InvalidData,
|
io::ErrorKind::InvalidData,
|
||||||
format!("{}", ReturnFlags::ClientReqSaltInvMsg),
|
format!("Could not get server salt, received invalid salt length."),
|
||||||
)),
|
)
|
||||||
MessageType::DataTransfer => {
|
})
|
||||||
if ret_msg.data.len() != digest::SHA512_OUTPUT_LEN {
|
|
||||||
Err(io::Error::new(
|
|
||||||
io::ErrorKind::InvalidData,
|
|
||||||
format!("{}", ReturnFlags::ClientReqSaltInvMsgRetSize),
|
|
||||||
))
|
|
||||||
} else if ret_msg.instruction == salt_type {
|
|
||||||
Ok(*array_ref!(ret_msg.data, 0, digest::SHA512_OUTPUT_LEN))
|
|
||||||
} else {
|
|
||||||
Err(io::Error::new(
|
|
||||||
io::ErrorKind::InvalidData,
|
|
||||||
format!("{}", ReturnFlags::ClientReqSaltInvMsgInst),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MessageType::ServerReturn => match ret_msg.instruction {
|
|
||||||
0 => Err(io::Error::new(
|
|
||||||
io::ErrorKind::InvalidData,
|
|
||||||
format!("{}", ReturnFlags::ClientReqSaltRej),
|
|
||||||
)),
|
|
||||||
_ => Err(io::Error::new(
|
|
||||||
io::ErrorKind::InvalidData,
|
|
||||||
format!("{}", ReturnFlags::ClientReqSaltInvMsg),
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
pub mod cmd;
|
pub mod cmd;
|
||||||
pub mod gen_tls_client_config;
|
|
||||||
pub mod handle_data;
|
pub mod handle_data;
|
||||||
|
pub mod tls;
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
use ring::{digest, pbkdf2};
|
|
||||||
use std::num::NonZeroU32;
|
|
||||||
|
|
||||||
/// A generic hashing abstraction function.
|
|
||||||
///
|
|
||||||
/// Useful for quickly swapping the current hashing system.
|
|
||||||
///
|
|
||||||
/// Arguments:
|
|
||||||
/// val - The value to be hashed.
|
|
||||||
/// salt - The whole salt to be used.
|
|
||||||
/// iter - The number of iteration to use.
|
|
||||||
///
|
|
||||||
/// Returns: u8 array of size 64 bytes.
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
/// ```rust
|
|
||||||
/// let email_hash = hash("test@test.com", [0u8; 64], 124000);
|
|
||||||
/// ```
|
|
||||||
pub fn hash(val: &Vec<u8>, salt: &Vec<u8>, iter: u32) -> [u8; digest::SHA512_OUTPUT_LEN] {
|
|
||||||
let iterations: NonZeroU32 = NonZeroU32::new(iter).unwrap();
|
|
||||||
|
|
||||||
let mut hash = [0u8; digest::SHA512_OUTPUT_LEN];
|
|
||||||
pbkdf2::derive(
|
|
||||||
pbkdf2::PBKDF2_HMAC_SHA512,
|
|
||||||
iterations,
|
|
||||||
&salt,
|
|
||||||
val,
|
|
||||||
&mut hash,
|
|
||||||
);
|
|
||||||
hash
|
|
||||||
}
|
|
|
@ -1,6 +1,4 @@
|
||||||
pub mod hash;
|
|
||||||
pub mod order;
|
pub mod order;
|
||||||
pub mod portfolio;
|
pub mod portfolio;
|
||||||
pub mod position;
|
pub mod position;
|
||||||
pub mod session;
|
|
||||||
pub mod transaction;
|
pub mod transaction;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::common::account::position::Position;
|
pub use crate::common::account::position::*;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Default)]
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Default)]
|
||||||
pub struct Portfolio {
|
pub struct Portfolio {
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
use chrono::{DateTime, Utc};
|
|
||||||
use std::net::Ipv4Addr;
|
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
|
||||||
pub struct SessionID {
|
|
||||||
pub sess_id: String,
|
|
||||||
pub client_ip: Ipv4Addr,
|
|
||||||
pub expiry_date: DateTime<Utc>,
|
|
||||||
pub is_active: bool,
|
|
||||||
}
|
|
||||||
impl std::fmt::Display for SessionID {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"({}, {}, {}, {})",
|
|
||||||
self.sess_id, self.client_ip, self.expiry_date, self.is_active
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Clone, Copy)]
|
||||||
|
pub enum Command {
|
||||||
|
Default = 0,
|
||||||
|
Success = 1,
|
||||||
|
Failure = 2,
|
||||||
|
LoginMethod1 = 3,
|
||||||
|
LoginMethod2,
|
||||||
|
Register,
|
||||||
|
PurchaseAsset,
|
||||||
|
SellAsset,
|
||||||
|
GenHashSalt,
|
||||||
|
GetEmailSalt,
|
||||||
|
GetPasswordSalt,
|
||||||
|
GetAssetInfo,
|
||||||
|
GetAssetValue,
|
||||||
|
GetAssetValueCurrent,
|
||||||
|
GetUserInfo,
|
||||||
|
GetUserPortfolio,
|
||||||
|
GetUserTransactionHist,
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for Command {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{:#?}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Command {
|
||||||
|
fn default() -> Self {
|
||||||
|
Command::Success
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,31 +0,0 @@
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Default, Eq, PartialEq, Clone, Debug)]
|
|
||||||
pub struct Company {
|
|
||||||
pub id: i64,
|
|
||||||
pub symbol: String,
|
|
||||||
pub isin: String,
|
|
||||||
pub company_name: String,
|
|
||||||
pub primary_exchange: String,
|
|
||||||
pub sector: String,
|
|
||||||
pub industry: String,
|
|
||||||
pub primary_sic_code: String,
|
|
||||||
pub employees: i64,
|
|
||||||
}
|
|
||||||
impl std::fmt::Display for Company {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"({}, {}, {}, {}, {}, {}, {}, {}, {})",
|
|
||||||
self.id,
|
|
||||||
self.symbol,
|
|
||||||
self.isin,
|
|
||||||
self.company_name,
|
|
||||||
self.primary_exchange,
|
|
||||||
self.sector,
|
|
||||||
self.industry,
|
|
||||||
self.primary_sic_code,
|
|
||||||
self.employees
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
pub mod company;
|
|
||||||
pub mod stock_val;
|
|
|
@ -1,21 +0,0 @@
|
||||||
use postgres_types::{FromSql, ToSql};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Default, PartialEq, Debug, ToSql, FromSql, Serialize, Deserialize)]
|
|
||||||
pub struct StockVal {
|
|
||||||
pub id: i64,
|
|
||||||
pub isin: String,
|
|
||||||
pub time_epoch: i64,
|
|
||||||
pub ask_price: f64,
|
|
||||||
pub bid_price: f64,
|
|
||||||
pub volume: i64,
|
|
||||||
}
|
|
||||||
impl std::fmt::Display for StockVal {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"({}, {}, {}, {}, {}, {})",
|
|
||||||
self.id, self.isin, self.time_epoch, self.ask_price, self.bid_price, self.volume
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
|
||||||
/// user_id - The user id that is authorized.
|
/// user_id - The user id that is authorized.
|
||||||
/// exp - The unix epoch at which this claim expires.
|
/// exp - The unix epoch at which this claim expires.
|
||||||
#[derive(Debug, Default, Serialize, Deserialize)]
|
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||||
pub struct JWTClaim {
|
pub struct Claim {
|
||||||
pub user_id: i64,
|
pub user_id: i64,
|
||||||
pub exp: u64,
|
pub exp: u64,
|
||||||
}
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
use log::warn;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
pub use crate::common::command::*;
|
||||||
|
|
||||||
|
use tokio::io::AsyncReadExt;
|
||||||
|
use tokio::io::AsyncWriteExt;
|
||||||
|
use tokio::net::TcpStream;
|
||||||
|
|
||||||
|
#[cfg(all(feature = "client", not(feature = "server")))]
|
||||||
|
use tokio_rustls::client::TlsStream;
|
||||||
|
#[cfg(all(feature = "server", not(feature = "client")))]
|
||||||
|
use tokio_rustls::server::TlsStream;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, PartialEq, Debug, Default)]
|
||||||
|
pub struct Message {
|
||||||
|
pub command: Command,
|
||||||
|
pub data: Vec<u8>,
|
||||||
|
}
|
||||||
|
impl Message {
|
||||||
|
pub fn new() -> Message {
|
||||||
|
Message {
|
||||||
|
command: Command::Default,
|
||||||
|
data: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn command<'a>(&'a mut self, command: Command) -> &'a mut Message {
|
||||||
|
self.command = command;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn data<'a, T>(&'a mut self, data: T) -> &'a mut Message
|
||||||
|
where
|
||||||
|
T: serde::Serialize,
|
||||||
|
{
|
||||||
|
self.data = bincode::serialize(&data).unwrap();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send(&self, socket: &mut TlsStream<TcpStream>) -> std::io::Result<()> {
|
||||||
|
/* automagically log failed commands */
|
||||||
|
if self.assert_command(Command::Failure) {
|
||||||
|
warn!("Operation failed, sending error to client.");
|
||||||
|
warn!("Error: {}", self.get_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
socket
|
||||||
|
.write_all(bincode::serialize(self).unwrap().as_slice())
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn receive<'a>(socket: &mut TlsStream<TcpStream>) -> std::io::Result<Message> {
|
||||||
|
let mut buf = Vec::with_capacity(4096);
|
||||||
|
socket.read_buf(&mut buf).await?;
|
||||||
|
Ok(bincode::deserialize(&buf).map_err(|_| {
|
||||||
|
std::io::Error::new(
|
||||||
|
std::io::ErrorKind::InvalidInput,
|
||||||
|
format!("Failed parsing recieved message."),
|
||||||
|
)
|
||||||
|
})?)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use this function for more easier bincode conversion of received data.
|
||||||
|
pub fn get_data<'a, T>(&'a self) -> Result<T, String>
|
||||||
|
where
|
||||||
|
T: serde::Deserialize<'a>,
|
||||||
|
{
|
||||||
|
bincode::deserialize(&self.data).map_err(|_| format!("Failed getting message data."))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_err(&self) -> &str {
|
||||||
|
// Tests should handle this function being called not in the
|
||||||
|
// correct context.
|
||||||
|
// i.e. tests should ensure that functions error out gracefully.
|
||||||
|
bincode::deserialize(&self.data).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn assert_command(&self, command: Command) -> bool {
|
||||||
|
if self.command != command {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn assert_data(&self) -> bool {
|
||||||
|
if self.data.len() == 0 {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Message {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "({}, {:#?})", self.command, self.data)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,49 +0,0 @@
|
||||||
use crate::common::message::message::Message;
|
|
||||||
use crate::common::message::message_type::MessageType;
|
|
||||||
|
|
||||||
/// Asserts a recieved message meta information.
|
|
||||||
///
|
|
||||||
/// Takes in a message and meta information to check the message against.
|
|
||||||
/// Can be used to check some attributes.
|
|
||||||
/// For example, you can check for message to have an X amount of arguments, and
|
|
||||||
/// not check how many arguments are passed.
|
|
||||||
///
|
|
||||||
/// Arguments:
|
|
||||||
/// message - The mesage to assert against.
|
|
||||||
/// msg_type - MessageType expected.
|
|
||||||
/// check_arg_cnt - Whether to check for the argument account.
|
|
||||||
/// arg_cnt - The argument count expected.
|
|
||||||
/// check_dnum - Whether to check for the number data message.
|
|
||||||
/// msg_dnum - The number of the data message.
|
|
||||||
/// check_dmax - Whether to check for the max data message.
|
|
||||||
/// msg_dmax - The number of the max data message.
|
|
||||||
/// check_len - Whether to check for the data payload length.
|
|
||||||
/// data_len - The length of the data payload.
|
|
||||||
///
|
|
||||||
/// Returns: a boolean.
|
|
||||||
pub fn assert_msg(
|
|
||||||
message: &Message,
|
|
||||||
msg_type: MessageType,
|
|
||||||
check_arg_cnt: bool,
|
|
||||||
arg_cnt: usize,
|
|
||||||
check_dnum: bool,
|
|
||||||
msg_dnum: usize,
|
|
||||||
check_dmax: bool,
|
|
||||||
msg_dmax: usize,
|
|
||||||
check_len: bool,
|
|
||||||
data_len: usize,
|
|
||||||
) -> bool {
|
|
||||||
if message.msgtype != msg_type {
|
|
||||||
return false;
|
|
||||||
} else if check_arg_cnt && (message.argument_count != arg_cnt) {
|
|
||||||
return false;
|
|
||||||
} else if check_dnum && (message.data_message_number != msg_dnum) {
|
|
||||||
return false;
|
|
||||||
} else if check_dmax && (message.data_message_max != msg_dmax) {
|
|
||||||
return false;
|
|
||||||
} else if check_len && (message.data.len() != data_len) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
#[allow(dead_code)]
|
|
||||||
static INST_SWITCH_STATE: isize = 0;
|
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
|
||||||
pub enum CommandInst {
|
|
||||||
LoginMethod1 = 1,
|
|
||||||
LoginMethod2 = 2,
|
|
||||||
Register = 3,
|
|
||||||
PurchaseAsset = 4,
|
|
||||||
SellAsset = 5,
|
|
||||||
GenHashSalt = 6,
|
|
||||||
GetEmailSalt = 7,
|
|
||||||
GetPasswordSalt = 8,
|
|
||||||
}
|
|
||||||
impl std::fmt::Display for CommandInst {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{:#?}", self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
static INST_COMMAND_MAX_ID: isize = CommandInst::GetPasswordSalt as isize;
|
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
|
||||||
pub enum DataTransferInst {
|
|
||||||
GetAssetInfo = 6,
|
|
||||||
GetAssetValue = 7,
|
|
||||||
GetAssetValueCurrent = 8,
|
|
||||||
GetUserInfo = 9,
|
|
||||||
GetUserPortfolio = 10,
|
|
||||||
GetUserTransactionHist = 11,
|
|
||||||
}
|
|
||||||
impl std::fmt::Display for DataTransferInst {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{:#?}", self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
static INST_DATA_MAX_ID: isize = DataTransferInst::GetUserTransactionHist as isize;
|
|
|
@ -1,27 +0,0 @@
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::common::message::message_type::MessageType;
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, PartialEq, Debug, Default)]
|
|
||||||
pub struct Message {
|
|
||||||
pub msgtype: MessageType,
|
|
||||||
pub instruction: i64,
|
|
||||||
pub argument_count: usize,
|
|
||||||
pub data_message_number: usize,
|
|
||||||
pub data_message_max: usize,
|
|
||||||
pub data: Vec<u8>,
|
|
||||||
}
|
|
||||||
impl std::fmt::Display for Message {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"({}, {}, {}, {}, {}, {:#?})",
|
|
||||||
self.msgtype,
|
|
||||||
self.instruction,
|
|
||||||
self.argument_count,
|
|
||||||
self.data_message_number,
|
|
||||||
self.data_message_max,
|
|
||||||
self.data
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
use crate::common::message::message::Message;
|
|
||||||
use crate::common::message::message_type::MessageType;
|
|
||||||
|
|
||||||
pub fn message_builder(
|
|
||||||
msg_type: MessageType,
|
|
||||||
inst: i64,
|
|
||||||
arg_cnt: usize,
|
|
||||||
data_msg_num: usize,
|
|
||||||
data_msg_max: usize,
|
|
||||||
data: Vec<u8>,
|
|
||||||
) -> Message {
|
|
||||||
let mut message: Message = Message::default();
|
|
||||||
message.msgtype = msg_type;
|
|
||||||
message.instruction = inst;
|
|
||||||
message.argument_count = arg_cnt;
|
|
||||||
message.data_message_number = data_msg_num;
|
|
||||||
message.data_message_max = data_msg_max;
|
|
||||||
message.data = data;
|
|
||||||
message
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, PartialEq, Debug)]
|
|
||||||
pub enum MessageType {
|
|
||||||
Command = 0,
|
|
||||||
DataTransfer = 1,
|
|
||||||
ServerReturn = 2,
|
|
||||||
}
|
|
||||||
impl std::fmt::Display for MessageType {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{:#?}", self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for MessageType {
|
|
||||||
fn default() -> Self {
|
|
||||||
MessageType::Command
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
pub mod assert_msg;
|
|
||||||
pub mod inst;
|
|
||||||
pub mod message;
|
|
||||||
pub mod message_builder;
|
|
||||||
pub mod message_type;
|
|
|
@ -1,2 +0,0 @@
|
||||||
pub mod return_flags;
|
|
||||||
pub mod servers_pool;
|
|
|
@ -1,75 +0,0 @@
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(PartialEq, Debug, Serialize, Deserialize)]
|
|
||||||
pub enum ReturnFlags {
|
|
||||||
LibtraderInitClientConnect = 1,
|
|
||||||
LibtraderInitLogFailed = 2,
|
|
||||||
LibtraderInitFailed = 3,
|
|
||||||
CommonGenLogDirCreationFailed = 4,
|
|
||||||
CommonTlsBadConfig = 5,
|
|
||||||
|
|
||||||
CommonGetCompanyFailed = 6,
|
|
||||||
CommonGetStockFailed = 7,
|
|
||||||
|
|
||||||
ServerDbConnectFailed = 8,
|
|
||||||
ServerDbWriteFailed = 9,
|
|
||||||
|
|
||||||
ServerDbUserHashNotFound = 10,
|
|
||||||
ServerDbUserSaltNotFound = 11,
|
|
||||||
|
|
||||||
ServerDbCreateTransactionFailed = 12,
|
|
||||||
ServerDbCreatePositionFailed = 13,
|
|
||||||
ServerDbCreateStockFailed = 14,
|
|
||||||
ServerDbCreateCompanyFailed = 15,
|
|
||||||
|
|
||||||
ServerDbSearchStockNotFound = 16,
|
|
||||||
ServerDbSearchCompanyNotFound = 17,
|
|
||||||
|
|
||||||
ServerRegisterInvMsg = 18,
|
|
||||||
ServerLoginInvMsg = 19,
|
|
||||||
|
|
||||||
ServerPurchaseAssetInvMsg = 20,
|
|
||||||
|
|
||||||
ServerAccUnauthorized = 21,
|
|
||||||
ServerAccUserExists = 22,
|
|
||||||
|
|
||||||
ServerGetAssetDataInvMsg = 23,
|
|
||||||
ServerGetAssetInfoInvMsg = 24,
|
|
||||||
|
|
||||||
ServerGetUserIdNotFound = 25,
|
|
||||||
|
|
||||||
ServerRetrieveTransactionFailed = 26,
|
|
||||||
ServerRetrieveTransactionInvMsg = 27,
|
|
||||||
ServerRetrievePortfolioFailed = 28,
|
|
||||||
ServerRetrievePortfolioInvMsg = 29,
|
|
||||||
|
|
||||||
ServerCreateJwtTokenFailed = 30,
|
|
||||||
|
|
||||||
ServerTlsConnWriteFailed = 31,
|
|
||||||
ServerTlsConnProcessFailed = 32,
|
|
||||||
ServerTlsConnReadPlainFailed = 33,
|
|
||||||
ServerTlsServerAcceptFailed = 34,
|
|
||||||
|
|
||||||
ServerHandleDataRcvdInvMsg = 35,
|
|
||||||
|
|
||||||
ClientAccRetrievePortfolioError = 36,
|
|
||||||
ClientAccRetrieveTransactionError = 37,
|
|
||||||
ClientAccCreationFailed = 38,
|
|
||||||
ClientAccInvalidSessionId = 39,
|
|
||||||
ClientAccUnauthorized = 40,
|
|
||||||
|
|
||||||
ClientReqSaltFailed = 41,
|
|
||||||
ClientReqSaltInvMsg = 42,
|
|
||||||
ClientReqSaltInvMsgRetSize = 43,
|
|
||||||
ClientReqSaltInvMsgInst = 44,
|
|
||||||
ClientReqSaltRej = 45,
|
|
||||||
ClientGenSaltFailed = 46,
|
|
||||||
|
|
||||||
ClientTlsReadError = 47,
|
|
||||||
ClientWaitAndReadBranched = 48,
|
|
||||||
}
|
|
||||||
impl std::fmt::Display for ReturnFlags {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{:#?}", self)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
pub static SERVERS_IPS: [&str; 2] = ["", ""];
|
|
||||||
pub static SERVERS_CERTS: [&str; 2] = ["", ""];
|
|
|
@ -1,5 +1,4 @@
|
||||||
pub mod account;
|
pub mod account;
|
||||||
pub mod generic;
|
pub mod command;
|
||||||
|
pub mod jwt;
|
||||||
pub mod message;
|
pub mod message;
|
||||||
pub mod misc;
|
|
||||||
pub mod sessions;
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
pub mod jwt_claim;
|
|
|
@ -1,7 +1,5 @@
|
||||||
/* Server crates */
|
/* Server crates */
|
||||||
#[cfg(all(feature = "server", not(feature = "client")))]
|
#[cfg(all(feature = "server", not(feature = "client")))]
|
||||||
extern crate arrayref;
|
|
||||||
#[cfg(all(feature = "server", not(feature = "client")))]
|
|
||||||
extern crate json;
|
extern crate json;
|
||||||
#[cfg(all(feature = "server", not(feature = "client")))]
|
#[cfg(all(feature = "server", not(feature = "client")))]
|
||||||
extern crate tokio;
|
extern crate tokio;
|
||||||
|
@ -9,14 +7,8 @@ extern crate tokio;
|
||||||
/* Client crates */
|
/* Client crates */
|
||||||
#[cfg(all(feature = "client", not(feature = "server")))]
|
#[cfg(all(feature = "client", not(feature = "server")))]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate arrayref;
|
|
||||||
#[cfg(all(feature = "client", not(feature = "server")))]
|
|
||||||
#[macro_use]
|
|
||||||
extern crate json;
|
extern crate json;
|
||||||
|
|
||||||
#[cfg(all(feature = "server", feature = "client"))]
|
|
||||||
#[macro_use]
|
|
||||||
extern crate arrayref;
|
|
||||||
#[cfg(all(feature = "server", feature = "client"))]
|
#[cfg(all(feature = "server", feature = "client"))]
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate json;
|
extern crate json;
|
||||||
|
|
|
@ -1,215 +1,160 @@
|
||||||
use log::warn;
|
use log::warn;
|
||||||
use std::num::NonZeroU32;
|
|
||||||
|
|
||||||
use data_encoding::HEXUPPER;
|
use argon2::{
|
||||||
use ring::pbkdf2;
|
password_hash::{PasswordHash, PasswordVerifier},
|
||||||
|
Argon2,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::common::message::assert_msg::assert_msg;
|
use crate::common::message::*;
|
||||||
use crate::common::message::inst::CommandInst;
|
|
||||||
use crate::common::message::message::Message;
|
|
||||||
use crate::common::message::message_builder::message_builder;
|
|
||||||
use crate::common::message::message_type::MessageType;
|
|
||||||
|
|
||||||
use crate::server::db::cmd::get_user_hash::get_user_hash;
|
use crate::server::db::cmd::get_user_hash::*;
|
||||||
use crate::server::db::cmd::get_user_id::get_user_id;
|
use crate::server::db::cmd::get_user_id::*;
|
||||||
use crate::server::db::cmd::get_user_salt::get_user_salt;
|
|
||||||
|
|
||||||
use crate::server::network::jwt_wrapper::create_jwt_token;
|
use crate::server::network::jwt::*;
|
||||||
|
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use tokio_rustls::server::TlsStream;
|
use tokio_rustls::server::TlsStream;
|
||||||
|
|
||||||
pub async fn acc_auth(
|
pub async fn authorize(
|
||||||
sql_conn: &tokio_postgres::Client,
|
sql_conn: &tokio_postgres::Client,
|
||||||
tls_connection: &mut TlsStream<TcpStream>,
|
socket: &mut TlsStream<TcpStream>,
|
||||||
message: &Message,
|
message: &Message,
|
||||||
) -> std::io::Result<()> {
|
) -> std::io::Result<()> {
|
||||||
/* assert recieved message */
|
/* assert recieved message */
|
||||||
if !assert_msg(
|
if !message.assert_command(Command::LoginMethod1) || !message.assert_data() {
|
||||||
message,
|
|
||||||
MessageType::Command,
|
|
||||||
true,
|
|
||||||
3,
|
|
||||||
false,
|
|
||||||
0,
|
|
||||||
false,
|
|
||||||
0,
|
|
||||||
false,
|
|
||||||
0,
|
|
||||||
) && message.instruction == CommandInst::LoginMethod1 as i64
|
|
||||||
&& message.data.len() != 0
|
|
||||||
{
|
|
||||||
warn!("LOGIN_INVALID_MESSAGE");
|
warn!("LOGIN_INVALID_MESSAGE");
|
||||||
return tls_connection.shutdown().await;
|
return socket.shutdown().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Parse account data.
|
* Parse account data.
|
||||||
* */
|
* */
|
||||||
/* get json data */
|
/* get json data */
|
||||||
let stringified_data = std::str::from_utf8(&message.data).unwrap();
|
let stringified_data: String = message.get_data().map_err(|_| {
|
||||||
|
std::io::Error::new(
|
||||||
|
std::io::ErrorKind::InvalidData,
|
||||||
|
"Failed authorizing account, could not parse message data!",
|
||||||
|
)
|
||||||
|
})?;
|
||||||
let data = json::parse(&stringified_data).unwrap();
|
let data = json::parse(&stringified_data).unwrap();
|
||||||
/* get email, password, and username hashes */
|
/* get email, password, and username hashes */
|
||||||
let email_hash = HEXUPPER
|
let email_client_hash = data["hashed_email"].as_str().unwrap();
|
||||||
.decode(data["hashed_email"].as_str().unwrap().as_bytes())
|
let passw_client_hash = data["hashed_passw"].as_str().unwrap();
|
||||||
.unwrap();
|
|
||||||
let password_hash = HEXUPPER
|
|
||||||
.decode(data["hashed_password"].as_str().unwrap().as_bytes())
|
|
||||||
.unwrap();
|
|
||||||
let username = data["username"].as_str().unwrap();
|
let username = data["username"].as_str().unwrap();
|
||||||
|
|
||||||
/*
|
|
||||||
* Get server salts
|
|
||||||
* */
|
|
||||||
let email_salt = HEXUPPER
|
|
||||||
.decode(
|
|
||||||
get_user_salt(sql_conn, username, true, true)
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.as_bytes(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let password_salt = HEXUPPER
|
|
||||||
.decode(
|
|
||||||
get_user_salt(sql_conn, username, false, true)
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.as_bytes(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Get server hashes
|
* Get server hashes
|
||||||
* */
|
* */
|
||||||
let email_db = HEXUPPER
|
let email_server_hash = match get_user_hash(sql_conn, username, true).await {
|
||||||
.decode(
|
Ok(val) => val,
|
||||||
get_user_hash(sql_conn, username, true)
|
Err(_) => {
|
||||||
.await
|
return Message::new()
|
||||||
.unwrap()
|
.command(Command::Failure)
|
||||||
.as_bytes(),
|
.data(format!(
|
||||||
)
|
"Failed authorizing user, {}, does not exist!",
|
||||||
.unwrap();
|
username
|
||||||
let password_db = HEXUPPER
|
))
|
||||||
.decode(
|
.send(socket)
|
||||||
get_user_hash(sql_conn, username, false)
|
.await;
|
||||||
.await
|
}
|
||||||
.unwrap()
|
};
|
||||||
.as_bytes(),
|
|
||||||
)
|
let passw_server_hash = match get_user_hash(sql_conn, username, false).await {
|
||||||
.unwrap();
|
Ok(val) => val,
|
||||||
|
Err(_) => {
|
||||||
|
return Message::new()
|
||||||
|
.command(Command::Failure)
|
||||||
|
.data(format!(
|
||||||
|
"Failed authorizing user, {}, does not exist!",
|
||||||
|
username
|
||||||
|
))
|
||||||
|
.send(socket)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Verify creds
|
* Verify creds
|
||||||
* */
|
* */
|
||||||
let email_ret = pbkdf2::verify(
|
let argon2id = Argon2::default();
|
||||||
pbkdf2::PBKDF2_HMAC_SHA512,
|
|
||||||
NonZeroU32::new(350_000).unwrap(),
|
let parsed_email_hash = match PasswordHash::new(&email_server_hash) {
|
||||||
&email_salt,
|
Ok(val) => val,
|
||||||
&email_hash,
|
Err(_) => {
|
||||||
&email_db,
|
return Message::new()
|
||||||
);
|
.command(Command::Failure)
|
||||||
match email_ret.is_ok() {
|
.data("Email Hash Invalid")
|
||||||
true => {}
|
.send(socket)
|
||||||
false => {
|
.await;
|
||||||
let server_response = message_builder(
|
|
||||||
MessageType::ServerReturn,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
bincode::serialize(&"Email Incorrect").unwrap(),
|
|
||||||
);
|
|
||||||
match tls_connection
|
|
||||||
.write_all(&bincode::serialize(&server_response).unwrap())
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
_ => return Ok(()),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let pass_ret = pbkdf2::verify(
|
if argon2id
|
||||||
pbkdf2::PBKDF2_HMAC_SHA512,
|
.verify_password(&email_client_hash.as_bytes(), &parsed_email_hash)
|
||||||
NonZeroU32::new(500_000).unwrap(),
|
.is_err()
|
||||||
&password_salt,
|
{
|
||||||
&password_hash,
|
return Message::new()
|
||||||
&password_db,
|
.command(Command::Failure)
|
||||||
);
|
.data("Email Incorrect")
|
||||||
match pass_ret.is_ok() {
|
.send(socket)
|
||||||
true => {}
|
.await;
|
||||||
false => {
|
}
|
||||||
let server_response = message_builder(
|
|
||||||
MessageType::ServerReturn,
|
let parsed_passw_hash = match PasswordHash::new(&passw_server_hash) {
|
||||||
0,
|
Ok(val) => val,
|
||||||
0,
|
Err(_) => {
|
||||||
0,
|
return Message::new()
|
||||||
0,
|
.command(Command::Failure)
|
||||||
bincode::serialize(&"Password Incorrect").unwrap(),
|
.data("Password Hash Invalid")
|
||||||
);
|
.send(socket)
|
||||||
match tls_connection
|
.await;
|
||||||
.write_all(&bincode::serialize(&server_response).unwrap())
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
_ => return Ok(()),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
if argon2id
|
||||||
|
.verify_password(&passw_client_hash.as_bytes(), &parsed_passw_hash)
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
return Message::new()
|
||||||
|
.command(Command::Failure)
|
||||||
|
.data("Password Incorrect")
|
||||||
|
.send(socket)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Generate JWT token
|
* Generate JWT token
|
||||||
* */
|
* */
|
||||||
/* get user id*/
|
/* get user id*/
|
||||||
let user_id = get_user_id(sql_conn, username).await?;
|
let user_id = match get_user_id(sql_conn, username).await {
|
||||||
|
Ok(val) => val,
|
||||||
|
Err(_) => {
|
||||||
|
return Message::new()
|
||||||
|
.command(Command::Failure)
|
||||||
|
.data(format!(
|
||||||
|
"Failed authorizing user, {}, does not exist!",
|
||||||
|
username
|
||||||
|
))
|
||||||
|
.send(socket)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/* gen the actual token */
|
/* gen the actual token */
|
||||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||||
let beginning_of_time = SystemTime::now() + Duration::from_secs(4 * 60 * 60);
|
let beginning_of_time = SystemTime::now() + Duration::from_secs(4 * 60 * 60);
|
||||||
let jwt_token = create_jwt_token(
|
|
||||||
|
let jwt_result = create_jwt_token(
|
||||||
user_id,
|
user_id,
|
||||||
beginning_of_time
|
beginning_of_time
|
||||||
.duration_since(UNIX_EPOCH)
|
.duration_since(UNIX_EPOCH)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_secs(),
|
.as_secs(),
|
||||||
);
|
)
|
||||||
|
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
|
||||||
|
|
||||||
/*
|
Message::new()
|
||||||
* server failed to generate JWT token.
|
.command(Command::Success)
|
||||||
* inform client about issue
|
.data(jwt_result)
|
||||||
* */
|
.send(socket)
|
||||||
if jwt_token.is_err() {
|
|
||||||
let server_response = message_builder(
|
|
||||||
MessageType::ServerReturn,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
bincode::serialize(&"Login failed, try again later.").unwrap(),
|
|
||||||
);
|
|
||||||
match tls_connection
|
|
||||||
.write_all(&bincode::serialize(&server_response).unwrap())
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
// We already failed,
|
|
||||||
// we don't care if client doesn't recieve
|
|
||||||
_ => return Ok(()),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Send the JWT token
|
|
||||||
* */
|
|
||||||
let message = message_builder(
|
|
||||||
MessageType::ServerReturn,
|
|
||||||
1,
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
jwt_token.unwrap().as_bytes().to_vec(),
|
|
||||||
);
|
|
||||||
match tls_connection
|
|
||||||
.write_all(bincode::serialize(&message).unwrap().as_slice())
|
|
||||||
.await
|
.await
|
||||||
{
|
|
||||||
_ => Ok(()), // Don't care if client doesn't receive
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,182 +1,160 @@
|
||||||
use data_encoding::HEXUPPER;
|
use argon2::password_hash::PasswordHash;
|
||||||
use log::warn;
|
use log::warn;
|
||||||
|
|
||||||
use crate::common::message::assert_msg::assert_msg;
|
use crate::common::account::portfolio::*;
|
||||||
use crate::common::message::inst::CommandInst;
|
use crate::common::message::*;
|
||||||
use crate::common::message::message_builder::message_builder;
|
|
||||||
use crate::common::message::message_type::MessageType;
|
|
||||||
|
|
||||||
use crate::common::account::portfolio::Portfolio;
|
use crate::server::account::hash::*;
|
||||||
use crate::common::message::message::Message;
|
|
||||||
use crate::common::misc::return_flags::ReturnFlags;
|
|
||||||
|
|
||||||
use crate::server::account::hash_email::hash_email;
|
use crate::server::db::cmd::user_exists::*;
|
||||||
use crate::server::account::hash_pwd::hash_pwd;
|
use crate::server::ds::account::*;
|
||||||
use crate::server::ds::account::Account;
|
|
||||||
|
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use tokio_rustls::server::TlsStream;
|
use tokio_rustls::server::TlsStream;
|
||||||
|
|
||||||
pub async fn acc_create(
|
pub async fn create(
|
||||||
sql_conn: &tokio_postgres::Client,
|
sql_conn: &tokio_postgres::Client,
|
||||||
tls_connection: &mut TlsStream<TcpStream>,
|
socket: &mut TlsStream<TcpStream>,
|
||||||
message: &Message,
|
message: &Message,
|
||||||
) -> std::io::Result<()> {
|
) -> std::io::Result<()> {
|
||||||
/* assert recieved message */
|
/* assert recieved message */
|
||||||
if !assert_msg(
|
if !message.assert_command(Command::Register) || !message.assert_data() {
|
||||||
message,
|
|
||||||
MessageType::Command,
|
|
||||||
true,
|
|
||||||
5,
|
|
||||||
false,
|
|
||||||
0,
|
|
||||||
false,
|
|
||||||
0,
|
|
||||||
false,
|
|
||||||
0,
|
|
||||||
) && message.instruction == CommandInst::Register as i64
|
|
||||||
&& message.data.len() != 0
|
|
||||||
{
|
|
||||||
warn!("REGISTER_INVALID_MESSAGE");
|
warn!("REGISTER_INVALID_MESSAGE");
|
||||||
return tls_connection.shutdown().await;
|
return socket.shutdown().await; // just get off of my lawn
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Parse account data
|
* Parse account data
|
||||||
* */
|
* */
|
||||||
/* get json data */
|
/* get json data */
|
||||||
let stringified_data = std::str::from_utf8(&message.data).unwrap().to_string();
|
let stringified_data: String = bincode::deserialize(&message.data).unwrap();
|
||||||
let data = json::parse(&stringified_data).unwrap();
|
let data = json::parse(&stringified_data).unwrap();
|
||||||
/* get email, password salts and client hashes */
|
|
||||||
let email_hash = HEXUPPER
|
|
||||||
.decode(data["email_hash"].as_str().unwrap().to_string().as_bytes())
|
|
||||||
.unwrap();
|
|
||||||
let email_client_salt = HEXUPPER
|
|
||||||
.decode(
|
|
||||||
data["email_client_salt"]
|
|
||||||
.as_str()
|
|
||||||
.unwrap()
|
|
||||||
.to_string()
|
|
||||||
.as_bytes(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let password_hash = HEXUPPER
|
|
||||||
.decode(
|
|
||||||
data["password_hash"]
|
|
||||||
.as_str()
|
|
||||||
.unwrap()
|
|
||||||
.to_string()
|
|
||||||
.as_bytes(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let password_client_salt = HEXUPPER
|
|
||||||
.decode(
|
|
||||||
data["password_client_salt"]
|
|
||||||
.as_str()
|
|
||||||
.unwrap()
|
|
||||||
.to_string()
|
|
||||||
.as_bytes(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
/* get username */
|
/*
|
||||||
let username: String = data["username"].as_str().unwrap().to_string();
|
* check if username is available in the database
|
||||||
|
* */
|
||||||
|
let username = data["username"].as_str().unwrap();
|
||||||
|
|
||||||
|
/* search for an account with same name */
|
||||||
|
if !user_exists(sql_conn, username).await {
|
||||||
|
/*
|
||||||
|
* Inform cient that user already exists
|
||||||
|
* Note: figure out if this is a security? issue
|
||||||
|
*/
|
||||||
|
return Message::new()
|
||||||
|
.command(Command::Failure)
|
||||||
|
.data("Username already exists")
|
||||||
|
.send(socket)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* get email, password client PHC strings */
|
||||||
|
let (email_client_hash_phc, passw_client_hash_phc) = match (
|
||||||
|
data["email_client_hash_phc"].as_str(),
|
||||||
|
data["passw_client_hash_phc"].as_str(),
|
||||||
|
) {
|
||||||
|
(Some(a), Some(b)) => (a, b),
|
||||||
|
_ => {
|
||||||
|
/* received empty PHC strings */
|
||||||
|
return Message::new()
|
||||||
|
.command(Command::Failure)
|
||||||
|
.data("Received empty PHC strings")
|
||||||
|
.send(socket)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let (email_client, passw_client) = match (
|
||||||
|
PasswordHash::new(email_client_hash_phc),
|
||||||
|
PasswordHash::new(passw_client_hash_phc),
|
||||||
|
) {
|
||||||
|
(Ok(a), Ok(b)) => (a, b),
|
||||||
|
_ => {
|
||||||
|
return Message::new()
|
||||||
|
.command(Command::Failure)
|
||||||
|
.data("Received invalid PHC strings")
|
||||||
|
.send(socket)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* store salt _ONLY_ from the PHC string received,
|
||||||
|
* Note: discard the main hash sent from client after hashing by server */
|
||||||
|
let (email_client_salt, passw_client_salt) = match (email_client.salt, passw_client.salt) {
|
||||||
|
(Some(a), Some(b)) => (a, b),
|
||||||
|
_ => {
|
||||||
|
return Message::new()
|
||||||
|
.command(Command::Failure)
|
||||||
|
.data("Received invalid salts")
|
||||||
|
.send(socket)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/* generate account struct */
|
/* generate account struct */
|
||||||
let mut account: Account = Account {
|
let mut account: Account = Account {
|
||||||
username: username,
|
username: username.to_string(),
|
||||||
|
|
||||||
email_hash: "".to_string(),
|
email_hash_phc: "".to_string(),
|
||||||
server_email_salt: "".to_string(),
|
client_email_salt: email_client_salt.to_string(),
|
||||||
client_email_salt: HEXUPPER.encode(&email_client_salt),
|
|
||||||
|
|
||||||
pass_hash: "".to_string(),
|
passw_hash_phc: "".to_string(),
|
||||||
server_pass_salt: "".to_string(),
|
client_passw_salt: passw_client_salt.to_string(),
|
||||||
client_pass_salt: HEXUPPER.encode(&password_client_salt),
|
|
||||||
|
|
||||||
is_pass: true,
|
|
||||||
portfolio: Portfolio::default(),
|
portfolio: Portfolio::default(),
|
||||||
transactions: Vec::new(),
|
transactions: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* check if username is available in the database
|
* Hash the email and password and store them.
|
||||||
* */
|
* */
|
||||||
|
if let (Some(email_client_hash), Some(passw_client_hash)) =
|
||||||
/* search for an account with same name */
|
(email_client.hash, passw_client.hash)
|
||||||
for _ in &sql_conn
|
|
||||||
.query(
|
|
||||||
"SELECT username FROM accounts_schema.accounts WHERE username LIKE $1",
|
|
||||||
&[&account.username],
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
{
|
{
|
||||||
/*
|
account.email_hash_phc = hash(email_client_hash.to_string());
|
||||||
* Inform cient that user already exists
|
account.passw_hash_phc = hash(passw_client_hash.to_string());
|
||||||
* Note: figure out if this is a security? issue
|
} else {
|
||||||
*/
|
return Message::new()
|
||||||
let server_response = message_builder(
|
.command(Command::Failure)
|
||||||
MessageType::ServerReturn,
|
.data("Received empty hashes")
|
||||||
0,
|
.send(socket)
|
||||||
0,
|
.await;
|
||||||
0,
|
|
||||||
0,
|
|
||||||
bincode::serialize(&format!("{:#?}", ReturnFlags::ServerAccUserExists)).unwrap(),
|
|
||||||
);
|
|
||||||
match tls_connection
|
|
||||||
.write_all(&bincode::serialize(&server_response).unwrap())
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
// Don't care if user didn't recieve a reply
|
|
||||||
_ => return Ok(()),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Hash the email and password.
|
|
||||||
* */
|
|
||||||
/* hash the email */
|
|
||||||
let email_server_hash = hash_email(&email_hash);
|
|
||||||
account.email_hash = HEXUPPER.encode(&email_server_hash.0);
|
|
||||||
account.server_email_salt = HEXUPPER.encode(&email_server_hash.1);
|
|
||||||
/* hash the password */
|
|
||||||
let password_server_hash = hash_pwd(&password_hash);
|
|
||||||
account.pass_hash = HEXUPPER.encode(&password_server_hash.0);
|
|
||||||
account.server_pass_salt = HEXUPPER.encode(&password_server_hash.1);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Write the account to the database.
|
* Write the account to the database.
|
||||||
* */
|
* */
|
||||||
let creation_result = sql_conn.execute("INSERT INTO accounts_schema.accounts \
|
let creation_result = sql_conn
|
||||||
(username, email_hash, server_email_salt, client_email_salt, pass_hash, server_pass_salt, client_pass_salt)
|
.execute(
|
||||||
|
"INSERT INTO accounts_schema.accounts \
|
||||||
|
(username, email_hash_phc, client_email_salt, pass_hash_phc, client_pass_salt)
|
||||||
VALUES \
|
VALUES \
|
||||||
($1, $2, $3, $4, $5, $6, $7)",
|
($1, $2, $3, $4, $5)",
|
||||||
&[&account.username,
|
&[
|
||||||
&account.email_hash, &account.server_email_salt, &account.client_email_salt,
|
&account.username,
|
||||||
&account.pass_hash, &account.server_pass_salt, &account.client_pass_salt]).await;
|
&account.email_hash_phc,
|
||||||
|
&account.client_email_salt,
|
||||||
|
&account.passw_hash_phc,
|
||||||
|
&account.client_passw_salt,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Send to client SQL result
|
* Send to client SQL result
|
||||||
*/
|
*/
|
||||||
let server_response = message_builder(
|
match creation_result {
|
||||||
MessageType::ServerReturn,
|
Ok(_) => Message::new().command(Command::Success).send(socket).await,
|
||||||
if creation_result.is_ok() { 1 } else { 0 },
|
Err(_) => {
|
||||||
0,
|
Message::new()
|
||||||
0,
|
.command(Command::Failure)
|
||||||
0,
|
.data(format!(
|
||||||
if creation_result.is_ok() {
|
"Failed creating an account, server error, {:#?}, \
|
||||||
Vec::new()
|
please try again later.",
|
||||||
} else {
|
creation_result
|
||||||
bincode::serialize(&format!("{:#?}", creation_result)).unwrap()
|
))
|
||||||
},
|
.send(socket)
|
||||||
);
|
.await
|
||||||
match tls_connection
|
}
|
||||||
.write_all(&bincode::serialize(&server_response).unwrap())
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
// Don't care if user didn't recieve a reply
|
|
||||||
_ => Ok(()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,31 +1,33 @@
|
||||||
use ring::{digest, pbkdf2};
|
use argon2::{
|
||||||
use std::num::NonZeroU32;
|
password_hash::{PasswordHasher, SaltString},
|
||||||
|
Argon2,
|
||||||
|
};
|
||||||
|
use rand_core::OsRng;
|
||||||
|
|
||||||
/// A generic hashing abstraction function.
|
/// Generates a storable server email hash from a client hashed email.
|
||||||
///
|
///
|
||||||
/// Useful for quickly swapping the current hashing system.
|
/// Takes in a client hashed email, outputs a storable new hash. The returned result is 'safe' to
|
||||||
|
/// be stored on the server side database. The salt returned is for the hashed version of the
|
||||||
|
/// hashed client email.
|
||||||
///
|
///
|
||||||
/// Arguments:
|
/// Arguments:
|
||||||
/// val - The value to be hashed.
|
/// hashed_email - The client hashed email sent to the server.
|
||||||
/// salt - The whole salt to be used.
|
|
||||||
/// iter - The number of iteration to use.
|
|
||||||
///
|
///
|
||||||
/// Returns: u8 array of size 64 bytes.
|
/// Returns: a tuple containing the final hash and the hash's salt, nothing on failure.
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// let email_hash = hash("test@test.com", [0u8; 64], 124000);
|
/// let enc = hash_email("THISISTOTALLYAHASHEDTHING...").unwrap();
|
||||||
|
/// println!("Server Email Hash: {}", HEXUPPER.encode(&enc.0));
|
||||||
|
/// println!("Server Email Salt: {}", HEXUPPER.encode(&enc.1));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn hash(val: &Vec<u8>, salt: &Vec<u8>, iter: u32) -> [u8; digest::SHA512_OUTPUT_LEN] {
|
pub fn hash(hashed_email: String) -> String {
|
||||||
let iterations: NonZeroU32 = NonZeroU32::new(iter).unwrap();
|
let salt = SaltString::generate(&mut OsRng);
|
||||||
|
|
||||||
let mut hash = [0u8; digest::SHA512_OUTPUT_LEN];
|
let argon2id = Argon2::default();
|
||||||
pbkdf2::derive(
|
let phc = argon2id
|
||||||
pbkdf2::PBKDF2_HMAC_SHA512,
|
.hash_password_simple(&hashed_email.as_bytes(), &salt)
|
||||||
iterations,
|
.unwrap();
|
||||||
&salt,
|
|
||||||
val,
|
phc.to_string()
|
||||||
&mut hash,
|
|
||||||
);
|
|
||||||
hash
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
use ring::rand::SecureRandom;
|
|
||||||
use ring::{digest, rand};
|
|
||||||
|
|
||||||
use crate::common::account::hash::hash;
|
|
||||||
|
|
||||||
/// Generates a storable server email hash from a client hashed email.
|
|
||||||
///
|
|
||||||
/// Takes in a client hashed email, outputs a storable new hash. The returned result is 'safe' to
|
|
||||||
/// be stored on the server side database. The salt returned is for the hashed version of the
|
|
||||||
/// hashed client email.
|
|
||||||
///
|
|
||||||
/// Arguments:
|
|
||||||
/// hashed_email - The client hashed email sent to the server.
|
|
||||||
///
|
|
||||||
/// Returns: a tuple containing the final hash and the hash's salt, nothing on failure.
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
/// ```rust
|
|
||||||
/// let enc = hash_email("THISISTOTALLYAHASHEDTHING...").unwrap();
|
|
||||||
/// println!("Server Email Hash: {}", HEXUPPER.encode(&enc.0));
|
|
||||||
/// println!("Server Email Salt: {}", HEXUPPER.encode(&enc.1));
|
|
||||||
/// ```
|
|
||||||
pub fn hash_email(
|
|
||||||
hashed_email: &Vec<u8>,
|
|
||||||
) -> (
|
|
||||||
[u8; digest::SHA512_OUTPUT_LEN],
|
|
||||||
[u8; digest::SHA512_OUTPUT_LEN],
|
|
||||||
) {
|
|
||||||
let rng = rand::SystemRandom::new();
|
|
||||||
|
|
||||||
let mut salt = [0u8; digest::SHA512_OUTPUT_LEN];
|
|
||||||
rng.fill(&mut salt).unwrap();
|
|
||||||
|
|
||||||
let hash = hash(hashed_email, &salt.to_vec(), 350_000);
|
|
||||||
(hash, salt)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
use data_encoding::HEXUPPER;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_account_hash_email_server() {
|
|
||||||
let email = "totallyrealemail@anemail.c0m";
|
|
||||||
|
|
||||||
/* ensure that hash_email_server() works */
|
|
||||||
let output = hash_email(&email.as_bytes().to_vec());
|
|
||||||
assert_ne!(output.0.len(), 0);
|
|
||||||
assert_ne!(output.1.len(), 0);
|
|
||||||
|
|
||||||
/* ensure that hash_email_server() generates different output
|
|
||||||
* each time it is run.
|
|
||||||
* */
|
|
||||||
// Generate new server salt.
|
|
||||||
let enc0 = hash_email(&email.as_bytes().to_vec());
|
|
||||||
let enc1 = hash_email(&email.as_bytes().to_vec());
|
|
||||||
assert_ne!(HEXUPPER.encode(&enc0.0), HEXUPPER.encode(&enc1.0));
|
|
||||||
assert_ne!(HEXUPPER.encode(&enc0.1), HEXUPPER.encode(&enc1.1));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
use ring::rand::SecureRandom;
|
|
||||||
use ring::{digest, rand};
|
|
||||||
|
|
||||||
use crate::common::account::hash::hash;
|
|
||||||
|
|
||||||
/// Generates a storable server password hash from a client hashed password.
|
|
||||||
///
|
|
||||||
/// Takes in a client hashed password, outputs a storable new hash. The returned result is 'safe'
|
|
||||||
/// to be stored on the server side. The salt returned is for the hashed version of the hashed
|
|
||||||
/// client password.
|
|
||||||
///
|
|
||||||
/// Arguments:
|
|
||||||
/// hashed_pass - The client hashed password sent to the server.
|
|
||||||
///
|
|
||||||
/// Returns: a tuple containing the final hash and the hash's salt, nothing on failure.
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
/// ```rust
|
|
||||||
/// let enc = hash_pwd("THISISTOTALLYAHASHEDTHING...").unwrap();
|
|
||||||
/// println!("Server Hash: {}", HEXUPPER.encode(&enc.0));
|
|
||||||
/// println!("Server Salt: {}", HEXUPPER.encode(&enc.1));
|
|
||||||
/// ```
|
|
||||||
pub fn hash_pwd(
|
|
||||||
hashed_pass: &Vec<u8>,
|
|
||||||
) -> (
|
|
||||||
[u8; digest::SHA512_OUTPUT_LEN],
|
|
||||||
[u8; digest::SHA512_OUTPUT_LEN],
|
|
||||||
) {
|
|
||||||
// sever hash, server salt
|
|
||||||
let rng = rand::SystemRandom::new();
|
|
||||||
|
|
||||||
let mut salt = [0u8; digest::SHA512_OUTPUT_LEN];
|
|
||||||
rng.fill(&mut salt).unwrap();
|
|
||||||
|
|
||||||
let hash = hash(hashed_pass, &salt.to_vec(), 500_000);
|
|
||||||
(hash, salt)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
use data_encoding::HEXUPPER;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_account_hash_pwd_server() {
|
|
||||||
let pass = "goodlilpassword";
|
|
||||||
|
|
||||||
/* ensure that hash_pwd_server() works */
|
|
||||||
let output = hash_pwd(&pass.as_bytes().to_vec());
|
|
||||||
assert_ne!(output.0.len(), 0);
|
|
||||||
assert_ne!(output.1.len(), 0);
|
|
||||||
|
|
||||||
/* ensure that hash_pwd_server() generates different output
|
|
||||||
* each time it is run.
|
|
||||||
* */
|
|
||||||
// Generate new server salt.
|
|
||||||
let enc0 = hash_pwd(&pass.as_bytes().to_vec());
|
|
||||||
let enc1 = hash_pwd(&pass.as_bytes().to_vec());
|
|
||||||
assert_ne!(HEXUPPER.encode(&enc0.0), HEXUPPER.encode(&enc1.0));
|
|
||||||
assert_ne!(HEXUPPER.encode(&enc0.1), HEXUPPER.encode(&enc1.1));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,7 @@
|
||||||
pub mod authorization;
|
pub mod authorization;
|
||||||
pub mod creation;
|
pub mod creation;
|
||||||
pub mod hash;
|
pub mod hash;
|
||||||
pub mod hash_email;
|
|
||||||
pub mod hash_pwd;
|
pub use crate::server::account::authorization::authorize;
|
||||||
pub mod retrieval_portfolio;
|
pub use crate::server::account::creation::create;
|
||||||
pub mod retrieval_transaction;
|
pub use crate::server::account::hash::hash;
|
||||||
|
|
|
@ -1,121 +0,0 @@
|
||||||
use log::warn;
|
|
||||||
|
|
||||||
use crate::common::account::portfolio::Portfolio;
|
|
||||||
use crate::common::account::position::Position;
|
|
||||||
use crate::common::message::assert_msg::assert_msg;
|
|
||||||
use crate::common::message::inst::DataTransferInst;
|
|
||||||
use crate::common::message::message::Message;
|
|
||||||
use crate::common::message::message_builder::message_builder;
|
|
||||||
use crate::common::message::message_type::MessageType;
|
|
||||||
|
|
||||||
use crate::server::db::initializer::db_connect;
|
|
||||||
use crate::server::network::jwt_wrapper::verify_jwt_token;
|
|
||||||
|
|
||||||
use tokio::io::AsyncWriteExt;
|
|
||||||
use tokio::net::TcpStream;
|
|
||||||
use tokio_rustls::server::TlsStream;
|
|
||||||
|
|
||||||
pub async fn acc_retrieve_portfolio(
|
|
||||||
tls_connection: &mut TlsStream<TcpStream>,
|
|
||||||
message: &Message,
|
|
||||||
) -> std::io::Result<()> {
|
|
||||||
/* assert recieved message */
|
|
||||||
if !assert_msg(
|
|
||||||
message,
|
|
||||||
MessageType::Command,
|
|
||||||
true,
|
|
||||||
1,
|
|
||||||
false,
|
|
||||||
0,
|
|
||||||
false,
|
|
||||||
0,
|
|
||||||
false,
|
|
||||||
0,
|
|
||||||
) && message.instruction == DataTransferInst::GetUserPortfolio as i64
|
|
||||||
&& message.data.len() != 0
|
|
||||||
{
|
|
||||||
warn!("RETRIEVE_PORTFOLIO_INVALID_MESSAGE");
|
|
||||||
return tls_connection.shutdown().await;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* verify JWT token */
|
|
||||||
let token = match verify_jwt_token(bincode::deserialize(&message.data).unwrap()) {
|
|
||||||
Ok(token) => token,
|
|
||||||
Err(_) => {
|
|
||||||
warn!("ACC_RETRIEVE_PORTFOLIO_UNAUTH_TOKEN");
|
|
||||||
let server_response = message_builder(
|
|
||||||
MessageType::ServerReturn,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
bincode::serialize(&"Password Incorrect").unwrap(),
|
|
||||||
);
|
|
||||||
match tls_connection
|
|
||||||
.write_all(&bincode::serialize(&server_response).unwrap())
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
_ => {
|
|
||||||
// TODO: do we shutdown connection or do we let handle_data caller do it's
|
|
||||||
// thing
|
|
||||||
tls_connection.shutdown().await.unwrap();
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/* connect to SQL database using user ```postfolio_schema_user``` */
|
|
||||||
let sql_conn = db_connect(
|
|
||||||
std::env::var("DB_PORTFOLIO_USER").unwrap(),
|
|
||||||
std::env::var("DB_PORTFOLIO_PASS").unwrap(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
/* get userId's portfolio positions */
|
|
||||||
let mut portfolio: Portfolio = Portfolio::default();
|
|
||||||
// get position data from the portfolio_schema.positions table.
|
|
||||||
for row in sql_conn
|
|
||||||
.query(
|
|
||||||
"SELECT * FROM portfolio_schema.positions WHERE user_id = $1",
|
|
||||||
&[&token.user_id],
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
{
|
|
||||||
let mut pos: Position = Position::default();
|
|
||||||
pos.stock_symbol = row.get(2);
|
|
||||||
pos.stock_open_amount = row.get(3);
|
|
||||||
pos.stock_open_price = row.get(4);
|
|
||||||
pos.stock_open_cost = row.get(5);
|
|
||||||
pos.stock_close_amount = row.get(6);
|
|
||||||
pos.stock_close_price = row.get(7);
|
|
||||||
pos.open_epoch = row.get(8);
|
|
||||||
pos.close_epoch = row.get(9);
|
|
||||||
pos.is_open = row.get(10);
|
|
||||||
|
|
||||||
pos.is_buy = row.get(11);
|
|
||||||
portfolio.open_positions.push(pos);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* build a message */
|
|
||||||
let message = message_builder(
|
|
||||||
MessageType::DataTransfer,
|
|
||||||
1,
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
bincode::serialize(&portfolio).unwrap(),
|
|
||||||
);
|
|
||||||
match tls_connection
|
|
||||||
.write_all(&bincode::serialize(&message).unwrap())
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(()) => Ok(()),
|
|
||||||
Err(err) => {
|
|
||||||
// Log issue in writing to client
|
|
||||||
warn!("Could not write to cient! With Error: {}\nIgnoring...", err);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,90 +0,0 @@
|
||||||
use log::warn;
|
|
||||||
|
|
||||||
use crate::common::account::transaction::Transaction;
|
|
||||||
use crate::common::message::assert_msg::assert_msg;
|
|
||||||
use crate::common::message::inst::DataTransferInst;
|
|
||||||
use crate::common::message::message::Message;
|
|
||||||
use crate::common::message::message_builder::message_builder;
|
|
||||||
use crate::common::message::message_type::MessageType;
|
|
||||||
|
|
||||||
use crate::server::network::jwt_wrapper::verify_jwt_token;
|
|
||||||
|
|
||||||
use tokio::io::AsyncWriteExt;
|
|
||||||
use tokio::net::TcpStream;
|
|
||||||
use tokio_rustls::server::TlsStream;
|
|
||||||
|
|
||||||
pub async fn acc_retrieve_transaction(
|
|
||||||
sql_conn: &tokio_postgres::Client,
|
|
||||||
tls_connection: &mut TlsStream<TcpStream>,
|
|
||||||
message: &Message,
|
|
||||||
) -> std::io::Result<()> {
|
|
||||||
/* assert recieved message */
|
|
||||||
if !assert_msg(
|
|
||||||
message,
|
|
||||||
MessageType::DataTransfer,
|
|
||||||
true,
|
|
||||||
1,
|
|
||||||
false,
|
|
||||||
0,
|
|
||||||
false,
|
|
||||||
0,
|
|
||||||
false,
|
|
||||||
0,
|
|
||||||
) && message.instruction == DataTransferInst::GetUserTransactionHist as i64
|
|
||||||
&& message.data.len() != 0
|
|
||||||
{
|
|
||||||
warn!("RETRIEVE_TRANSACTION_INVALID_MESSAGE");
|
|
||||||
return tls_connection.shutdown().await;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* verify JWT token */
|
|
||||||
let token = match verify_jwt_token(bincode::deserialize(&message.data).unwrap()) {
|
|
||||||
Ok(token) => token,
|
|
||||||
Err(_) => {
|
|
||||||
warn!("ACC_RETRIEVE_TRANSACTION_UNAUTH_TOKEN");
|
|
||||||
tls_connection.shutdown().await.unwrap();
|
|
||||||
// Unauth aren't big deal,
|
|
||||||
// maybe later we can make more sophisticated DOS attack detection
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/* get userId's transactions */
|
|
||||||
let mut transactions: Vec<Transaction> = Vec::new();
|
|
||||||
for row in sql_conn
|
|
||||||
.query(
|
|
||||||
"SELECT * FROM accounts_schema.transactions WHERE user_id = $1",
|
|
||||||
&[&token.user_id],
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
{
|
|
||||||
let mut transaction = Transaction::default();
|
|
||||||
transaction.stock_symbol = row.get(2);
|
|
||||||
transaction.shares_size = row.get(3);
|
|
||||||
transaction.shares_cost = row.get(4);
|
|
||||||
transaction.is_buy = row.get(5);
|
|
||||||
|
|
||||||
transactions.push(transaction);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* build message to be send */
|
|
||||||
let message = message_builder(
|
|
||||||
MessageType::ServerReturn,
|
|
||||||
1,
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
bincode::serialize(&transactions).unwrap(),
|
|
||||||
);
|
|
||||||
match tls_connection
|
|
||||||
.write_all(&bincode::serialize(&message).unwrap())
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(()) => Ok(()),
|
|
||||||
Err(err) => {
|
|
||||||
warn!("Could not write to client! Error: {}", err);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,51 +1,50 @@
|
||||||
use crate::common::generic::company::Company;
|
//use crate::common::generic::company::*;
|
||||||
use crate::common::misc::return_flags::ReturnFlags;
|
//
|
||||||
|
///// Creates a company on the postgres SQL database.
|
||||||
/// Creates a company on the postgres SQL database.
|
/////
|
||||||
///
|
///// Takes in a company and writes an entry in public.companies.
|
||||||
/// Takes in a company and writes an entry in public.companies.
|
///// Should be used in Async contexts.
|
||||||
/// Should be used in Async contexts.
|
/////
|
||||||
///
|
///// Arguments:
|
||||||
/// Arguments:
|
///// sql_conn - The SQL connection to use.
|
||||||
/// sql_conn - The SQL connection to use.
|
///// company - The company to create.
|
||||||
/// company - The company to create.
|
/////
|
||||||
///
|
///// Returns: the company, a string containing reason of failure on error.
|
||||||
/// Returns: the company, a string containing reason of failure on error.
|
/////
|
||||||
///
|
///// Example:
|
||||||
/// Example:
|
///// ```rust
|
||||||
/// ```rust
|
///// match create_company(company) {
|
||||||
/// match create_company(company) {
|
///// Ok(()) => info!("created company"),
|
||||||
/// Ok(()) => info!("created company"),
|
///// Err(err) => error!("Failed to create company with error: {}", err),
|
||||||
/// Err(err) => error!("Failed to create company with error: {}", err),
|
///// }
|
||||||
/// }
|
///// ```
|
||||||
/// ```
|
//pub async fn create_company(
|
||||||
pub async fn create_company(
|
// sql_conn: &mut tokio_postgres::Client,
|
||||||
sql_conn: &mut tokio_postgres::Client,
|
// company: Company,
|
||||||
company: Company,
|
//) -> Result<Company, String> {
|
||||||
) -> Result<Company, ReturnFlags> {
|
// /*
|
||||||
/*
|
// * Creates a company entry in database in public.companies.
|
||||||
* Creates a company entry in database in public.companies.
|
// */
|
||||||
*/
|
//
|
||||||
|
// // Insert argument company into public.companies database table.
|
||||||
// Insert argument company into public.companies database table.
|
// match sql_conn
|
||||||
match sql_conn
|
// .execute(
|
||||||
.execute(
|
// "INSERT INTO public.companies VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||||
"INSERT INTO public.companies VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
// &[
|
||||||
&[
|
// &company.id,
|
||||||
&company.id,
|
// &company.symbol,
|
||||||
&company.symbol,
|
// &company.isin,
|
||||||
&company.isin,
|
// &company.company_name,
|
||||||
&company.company_name,
|
// &company.primary_exchange,
|
||||||
&company.primary_exchange,
|
// &company.sector,
|
||||||
&company.sector,
|
// &company.industry,
|
||||||
&company.industry,
|
// &company.primary_sic_code,
|
||||||
&company.primary_sic_code,
|
// &company.employees,
|
||||||
&company.employees,
|
// ],
|
||||||
],
|
// )
|
||||||
)
|
// .await
|
||||||
.await
|
// {
|
||||||
{
|
// Ok(_row) => Ok(company),
|
||||||
Ok(_row) => Ok(company),
|
// Err(_) => Err(format!("Failed creating company, {}.", company.symbol))
|
||||||
Err(_) => Err(ReturnFlags::ServerDbCreateCompanyFailed),
|
// }
|
||||||
}
|
//}
|
||||||
}
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use crate::common::account::position::Position;
|
use crate::common::account::position::*;
|
||||||
use crate::common::misc::return_flags::ReturnFlags;
|
|
||||||
|
|
||||||
/// Creates a position on the postgre SQL database
|
/// Creates a position on the postgre SQL database
|
||||||
///
|
///
|
||||||
|
@ -22,7 +21,7 @@ pub async fn create_position(
|
||||||
sql_conn: &mut tokio_postgres::Client,
|
sql_conn: &mut tokio_postgres::Client,
|
||||||
user_id: i64,
|
user_id: i64,
|
||||||
position: Position,
|
position: Position,
|
||||||
) -> Result<(), ReturnFlags> {
|
) -> Result<(), String> {
|
||||||
/*
|
/*
|
||||||
* Creates a position entry in database in portfolio_schema.positions.
|
* Creates a position entry in database in portfolio_schema.positions.
|
||||||
* */
|
* */
|
||||||
|
@ -36,6 +35,6 @@ pub async fn create_position(
|
||||||
&position.stock_open_cost, &position.stock_close_amount, &position.stock_close_price,
|
&position.stock_open_cost, &position.stock_close_amount, &position.stock_close_price,
|
||||||
&position.open_epoch, &position.close_epoch, &position.is_buy, &position.is_open]).await {
|
&position.open_epoch, &position.close_epoch, &position.is_buy, &position.is_open]).await {
|
||||||
Ok(_rows) => Ok(()),
|
Ok(_rows) => Ok(()),
|
||||||
Err(_) => Err(ReturnFlags::ServerDbCreatePositionFailed),
|
Err(_) => Err("Failed to create user position.".to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
use crate::common::misc::return_flags::ReturnFlags;
|
|
||||||
|
|
||||||
/// Creates a stock on the postgres SQL database.
|
/// Creates a stock on the postgres SQL database.
|
||||||
///
|
///
|
||||||
/// Takes in a stock name and creates a table in the ```asset_schema``` schema
|
/// Takes in a stock name and creates a table in the ```asset_schema``` schema
|
||||||
|
@ -21,7 +19,7 @@ use crate::common::misc::return_flags::ReturnFlags;
|
||||||
pub async fn create_stock(
|
pub async fn create_stock(
|
||||||
sql_conn: &mut tokio_postgres::Client,
|
sql_conn: &mut tokio_postgres::Client,
|
||||||
stock_name: &str,
|
stock_name: &str,
|
||||||
) -> Result<(), ReturnFlags> {
|
) -> Result<(), String> {
|
||||||
/*
|
/*
|
||||||
* Creates a stock table in database in assets schema.
|
* Creates a stock table in database in assets schema.
|
||||||
*/
|
*/
|
||||||
|
@ -46,6 +44,6 @@ pub async fn create_stock(
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_rows) => Ok(()),
|
Ok(_rows) => Ok(()),
|
||||||
Err(_) => Err(ReturnFlags::ServerDbCreateStockFailed),
|
Err(_) => Err(format!("Failed creating stock, {}", stock_name)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use crate::common::account::transaction::Transaction;
|
use crate::common::account::transaction::*;
|
||||||
use crate::common::misc::return_flags::ReturnFlags;
|
|
||||||
|
|
||||||
/// Creates a transaction on the postgre SQL database
|
/// Creates a transaction on the postgre SQL database
|
||||||
///
|
///
|
||||||
|
@ -22,7 +21,7 @@ pub async fn create_transaction(
|
||||||
sql_conn: &mut tokio_postgres::Client,
|
sql_conn: &mut tokio_postgres::Client,
|
||||||
user_id: i64,
|
user_id: i64,
|
||||||
transaction: &Transaction,
|
transaction: &Transaction,
|
||||||
) -> Result<(), ReturnFlags> {
|
) -> Result<(), String> {
|
||||||
/*
|
/*
|
||||||
* Creates a transaction entry in database in accounts_schema.transactions.
|
* Creates a transaction entry in database in accounts_schema.transactions.
|
||||||
* */
|
* */
|
||||||
|
@ -44,6 +43,6 @@ pub async fn create_transaction(
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_rows) => Ok(()),
|
Ok(_rows) => Ok(()),
|
||||||
Err(_) => Err(ReturnFlags::ServerDbCreateTransactionFailed),
|
Err(_) => Err("Failed to create user transaction.".to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
use crate::common::command::*;
|
||||||
|
use crate::server::db::cmd::user_exists::*;
|
||||||
|
|
||||||
|
pub async fn get_client_salt(
|
||||||
|
sql_conn: &tokio_postgres::Client,
|
||||||
|
username: &str,
|
||||||
|
command: Command,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
/* check that user exists */
|
||||||
|
if user_exists(sql_conn, username).await {
|
||||||
|
let query_variable: String = match command {
|
||||||
|
Command::GetEmailSalt => "client_email_salt",
|
||||||
|
Command::GetPasswordSalt => "client_pass_salt",
|
||||||
|
_ => {
|
||||||
|
return Err(format!(
|
||||||
|
"Could not get salt for user, {}, requested salt type, {}, is invalid.",
|
||||||
|
username, command
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.into();
|
||||||
|
|
||||||
|
for row in &sql_conn
|
||||||
|
.query(
|
||||||
|
format!(
|
||||||
|
"SELECT username, {} FROM accounts_schema.accounts WHERE username LIKE $1",
|
||||||
|
&query_variable
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
&[&username],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
{
|
||||||
|
return Ok(row.get(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err("Failed to retrieve salt of non-existing user.".to_string())
|
||||||
|
}
|
|
@ -1,54 +1,53 @@
|
||||||
use crate::common::generic::company::Company;
|
//use crate::common::generic::company::*;
|
||||||
use crate::common::misc::return_flags::ReturnFlags;
|
//
|
||||||
|
///// Returns a company from the postgres SQL database.
|
||||||
/// Returns a company from the postgres SQL database.
|
/////
|
||||||
///
|
///// Takes in a company symbol and returns a company.
|
||||||
/// Takes in a company symbol and returns a company.
|
///// Shuold be used in Async contexts
|
||||||
/// Shuold be used in Async contexts
|
/////
|
||||||
///
|
///// Arguments:
|
||||||
/// Arguments:
|
///// sql_conn - The SQL connection to use.
|
||||||
/// sql_conn - The SQL connection to use.
|
///// search_symbol - The specific company symbol to find.
|
||||||
/// search_symbol - The specific company symbol to find.
|
/////
|
||||||
///
|
///// Returns: a reference to the found company on success, and a string containing the reason of
|
||||||
/// Returns: a reference to the found company on success, and a string containing the reason of
|
///// failure on error.
|
||||||
/// failure on error.
|
/////
|
||||||
///
|
///// Example:
|
||||||
/// Example:
|
///// ```rust
|
||||||
/// ```rust
|
///// match get_company_from_db("AAPL".to_string()) {
|
||||||
/// match get_company_from_db("AAPL".to_string()) {
|
///// Ok(found_company) => info!("we found it! {:?}", found_company),
|
||||||
/// Ok(found_company) => info!("we found it! {:?}", found_company),
|
///// Err(err) => error!("we must found the sacred company! err: {}", err),
|
||||||
/// Err(err) => error!("we must found the sacred company! err: {}", err),
|
///// }
|
||||||
/// }
|
///// ```
|
||||||
/// ```
|
//pub async fn get_company_from_db(
|
||||||
pub async fn get_company_from_db(
|
// sql_conn: &mut tokio_postgres::Client,
|
||||||
sql_conn: &mut tokio_postgres::Client,
|
// searched_symbol: &str,
|
||||||
searched_symbol: &str,
|
//) -> Result<Company, String> {
|
||||||
) -> Result<Company, ReturnFlags> {
|
// /*
|
||||||
/*
|
// * Returns company entry from database
|
||||||
* Returns company entry from database
|
// */
|
||||||
*/
|
// // Connect to database.
|
||||||
// Connect to database.
|
// match sql_conn
|
||||||
match sql_conn
|
// .query(
|
||||||
.query(
|
// "SELECT * FROM public.companies WHERE symbol=$1",
|
||||||
"SELECT * FROM public.companies WHERE symbol=$1",
|
// &[&searched_symbol],
|
||||||
&[&searched_symbol],
|
// )
|
||||||
)
|
// .await
|
||||||
.await
|
// {
|
||||||
{
|
// Ok(row) => {
|
||||||
Ok(row) => {
|
// let mut found_company: Company = Company::default();
|
||||||
let mut found_company: Company = Company::default();
|
// found_company.id = row[0].get(0);
|
||||||
found_company.id = row[0].get(0);
|
// found_company.symbol = row[0].get(1);
|
||||||
found_company.symbol = row[0].get(1);
|
// found_company.isin = row[0].get(2);
|
||||||
found_company.isin = row[0].get(2);
|
// found_company.company_name = row[0].get(3);
|
||||||
found_company.company_name = row[0].get(3);
|
// found_company.primary_exchange = row[0].get(4);
|
||||||
found_company.primary_exchange = row[0].get(4);
|
// found_company.sector = row[0].get(5);
|
||||||
found_company.sector = row[0].get(5);
|
// found_company.industry = row[0].get(6);
|
||||||
found_company.industry = row[0].get(6);
|
// found_company.primary_sic_code = row[0].get(7);
|
||||||
found_company.primary_sic_code = row[0].get(7);
|
// found_company.employees = row[0].get(8);
|
||||||
found_company.employees = row[0].get(8);
|
//
|
||||||
|
// return Ok(found_company);
|
||||||
return Ok(found_company);
|
// }
|
||||||
}
|
// Err(_) => Err(format!("Failed getting company, {}, not found.", searched_symbol))
|
||||||
Err(_) => Err(ReturnFlags::ServerDbSearchCompanyNotFound),
|
// }
|
||||||
}
|
//}
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
use argon2::password_hash::PasswordHash;
|
||||||
|
|
||||||
|
use crate::common::command::*;
|
||||||
|
use crate::server::db::cmd::user_exists::*;
|
||||||
|
|
||||||
|
pub async fn get_server_salt(
|
||||||
|
sql_conn: &tokio_postgres::Client,
|
||||||
|
username: &str,
|
||||||
|
command: Command,
|
||||||
|
) -> Result<String, String> {
|
||||||
|
/* check that user exists */
|
||||||
|
if user_exists(sql_conn, username).await {
|
||||||
|
let query_variable: String = match command {
|
||||||
|
Command::GetEmailSalt => "email_hash_phc",
|
||||||
|
Command::GetPasswordSalt => "pass_hash_phc",
|
||||||
|
_ => {
|
||||||
|
return Err(format!(
|
||||||
|
"Could not get salt for user, {}, requested salt type, {}, is invalid.",
|
||||||
|
username, command
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.into();
|
||||||
|
|
||||||
|
for row in &sql_conn
|
||||||
|
.query(
|
||||||
|
format!(
|
||||||
|
"SELECT username, {} FROM accounts_schema.accounts WHERE username LIKE $1",
|
||||||
|
query_variable
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
&[&username],
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
{
|
||||||
|
/* parse PHC string */
|
||||||
|
let parsed_phc = PasswordHash::new(row.get(1))
|
||||||
|
.map_err(|_| format!("Account, {}, corrupted, invalid parsed PHC", username))?;
|
||||||
|
if let Some(salt) = parsed_phc.salt {
|
||||||
|
return Ok(salt.to_string());
|
||||||
|
} else {
|
||||||
|
return Err(format!("Account, {}, corrupted, empty salt!", username));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err("Failed to retrieve salt of non-existing user.".to_string())
|
||||||
|
}
|
|
@ -1,179 +1,178 @@
|
||||||
use crate::common::generic::stock_val::StockVal;
|
//use crate::common::generic::stock_val::*;
|
||||||
use crate::common::misc::return_flags::ReturnFlags;
|
//
|
||||||
|
///// Returns the whole stock data from the postgres SQL database.
|
||||||
/// Returns the whole stock data from the postgres SQL database.
|
/////
|
||||||
///
|
///// Takes in a stock symbol and returns the whole data entries of the searched stock.
|
||||||
/// Takes in a stock symbol and returns the whole data entries of the searched stock.
|
///// Should be used in Async contexts.
|
||||||
/// Should be used in Async contexts.
|
/////
|
||||||
///
|
///// Arguments:
|
||||||
/// Arguments:
|
///// sql_conn - The SQL connection to use.
|
||||||
/// sql_conn - The SQL connection to use.
|
///// searched_symbol - The name of the stock table.
|
||||||
/// searched_symbol - The name of the stock table.
|
/////
|
||||||
///
|
///// Returns: a Vec<StockVal> on success, and a string containing the reason of failure on error.
|
||||||
/// Returns: a Vec<StockVal> on success, and a string containing the reason of failure on error.
|
/////
|
||||||
///
|
///// Example:
|
||||||
/// Example:
|
///// ```rust
|
||||||
/// ```rust
|
///// match get_stock_from_db("AAPL".into()) {
|
||||||
/// match get_stock_from_db("AAPL".into()) {
|
///// Ok(vals) => {
|
||||||
/// Ok(vals) => {
|
///// /* do something with the values */
|
||||||
/// /* do something with the values */
|
///// },
|
||||||
/// },
|
///// Err(err) => panic!("failed to get the stock value, reason: {}", err)
|
||||||
/// Err(err) => panic!("failed to get the stock value, reason: {}", err)
|
///// };
|
||||||
/// };
|
///// ```
|
||||||
/// ```
|
//pub async fn get_stock_from_db(
|
||||||
pub async fn get_stock_from_db(
|
// sql_conn: &mut tokio_postgres::Client,
|
||||||
sql_conn: &mut tokio_postgres::Client,
|
// searched_symbol: &str,
|
||||||
searched_symbol: &str,
|
//) -> Result<Vec<StockVal>, String> {
|
||||||
) -> Result<Vec<StockVal>, ReturnFlags> {
|
// /*
|
||||||
/*
|
// * Returns all stock values from database.
|
||||||
* Returns all stock values from database.
|
// */
|
||||||
*/
|
//
|
||||||
|
// // Query database for table.
|
||||||
// Query database for table.
|
// let mut stocks: Vec<StockVal> = Vec::new();
|
||||||
let mut stocks: Vec<StockVal> = Vec::new();
|
// match sql_conn
|
||||||
match sql_conn
|
// .query(
|
||||||
.query(
|
// format!("SELECT * FROM asset_schema.{}", searched_symbol).as_str(),
|
||||||
format!("SELECT * FROM asset_schema.{}", searched_symbol).as_str(),
|
// &[],
|
||||||
&[],
|
// )
|
||||||
)
|
// .await
|
||||||
.await
|
// {
|
||||||
{
|
// Ok(all_rows) => {
|
||||||
Ok(all_rows) => {
|
// for row in all_rows {
|
||||||
for row in all_rows {
|
// let mut val: StockVal = StockVal::default();
|
||||||
let mut val: StockVal = StockVal::default();
|
// val.id = row.get(0);
|
||||||
val.id = row.get(0);
|
// val.isin = row.get(1);
|
||||||
val.isin = row.get(1);
|
// val.time_epoch = row.get(2);
|
||||||
val.time_epoch = row.get(2);
|
// val.ask_price = row.get(3);
|
||||||
val.ask_price = row.get(3);
|
// val.bid_price = row.get(4);
|
||||||
val.bid_price = row.get(4);
|
// val.volume = row.get(5);
|
||||||
val.volume = row.get(5);
|
// stocks.push(val);
|
||||||
stocks.push(val);
|
// }
|
||||||
}
|
// Ok(stocks)
|
||||||
Ok(stocks)
|
// }
|
||||||
}
|
// Err(_) => Err(format!("Failed to retrieve stock, {}, could not be found.", searched_symbol))
|
||||||
Err(_) => Err(ReturnFlags::ServerDbSearchStockNotFound),
|
// }
|
||||||
}
|
//}
|
||||||
}
|
//
|
||||||
|
///// Returns stock data since an unix epoch from the postgres SQL database.
|
||||||
/// Returns stock data since an unix epoch from the postgres SQL database.
|
/////
|
||||||
///
|
///// Takes in a stock symbol and returns the data entries after a specified epoch of the searched stock.
|
||||||
/// Takes in a stock symbol and returns the data entries after a specified epoch of the searched stock.
|
///// Should be used in Async contexts.
|
||||||
/// Should be used in Async contexts.
|
/////
|
||||||
///
|
///// Arguments:
|
||||||
/// Arguments:
|
///// sql_conn - The SQL connection to use.
|
||||||
/// sql_conn - The SQL connection to use.
|
///// searched_symbol - The name of the stock table.
|
||||||
/// searched_symbol - The name of the stock table.
|
///// time_epoch - The time from which the stock data retrieved.
|
||||||
/// time_epoch - The time from which the stock data retrieved.
|
/////
|
||||||
///
|
///// Returns: a Vec<StockVal> on success, and a string containing the reason of failure on error.
|
||||||
/// Returns: a Vec<StockVal> on success, and a string containing the reason of failure on error.
|
/////
|
||||||
///
|
///// Example:
|
||||||
/// Example:
|
///// ```rust
|
||||||
/// ```rust
|
///// match get_stock_from_db_since_epoch("AAPL".into(), 123456) {
|
||||||
/// match get_stock_from_db_since_epoch("AAPL".into(), 123456) {
|
///// Ok(vals) => {
|
||||||
/// Ok(vals) => {
|
///// /* do something with the filtered values */
|
||||||
/// /* do something with the filtered values */
|
///// },
|
||||||
/// },
|
///// Err(err) => panic!("failed to get the stock value, reason: {}", err)
|
||||||
/// Err(err) => panic!("failed to get the stock value, reason: {}", err)
|
///// };
|
||||||
/// };
|
///// ```
|
||||||
/// ```
|
//pub async fn get_stock_from_db_since_epoch(
|
||||||
pub async fn get_stock_from_db_since_epoch(
|
// sql_conn: &mut tokio_postgres::Client,
|
||||||
sql_conn: &mut tokio_postgres::Client,
|
// searched_symbol: &str,
|
||||||
searched_symbol: &str,
|
// time_epoch: i64,
|
||||||
time_epoch: i64,
|
//) -> Result<Vec<StockVal>, String> {
|
||||||
) -> Result<Vec<StockVal>, ReturnFlags> {
|
// /*
|
||||||
/*
|
// * Returns all stock values from database since a time epoch.
|
||||||
* Returns all stock values from database since a time epoch.
|
// */
|
||||||
*/
|
//
|
||||||
|
// // Query database for table.
|
||||||
// Query database for table.
|
// let mut stocks: Vec<StockVal> = Vec::new();
|
||||||
let mut stocks: Vec<StockVal> = Vec::new();
|
// match sql_conn
|
||||||
match sql_conn
|
// .query(
|
||||||
.query(
|
// format!(
|
||||||
format!(
|
// "SELECT * FROM asset_schema.{} WHERE time_epoch >= {}",
|
||||||
"SELECT * FROM asset_schema.{} WHERE time_epoch >= {}",
|
// searched_symbol, time_epoch
|
||||||
searched_symbol, time_epoch
|
// )
|
||||||
)
|
// .as_str(),
|
||||||
.as_str(),
|
// &[],
|
||||||
&[],
|
// )
|
||||||
)
|
// .await
|
||||||
.await
|
// {
|
||||||
{
|
// Ok(all_rows) => {
|
||||||
Ok(all_rows) => {
|
// for row in all_rows {
|
||||||
for row in all_rows {
|
// let mut val: StockVal = StockVal::default();
|
||||||
let mut val: StockVal = StockVal::default();
|
// val.id = row.get(0);
|
||||||
val.id = row.get(0);
|
// val.isin = row.get(1);
|
||||||
val.isin = row.get(1);
|
// val.time_epoch = row.get(2);
|
||||||
val.time_epoch = row.get(2);
|
// val.ask_price = row.get(3);
|
||||||
val.ask_price = row.get(3);
|
// val.bid_price = row.get(4);
|
||||||
val.bid_price = row.get(4);
|
// val.volume = row.get(5);
|
||||||
val.volume = row.get(5);
|
// stocks.push(val);
|
||||||
stocks.push(val);
|
// }
|
||||||
}
|
// Ok(stocks)
|
||||||
Ok(stocks)
|
// }
|
||||||
}
|
// Err(_) => Err(format!("Failed to retrieve stock, {}, could not be found.", searched_symbol))
|
||||||
Err(_) => Err(ReturnFlags::ServerDbSearchStockNotFound),
|
// }
|
||||||
}
|
//}
|
||||||
}
|
//
|
||||||
|
///// Returns stock data between two unix epochs from the postgres SQL database.
|
||||||
/// Returns stock data between two unix epochs from the postgres SQL database.
|
/////
|
||||||
///
|
///// Takes in a stock symbol and returns the data entries between two specified unix epochs of the searched
|
||||||
/// Takes in a stock symbol and returns the data entries between two specified unix epochs of the searched
|
///// stock.
|
||||||
/// stock.
|
///// Should be used in Async contexts.
|
||||||
/// Should be used in Async contexts.
|
/////
|
||||||
///
|
///// Arguments:
|
||||||
/// Arguments:
|
///// sql_conn - The SQL connection to use.
|
||||||
/// sql_conn - The SQL connection to use.
|
///// searched_symbol - The name of the stock table.
|
||||||
/// searched_symbol - The name of the stock table.
|
///// first_time_epoch - The time from which the stock data is first retrieved.
|
||||||
/// first_time_epoch - The time from which the stock data is first retrieved.
|
///// second_time_epoch - The time from which the stock data ends.
|
||||||
/// second_time_epoch - The time from which the stock data ends.
|
/////
|
||||||
///
|
///// Returns: a Vec<StockVal> on success, and a string containing the reason of failure on error.
|
||||||
/// Returns: a Vec<StockVal> on success, and a string containing the reason of failure on error.
|
/////
|
||||||
///
|
///// Example:
|
||||||
/// Example:
|
///// ```rust
|
||||||
/// ```rust
|
///// match get_stock_from_db_between_epochs("AAPL".into(), 123456, 123459) {
|
||||||
/// match get_stock_from_db_between_epochs("AAPL".into(), 123456, 123459) {
|
///// Ok(vals) => {
|
||||||
/// Ok(vals) => {
|
///// /* do something with the filtered values */
|
||||||
/// /* do something with the filtered values */
|
///// },
|
||||||
/// },
|
///// Err(err) => panic!("failed to get the stock value, reason: {}", err)
|
||||||
/// Err(err) => panic!("failed to get the stock value, reason: {}", err)
|
///// };
|
||||||
/// };
|
///// ```
|
||||||
/// ```
|
//pub async fn get_stock_from_db_between_epochs(
|
||||||
pub async fn get_stock_from_db_between_epochs(
|
// sql_conn: &mut tokio_postgres::Client,
|
||||||
sql_conn: &mut tokio_postgres::Client,
|
// searched_symbol: &str,
|
||||||
searched_symbol: &str,
|
// first_time_epoch: i64,
|
||||||
first_time_epoch: i64,
|
// second_time_epoch: i64,
|
||||||
second_time_epoch: i64,
|
//) -> Result<Vec<StockVal>, String> {
|
||||||
) -> Result<Vec<StockVal>, ReturnFlags> {
|
// /*
|
||||||
/*
|
// * Returns all stock values from database between two time epochs.
|
||||||
* Returns all stock values from database between two time epochs.
|
// */
|
||||||
*/
|
//
|
||||||
|
// // Query database for table.
|
||||||
// Query database for table.
|
// let mut stocks: Vec<StockVal> = Vec::new();
|
||||||
let mut stocks: Vec<StockVal> = Vec::new();
|
// match sql_conn
|
||||||
match sql_conn
|
// .query(
|
||||||
.query(
|
// format!(
|
||||||
format!(
|
// "SELECT * FROM asset_schema.{} WHERE time_epoch >= {} AND time_epoch <= {}",
|
||||||
"SELECT * FROM asset_schema.{} WHERE time_epoch >= {} AND time_epoch <= {}",
|
// searched_symbol, first_time_epoch, second_time_epoch
|
||||||
searched_symbol, first_time_epoch, second_time_epoch
|
// )
|
||||||
)
|
// .as_str(),
|
||||||
.as_str(),
|
// &[],
|
||||||
&[],
|
// )
|
||||||
)
|
// .await
|
||||||
.await
|
// {
|
||||||
{
|
// Ok(all_rows) => {
|
||||||
Ok(all_rows) => {
|
// for row in all_rows {
|
||||||
for row in all_rows {
|
// let mut val: StockVal = StockVal::default();
|
||||||
let mut val: StockVal = StockVal::default();
|
// val.id = row.get(0);
|
||||||
val.id = row.get(0);
|
// val.isin = row.get(1);
|
||||||
val.isin = row.get(1);
|
// val.time_epoch = row.get(2);
|
||||||
val.time_epoch = row.get(2);
|
// val.ask_price = row.get(3);
|
||||||
val.ask_price = row.get(3);
|
// val.bid_price = row.get(4);
|
||||||
val.bid_price = row.get(4);
|
// val.volume = row.get(5);
|
||||||
val.volume = row.get(5);
|
// stocks.push(val);
|
||||||
stocks.push(val);
|
// }
|
||||||
}
|
// Ok(stocks)
|
||||||
Ok(stocks)
|
// }
|
||||||
}
|
// Err(_) => Err(format!("Failed to retrieve stock, {}, could not be found.", searched_symbol))
|
||||||
Err(_) => Err(ReturnFlags::ServerDbSearchStockNotFound),
|
// }
|
||||||
}
|
//}
|
||||||
}
|
|
||||||
|
|
|
@ -1,28 +1,26 @@
|
||||||
use crate::common::misc::return_flags::ReturnFlags;
|
use crate::server::db::cmd::user_exists::*;
|
||||||
|
|
||||||
use crate::server::db::cmd::user_exists::user_exists;
|
|
||||||
|
|
||||||
pub async fn get_user_hash(
|
pub async fn get_user_hash(
|
||||||
sql_conn: &tokio_postgres::Client,
|
sql_conn: &tokio_postgres::Client,
|
||||||
username: &str,
|
username: &str,
|
||||||
is_email: bool,
|
is_email: bool,
|
||||||
) -> Result<String, ReturnFlags> {
|
) -> Result<String, String> {
|
||||||
/* check that user exists*/
|
/* check that user exists*/
|
||||||
if user_exists(sql_conn, username).await {
|
if user_exists(sql_conn, username).await {
|
||||||
if is_email {
|
if is_email {
|
||||||
for row in
|
for row in
|
||||||
&sql_conn.query("SELECT username, email_hash FROM accounts_schema.accounts WHERE username LIKE $1",
|
&sql_conn.query("SELECT username, email_hash_phc FROM accounts_schema.accounts WHERE username LIKE $1",
|
||||||
&[&username]).await.unwrap() {
|
&[&username]).await.unwrap() {
|
||||||
return Ok(row.get(1));
|
return Ok(row.get(1));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for row in
|
for row in
|
||||||
&sql_conn.query("SELECT username, pass_hash FROM accounts_schema.accounts WHERE username LIKE $1",
|
&sql_conn.query("SELECT username, pass_hash_phc FROM accounts_schema.accounts WHERE username LIKE $1",
|
||||||
&[&username]).await.unwrap() {
|
&[&username]).await.unwrap() {
|
||||||
return Ok(row.get(1));
|
return Ok(row.get(1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(ReturnFlags::ServerDbUserHashNotFound)
|
Err("Failed to get username hash of non-existing user.".to_string())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,6 @@
|
||||||
use crate::server::db::cmd::user_exists::user_exists;
|
use crate::server::db::cmd::user_exists::*;
|
||||||
|
|
||||||
use crate::common::misc::return_flags::ReturnFlags;
|
pub async fn get_user_id(sql_conn: &tokio_postgres::Client, username: &str) -> Result<i64, String> {
|
||||||
|
|
||||||
pub async fn get_user_id(
|
|
||||||
sql_conn: &tokio_postgres::Client,
|
|
||||||
username: &str,
|
|
||||||
) -> std::io::Result<i64> {
|
|
||||||
/* check that user exists */
|
/* check that user exists */
|
||||||
if user_exists(sql_conn, username).await {
|
if user_exists(sql_conn, username).await {
|
||||||
for row in sql_conn
|
for row in sql_conn
|
||||||
|
@ -19,8 +14,5 @@ pub async fn get_user_id(
|
||||||
return Ok(row.get(0));
|
return Ok(row.get(0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(std::io::Error::new(
|
Err(format!("Failed getting ID of username, {}", username))
|
||||||
std::io::ErrorKind::NotFound,
|
|
||||||
format!("{}", ReturnFlags::ServerGetUserIdNotFound),
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
use crate::common::misc::return_flags::ReturnFlags;
|
|
||||||
|
|
||||||
use crate::server::db::cmd::user_exists::user_exists;
|
|
||||||
|
|
||||||
pub async fn get_user_salt(
|
|
||||||
sql_conn: &tokio_postgres::Client,
|
|
||||||
username: &str,
|
|
||||||
is_email: bool,
|
|
||||||
is_server: bool,
|
|
||||||
) -> Result<String, ReturnFlags> {
|
|
||||||
/* check that user exists*/
|
|
||||||
if user_exists(sql_conn, username).await {
|
|
||||||
if is_server {
|
|
||||||
if is_email {
|
|
||||||
for row in
|
|
||||||
&sql_conn.query("SELECT username, server_email_salt FROM accounts_schema.accounts WHERE username LIKE $1",
|
|
||||||
&[&username]).await.unwrap() {
|
|
||||||
return Ok(row.get(1));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for row in
|
|
||||||
&sql_conn.query("SELECT username, server_pass_salt FROM accounts_schema.accounts WHERE username LIKE $1",
|
|
||||||
&[&username]).await.unwrap() {
|
|
||||||
return Ok(row.get(1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if is_email {
|
|
||||||
for row in
|
|
||||||
&sql_conn.query("SELECT username, client_email_salt FROM accounts_schema.accounts WHERE username LIKE $1",
|
|
||||||
&[&username]).await.unwrap() {
|
|
||||||
return Ok(row.get(1));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for row in
|
|
||||||
&sql_conn.query("SELECT username, client_pass_salt FROM accounts_schema.accounts WHERE username LIKE $1",
|
|
||||||
&[&username]).await.unwrap() {
|
|
||||||
return Ok(row.get(1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Err(ReturnFlags::ServerDbUserSaltNotFound)
|
|
||||||
}
|
|
|
@ -7,7 +7,8 @@ pub mod get_stock;
|
||||||
pub mod create_position;
|
pub mod create_position;
|
||||||
pub mod create_transaction;
|
pub mod create_transaction;
|
||||||
|
|
||||||
|
pub mod get_client_salt;
|
||||||
|
pub mod get_server_salt;
|
||||||
pub mod get_user_hash;
|
pub mod get_user_hash;
|
||||||
pub mod get_user_id;
|
pub mod get_user_id;
|
||||||
pub mod get_user_salt;
|
|
||||||
pub mod user_exists;
|
pub mod user_exists;
|
||||||
|
|
|
@ -2,11 +2,9 @@ CREATE TABLE accounts_schema.accounts (
|
||||||
id BIGSERIAL PRIMARY KEY,
|
id BIGSERIAL PRIMARY KEY,
|
||||||
username TEXT UNIQUE NOT NULL,
|
username TEXT UNIQUE NOT NULL,
|
||||||
|
|
||||||
email_hash TEXT UNIQUE NOT NULL,
|
email_hash_phc TEXT UNIQUE NOT NULL,
|
||||||
server_email_salt TEXT UNIQUE NOT NULL,
|
|
||||||
client_email_salt TEXT UNIQUE NOT NULL,
|
client_email_salt TEXT UNIQUE NOT NULL,
|
||||||
|
|
||||||
pass_hash TEXT UNIQUE NOT NULL,
|
pass_hash_phc TEXT UNIQUE NOT NULL,
|
||||||
server_pass_salt TEXT UNIQUE NOT NULL,
|
|
||||||
client_pass_salt TEXT UNIQUE NOT NULL
|
client_pass_salt TEXT UNIQUE NOT NULL
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,15 +5,12 @@ use crate::common::account::transaction::Transaction;
|
||||||
pub struct Account {
|
pub struct Account {
|
||||||
pub username: String,
|
pub username: String,
|
||||||
|
|
||||||
pub email_hash: String,
|
pub email_hash_phc: String,
|
||||||
pub server_email_salt: String,
|
|
||||||
pub client_email_salt: String,
|
pub client_email_salt: String,
|
||||||
|
|
||||||
pub pass_hash: String,
|
pub passw_hash_phc: String,
|
||||||
pub server_pass_salt: String,
|
pub client_passw_salt: String,
|
||||||
pub client_pass_salt: String,
|
|
||||||
|
|
||||||
pub is_pass: bool,
|
|
||||||
pub portfolio: Portfolio,
|
pub portfolio: Portfolio,
|
||||||
pub transactions: Vec<Transaction>,
|
pub transactions: Vec<Transaction>,
|
||||||
}
|
}
|
||||||
|
@ -22,11 +19,12 @@ impl std::fmt::Display for Account {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"({}, {}, {}, {}, {}, {:#?})",
|
"({}, {}, {}, {}, {}, {}, {:#?})",
|
||||||
self.username,
|
self.username,
|
||||||
self.email_hash,
|
self.email_hash_phc,
|
||||||
self.is_pass,
|
self.client_email_salt,
|
||||||
self.pass_hash,
|
self.passw_hash_phc,
|
||||||
|
self.client_passw_salt,
|
||||||
self.portfolio,
|
self.portfolio,
|
||||||
self.transactions
|
self.transactions
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
use crate::common::generic::company::Company;
|
|
||||||
use crate::common::generic::stock_val::StockVal;
|
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
|
||||||
pub struct GlobalState {
|
|
||||||
pub companies: HashMap<String, Company>, // symbol, company
|
|
||||||
pub stock_vals: HashMap<String, StockVal>, // symbol, stockval
|
|
||||||
}
|
|
||||||
impl std::fmt::Display for GlobalState {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "({:#?}, {:#?})", self.companies, self.stock_vals)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,2 +1 @@
|
||||||
pub mod account;
|
pub mod account;
|
||||||
pub mod global_state;
|
|
||||||
|
|
|
@ -10,10 +10,10 @@ use tokio::io::AsyncReadExt;
|
||||||
use tokio::net::TcpListener;
|
use tokio::net::TcpListener;
|
||||||
use tokio_rustls::TlsAcceptor;
|
use tokio_rustls::TlsAcceptor;
|
||||||
|
|
||||||
use crate::server::network::gen_tls_server_config::gen_tls_server_config;
|
use crate::server::network::tls::*;
|
||||||
|
|
||||||
use crate::server::db::initializer::db_connect;
|
use crate::server::db::initializer::*;
|
||||||
use crate::server::network::handle_data::handle_data;
|
use crate::server::network::handle_data::*;
|
||||||
|
|
||||||
/// Server Options
|
/// Server Options
|
||||||
#[derive(FromArgs)]
|
#[derive(FromArgs)]
|
||||||
|
@ -42,7 +42,7 @@ tokio::task_local! {
|
||||||
/// libtrader_init_log()?;
|
/// libtrader_init_log()?;
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
fn libtrader_init_log() -> std::io::Result<()> {
|
fn initialize_log() -> std::io::Result<()> {
|
||||||
use fern::colors::{Color, ColoredLevelConfig};
|
use fern::colors::{Color, ColoredLevelConfig};
|
||||||
|
|
||||||
let mut dispatch = fern::Dispatch::new().format(|out, message, record| {
|
let mut dispatch = fern::Dispatch::new().format(|out, message, record| {
|
||||||
|
@ -124,9 +124,9 @@ fn libtrader_init_log() -> std::io::Result<()> {
|
||||||
/// .await;
|
/// .await;
|
||||||
/// });
|
/// });
|
||||||
/// ```
|
/// ```
|
||||||
pub async fn libtrader_init_server() -> std::io::Result<()> {
|
pub async fn initialize() -> std::io::Result<()> {
|
||||||
// Initialize log.
|
// Initialize log.
|
||||||
libtrader_init_log()?;
|
initialize_log()?;
|
||||||
|
|
||||||
// Initialize SQL connection
|
// Initialize SQL connection
|
||||||
let sql_shared_conn = Arc::new(
|
let sql_shared_conn = Arc::new(
|
||||||
|
@ -160,6 +160,12 @@ pub async fn libtrader_init_server() -> std::io::Result<()> {
|
||||||
|
|
||||||
let listener = TcpListener::bind(&addr).await?;
|
let listener = TcpListener::bind(&addr).await?;
|
||||||
|
|
||||||
|
// create temporary file to mark server is running
|
||||||
|
let marker_path = std::path::Path::new("/tmp/paper/running");
|
||||||
|
let marker_prefix = marker_path.parent().unwrap();
|
||||||
|
std::fs::create_dir_all(marker_prefix).unwrap();
|
||||||
|
std::fs::write(marker_path, "").unwrap();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let (socket, peer_addr) = listener.accept().await?; // socket, peer_addr
|
let (socket, peer_addr) = listener.accept().await?; // socket, peer_addr
|
||||||
let acceptor = acceptor.clone();
|
let acceptor = acceptor.clone();
|
||||||
|
|
|
@ -3,3 +3,6 @@ pub mod db;
|
||||||
pub mod ds;
|
pub mod ds;
|
||||||
pub mod initializer;
|
pub mod initializer;
|
||||||
pub mod network;
|
pub mod network;
|
||||||
|
|
||||||
|
pub use crate::server::initializer::initialize;
|
||||||
|
pub use crate::server::initializer::IP;
|
||||||
|
|
|
@ -1,16 +1,11 @@
|
||||||
use data_encoding::HEXUPPER;
|
use argon2::password_hash::SaltString;
|
||||||
|
use rand_core::OsRng;
|
||||||
|
|
||||||
use crate::common::message::inst::{CommandInst, DataTransferInst};
|
use crate::common::message::*;
|
||||||
use crate::common::message::message::Message;
|
|
||||||
use crate::common::message::message_builder::message_builder;
|
|
||||||
use crate::common::message::message_type::MessageType;
|
|
||||||
|
|
||||||
use crate::server::account::authorization::acc_auth;
|
use crate::server::account;
|
||||||
use crate::server::account::creation::acc_create;
|
use crate::server::db::cmd::get_client_salt::*;
|
||||||
use crate::server::account::retrieval_portfolio::acc_retrieve_portfolio;
|
|
||||||
use crate::server::account::retrieval_transaction::acc_retrieve_transaction;
|
|
||||||
|
|
||||||
use tokio::io::AsyncWriteExt;
|
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::TcpStream;
|
||||||
use tokio_rustls::server::TlsStream;
|
use tokio_rustls::server::TlsStream;
|
||||||
|
|
||||||
|
@ -18,114 +13,51 @@ pub async fn handle_data(
|
||||||
sql_conn: &tokio_postgres::Client,
|
sql_conn: &tokio_postgres::Client,
|
||||||
socket: &mut TlsStream<TcpStream>,
|
socket: &mut TlsStream<TcpStream>,
|
||||||
buf: &[u8],
|
buf: &[u8],
|
||||||
) -> std::io::Result<()> {
|
) -> Result<(), String> {
|
||||||
/* decode incoming message */
|
/* decode incoming message */
|
||||||
let client_msg: Message = bincode::deserialize(&buf).map_err(|err| {
|
let client_msg: Message = bincode::deserialize(&buf)
|
||||||
std::io::Error::new(
|
.map_err(|err| format!("HANDLE_DATA_RCVD_INVALID_MSG: {}", err))?;
|
||||||
std::io::ErrorKind::InvalidInput,
|
|
||||||
format!("HANDLE_DATA_RCVD_INVALID_MSG: {}", err),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
//println!("This is a message: {}", client_msg);
|
|
||||||
|
|
||||||
/* handle individual client instructions */
|
/* handle individual client instructions */
|
||||||
match client_msg.instruction {
|
// a command which is executed is assumed to return an IO result.
|
||||||
_ if client_msg.instruction == CommandInst::GenHashSalt as i64 => {
|
// handle_data() would then log if writing to client failed.
|
||||||
use ring::rand::SecureRandom;
|
let cmd_client_write_result: std::io::Result<()> = match client_msg.command {
|
||||||
use ring::{digest, rand};
|
Command::GenHashSalt => {
|
||||||
let rng = rand::SystemRandom::new();
|
Message::new()
|
||||||
let mut salt = [0u8; digest::SHA512_OUTPUT_LEN / 2];
|
.command(Command::Success)
|
||||||
rng.fill(&mut salt).unwrap();
|
.data(SaltString::generate(&mut OsRng).as_str().to_string())
|
||||||
|
.send(socket)
|
||||||
let server_response: Message = message_builder(
|
|
||||||
MessageType::DataTransfer,
|
|
||||||
CommandInst::GenHashSalt as i64,
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
salt.to_vec(),
|
|
||||||
);
|
|
||||||
socket
|
|
||||||
.write_all(bincode::serialize(&server_response).unwrap().as_slice())
|
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
_ if client_msg.instruction == CommandInst::GetEmailSalt as i64 => {
|
Command::GetEmailSalt | Command::GetPasswordSalt => {
|
||||||
use crate::server::db::cmd::get_user_salt::get_user_salt;
|
let salt = get_client_salt(sql_conn, client_msg.get_data()?, client_msg.command).await;
|
||||||
match get_user_salt(
|
let mut response = Message::new();
|
||||||
sql_conn,
|
if salt.is_ok() {
|
||||||
String::from_utf8(client_msg.data).unwrap().as_str(),
|
response
|
||||||
true,
|
.command(Command::Success)
|
||||||
false,
|
.data(salt.unwrap())
|
||||||
)
|
.send(socket)
|
||||||
.await
|
.await
|
||||||
{
|
} else {
|
||||||
Ok(salt) => {
|
response.command(Command::Failure).send(socket).await
|
||||||
let server_response: Message = message_builder(
|
|
||||||
MessageType::DataTransfer,
|
|
||||||
CommandInst::GetEmailSalt as i64,
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
HEXUPPER.decode(salt.as_bytes()).unwrap(),
|
|
||||||
);
|
|
||||||
socket
|
|
||||||
.write_all(bincode::serialize(&server_response).unwrap().as_slice())
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
let server_response =
|
|
||||||
message_builder(MessageType::ServerReturn, 0, 0, 0, 0, Vec::new());
|
|
||||||
socket
|
|
||||||
.write_all(bincode::serialize(&server_response).unwrap().as_slice())
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ if client_msg.instruction == CommandInst::GetPasswordSalt as i64 => {
|
Command::Register => account::create(sql_conn, socket, &client_msg).await,
|
||||||
use crate::server::db::cmd::get_user_salt::get_user_salt;
|
Command::LoginMethod1 => account::authorize(sql_conn, socket, &client_msg).await,
|
||||||
match get_user_salt(
|
_ => {
|
||||||
sql_conn,
|
Message::new()
|
||||||
String::from_utf8(client_msg.data).unwrap().as_str(),
|
.command(Command::Failure)
|
||||||
false,
|
.data("Could not handle an unknown command!")
|
||||||
false,
|
.send(socket)
|
||||||
)
|
.await
|
||||||
.await
|
}
|
||||||
{
|
};
|
||||||
Ok(salt) => {
|
|
||||||
let server_response: Message = message_builder(
|
|
||||||
MessageType::DataTransfer,
|
|
||||||
CommandInst::GetPasswordSalt as i64,
|
|
||||||
1,
|
|
||||||
0,
|
|
||||||
1,
|
|
||||||
HEXUPPER.decode(salt.as_bytes()).unwrap(),
|
|
||||||
);
|
|
||||||
socket
|
|
||||||
.write_all(bincode::serialize(&server_response).unwrap().as_slice())
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
let server_response =
|
|
||||||
message_builder(MessageType::ServerReturn, 0, 0, 0, 0, Vec::new());
|
|
||||||
|
|
||||||
socket
|
match cmd_client_write_result {
|
||||||
.write_all(bincode::serialize(&server_response).unwrap().as_slice())
|
Ok(_) => Ok(()),
|
||||||
.await
|
Err(e) => Err(format!(
|
||||||
}
|
"Failed running command, writing to socket failed. Error: {}",
|
||||||
}
|
e
|
||||||
}
|
)),
|
||||||
_ if client_msg.instruction == CommandInst::Register as i64 => {
|
|
||||||
acc_create(sql_conn, socket, &client_msg).await
|
|
||||||
}
|
|
||||||
_ if client_msg.instruction == CommandInst::LoginMethod1 as i64 => {
|
|
||||||
acc_auth(sql_conn, socket, &client_msg).await
|
|
||||||
}
|
|
||||||
_ if client_msg.instruction == DataTransferInst::GetUserPortfolio as i64 => {
|
|
||||||
acc_retrieve_portfolio(socket, &client_msg).await
|
|
||||||
}
|
|
||||||
_ if client_msg.instruction == DataTransferInst::GetUserTransactionHist as i64 => {
|
|
||||||
acc_retrieve_transaction(sql_conn, socket, &client_msg).await
|
|
||||||
}
|
|
||||||
_ => Ok(()),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
use crate::common::jwt;
|
||||||
|
use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
|
||||||
|
|
||||||
|
pub static JWT_SECRET: &'static str = "seecreet";
|
||||||
|
|
||||||
|
/// Encodes a JWT token.
|
||||||
|
///
|
||||||
|
/// Takes in the authorized user id and expiry date of the authorization.
|
||||||
|
/// Outputs a string of the token.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// user_id - The DB entry id of the authorized user.
|
||||||
|
/// exp - The unix epoch at which the token expires
|
||||||
|
///
|
||||||
|
/// Returns: a string of the token on success, string on error.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```rust
|
||||||
|
/// let token = create_jwt_token(auth_user_id, unix_expiry_epoch).unwrap();
|
||||||
|
/// ```
|
||||||
|
pub fn create_jwt_token(user_id: i64, exp: u64) -> Result<String, String> {
|
||||||
|
let mut header = Header::default();
|
||||||
|
header.alg = Algorithm::HS512;
|
||||||
|
|
||||||
|
let claim = jwt::Claim {
|
||||||
|
user_id: user_id,
|
||||||
|
exp: exp,
|
||||||
|
};
|
||||||
|
match encode(
|
||||||
|
&header,
|
||||||
|
&claim,
|
||||||
|
&EncodingKey::from_secret(JWT_SECRET.as_bytes()),
|
||||||
|
) {
|
||||||
|
Ok(token) => Ok(token),
|
||||||
|
Err(_) => Err("Failed to create JWT token.".to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Decodes and verifies a JWT token.
|
||||||
|
///
|
||||||
|
/// Takes in an encoded JWT token and outputs it's JWTClaim.
|
||||||
|
///
|
||||||
|
/// Arguments:
|
||||||
|
/// token - the JWT token to be decoded
|
||||||
|
///
|
||||||
|
/// Returns: JWTClaim on success, nothing on error.
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
/// ```rust
|
||||||
|
/// assert_eq!(verify_jwt_token(token).unwrap(), true);
|
||||||
|
/// ```
|
||||||
|
pub fn verify_jwt_token(token: String) -> Result<jwt::Claim, String> {
|
||||||
|
let mut validation = Validation::new(Algorithm::HS512);
|
||||||
|
validation.leeway = 25;
|
||||||
|
match decode::<jwt::Claim>(
|
||||||
|
&token,
|
||||||
|
&DecodingKey::from_secret(JWT_SECRET.as_bytes()),
|
||||||
|
&validation,
|
||||||
|
) {
|
||||||
|
Ok(data) => Ok(data.claims),
|
||||||
|
Err(_) => Err("Invalid JWT token.".to_string()),
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,104 +0,0 @@
|
||||||
use crate::common::misc::return_flags::ReturnFlags;
|
|
||||||
use crate::common::sessions::jwt_claim::JWTClaim;
|
|
||||||
use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
|
|
||||||
|
|
||||||
pub static JWT_SECRET: &'static str = "seecreet";
|
|
||||||
|
|
||||||
/// Encodes a JWT token.
|
|
||||||
///
|
|
||||||
/// Takes in the authorized user id and expiry date of the authorization.
|
|
||||||
/// Outputs a string of the token.
|
|
||||||
///
|
|
||||||
/// Arguments:
|
|
||||||
/// user_id - The DB entry id of the authorized user.
|
|
||||||
/// exp - The unix epoch at which the token expires
|
|
||||||
///
|
|
||||||
/// Returns: a string of the token on success, string on error.
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
/// ```rust
|
|
||||||
/// let token = create_jwt_token(auth_user_id, unix_expiry_epoch).unwrap();
|
|
||||||
/// ```
|
|
||||||
pub fn create_jwt_token(user_id: i64, exp: u64) -> Result<String, ReturnFlags> {
|
|
||||||
let mut header = Header::default();
|
|
||||||
header.alg = Algorithm::HS512;
|
|
||||||
|
|
||||||
let claim = JWTClaim {
|
|
||||||
user_id: user_id,
|
|
||||||
exp: exp,
|
|
||||||
};
|
|
||||||
match encode(
|
|
||||||
&header,
|
|
||||||
&claim,
|
|
||||||
&EncodingKey::from_secret(JWT_SECRET.as_bytes()),
|
|
||||||
) {
|
|
||||||
Ok(token) => Ok(token),
|
|
||||||
Err(_) => Err(ReturnFlags::ServerCreateJwtTokenFailed),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Decodes and verifies a JWT token.
|
|
||||||
///
|
|
||||||
/// Takes in an encoded JWT token and outputs it's JWTClaim.
|
|
||||||
///
|
|
||||||
/// Arguments:
|
|
||||||
/// token - the JWT token to be decoded
|
|
||||||
///
|
|
||||||
/// Returns: JWTClaim on success, nothing on error.
|
|
||||||
///
|
|
||||||
/// Example:
|
|
||||||
/// ```rust
|
|
||||||
/// assert_eq!(verify_jwt_token(token).unwrap(), true);
|
|
||||||
/// ```
|
|
||||||
pub fn verify_jwt_token(token: String) -> Result<JWTClaim, ()> {
|
|
||||||
let mut validation = Validation::new(Algorithm::HS512);
|
|
||||||
validation.leeway = 25;
|
|
||||||
match decode::<JWTClaim>(
|
|
||||||
&token,
|
|
||||||
&DecodingKey::from_secret(JWT_SECRET.as_bytes()),
|
|
||||||
&validation,
|
|
||||||
) {
|
|
||||||
Ok(data) => Ok(data.claims),
|
|
||||||
Err(_) => Err(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_create_jwt_token() {
|
|
||||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
|
||||||
let start = SystemTime::now() + Duration::from_secs(4 * 60 * 60);
|
|
||||||
match create_jwt_token(1i64, start.duration_since(UNIX_EPOCH).unwrap().as_secs()) {
|
|
||||||
Ok(token) => {
|
|
||||||
let claims = verify_jwt_token(token).unwrap();
|
|
||||||
assert_eq!(claims.user_id, 1i64);
|
|
||||||
assert_eq!(
|
|
||||||
claims.exp,
|
|
||||||
start.duration_since(UNIX_EPOCH).unwrap().as_secs()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Err(_) => panic!("TEST_CREATE_JWT_TOKEN_FAILED"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_verify_jwt_token() {
|
|
||||||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
|
||||||
let start = SystemTime::now() + Duration::from_secs(4 * 60 * 60);
|
|
||||||
let token =
|
|
||||||
create_jwt_token(1i64, start.duration_since(UNIX_EPOCH).unwrap().as_secs()).unwrap();
|
|
||||||
match verify_jwt_token(token) {
|
|
||||||
Ok(claims) => {
|
|
||||||
assert_eq!(claims.user_id, 1i64);
|
|
||||||
assert_eq!(
|
|
||||||
claims.exp,
|
|
||||||
start.duration_since(UNIX_EPOCH).unwrap().as_secs()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Err(_) => panic!("TEST_VERIFY_JWT_TOKEN_FAILED"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +1,3 @@
|
||||||
pub mod gen_tls_server_config;
|
|
||||||
pub mod handle_data;
|
pub mod handle_data;
|
||||||
pub mod jwt_wrapper;
|
pub mod jwt;
|
||||||
|
pub mod tls;
|
||||||
|
|
|
@ -12,13 +12,13 @@
|
||||||
* [ ] ret data on server
|
* [ ] ret data on server
|
||||||
* [ ] transaction data retrieval
|
* [ ] transaction data retrieval
|
||||||
* [ ] split data into multiple writes
|
* [ ] split data into multiple writes
|
||||||
- add testing suite
|
|
||||||
* [ ] configure ci to run those tests
|
|
||||||
* [ ] add tests for all server functions
|
|
||||||
* [ ] add tests for all client functions
|
|
||||||
|
|
||||||
## In progress
|
## In progress
|
||||||
|
|
||||||
|
- add testing suite
|
||||||
|
* [x] configure ci to run those tests
|
||||||
|
* [ ] add tests for all server functions
|
||||||
|
* [ ] add tests for all client functions
|
||||||
|
|
||||||
## Done
|
## Done
|
||||||
|
|
||||||
|
@ -145,3 +145,21 @@
|
||||||
- server move network code to somewhere plausable.
|
- server move network code to somewhere plausable.
|
||||||
* [x] move assert_msg to message namespace
|
* [x] move assert_msg to message namespace
|
||||||
- investigate server logging doesn't include location of log method caller
|
- investigate server logging doesn't include location of log method caller
|
||||||
|
- data implmentation stuff
|
||||||
|
* [ ] implement buy & sell
|
||||||
|
* [ ] impl on client
|
||||||
|
* [ ] impl on server
|
||||||
|
* [ ] assets data retrieval
|
||||||
|
* [ ] ret data client
|
||||||
|
* [ ] ret data on server
|
||||||
|
* [ ] transaction data retrieval
|
||||||
|
* [ ] split data into multiple writes
|
||||||
|
- add testing suite
|
||||||
|
* [ ] configure ci to run those tests
|
||||||
|
* [ ] add tests for all server functions
|
||||||
|
* [ ] add tests for all client functions
|
||||||
|
- create correct modules
|
||||||
|
* [x] fix weird file naming
|
||||||
|
* [x] fix namespace naming
|
||||||
|
* [x] remove unneeded MessageType
|
||||||
|
* [x] make server return coded
|
||||||
|
|
Loading…
Reference in New Issue