move user structs into common
This commit is contained in:
parent
ed517b6dab
commit
8264ced7a6
6 changed files with 58 additions and 40 deletions
|
@ -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
19
vtse-common/src/user.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -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?,
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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(
|
|
||||||
self.0.as_bytes(),
|
|
||||||
OPSLIMIT_INTERACTIVE,
|
|
||||||
MEMLIMIT_INTERACTIVE,
|
|
||||||
)?
|
|
||||||
.as_ref()
|
|
||||||
.to_vec(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn as_bytes(&self) -> &[u8] {
|
fn try_from(password: Password) -> Result<Self, Self::Error> {
|
||||||
self.0.as_bytes()
|
Ok(Self(pwhash(
|
||||||
|
password.as_bytes(),
|
||||||
|
OPSLIMIT_INTERACTIVE,
|
||||||
|
MEMLIMIT_INTERACTIVE,
|
||||||
|
)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<[u8]> for SaltedPassword {
|
||||||
|
fn as_ref(&self) -> &[u8] {
|
||||||
|
self.0.as_ref()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue