diff --git a/.idea/workspace.xml b/.idea/workspace.xml index d6c29b2..49f3395 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -13,7 +13,8 @@ - + + @@ -23,6 +24,8 @@ + + @@ -105,22 +108,38 @@ - + - - + + - - + + - - + + - + + + + + + + + + + + + + + + + + diff --git a/Cargo.lock b/Cargo.lock index 8daa560..df5977a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -252,6 +252,16 @@ dependencies = [ "subtle", ] +[[package]] +name = "crypto-mac" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58bcd97a54c7ca5ce2f6eb16f6bede5b0ab5f0055fedc17d2f0b4466e21671ca" +dependencies = [ + "generic-array", + "subtle", +] + [[package]] name = "digest" version = "0.9.0" @@ -298,7 +308,11 @@ dependencies = [ "dotenv", "msgrpc", "postgres", + "rand", "redis", + "scrypt", + "serde", + "serde_postgres", ] [[package]] @@ -469,7 +483,17 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" dependencies = [ - "crypto-mac", + "crypto-mac 0.8.0", + "digest", +] + +[[package]] +name = "hmac" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deae6d9dbb35ec2c502d62b8f7b1c000a0822c3b0794ba36b3149c0a1c840dff" +dependencies = [ + "crypto-mac 0.9.1", "digest", ] @@ -727,6 +751,15 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "pbkdf2" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7170d73bf11f39b4ce1809aabc95bf5c33564cdc16fc3200ddda17a5f6e5e48b" +dependencies = [ + "crypto-mac 0.9.1", +] + [[package]] name = "percent-encoding" version = "2.1.0" @@ -820,7 +853,7 @@ dependencies = [ "byteorder", "bytes", "fallible-iterator", - "hmac", + "hmac 0.8.1", "md5", "memchr", "rand", @@ -976,11 +1009,50 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "scrypt" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3437654bbbe34054a268b3859fe41f871215069b39f0aef78808d85c37100696" +dependencies = [ + "base64", + "hmac 0.9.0", + "pbkdf2", + "rand", + "rand_core", + "sha2", + "subtle", +] + [[package]] name = "serde" version = "1.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e54c9a88f2da7238af84b5101443f0c0d0a3bbdc455e34a5c9497b1903ed55d5" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "609feed1d0a73cc36a0182a840a9b37b4a82f0b1150369f0536a9e3f2a31dc48" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_postgres" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05272a9cdfedf1b7b43cd14151bd9566db49b6396254a4cf42aa3cf8df06fe7f" +dependencies = [ + "serde", + "tokio-postgres", +] [[package]] name = "sha1" diff --git a/Cargo.toml b/Cargo.toml index 127f6b5..c89ad00 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,5 +9,9 @@ edition = "2018" [dependencies] msgrpc = {path = "./msg-rpc"} postgres = "0.17.5" +serde_postgres = "0.2.0" dotenv = "0.15.0" -redis = "0.17.0" \ No newline at end of file +redis = "0.17.0" +serde = {version = "1.0.115", features = ["serde_derive"]} +rand = "0.7.3" +scrypt = "0.4.1" \ No newline at end of file diff --git a/src/database/mod.rs b/src/database/mod.rs index cf8e835..6e8e1cd 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -8,6 +8,7 @@ use postgres::{Client, NoTls}; use redis::{RedisError, RedisResult}; use std::sync::{Arc, Mutex}; +pub mod models; pub mod permissions; pub mod role_permissions; pub mod roles; @@ -24,7 +25,7 @@ pub type RedisClient = redis::Client; pub type RedisConnection = redis::Connection; pub type PostgresError = postgres::Error; -pub trait Model { +pub trait Table { fn new( database_connection: Arc>, redis_connection: Arc>, @@ -36,6 +37,9 @@ pub trait Model { pub enum Error { Redis(RedisError), Postgres(PostgresError), + RecordExists, + ScryptError, + DeserializeError(serde_postgres::DeError), } pub type DatabaseError = Error; diff --git a/src/database/models.rs b/src/database/models.rs new file mode 100644 index 0000000..f8873a4 --- /dev/null +++ b/src/database/models.rs @@ -0,0 +1,23 @@ +use postgres::Row; +use serde::Deserialize; + +#[derive(Clone, Debug)] +pub struct UserRecord { + pub id: i32, + pub name: String, + pub email: String, + pub password_hash: Vec, + pub salt: Vec, +} + +impl UserRecord { + pub fn from_ordered_row(row: &Row) -> Self { + Self { + id: row.get(0), + name: row.get(1), + email: row.get(2), + password_hash: row.get(3), + salt: row.get(4), + } + } +} diff --git a/src/database/permissions.rs b/src/database/permissions.rs index 43f35d5..e186908 100644 --- a/src/database/permissions.rs +++ b/src/database/permissions.rs @@ -1,4 +1,4 @@ -use crate::database::{DatabaseClient, DatabaseError, DatabaseResult, Model, RedisConnection}; +use crate::database::{DatabaseClient, DatabaseError, DatabaseResult, RedisConnection, Table}; use postgres::Client; use std::sync::{Arc, Mutex}; @@ -8,7 +8,7 @@ pub struct Permissions { redis_connection: Arc>, } -impl Model for Permissions { +impl Table for Permissions { fn new( database_connection: Arc>, redis_connection: Arc>, diff --git a/src/database/role_permissions.rs b/src/database/role_permissions.rs index 8ba6aec..85f630e 100644 --- a/src/database/role_permissions.rs +++ b/src/database/role_permissions.rs @@ -1,4 +1,4 @@ -use crate::database::{DatabaseClient, DatabaseError, DatabaseResult, Model, RedisConnection}; +use crate::database::{DatabaseClient, DatabaseError, DatabaseResult, RedisConnection, Table}; use postgres::{Client, Error}; use std::sync::{Arc, Mutex}; @@ -8,7 +8,7 @@ pub struct RolePermissions { redis_connection: Arc>, } -impl Model for RolePermissions { +impl Table for RolePermissions { fn new( database_connection: Arc>, redis_connection: Arc>, diff --git a/src/database/roles.rs b/src/database/roles.rs index bb0d8ac..98508ad 100644 --- a/src/database/roles.rs +++ b/src/database/roles.rs @@ -1,5 +1,5 @@ use crate::database::role_permissions::RolePermissions; -use crate::database::{DatabaseError, DatabaseResult, Model, RedisConnection}; +use crate::database::{DatabaseError, DatabaseResult, RedisConnection, Table}; use postgres::{Client, Error}; use std::sync::{Arc, Mutex}; @@ -10,7 +10,7 @@ pub struct Roles { role_permission: RolePermissions, } -impl Model for Roles { +impl Table for Roles { fn new( database_connection: Arc>, redis_connection: Arc>, diff --git a/src/database/user_roles.rs b/src/database/user_roles.rs index e36cd27..db6b2fb 100644 --- a/src/database/user_roles.rs +++ b/src/database/user_roles.rs @@ -1,4 +1,4 @@ -use crate::database::{DatabaseError, DatabaseResult, Model, RedisConnection}; +use crate::database::{DatabaseError, DatabaseResult, RedisConnection, Table}; use postgres::{Client, Error}; use std::sync::{Arc, Mutex}; @@ -8,7 +8,7 @@ pub struct UserRoles { redis_connection: Arc>, } -impl Model for UserRoles { +impl Table for UserRoles { fn new( database_connection: Arc>, redis_connection: Arc>, diff --git a/src/database/users.rs b/src/database/users.rs index 4b13f04..b3a3d69 100644 --- a/src/database/users.rs +++ b/src/database/users.rs @@ -1,6 +1,9 @@ +use crate::database::models::UserRecord; use crate::database::user_roles::UserRoles; -use crate::database::{DatabaseError, DatabaseResult, Model, RedisConnection}; +use crate::database::{DatabaseError, DatabaseResult, RedisConnection, Table}; +use crate::utils::create_salt; use postgres::{Client, Error}; +use scrypt::ScryptParams; use std::sync::{Arc, Mutex}; #[derive(Clone)] @@ -10,7 +13,9 @@ pub struct Users { user_roles: UserRoles, } -impl Model for Users { +const SALT_LENGTH: usize = 16; + +impl Table for Users { fn new( database_connection: Arc>, redis_connection: Arc>, @@ -34,10 +39,43 @@ impl Model for Users { id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, email VARCHAR(255) UNIQUE NOT NULL, - password_hash VARCHAR(32) NOT NULL, - salt VARCHAR(16) NOT NULL + password_hash BYTEA NOT NULL, + salt BYTEA NOT NULL );", ) .map_err(|e| DatabaseError::Postgres(e)) } } + +impl Users { + pub fn create_user( + &self, + name: String, + email: String, + password: String, + ) -> DatabaseResult { + let mut connection = self.database_connection.lock().unwrap(); + + if !connection + .query("SELECT email FROM users WHERE email = $1", &[&email]) + .map_err(|e| DatabaseError::Postgres(e))? + .is_empty() + { + return Err(DatabaseError::RecordExists); + } + let salt = create_salt(SALT_LENGTH); + let mut pw_hash = [0u8; 32]; + scrypt::scrypt( + password.as_bytes(), + &salt, + &ScryptParams::recommended(), + &mut pw_hash, + ) + .map_err(|_| DatabaseError::ScryptError)?; + let row = connection.query_one(" + INSERT INTO users (name, email, password_hash, salt) VALUES ($1, $2, $3, $4) RETURNING *; + ", &[&name, &email, &pw_hash.to_vec(), &salt.to_vec()]).map_err(|e|DatabaseError::Postgres(e))?; + + Ok(UserRecord::from_ordered_row(&row)) + } +} diff --git a/src/lib.rs b/src/lib.rs index 057c83b..5c8e349 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,3 @@ +pub mod database; pub mod server; -pub mod database; \ No newline at end of file +pub mod utils; diff --git a/src/main.rs b/src/main.rs index bea523c..7cc46a7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,4 +3,15 @@ use flotte_user_management::database::Database; fn main() { let database = Database::new().unwrap(); database.init().unwrap(); + println!( + "{:?}", + database + .users + .create_user( + "John Doe".to_string(), + "johndoe@protonmail.com".to_string(), + "ttest".to_string() + ) + .unwrap() + ) } diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..77e9b13 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,9 @@ +use rand::Rng; + +pub fn create_salt(length: usize) -> [u8; 16] { + let mut rng = rand::thread_rng(); + let mut salt = [0u8; 16]; + rng.fill(&mut salt); + + salt +}