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 @@
+
+
@@ -93,7 +96,7 @@
1599728150675
-
+
@@ -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
+}