Feature: Enable/Disable login, change email

This commit is contained in:
Keanu D?lle 2020-12-22 17:47:07 +01:00
parent 5e262dd5cd
commit e3989e6108
26 changed files with 409 additions and 55 deletions

View File

@ -1,9 +1,13 @@
-- Your SQL goes here
create table users
(
id uuid default uuid_generate_v1() not null,
id uuid default uuid_generate_v1() not null
constraint pk___users___id
primary key,
password text,
email text,
constraint pk___users___id
primary key (id)
);
email text not null
);
create unique index users_email_uindex
on users (email);

View File

@ -1,29 +1,31 @@
create table members
(
entity_id uuid default uuid_generate_v1() not null
entity_id uuid default uuid_generate_v1() not null
constraint pk___members___id
primary key
constraint members_entities_entity_id_fk
references entities
on update cascade on delete cascade,
users_id uuid
users_id uuid
constraint fk___members___users_id___users
references users,
firstname text not null,
lastname text not null,
date_of_birth date,
sex smallint,
salutation text,
place_of_birth text,
academic_titles text,
references users
on update cascade on delete set null,
firstname text not null,
lastname text not null,
date_of_birth date,
sex smallint,
salutation text,
place_of_birth text,
academic_titles text,
personnel_number integer,
ui_language text,
nationality text,
entrance_date date,
birth_name text,
iban text,
bic text
ui_language text,
nationality text,
entrance_date date,
birth_name text,
iban text,
bic text
);
create unique index members_personnel_number_uindex
on members (personnel_number);

View File

@ -20,6 +20,9 @@ $( document ).ready(function() {
UnitModule.get_units();
$("#add_operation_unit_submit").on("click", UnitModule.add_unit_submit_listener);
$(".delete_unit_from_member_button").on("click", UnitModule.delete_unit);
$("#login_allowed").on("change", LoginModule.login_allowed_checkbox_listener);
$("#login_save_button").on("click", LoginModule.login_submit);
}
});
@ -419,4 +422,88 @@ UnitModule = (
delete_unit: delete_unit,
}
}
)();
)();
LoginModule = (function(){
var login_allowed_checkbox_listener = function(){
var ro = $("#login_email").prop('readonly');
if(ro){
$("#login_email").prop('readonly', false);
}else{
$("#login_email").prop('readonly', true);
}
};
var login_submit = function(){
var login_allowed = $("#login_allowed").prop('checked');
if(login_allowed){
var email = $("#login_email").val();
if(email.trim() === ""){
alert("Bitte eine Email-Adresse für den Login angeben!");
}else{
var user_id = $("#login_save_button").data("user-id");
if(user_id === ""){//New entry
var user = $();
user.email = email.trim();
user.member_id = member_id;
$.ajax({
type: "POST",
url: "/api/users/",
contentType: 'application/json',
data: JSON.stringify(user),
timeout: 3000,
error: function () {
alert("Verbindung zum Server unterbrochen!");
},
success: function (data) {
if(is_ok(data)) {
$("#login_save_button").data("user-id", data.user_id);
}
}
});
}else{ //Update existing entry
var user = $();
user.user_id = user_id;
user.email = email.trim();
user.member_id = member_id;
$.ajax({
type: "PUT",
url: "/api/users/"+user_id,
contentType: 'application/json',
data: JSON.stringify(user),
timeout: 3000,
error: function () {
alert("Verbindung zum Server unterbrochen!");
},
success: function (data) {
if(is_ok(data)) {
}
}
});
}
}
}else{
//Delete user for member
$.ajax({
type: "DELETE",
url: "/api/users/"+$("#login_save_button").data("user-id"),
contentType: 'application/json',
timeout: 3000,
error: function () {
alert("Verbindung zum Server unterbrochen!");
},
success: function (data) {
if(is_ok(data)) {
$("#login_email").val("");
}
}
});
}
}
return{
login_allowed_checkbox_listener: login_allowed_checkbox_listener,
login_submit: login_submit,
}
})();

View File

@ -256,15 +256,16 @@
<div class="card-header">Login</div>
<div class="card-body">
<div class="col">
<p>Achtung: Wird der Login deaktiviert, werden Email-Adresse und Passwort dauerhaft gelöscht und müssen nach dann ggf. neu gesetzt werden.</p>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="login_allowed" {{#if ../readonly}}disabled{{/if}}>
<input type="checkbox" class="form-check-input" {{#if login.login_allowed}}checked{{/if}} id="login_allowed" {{#if ../readonly}}disabled{{/if}}>
<label class="form-check-label" for="login_allowed">Login aktiviert</label>
</div><hr>
<div class="form-group row">
<label for="login_email" class="col-sm-3">Email-Adresse</label>
<input type="email" class="form-control col-sm-9" id="login_email" {{#if ../readonly}}readonly{{/if}}>
<input type="email" class="form-control col-sm-9" id="login_email" value="{{login.email}}" {{#if (not login.login_allowed)}}readonly{{/if}}{{#if ../readonly}}readonly{{/if}}>
</div>
{{#if (not ../readonly)}}{{#if_in_list ../../caller_permissions "modules.member_management.profile.login.edit"}}<button type="button" id="login_save_button" class="btn btn-primary" style="float: right">Speichern</button>{{/if_in_list}}{{/if}}
{{#if (not ../readonly)}}{{#if_in_list ../../caller_permissions "modules.member_management.profile.login.edit"}}<button type="button" id="login_save_button" class="btn btn-primary" data-user-id="{{login.user_id}}" style="float: right">Speichern</button>{{/if_in_list}}{{/if}}
</div>
</div>
</div>{{/if_in_list}}

View File

@ -5,6 +5,11 @@ use crate::diesel::QueryDsl;
use crate::helper::settings::Settings;
use diesel::ExpressionMethods;
use rocket::State;
use crate::schema::members::columns::entity_id;
use diesel::result::Error;
use crate::modules::member_management::model::login::Login;
use crate::schema::users::dsl::users;
use crate::schema::members::dsl::members;
pub fn get_user_by_email(email: String, settings: &State<Settings>) -> Option<User> {
use crate::schema::users::dsl::email as email1;
@ -17,3 +22,77 @@ pub fn get_user_by_email(email: String, settings: &State<Settings>) -> Option<Us
Err(_) => None,
}
}
/// Tries to get user associated to member entity id
/// Returns:
/// * Ok<Some<User>> if user found
/// * Ok<None> if no user found for member entity id
/// * Err<diesel::result::Error> if database error occured
pub fn get_user_by_member(settings: &State<Settings>, member_uuid: uuid::Uuid) -> Result<Option<User>, diesel::result::Error>{
use crate::schema::members::dsl::members;
use crate::schema::users::dsl::*;
let connection = establish_connection(settings);
let user : Result<User, diesel::result::Error> = members.inner_join(crate::schema::users::table).filter(entity_id.eq(member_uuid)).select((id, password, email)).first(&connection);
match user{
Ok(user) => Ok(Some(user)),
Err(e) => {
match e{
Error::NotFound => {Ok(None)}
_ => {
error!("Couldn't get user for member: {}", e);
Err(e)
}
}
}
}
}
pub fn add_user_to_member(settings: &State<Settings>, member_uuid: uuid::Uuid, email: String) -> Result<User, diesel::result::Error>{
let connection = establish_connection(settings);
let user : Result<User, diesel::result::Error> = diesel::insert_into(users).values(crate::schema::users::email.eq(email)).get_result(&connection);
match user{
Ok(user) => {
match diesel::update(members.filter(crate::schema::members::entity_id.eq(member_uuid))).set(crate::schema::members::users_id.eq(user.id)).execute(&connection){
Ok(_) => Ok(user),
Err(e) => {
error!("Couldn't add user to member: {}", e);
Err(e)
}
}
}
Err(e) => {
error!("Couldn't create new user: {}", e);
Err(e)
}
}
}
pub fn remove_user(settings: &State<Settings>, user_id: uuid::Uuid) -> Result<(), diesel::result::Error>{
use crate::schema::users::id;
let connection = establish_connection(settings);
match diesel::delete(users.filter(id.eq(user_id))).execute(&connection){
Ok(_) => Ok(()),
Err(e) => {
error!("Couldn't delete user: {}", e);
Err(e)
}
}
}
pub fn update_user_email(settings: &State<Settings>, user_id: uuid::Uuid, email: String) -> Result<User, diesel::result::Error>{
let connection = establish_connection(settings);
match diesel::update(users.filter(crate::schema::users::id.eq(user_id))).set(crate::schema::users::email.eq(email)).get_result(&connection){
Ok(user) => Ok(user),
Err(e) => {
error!("Couldn't update user (id: {}) email: {}", user_id, e);
Err(e)
}
}
}

View File

@ -2,5 +2,5 @@
pub struct User {
pub(crate) id: uuid::Uuid,
pub(crate) password: Option<String>,
pub(crate) email: Option<String>,
pub(crate) email: String,
}

View File

@ -130,7 +130,7 @@ mod tests {
let user = User {
id: uuid,
password: None,
email: None,
email: "test@test.de".to_string(),
};
let inserted_cookie = storage.add(
@ -155,7 +155,7 @@ mod tests {
let user = User {
id: uuid,
password: None,
email: None,
email: "test@test.de".to_string(),
};
let inserted_cookie = storage.add(
@ -181,7 +181,7 @@ mod tests {
let user = User {
id: uuid,
password: None,
email: None,
email: "test@test.de".to_string(),
};
let inserted_cookie = storage.add(
@ -204,7 +204,7 @@ mod tests {
let user = User {
id: uuid,
password: None,
email: None,
email: "test@test.de".to_string(),
};
let inserted_cookie = storage.add(

View File

@ -59,8 +59,8 @@ fn main() {
modules::welcome::view::logout::logout_get,
modules::member_management::view::selection_get::member_management_selection_get,
modules::member_management::view::selection_post::member_management_selection_post,
modules::member_management::view::core_data_get::member_management_core_data_get,
modules::member_management::view::core_data_post::member_management_core_data_post,
modules::member_management::view::profile_get::member_management_core_data_get,
modules::member_management::view::profile_post::member_management_core_data_post,
modules::member_management::view::groups_get::member_management_groups_get,
modules::api::member_management::view::member_qualifications::api_member_remove_qualification,
modules::api::member_management::view::member_qualifications::api_member_get_qualifications,
@ -85,6 +85,9 @@ fn main() {
modules::api::units::read::read_unit_list,
modules::api::units::update::put_member_in_unit,
modules::api::units::delete::delete_member_from_unit,
modules::api::users::create::create_user,
modules::api::users::delete::delete_user,
modules::api::users::update::update_user,
modules::member_management::view::personal_profile::member_management_personal_profile_get,
modules::member_management::view::personal_profile::member_management_personal_profile_post,
modules::member_management::view::create_member::member_management_add_member,

View File

@ -2,4 +2,5 @@ pub mod groups;
pub mod member_management;
pub mod members;
pub mod model;
pub mod units;
pub mod units;
pub mod users;

View File

@ -14,7 +14,7 @@ pub fn read_unit_list(
settings: State<Settings>,
cookie: SessionCookie,
) -> Result<Json<Vec<RawUnit>>, Json<ApiErrorWrapper>> {
let caller = parse_member_cookie(cookie.member)?;
let _caller = parse_member_cookie(cookie.member)?;
match get_units(&settings){
Ok(units) => Ok(Json(units)),

View File

@ -0,0 +1,41 @@
use crate::helper::settings::Settings;
use rocket::State;
use crate::helper::session_cookies::model::SessionCookie;
use crate::modules::member_management::model::login::Login;
use crate::modules::api::model::api_outcome::{ApiErrorWrapper, ApiError};
use rocket_contrib::json::Json;
use crate::modules::api::member_management::controller::parser::parse_member_cookie;
use crate::helper::check_access::check_access;
use crate::database::controller::groups::get_groups_for_member;
use rocket::request::Form;
use crate::database::controller::users::add_user_to_member;
use crate::helper::translate_diesel_error::translate_diesel;
#[derive(Queryable, Clone, Deserialize, Serialize)]
pub struct CreateUserData{
pub(crate) email: String,
pub(crate) member_id: uuid::Uuid,
}
#[post("/api/users", format = "json", data = "<create_user_data>")]
pub fn create_user(settings: State<Settings>, cookie: SessionCookie, create_user_data: Json<CreateUserData>) -> Result<Json<Login>, Json<ApiErrorWrapper>>{
let caller = parse_member_cookie(cookie.member)?;
let data = create_user_data.into_inner();
let member_groups = get_groups_for_member(&settings, data.member_id);
if caller.entity_id != data.member_id { //Skip permission check if user edits own login
if !check_access(&settings, data.member_id, member_groups, caller.entity_id, "modules.member_management.profile.login.edit".to_string()) {
return Err(Json(ApiError::new(401, "Keine Rechte Login für dieses Mitglied anzulegen!".to_string()).to_wrapper()))
}
}
match add_user_to_member(&settings, data.member_id, data.email){
Ok(user) => Ok(Json(Login{
user_id: Some(user.id),
email: Some(user.email),
login_allowed: true
})),
Err(e) => Err(translate_diesel(e))
}
}

View File

@ -0,0 +1,38 @@
use crate::helper::settings::Settings;
use rocket::State;
use crate::helper::session_cookies::model::SessionCookie;
use crate::modules::member_management::model::login::Login;
use crate::modules::api::model::api_outcome::{ApiErrorWrapper, ApiError};
use rocket_contrib::json::Json;
use crate::modules::api::member_management::controller::parser::{parse_member_cookie, parse_uuid};
use crate::helper::check_access::check_access;
use crate::database::controller::groups::get_groups_for_member;
use rocket::request::Form;
use crate::database::controller::users::{add_user_to_member, remove_user};
use crate::helper::translate_diesel_error::translate_diesel;
use crate::database::controller::members::get_members_by_user_uuid;
#[delete("/api/users/<user_id>", format = "json")]
pub fn delete_user(settings: State<Settings>, cookie: SessionCookie, user_id: String) -> Result<(), Json<ApiErrorWrapper>>{
let caller = parse_member_cookie(cookie.member)?;
let user_id = parse_uuid(user_id)?;
let member = get_members_by_user_uuid(user_id, &settings);
let member = match member.first(){
Some(member) => member,
None => return Err(Json(ApiError::new(404, "Nicht gefunden.".to_string()).to_wrapper()))
};
let member_groups = get_groups_for_member(&settings, member.entity_id);
if caller.entity_id != member.entity_id { //Skip permission check if user edits own login
if !check_access(&settings, member.entity_id, member_groups, caller.entity_id, "modules.member_management.profile.login.edit".to_string()) {
return Err(Json(ApiError::new(401, "Keine Rechte Login für dieses Mitglied anzulegen!".to_string()).to_wrapper()))
}
}
match remove_user(&settings, user_id){
Ok(_) => Ok(()),
Err(e) => Err(translate_diesel(e))
}
}

View File

@ -0,0 +1,4 @@
pub mod create;
pub mod delete;
pub mod update;
pub mod read;

View File

View File

@ -0,0 +1,48 @@
use crate::helper::settings::Settings;
use rocket::State;
use crate::helper::session_cookies::model::SessionCookie;
use crate::modules::member_management::model::login::Login;
use crate::modules::api::model::api_outcome::{ApiErrorWrapper, ApiError};
use rocket_contrib::json::Json;
use crate::modules::api::member_management::controller::parser::{parse_member_cookie, parse_uuid};
use crate::helper::check_access::check_access;
use crate::database::controller::groups::get_groups_for_member;
use rocket::request::Form;
use crate::database::controller::users::{add_user_to_member, update_user_email};
use crate::helper::translate_diesel_error::translate_diesel;
#[derive(Queryable, Clone, Deserialize, Serialize)]
pub struct UpdateUserData{
pub(crate) user_id: uuid::Uuid,
pub(crate) email: String,
pub(crate) member_id: uuid::Uuid,
}
#[put("/api/users/<user_id>", format = "json", data = "<update_user_data>")]
pub fn update_user(settings: State<Settings>, cookie: SessionCookie, user_id: String, update_user_data: Json<UpdateUserData>) -> Result<Json<Login>, Json<ApiErrorWrapper>>{
let caller = parse_member_cookie(cookie.member)?;
let data = update_user_data.into_inner();
let user_id = parse_uuid(user_id)?;
if user_id != data.user_id{
return Err(Json(ApiError::new(400, "User id's doesn't match".to_string()).to_wrapper()))
}
let member_groups = get_groups_for_member(&settings, data.member_id);
if caller.entity_id != data.member_id { //Skip permission check if user edits own login
if !check_access(&settings, data.member_id, member_groups, caller.entity_id, "modules.member_management.profile.login.edit".to_string()) {
return Err(Json(ApiError::new(401, "Keine Rechte Login für dieses Mitglied zu verändern!".to_string()).to_wrapper()))
}
}
match update_user_email(&settings, user_id, data.email){
Ok(user) => Ok(Json(Login{
user_id: Some(user.id),
email: Some(user.email),
login_allowed: true
})),
Err(e) => Err(translate_diesel(e))
}
}

View File

@ -0,0 +1,31 @@
use crate::database::model::users::User;
use crate::modules::member_management::model::login::Login;
use diesel::result::Error;
impl From<Result<Option<User>, diesel::result::Error>> for Login{
fn from(u: Result<Option<User>, Error>) -> Self {
match u{
Ok(user_option) => match user_option{
Some(user) => {
Login{
user_id: Some(user.id),
email: Some(user.email),
login_allowed: true
}
},
None => {
Login{
user_id: None,
email: None,
login_allowed: false
}
}
},
Err(e) => Login{
user_id: None,
email: None,
login_allowed: false
}
}
}
}

View File

@ -5,6 +5,7 @@ use crate::database::controller::member_qualifications::get_qualifcation_categor
use crate::helper::age_calculator::calculate_age;
use crate::helper::caller_permissions::get_permissions_for_context;
use crate::helper::check_access::check_access_legacy;
use crate::modules::member_management::model::login::Login;
use crate::helper::settings::Settings;
use crate::helper::sitebuilder::model::general::{Footer, Header, Script, Stylesheet};
use crate::helper::sitebuilder::model::sidebar::Sidebar;
@ -16,6 +17,7 @@ use rocket::http::Status;
use rocket::State;
use rocket_contrib::templates::Template;
use crate::database::controller::units_members::get_members_units;
use crate::database::controller::users::get_user_by_member;
pub fn handle_view(
settings: &State<Settings>,
@ -49,9 +51,11 @@ pub fn handle_view(
let caller_permissions = get_permissions_for_context(settings, &member, &member);
let units = match get_members_units(settings, member.entity_id){
Ok(units) => units,
Err(e) => return Err(Status::InternalServerError)
Err(_) => return Err(Status::InternalServerError)
};
let login = Login::from(get_user_by_member(settings, member.entity_id));
return Ok(Template::render(
"module_member_management_personal_profile",
MemberModuleProfile {
@ -67,6 +71,7 @@ pub fn handle_view(
sidebar,
caller_permissions,
units,
login
},
));
}
@ -104,9 +109,11 @@ pub fn handle_edit(
let units = match get_members_units(settings, member.entity_id){
Ok(units) => units,
Err(e) => return Err(Status::InternalServerError)
Err(_) => return Err(Status::InternalServerError)
};
let login = Login::from(get_user_by_member(settings, member.entity_id));
return Ok(Template::render(
"module_member_management_personal_profile",
MemberModuleProfile {
@ -121,7 +128,8 @@ pub fn handle_edit(
readonly: false,
sidebar,
caller_permissions,
units
units,
login
},
));
}

View File

@ -9,6 +9,7 @@ use crate::helper::settings::Settings;
use crate::helper::sitebuilder::model::general::{Footer, Header, Script, Stylesheet};
use crate::helper::sitebuilder::model::sidebar::Sidebar;
use crate::modules::member_management::model::member::Member;
use crate::modules::member_management::model::login::Login;
use crate::modules::member_management::model::member_module::{
EmptyMemberModuleProfile, MemberModuleProfile,
};
@ -16,6 +17,7 @@ use rocket::http::Status;
use rocket::State;
use rocket_contrib::templates::Template;
use crate::database::controller::units_members::get_members_units;
use crate::database::controller::users::get_user_by_member;
/// No member submitted, only show searchbar
pub fn handle_empty(caller: Member) -> Result<Template, Status> {
@ -86,9 +88,11 @@ pub fn handle_view(
let units = match get_members_units(settings, member.entity_id){
Ok(units) => units,
Err(e) => return Err(Status::InternalServerError)
Err(_) => return Err(Status::InternalServerError)
};
let login = Login::from(get_user_by_member(settings, member.entity_id));
return Ok(Template::render(
"module_member_management_profile",
MemberModuleProfile {
@ -104,6 +108,7 @@ pub fn handle_view(
sidebar,
caller_permissions,
units,
login,
},
));
}
@ -155,9 +160,11 @@ pub fn handle_edit(
let units = match get_members_units(settings, member.entity_id){
Ok(units) => units,
Err(e) => return Err(Status::InternalServerError)
Err(_) => return Err(Status::InternalServerError)
};
let login = Login::from(get_user_by_member(settings, member.entity_id));
return Ok(Template::render(
"module_member_management_profile",
MemberModuleProfile {
@ -172,7 +179,8 @@ pub fn handle_edit(
readonly: false,
sidebar,
caller_permissions,
units
units,
login
},
));
}

View File

@ -6,4 +6,5 @@ pub mod member_profile;
pub mod member_trait;
pub mod render;
pub mod member_personal_profile;
pub mod create_member;
pub mod create_member;
pub mod login_trait;

View File

@ -0,0 +1,6 @@
#[derive(Serialize)]
pub struct Login{
pub(crate) user_id: Option<uuid::Uuid>,
pub(crate) email: Option<String>,
pub(crate) login_allowed: bool,
}

View File

@ -8,6 +8,7 @@ use crate::modules::member_management::model::member::{Member, MemberWithAccess}
use crate::modules::member_management::model::qualifications::QualificationCategory;
use std::collections::HashMap;
use crate::database::model::units::Unit;
use crate::modules::member_management::model::login::Login;
#[derive(Serialize)]
pub struct MemberModuleSelection {
@ -39,6 +40,7 @@ pub struct MemberModuleProfile {
pub sidebar: Sidebar,
pub caller_permissions: Vec<String>,
pub units: Vec<Unit>,
pub login: Login,
}
#[derive(Serialize)]

View File

@ -4,3 +4,4 @@ pub mod licenses;
pub mod member;
pub mod member_module;
pub mod qualifications;
pub mod login;

View File

@ -1,5 +1,5 @@
pub mod core_data_get;
pub mod core_data_post;
pub mod profile_get;
pub mod profile_post;
pub mod groups_get;
pub mod selection_get;
pub mod selection_post;

View File

@ -128,16 +128,6 @@ table! {
}
}
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
locked_logins (email) {
email -> Text,
locked_until -> Timestamp,
}
}
table! {
use diesel::sql_types::*;
use diesel_geometry::sql_types::*;
@ -285,7 +275,7 @@ table! {
users (id) {
id -> Uuid,
password -> Nullable<Text>,
email -> Nullable<Text>,
email -> Text,
}
}
@ -347,7 +337,6 @@ allow_tables_to_appear_in_same_query!(
groups_entities,
license_categories,
licenses_members,
locked_logins,
login_attempts,
members,
members_roles,