move user structs into common

This commit is contained in:
Edward Shen 2021-02-08 19:37:19 -05:00
parent ed517b6dab
commit 8264ced7a6
Signed by: edward
GPG key ID: 19182661E818369F
6 changed files with 58 additions and 40 deletions

View file

@ -1,6 +1,7 @@
pub mod net; pub mod net;
pub mod operations; pub mod operations;
pub mod stock; pub mod stock;
pub mod user;
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {

19
vtse-common/src/user.rs Normal file
View file

@ -0,0 +1,19 @@
use serde::Deserialize;
use uuid::Uuid;
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default, Deserialize, sqlx::Type)]
#[sqlx(transparent)]
pub struct ApiKey(pub Uuid);
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default, Deserialize, sqlx::Type)]
#[sqlx(transparent)]
pub struct Username(String);
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default, Deserialize)]
pub struct Password(String);
impl Password {
pub fn as_bytes(&self) -> &[u8] {
self.0.as_bytes()
}
}

View file

@ -68,7 +68,7 @@ async fn handle_stream(mut socket: TcpStream, pool: PgPool) -> Result<()> {
let response = match parsed { let response = match parsed {
ServerOperation::Query(op) => match op { ServerOperation::Query(op) => match op {
QueryOperation::StockInfo { stock } => state.stock_info(stock, &pool).await?, QueryOperation::StockInfo { stock } => state.stock_info(stock, &pool).await?,
QueryOperation::User(_) => todo!(), QueryOperation::User(username) => state.user_info(username, &pool).await?,
}, },
ServerOperation::User(op) => match op { ServerOperation::User(op) => match op {
UserOperation::Login { api_key } => state.login(api_key, &pool).await?, UserOperation::Login { api_key } => state.login(api_key, &pool).await?,

View file

@ -1,7 +1,7 @@
use serde::Deserialize; use serde::Deserialize;
use vtse_common::stock::StockName; use vtse_common::stock::StockName;
use crate::user::{ApiKey, Password, Username}; use vtse_common::user::{ApiKey, Password, Username};
#[derive(Deserialize, Debug, PartialEq)] #[derive(Deserialize, Debug, PartialEq)]
#[serde(untagged)] #[serde(untagged)]

View file

@ -1,11 +1,11 @@
use crate::user::{ApiKey, Password, Username}; use crate::user::SaltedPassword;
use sodiumoxide::crypto::pwhash::argon2id13::{pwhash_verify, HashedPassword}; use sodiumoxide::crypto::pwhash::argon2id13::{pwhash_verify, HashedPassword};
use sqlx::{query, PgPool}; use sqlx::{query, PgPool};
use std::convert::TryFrom;
use thiserror::Error; use thiserror::Error;
use vtse_common::{ use vtse_common::net::{ServerResponse, UserError};
net::{ServerResponse, UserError}, use vtse_common::stock::{Stock, StockName};
stock::{Stock, StockName}, use vtse_common::user::{ApiKey, Password, Username};
};
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub(crate) enum StateError { pub(crate) enum StateError {
@ -44,7 +44,7 @@ impl AppState {
impl AppState { impl AppState {
pub(crate) async fn stock_info(&self, stock_name: StockName, pool: &PgPool) -> OperationResult { pub(crate) async fn stock_info(&self, stock_name: StockName, pool: &PgPool) -> OperationResult {
let stock = query!( let stock = query!(
"SELECT name, symbol, description, price FROM stocks WHERE name = $1::text", "SELECT name, symbol, description, price FROM stocks WHERE name = $1",
stock_name as StockName stock_name as StockName
) )
.fetch_one(pool) .fetch_one(pool)
@ -57,6 +57,10 @@ impl AppState {
stock.price, stock.price,
))) )))
} }
pub(crate) async fn user_info(&self, username: Username, pool: &PgPool) -> OperationResult {
todo!()
}
} }
/// User operation implementation /// User operation implementation
@ -88,12 +92,14 @@ impl AppState {
pool: &PgPool, pool: &PgPool,
) -> OperationResult { ) -> OperationResult {
self.assert_state(AppState::Unauthorized)?; self.assert_state(AppState::Unauthorized)?;
let salted_password =
SaltedPassword::try_from(password).map_err(|_| StateError::PasswordHash)?;
query!( query!(
"INSERT INTO users "INSERT INTO users
(username, pwhash_data) (username, pwhash_data)
VALUES ($1::varchar, $2::bytea)", VALUES ($1, $2)",
username as Username, username as Username,
password.salt().map_err(|_| StateError::PasswordHash)?.0, salted_password.as_ref(),
) )
.execute(pool) .execute(pool)
.await?; .await?;
@ -149,7 +155,7 @@ impl AppState {
) -> Result<Option<i32>, StateError> { ) -> Result<Option<i32>, StateError> {
let result = query!( let result = query!(
"SELECT user_id, pwhash_data FROM users "SELECT user_id, pwhash_data FROM users
WHERE username = $1::varchar", WHERE username = $1",
username as &Username username as &Username
) )
.fetch_one(pool) .fetch_one(pool)

View file

@ -1,36 +1,28 @@
use serde::Deserialize; use sodiumoxide::crypto::pwhash::{
use sodiumoxide::crypto::pwhash::argon2id13::{pwhash, MEMLIMIT_INTERACTIVE, OPSLIMIT_INTERACTIVE}; argon2id13::HashedPassword,
use uuid::Uuid; argon2id13::{pwhash, MEMLIMIT_INTERACTIVE, OPSLIMIT_INTERACTIVE},
};
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default, Deserialize, sqlx::Type)] use std::convert::TryFrom;
#[sqlx(transparent)] use vtse_common::user::Password;
pub(crate) struct ApiKey(pub(crate) Uuid);
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default, Deserialize, sqlx::Type)]
#[sqlx(transparent)]
pub(crate) struct Username(String);
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default, Deserialize)]
pub(crate) struct Password(String);
#[derive(sqlx::Type)] #[derive(sqlx::Type)]
#[sqlx(transparent)] #[sqlx(transparent)]
pub(crate) struct SaltedPasswordData(pub(crate) Vec<u8>); pub(crate) struct SaltedPassword(HashedPassword);
impl Password { impl TryFrom<Password> for SaltedPassword {
pub(crate) fn salt(self) -> Result<SaltedPasswordData, ()> { type Error = ();
Ok(SaltedPasswordData(
pwhash( fn try_from(password: Password) -> Result<Self, Self::Error> {
self.0.as_bytes(), Ok(Self(pwhash(
password.as_bytes(),
OPSLIMIT_INTERACTIVE, OPSLIMIT_INTERACTIVE,
MEMLIMIT_INTERACTIVE, MEMLIMIT_INTERACTIVE,
)? )?))
.as_ref() }
.to_vec(), }
))
} impl AsRef<[u8]> for SaltedPassword {
fn as_ref(&self) -> &[u8] {
pub(crate) fn as_bytes(&self) -> &[u8] { self.0.as_ref()
self.0.as_bytes()
} }
} }