From d32eeee33d36fa654c2863244272189b7ed59272 Mon Sep 17 00:00:00 2001 From: trivernis Date: Wed, 18 Nov 2020 18:52:37 +0100 Subject: [PATCH 1/3] Add attribute column to store custom data Signed-off-by: trivernis --- Cargo.lock | 2 ++ Cargo.toml | 2 +- src/database/mod.rs | 4 +++- src/database/models.rs | 39 +++++++++++++++++++++++++----------- src/database/users.rs | 42 +++++++++++++++++++++++---------------- src/server/http_server.rs | 4 ++++ src/server/messages.rs | 9 +++++---- 7 files changed, 67 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dd15d4a..0e893d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1151,6 +1151,8 @@ dependencies = [ "bytes", "fallible-iterator", "postgres-protocol", + "serde", + "serde_json", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 29f4f79..b936f81 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ license = "GPL-3.0" [dependencies] msgrpc = "0.1.0" -postgres = "0.17.5" +postgres = {version = "0.17.5", features = ["with-serde_json-1"]} serde_postgres = "0.2.0" dotenv = "0.15.0" serde = { version = "1.0.115", features = ["serde_derive"] } diff --git a/src/database/mod.rs b/src/database/mod.rs index 385f97b..12a4e6e 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -14,6 +14,7 @@ use crate::database::roles::Roles; use crate::database::user_roles::UserRoles; use crate::database::users::Users; use crate::utils::error::DatabaseResult; +use serde_json::Value; pub mod models; pub mod permissions; @@ -80,8 +81,9 @@ impl Database { "ADMIN".to_string(), dotenv::var(ENV_ADMIN_EMAIL).unwrap_or(DEFAULT_ADMIN_EMAIL.to_string()), dotenv::var(ENV_ADMIN_PASSWORD).unwrap_or(DEFAULT_ADMIN_PASSWORD.to_string()), + Value::Null, ) { - log::debug!("Failed to create admin user {}", e); + log::debug!("Failed to create admin user: {}", e); } else { log::debug!("Admin user created successfully!"); } diff --git a/src/database/models.rs b/src/database/models.rs index 92e25ad..55020d8 100644 --- a/src/database/models.rs +++ b/src/database/models.rs @@ -4,27 +4,28 @@ use postgres::Row; use serde::{Deserialize, Serialize}; -use zeroize::Zeroize; +use serde_json::Value; /// Record to store data in when retrieving rows from the users table -#[derive(Clone, Debug, Zeroize, Serialize)] -#[zeroize(drop)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct UserRecord { pub id: i32, pub name: String, pub email: String, pub password_hash: Vec, pub salt: Vec, + pub attributes: serde_json::Value, } impl UserRecord { - pub fn from_ordered_row(row: &Row) -> Self { + pub fn from_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), + id: row.get("id"), + name: row.get("name"), + email: row.get("email"), + password_hash: row.get("password_hash"), + salt: row.get("salt"), + attributes: row.get("attributes"), } } } @@ -62,6 +63,18 @@ pub struct UserInformation { pub id: i32, pub name: String, pub email: String, + pub attributes: Value, +} + +impl UserInformation { + pub fn from_row(row: Row) -> Self { + Self { + id: row.get("id"), + name: row.get("name"), + email: row.get("email"), + attributes: row.get("attributes"), + } + } } #[derive(Serialize, Deserialize, JsonSchema)] @@ -69,15 +82,17 @@ pub struct UserFullInformation { pub id: i32, pub name: String, pub email: String, + pub attributes: Value, pub roles: Vec, } impl From for UserInformation { fn from(record: UserRecord) -> Self { Self { - id: record.id.clone(), - name: record.name.clone(), - email: record.email.clone(), + id: record.id, + name: record.name, + email: record.email, + attributes: record.attributes, } } } diff --git a/src/database/users.rs b/src/database/users.rs index e637ecd..c292fc6 100644 --- a/src/database/users.rs +++ b/src/database/users.rs @@ -13,6 +13,7 @@ use crate::database::user_roles::UserRoles; use crate::database::{DatabaseResult, PostgresPool, Table}; use crate::utils::error::DBError; use crate::utils::{create_salt, hash_password}; +use serde_json::Value; /// Table that stores users with their email addresses and hashed passwords #[derive(Clone)] @@ -38,7 +39,8 @@ impl Table for Users { name VARCHAR(255) NOT NULL, email VARCHAR(255) UNIQUE NOT NULL, password_hash BYTEA NOT NULL, - salt BYTEA NOT NULL + salt BYTEA NOT NULL, + attributes JSON NOT NULL DEFAULT '{}' );", )?; @@ -55,6 +57,7 @@ impl Users { name: String, email: String, password: String, + attributes: Value, ) -> DatabaseResult { let mut connection = self.pool.get()?; let mut password = Zeroizing::new(password); @@ -72,10 +75,10 @@ impl Users { hash_password(password.as_bytes(), &*salt).map_err(|e| DBError::GenericError(e))?; password.zeroize(); 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()])?; + INSERT INTO users (name, email, password_hash, salt, attributes) VALUES ($1, $2, $3, $4, $5) RETURNING *; + ", &[&name, &email, &pw_hash.to_vec(), &salt.to_vec(), &attributes])?; - Ok(UserRecord::from_ordered_row(&row)) + Ok(UserRecord::from_row(row)) } pub fn update_user( @@ -83,13 +86,15 @@ impl Users { old_email: &String, name: &String, email: &String, + attributes: &Value, password: &Option, ) -> DatabaseResult { log::trace!( - "Updating user {} with new entries name: {}, email: {}", + "Updating user {} with new entries name: {}, email: {}, attributes: {:?}", old_email, name, - email + email, + attributes, ); let mut connection = self.pool.get()?; if connection @@ -115,17 +120,17 @@ impl Users { let pw_hash = hash_password(password.as_bytes(), &*salt).map_err(|e| DBError::GenericError(e))?; connection.query_one( - "UPDATE users SET name = $1, email = $2, password_hash = $3, salt = $4 WHERE email = $5 RETURNING *", - &[&name, &email, &pw_hash.to_vec(), &salt.to_vec(), &old_email], + "UPDATE users SET name = $1, email = $2, password_hash = $3, salt = $4, attributes = $5 WHERE email = $6 RETURNING *", + &[&name, &email, &pw_hash.to_vec(), &salt.to_vec(), &attributes, &old_email], )? } else { connection.query_one( - "UPDATE users SET name = $1, email = $2 WHERE email = $3 RETURNING *", - &[&name, &email, &old_email], + "UPDATE users SET name = $1, email = $2, attributes = $3 WHERE email = $4 RETURNING *", + &[&name, &email, &attributes, &old_email], )? }; - Ok(serde_postgres::from_row::(&new_record)?) + Ok(UserInformation::from_row(new_record)) } /// Returns information about a user by Id @@ -133,10 +138,13 @@ impl Users { log::trace!("Looking up entry for user with id {}", id); let mut connection = self.pool.get()?; let result = connection - .query_opt("SELECT id, name, email FROM users WHERE id = $1", &[&id])? + .query_opt( + "SELECT id, name, email, attributes FROM users WHERE id = $1", + &[&id], + )? .ok_or(DBError::RecordDoesNotExist)?; - Ok(serde_postgres::from_row::(&result)?) + Ok(UserInformation::from_row(result)) } pub fn get_user_by_email(&self, email: &String) -> DatabaseResult { @@ -144,22 +152,22 @@ impl Users { let mut connection = self.pool.get()?; let result = connection .query_opt( - "SELECT id, name, email FROM users WHERE email = $1", + "SELECT id, name, email, attributes FROM users WHERE email = $1", &[email], )? .ok_or(DBError::RecordDoesNotExist)?; - Ok(serde_postgres::from_row::(&result)?) + Ok(UserInformation::from_row(result)) } pub fn get_users(&self) -> DatabaseResult> { log::trace!("Returning a list of all users..."); let mut connection = self.pool.get()?; - let results = connection.query("SELECT id, name, email FROM users", &[])?; + let results = connection.query("SELECT id, name, email, attributes FROM users", &[])?; let mut users = Vec::new(); for result in results { - users.push(serde_postgres::from_row::(&result)?); + users.push(UserInformation::from_row(result)); } Ok(users) diff --git a/src/server/http_server.rs b/src/server/http_server.rs index a8850f3..2101035 100644 --- a/src/server/http_server.rs +++ b/src/server/http_server.rs @@ -391,6 +391,7 @@ impl UserHttpServer { id: user.id, name: user.name, email: user.email, + attributes: user.attributes, roles, })) } @@ -411,6 +412,7 @@ impl UserHttpServer { message.name.clone(), message.email.clone(), message.password.clone(), + message.attributes.clone(), )?; Ok(Response::json(&UserInformation::from(result)).with_status_code(201)) @@ -437,6 +439,7 @@ impl UserHttpServer { &email, &message.name.clone().unwrap_or(user_record.name), &message.email.clone().unwrap_or(user_record.email), + &message.attributes.clone().unwrap_or(user_record.attributes), &message.password, )?; let roles = if let Some(roles) = &message.roles { @@ -450,6 +453,7 @@ impl UserHttpServer { id: record.id, email: record.email, name: record.name, + attributes: record.attributes, roles, })) } diff --git a/src/server/messages.rs b/src/server/messages.rs index c23d590..3bc25c8 100644 --- a/src/server/messages.rs +++ b/src/server/messages.rs @@ -12,6 +12,7 @@ use zeroize::Zeroize; use crate::database::models::{CreatePermissionsEntry, Permission}; use crate::utils::error::DBError; +use serde_json::Value; #[derive(Deserialize)] pub struct TokenRequest { @@ -120,22 +121,22 @@ pub struct DeleteRoleResponse { pub role: String, } -#[derive(Deserialize, JsonSchema, Zeroize)] -#[zeroize(drop)] +#[derive(Deserialize, JsonSchema)] pub struct UpdateUserRequest { pub name: Option, pub email: Option, pub password: Option, pub roles: Option>, + pub attributes: Option, pub own_password: String, } -#[derive(Deserialize, JsonSchema, Zeroize)] -#[zeroize(drop)] +#[derive(Deserialize, JsonSchema)] pub struct CreateUserRequest { pub name: String, pub email: String, pub password: String, + pub attributes: Value, } #[derive(Deserialize, JsonSchema, Zeroize)] From 50f92ea8daebee74aee621ceb52dfaaaf477392d Mon Sep 17 00:00:00 2001 From: trivernis Date: Wed, 18 Nov 2020 18:58:33 +0100 Subject: [PATCH 2/3] Add user information to login response Signed-off-by: trivernis --- src/server/http_server.rs | 22 ++++++++++++++++------ src/server/messages.rs | 13 +++++++++++-- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/server/http_server.rs b/src/server/http_server.rs index 2101035..1713cb2 100644 --- a/src/server/http_server.rs +++ b/src/server/http_server.rs @@ -21,8 +21,8 @@ use crate::database::Database; use crate::server::documentation::RESTDocumentation; use crate::server::messages::{ CreateUserRequest, DeleteRoleResponse, DeleteUserRequest, DeleteUserResponse, ErrorMessage, - FullRoleData, LoginMessage, LogoutConfirmation, LogoutMessage, ModifyRoleRequest, - RefreshMessage, UpdateUserRequest, + FullRoleData, LoginRequest, LoginResponse, LogoutConfirmation, LogoutMessage, + ModifyRoleRequest, RefreshMessage, UpdateUserRequest, }; use crate::utils::error::DBError; use crate::utils::get_user_id_from_token; @@ -181,7 +181,7 @@ impl UserHttpServer { fn build_docs() -> Result { let mut doc = RESTDocumentation::new("/info"); - doc.add_path::( + doc.add_path::( "/login", "POST", "Returns request and refresh tokens", @@ -261,15 +261,25 @@ impl UserHttpServer { /// Handles the login part of the REST api fn login(database: &Database, request: &Request) -> HTTPResult { - let login_request: LoginMessage = + let login_request: LoginRequest = serde_json::from_str(parse_string_body(request)?.as_str()) .map_err(|e| HTTPError::new(e.to_string(), 400))?; let tokens = database .users .create_tokens(&login_request.email, &login_request.password)?; - - Ok(Response::json(&tokens).with_status_code(201)) + let user = database + .users + .get_user(get_user_id_from_token(&tokens.request_token).unwrap())?; + + Ok(Response::json(&LoginResponse { + request_token: tokens.request_token.clone(), + refresh_token: tokens.refresh_token.clone(), + request_ttl: tokens.request_ttl, + refresh_ttl: tokens.refresh_ttl, + user, + }) + .with_status_code(201)) } /// Handles the new token part of the rest api diff --git a/src/server/messages.rs b/src/server/messages.rs index 3bc25c8..4a3ee44 100644 --- a/src/server/messages.rs +++ b/src/server/messages.rs @@ -10,7 +10,7 @@ use serde::export::Formatter; use serde::{Deserialize, Serialize}; use zeroize::Zeroize; -use crate::database::models::{CreatePermissionsEntry, Permission}; +use crate::database::models::{CreatePermissionsEntry, Permission, UserInformation}; use crate::utils::error::DBError; use serde_json::Value; @@ -86,11 +86,20 @@ pub struct CreatePermissionsRequest { #[derive(Deserialize, Zeroize, JsonSchema)] #[zeroize(drop)] -pub struct LoginMessage { +pub struct LoginRequest { pub email: String, pub password: String, } +#[derive(Deserialize, Serialize, JsonSchema)] +pub struct LoginResponse { + pub request_token: String, + pub refresh_token: String, + pub request_ttl: i32, + pub refresh_ttl: i32, + pub user: UserInformation, +} + #[derive(Deserialize, Zeroize, JsonSchema)] #[zeroize(drop)] pub struct RefreshMessage { From a32510e2ffc8fd8c601dd80328c9a02892a0d50c Mon Sep 17 00:00:00 2001 From: trivernis Date: Wed, 18 Nov 2020 19:04:20 +0100 Subject: [PATCH 3/3] Change user attribute data type to JSONB Signed-off-by: trivernis --- src/database/users.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/database/users.rs b/src/database/users.rs index c292fc6..4c17352 100644 --- a/src/database/users.rs +++ b/src/database/users.rs @@ -40,7 +40,7 @@ impl Table for Users { email VARCHAR(255) UNIQUE NOT NULL, password_hash BYTEA NOT NULL, salt BYTEA NOT NULL, - attributes JSON NOT NULL DEFAULT '{}' + attributes JSONB NOT NULL DEFAULT '{}' );", )?;