diff --git a/modules/users/SCsub b/modules/users/SCsub index 8ef7da90a..45df2a45b 100644 --- a/modules/users/SCsub +++ b/modules/users/SCsub @@ -8,6 +8,7 @@ sources = [ "register_types.cpp", "users/user.cpp", + "managers/user_manager.cpp", ] if ARGUMENTS.get('custom_modules_shared', 'no') == 'yes': diff --git a/modules/users/config.py b/modules/users/config.py index 3da340e80..c02e19db8 100644 --- a/modules/users/config.py +++ b/modules/users/config.py @@ -11,6 +11,7 @@ def configure(env): def get_doc_classes(): return [ "User", + "UserManager", ] diff --git a/modules/users/managers/user_manager.cpp b/modules/users/managers/user_manager.cpp new file mode 100644 index 000000000..4b5a7634f --- /dev/null +++ b/modules/users/managers/user_manager.cpp @@ -0,0 +1,124 @@ +#include "user_manager.h" + +#include "../users/user.h" + +Ref UserManager::get_user(const int id) { + return call("_get_user", id); +} +Ref UserManager::get_user_name(const String &user_name) { + return call("_get_user_name", user_name); +} +void UserManager::save_user(const Ref &user) { + call("_save_user", user); +} +Ref UserManager::create_user() { + return call("_create_user"); +} +bool UserManager::is_username_taken(const String &user_name) { + return call("_is_username_taken", user_name); +} +bool UserManager::is_email_taken(const String &email_input) { + return call("_is_email_taken", email_input); +} +bool UserManager::check_password(const Ref &user, const String &p_password) { + return call("_check_password", user, p_password); +} +void UserManager::create_password(const Ref &user, const String &p_password) { + call("_create_password", user, p_password); +} +String UserManager::hash_password(const Ref &user, const String &p_password) { + return call("_hash_password", user, p_password); +} + +Ref UserManager::_get_user(const int id) { + return Ref(); +} +Ref UserManager::_get_user_name(const String &user_name) { + return Ref(); +} +void UserManager::_save_user(Ref user) { +} +Ref UserManager::_create_user() { + Ref u; + u.instance(); + + return u; +} +bool UserManager::_is_username_taken(const String &user_name) { + return false; +} +bool UserManager::_is_email_taken(const String &email) { + return false; +} +bool UserManager::_check_password(const Ref &user, const String &p_password) { + return hash_password(user, p_password) == user->get_password_hash(); +} +void UserManager::_create_password(Ref user, const String &p_password) { + if (!user.is_valid()) { + printf("Error UserManager::create_password !user.is_valid()!\n"); + return; + } + + // todo improve a bit + user->set_pre_salt(hash_password(user, user->get_name_user_input() + user->get_email_user_input())); + user->set_post_salt(hash_password(user, user->get_email_user_input() + user->get_name_user_input())); + + user->set_password_hash(hash_password(user, p_password)); +} +String UserManager::_hash_password(const Ref &user, const String &p_password) { + ERR_FAIL_COND_V(!user.is_valid(), ""); + + String p = user->get_pre_salt() + p_password + user->get_post_salt(); + return p.sha256_text(); +} + +Vector> UserManager::get_all() { + return Vector>(); +} + +UserManager::UserManager() { +} + +UserManager::~UserManager() { +} + +void UserManager::_bind_methods() { + BIND_VMETHOD(MethodInfo(PropertyInfo(Variant::OBJECT, "ret", PROPERTY_HINT_RESOURCE_TYPE, "User"), "_get_user", PropertyInfo(Variant::INT, "id"))); + BIND_VMETHOD(MethodInfo(PropertyInfo(Variant::OBJECT, "ret", PROPERTY_HINT_RESOURCE_TYPE, "User"), "_get_user_name", PropertyInfo(Variant::STRING, "user_name"))); + BIND_VMETHOD(MethodInfo("_save_user", PropertyInfo(Variant::OBJECT, "user", PROPERTY_HINT_RESOURCE_TYPE, "User"))); + + BIND_VMETHOD(MethodInfo(PropertyInfo(Variant::OBJECT, "ret", PROPERTY_HINT_RESOURCE_TYPE, "User"), "_create_user")); + + BIND_VMETHOD(MethodInfo(PropertyInfo(Variant::BOOL, "ret"), "_is_username_taken", PropertyInfo(Variant::STRING, "user_name"))); + BIND_VMETHOD(MethodInfo(PropertyInfo(Variant::BOOL, "ret"), "_is_email_taken", PropertyInfo(Variant::STRING, "email"))); + + BIND_VMETHOD(MethodInfo(PropertyInfo(Variant::BOOL, "ret"), "_check_password", PropertyInfo(Variant::OBJECT, "user", PROPERTY_HINT_RESOURCE_TYPE, "User"), PropertyInfo(Variant::STRING, "password"))); + BIND_VMETHOD(MethodInfo("_create_password", PropertyInfo(Variant::OBJECT, "user", PROPERTY_HINT_RESOURCE_TYPE, "User"), PropertyInfo(Variant::STRING, "password"))); + BIND_VMETHOD(MethodInfo(PropertyInfo(Variant::STRING, "ret"), "_hash_password", PropertyInfo(Variant::OBJECT, "user", PROPERTY_HINT_RESOURCE_TYPE, "User"), PropertyInfo(Variant::STRING, "password"))); + + ClassDB::bind_method(D_METHOD("get_user", "id"), &UserManager::get_user); + ClassDB::bind_method(D_METHOD("get_user_name", "user_name"), &UserManager::get_user_name); + ClassDB::bind_method(D_METHOD("save_user", "user"), &UserManager::save_user); + + ClassDB::bind_method(D_METHOD("create_user"), &UserManager::create_user); + + ClassDB::bind_method(D_METHOD("is_username_taken", "user_name"), &UserManager::is_username_taken); + ClassDB::bind_method(D_METHOD("is_email_taken", "email"), &UserManager::is_email_taken); + + ClassDB::bind_method(D_METHOD("check_password", "user", "password"), &UserManager::check_password); + ClassDB::bind_method(D_METHOD("create_password", "user", "password"), &UserManager::create_password); + ClassDB::bind_method(D_METHOD("hash_password", "user", "password"), &UserManager::hash_password); + + ClassDB::bind_method(D_METHOD("_get_user", "id"), &UserManager::_get_user); + ClassDB::bind_method(D_METHOD("_get_user_name", "user_name"), &UserManager::_get_user_name); + ClassDB::bind_method(D_METHOD("_save_user", "user"), &UserManager::_save_user); + + ClassDB::bind_method(D_METHOD("_create_user"), &UserManager::_create_user); + + ClassDB::bind_method(D_METHOD("_is_username_taken", "user_name"), &UserManager::_is_username_taken); + ClassDB::bind_method(D_METHOD("_is_email_taken", "email"), &UserManager::_is_email_taken); + + ClassDB::bind_method(D_METHOD("_check_password", "user", "password"), &UserManager::_check_password); + ClassDB::bind_method(D_METHOD("_create_password", "user", "password"), &UserManager::_create_password); + ClassDB::bind_method(D_METHOD("_hash_password", "user", "password"), &UserManager::_hash_password); +} diff --git a/modules/users/managers/user_manager.h b/modules/users/managers/user_manager.h new file mode 100644 index 000000000..4ed4de36e --- /dev/null +++ b/modules/users/managers/user_manager.h @@ -0,0 +1,51 @@ +#ifndef USER_MANAGER_H +#define USER_MANAGER_H + +#include "core/reference.h" +#include "core/ustring.h" +#include "core/vector.h" + +#include "scene/main/node.h" + +class User; + +class UserManager : public Node { + GDCLASS(UserManager, Node); + +public: + Ref get_user(const int id); + Ref get_user_name(const String &user_name); + void save_user(const Ref &user); + + Ref create_user(); + + bool is_username_taken(const String &user_name); + bool is_email_taken(const String &email); + + bool check_password(const Ref &user, const String &p_password); + void create_password(const Ref &user, const String &p_password); + String hash_password(const Ref &user, const String &p_password); + + virtual Ref _get_user(const int id); + virtual Ref _get_user_name(const String &user); + virtual void _save_user(Ref user); + + virtual Ref _create_user(); + + virtual bool _is_username_taken(const String &user_name); + virtual bool _is_email_taken(const String &email); + + virtual bool _check_password(const Ref &user, const String &p_password); + virtual void _create_password(Ref user, const String &p_password); + virtual String _hash_password(const Ref &user, const String &p_password); + + Vector> get_all(); + + UserManager(); + ~UserManager(); + +protected: + static void _bind_methods(); +}; + +#endif diff --git a/modules/users/register_types.cpp b/modules/users/register_types.cpp index 65d1a0cc6..3d9888c3c 100644 --- a/modules/users/register_types.cpp +++ b/modules/users/register_types.cpp @@ -26,8 +26,11 @@ SOFTWARE. #include "users/user.h" +#include "managers/user_manager.h" + void register_users_types() { ClassDB::register_class(); + ClassDB::register_class(); } void unregister_users_types() { diff --git a/modules/users/singleton/user_db.cpp b/modules/users/singleton/user_db.cpp new file mode 100644 index 000000000..4e8dd0913 --- /dev/null +++ b/modules/users/singleton/user_db.cpp @@ -0,0 +1,820 @@ +#include "user_controller.h" + +#include "web/html/form_validator.h" +#include "web/html/html_builder.h" +#include "web/http/cookie.h" +#include "web/http/http_session.h" +#include "web/http/request.h" +#include "web/http/session_manager.h" +#include "web/http/web_permission.h" + +#include "database/database.h" +#include "database/database_manager.h" +#include "database/query_builder.h" +#include "database/query_result.h" +#include "database/table_builder.h" + +#include "crypto/hash/sha256.h" + +void UserController::handle_request_main(Request *request) { + if (_web_permission.is_valid()) { + if (_web_permission->activate(request)) { + return; + } + } + + if (request->session.is_valid()) { + Ref u = request->reference_data["user"]; + + if (u.is_valid()) { + handle_request(u, request); + + return; + } + } + + const String &segment = request->get_current_path_segment(); + + if (segment == "") { + handle_login_request_default(request); + + return; + } else if (segment == "login") { + handle_login_request_default(request); + + return; + } else if (segment == "register") { + handle_register_request_default(request); + + return; + } + + handle_login_request_default(request); +} + +void UserController::handle_login_request_default(Request *request) { + LoginRequestData data; + + if (request->get_method() == HTTP_METHOD_POST) { + // this is probbaly not needed + // it's ok for now as I need to test the validators more + Vector errors; + _login_validator->validate(request, &errors); + for (int i = 0; i < errors.size(); ++i) { + data.error_str += errors[i] + "
"; + } + // not needed end + + data.uname_val = request->get_parameter("username"); + data.pass_val = request->get_parameter("password"); + + Ref user = db_get_user(data.uname_val); + + if (user.is_valid()) { + if (!check_password(user, data.pass_val)) { + data.error_str += "Invalid username or password!"; + } else { + Ref session = request->get_or_create_session(); + + session->add("user_id", user->id); + SessionManager::get_singleton()->save_session(session); + + ::Cookie c = ::Cookie("session_id", session->session_id); + c.path = "/"; + + request->add_cookie(c); + + render_login_success(request); + + return; + } + } else { + data.error_str += "Invalid username or password!"; + } + } + + render_login_request_default(request, &data); +} + +void UserController::render_login_request_default(Request *request, LoginRequestData *data) { + HTMLBuilder b; + + b.w("Login"); + b.br(); + + { + if (data->error_str.size() != 0) { + b.div()->cls("error"); + + b.w(data->error_str); + + b.cdiv(); + } + } + + b.div()->cls("login"); + { + // todo href path helper + b.form()->method("POST")->href("/user/login"); + { + b.csrf_token(request); + + b.w("Username"); + b.br(); + b.input()->type("text")->name("username")->value(data->uname_val); + b.cinput(); + b.br(); + + b.w("Password"); + b.br(); + b.input()->type("password")->name("password"); + b.cinput(); + b.br(); + + b.input()->type("submit")->value("Send"); + b.cinput(); + } + b.cform(); + } + b.cdiv(); + + request->body += b.result; + + request->compile_and_send_body(); +} + +void UserController::handle_register_request_default(Request *request) { + RegisterRequestData data; + + if (request->get_method() == HTTP_METHOD_POST) { + Vector errors; + + _registration_validator->validate(request, &errors); + + for (int i = 0; i < errors.size(); ++i) { + data.error_str += errors[i] + "
"; + } + + data.uname_val = request->get_parameter("username"); + data.email_val = request->get_parameter("email"); + data.pass_val = request->get_parameter("password"); + data.pass_check_val = request->get_parameter("password_check"); + + // todo username length etc check + // todo pw length etc check + + if (is_username_taken(data.uname_val)) { + data.error_str += "Username already taken!
"; + } + + if (is_email_taken(data.email_val)) { + data.error_str += "Email already in use!
"; + } + + if (data.pass_val != data.pass_check_val) { + data.error_str += "The passwords did not match!
"; + } + + if (data.error_str.size() == 0) { + Ref user; + user = create_user(); + + user->name_user_input = data.uname_val; + user->email_user_input = data.email_val; + + create_password(user, data.pass_val); + db_save_user(user); + + render_register_success(request); + return; + } + } + + render_register_request_default(request, &data); +} + +void UserController::render_register_success(Request *request) { + HTMLBuilder b; + + b.div()->cls("success"); + { + b.w("Registration successful! You can now log in!"); + b.br(); + b.a()->href("/user/login"); + b.w(">> Login <<"); + b.ca(); + } + b.cdiv(); + + request->body += b.result; + + request->compile_and_send_body(); +} + +void UserController::render_register_request_default(Request *request, RegisterRequestData *data) { + HTMLBuilder b; + + b.w("Registration"); + b.br(); + + { + if (data->error_str.size() != 0) { + b.div()->cls("error"); + + b.w(data->error_str); + + b.cdiv(); + } + } + + b.div()->cls("register"); + { + // todo href path helper + b.form()->method("POST")->href("/user/register"); + { + b.csrf_token(request); + + b.w("Username"); + b.br(); + b.input()->type("text")->name("username")->value(data->uname_val); + b.cinput(); + b.br(); + + b.w("Email"); + b.br(); + b.input()->type("email")->name("email")->value(data->email_val); + b.cinput(); + b.br(); + + b.w("Password"); + b.br(); + b.input()->type("password")->name("password"); + b.cinput(); + b.br(); + + b.w("Password again"); + b.br(); + b.input()->type("password")->name("password_check"); + b.cinput(); + b.br(); + + b.input()->type("submit")->value("Register"); + b.cinput(); + } + b.cform(); + } + b.cdiv(); + + request->body += b.result; + + request->compile_and_send_body(); +} + +void UserController::render_already_logged_in_error(Request *request) { + request->body += "You are already logged in."; + + request->compile_and_send_body(); +} + +void UserController::render_login_success(Request *request) { + request->body = "Login Success!
"; + + // request->compile_and_send_body(); + request->send_redirect("/user/settings"); +} + +void UserController::handle_request(Ref &user, Request *request) { + const String &segment = request->get_current_path_segment(); + + if (segment == "") { + handle_main_page_request(user, request); + } else if (segment == "settings") { + handle_settings_request(user, request); + } else if (segment == "password_reset") { + handle_password_reset_request(user, request); + } else if (segment == "logout") { + handle_logout_request(user, request); + } else if (segment == "delete") { + handle_delete_request(user, request); + } else if (segment == "login") { + render_already_logged_in_error(request); + } else if (segment == "register") { + render_already_logged_in_error(request); + } else { + request->send_error(404); + } +} + +void UserController::handle_main_page_request(Ref &user, Request *request) { + request->body += "handle_main_page_request"; + + request->compile_and_send_body(); +} + +void UserController::handle_settings_request(Ref &user, Request *request) { + SettingsRequestData data; + + if (request->get_method() == HTTP_METHOD_POST) { + data.uname_val = request->get_parameter("username"); + data.email_val = request->get_parameter("email"); + data.pass_val = request->get_parameter("password"); + data.pass_check_val = request->get_parameter("password_check"); + + bool changed = false; + + Vector errors; + + bool valid = _profile_validator->validate(request, &errors); + + for (int i = 0; i < errors.size(); ++i) { + data.error_str += errors[i] + "
"; + } + + if (valid) { + if (data.uname_val == user->name_user_input) { + data.uname_val = ""; + } + + if (data.email_val == user->email_user_input) { + data.email_val = ""; + } + + if (data.uname_val != "") { + if (is_username_taken(data.uname_val)) { + data.error_str += "Username already taken!
"; + } else { + // todo sanitize for html special chars! + user->name_user_input = data.uname_val; + changed = true; + data.uname_val = ""; + } + } + + if (data.email_val != "") { + if (is_email_taken(data.email_val)) { + data.error_str += "Email already in use!
"; + } else { + // todo sanitize for html special chars! + // also send email + user->email_user_input = data.email_val; + changed = true; + data.email_val = ""; + } + } + + if (data.pass_val != "") { + if (data.pass_val != data.pass_check_val) { + data.error_str += "The passwords did not match!
"; + } else { + create_password(user, data.pass_val); + + changed = true; + } + } + + if (changed) { + db_save_user(user); + } + } + } + + render_settings_request(user, request, &data); +} + +void UserController::render_settings_request(Ref &user, Request *request, SettingsRequestData *data) { + HTMLBuilder b; + + b.w("Settings"); + b.br(); + + { + if (data->error_str.size() != 0) { + b.div()->cls("error"); + + b.w(data->error_str); + + b.cdiv(); + } + } + + b.div()->cls("settings"); + { + // todo href path helper + b.form()->method("POST")->href("/user/settings"); + { + b.csrf_token(request); + + b.w("Username"); + b.br(); + b.input()->type("text")->name("username")->placeholder(user->name_user_input)->value(data->uname_val); + b.cinput(); + b.br(); + + b.w("Email"); + b.br(); + b.input()->type("email")->name("email")->placeholder(user->email_user_input)->value(data->email_val); + b.cinput(); + b.br(); + + b.w("Password"); + b.br(); + b.input()->type("password")->placeholder("*******")->name("password"); + b.cinput(); + b.br(); + + b.w("Password again"); + b.br(); + b.input()->type("password")->placeholder("*******")->name("password_check"); + b.cinput(); + b.br(); + + b.input()->type("submit")->value("Save"); + b.cinput(); + } + b.cform(); + } + b.cdiv(); + + request->body += b.result; + + request->compile_and_send_body(); +} + +void UserController::handle_password_reset_request(Ref &user, Request *request) { + request->body += "handle_password_reset_request"; + + request->compile_and_send_body(); +} + +void UserController::handle_logout_request(Ref &user, Request *request) { + request->remove_cookie("session_id"); + + db_save_user(user); + + SessionManager::get_singleton()->delete_session(request->session->session_id); + request->session = nullptr; + + HTMLBuilder b; + b.w("Logout successful!"); + request->body += b.result; + + request->compile_and_send_body(); +} + +void UserController::handle_delete_request(Ref &user, Request *request) { + request->body += "handle_delete_request"; + + request->compile_and_send_body(); +} + +void UserController::create_validators() { + if (!_login_validator) { + // Login + _login_validator = new FormValidator(); + + _login_validator->new_field("username", "Username")->need_to_exist()->need_to_be_alpha_numeric()->need_minimum_length(5)->need_maximum_length(20); + FormField *pw = _login_validator->new_field("password", "Password"); + pw->need_to_exist(); + pw->need_to_have_lowercase_character()->need_to_have_uppercase_character(); + pw->need_minimum_length(5); + } + + if (!_registration_validator) { + // Registration + _registration_validator = new FormValidator(); + + _registration_validator->new_field("username", "Username")->need_to_exist()->need_to_be_alpha_numeric()->need_minimum_length(5)->need_maximum_length(20); + _registration_validator->new_field("email", "Email")->need_to_exist()->need_to_be_email(); + + FormField *pw = _registration_validator->new_field("password", "Password"); + pw->need_to_exist(); + pw->need_to_have_lowercase_character()->need_to_have_uppercase_character(); + pw->need_minimum_length(5); + + _registration_validator->new_field("password_check", "Password check")->need_to_match("password"); + + _registration_validator->new_field("email", "Email")->need_to_exist()->need_to_be_email(); + } + + if (!_profile_validator) { + _profile_validator = new FormValidator(); + + _profile_validator->new_field("username", "Username")->ignore_if_not_exists()->need_to_be_alpha_numeric()->need_minimum_length(5)->need_maximum_length(20); + _profile_validator->new_field("email", "Email")->ignore_if_not_exists()->need_to_be_email(); + + FormField *pw = _profile_validator->new_field("password", "Password"); + pw->ignore_if_not_exists(); + pw->need_to_have_lowercase_character()->need_to_have_uppercase_character(); + pw->need_minimum_length(5); + + _profile_validator->new_field("password_check", "Password check")->ignore_if_other_field_not_exists("password")->need_to_match("password"); + } +} + +Ref UserController::db_get_user(const int id) { + if (id == 0) { + return Ref(); + } + + Ref b = get_query_builder(); + + b->select("username, email, rank, pre_salt, post_salt, password_hash, banned, password_reset_token, locked"); + b->from(_table_name); + + b->where()->wp("id", id); + + b->end_command(); + + Ref r = b->run(); + + if (!r->next_row()) { + return Ref(); + } + + Ref user; + user = create_user(); + + user->id = id; + user->name_user_input = r->get_cell(0); + user->email_user_input = r->get_cell(1); + user->rank = r->get_cell_int(2); + user->pre_salt = r->get_cell(3); + user->post_salt = r->get_cell(4); + user->password_hash = r->get_cell(5); + user->banned = r->get_cell_bool(6); + user->password_reset_token = r->get_cell(7); + user->locked = r->get_cell_bool(8); + + return user; +} + +Ref UserController::db_get_user(const String &user_name_input) { + if (user_name_input == "") { + return Ref(); + } + + Ref b = get_query_builder(); + + b->select("id, email, rank, pre_salt, post_salt, password_hash, banned, password_reset_token, locked"); + b->from(_table_name); + b->where()->wp("username", user_name_input); + b->end_command(); + + Ref r = b->run(); + + if (!r->next_row()) { + return Ref(); + } + + Ref user; + user = create_user(); + + user->id = r->get_cell_int(0); + user->name_user_input = user_name_input; + user->email_user_input = r->get_cell(1); + user->rank = r->get_cell_int(2); + user->pre_salt = r->get_cell(3); + user->post_salt = r->get_cell(4); + user->password_hash = r->get_cell(5); + user->banned = r->get_cell_bool(6); + user->password_reset_token = r->get_cell(7); + user->locked = r->get_cell_bool(8); + + return user; +} + +void UserController::db_save_user(Ref &user) { + Ref b = get_query_builder(); + + if (user->id == 0) { + b->insert(_table_name, "username, email, rank, pre_salt, post_salt, password_hash, banned, password_reset_token, locked"); + + b->values(); + b->val(user->name_user_input); + b->val(user->email_user_input); + b->val(user->rank); + b->val(user->pre_salt); + b->val(user->post_salt); + b->val(user->password_hash); + b->val(user->banned); + b->val(user->password_reset_token); + b->val(user->locked); + b->cvalues(); + + b->end_command(); + b->select_last_insert_id(); + + Ref r = b->run(); + + user->id = r->get_last_insert_rowid(); + } else { + b->update(_table_name); + b->set(); + b->setp("username", user->name_user_input); + b->setp("email", user->email_user_input); + b->setp("rank", user->rank); + b->setp("pre_salt", user->pre_salt); + b->setp("post_salt", user->post_salt); + b->setp("password_hash", user->password_hash); + b->setp("banned", user->banned); + b->setp("password_reset_token", user->password_reset_token); + b->setp("locked", user->locked); + b->cset(); + b->where()->wp("id", user->id); + + // b->print(); + + b->run_query(); + } +} + +Vector> UserController::db_get_all() { + Ref b = get_query_builder(); + + b->select("id, username, email, rank, pre_salt, post_salt, password_hash, banned, password_reset_token, locked"); + b->from(_table_name); + b->end_command(); + // b->print(); + + Vector> users; + + Ref r = b->run(); + + while (r->next_row()) { + Ref user = create_user(); + + user->id = r->get_cell_int(0); + user->name_user_input = r->get_cell(1); + user->email_user_input = r->get_cell(2); + user->rank = r->get_cell_int(3); + user->pre_salt = r->get_cell(4); + user->post_salt = r->get_cell(5); + user->password_hash = r->get_cell(6); + user->banned = r->get_cell_bool(7); + user->password_reset_token = r->get_cell(8); + user->locked = r->get_cell_bool(9); + + users.push_back(user); + } + + return users; +} + +Ref UserController::create_user() { + Ref u; + u.instance(); + + return u; +} + +bool UserController::is_username_taken(const String &user_name_input) { + Ref b = get_query_builder(); + + b->select("id")->from(_table_name)->where("username")->like(user_name_input)->end_command(); + + Ref r = b->run(); + + return r->next_row(); +} +bool UserController::is_email_taken(const String &email_input) { + Ref b = get_query_builder(); + + b->select("id")->from(_table_name)->where("username")->like(email_input)->end_command(); + + Ref r = b->run(); + + return r->next_row(); +} + +bool UserController::check_password(const Ref &user, const String &p_password) { + return hash_password(user, p_password) == user->password_hash; +} + +void UserController::create_password(Ref &user, const String &p_password) { + if (!user.is_valid()) { + printf("Error UserController::create_password !user.is_valid()!\n"); + return; + } + + // todo improve a bit + user->pre_salt = hash_password(user, user->name_user_input + user->email_user_input); + user->post_salt = hash_password(user, user->email_user_input + user->name_user_input); + + user->password_hash = hash_password(user, p_password); +} + +String UserController::hash_password(const Ref &user, const String &p_password) { + if (!user.is_valid()) { + printf("Error UserController::hash_password !user.is_valid()!\n"); + return ""; + } + + Ref s = SHA256::get(); + + String p = user->pre_salt + p_password + user->post_salt; + + String c = s->compute(p); + + return c; +} + +void UserController::create_table() { + Ref tb = get_table_builder(); + + tb->create_table(_table_name); + tb->integer("id")->auto_increment()->next_row(); + tb->varchar("username", 60)->not_null()->next_row(); + tb->varchar("email", 100)->not_null()->next_row(); + tb->integer("rank")->not_null()->next_row(); + tb->varchar("pre_salt", 100)->next_row(); + tb->varchar("post_salt", 100)->next_row(); + tb->varchar("password_hash", 100)->next_row(); + tb->integer("banned")->next_row(); + tb->varchar("password_reset_token", 100)->next_row(); + tb->integer("locked")->next_row(); + tb->primary_key("id"); + tb->ccreate_table(); + tb->run_query(); + // tb->print(); +} +void UserController::drop_table() { + Ref tb = get_table_builder(); + + tb->drop_table_if_exists(_table_name)->run_query(); +} + +void UserController::create_default_entries() { + Ref user; + user = create_user(); + + user->rank = 3; + user->name_user_input = "admin"; + user->email_user_input = "admin@admin.com"; + + create_password(user, "Password"); + db_save_user(user); + + user = create_user(); + + user->rank = 1; + user->name_user_input = "user"; + user->email_user_input = "user@user.com"; + + create_password(user, "Password"); + db_save_user(user); +} + +UserController *UserController::get_singleton() { + return _self; +} + +UserController::UserController() : + WebNode() { + if (_self) { + printf("UserController::UserController(): Error! self is not null!/n"); + } + + _self = this; + + create_validators(); +} + +UserController::~UserController() { + if (_self == this) { + _self = nullptr; + } +} + +UserController *UserController::_self = nullptr; + +FormValidator *UserController::_login_validator = nullptr; +FormValidator *UserController::_registration_validator = nullptr; +FormValidator *UserController::_profile_validator = nullptr; + +String UserController::_path = "./"; +String UserController::_table_name = "users"; + +// returnring true means handled, false means continue +bool UserSessionSetupMiddleware::on_before_handle_request_main(Request *request) { + if (request->session.is_valid()) { + int user_id = request->session->get_int("user_id"); + + if (user_id != 0) { + Ref u = UserController::get_singleton()->db_get_user(user_id); + + if (u.is_valid()) { + request->reference_data["user"] = u; + } else { + // log + request->session->remove("user_id"); + } + } + } + + return false; +} + +UserSessionSetupMiddleware::UserSessionSetupMiddleware() { +} +UserSessionSetupMiddleware::~UserSessionSetupMiddleware() { +} diff --git a/modules/users/singleton/user_db.h b/modules/users/singleton/user_db.h new file mode 100644 index 000000000..f421c0a00 --- /dev/null +++ b/modules/users/singleton/user_db.h @@ -0,0 +1,137 @@ +#ifndef USER_CONTROLLER_H +#define USER_CONTROLLER_H + +#include "core/containers/vector.h" +#include "core/string.h" + +#include "web/http/web_node.h" + +#include "user.h" +#include "web/http/middleware.h" + +class Request; +class FormValidator; + +//TODO +// Break this up into multiple small webnodes (per page) +// that would make this a lot more customizable +// Only User management, save, load etc should be kept here +// and this node should be expected to be autoloaded. +// It should keep get_singleton() and c++ stuff that deal with users should expect it's presence +// they should use err macros to not crash the app though +// Rename this UserManager +// Also users are useful for not just web stuff, so don't rename them +// Make this inherit from Node instead, only inherit the web user handling from webnodes. +// Also for other webnodes that have admin functionality, break those into separate nodes. It's a lot safer, +// and I think they will work better in this setting like this. + +//Add a UserLevelWebPermission WebPermission. It should read a new projectsettings entry, and use it as an enum, that +// can be manipulated form the inspector. + +// The RBAC system can probably be removed, as WebPermissions + the editor is a lot more powerful. + +//Note move this with the user controller to it's own module. + +class UserController : public WebNode { + RCPP_OBJECT(UserController, WebNode); + +public: + void handle_request_main(Request *request); + + struct LoginRequestData { + String error_str; + String uname_val; + String pass_val; + }; + + virtual void handle_login_request_default(Request *request); + virtual void render_login_request_default(Request *request, LoginRequestData *data); + + struct RegisterRequestData { + String error_str; + String uname_val; + String email_val; + String pass_val; + String pass_check_val; + }; + + virtual void handle_register_request_default(Request *request); + virtual void render_register_request_default(Request *request, RegisterRequestData *data); + virtual void render_register_success(Request *request); + + virtual void render_already_logged_in_error(Request *request); + virtual void render_login_success(Request *request); + + virtual void handle_request(Ref &user, Request *request); + virtual void handle_main_page_request(Ref &user, Request *request); + + struct SettingsRequestData { + String error_str; + + String uname_val; + String email_val; + String pass_val; + String pass_check_val; + }; + + virtual void handle_settings_request(Ref &user, Request *request); + virtual void render_settings_request(Ref &user, Request *request, SettingsRequestData *data); + + virtual void handle_password_reset_request(Ref &user, Request *request); + virtual void handle_logout_request(Ref &user, Request *request); + virtual void handle_delete_request(Ref &user, Request *request); + + virtual void create_validators(); + + // db + + virtual Ref db_get_user(const int id); + virtual Ref db_get_user(const String &user_name_input); + virtual void db_save_user(Ref &user); + + virtual Vector> db_get_all(); + + virtual Ref create_user(); + + bool is_username_taken(const String &user_name_input); + bool is_email_taken(const String &email_input); + + virtual bool check_password(const Ref &user, const String &p_password); + virtual void create_password(Ref &user, const String &p_password); + virtual String hash_password(const Ref &user, const String &p_password); + + virtual void create_table(); + virtual void drop_table(); + virtual void create_default_entries(); + + static UserController *get_singleton(); + + UserController(); + ~UserController(); + +protected: + static UserController *_self; + + static FormValidator *_login_validator; + static FormValidator *_registration_validator; + static FormValidator *_profile_validator; + + String _file_path; + + static String _path; + static String _table_name; +}; + +// just session setup +class UserSessionSetupMiddleware : public Middleware { + RCPP_OBJECT(UserSessionSetupMiddleware, Middleware); + +public: + //returnring true means handled, false means continue + bool on_before_handle_request_main(Request *request); + + UserSessionSetupMiddleware(); + ~UserSessionSetupMiddleware(); +}; + +#endif diff --git a/modules/users/users/user.cpp b/modules/users/users/user.cpp index 5b58161cd..47a6f007d 100644 --- a/modules/users/users/user.cpp +++ b/modules/users/users/user.cpp @@ -1,63 +1,63 @@ #include "user.h" #include "core/class_db.h" -String User::get_name_user_input() { +String User::get_name_user_input() const { return _name_user_input; } void User::set_name_user_input(const String &val) { _name_user_input = val; } -String User::get_email_user_input() { +String User::get_email_user_input() const { return _email_user_input; } void User::set_email_user_input(const String &val) { _email_user_input = val; } -int User::get_rank() { +int User::get_rank() const { return _rank; } void User::set_rank(const int &val) { _rank = val; } -String User::get_pre_salt() { +String User::get_pre_salt() const { return _pre_salt; } void User::set_pre_salt(const String &val) { _pre_salt = val; } -String User::get_post_salt() { +String User::get_post_salt() const { return _post_salt; } void User::set_post_salt(const String &val) { _post_salt = val; } -String User::get_password_hash() { +String User::get_password_hash() const { return _password_hash; } void User::set_password_hash(const String &val) { _password_hash = val; } -bool User::get_banned() { +bool User::get_banned() const { return _banned; } void User::set_banned(const bool &val) { _banned = val; } -String User::get_password_reset_token() { +String User::get_password_reset_token() const { return _password_reset_token; } void User::set_password_reset_token(const String &val) { _password_reset_token = val; } -bool User::get_locked() { +bool User::get_locked() const { return _locked; } void User::set_locked(const bool &val) { diff --git a/modules/users/users/user.h b/modules/users/users/user.h index f9a62aaf5..7f9b2891f 100644 --- a/modules/users/users/user.h +++ b/modules/users/users/user.h @@ -19,31 +19,31 @@ public: PERMISSION_NONE = 0 }; - String get_name_user_input(); + String get_name_user_input() const; void set_name_user_input(const String &val); - String get_email_user_input(); + String get_email_user_input() const; void set_email_user_input(const String &val); - int get_rank(); + int get_rank() const; void set_rank(const int &val); - String get_pre_salt(); + String get_pre_salt() const; void set_pre_salt(const String &val); - String get_post_salt(); + String get_post_salt() const; void set_post_salt(const String &val); - String get_password_hash(); + String get_password_hash() const; void set_password_hash(const String &val); - bool get_banned(); + bool get_banned() const; void set_banned(const bool &val); - String get_password_reset_token(); + String get_password_reset_token() const; void set_password_reset_token(const String &val); - bool get_locked(); + bool get_locked() const; void set_locked(const bool &val); User();