Merge pull request #17 from fLotte-meets-HWR-DB/develop

Users now have attributes to store custom data
leon_tries_rust
Trivernis 4 years ago committed by GitHub
commit 8ef4e744bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

2
Cargo.lock generated

@ -1151,6 +1151,8 @@ dependencies = [
"bytes", "bytes",
"fallible-iterator", "fallible-iterator",
"postgres-protocol", "postgres-protocol",
"serde",
"serde_json",
] ]
[[package]] [[package]]

@ -9,7 +9,7 @@ license = "GPL-3.0"
[dependencies] [dependencies]
msgrpc = "0.1.0" msgrpc = "0.1.0"
postgres = "0.17.5" postgres = {version = "0.17.5", features = ["with-serde_json-1"]}
serde_postgres = "0.2.0" serde_postgres = "0.2.0"
dotenv = "0.15.0" dotenv = "0.15.0"
serde = { version = "1.0.115", features = ["serde_derive"] } serde = { version = "1.0.115", features = ["serde_derive"] }

@ -14,6 +14,7 @@ use crate::database::roles::Roles;
use crate::database::user_roles::UserRoles; use crate::database::user_roles::UserRoles;
use crate::database::users::Users; use crate::database::users::Users;
use crate::utils::error::DatabaseResult; use crate::utils::error::DatabaseResult;
use serde_json::Value;
pub mod models; pub mod models;
pub mod permissions; pub mod permissions;
@ -80,8 +81,9 @@ impl Database {
"ADMIN".to_string(), "ADMIN".to_string(),
dotenv::var(ENV_ADMIN_EMAIL).unwrap_or(DEFAULT_ADMIN_EMAIL.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()), 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 { } else {
log::debug!("Admin user created successfully!"); log::debug!("Admin user created successfully!");
} }

@ -4,27 +4,28 @@
use postgres::Row; use postgres::Row;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use zeroize::Zeroize; use serde_json::Value;
/// Record to store data in when retrieving rows from the users table /// Record to store data in when retrieving rows from the users table
#[derive(Clone, Debug, Zeroize, Serialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
#[zeroize(drop)]
pub struct UserRecord { pub struct UserRecord {
pub id: i32, pub id: i32,
pub name: String, pub name: String,
pub email: String, pub email: String,
pub password_hash: Vec<u8>, pub password_hash: Vec<u8>,
pub salt: Vec<u8>, pub salt: Vec<u8>,
pub attributes: serde_json::Value,
} }
impl UserRecord { impl UserRecord {
pub fn from_ordered_row(row: &Row) -> Self { pub fn from_row(row: Row) -> Self {
Self { Self {
id: row.get(0), id: row.get("id"),
name: row.get(1), name: row.get("name"),
email: row.get(2), email: row.get("email"),
password_hash: row.get(3), password_hash: row.get("password_hash"),
salt: row.get(4), salt: row.get("salt"),
attributes: row.get("attributes"),
} }
} }
} }
@ -62,6 +63,18 @@ pub struct UserInformation {
pub id: i32, pub id: i32,
pub name: String, pub name: String,
pub email: 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)] #[derive(Serialize, Deserialize, JsonSchema)]
@ -69,15 +82,17 @@ pub struct UserFullInformation {
pub id: i32, pub id: i32,
pub name: String, pub name: String,
pub email: String, pub email: String,
pub attributes: Value,
pub roles: Vec<Role>, pub roles: Vec<Role>,
} }
impl From<UserRecord> for UserInformation { impl From<UserRecord> for UserInformation {
fn from(record: UserRecord) -> Self { fn from(record: UserRecord) -> Self {
Self { Self {
id: record.id.clone(), id: record.id,
name: record.name.clone(), name: record.name,
email: record.email.clone(), email: record.email,
attributes: record.attributes,
} }
} }
} }

@ -13,6 +13,7 @@ use crate::database::user_roles::UserRoles;
use crate::database::{DatabaseResult, PostgresPool, Table}; use crate::database::{DatabaseResult, PostgresPool, Table};
use crate::utils::error::DBError; use crate::utils::error::DBError;
use crate::utils::{create_salt, hash_password}; use crate::utils::{create_salt, hash_password};
use serde_json::Value;
/// Table that stores users with their email addresses and hashed passwords /// Table that stores users with their email addresses and hashed passwords
#[derive(Clone)] #[derive(Clone)]
@ -38,7 +39,8 @@ impl Table for Users {
name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL, email VARCHAR(255) UNIQUE NOT NULL,
password_hash BYTEA NOT NULL, password_hash BYTEA NOT NULL,
salt BYTEA NOT NULL salt BYTEA NOT NULL,
attributes JSONB NOT NULL DEFAULT '{}'
);", );",
)?; )?;
@ -55,6 +57,7 @@ impl Users {
name: String, name: String,
email: String, email: String,
password: String, password: String,
attributes: Value,
) -> DatabaseResult<UserRecord> { ) -> DatabaseResult<UserRecord> {
let mut connection = self.pool.get()?; let mut connection = self.pool.get()?;
let mut password = Zeroizing::new(password); let mut password = Zeroizing::new(password);
@ -72,10 +75,10 @@ impl Users {
hash_password(password.as_bytes(), &*salt).map_err(|e| DBError::GenericError(e))?; hash_password(password.as_bytes(), &*salt).map_err(|e| DBError::GenericError(e))?;
password.zeroize(); password.zeroize();
let row = connection.query_one(" let row = connection.query_one("
INSERT INTO users (name, email, password_hash, salt) VALUES ($1, $2, $3, $4) RETURNING *; 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()])?; ", &[&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( pub fn update_user(
@ -83,13 +86,15 @@ impl Users {
old_email: &String, old_email: &String,
name: &String, name: &String,
email: &String, email: &String,
attributes: &Value,
password: &Option<String>, password: &Option<String>,
) -> DatabaseResult<UserInformation> { ) -> DatabaseResult<UserInformation> {
log::trace!( log::trace!(
"Updating user {} with new entries name: {}, email: {}", "Updating user {} with new entries name: {}, email: {}, attributes: {:?}",
old_email, old_email,
name, name,
email email,
attributes,
); );
let mut connection = self.pool.get()?; let mut connection = self.pool.get()?;
if connection if connection
@ -115,17 +120,17 @@ impl Users {
let pw_hash = let pw_hash =
hash_password(password.as_bytes(), &*salt).map_err(|e| DBError::GenericError(e))?; hash_password(password.as_bytes(), &*salt).map_err(|e| DBError::GenericError(e))?;
connection.query_one( connection.query_one(
"UPDATE users SET name = $1, email = $2, password_hash = $3, salt = $4 WHERE email = $5 RETURNING *", "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(), &old_email], &[&name, &email, &pw_hash.to_vec(), &salt.to_vec(), &attributes, &old_email],
)? )?
} else { } else {
connection.query_one( connection.query_one(
"UPDATE users SET name = $1, email = $2 WHERE email = $3 RETURNING *", "UPDATE users SET name = $1, email = $2, attributes = $3 WHERE email = $4 RETURNING *",
&[&name, &email, &old_email], &[&name, &email, &attributes, &old_email],
)? )?
}; };
Ok(serde_postgres::from_row::<UserInformation>(&new_record)?) Ok(UserInformation::from_row(new_record))
} }
/// Returns information about a user by Id /// Returns information about a user by Id
@ -133,10 +138,13 @@ impl Users {
log::trace!("Looking up entry for user with id {}", id); log::trace!("Looking up entry for user with id {}", id);
let mut connection = self.pool.get()?; let mut connection = self.pool.get()?;
let result = connection 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_or(DBError::RecordDoesNotExist)?;
Ok(serde_postgres::from_row::<UserInformation>(&result)?) Ok(UserInformation::from_row(result))
} }
pub fn get_user_by_email(&self, email: &String) -> DatabaseResult<UserInformation> { pub fn get_user_by_email(&self, email: &String) -> DatabaseResult<UserInformation> {
@ -144,22 +152,22 @@ impl Users {
let mut connection = self.pool.get()?; let mut connection = self.pool.get()?;
let result = connection let result = connection
.query_opt( .query_opt(
"SELECT id, name, email FROM users WHERE email = $1", "SELECT id, name, email, attributes FROM users WHERE email = $1",
&[email], &[email],
)? )?
.ok_or(DBError::RecordDoesNotExist)?; .ok_or(DBError::RecordDoesNotExist)?;
Ok(serde_postgres::from_row::<UserInformation>(&result)?) Ok(UserInformation::from_row(result))
} }
pub fn get_users(&self) -> DatabaseResult<Vec<UserInformation>> { pub fn get_users(&self) -> DatabaseResult<Vec<UserInformation>> {
log::trace!("Returning a list of all users..."); log::trace!("Returning a list of all users...");
let mut connection = self.pool.get()?; 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(); let mut users = Vec::new();
for result in results { for result in results {
users.push(serde_postgres::from_row::<UserInformation>(&result)?); users.push(UserInformation::from_row(result));
} }
Ok(users) Ok(users)

@ -21,8 +21,8 @@ use crate::database::Database;
use crate::server::documentation::RESTDocumentation; use crate::server::documentation::RESTDocumentation;
use crate::server::messages::{ use crate::server::messages::{
CreateUserRequest, DeleteRoleResponse, DeleteUserRequest, DeleteUserResponse, ErrorMessage, CreateUserRequest, DeleteRoleResponse, DeleteUserRequest, DeleteUserResponse, ErrorMessage,
FullRoleData, LoginMessage, LogoutConfirmation, LogoutMessage, ModifyRoleRequest, FullRoleData, LoginRequest, LoginResponse, LogoutConfirmation, LogoutMessage,
RefreshMessage, UpdateUserRequest, ModifyRoleRequest, RefreshMessage, UpdateUserRequest,
}; };
use crate::utils::error::DBError; use crate::utils::error::DBError;
use crate::utils::get_user_id_from_token; use crate::utils::get_user_id_from_token;
@ -181,7 +181,7 @@ impl UserHttpServer {
fn build_docs() -> Result<RESTDocumentation, serde_json::Error> { fn build_docs() -> Result<RESTDocumentation, serde_json::Error> {
let mut doc = RESTDocumentation::new("/info"); let mut doc = RESTDocumentation::new("/info");
doc.add_path::<LoginMessage, SessionTokens>( doc.add_path::<LoginRequest, LoginResponse>(
"/login", "/login",
"POST", "POST",
"Returns request and refresh tokens", "Returns request and refresh tokens",
@ -261,15 +261,25 @@ impl UserHttpServer {
/// Handles the login part of the REST api /// Handles the login part of the REST api
fn login(database: &Database, request: &Request) -> HTTPResult<Response> { fn login(database: &Database, request: &Request) -> HTTPResult<Response> {
let login_request: LoginMessage = let login_request: LoginRequest =
serde_json::from_str(parse_string_body(request)?.as_str()) serde_json::from_str(parse_string_body(request)?.as_str())
.map_err(|e| HTTPError::new(e.to_string(), 400))?; .map_err(|e| HTTPError::new(e.to_string(), 400))?;
let tokens = database let tokens = database
.users .users
.create_tokens(&login_request.email, &login_request.password)?; .create_tokens(&login_request.email, &login_request.password)?;
let user = database
Ok(Response::json(&tokens).with_status_code(201)) .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 /// Handles the new token part of the rest api
@ -391,6 +401,7 @@ impl UserHttpServer {
id: user.id, id: user.id,
name: user.name, name: user.name,
email: user.email, email: user.email,
attributes: user.attributes,
roles, roles,
})) }))
} }
@ -411,6 +422,7 @@ impl UserHttpServer {
message.name.clone(), message.name.clone(),
message.email.clone(), message.email.clone(),
message.password.clone(), message.password.clone(),
message.attributes.clone(),
)?; )?;
Ok(Response::json(&UserInformation::from(result)).with_status_code(201)) Ok(Response::json(&UserInformation::from(result)).with_status_code(201))
@ -437,6 +449,7 @@ impl UserHttpServer {
&email, &email,
&message.name.clone().unwrap_or(user_record.name), &message.name.clone().unwrap_or(user_record.name),
&message.email.clone().unwrap_or(user_record.email), &message.email.clone().unwrap_or(user_record.email),
&message.attributes.clone().unwrap_or(user_record.attributes),
&message.password, &message.password,
)?; )?;
let roles = if let Some(roles) = &message.roles { let roles = if let Some(roles) = &message.roles {
@ -450,6 +463,7 @@ impl UserHttpServer {
id: record.id, id: record.id,
email: record.email, email: record.email,
name: record.name, name: record.name,
attributes: record.attributes,
roles, roles,
})) }))
} }

@ -10,8 +10,9 @@ use serde::export::Formatter;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use zeroize::Zeroize; use zeroize::Zeroize;
use crate::database::models::{CreatePermissionsEntry, Permission}; use crate::database::models::{CreatePermissionsEntry, Permission, UserInformation};
use crate::utils::error::DBError; use crate::utils::error::DBError;
use serde_json::Value;
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct TokenRequest { pub struct TokenRequest {
@ -85,11 +86,20 @@ pub struct CreatePermissionsRequest {
#[derive(Deserialize, Zeroize, JsonSchema)] #[derive(Deserialize, Zeroize, JsonSchema)]
#[zeroize(drop)] #[zeroize(drop)]
pub struct LoginMessage { pub struct LoginRequest {
pub email: String, pub email: String,
pub password: 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)] #[derive(Deserialize, Zeroize, JsonSchema)]
#[zeroize(drop)] #[zeroize(drop)]
pub struct RefreshMessage { pub struct RefreshMessage {
@ -120,22 +130,22 @@ pub struct DeleteRoleResponse {
pub role: String, pub role: String,
} }
#[derive(Deserialize, JsonSchema, Zeroize)] #[derive(Deserialize, JsonSchema)]
#[zeroize(drop)]
pub struct UpdateUserRequest { pub struct UpdateUserRequest {
pub name: Option<String>, pub name: Option<String>,
pub email: Option<String>, pub email: Option<String>,
pub password: Option<String>, pub password: Option<String>,
pub roles: Option<Vec<String>>, pub roles: Option<Vec<String>>,
pub attributes: Option<Value>,
pub own_password: String, pub own_password: String,
} }
#[derive(Deserialize, JsonSchema, Zeroize)] #[derive(Deserialize, JsonSchema)]
#[zeroize(drop)]
pub struct CreateUserRequest { pub struct CreateUserRequest {
pub name: String, pub name: String,
pub email: String, pub email: String,
pub password: String, pub password: String,
pub attributes: Value,
} }
#[derive(Deserialize, JsonSchema, Zeroize)] #[derive(Deserialize, JsonSchema, Zeroize)]

Loading…
Cancel
Save