diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..6ed36dd --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/backend/.cargo/config.toml b/backend/.cargo/config.toml new file mode 100644 index 0000000..33306ab --- /dev/null +++ b/backend/.cargo/config.toml @@ -0,0 +1,3 @@ +[build] +rustflags = ["--cfg", "tokio_unstable"] +incremental = true diff --git a/backend/.env b/backend/.env new file mode 100644 index 0000000..c773194 --- /dev/null +++ b/backend/.env @@ -0,0 +1 @@ +DATABASE_URL=sqlite.db \ No newline at end of file diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..a792953 --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,133 @@ +# Created by https://www.toptal.com/developers/gitignore/api/clion,rust +# Edit at https://www.toptal.com/developers/gitignore?templates=clion,rust + +### CLion ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### CLion Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +# https://plugins.jetbrains.com/plugin/7973-sonarlint +.idea/**/sonarlint/ + +# SonarQube Plugin +# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin +.idea/**/sonarIssues.xml + +# Markdown Navigator plugin +# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced +.idea/**/markdown-navigator.xml +.idea/**/markdown-navigator-enh.xml +.idea/**/markdown-navigator/ + +# Cache file creation bug +# See https://youtrack.jetbrains.com/issue/JBR-2257 +.idea/$CACHE_FILE$ + +# CodeStream plugin +# https://plugins.jetbrains.com/plugin/12206-codestream +.idea/codestream.xml + +# Azure Toolkit for IntelliJ plugin +# https://plugins.jetbrains.com/plugin/8053-azure-toolkit-for-intellij +.idea/**/azureSettings.xml + +### Rust ### +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +# End of https://www.toptal.com/developers/gitignore/api/clion,rust diff --git a/backend/.idea/.gitignore b/backend/.idea/.gitignore deleted file mode 100644 index 13566b8..0000000 --- a/backend/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/backend/.idea/.name b/backend/.idea/.name deleted file mode 100644 index 98cd9e7..0000000 --- a/backend/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -backend \ No newline at end of file diff --git a/backend/.idea/backend.iml b/backend/.idea/backend.iml index f08604b..c254557 100644 --- a/backend/.idea/backend.iml +++ b/backend/.idea/backend.iml @@ -1,2 +1,11 @@ - \ No newline at end of file + + + + + + + + + + \ No newline at end of file diff --git a/backend/.idea/cmake.xml b/backend/.idea/cmake.xml deleted file mode 100644 index f5374c0..0000000 --- a/backend/.idea/cmake.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/backend/.idea/dataSources.xml b/backend/.idea/dataSources.xml deleted file mode 100644 index 78eab0c..0000000 --- a/backend/.idea/dataSources.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - sqlite.xerial - true - org.sqlite.JDBC - jdbc:sqlite:$PROJECT_DIR$/../run/sqlite.db - $ProjectFileDir$ - - - \ No newline at end of file diff --git a/backend/.idea/file_server.iml b/backend/.idea/file_server.iml deleted file mode 100644 index f08604b..0000000 --- a/backend/.idea/file_server.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/backend/.idea/misc.xml b/backend/.idea/misc.xml deleted file mode 100644 index 72b029a..0000000 --- a/backend/.idea/misc.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/backend/.idea/modules.xml b/backend/.idea/modules.xml index 064f412..e066844 100644 --- a/backend/.idea/modules.xml +++ b/backend/.idea/modules.xml @@ -2,7 +2,7 @@ - + \ No newline at end of file diff --git a/backend/.idea/runConfigurations/backend.xml b/backend/.idea/runConfigurations/backend.xml deleted file mode 100644 index 0e1cbe1..0000000 --- a/backend/.idea/runConfigurations/backend.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/backend/CMakeLists.txt b/backend/CMakeLists.txt deleted file mode 100644 index 6bc1a4f..0000000 --- a/backend/CMakeLists.txt +++ /dev/null @@ -1,96 +0,0 @@ -cmake_minimum_required(VERSION 3.21) - -if (WIN32 AND (NOT VCPKG_TARGET_TRIPLET)) - set(VCPKG_TARGET_TRIPLET x64-windows-static) -endif (WIN32 AND (NOT VCPKG_TARGET_TRIPLET)) -#set(VCPKG_LIBRARY_LINKAGE static) - -project(backend) - -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_CXX_STANDARD_REQUIRED YES) - -add_executable(backend - src/main.cpp - - src/dto/dto.h - src/dto/responses.cpp - - src/db/db.h - src/db/db.cpp - - src/controllers/controllers.h - src/controllers/admin.cpp - src/controllers/user.cpp - - src/controllers/fs/fs_routes.cpp - src/controllers/fs/fs_functions.cpp - - src/controllers/auth/auth_common.cpp - src/controllers/auth/auth_basic.cpp - src/controllers/auth/auth_2fa.cpp - src/controllers/auth/auth_gitlab.cpp - - src/filters/filters.h - src/filters/filters.cpp - - model/Inode.cc - model/Inode.h - model/Tokens.cc - model/Tokens.h - model/User.cc - model/User.h - - SMTPMail-drogon-master/SMTPMail.cc -) - - - -find_package(Drogon CONFIG REQUIRED) -find_package(OpenSSL REQUIRED) -find_package(OpenCV CONFIG REQUIRED) -find_package(kubazip CONFIG REQUIRED) -find_path(JWT_CPP_INCLUDE_DIRS "jwt-cpp/base.h") -find_path(BOTAN_INCLUDE_DIRS "botan/botan.h") -find_path(QR_INCLUDE_DIRS "qrcodegen.hpp") -find_library(BOTAN_LIBRARY NAMES botan-2 botan) -find_library(QR_LIBRARY nayuki-qr-code-generator) - -target_include_directories(backend PRIVATE - src - model - shl - SMTPMail-drogon-master - ${OpenCV_INCLUDE_DIRS} - ${JWT_CPP_INCLUDE_DIRS} - ${BOTAN_INCLUDE_DIRS} - ${QR_INCLUDE_DIRS} -) - -target_link_libraries(backend - Drogon::Drogon - OpenSSL::SSL - kubazip::kubazip - ${OpenCV_LIBS} - ${BOTAN_LIBRARY} - ${QR_LIBRARY} -) - -set_property(TARGET backend PROPERTY MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") - -install(TARGETS backend RUNTIME_DEPENDENCY_SET backend_deps DESTINATION .) -install(RUNTIME_DEPENDENCY_SET backend_deps) - -if(NOT MSVC) - target_compile_options(backend PRIVATE - $<$:-g -Wall -Wno-unknown-pragmas> - $<$:-O3> - ) -else() - target_compile_options(backend PRIVATE /W4 /wd4068) -endif(NOT MSVC) - -if(WIN32) - target_link_libraries(backend iphlpapi) - target_compile_definitions(backend PRIVATE NOMINMAX _WIN32_WINNT=0x0A00) -endif() diff --git a/backend/Cargo.toml b/backend/Cargo.toml new file mode 100644 index 0000000..a55cde2 --- /dev/null +++ b/backend/Cargo.toml @@ -0,0 +1,44 @@ +[package] +name = "backend_rust" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rusqlite = { version = "0.28.0", features = ["bundled"] } +diesel = { version = "2.0.1", features = ["sqlite", "r2d2", "returning_clauses_for_sqlite_3_35"] } +diesel_migrations = "2.0.0" +r2d2_sqlite = "0.21.0" + +warp = { version = "0.3.3", features = ["compression", "compression-brotli", "compression-gzip"] } +headers = "0.3" +tokio = { version = "1.21.2", features = ["full", "tracing"] } +tokio-util = { version = "0.7.4", features = ["codec"] } +console-subscriber = "0.1.8" +futures = "0.3.24" +bytes = "1.2.1" +tracing = { version = "0.1.37", features = ["log-always"] } +log = "0.4.17" + +serde = { version = "1.0.145", features = ["derive"] } +serde_repr = "0.1.9" + +pretty_env_logger = "0.4" +lazy_static = "1.4.0" +json = "0.12.4" + +jsonwebtoken = "8.1.1" +thiserror = "1.0.37" +chrono = "0.4.22" +rust-argon2 = "1.0.0" +lettre = "0.10.1" +ureq = { version = "2.5.0", features = ["json"] } +totp-rs = { version = "3.0.1", features = ["qr"] } +ring = { version = "0.16.20", default-features = false } +mime_guess = "2.0.4" +zip = "0.6.2" +base64 = "0.13.0" +image = "0.24.4" +cached = "0.39.0" +stretto = "0.7.1" diff --git a/backend/SMTPMail-drogon-master/LICENSE b/backend/SMTPMail-drogon-master/LICENSE deleted file mode 100644 index bd8c790..0000000 --- a/backend/SMTPMail-drogon-master/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2020 ihmc3jn09hk - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/backend/SMTPMail-drogon-master/README.md b/backend/SMTPMail-drogon-master/README.md deleted file mode 100644 index 5463867..0000000 --- a/backend/SMTPMail-drogon-master/README.md +++ /dev/null @@ -1,79 +0,0 @@ -# SMTPMail-drogon -Simple Mail for the Drogon framework. - -It is made as a plugin for the [drogon](https://github.com/an-tao/drogon) framework. -It can be included into the drogon build with little -modification of the class declaration. -## Updates -- **[ 13-06-2022 ] Fixed vulnerability issues reported by [Sam](https://snoopysecurity.github.io/about).** -- [ 13-09-2021 ] Added [HTML content support](https://github.com/ihmc3jn09hk/SMTPMail-drogon/pull/1). -- [ 23-12-2020 ] Added DNS support. - -## Acknowledgement -* The implementation takes SMTPClient for Qt from [kelvins](https://github.com/kelvins/SMTPClient) as reference. -* There requires a delay SSL encryption from the Tcp-socket (named TcpClient in trantor/drogon) and the major -author of drogon [reponsed](https://github.com/an-tao/drogon/issues/346) quickly. - -## Usage -Download to the plugin directory of the target drogon app, E.g. ~/drogon-app/plugins -```bash -$ git clone https://github.com/ihmc3jn09hk/SMTPMail-drogon.git -$ cp SMTPMail-drogon/SMTPMail.* ~/drogon-app/plugins -``` - -* _Be aware of add the plugin into the config.json. Set the "name" field to "SMTPMail"_ - -Add the reference header and get the plugin from the app(), E.g. - -```c++ -... -#include "../plugins/SMTPMail.h" -... - -//Inside some function, E.g. A controller function. -... -//Send an email -auto *smtpmailPtr = app().getPlugin(); -auto id = smtpmailPtr->sendEmail( - "127.0.0.1", //The server IP/DNS - 587, //The port - "mailer@something.com", //Who send the email - "receiver@otherthing.com", //Send to whom - "Testing SMTPMail Function", //Email Subject/Title - "Hello from drogon plugin", //Content - "mailer@something.com", //Login user - "123456", //User password - false //Is HTML content - ); -... -//Or get noted when email is sent -... -void callback(const std::string &msg) -{ - LOG_INFO << msg; /*Output e.g. "EMail sent. ID : 96ESERVDDFH17588ECF0C7B00326E3"*/ - /*Do whatever you like*/ -} -... -auto *smtpmailPtr = app().getPlugin(); -auto id = smtpmailPtr->sendEmail( - "127.0.0.1", //The server IP/DNS - 587, //The port - "mailer@something.com", //Who send the email - "receiver@otherthing.com", //Send to whom - "Testing SMTPMail Function", //Email Subject/Title - "Hello from drogon plugin", //Content - "mailer@something.com", //Login user - "123456", //User password - false, //Is HTML content - callback //Callback - ); -``` - -```bash -$ cd ~/drogon-app/build -$ make -``` - -## Licence -* Feel free to use, thanks to open-source. -* For the sake of concern on commercial usage, a simple licence is included in each of the files. diff --git a/backend/SMTPMail-drogon-master/SMTPMail.cc b/backend/SMTPMail-drogon-master/SMTPMail.cc deleted file mode 100644 index 8b105c2..0000000 --- a/backend/SMTPMail-drogon-master/SMTPMail.cc +++ /dev/null @@ -1,400 +0,0 @@ -/** -* -* SMTPMail.cc * -* -* This plugin is for SMTP mail delivery for the Drogon web-framework. -Implementation -* reference from the project "SMTPClient" with Qt5 by kelvins. Please check out -* https://github.com/kelvins/SMTPClient. - -Feel free to use the code. For the sake of any concern, the following licence is -attached. - - Copyright 2020 ihmc3jn09hk - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - */ - -#include "SMTPMail.h" -#include -#include -#include -#include - -using namespace drogon; -using namespace trantor; - -struct EMail { - enum states { - Init, - HandShake, - Tls, - Auth, - User, - Pass, - Mail, - Rcpt, - Data, - Body, - Quit, - Close - }; - std::string m_from; - std::string m_to; - std::string m_subject; - std::string m_content; - std::string m_user; - std::string m_passwd; - states m_status; - std::string m_uuid; - bool m_isHTML{false}; - std::shared_ptr m_socket; - - EMail(std::string from, std::string to, std::string subject, - std::string content, std::string user, std::string passwd, bool isHTML, - std::shared_ptr socket) - : m_from(std::move(from)), m_to(std::move(to)), - m_subject(std::move(subject)), m_content(std::move(content)), - m_user(std::move(user)), m_passwd(std::move(passwd)), - m_socket(std::move(socket)), m_isHTML(isHTML), - m_uuid(drogon::utils::getUuid()) { - m_status = Init; - } - - ~EMail() = default; - - static std::unordered_map> - m_emails; // Container for processing emails -}; - -std::unordered_map> EMail::m_emails; - -void SMTPMail::initAndStart(const Json::Value &config) { - /// Initialize and start the plugin - LOG_INFO << "SMTPMail initialized and started"; -} - -void SMTPMail::shutdown() { - /// Shutdown the plugin - LOG_INFO << "STMPMail shutdown"; -} - -void messagesHandle(const trantor::TcpConnectionPtr &connPtr, - trantor::MsgBuffer *msg, - const std::shared_ptr &email, - const std::function &cb) { - std::string receivedMsg; - while (msg->readableBytes() > 0) { - std::string buf(msg->peek(), msg->readableBytes()); - receivedMsg.append(buf); - // LOG_INFO << buf; - msg->retrieveAll(); - } - LOG_TRACE << "receive: " << receivedMsg; - std::string responseCode(receivedMsg.begin(), receivedMsg.begin() + 3); - // std::string responseMsg(receivedMsg.begin() + 4, receivedMsg.end()); - - if (email->m_status == EMail::Init && responseCode == "220") { - std::string outMsg; - trantor::MsgBuffer out; - - outMsg.append("EHLO smtpclient.qw"); - outMsg.append("\r\n"); - - out.append(outMsg.data(), outMsg.size()); - - connPtr->send(std::move(out)); - - email->m_status = EMail::HandShake; - } else if (email->m_status == EMail::HandShake && responseCode == "220") { - std::string outMsg; - trantor::MsgBuffer out; - - outMsg.append("EHLO smtpclient.qw"); - outMsg.append("\r\n"); - - out.append(outMsg.data(), outMsg.size()); - - connPtr->startClientEncryption( - [connPtr, out]() { - // LOG_TRACE << "SSL established"; - connPtr->send(out); - }, - false, false); - - email->m_status = EMail::Auth; - } else if (email->m_status == EMail::HandShake && responseCode == "250") { - std::string outMsg; - trantor::MsgBuffer out; - - outMsg.append("STARTTLS"); - outMsg.append("\r\n"); - - out.append(outMsg.data(), outMsg.size()); - - connPtr->send(std::move(out)); - - email->m_status = EMail::HandShake; - } else if (email->m_status == EMail::Auth && responseCode == "250") { - trantor::MsgBuffer out; - std::string outMsg; - - outMsg.append("AUTH LOGIN"); - outMsg.append("\r\n"); - - out.append(outMsg.data(), outMsg.size()); - - connPtr->send(std::move(out)); - - email->m_status = EMail::User; - } else if (email->m_status == EMail::User && responseCode == "334") { - trantor::MsgBuffer out; - std::string outMsg; - - std::string secret(email->m_user); - - // outMsg.append(base64_encode(reinterpret_cast(secret.c_str()), secret.length())); - outMsg.append(drogon::utils::base64Encode( - reinterpret_cast(secret.c_str()), - secret.length())); - - outMsg.append("\r\n"); - - out.append(outMsg.data(), outMsg.size()); - - connPtr->send(std::move(out)); - - email->m_status = EMail::Pass; - } else if (email->m_status == EMail::Pass && responseCode == "334") { - trantor::MsgBuffer out; - std::string outMsg; - - std::string secret(email->m_passwd); - - outMsg.append(drogon::utils::base64Encode( - reinterpret_cast(secret.c_str()), - secret.length())); - outMsg.append("\r\n"); - - out.append(outMsg.data(), outMsg.size()); - - connPtr->send(std::move(out)); - - email->m_status = EMail::Mail; - } else if (email->m_status == EMail::Mail && responseCode == "235") { - trantor::MsgBuffer out; - std::string outMsg; - - outMsg.append("MAIL FROM:<"); - outMsg.append(email->m_from); - outMsg.append(">\r\n"); - - out.append(outMsg.data(), outMsg.size()); - - connPtr->send(std::move(out)); - - email->m_status = EMail::Rcpt; - } else if (email->m_status == EMail::Rcpt && responseCode == "250") { - trantor::MsgBuffer out; - std::string outMsg; - - outMsg.append("RCPT TO:<"); - outMsg.append(email->m_to); - outMsg.append(">\r\n"); - - out.append(outMsg.data(), outMsg.size()); - - connPtr->send(std::move(out)); - - email->m_status = EMail::Data; - } else if (email->m_status == EMail::Data && responseCode == "250") { - trantor::MsgBuffer out; - std::string outMsg; - - outMsg.append("DATA"); - outMsg.append("\r\n"); - - out.append(outMsg.data(), outMsg.size()); - - connPtr->send(std::move(out)); - - email->m_status = EMail::Body; - } else if (email->m_status == EMail::Body && responseCode == "354") { - trantor::MsgBuffer out; - std::string outMsg; - std::time_t t = std::time(nullptr); - char buf[100]; - std::strftime(buf, 100, "%a, %d %b %Y %T %z", std::localtime(&t)); - - outMsg.append("To: " + email->m_to + "\r\n"); - outMsg.append("From: " + email->m_from + "\r\n"); - outMsg.append("Date: " + std::string(buf) + "\r\n"); - if (email->m_isHTML) { - outMsg.append("Content-Type: text/html;\r\n"); - } - outMsg.append("Subject: " + email->m_subject + "\r\n\r\n"); - - outMsg.append(email->m_content); - outMsg.append("\r\n.\r\n"); - - out.append(outMsg.data(), outMsg.size()); - - connPtr->send(std::move(out)); - - email->m_status = EMail::Quit; - } else if (email->m_status == EMail::Quit && responseCode == "250") { - trantor::MsgBuffer out; - std::string outMsg; - - outMsg.append("QUIT"); - outMsg.append("\r\n"); - - out.append(outMsg.data(), outMsg.size()); - - connPtr->send(std::move(out)); - - email->m_status = EMail::Close; - } else if (email->m_status == EMail::Close) { - /*Callback here for succeed delivery is probable*/ - cb("EMail sent. ID : " + email->m_uuid); - return; - } else { - email->m_status = EMail::Close; - /*Callback here for notification is probable*/ - cb(receivedMsg); - } -} - -std::string -SMTPMail::sendEmail(const std::string &mailServer, const uint16_t &port, - const std::string &from, const std::string &to, - const std::string &subject, const std::string &content, - const std::string &user, const std::string &passwd, - bool isHTML, - const std::function &cb) { - if (mailServer.empty() || from.empty() || to.empty() || subject.empty() || - user.empty() || passwd.empty()) { - LOG_WARN << "Invalid input(s) - " - << "\nServer : " << mailServer << "\nPort : " << port - << "\nfrom : " << from << "\nto : " << to - << "\nsubject : " << subject << "\nuser : " << user - << "\npasswd : " << passwd; - return {}; - } - - static auto hasLineBreak = [](const std::string &msg) { - if (std::string::npos != msg.find_first_of("\n") || - std::string::npos != msg.find_first_of("\r")) { - return true; - } - return false; - }; - - if (hasLineBreak(from)) { - LOG_WARN << "Invalid \"FROM\" data : " << from; - return {}; - } - if (hasLineBreak(to)) { - LOG_WARN << "Invalid \"TO\" data : " << to; - return {}; - } - if (hasLineBreak(subject)) { - LOG_WARN << "Invalid \"SUBJECT\" data : " << subject.data(); - return {}; - } - - LOG_TRACE << "New TcpClient : " << mailServer << ":" << port; - - // Create the email - auto email = std::make_shared(from, to, subject, content, user, passwd, - isHTML, nullptr); - - auto resolver = app().getResolver(); - resolver->resolve( - mailServer, [email, port, cb](const trantor::InetAddress &addr) { - constexpr size_t defaultLoopIdA = 10; - constexpr size_t defaultLoopIdB = 9; - auto loopA = app().getIOLoop(defaultLoopIdA); - auto loopB = app().getIOLoop(defaultLoopIdB); - - if ( loopA == loopB ) { - LOG_WARN << "Please provide at least 2 threads for this plugin"; - return; - } - - auto loop = loopA->isInLoopThread() ? loopB : loopA; - - assert(loop); // Should never be null - trantor::InetAddress addr_(addr.toIp(), port, false); - auto tcpSocket = - std::make_shared(loop, addr_, "SMTPMail"); - - email->m_socket = tcpSocket; - - std::weak_ptr email_wptr = email; - - EMail::m_emails.emplace(email->m_uuid, - email); // Assuming there is no uuid collision - tcpSocket->setConnectionCallback( - [email_wptr](const trantor::TcpConnectionPtr &connPtr) { - auto email_ptr = email_wptr.lock(); - if (!email_ptr) { - LOG_WARN << "EMail pointer gone"; - return; - } - if (connPtr->connected()) { - // send request; - LOG_TRACE << "Connection established!"; - } else { - LOG_TRACE << "Connection disconnect"; - EMail::m_emails.erase( - email_ptr->m_uuid); // Remove the email in list - // thisPtr->onError(std::string("ReqResult::NetworkFailure")); - } - }); - tcpSocket->setConnectionErrorCallback([email_wptr]() { - auto email_ptr = email_wptr.lock(); - if (!email_ptr) { - LOG_ERROR << "EMail pointer gone"; - return; - } - // can't connect to server - LOG_ERROR << "Bad Server address"; - EMail::m_emails.erase(email_ptr->m_uuid); // Remove the email in list - // thisPtr->onError(std::string("ReqResult::BadServerAddress")); - }); - auto cb_(cb ? cb : [](const std::string &msg) { - LOG_INFO << "Default email callback : " << msg; - }); - tcpSocket->setMessageCallback( - [email_wptr, cb_](const trantor::TcpConnectionPtr &connPtr, - trantor::MsgBuffer *msg) { - auto email_ptr = email_wptr.lock(); - if (!email_ptr) { - LOG_ERROR << "EMail pointer gone"; - return; - } - // email->m_socket->disconnect(); - messagesHandle(connPtr, msg, email_ptr, cb_); - }); - tcpSocket->connect(); // Start trying to send the email - }); - return email->m_uuid; -} diff --git a/backend/SMTPMail-drogon-master/SMTPMail.h b/backend/SMTPMail-drogon-master/SMTPMail.h deleted file mode 100644 index 2170bb4..0000000 --- a/backend/SMTPMail-drogon-master/SMTPMail.h +++ /dev/null @@ -1,63 +0,0 @@ -/** - * - * SMTPMail.h - * - * This plugin is for SMTP mail delievery for the Drogon web-framework. -Implementation - * reference from the project "SMTPClient" with Qt5 by kelvins. Please check out - * https://github.com/kelvins/SMTPClient. - -Copyright 2020 ihmc3jn09hk - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - */ - -#pragma once - -#include - -class SMTPMail : public drogon::Plugin { -public: - SMTPMail() = default; - /// This method must be called by drogon to initialize and start the plugin. - /// It must be implemented by the user. - void initAndStart(const Json::Value &config) override; - - /// This method must be called by drogon to shutdown the plugin. - /// It must be implemented by the user. - void shutdown() override; - - /** Send an email - * return : An ID of the email. - */ - std::string sendEmail( - const std::string - &mailServer, // Mail server address/dns E.g. 127.0.0.1/smtp.mail.com - const uint16_t &port, // Port E.g. 587 - const std::string &from, // Send from whom E.g. drogon@gmail.com - const std::string &to, // Reciever E.g. drogon@yahoo.com - const std::string &subject, // The email title/subject - const std::string &content, // The email content. - const std::string &user, // User (Usually same as "from") - const std::string &passwd, // Password - bool isHTML, // content type - const std::function &cb = {} - // The callback for email sent notification - ); -}; diff --git a/backend/default_config.json b/backend/default_config.json deleted file mode 100644 index 847bb4b..0000000 --- a/backend/default_config.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "gitlab_id": "", - "gitlab_secret": "", - "gitlab_url": "", - "gitlab_api_url": "", - "gitlab_redirect_url": "", - "smtp_server": "", - "smtp_port": 25, - "smtp_user": "", - "smtp_password": "" -} \ No newline at end of file diff --git a/backend/diesel.toml b/backend/diesel.toml new file mode 100644 index 0000000..35a12ff --- /dev/null +++ b/backend/diesel.toml @@ -0,0 +1,8 @@ +# For documentation on how to configure this file, +# see https://diesel.rs/guides/configuring-diesel-cli + +[print_schema] +file = "src/schema.rs" + +[migrations_directory] +dir = "migrations" diff --git a/backend/migrations/.keep b/backend/migrations/.keep new file mode 100644 index 0000000..e69de29 diff --git a/backend/migrations/2022-10-05-145523_init/down.sql b/backend/migrations/2022-10-05-145523_init/down.sql new file mode 100644 index 0000000..6700746 --- /dev/null +++ b/backend/migrations/2022-10-05-145523_init/down.sql @@ -0,0 +1,3 @@ +DROP TABLE inode; +DROP TABLE user; +DROP TABLE tokens \ No newline at end of file diff --git a/backend/migrations/2022-10-05-145523_init/up.sql b/backend/migrations/2022-10-05-145523_init/up.sql new file mode 100644 index 0000000..32f12ca --- /dev/null +++ b/backend/migrations/2022-10-05-145523_init/up.sql @@ -0,0 +1,28 @@ +CREATE TABLE tokens ( + 'id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + 'owner_id' INTEGER NOT NULL, + 'exp' INT8 NOT NULL +); + +CREATE TABLE user ( + 'id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + 'gitlab' BOOLEAN NOT NULL, + 'name' TEXT NOT NULL, + 'password' TEXT NOT NULL, + 'role' INT2 NOT NULL, + 'root_id' INTEGER NOT NULL, + 'tfa_type' INT2 NOT NULL, + 'tfa_secret' BLOB, + 'gitlab_at' TEXT, + 'gitlab_rt' TEXT +); + +CREATE TABLE inode ( + 'id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + 'is_file' BOOLEAN NOT NULL, + 'name' TEXT NOT NULL, + 'parent_id' INTEGER, + 'owner_id' INTEGER NOT NULL, + 'size' INT8, + 'has_preview' BOOLEAN NOT NULL +) diff --git a/backend/model/Inode.cc b/backend/model/Inode.cc deleted file mode 100644 index 5819a51..0000000 --- a/backend/model/Inode.cc +++ /dev/null @@ -1,1254 +0,0 @@ -/** - * - * Inode.cc - * DO NOT EDIT. This file is generated by drogon_ctl - * - */ - -#include "Inode.h" -#include "drogon/utils/Utilities.h" -#include - -using namespace drogon; -using namespace drogon::orm; -using namespace drogon_model::sqlite3; - -const std::string Inode::Cols::_id = "id"; -const std::string Inode::Cols::_is_file = "is_file"; -const std::string Inode::Cols::_name = "name"; -const std::string Inode::Cols::_parent_id = "parent_id"; -const std::string Inode::Cols::_owner_id = "owner_id"; -const std::string Inode::Cols::_size = "size"; -const std::string Inode::Cols::_has_preview = "has_preview"; -const std::string Inode::primaryKeyName = "id"; -const bool Inode::hasPrimaryKey = true; -const std::string Inode::tableName = "inode"; - -const std::vector Inode::metaData_={ -{"id","uint64_t","integer",8,1,1,1}, -{"is_file","uint64_t","integer",8,0,0,1}, -{"name","std::string","text",0,0,0,0}, -{"parent_id","uint64_t","integer",8,0,0,0}, -{"owner_id","uint64_t","integer",8,0,0,1}, -{"size","uint64_t","integer",8,0,0,0}, -{"has_preview","uint64_t","integer",8,0,0,1} -}; -const std::string &Inode::getColumnName(size_t index) noexcept(false) -{ - assert(index < metaData_.size()); - return metaData_[index].colName_; -} -Inode::Inode(const Row &r, const ssize_t indexOffset) noexcept -{ - if(indexOffset < 0) - { - if(!r["id"].isNull()) - { - id_=std::make_shared(r["id"].as()); - } - if(!r["is_file"].isNull()) - { - isFile_=std::make_shared(r["is_file"].as()); - } - if(!r["name"].isNull()) - { - name_=std::make_shared(r["name"].as()); - } - if(!r["parent_id"].isNull()) - { - parentId_=std::make_shared(r["parent_id"].as()); - } - if(!r["owner_id"].isNull()) - { - ownerId_=std::make_shared(r["owner_id"].as()); - } - if(!r["size"].isNull()) - { - size_=std::make_shared(r["size"].as()); - } - if(!r["has_preview"].isNull()) - { - hasPreview_=std::make_shared(r["has_preview"].as()); - } - } - else - { - size_t offset = (size_t)indexOffset; - if(offset + 7 > r.size()) - { - LOG_FATAL << "Invalid SQL result for this model"; - return; - } - size_t index; - index = offset + 0; - if(!r[index].isNull()) - { - id_=std::make_shared(r[index].as()); - } - index = offset + 1; - if(!r[index].isNull()) - { - isFile_=std::make_shared(r[index].as()); - } - index = offset + 2; - if(!r[index].isNull()) - { - name_=std::make_shared(r[index].as()); - } - index = offset + 3; - if(!r[index].isNull()) - { - parentId_=std::make_shared(r[index].as()); - } - index = offset + 4; - if(!r[index].isNull()) - { - ownerId_=std::make_shared(r[index].as()); - } - index = offset + 5; - if(!r[index].isNull()) - { - size_=std::make_shared(r[index].as()); - } - index = offset + 6; - if(!r[index].isNull()) - { - hasPreview_=std::make_shared(r[index].as()); - } - } - -} - -Inode::Inode(const Json::Value &pJson, const std::vector &pMasqueradingVector) noexcept(false) -{ - if(pMasqueradingVector.size() != 7) - { - LOG_ERROR << "Bad masquerading vector"; - return; - } - if(!pMasqueradingVector[0].empty() && pJson.isMember(pMasqueradingVector[0])) - { - dirtyFlag_[0] = true; - if(!pJson[pMasqueradingVector[0]].isNull()) - { - id_=std::make_shared((uint64_t)pJson[pMasqueradingVector[0]].asUInt64()); - } - } - if(!pMasqueradingVector[1].empty() && pJson.isMember(pMasqueradingVector[1])) - { - dirtyFlag_[1] = true; - if(!pJson[pMasqueradingVector[1]].isNull()) - { - isFile_=std::make_shared((uint64_t)pJson[pMasqueradingVector[1]].asUInt64()); - } - } - if(!pMasqueradingVector[2].empty() && pJson.isMember(pMasqueradingVector[2])) - { - dirtyFlag_[2] = true; - if(!pJson[pMasqueradingVector[2]].isNull()) - { - name_=std::make_shared(pJson[pMasqueradingVector[2]].asString()); - } - } - if(!pMasqueradingVector[3].empty() && pJson.isMember(pMasqueradingVector[3])) - { - dirtyFlag_[3] = true; - if(!pJson[pMasqueradingVector[3]].isNull()) - { - parentId_=std::make_shared((uint64_t)pJson[pMasqueradingVector[3]].asUInt64()); - } - } - if(!pMasqueradingVector[4].empty() && pJson.isMember(pMasqueradingVector[4])) - { - dirtyFlag_[4] = true; - if(!pJson[pMasqueradingVector[4]].isNull()) - { - ownerId_=std::make_shared((uint64_t)pJson[pMasqueradingVector[4]].asUInt64()); - } - } - if(!pMasqueradingVector[5].empty() && pJson.isMember(pMasqueradingVector[5])) - { - dirtyFlag_[5] = true; - if(!pJson[pMasqueradingVector[5]].isNull()) - { - size_=std::make_shared((uint64_t)pJson[pMasqueradingVector[5]].asUInt64()); - } - } - if(!pMasqueradingVector[6].empty() && pJson.isMember(pMasqueradingVector[6])) - { - dirtyFlag_[6] = true; - if(!pJson[pMasqueradingVector[6]].isNull()) - { - hasPreview_=std::make_shared((uint64_t)pJson[pMasqueradingVector[6]].asUInt64()); - } - } -} - -Inode::Inode(const Json::Value &pJson) noexcept(false) -{ - if(pJson.isMember("id")) - { - dirtyFlag_[0]=true; - if(!pJson["id"].isNull()) - { - id_=std::make_shared((uint64_t)pJson["id"].asUInt64()); - } - } - if(pJson.isMember("is_file")) - { - dirtyFlag_[1]=true; - if(!pJson["is_file"].isNull()) - { - isFile_=std::make_shared((uint64_t)pJson["is_file"].asUInt64()); - } - } - if(pJson.isMember("name")) - { - dirtyFlag_[2]=true; - if(!pJson["name"].isNull()) - { - name_=std::make_shared(pJson["name"].asString()); - } - } - if(pJson.isMember("parent_id")) - { - dirtyFlag_[3]=true; - if(!pJson["parent_id"].isNull()) - { - parentId_=std::make_shared((uint64_t)pJson["parent_id"].asUInt64()); - } - } - if(pJson.isMember("owner_id")) - { - dirtyFlag_[4]=true; - if(!pJson["owner_id"].isNull()) - { - ownerId_=std::make_shared((uint64_t)pJson["owner_id"].asUInt64()); - } - } - if(pJson.isMember("size")) - { - dirtyFlag_[5]=true; - if(!pJson["size"].isNull()) - { - size_=std::make_shared((uint64_t)pJson["size"].asUInt64()); - } - } - if(pJson.isMember("has_preview")) - { - dirtyFlag_[6]=true; - if(!pJson["has_preview"].isNull()) - { - hasPreview_=std::make_shared((uint64_t)pJson["has_preview"].asUInt64()); - } - } -} - -void Inode::updateByMasqueradedJson(const Json::Value &pJson, - const std::vector &pMasqueradingVector) noexcept(false) -{ - if(pMasqueradingVector.size() != 7) - { - LOG_ERROR << "Bad masquerading vector"; - return; - } - if(!pMasqueradingVector[0].empty() && pJson.isMember(pMasqueradingVector[0])) - { - if(!pJson[pMasqueradingVector[0]].isNull()) - { - id_=std::make_shared((uint64_t)pJson[pMasqueradingVector[0]].asUInt64()); - } - } - if(!pMasqueradingVector[1].empty() && pJson.isMember(pMasqueradingVector[1])) - { - dirtyFlag_[1] = true; - if(!pJson[pMasqueradingVector[1]].isNull()) - { - isFile_=std::make_shared((uint64_t)pJson[pMasqueradingVector[1]].asUInt64()); - } - } - if(!pMasqueradingVector[2].empty() && pJson.isMember(pMasqueradingVector[2])) - { - dirtyFlag_[2] = true; - if(!pJson[pMasqueradingVector[2]].isNull()) - { - name_=std::make_shared(pJson[pMasqueradingVector[2]].asString()); - } - } - if(!pMasqueradingVector[3].empty() && pJson.isMember(pMasqueradingVector[3])) - { - dirtyFlag_[3] = true; - if(!pJson[pMasqueradingVector[3]].isNull()) - { - parentId_=std::make_shared((uint64_t)pJson[pMasqueradingVector[3]].asUInt64()); - } - } - if(!pMasqueradingVector[4].empty() && pJson.isMember(pMasqueradingVector[4])) - { - dirtyFlag_[4] = true; - if(!pJson[pMasqueradingVector[4]].isNull()) - { - ownerId_=std::make_shared((uint64_t)pJson[pMasqueradingVector[4]].asUInt64()); - } - } - if(!pMasqueradingVector[5].empty() && pJson.isMember(pMasqueradingVector[5])) - { - dirtyFlag_[5] = true; - if(!pJson[pMasqueradingVector[5]].isNull()) - { - size_=std::make_shared((uint64_t)pJson[pMasqueradingVector[5]].asUInt64()); - } - } - if(!pMasqueradingVector[6].empty() && pJson.isMember(pMasqueradingVector[6])) - { - dirtyFlag_[6] = true; - if(!pJson[pMasqueradingVector[6]].isNull()) - { - hasPreview_=std::make_shared((uint64_t)pJson[pMasqueradingVector[6]].asUInt64()); - } - } -} - -void Inode::updateByJson(const Json::Value &pJson) noexcept(false) -{ - if(pJson.isMember("id")) - { - if(!pJson["id"].isNull()) - { - id_=std::make_shared((uint64_t)pJson["id"].asUInt64()); - } - } - if(pJson.isMember("is_file")) - { - dirtyFlag_[1] = true; - if(!pJson["is_file"].isNull()) - { - isFile_=std::make_shared((uint64_t)pJson["is_file"].asUInt64()); - } - } - if(pJson.isMember("name")) - { - dirtyFlag_[2] = true; - if(!pJson["name"].isNull()) - { - name_=std::make_shared(pJson["name"].asString()); - } - } - if(pJson.isMember("parent_id")) - { - dirtyFlag_[3] = true; - if(!pJson["parent_id"].isNull()) - { - parentId_=std::make_shared((uint64_t)pJson["parent_id"].asUInt64()); - } - } - if(pJson.isMember("owner_id")) - { - dirtyFlag_[4] = true; - if(!pJson["owner_id"].isNull()) - { - ownerId_=std::make_shared((uint64_t)pJson["owner_id"].asUInt64()); - } - } - if(pJson.isMember("size")) - { - dirtyFlag_[5] = true; - if(!pJson["size"].isNull()) - { - size_=std::make_shared((uint64_t)pJson["size"].asUInt64()); - } - } - if(pJson.isMember("has_preview")) - { - dirtyFlag_[6] = true; - if(!pJson["has_preview"].isNull()) - { - hasPreview_=std::make_shared((uint64_t)pJson["has_preview"].asUInt64()); - } - } -} - -const uint64_t &Inode::getValueOfId() const noexcept -{ - const static uint64_t defaultValue = uint64_t(); - if(id_) - return *id_; - return defaultValue; -} -const std::shared_ptr &Inode::getId() const noexcept -{ - return id_; -} -void Inode::setId(const uint64_t &pId) noexcept -{ - id_ = std::make_shared(pId); - dirtyFlag_[0] = true; -} -const typename Inode::PrimaryKeyType & Inode::getPrimaryKey() const -{ - assert(id_); - return *id_; -} - -const uint64_t &Inode::getValueOfIsFile() const noexcept -{ - const static uint64_t defaultValue = uint64_t(); - if(isFile_) - return *isFile_; - return defaultValue; -} -const std::shared_ptr &Inode::getIsFile() const noexcept -{ - return isFile_; -} -void Inode::setIsFile(const uint64_t &pIsFile) noexcept -{ - isFile_ = std::make_shared(pIsFile); - dirtyFlag_[1] = true; -} - -const std::string &Inode::getValueOfName() const noexcept -{ - const static std::string defaultValue = std::string(); - if(name_) - return *name_; - return defaultValue; -} -const std::shared_ptr &Inode::getName() const noexcept -{ - return name_; -} -void Inode::setName(const std::string &pName) noexcept -{ - name_ = std::make_shared(pName); - dirtyFlag_[2] = true; -} -void Inode::setName(std::string &&pName) noexcept -{ - name_ = std::make_shared(std::move(pName)); - dirtyFlag_[2] = true; -} -void Inode::setNameToNull() noexcept -{ - name_.reset(); - dirtyFlag_[2] = true; -} - -const uint64_t &Inode::getValueOfParentId() const noexcept -{ - const static uint64_t defaultValue = uint64_t(); - if(parentId_) - return *parentId_; - return defaultValue; -} -const std::shared_ptr &Inode::getParentId() const noexcept -{ - return parentId_; -} -void Inode::setParentId(const uint64_t &pParentId) noexcept -{ - parentId_ = std::make_shared(pParentId); - dirtyFlag_[3] = true; -} -void Inode::setParentIdToNull() noexcept -{ - parentId_.reset(); - dirtyFlag_[3] = true; -} - -const uint64_t &Inode::getValueOfOwnerId() const noexcept -{ - const static uint64_t defaultValue = uint64_t(); - if(ownerId_) - return *ownerId_; - return defaultValue; -} -const std::shared_ptr &Inode::getOwnerId() const noexcept -{ - return ownerId_; -} -void Inode::setOwnerId(const uint64_t &pOwnerId) noexcept -{ - ownerId_ = std::make_shared(pOwnerId); - dirtyFlag_[4] = true; -} - -const uint64_t &Inode::getValueOfSize() const noexcept -{ - const static uint64_t defaultValue = uint64_t(); - if(size_) - return *size_; - return defaultValue; -} -const std::shared_ptr &Inode::getSize() const noexcept -{ - return size_; -} -void Inode::setSize(const uint64_t &pSize) noexcept -{ - size_ = std::make_shared(pSize); - dirtyFlag_[5] = true; -} -void Inode::setSizeToNull() noexcept -{ - size_.reset(); - dirtyFlag_[5] = true; -} - -const uint64_t &Inode::getValueOfHasPreview() const noexcept -{ - const static uint64_t defaultValue = uint64_t(); - if(hasPreview_) - return *hasPreview_; - return defaultValue; -} -const std::shared_ptr &Inode::getHasPreview() const noexcept -{ - return hasPreview_; -} -void Inode::setHasPreview(const uint64_t &pHasPreview) noexcept -{ - hasPreview_ = std::make_shared(pHasPreview); - dirtyFlag_[6] = true; -} - -void Inode::updateId(const uint64_t id) -{ - id_ = std::make_shared(id); -} - -const std::vector &Inode::insertColumns() noexcept -{ - static const std::vector inCols={ - "is_file", - "name", - "parent_id", - "owner_id", - "size", - "has_preview" - }; - return inCols; -} - -void Inode::outputArgs(drogon::orm::internal::SqlBinder &binder) const -{ - if(dirtyFlag_[1]) - { - if(getIsFile()) - { - binder << getValueOfIsFile(); - } - else - { - binder << nullptr; - } - } - if(dirtyFlag_[2]) - { - if(getName()) - { - binder << getValueOfName(); - } - else - { - binder << nullptr; - } - } - if(dirtyFlag_[3]) - { - if(getParentId()) - { - binder << getValueOfParentId(); - } - else - { - binder << nullptr; - } - } - if(dirtyFlag_[4]) - { - if(getOwnerId()) - { - binder << getValueOfOwnerId(); - } - else - { - binder << nullptr; - } - } - if(dirtyFlag_[5]) - { - if(getSize()) - { - binder << getValueOfSize(); - } - else - { - binder << nullptr; - } - } - if(dirtyFlag_[6]) - { - if(getHasPreview()) - { - binder << getValueOfHasPreview(); - } - else - { - binder << nullptr; - } - } -} - -const std::vector Inode::updateColumns() const -{ - std::vector ret; - if(dirtyFlag_[1]) - { - ret.push_back(getColumnName(1)); - } - if(dirtyFlag_[2]) - { - ret.push_back(getColumnName(2)); - } - if(dirtyFlag_[3]) - { - ret.push_back(getColumnName(3)); - } - if(dirtyFlag_[4]) - { - ret.push_back(getColumnName(4)); - } - if(dirtyFlag_[5]) - { - ret.push_back(getColumnName(5)); - } - if(dirtyFlag_[6]) - { - ret.push_back(getColumnName(6)); - } - return ret; -} - -void Inode::updateArgs(drogon::orm::internal::SqlBinder &binder) const -{ - if(dirtyFlag_[1]) - { - if(getIsFile()) - { - binder << getValueOfIsFile(); - } - else - { - binder << nullptr; - } - } - if(dirtyFlag_[2]) - { - if(getName()) - { - binder << getValueOfName(); - } - else - { - binder << nullptr; - } - } - if(dirtyFlag_[3]) - { - if(getParentId()) - { - binder << getValueOfParentId(); - } - else - { - binder << nullptr; - } - } - if(dirtyFlag_[4]) - { - if(getOwnerId()) - { - binder << getValueOfOwnerId(); - } - else - { - binder << nullptr; - } - } - if(dirtyFlag_[5]) - { - if(getSize()) - { - binder << getValueOfSize(); - } - else - { - binder << nullptr; - } - } - if(dirtyFlag_[6]) - { - if(getHasPreview()) - { - binder << getValueOfHasPreview(); - } - else - { - binder << nullptr; - } - } -} -Json::Value Inode::toJson() const -{ - Json::Value ret; - if(getId()) - { - ret["id"]=(Json::UInt64)getValueOfId(); - } - else - { - ret["id"]=Json::Value(); - } - if(getIsFile()) - { - ret["is_file"]=(Json::UInt64)getValueOfIsFile(); - } - else - { - ret["is_file"]=Json::Value(); - } - if(getName()) - { - ret["name"]=getValueOfName(); - } - else - { - ret["name"]=Json::Value(); - } - if(getParentId()) - { - ret["parent_id"]=(Json::UInt64)getValueOfParentId(); - } - else - { - ret["parent_id"]=Json::Value(); - } - if(getOwnerId()) - { - ret["owner_id"]=(Json::UInt64)getValueOfOwnerId(); - } - else - { - ret["owner_id"]=Json::Value(); - } - if(getSize()) - { - ret["size"]=(Json::UInt64)getValueOfSize(); - } - else - { - ret["size"]=Json::Value(); - } - if(getHasPreview()) - { - ret["has_preview"]=(Json::UInt64)getValueOfHasPreview(); - } - else - { - ret["has_preview"]=Json::Value(); - } - return ret; -} - -Json::Value Inode::toMasqueradedJson( - const std::vector &pMasqueradingVector) const -{ - Json::Value ret; - if(pMasqueradingVector.size() == 7) - { - if(!pMasqueradingVector[0].empty()) - { - if(getId()) - { - ret[pMasqueradingVector[0]]=(Json::UInt64)getValueOfId(); - } - else - { - ret[pMasqueradingVector[0]]=Json::Value(); - } - } - if(!pMasqueradingVector[1].empty()) - { - if(getIsFile()) - { - ret[pMasqueradingVector[1]]=(Json::UInt64)getValueOfIsFile(); - } - else - { - ret[pMasqueradingVector[1]]=Json::Value(); - } - } - if(!pMasqueradingVector[2].empty()) - { - if(getName()) - { - ret[pMasqueradingVector[2]]=getValueOfName(); - } - else - { - ret[pMasqueradingVector[2]]=Json::Value(); - } - } - if(!pMasqueradingVector[3].empty()) - { - if(getParentId()) - { - ret[pMasqueradingVector[3]]=(Json::UInt64)getValueOfParentId(); - } - else - { - ret[pMasqueradingVector[3]]=Json::Value(); - } - } - if(!pMasqueradingVector[4].empty()) - { - if(getOwnerId()) - { - ret[pMasqueradingVector[4]]=(Json::UInt64)getValueOfOwnerId(); - } - else - { - ret[pMasqueradingVector[4]]=Json::Value(); - } - } - if(!pMasqueradingVector[5].empty()) - { - if(getSize()) - { - ret[pMasqueradingVector[5]]=(Json::UInt64)getValueOfSize(); - } - else - { - ret[pMasqueradingVector[5]]=Json::Value(); - } - } - if(!pMasqueradingVector[6].empty()) - { - if(getHasPreview()) - { - ret[pMasqueradingVector[6]]=(Json::UInt64)getValueOfHasPreview(); - } - else - { - ret[pMasqueradingVector[6]]=Json::Value(); - } - } - return ret; - } - LOG_ERROR << "Masquerade failed"; - if(getId()) - { - ret["id"]=(Json::UInt64)getValueOfId(); - } - else - { - ret["id"]=Json::Value(); - } - if(getIsFile()) - { - ret["is_file"]=(Json::UInt64)getValueOfIsFile(); - } - else - { - ret["is_file"]=Json::Value(); - } - if(getName()) - { - ret["name"]=getValueOfName(); - } - else - { - ret["name"]=Json::Value(); - } - if(getParentId()) - { - ret["parent_id"]=(Json::UInt64)getValueOfParentId(); - } - else - { - ret["parent_id"]=Json::Value(); - } - if(getOwnerId()) - { - ret["owner_id"]=(Json::UInt64)getValueOfOwnerId(); - } - else - { - ret["owner_id"]=Json::Value(); - } - if(getSize()) - { - ret["size"]=(Json::UInt64)getValueOfSize(); - } - else - { - ret["size"]=Json::Value(); - } - if(getHasPreview()) - { - ret["has_preview"]=(Json::UInt64)getValueOfHasPreview(); - } - else - { - ret["has_preview"]=Json::Value(); - } - return ret; -} - -bool Inode::validateJsonForCreation(const Json::Value &pJson, std::string &err) -{ - if(pJson.isMember("id")) - { - if(!validJsonOfField(0, "id", pJson["id"], err, true)) - return false; - } - if(pJson.isMember("is_file")) - { - if(!validJsonOfField(1, "is_file", pJson["is_file"], err, true)) - return false; - } - else - { - err="The is_file column cannot be null"; - return false; - } - if(pJson.isMember("name")) - { - if(!validJsonOfField(2, "name", pJson["name"], err, true)) - return false; - } - if(pJson.isMember("parent_id")) - { - if(!validJsonOfField(3, "parent_id", pJson["parent_id"], err, true)) - return false; - } - if(pJson.isMember("owner_id")) - { - if(!validJsonOfField(4, "owner_id", pJson["owner_id"], err, true)) - return false; - } - else - { - err="The owner_id column cannot be null"; - return false; - } - if(pJson.isMember("size")) - { - if(!validJsonOfField(5, "size", pJson["size"], err, true)) - return false; - } - if(pJson.isMember("has_preview")) - { - if(!validJsonOfField(6, "has_preview", pJson["has_preview"], err, true)) - return false; - } - else - { - err="The has_preview column cannot be null"; - return false; - } - return true; -} -bool Inode::validateMasqueradedJsonForCreation(const Json::Value &pJson, - const std::vector &pMasqueradingVector, - std::string &err) -{ - if(pMasqueradingVector.size() != 7) - { - err = "Bad masquerading vector"; - return false; - } - try { - if(!pMasqueradingVector[0].empty()) - { - if(pJson.isMember(pMasqueradingVector[0])) - { - if(!validJsonOfField(0, pMasqueradingVector[0], pJson[pMasqueradingVector[0]], err, true)) - return false; - } - } - if(!pMasqueradingVector[1].empty()) - { - if(pJson.isMember(pMasqueradingVector[1])) - { - if(!validJsonOfField(1, pMasqueradingVector[1], pJson[pMasqueradingVector[1]], err, true)) - return false; - } - else - { - err="The " + pMasqueradingVector[1] + " column cannot be null"; - return false; - } - } - if(!pMasqueradingVector[2].empty()) - { - if(pJson.isMember(pMasqueradingVector[2])) - { - if(!validJsonOfField(2, pMasqueradingVector[2], pJson[pMasqueradingVector[2]], err, true)) - return false; - } - } - if(!pMasqueradingVector[3].empty()) - { - if(pJson.isMember(pMasqueradingVector[3])) - { - if(!validJsonOfField(3, pMasqueradingVector[3], pJson[pMasqueradingVector[3]], err, true)) - return false; - } - } - if(!pMasqueradingVector[4].empty()) - { - if(pJson.isMember(pMasqueradingVector[4])) - { - if(!validJsonOfField(4, pMasqueradingVector[4], pJson[pMasqueradingVector[4]], err, true)) - return false; - } - else - { - err="The " + pMasqueradingVector[4] + " column cannot be null"; - return false; - } - } - if(!pMasqueradingVector[5].empty()) - { - if(pJson.isMember(pMasqueradingVector[5])) - { - if(!validJsonOfField(5, pMasqueradingVector[5], pJson[pMasqueradingVector[5]], err, true)) - return false; - } - } - if(!pMasqueradingVector[6].empty()) - { - if(pJson.isMember(pMasqueradingVector[6])) - { - if(!validJsonOfField(6, pMasqueradingVector[6], pJson[pMasqueradingVector[6]], err, true)) - return false; - } - else - { - err="The " + pMasqueradingVector[6] + " column cannot be null"; - return false; - } - } - } - catch(const Json::LogicError &e) - { - err = e.what(); - return false; - } - return true; -} -bool Inode::validateJsonForUpdate(const Json::Value &pJson, std::string &err) -{ - if(pJson.isMember("id")) - { - if(!validJsonOfField(0, "id", pJson["id"], err, false)) - return false; - } - else - { - err = "The value of primary key must be set in the json object for update"; - return false; - } - if(pJson.isMember("is_file")) - { - if(!validJsonOfField(1, "is_file", pJson["is_file"], err, false)) - return false; - } - if(pJson.isMember("name")) - { - if(!validJsonOfField(2, "name", pJson["name"], err, false)) - return false; - } - if(pJson.isMember("parent_id")) - { - if(!validJsonOfField(3, "parent_id", pJson["parent_id"], err, false)) - return false; - } - if(pJson.isMember("owner_id")) - { - if(!validJsonOfField(4, "owner_id", pJson["owner_id"], err, false)) - return false; - } - if(pJson.isMember("size")) - { - if(!validJsonOfField(5, "size", pJson["size"], err, false)) - return false; - } - if(pJson.isMember("has_preview")) - { - if(!validJsonOfField(6, "has_preview", pJson["has_preview"], err, false)) - return false; - } - return true; -} -bool Inode::validateMasqueradedJsonForUpdate(const Json::Value &pJson, - const std::vector &pMasqueradingVector, - std::string &err) -{ - if(pMasqueradingVector.size() != 7) - { - err = "Bad masquerading vector"; - return false; - } - try { - if(!pMasqueradingVector[0].empty() && pJson.isMember(pMasqueradingVector[0])) - { - if(!validJsonOfField(0, pMasqueradingVector[0], pJson[pMasqueradingVector[0]], err, false)) - return false; - } - else - { - err = "The value of primary key must be set in the json object for update"; - return false; - } - if(!pMasqueradingVector[1].empty() && pJson.isMember(pMasqueradingVector[1])) - { - if(!validJsonOfField(1, pMasqueradingVector[1], pJson[pMasqueradingVector[1]], err, false)) - return false; - } - if(!pMasqueradingVector[2].empty() && pJson.isMember(pMasqueradingVector[2])) - { - if(!validJsonOfField(2, pMasqueradingVector[2], pJson[pMasqueradingVector[2]], err, false)) - return false; - } - if(!pMasqueradingVector[3].empty() && pJson.isMember(pMasqueradingVector[3])) - { - if(!validJsonOfField(3, pMasqueradingVector[3], pJson[pMasqueradingVector[3]], err, false)) - return false; - } - if(!pMasqueradingVector[4].empty() && pJson.isMember(pMasqueradingVector[4])) - { - if(!validJsonOfField(4, pMasqueradingVector[4], pJson[pMasqueradingVector[4]], err, false)) - return false; - } - if(!pMasqueradingVector[5].empty() && pJson.isMember(pMasqueradingVector[5])) - { - if(!validJsonOfField(5, pMasqueradingVector[5], pJson[pMasqueradingVector[5]], err, false)) - return false; - } - if(!pMasqueradingVector[6].empty() && pJson.isMember(pMasqueradingVector[6])) - { - if(!validJsonOfField(6, pMasqueradingVector[6], pJson[pMasqueradingVector[6]], err, false)) - return false; - } - } - catch(const Json::LogicError &e) - { - err = e.what(); - return false; - } - return true; -} -bool Inode::validJsonOfField(size_t index, - const std::string &fieldName, - const Json::Value &pJson, - std::string &err, - bool isForCreation) -{ - switch(index) - { - case 0: - if(pJson.isNull()) - { - err="The " + fieldName + " column cannot be null"; - return false; - } - if(isForCreation) - { - err="The automatic primary key cannot be set"; - return false; - } - if(!pJson.isUInt64()) - { - err="Type error in the "+fieldName+" field"; - return false; - } - break; - case 1: - if(pJson.isNull()) - { - err="The " + fieldName + " column cannot be null"; - return false; - } - if(!pJson.isUInt64()) - { - err="Type error in the "+fieldName+" field"; - return false; - } - break; - case 2: - if(pJson.isNull()) - { - return true; - } - if(!pJson.isString()) - { - err="Type error in the "+fieldName+" field"; - return false; - } - break; - case 3: - if(pJson.isNull()) - { - return true; - } - if(!pJson.isUInt64()) - { - err="Type error in the "+fieldName+" field"; - return false; - } - break; - case 4: - if(pJson.isNull()) - { - err="The " + fieldName + " column cannot be null"; - return false; - } - if(!pJson.isUInt64()) - { - err="Type error in the "+fieldName+" field"; - return false; - } - break; - case 5: - if(pJson.isNull()) - { - return true; - } - if(!pJson.isUInt64()) - { - err="Type error in the "+fieldName+" field"; - return false; - } - break; - case 6: - if(pJson.isNull()) - { - err="The " + fieldName + " column cannot be null"; - return false; - } - if(!pJson.isUInt64()) - { - err="Type error in the "+fieldName+" field"; - return false; - } - break; - default: - err="Internal error in the server"; - return false; - break; - } - return true; -} diff --git a/backend/model/Inode.h b/backend/model/Inode.h deleted file mode 100644 index 684f179..0000000 --- a/backend/model/Inode.h +++ /dev/null @@ -1,295 +0,0 @@ -/** - * - * Inode.h - * DO NOT EDIT. This file is generated by drogon_ctl - * - */ - -#pragma once -#include "drogon/orm/Result.h" -#include "drogon/orm/Row.h" -#include "drogon/orm/Field.h" -#include "drogon/orm/SqlBinder.h" -#include "drogon/orm/Mapper.h" -#ifdef __cpp_impl_coroutine -#include -#endif -#include "trantor/utils/Date.h" -#include "trantor/utils/Logger.h" -#include "json/json.h" -#include -#include -#include -#include -#include -#include - -namespace drogon -{ -namespace orm -{ -class DbClient; -using DbClientPtr = std::shared_ptr; -} -} -namespace drogon_model -{ -namespace sqlite3 -{ - -class Inode -{ - public: - struct Cols - { - static const std::string _id; - static const std::string _is_file; - static const std::string _name; - static const std::string _parent_id; - static const std::string _owner_id; - static const std::string _size; - static const std::string _has_preview; - }; - - const static int primaryKeyNumber; - const static std::string tableName; - const static bool hasPrimaryKey; - const static std::string primaryKeyName; - using PrimaryKeyType = uint64_t; - const PrimaryKeyType &getPrimaryKey() const; - - /** - * @brief constructor - * @param r One row of records in the SQL query result. - * @param indexOffset Set the offset to -1 to access all columns by column names, - * otherwise access all columns by offsets. - * @note If the SQL is not a style of 'select * from table_name ...' (select all - * columns by an asterisk), please set the offset to -1. - */ - explicit Inode(const drogon::orm::Row &r, const ssize_t indexOffset = 0) noexcept; - - /** - * @brief constructor - * @param pJson The json object to construct a new instance. - */ - explicit Inode(const Json::Value &pJson) noexcept(false); - - /** - * @brief constructor - * @param pJson The json object to construct a new instance. - * @param pMasqueradingVector The aliases of table columns. - */ - Inode(const Json::Value &pJson, const std::vector &pMasqueradingVector) noexcept(false); - - Inode() = default; - - void updateByJson(const Json::Value &pJson) noexcept(false); - void updateByMasqueradedJson(const Json::Value &pJson, - const std::vector &pMasqueradingVector) noexcept(false); - static bool validateJsonForCreation(const Json::Value &pJson, std::string &err); - static bool validateMasqueradedJsonForCreation(const Json::Value &, - const std::vector &pMasqueradingVector, - std::string &err); - static bool validateJsonForUpdate(const Json::Value &pJson, std::string &err); - static bool validateMasqueradedJsonForUpdate(const Json::Value &, - const std::vector &pMasqueradingVector, - std::string &err); - static bool validJsonOfField(size_t index, - const std::string &fieldName, - const Json::Value &pJson, - std::string &err, - bool isForCreation); - - /** For column id */ - ///Get the value of the column id, returns the default value if the column is null - const uint64_t &getValueOfId() const noexcept; - ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null - const std::shared_ptr &getId() const noexcept; - ///Set the value of the column id - void setId(const uint64_t &pId) noexcept; - - /** For column is_file */ - ///Get the value of the column is_file, returns the default value if the column is null - const uint64_t &getValueOfIsFile() const noexcept; - ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null - const std::shared_ptr &getIsFile() const noexcept; - ///Set the value of the column is_file - void setIsFile(const uint64_t &pIsFile) noexcept; - - /** For column name */ - ///Get the value of the column name, returns the default value if the column is null - const std::string &getValueOfName() const noexcept; - ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null - const std::shared_ptr &getName() const noexcept; - ///Set the value of the column name - void setName(const std::string &pName) noexcept; - void setName(std::string &&pName) noexcept; - void setNameToNull() noexcept; - - /** For column parent_id */ - ///Get the value of the column parent_id, returns the default value if the column is null - const uint64_t &getValueOfParentId() const noexcept; - ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null - const std::shared_ptr &getParentId() const noexcept; - ///Set the value of the column parent_id - void setParentId(const uint64_t &pParentId) noexcept; - void setParentIdToNull() noexcept; - - /** For column owner_id */ - ///Get the value of the column owner_id, returns the default value if the column is null - const uint64_t &getValueOfOwnerId() const noexcept; - ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null - const std::shared_ptr &getOwnerId() const noexcept; - ///Set the value of the column owner_id - void setOwnerId(const uint64_t &pOwnerId) noexcept; - - /** For column size */ - ///Get the value of the column size, returns the default value if the column is null - const uint64_t &getValueOfSize() const noexcept; - ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null - const std::shared_ptr &getSize() const noexcept; - ///Set the value of the column size - void setSize(const uint64_t &pSize) noexcept; - void setSizeToNull() noexcept; - - /** For column has_preview */ - ///Get the value of the column has_preview, returns the default value if the column is null - const uint64_t &getValueOfHasPreview() const noexcept; - ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null - const std::shared_ptr &getHasPreview() const noexcept; - ///Set the value of the column has_preview - void setHasPreview(const uint64_t &pHasPreview) noexcept; - - - static size_t getColumnNumber() noexcept { return 7; } - static const std::string &getColumnName(size_t index) noexcept(false); - - Json::Value toJson() const; - Json::Value toMasqueradedJson(const std::vector &pMasqueradingVector) const; - /// Relationship interfaces - private: - friend drogon::orm::Mapper; -#ifdef __cpp_impl_coroutine - friend drogon::orm::CoroMapper; -#endif - static const std::vector &insertColumns() noexcept; - void outputArgs(drogon::orm::internal::SqlBinder &binder) const; - const std::vector updateColumns() const; - void updateArgs(drogon::orm::internal::SqlBinder &binder) const; - ///For mysql or sqlite3 - void updateId(const uint64_t id); - std::shared_ptr id_; - std::shared_ptr isFile_; - std::shared_ptr name_; - std::shared_ptr parentId_; - std::shared_ptr ownerId_; - std::shared_ptr size_; - std::shared_ptr hasPreview_; - struct MetaData - { - const std::string colName_; - const std::string colType_; - const std::string colDatabaseType_; - const ssize_t colLength_; - const bool isAutoVal_; - const bool isPrimaryKey_; - const bool notNull_; - }; - static const std::vector metaData_; - bool dirtyFlag_[7]={ false }; - public: - static const std::string &sqlForFindingByPrimaryKey() - { - static const std::string sql="select * from " + tableName + " where id = ?"; - return sql; - } - - static const std::string &sqlForDeletingByPrimaryKey() - { - static const std::string sql="delete from " + tableName + " where id = ?"; - return sql; - } - std::string sqlForInserting(bool &needSelection) const - { - std::string sql="insert into " + tableName + " ("; - size_t parametersCount = 0; - needSelection = false; - if(dirtyFlag_[1]) - { - sql += "is_file,"; - ++parametersCount; - } - if(dirtyFlag_[2]) - { - sql += "name,"; - ++parametersCount; - } - if(dirtyFlag_[3]) - { - sql += "parent_id,"; - ++parametersCount; - } - if(dirtyFlag_[4]) - { - sql += "owner_id,"; - ++parametersCount; - } - if(dirtyFlag_[5]) - { - sql += "size,"; - ++parametersCount; - } - if(dirtyFlag_[6]) - { - sql += "has_preview,"; - ++parametersCount; - } - if(parametersCount > 0) - { - sql[sql.length()-1]=')'; - sql += " values ("; - } - else - sql += ") values ("; - - if(dirtyFlag_[1]) - { - sql.append("?,"); - - } - if(dirtyFlag_[2]) - { - sql.append("?,"); - - } - if(dirtyFlag_[3]) - { - sql.append("?,"); - - } - if(dirtyFlag_[4]) - { - sql.append("?,"); - - } - if(dirtyFlag_[5]) - { - sql.append("?,"); - - } - if(dirtyFlag_[6]) - { - sql.append("?,"); - - } - if(parametersCount > 0) - { - sql.resize(sql.length() - 1); - } - sql.append(1, ')'); - LOG_TRACE << sql; - return sql; - } -}; -} // namespace sqlite3 -} // namespace drogon_model diff --git a/backend/model/Tokens.cc b/backend/model/Tokens.cc deleted file mode 100644 index 7b8311a..0000000 --- a/backend/model/Tokens.cc +++ /dev/null @@ -1,631 +0,0 @@ -/** - * - * Tokens.cc - * DO NOT EDIT. This file is generated by drogon_ctl - * - */ - -#include "Tokens.h" -#include "drogon/utils/Utilities.h" -#include - -using namespace drogon; -using namespace drogon::orm; -using namespace drogon_model::sqlite3; - -const std::string Tokens::Cols::_id = "id"; -const std::string Tokens::Cols::_owner_id = "owner_id"; -const std::string Tokens::Cols::_exp = "exp"; -const std::string Tokens::primaryKeyName = "id"; -const bool Tokens::hasPrimaryKey = true; -const std::string Tokens::tableName = "tokens"; - -const std::vector Tokens::metaData_={ -{"id","uint64_t","integer",8,1,1,1}, -{"owner_id","uint64_t","integer",8,0,0,1}, -{"exp","uint64_t","integer",8,0,0,1} -}; -const std::string &Tokens::getColumnName(size_t index) noexcept(false) -{ - assert(index < metaData_.size()); - return metaData_[index].colName_; -} -Tokens::Tokens(const Row &r, const ssize_t indexOffset) noexcept -{ - if(indexOffset < 0) - { - if(!r["id"].isNull()) - { - id_=std::make_shared(r["id"].as()); - } - if(!r["owner_id"].isNull()) - { - ownerId_=std::make_shared(r["owner_id"].as()); - } - if(!r["exp"].isNull()) - { - exp_=std::make_shared(r["exp"].as()); - } - } - else - { - size_t offset = (size_t)indexOffset; - if(offset + 3 > r.size()) - { - LOG_FATAL << "Invalid SQL result for this model"; - return; - } - size_t index; - index = offset + 0; - if(!r[index].isNull()) - { - id_=std::make_shared(r[index].as()); - } - index = offset + 1; - if(!r[index].isNull()) - { - ownerId_=std::make_shared(r[index].as()); - } - index = offset + 2; - if(!r[index].isNull()) - { - exp_=std::make_shared(r[index].as()); - } - } - -} - -Tokens::Tokens(const Json::Value &pJson, const std::vector &pMasqueradingVector) noexcept(false) -{ - if(pMasqueradingVector.size() != 3) - { - LOG_ERROR << "Bad masquerading vector"; - return; - } - if(!pMasqueradingVector[0].empty() && pJson.isMember(pMasqueradingVector[0])) - { - dirtyFlag_[0] = true; - if(!pJson[pMasqueradingVector[0]].isNull()) - { - id_=std::make_shared((uint64_t)pJson[pMasqueradingVector[0]].asUInt64()); - } - } - if(!pMasqueradingVector[1].empty() && pJson.isMember(pMasqueradingVector[1])) - { - dirtyFlag_[1] = true; - if(!pJson[pMasqueradingVector[1]].isNull()) - { - ownerId_=std::make_shared((uint64_t)pJson[pMasqueradingVector[1]].asUInt64()); - } - } - if(!pMasqueradingVector[2].empty() && pJson.isMember(pMasqueradingVector[2])) - { - dirtyFlag_[2] = true; - if(!pJson[pMasqueradingVector[2]].isNull()) - { - exp_=std::make_shared((uint64_t)pJson[pMasqueradingVector[2]].asUInt64()); - } - } -} - -Tokens::Tokens(const Json::Value &pJson) noexcept(false) -{ - if(pJson.isMember("id")) - { - dirtyFlag_[0]=true; - if(!pJson["id"].isNull()) - { - id_=std::make_shared((uint64_t)pJson["id"].asUInt64()); - } - } - if(pJson.isMember("owner_id")) - { - dirtyFlag_[1]=true; - if(!pJson["owner_id"].isNull()) - { - ownerId_=std::make_shared((uint64_t)pJson["owner_id"].asUInt64()); - } - } - if(pJson.isMember("exp")) - { - dirtyFlag_[2]=true; - if(!pJson["exp"].isNull()) - { - exp_=std::make_shared((uint64_t)pJson["exp"].asUInt64()); - } - } -} - -void Tokens::updateByMasqueradedJson(const Json::Value &pJson, - const std::vector &pMasqueradingVector) noexcept(false) -{ - if(pMasqueradingVector.size() != 3) - { - LOG_ERROR << "Bad masquerading vector"; - return; - } - if(!pMasqueradingVector[0].empty() && pJson.isMember(pMasqueradingVector[0])) - { - if(!pJson[pMasqueradingVector[0]].isNull()) - { - id_=std::make_shared((uint64_t)pJson[pMasqueradingVector[0]].asUInt64()); - } - } - if(!pMasqueradingVector[1].empty() && pJson.isMember(pMasqueradingVector[1])) - { - dirtyFlag_[1] = true; - if(!pJson[pMasqueradingVector[1]].isNull()) - { - ownerId_=std::make_shared((uint64_t)pJson[pMasqueradingVector[1]].asUInt64()); - } - } - if(!pMasqueradingVector[2].empty() && pJson.isMember(pMasqueradingVector[2])) - { - dirtyFlag_[2] = true; - if(!pJson[pMasqueradingVector[2]].isNull()) - { - exp_=std::make_shared((uint64_t)pJson[pMasqueradingVector[2]].asUInt64()); - } - } -} - -void Tokens::updateByJson(const Json::Value &pJson) noexcept(false) -{ - if(pJson.isMember("id")) - { - if(!pJson["id"].isNull()) - { - id_=std::make_shared((uint64_t)pJson["id"].asUInt64()); - } - } - if(pJson.isMember("owner_id")) - { - dirtyFlag_[1] = true; - if(!pJson["owner_id"].isNull()) - { - ownerId_=std::make_shared((uint64_t)pJson["owner_id"].asUInt64()); - } - } - if(pJson.isMember("exp")) - { - dirtyFlag_[2] = true; - if(!pJson["exp"].isNull()) - { - exp_=std::make_shared((uint64_t)pJson["exp"].asUInt64()); - } - } -} - -const uint64_t &Tokens::getValueOfId() const noexcept -{ - const static uint64_t defaultValue = uint64_t(); - if(id_) - return *id_; - return defaultValue; -} -const std::shared_ptr &Tokens::getId() const noexcept -{ - return id_; -} -void Tokens::setId(const uint64_t &pId) noexcept -{ - id_ = std::make_shared(pId); - dirtyFlag_[0] = true; -} -const typename Tokens::PrimaryKeyType & Tokens::getPrimaryKey() const -{ - assert(id_); - return *id_; -} - -const uint64_t &Tokens::getValueOfOwnerId() const noexcept -{ - const static uint64_t defaultValue = uint64_t(); - if(ownerId_) - return *ownerId_; - return defaultValue; -} -const std::shared_ptr &Tokens::getOwnerId() const noexcept -{ - return ownerId_; -} -void Tokens::setOwnerId(const uint64_t &pOwnerId) noexcept -{ - ownerId_ = std::make_shared(pOwnerId); - dirtyFlag_[1] = true; -} - -const uint64_t &Tokens::getValueOfExp() const noexcept -{ - const static uint64_t defaultValue = uint64_t(); - if(exp_) - return *exp_; - return defaultValue; -} -const std::shared_ptr &Tokens::getExp() const noexcept -{ - return exp_; -} -void Tokens::setExp(const uint64_t &pExp) noexcept -{ - exp_ = std::make_shared(pExp); - dirtyFlag_[2] = true; -} - -void Tokens::updateId(const uint64_t id) -{ - id_ = std::make_shared(id); -} - -const std::vector &Tokens::insertColumns() noexcept -{ - static const std::vector inCols={ - "owner_id", - "exp" - }; - return inCols; -} - -void Tokens::outputArgs(drogon::orm::internal::SqlBinder &binder) const -{ - if(dirtyFlag_[1]) - { - if(getOwnerId()) - { - binder << getValueOfOwnerId(); - } - else - { - binder << nullptr; - } - } - if(dirtyFlag_[2]) - { - if(getExp()) - { - binder << getValueOfExp(); - } - else - { - binder << nullptr; - } - } -} - -const std::vector Tokens::updateColumns() const -{ - std::vector ret; - if(dirtyFlag_[1]) - { - ret.push_back(getColumnName(1)); - } - if(dirtyFlag_[2]) - { - ret.push_back(getColumnName(2)); - } - return ret; -} - -void Tokens::updateArgs(drogon::orm::internal::SqlBinder &binder) const -{ - if(dirtyFlag_[1]) - { - if(getOwnerId()) - { - binder << getValueOfOwnerId(); - } - else - { - binder << nullptr; - } - } - if(dirtyFlag_[2]) - { - if(getExp()) - { - binder << getValueOfExp(); - } - else - { - binder << nullptr; - } - } -} -Json::Value Tokens::toJson() const -{ - Json::Value ret; - if(getId()) - { - ret["id"]=(Json::UInt64)getValueOfId(); - } - else - { - ret["id"]=Json::Value(); - } - if(getOwnerId()) - { - ret["owner_id"]=(Json::UInt64)getValueOfOwnerId(); - } - else - { - ret["owner_id"]=Json::Value(); - } - if(getExp()) - { - ret["exp"]=(Json::UInt64)getValueOfExp(); - } - else - { - ret["exp"]=Json::Value(); - } - return ret; -} - -Json::Value Tokens::toMasqueradedJson( - const std::vector &pMasqueradingVector) const -{ - Json::Value ret; - if(pMasqueradingVector.size() == 3) - { - if(!pMasqueradingVector[0].empty()) - { - if(getId()) - { - ret[pMasqueradingVector[0]]=(Json::UInt64)getValueOfId(); - } - else - { - ret[pMasqueradingVector[0]]=Json::Value(); - } - } - if(!pMasqueradingVector[1].empty()) - { - if(getOwnerId()) - { - ret[pMasqueradingVector[1]]=(Json::UInt64)getValueOfOwnerId(); - } - else - { - ret[pMasqueradingVector[1]]=Json::Value(); - } - } - if(!pMasqueradingVector[2].empty()) - { - if(getExp()) - { - ret[pMasqueradingVector[2]]=(Json::UInt64)getValueOfExp(); - } - else - { - ret[pMasqueradingVector[2]]=Json::Value(); - } - } - return ret; - } - LOG_ERROR << "Masquerade failed"; - if(getId()) - { - ret["id"]=(Json::UInt64)getValueOfId(); - } - else - { - ret["id"]=Json::Value(); - } - if(getOwnerId()) - { - ret["owner_id"]=(Json::UInt64)getValueOfOwnerId(); - } - else - { - ret["owner_id"]=Json::Value(); - } - if(getExp()) - { - ret["exp"]=(Json::UInt64)getValueOfExp(); - } - else - { - ret["exp"]=Json::Value(); - } - return ret; -} - -bool Tokens::validateJsonForCreation(const Json::Value &pJson, std::string &err) -{ - if(pJson.isMember("id")) - { - if(!validJsonOfField(0, "id", pJson["id"], err, true)) - return false; - } - if(pJson.isMember("owner_id")) - { - if(!validJsonOfField(1, "owner_id", pJson["owner_id"], err, true)) - return false; - } - else - { - err="The owner_id column cannot be null"; - return false; - } - if(pJson.isMember("exp")) - { - if(!validJsonOfField(2, "exp", pJson["exp"], err, true)) - return false; - } - else - { - err="The exp column cannot be null"; - return false; - } - return true; -} -bool Tokens::validateMasqueradedJsonForCreation(const Json::Value &pJson, - const std::vector &pMasqueradingVector, - std::string &err) -{ - if(pMasqueradingVector.size() != 3) - { - err = "Bad masquerading vector"; - return false; - } - try { - if(!pMasqueradingVector[0].empty()) - { - if(pJson.isMember(pMasqueradingVector[0])) - { - if(!validJsonOfField(0, pMasqueradingVector[0], pJson[pMasqueradingVector[0]], err, true)) - return false; - } - } - if(!pMasqueradingVector[1].empty()) - { - if(pJson.isMember(pMasqueradingVector[1])) - { - if(!validJsonOfField(1, pMasqueradingVector[1], pJson[pMasqueradingVector[1]], err, true)) - return false; - } - else - { - err="The " + pMasqueradingVector[1] + " column cannot be null"; - return false; - } - } - if(!pMasqueradingVector[2].empty()) - { - if(pJson.isMember(pMasqueradingVector[2])) - { - if(!validJsonOfField(2, pMasqueradingVector[2], pJson[pMasqueradingVector[2]], err, true)) - return false; - } - else - { - err="The " + pMasqueradingVector[2] + " column cannot be null"; - return false; - } - } - } - catch(const Json::LogicError &e) - { - err = e.what(); - return false; - } - return true; -} -bool Tokens::validateJsonForUpdate(const Json::Value &pJson, std::string &err) -{ - if(pJson.isMember("id")) - { - if(!validJsonOfField(0, "id", pJson["id"], err, false)) - return false; - } - else - { - err = "The value of primary key must be set in the json object for update"; - return false; - } - if(pJson.isMember("owner_id")) - { - if(!validJsonOfField(1, "owner_id", pJson["owner_id"], err, false)) - return false; - } - if(pJson.isMember("exp")) - { - if(!validJsonOfField(2, "exp", pJson["exp"], err, false)) - return false; - } - return true; -} -bool Tokens::validateMasqueradedJsonForUpdate(const Json::Value &pJson, - const std::vector &pMasqueradingVector, - std::string &err) -{ - if(pMasqueradingVector.size() != 3) - { - err = "Bad masquerading vector"; - return false; - } - try { - if(!pMasqueradingVector[0].empty() && pJson.isMember(pMasqueradingVector[0])) - { - if(!validJsonOfField(0, pMasqueradingVector[0], pJson[pMasqueradingVector[0]], err, false)) - return false; - } - else - { - err = "The value of primary key must be set in the json object for update"; - return false; - } - if(!pMasqueradingVector[1].empty() && pJson.isMember(pMasqueradingVector[1])) - { - if(!validJsonOfField(1, pMasqueradingVector[1], pJson[pMasqueradingVector[1]], err, false)) - return false; - } - if(!pMasqueradingVector[2].empty() && pJson.isMember(pMasqueradingVector[2])) - { - if(!validJsonOfField(2, pMasqueradingVector[2], pJson[pMasqueradingVector[2]], err, false)) - return false; - } - } - catch(const Json::LogicError &e) - { - err = e.what(); - return false; - } - return true; -} -bool Tokens::validJsonOfField(size_t index, - const std::string &fieldName, - const Json::Value &pJson, - std::string &err, - bool isForCreation) -{ - switch(index) - { - case 0: - if(pJson.isNull()) - { - err="The " + fieldName + " column cannot be null"; - return false; - } - if(isForCreation) - { - err="The automatic primary key cannot be set"; - return false; - } - if(!pJson.isUInt64()) - { - err="Type error in the "+fieldName+" field"; - return false; - } - break; - case 1: - if(pJson.isNull()) - { - err="The " + fieldName + " column cannot be null"; - return false; - } - if(!pJson.isUInt64()) - { - err="Type error in the "+fieldName+" field"; - return false; - } - break; - case 2: - if(pJson.isNull()) - { - err="The " + fieldName + " column cannot be null"; - return false; - } - if(!pJson.isUInt64()) - { - err="Type error in the "+fieldName+" field"; - return false; - } - break; - default: - err="Internal error in the server"; - return false; - break; - } - return true; -} diff --git a/backend/model/Tokens.h b/backend/model/Tokens.h deleted file mode 100644 index a6c0da4..0000000 --- a/backend/model/Tokens.h +++ /dev/null @@ -1,211 +0,0 @@ -/** - * - * Tokens.h - * DO NOT EDIT. This file is generated by drogon_ctl - * - */ - -#pragma once -#include "drogon/orm/Result.h" -#include "drogon/orm/Row.h" -#include "drogon/orm/Field.h" -#include "drogon/orm/SqlBinder.h" -#include "drogon/orm/Mapper.h" -#ifdef __cpp_impl_coroutine -#include -#endif -#include "trantor/utils/Date.h" -#include "trantor/utils/Logger.h" -#include "json/json.h" -#include -#include -#include -#include -#include -#include - -namespace drogon -{ -namespace orm -{ -class DbClient; -using DbClientPtr = std::shared_ptr; -} -} -namespace drogon_model -{ -namespace sqlite3 -{ - -class Tokens -{ - public: - struct Cols - { - static const std::string _id; - static const std::string _owner_id; - static const std::string _exp; - }; - - const static int primaryKeyNumber; - const static std::string tableName; - const static bool hasPrimaryKey; - const static std::string primaryKeyName; - using PrimaryKeyType = uint64_t; - const PrimaryKeyType &getPrimaryKey() const; - - /** - * @brief constructor - * @param r One row of records in the SQL query result. - * @param indexOffset Set the offset to -1 to access all columns by column names, - * otherwise access all columns by offsets. - * @note If the SQL is not a style of 'select * from table_name ...' (select all - * columns by an asterisk), please set the offset to -1. - */ - explicit Tokens(const drogon::orm::Row &r, const ssize_t indexOffset = 0) noexcept; - - /** - * @brief constructor - * @param pJson The json object to construct a new instance. - */ - explicit Tokens(const Json::Value &pJson) noexcept(false); - - /** - * @brief constructor - * @param pJson The json object to construct a new instance. - * @param pMasqueradingVector The aliases of table columns. - */ - Tokens(const Json::Value &pJson, const std::vector &pMasqueradingVector) noexcept(false); - - Tokens() = default; - - void updateByJson(const Json::Value &pJson) noexcept(false); - void updateByMasqueradedJson(const Json::Value &pJson, - const std::vector &pMasqueradingVector) noexcept(false); - static bool validateJsonForCreation(const Json::Value &pJson, std::string &err); - static bool validateMasqueradedJsonForCreation(const Json::Value &, - const std::vector &pMasqueradingVector, - std::string &err); - static bool validateJsonForUpdate(const Json::Value &pJson, std::string &err); - static bool validateMasqueradedJsonForUpdate(const Json::Value &, - const std::vector &pMasqueradingVector, - std::string &err); - static bool validJsonOfField(size_t index, - const std::string &fieldName, - const Json::Value &pJson, - std::string &err, - bool isForCreation); - - /** For column id */ - ///Get the value of the column id, returns the default value if the column is null - const uint64_t &getValueOfId() const noexcept; - ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null - const std::shared_ptr &getId() const noexcept; - ///Set the value of the column id - void setId(const uint64_t &pId) noexcept; - - /** For column owner_id */ - ///Get the value of the column owner_id, returns the default value if the column is null - const uint64_t &getValueOfOwnerId() const noexcept; - ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null - const std::shared_ptr &getOwnerId() const noexcept; - ///Set the value of the column owner_id - void setOwnerId(const uint64_t &pOwnerId) noexcept; - - /** For column exp */ - ///Get the value of the column exp, returns the default value if the column is null - const uint64_t &getValueOfExp() const noexcept; - ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null - const std::shared_ptr &getExp() const noexcept; - ///Set the value of the column exp - void setExp(const uint64_t &pExp) noexcept; - - - static size_t getColumnNumber() noexcept { return 3; } - static const std::string &getColumnName(size_t index) noexcept(false); - - Json::Value toJson() const; - Json::Value toMasqueradedJson(const std::vector &pMasqueradingVector) const; - /// Relationship interfaces - private: - friend drogon::orm::Mapper; -#ifdef __cpp_impl_coroutine - friend drogon::orm::CoroMapper; -#endif - static const std::vector &insertColumns() noexcept; - void outputArgs(drogon::orm::internal::SqlBinder &binder) const; - const std::vector updateColumns() const; - void updateArgs(drogon::orm::internal::SqlBinder &binder) const; - ///For mysql or sqlite3 - void updateId(const uint64_t id); - std::shared_ptr id_; - std::shared_ptr ownerId_; - std::shared_ptr exp_; - struct MetaData - { - const std::string colName_; - const std::string colType_; - const std::string colDatabaseType_; - const ssize_t colLength_; - const bool isAutoVal_; - const bool isPrimaryKey_; - const bool notNull_; - }; - static const std::vector metaData_; - bool dirtyFlag_[3]={ false }; - public: - static const std::string &sqlForFindingByPrimaryKey() - { - static const std::string sql="select * from " + tableName + " where id = ?"; - return sql; - } - - static const std::string &sqlForDeletingByPrimaryKey() - { - static const std::string sql="delete from " + tableName + " where id = ?"; - return sql; - } - std::string sqlForInserting(bool &needSelection) const - { - std::string sql="insert into " + tableName + " ("; - size_t parametersCount = 0; - needSelection = false; - if(dirtyFlag_[1]) - { - sql += "owner_id,"; - ++parametersCount; - } - if(dirtyFlag_[2]) - { - sql += "exp,"; - ++parametersCount; - } - if(parametersCount > 0) - { - sql[sql.length()-1]=')'; - sql += " values ("; - } - else - sql += ") values ("; - - if(dirtyFlag_[1]) - { - sql.append("?,"); - - } - if(dirtyFlag_[2]) - { - sql.append("?,"); - - } - if(parametersCount > 0) - { - sql.resize(sql.length() - 1); - } - sql.append(1, ')'); - LOG_TRACE << sql; - return sql; - } -}; -} // namespace sqlite3 -} // namespace drogon_model diff --git a/backend/model/User.cc b/backend/model/User.cc deleted file mode 100644 index 205b6fa..0000000 --- a/backend/model/User.cc +++ /dev/null @@ -1,1762 +0,0 @@ -/** - * - * User.cc - * DO NOT EDIT. This file is generated by drogon_ctl - * - */ - -#include "User.h" -#include "drogon/utils/Utilities.h" -#include - -using namespace drogon; -using namespace drogon::orm; -using namespace drogon_model::sqlite3; - -const std::string User::Cols::_id = "id"; -const std::string User::Cols::_gitlab = "gitlab"; -const std::string User::Cols::_name = "name"; -const std::string User::Cols::_password = "password"; -const std::string User::Cols::_role = "role"; -const std::string User::Cols::_root_id = "root_id"; -const std::string User::Cols::_tfa_type = "tfa_type"; -const std::string User::Cols::_tfa_secret = "tfa_secret"; -const std::string User::Cols::_gitlab_at = "gitlab_at"; -const std::string User::Cols::_gitlab_rt = "gitlab_rt"; -const std::string User::primaryKeyName = "id"; -const bool User::hasPrimaryKey = true; -const std::string User::tableName = "user"; - -const std::vector User::metaData_={ -{"id","uint64_t","integer",8,1,1,1}, -{"gitlab","uint64_t","integer",8,0,0,1}, -{"name","std::string","text",0,0,0,1}, -{"password","std::string","text",0,0,0,1}, -{"role","uint64_t","integer",8,0,0,1}, -{"root_id","uint64_t","integer",8,0,0,1}, -{"tfa_type","uint64_t","integer",8,0,0,1}, -{"tfa_secret","std::vector","blob",0,0,0,0}, -{"gitlab_at","std::string","text",0,0,0,0}, -{"gitlab_rt","std::string","text",0,0,0,0} -}; -const std::string &User::getColumnName(size_t index) noexcept(false) -{ - assert(index < metaData_.size()); - return metaData_[index].colName_; -} -User::User(const Row &r, const ssize_t indexOffset) noexcept -{ - if(indexOffset < 0) - { - if(!r["id"].isNull()) - { - id_=std::make_shared(r["id"].as()); - } - if(!r["gitlab"].isNull()) - { - gitlab_=std::make_shared(r["gitlab"].as()); - } - if(!r["name"].isNull()) - { - name_=std::make_shared(r["name"].as()); - } - if(!r["password"].isNull()) - { - password_=std::make_shared(r["password"].as()); - } - if(!r["role"].isNull()) - { - role_=std::make_shared(r["role"].as()); - } - if(!r["root_id"].isNull()) - { - rootId_=std::make_shared(r["root_id"].as()); - } - if(!r["tfa_type"].isNull()) - { - tfaType_=std::make_shared(r["tfa_type"].as()); - } - if(!r["tfa_secret"].isNull()) - { - tfaSecret_=std::make_shared>(r["tfa_secret"].as>()); - } - if(!r["gitlab_at"].isNull()) - { - gitlabAt_=std::make_shared(r["gitlab_at"].as()); - } - if(!r["gitlab_rt"].isNull()) - { - gitlabRt_=std::make_shared(r["gitlab_rt"].as()); - } - } - else - { - size_t offset = (size_t)indexOffset; - if(offset + 10 > r.size()) - { - LOG_FATAL << "Invalid SQL result for this model"; - return; - } - size_t index; - index = offset + 0; - if(!r[index].isNull()) - { - id_=std::make_shared(r[index].as()); - } - index = offset + 1; - if(!r[index].isNull()) - { - gitlab_=std::make_shared(r[index].as()); - } - index = offset + 2; - if(!r[index].isNull()) - { - name_=std::make_shared(r[index].as()); - } - index = offset + 3; - if(!r[index].isNull()) - { - password_=std::make_shared(r[index].as()); - } - index = offset + 4; - if(!r[index].isNull()) - { - role_=std::make_shared(r[index].as()); - } - index = offset + 5; - if(!r[index].isNull()) - { - rootId_=std::make_shared(r[index].as()); - } - index = offset + 6; - if(!r[index].isNull()) - { - tfaType_=std::make_shared(r[index].as()); - } - index = offset + 7; - if(!r[index].isNull()) - { - tfaSecret_=std::make_shared>(r[index].as>()); - } - index = offset + 8; - if(!r[index].isNull()) - { - gitlabAt_=std::make_shared(r[index].as()); - } - index = offset + 9; - if(!r[index].isNull()) - { - gitlabRt_=std::make_shared(r[index].as()); - } - } - -} - -User::User(const Json::Value &pJson, const std::vector &pMasqueradingVector) noexcept(false) -{ - if(pMasqueradingVector.size() != 10) - { - LOG_ERROR << "Bad masquerading vector"; - return; - } - if(!pMasqueradingVector[0].empty() && pJson.isMember(pMasqueradingVector[0])) - { - dirtyFlag_[0] = true; - if(!pJson[pMasqueradingVector[0]].isNull()) - { - id_=std::make_shared((uint64_t)pJson[pMasqueradingVector[0]].asUInt64()); - } - } - if(!pMasqueradingVector[1].empty() && pJson.isMember(pMasqueradingVector[1])) - { - dirtyFlag_[1] = true; - if(!pJson[pMasqueradingVector[1]].isNull()) - { - gitlab_=std::make_shared((uint64_t)pJson[pMasqueradingVector[1]].asUInt64()); - } - } - if(!pMasqueradingVector[2].empty() && pJson.isMember(pMasqueradingVector[2])) - { - dirtyFlag_[2] = true; - if(!pJson[pMasqueradingVector[2]].isNull()) - { - name_=std::make_shared(pJson[pMasqueradingVector[2]].asString()); - } - } - if(!pMasqueradingVector[3].empty() && pJson.isMember(pMasqueradingVector[3])) - { - dirtyFlag_[3] = true; - if(!pJson[pMasqueradingVector[3]].isNull()) - { - password_=std::make_shared(pJson[pMasqueradingVector[3]].asString()); - } - } - if(!pMasqueradingVector[4].empty() && pJson.isMember(pMasqueradingVector[4])) - { - dirtyFlag_[4] = true; - if(!pJson[pMasqueradingVector[4]].isNull()) - { - role_=std::make_shared((uint64_t)pJson[pMasqueradingVector[4]].asUInt64()); - } - } - if(!pMasqueradingVector[5].empty() && pJson.isMember(pMasqueradingVector[5])) - { - dirtyFlag_[5] = true; - if(!pJson[pMasqueradingVector[5]].isNull()) - { - rootId_=std::make_shared((uint64_t)pJson[pMasqueradingVector[5]].asUInt64()); - } - } - if(!pMasqueradingVector[6].empty() && pJson.isMember(pMasqueradingVector[6])) - { - dirtyFlag_[6] = true; - if(!pJson[pMasqueradingVector[6]].isNull()) - { - tfaType_=std::make_shared((uint64_t)pJson[pMasqueradingVector[6]].asUInt64()); - } - } - if(!pMasqueradingVector[7].empty() && pJson.isMember(pMasqueradingVector[7])) - { - dirtyFlag_[7] = true; - if(!pJson[pMasqueradingVector[7]].isNull()) - { - auto str = pJson[pMasqueradingVector[7]].asString(); - tfaSecret_=std::make_shared>(drogon::utils::base64DecodeToVector(str)); - } - } - if(!pMasqueradingVector[8].empty() && pJson.isMember(pMasqueradingVector[8])) - { - dirtyFlag_[8] = true; - if(!pJson[pMasqueradingVector[8]].isNull()) - { - gitlabAt_=std::make_shared(pJson[pMasqueradingVector[8]].asString()); - } - } - if(!pMasqueradingVector[9].empty() && pJson.isMember(pMasqueradingVector[9])) - { - dirtyFlag_[9] = true; - if(!pJson[pMasqueradingVector[9]].isNull()) - { - gitlabRt_=std::make_shared(pJson[pMasqueradingVector[9]].asString()); - } - } -} - -User::User(const Json::Value &pJson) noexcept(false) -{ - if(pJson.isMember("id")) - { - dirtyFlag_[0]=true; - if(!pJson["id"].isNull()) - { - id_=std::make_shared((uint64_t)pJson["id"].asUInt64()); - } - } - if(pJson.isMember("gitlab")) - { - dirtyFlag_[1]=true; - if(!pJson["gitlab"].isNull()) - { - gitlab_=std::make_shared((uint64_t)pJson["gitlab"].asUInt64()); - } - } - if(pJson.isMember("name")) - { - dirtyFlag_[2]=true; - if(!pJson["name"].isNull()) - { - name_=std::make_shared(pJson["name"].asString()); - } - } - if(pJson.isMember("password")) - { - dirtyFlag_[3]=true; - if(!pJson["password"].isNull()) - { - password_=std::make_shared(pJson["password"].asString()); - } - } - if(pJson.isMember("role")) - { - dirtyFlag_[4]=true; - if(!pJson["role"].isNull()) - { - role_=std::make_shared((uint64_t)pJson["role"].asUInt64()); - } - } - if(pJson.isMember("root_id")) - { - dirtyFlag_[5]=true; - if(!pJson["root_id"].isNull()) - { - rootId_=std::make_shared((uint64_t)pJson["root_id"].asUInt64()); - } - } - if(pJson.isMember("tfa_type")) - { - dirtyFlag_[6]=true; - if(!pJson["tfa_type"].isNull()) - { - tfaType_=std::make_shared((uint64_t)pJson["tfa_type"].asUInt64()); - } - } - if(pJson.isMember("tfa_secret")) - { - dirtyFlag_[7]=true; - if(!pJson["tfa_secret"].isNull()) - { - auto str = pJson["tfa_secret"].asString(); - tfaSecret_=std::make_shared>(drogon::utils::base64DecodeToVector(str)); - } - } - if(pJson.isMember("gitlab_at")) - { - dirtyFlag_[8]=true; - if(!pJson["gitlab_at"].isNull()) - { - gitlabAt_=std::make_shared(pJson["gitlab_at"].asString()); - } - } - if(pJson.isMember("gitlab_rt")) - { - dirtyFlag_[9]=true; - if(!pJson["gitlab_rt"].isNull()) - { - gitlabRt_=std::make_shared(pJson["gitlab_rt"].asString()); - } - } -} - -void User::updateByMasqueradedJson(const Json::Value &pJson, - const std::vector &pMasqueradingVector) noexcept(false) -{ - if(pMasqueradingVector.size() != 10) - { - LOG_ERROR << "Bad masquerading vector"; - return; - } - if(!pMasqueradingVector[0].empty() && pJson.isMember(pMasqueradingVector[0])) - { - if(!pJson[pMasqueradingVector[0]].isNull()) - { - id_=std::make_shared((uint64_t)pJson[pMasqueradingVector[0]].asUInt64()); - } - } - if(!pMasqueradingVector[1].empty() && pJson.isMember(pMasqueradingVector[1])) - { - dirtyFlag_[1] = true; - if(!pJson[pMasqueradingVector[1]].isNull()) - { - gitlab_=std::make_shared((uint64_t)pJson[pMasqueradingVector[1]].asUInt64()); - } - } - if(!pMasqueradingVector[2].empty() && pJson.isMember(pMasqueradingVector[2])) - { - dirtyFlag_[2] = true; - if(!pJson[pMasqueradingVector[2]].isNull()) - { - name_=std::make_shared(pJson[pMasqueradingVector[2]].asString()); - } - } - if(!pMasqueradingVector[3].empty() && pJson.isMember(pMasqueradingVector[3])) - { - dirtyFlag_[3] = true; - if(!pJson[pMasqueradingVector[3]].isNull()) - { - password_=std::make_shared(pJson[pMasqueradingVector[3]].asString()); - } - } - if(!pMasqueradingVector[4].empty() && pJson.isMember(pMasqueradingVector[4])) - { - dirtyFlag_[4] = true; - if(!pJson[pMasqueradingVector[4]].isNull()) - { - role_=std::make_shared((uint64_t)pJson[pMasqueradingVector[4]].asUInt64()); - } - } - if(!pMasqueradingVector[5].empty() && pJson.isMember(pMasqueradingVector[5])) - { - dirtyFlag_[5] = true; - if(!pJson[pMasqueradingVector[5]].isNull()) - { - rootId_=std::make_shared((uint64_t)pJson[pMasqueradingVector[5]].asUInt64()); - } - } - if(!pMasqueradingVector[6].empty() && pJson.isMember(pMasqueradingVector[6])) - { - dirtyFlag_[6] = true; - if(!pJson[pMasqueradingVector[6]].isNull()) - { - tfaType_=std::make_shared((uint64_t)pJson[pMasqueradingVector[6]].asUInt64()); - } - } - if(!pMasqueradingVector[7].empty() && pJson.isMember(pMasqueradingVector[7])) - { - dirtyFlag_[7] = true; - if(!pJson[pMasqueradingVector[7]].isNull()) - { - auto str = pJson[pMasqueradingVector[7]].asString(); - tfaSecret_=std::make_shared>(drogon::utils::base64DecodeToVector(str)); - } - } - if(!pMasqueradingVector[8].empty() && pJson.isMember(pMasqueradingVector[8])) - { - dirtyFlag_[8] = true; - if(!pJson[pMasqueradingVector[8]].isNull()) - { - gitlabAt_=std::make_shared(pJson[pMasqueradingVector[8]].asString()); - } - } - if(!pMasqueradingVector[9].empty() && pJson.isMember(pMasqueradingVector[9])) - { - dirtyFlag_[9] = true; - if(!pJson[pMasqueradingVector[9]].isNull()) - { - gitlabRt_=std::make_shared(pJson[pMasqueradingVector[9]].asString()); - } - } -} - -void User::updateByJson(const Json::Value &pJson) noexcept(false) -{ - if(pJson.isMember("id")) - { - if(!pJson["id"].isNull()) - { - id_=std::make_shared((uint64_t)pJson["id"].asUInt64()); - } - } - if(pJson.isMember("gitlab")) - { - dirtyFlag_[1] = true; - if(!pJson["gitlab"].isNull()) - { - gitlab_=std::make_shared((uint64_t)pJson["gitlab"].asUInt64()); - } - } - if(pJson.isMember("name")) - { - dirtyFlag_[2] = true; - if(!pJson["name"].isNull()) - { - name_=std::make_shared(pJson["name"].asString()); - } - } - if(pJson.isMember("password")) - { - dirtyFlag_[3] = true; - if(!pJson["password"].isNull()) - { - password_=std::make_shared(pJson["password"].asString()); - } - } - if(pJson.isMember("role")) - { - dirtyFlag_[4] = true; - if(!pJson["role"].isNull()) - { - role_=std::make_shared((uint64_t)pJson["role"].asUInt64()); - } - } - if(pJson.isMember("root_id")) - { - dirtyFlag_[5] = true; - if(!pJson["root_id"].isNull()) - { - rootId_=std::make_shared((uint64_t)pJson["root_id"].asUInt64()); - } - } - if(pJson.isMember("tfa_type")) - { - dirtyFlag_[6] = true; - if(!pJson["tfa_type"].isNull()) - { - tfaType_=std::make_shared((uint64_t)pJson["tfa_type"].asUInt64()); - } - } - if(pJson.isMember("tfa_secret")) - { - dirtyFlag_[7] = true; - if(!pJson["tfa_secret"].isNull()) - { - auto str = pJson["tfa_secret"].asString(); - tfaSecret_=std::make_shared>(drogon::utils::base64DecodeToVector(str)); - } - } - if(pJson.isMember("gitlab_at")) - { - dirtyFlag_[8] = true; - if(!pJson["gitlab_at"].isNull()) - { - gitlabAt_=std::make_shared(pJson["gitlab_at"].asString()); - } - } - if(pJson.isMember("gitlab_rt")) - { - dirtyFlag_[9] = true; - if(!pJson["gitlab_rt"].isNull()) - { - gitlabRt_=std::make_shared(pJson["gitlab_rt"].asString()); - } - } -} - -const uint64_t &User::getValueOfId() const noexcept -{ - const static uint64_t defaultValue = uint64_t(); - if(id_) - return *id_; - return defaultValue; -} -const std::shared_ptr &User::getId() const noexcept -{ - return id_; -} -void User::setId(const uint64_t &pId) noexcept -{ - id_ = std::make_shared(pId); - dirtyFlag_[0] = true; -} -const typename User::PrimaryKeyType & User::getPrimaryKey() const -{ - assert(id_); - return *id_; -} - -const uint64_t &User::getValueOfGitlab() const noexcept -{ - const static uint64_t defaultValue = uint64_t(); - if(gitlab_) - return *gitlab_; - return defaultValue; -} -const std::shared_ptr &User::getGitlab() const noexcept -{ - return gitlab_; -} -void User::setGitlab(const uint64_t &pGitlab) noexcept -{ - gitlab_ = std::make_shared(pGitlab); - dirtyFlag_[1] = true; -} - -const std::string &User::getValueOfName() const noexcept -{ - const static std::string defaultValue = std::string(); - if(name_) - return *name_; - return defaultValue; -} -const std::shared_ptr &User::getName() const noexcept -{ - return name_; -} -void User::setName(const std::string &pName) noexcept -{ - name_ = std::make_shared(pName); - dirtyFlag_[2] = true; -} -void User::setName(std::string &&pName) noexcept -{ - name_ = std::make_shared(std::move(pName)); - dirtyFlag_[2] = true; -} - -const std::string &User::getValueOfPassword() const noexcept -{ - const static std::string defaultValue = std::string(); - if(password_) - return *password_; - return defaultValue; -} -const std::shared_ptr &User::getPassword() const noexcept -{ - return password_; -} -void User::setPassword(const std::string &pPassword) noexcept -{ - password_ = std::make_shared(pPassword); - dirtyFlag_[3] = true; -} -void User::setPassword(std::string &&pPassword) noexcept -{ - password_ = std::make_shared(std::move(pPassword)); - dirtyFlag_[3] = true; -} - -const uint64_t &User::getValueOfRole() const noexcept -{ - const static uint64_t defaultValue = uint64_t(); - if(role_) - return *role_; - return defaultValue; -} -const std::shared_ptr &User::getRole() const noexcept -{ - return role_; -} -void User::setRole(const uint64_t &pRole) noexcept -{ - role_ = std::make_shared(pRole); - dirtyFlag_[4] = true; -} - -const uint64_t &User::getValueOfRootId() const noexcept -{ - const static uint64_t defaultValue = uint64_t(); - if(rootId_) - return *rootId_; - return defaultValue; -} -const std::shared_ptr &User::getRootId() const noexcept -{ - return rootId_; -} -void User::setRootId(const uint64_t &pRootId) noexcept -{ - rootId_ = std::make_shared(pRootId); - dirtyFlag_[5] = true; -} - -const uint64_t &User::getValueOfTfaType() const noexcept -{ - const static uint64_t defaultValue = uint64_t(); - if(tfaType_) - return *tfaType_; - return defaultValue; -} -const std::shared_ptr &User::getTfaType() const noexcept -{ - return tfaType_; -} -void User::setTfaType(const uint64_t &pTfaType) noexcept -{ - tfaType_ = std::make_shared(pTfaType); - dirtyFlag_[6] = true; -} - -const std::vector &User::getValueOfTfaSecret() const noexcept -{ - const static std::vector defaultValue = std::vector(); - if(tfaSecret_) - return *tfaSecret_; - return defaultValue; -} -std::string User::getValueOfTfaSecretAsString() const noexcept -{ - const static std::string defaultValue = std::string(); - if(tfaSecret_) - return std::string(tfaSecret_->data(),tfaSecret_->size()); - return defaultValue; -} -const std::shared_ptr> &User::getTfaSecret() const noexcept -{ - return tfaSecret_; -} -void User::setTfaSecret(const std::vector &pTfaSecret) noexcept -{ - tfaSecret_ = std::make_shared>(pTfaSecret); - dirtyFlag_[7] = true; -} -void User::setTfaSecret(const std::string &pTfaSecret) noexcept -{ - tfaSecret_ = std::make_shared>(pTfaSecret.c_str(),pTfaSecret.c_str()+pTfaSecret.length()); - dirtyFlag_[7] = true; -} -void User::setTfaSecretToNull() noexcept -{ - tfaSecret_.reset(); - dirtyFlag_[7] = true; -} - -const std::string &User::getValueOfGitlabAt() const noexcept -{ - const static std::string defaultValue = std::string(); - if(gitlabAt_) - return *gitlabAt_; - return defaultValue; -} -const std::shared_ptr &User::getGitlabAt() const noexcept -{ - return gitlabAt_; -} -void User::setGitlabAt(const std::string &pGitlabAt) noexcept -{ - gitlabAt_ = std::make_shared(pGitlabAt); - dirtyFlag_[8] = true; -} -void User::setGitlabAt(std::string &&pGitlabAt) noexcept -{ - gitlabAt_ = std::make_shared(std::move(pGitlabAt)); - dirtyFlag_[8] = true; -} -void User::setGitlabAtToNull() noexcept -{ - gitlabAt_.reset(); - dirtyFlag_[8] = true; -} - -const std::string &User::getValueOfGitlabRt() const noexcept -{ - const static std::string defaultValue = std::string(); - if(gitlabRt_) - return *gitlabRt_; - return defaultValue; -} -const std::shared_ptr &User::getGitlabRt() const noexcept -{ - return gitlabRt_; -} -void User::setGitlabRt(const std::string &pGitlabRt) noexcept -{ - gitlabRt_ = std::make_shared(pGitlabRt); - dirtyFlag_[9] = true; -} -void User::setGitlabRt(std::string &&pGitlabRt) noexcept -{ - gitlabRt_ = std::make_shared(std::move(pGitlabRt)); - dirtyFlag_[9] = true; -} -void User::setGitlabRtToNull() noexcept -{ - gitlabRt_.reset(); - dirtyFlag_[9] = true; -} - -void User::updateId(const uint64_t id) -{ - id_ = std::make_shared(id); -} - -const std::vector &User::insertColumns() noexcept -{ - static const std::vector inCols={ - "gitlab", - "name", - "password", - "role", - "root_id", - "tfa_type", - "tfa_secret", - "gitlab_at", - "gitlab_rt" - }; - return inCols; -} - -void User::outputArgs(drogon::orm::internal::SqlBinder &binder) const -{ - if(dirtyFlag_[1]) - { - if(getGitlab()) - { - binder << getValueOfGitlab(); - } - else - { - binder << nullptr; - } - } - if(dirtyFlag_[2]) - { - if(getName()) - { - binder << getValueOfName(); - } - else - { - binder << nullptr; - } - } - if(dirtyFlag_[3]) - { - if(getPassword()) - { - binder << getValueOfPassword(); - } - else - { - binder << nullptr; - } - } - if(dirtyFlag_[4]) - { - if(getRole()) - { - binder << getValueOfRole(); - } - else - { - binder << nullptr; - } - } - if(dirtyFlag_[5]) - { - if(getRootId()) - { - binder << getValueOfRootId(); - } - else - { - binder << nullptr; - } - } - if(dirtyFlag_[6]) - { - if(getTfaType()) - { - binder << getValueOfTfaType(); - } - else - { - binder << nullptr; - } - } - if(dirtyFlag_[7]) - { - if(getTfaSecret()) - { - binder << getValueOfTfaSecret(); - } - else - { - binder << nullptr; - } - } - if(dirtyFlag_[8]) - { - if(getGitlabAt()) - { - binder << getValueOfGitlabAt(); - } - else - { - binder << nullptr; - } - } - if(dirtyFlag_[9]) - { - if(getGitlabRt()) - { - binder << getValueOfGitlabRt(); - } - else - { - binder << nullptr; - } - } -} - -const std::vector User::updateColumns() const -{ - std::vector ret; - if(dirtyFlag_[1]) - { - ret.push_back(getColumnName(1)); - } - if(dirtyFlag_[2]) - { - ret.push_back(getColumnName(2)); - } - if(dirtyFlag_[3]) - { - ret.push_back(getColumnName(3)); - } - if(dirtyFlag_[4]) - { - ret.push_back(getColumnName(4)); - } - if(dirtyFlag_[5]) - { - ret.push_back(getColumnName(5)); - } - if(dirtyFlag_[6]) - { - ret.push_back(getColumnName(6)); - } - if(dirtyFlag_[7]) - { - ret.push_back(getColumnName(7)); - } - if(dirtyFlag_[8]) - { - ret.push_back(getColumnName(8)); - } - if(dirtyFlag_[9]) - { - ret.push_back(getColumnName(9)); - } - return ret; -} - -void User::updateArgs(drogon::orm::internal::SqlBinder &binder) const -{ - if(dirtyFlag_[1]) - { - if(getGitlab()) - { - binder << getValueOfGitlab(); - } - else - { - binder << nullptr; - } - } - if(dirtyFlag_[2]) - { - if(getName()) - { - binder << getValueOfName(); - } - else - { - binder << nullptr; - } - } - if(dirtyFlag_[3]) - { - if(getPassword()) - { - binder << getValueOfPassword(); - } - else - { - binder << nullptr; - } - } - if(dirtyFlag_[4]) - { - if(getRole()) - { - binder << getValueOfRole(); - } - else - { - binder << nullptr; - } - } - if(dirtyFlag_[5]) - { - if(getRootId()) - { - binder << getValueOfRootId(); - } - else - { - binder << nullptr; - } - } - if(dirtyFlag_[6]) - { - if(getTfaType()) - { - binder << getValueOfTfaType(); - } - else - { - binder << nullptr; - } - } - if(dirtyFlag_[7]) - { - if(getTfaSecret()) - { - binder << getValueOfTfaSecret(); - } - else - { - binder << nullptr; - } - } - if(dirtyFlag_[8]) - { - if(getGitlabAt()) - { - binder << getValueOfGitlabAt(); - } - else - { - binder << nullptr; - } - } - if(dirtyFlag_[9]) - { - if(getGitlabRt()) - { - binder << getValueOfGitlabRt(); - } - else - { - binder << nullptr; - } - } -} -Json::Value User::toJson() const -{ - Json::Value ret; - if(getId()) - { - ret["id"]=(Json::UInt64)getValueOfId(); - } - else - { - ret["id"]=Json::Value(); - } - if(getGitlab()) - { - ret["gitlab"]=(Json::UInt64)getValueOfGitlab(); - } - else - { - ret["gitlab"]=Json::Value(); - } - if(getName()) - { - ret["name"]=getValueOfName(); - } - else - { - ret["name"]=Json::Value(); - } - if(getPassword()) - { - ret["password"]=getValueOfPassword(); - } - else - { - ret["password"]=Json::Value(); - } - if(getRole()) - { - ret["role"]=(Json::UInt64)getValueOfRole(); - } - else - { - ret["role"]=Json::Value(); - } - if(getRootId()) - { - ret["root_id"]=(Json::UInt64)getValueOfRootId(); - } - else - { - ret["root_id"]=Json::Value(); - } - if(getTfaType()) - { - ret["tfa_type"]=(Json::UInt64)getValueOfTfaType(); - } - else - { - ret["tfa_type"]=Json::Value(); - } - if(getTfaSecret()) - { - ret["tfa_secret"]=drogon::utils::base64Encode((const unsigned char *)getTfaSecret()->data(),getTfaSecret()->size()); - } - else - { - ret["tfa_secret"]=Json::Value(); - } - if(getGitlabAt()) - { - ret["gitlab_at"]=getValueOfGitlabAt(); - } - else - { - ret["gitlab_at"]=Json::Value(); - } - if(getGitlabRt()) - { - ret["gitlab_rt"]=getValueOfGitlabRt(); - } - else - { - ret["gitlab_rt"]=Json::Value(); - } - return ret; -} - -Json::Value User::toMasqueradedJson( - const std::vector &pMasqueradingVector) const -{ - Json::Value ret; - if(pMasqueradingVector.size() == 10) - { - if(!pMasqueradingVector[0].empty()) - { - if(getId()) - { - ret[pMasqueradingVector[0]]=(Json::UInt64)getValueOfId(); - } - else - { - ret[pMasqueradingVector[0]]=Json::Value(); - } - } - if(!pMasqueradingVector[1].empty()) - { - if(getGitlab()) - { - ret[pMasqueradingVector[1]]=(Json::UInt64)getValueOfGitlab(); - } - else - { - ret[pMasqueradingVector[1]]=Json::Value(); - } - } - if(!pMasqueradingVector[2].empty()) - { - if(getName()) - { - ret[pMasqueradingVector[2]]=getValueOfName(); - } - else - { - ret[pMasqueradingVector[2]]=Json::Value(); - } - } - if(!pMasqueradingVector[3].empty()) - { - if(getPassword()) - { - ret[pMasqueradingVector[3]]=getValueOfPassword(); - } - else - { - ret[pMasqueradingVector[3]]=Json::Value(); - } - } - if(!pMasqueradingVector[4].empty()) - { - if(getRole()) - { - ret[pMasqueradingVector[4]]=(Json::UInt64)getValueOfRole(); - } - else - { - ret[pMasqueradingVector[4]]=Json::Value(); - } - } - if(!pMasqueradingVector[5].empty()) - { - if(getRootId()) - { - ret[pMasqueradingVector[5]]=(Json::UInt64)getValueOfRootId(); - } - else - { - ret[pMasqueradingVector[5]]=Json::Value(); - } - } - if(!pMasqueradingVector[6].empty()) - { - if(getTfaType()) - { - ret[pMasqueradingVector[6]]=(Json::UInt64)getValueOfTfaType(); - } - else - { - ret[pMasqueradingVector[6]]=Json::Value(); - } - } - if(!pMasqueradingVector[7].empty()) - { - if(getTfaSecret()) - { - ret[pMasqueradingVector[7]]=drogon::utils::base64Encode((const unsigned char *)getTfaSecret()->data(),getTfaSecret()->size()); - } - else - { - ret[pMasqueradingVector[7]]=Json::Value(); - } - } - if(!pMasqueradingVector[8].empty()) - { - if(getGitlabAt()) - { - ret[pMasqueradingVector[8]]=getValueOfGitlabAt(); - } - else - { - ret[pMasqueradingVector[8]]=Json::Value(); - } - } - if(!pMasqueradingVector[9].empty()) - { - if(getGitlabRt()) - { - ret[pMasqueradingVector[9]]=getValueOfGitlabRt(); - } - else - { - ret[pMasqueradingVector[9]]=Json::Value(); - } - } - return ret; - } - LOG_ERROR << "Masquerade failed"; - if(getId()) - { - ret["id"]=(Json::UInt64)getValueOfId(); - } - else - { - ret["id"]=Json::Value(); - } - if(getGitlab()) - { - ret["gitlab"]=(Json::UInt64)getValueOfGitlab(); - } - else - { - ret["gitlab"]=Json::Value(); - } - if(getName()) - { - ret["name"]=getValueOfName(); - } - else - { - ret["name"]=Json::Value(); - } - if(getPassword()) - { - ret["password"]=getValueOfPassword(); - } - else - { - ret["password"]=Json::Value(); - } - if(getRole()) - { - ret["role"]=(Json::UInt64)getValueOfRole(); - } - else - { - ret["role"]=Json::Value(); - } - if(getRootId()) - { - ret["root_id"]=(Json::UInt64)getValueOfRootId(); - } - else - { - ret["root_id"]=Json::Value(); - } - if(getTfaType()) - { - ret["tfa_type"]=(Json::UInt64)getValueOfTfaType(); - } - else - { - ret["tfa_type"]=Json::Value(); - } - if(getTfaSecret()) - { - ret["tfa_secret"]=drogon::utils::base64Encode((const unsigned char *)getTfaSecret()->data(),getTfaSecret()->size()); - } - else - { - ret["tfa_secret"]=Json::Value(); - } - if(getGitlabAt()) - { - ret["gitlab_at"]=getValueOfGitlabAt(); - } - else - { - ret["gitlab_at"]=Json::Value(); - } - if(getGitlabRt()) - { - ret["gitlab_rt"]=getValueOfGitlabRt(); - } - else - { - ret["gitlab_rt"]=Json::Value(); - } - return ret; -} - -bool User::validateJsonForCreation(const Json::Value &pJson, std::string &err) -{ - if(pJson.isMember("id")) - { - if(!validJsonOfField(0, "id", pJson["id"], err, true)) - return false; - } - if(pJson.isMember("gitlab")) - { - if(!validJsonOfField(1, "gitlab", pJson["gitlab"], err, true)) - return false; - } - else - { - err="The gitlab column cannot be null"; - return false; - } - if(pJson.isMember("name")) - { - if(!validJsonOfField(2, "name", pJson["name"], err, true)) - return false; - } - else - { - err="The name column cannot be null"; - return false; - } - if(pJson.isMember("password")) - { - if(!validJsonOfField(3, "password", pJson["password"], err, true)) - return false; - } - else - { - err="The password column cannot be null"; - return false; - } - if(pJson.isMember("role")) - { - if(!validJsonOfField(4, "role", pJson["role"], err, true)) - return false; - } - else - { - err="The role column cannot be null"; - return false; - } - if(pJson.isMember("root_id")) - { - if(!validJsonOfField(5, "root_id", pJson["root_id"], err, true)) - return false; - } - else - { - err="The root_id column cannot be null"; - return false; - } - if(pJson.isMember("tfa_type")) - { - if(!validJsonOfField(6, "tfa_type", pJson["tfa_type"], err, true)) - return false; - } - else - { - err="The tfa_type column cannot be null"; - return false; - } - if(pJson.isMember("tfa_secret")) - { - if(!validJsonOfField(7, "tfa_secret", pJson["tfa_secret"], err, true)) - return false; - } - if(pJson.isMember("gitlab_at")) - { - if(!validJsonOfField(8, "gitlab_at", pJson["gitlab_at"], err, true)) - return false; - } - if(pJson.isMember("gitlab_rt")) - { - if(!validJsonOfField(9, "gitlab_rt", pJson["gitlab_rt"], err, true)) - return false; - } - return true; -} -bool User::validateMasqueradedJsonForCreation(const Json::Value &pJson, - const std::vector &pMasqueradingVector, - std::string &err) -{ - if(pMasqueradingVector.size() != 10) - { - err = "Bad masquerading vector"; - return false; - } - try { - if(!pMasqueradingVector[0].empty()) - { - if(pJson.isMember(pMasqueradingVector[0])) - { - if(!validJsonOfField(0, pMasqueradingVector[0], pJson[pMasqueradingVector[0]], err, true)) - return false; - } - } - if(!pMasqueradingVector[1].empty()) - { - if(pJson.isMember(pMasqueradingVector[1])) - { - if(!validJsonOfField(1, pMasqueradingVector[1], pJson[pMasqueradingVector[1]], err, true)) - return false; - } - else - { - err="The " + pMasqueradingVector[1] + " column cannot be null"; - return false; - } - } - if(!pMasqueradingVector[2].empty()) - { - if(pJson.isMember(pMasqueradingVector[2])) - { - if(!validJsonOfField(2, pMasqueradingVector[2], pJson[pMasqueradingVector[2]], err, true)) - return false; - } - else - { - err="The " + pMasqueradingVector[2] + " column cannot be null"; - return false; - } - } - if(!pMasqueradingVector[3].empty()) - { - if(pJson.isMember(pMasqueradingVector[3])) - { - if(!validJsonOfField(3, pMasqueradingVector[3], pJson[pMasqueradingVector[3]], err, true)) - return false; - } - else - { - err="The " + pMasqueradingVector[3] + " column cannot be null"; - return false; - } - } - if(!pMasqueradingVector[4].empty()) - { - if(pJson.isMember(pMasqueradingVector[4])) - { - if(!validJsonOfField(4, pMasqueradingVector[4], pJson[pMasqueradingVector[4]], err, true)) - return false; - } - else - { - err="The " + pMasqueradingVector[4] + " column cannot be null"; - return false; - } - } - if(!pMasqueradingVector[5].empty()) - { - if(pJson.isMember(pMasqueradingVector[5])) - { - if(!validJsonOfField(5, pMasqueradingVector[5], pJson[pMasqueradingVector[5]], err, true)) - return false; - } - else - { - err="The " + pMasqueradingVector[5] + " column cannot be null"; - return false; - } - } - if(!pMasqueradingVector[6].empty()) - { - if(pJson.isMember(pMasqueradingVector[6])) - { - if(!validJsonOfField(6, pMasqueradingVector[6], pJson[pMasqueradingVector[6]], err, true)) - return false; - } - else - { - err="The " + pMasqueradingVector[6] + " column cannot be null"; - return false; - } - } - if(!pMasqueradingVector[7].empty()) - { - if(pJson.isMember(pMasqueradingVector[7])) - { - if(!validJsonOfField(7, pMasqueradingVector[7], pJson[pMasqueradingVector[7]], err, true)) - return false; - } - } - if(!pMasqueradingVector[8].empty()) - { - if(pJson.isMember(pMasqueradingVector[8])) - { - if(!validJsonOfField(8, pMasqueradingVector[8], pJson[pMasqueradingVector[8]], err, true)) - return false; - } - } - if(!pMasqueradingVector[9].empty()) - { - if(pJson.isMember(pMasqueradingVector[9])) - { - if(!validJsonOfField(9, pMasqueradingVector[9], pJson[pMasqueradingVector[9]], err, true)) - return false; - } - } - } - catch(const Json::LogicError &e) - { - err = e.what(); - return false; - } - return true; -} -bool User::validateJsonForUpdate(const Json::Value &pJson, std::string &err) -{ - if(pJson.isMember("id")) - { - if(!validJsonOfField(0, "id", pJson["id"], err, false)) - return false; - } - else - { - err = "The value of primary key must be set in the json object for update"; - return false; - } - if(pJson.isMember("gitlab")) - { - if(!validJsonOfField(1, "gitlab", pJson["gitlab"], err, false)) - return false; - } - if(pJson.isMember("name")) - { - if(!validJsonOfField(2, "name", pJson["name"], err, false)) - return false; - } - if(pJson.isMember("password")) - { - if(!validJsonOfField(3, "password", pJson["password"], err, false)) - return false; - } - if(pJson.isMember("role")) - { - if(!validJsonOfField(4, "role", pJson["role"], err, false)) - return false; - } - if(pJson.isMember("root_id")) - { - if(!validJsonOfField(5, "root_id", pJson["root_id"], err, false)) - return false; - } - if(pJson.isMember("tfa_type")) - { - if(!validJsonOfField(6, "tfa_type", pJson["tfa_type"], err, false)) - return false; - } - if(pJson.isMember("tfa_secret")) - { - if(!validJsonOfField(7, "tfa_secret", pJson["tfa_secret"], err, false)) - return false; - } - if(pJson.isMember("gitlab_at")) - { - if(!validJsonOfField(8, "gitlab_at", pJson["gitlab_at"], err, false)) - return false; - } - if(pJson.isMember("gitlab_rt")) - { - if(!validJsonOfField(9, "gitlab_rt", pJson["gitlab_rt"], err, false)) - return false; - } - return true; -} -bool User::validateMasqueradedJsonForUpdate(const Json::Value &pJson, - const std::vector &pMasqueradingVector, - std::string &err) -{ - if(pMasqueradingVector.size() != 10) - { - err = "Bad masquerading vector"; - return false; - } - try { - if(!pMasqueradingVector[0].empty() && pJson.isMember(pMasqueradingVector[0])) - { - if(!validJsonOfField(0, pMasqueradingVector[0], pJson[pMasqueradingVector[0]], err, false)) - return false; - } - else - { - err = "The value of primary key must be set in the json object for update"; - return false; - } - if(!pMasqueradingVector[1].empty() && pJson.isMember(pMasqueradingVector[1])) - { - if(!validJsonOfField(1, pMasqueradingVector[1], pJson[pMasqueradingVector[1]], err, false)) - return false; - } - if(!pMasqueradingVector[2].empty() && pJson.isMember(pMasqueradingVector[2])) - { - if(!validJsonOfField(2, pMasqueradingVector[2], pJson[pMasqueradingVector[2]], err, false)) - return false; - } - if(!pMasqueradingVector[3].empty() && pJson.isMember(pMasqueradingVector[3])) - { - if(!validJsonOfField(3, pMasqueradingVector[3], pJson[pMasqueradingVector[3]], err, false)) - return false; - } - if(!pMasqueradingVector[4].empty() && pJson.isMember(pMasqueradingVector[4])) - { - if(!validJsonOfField(4, pMasqueradingVector[4], pJson[pMasqueradingVector[4]], err, false)) - return false; - } - if(!pMasqueradingVector[5].empty() && pJson.isMember(pMasqueradingVector[5])) - { - if(!validJsonOfField(5, pMasqueradingVector[5], pJson[pMasqueradingVector[5]], err, false)) - return false; - } - if(!pMasqueradingVector[6].empty() && pJson.isMember(pMasqueradingVector[6])) - { - if(!validJsonOfField(6, pMasqueradingVector[6], pJson[pMasqueradingVector[6]], err, false)) - return false; - } - if(!pMasqueradingVector[7].empty() && pJson.isMember(pMasqueradingVector[7])) - { - if(!validJsonOfField(7, pMasqueradingVector[7], pJson[pMasqueradingVector[7]], err, false)) - return false; - } - if(!pMasqueradingVector[8].empty() && pJson.isMember(pMasqueradingVector[8])) - { - if(!validJsonOfField(8, pMasqueradingVector[8], pJson[pMasqueradingVector[8]], err, false)) - return false; - } - if(!pMasqueradingVector[9].empty() && pJson.isMember(pMasqueradingVector[9])) - { - if(!validJsonOfField(9, pMasqueradingVector[9], pJson[pMasqueradingVector[9]], err, false)) - return false; - } - } - catch(const Json::LogicError &e) - { - err = e.what(); - return false; - } - return true; -} -bool User::validJsonOfField(size_t index, - const std::string &fieldName, - const Json::Value &pJson, - std::string &err, - bool isForCreation) -{ - switch(index) - { - case 0: - if(pJson.isNull()) - { - err="The " + fieldName + " column cannot be null"; - return false; - } - if(isForCreation) - { - err="The automatic primary key cannot be set"; - return false; - } - if(!pJson.isUInt64()) - { - err="Type error in the "+fieldName+" field"; - return false; - } - break; - case 1: - if(pJson.isNull()) - { - err="The " + fieldName + " column cannot be null"; - return false; - } - if(!pJson.isUInt64()) - { - err="Type error in the "+fieldName+" field"; - return false; - } - break; - case 2: - if(pJson.isNull()) - { - err="The " + fieldName + " column cannot be null"; - return false; - } - if(!pJson.isString()) - { - err="Type error in the "+fieldName+" field"; - return false; - } - break; - case 3: - if(pJson.isNull()) - { - err="The " + fieldName + " column cannot be null"; - return false; - } - if(!pJson.isString()) - { - err="Type error in the "+fieldName+" field"; - return false; - } - break; - case 4: - if(pJson.isNull()) - { - err="The " + fieldName + " column cannot be null"; - return false; - } - if(!pJson.isUInt64()) - { - err="Type error in the "+fieldName+" field"; - return false; - } - break; - case 5: - if(pJson.isNull()) - { - err="The " + fieldName + " column cannot be null"; - return false; - } - if(!pJson.isUInt64()) - { - err="Type error in the "+fieldName+" field"; - return false; - } - break; - case 6: - if(pJson.isNull()) - { - err="The " + fieldName + " column cannot be null"; - return false; - } - if(!pJson.isUInt64()) - { - err="Type error in the "+fieldName+" field"; - return false; - } - break; - case 7: - if(pJson.isNull()) - { - return true; - } - if(!pJson.isString()) - { - err="Type error in the "+fieldName+" field"; - return false; - } - break; - case 8: - if(pJson.isNull()) - { - return true; - } - if(!pJson.isString()) - { - err="Type error in the "+fieldName+" field"; - return false; - } - break; - case 9: - if(pJson.isNull()) - { - return true; - } - if(!pJson.isString()) - { - err="Type error in the "+fieldName+" field"; - return false; - } - break; - default: - err="Internal error in the server"; - return false; - break; - } - return true; -} diff --git a/backend/model/User.h b/backend/model/User.h deleted file mode 100644 index e5af610..0000000 --- a/backend/model/User.h +++ /dev/null @@ -1,361 +0,0 @@ -/** - * - * User.h - * DO NOT EDIT. This file is generated by drogon_ctl - * - */ - -#pragma once -#include "drogon/orm/Result.h" -#include "drogon/orm/Row.h" -#include "drogon/orm/Field.h" -#include "drogon/orm/SqlBinder.h" -#include "drogon/orm/Mapper.h" -#ifdef __cpp_impl_coroutine -#include -#endif -#include "trantor/utils/Date.h" -#include "trantor/utils/Logger.h" -#include "json/json.h" -#include -#include -#include -#include -#include -#include - -namespace drogon -{ -namespace orm -{ -class DbClient; -using DbClientPtr = std::shared_ptr; -} -} -namespace drogon_model -{ -namespace sqlite3 -{ - -class User -{ - public: - struct Cols - { - static const std::string _id; - static const std::string _gitlab; - static const std::string _name; - static const std::string _password; - static const std::string _role; - static const std::string _root_id; - static const std::string _tfa_type; - static const std::string _tfa_secret; - static const std::string _gitlab_at; - static const std::string _gitlab_rt; - }; - - const static int primaryKeyNumber; - const static std::string tableName; - const static bool hasPrimaryKey; - const static std::string primaryKeyName; - using PrimaryKeyType = uint64_t; - const PrimaryKeyType &getPrimaryKey() const; - - /** - * @brief constructor - * @param r One row of records in the SQL query result. - * @param indexOffset Set the offset to -1 to access all columns by column names, - * otherwise access all columns by offsets. - * @note If the SQL is not a style of 'select * from table_name ...' (select all - * columns by an asterisk), please set the offset to -1. - */ - explicit User(const drogon::orm::Row &r, const ssize_t indexOffset = 0) noexcept; - - /** - * @brief constructor - * @param pJson The json object to construct a new instance. - */ - explicit User(const Json::Value &pJson) noexcept(false); - - /** - * @brief constructor - * @param pJson The json object to construct a new instance. - * @param pMasqueradingVector The aliases of table columns. - */ - User(const Json::Value &pJson, const std::vector &pMasqueradingVector) noexcept(false); - - User() = default; - - void updateByJson(const Json::Value &pJson) noexcept(false); - void updateByMasqueradedJson(const Json::Value &pJson, - const std::vector &pMasqueradingVector) noexcept(false); - static bool validateJsonForCreation(const Json::Value &pJson, std::string &err); - static bool validateMasqueradedJsonForCreation(const Json::Value &, - const std::vector &pMasqueradingVector, - std::string &err); - static bool validateJsonForUpdate(const Json::Value &pJson, std::string &err); - static bool validateMasqueradedJsonForUpdate(const Json::Value &, - const std::vector &pMasqueradingVector, - std::string &err); - static bool validJsonOfField(size_t index, - const std::string &fieldName, - const Json::Value &pJson, - std::string &err, - bool isForCreation); - - /** For column id */ - ///Get the value of the column id, returns the default value if the column is null - const uint64_t &getValueOfId() const noexcept; - ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null - const std::shared_ptr &getId() const noexcept; - ///Set the value of the column id - void setId(const uint64_t &pId) noexcept; - - /** For column gitlab */ - ///Get the value of the column gitlab, returns the default value if the column is null - const uint64_t &getValueOfGitlab() const noexcept; - ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null - const std::shared_ptr &getGitlab() const noexcept; - ///Set the value of the column gitlab - void setGitlab(const uint64_t &pGitlab) noexcept; - - /** For column name */ - ///Get the value of the column name, returns the default value if the column is null - const std::string &getValueOfName() const noexcept; - ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null - const std::shared_ptr &getName() const noexcept; - ///Set the value of the column name - void setName(const std::string &pName) noexcept; - void setName(std::string &&pName) noexcept; - - /** For column password */ - ///Get the value of the column password, returns the default value if the column is null - const std::string &getValueOfPassword() const noexcept; - ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null - const std::shared_ptr &getPassword() const noexcept; - ///Set the value of the column password - void setPassword(const std::string &pPassword) noexcept; - void setPassword(std::string &&pPassword) noexcept; - - /** For column role */ - ///Get the value of the column role, returns the default value if the column is null - const uint64_t &getValueOfRole() const noexcept; - ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null - const std::shared_ptr &getRole() const noexcept; - ///Set the value of the column role - void setRole(const uint64_t &pRole) noexcept; - - /** For column root_id */ - ///Get the value of the column root_id, returns the default value if the column is null - const uint64_t &getValueOfRootId() const noexcept; - ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null - const std::shared_ptr &getRootId() const noexcept; - ///Set the value of the column root_id - void setRootId(const uint64_t &pRootId) noexcept; - - /** For column tfa_type */ - ///Get the value of the column tfa_type, returns the default value if the column is null - const uint64_t &getValueOfTfaType() const noexcept; - ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null - const std::shared_ptr &getTfaType() const noexcept; - ///Set the value of the column tfa_type - void setTfaType(const uint64_t &pTfaType) noexcept; - - /** For column tfa_secret */ - ///Get the value of the column tfa_secret, returns the default value if the column is null - const std::vector &getValueOfTfaSecret() const noexcept; - ///Return the column value by std::string with binary data - std::string getValueOfTfaSecretAsString() const noexcept; - ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null - const std::shared_ptr> &getTfaSecret() const noexcept; - ///Set the value of the column tfa_secret - void setTfaSecret(const std::vector &pTfaSecret) noexcept; - void setTfaSecret(const std::string &pTfaSecret) noexcept; - void setTfaSecretToNull() noexcept; - - /** For column gitlab_at */ - ///Get the value of the column gitlab_at, returns the default value if the column is null - const std::string &getValueOfGitlabAt() const noexcept; - ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null - const std::shared_ptr &getGitlabAt() const noexcept; - ///Set the value of the column gitlab_at - void setGitlabAt(const std::string &pGitlabAt) noexcept; - void setGitlabAt(std::string &&pGitlabAt) noexcept; - void setGitlabAtToNull() noexcept; - - /** For column gitlab_rt */ - ///Get the value of the column gitlab_rt, returns the default value if the column is null - const std::string &getValueOfGitlabRt() const noexcept; - ///Return a shared_ptr object pointing to the column const value, or an empty shared_ptr object if the column is null - const std::shared_ptr &getGitlabRt() const noexcept; - ///Set the value of the column gitlab_rt - void setGitlabRt(const std::string &pGitlabRt) noexcept; - void setGitlabRt(std::string &&pGitlabRt) noexcept; - void setGitlabRtToNull() noexcept; - - - static size_t getColumnNumber() noexcept { return 10; } - static const std::string &getColumnName(size_t index) noexcept(false); - - Json::Value toJson() const; - Json::Value toMasqueradedJson(const std::vector &pMasqueradingVector) const; - /// Relationship interfaces - private: - friend drogon::orm::Mapper; -#ifdef __cpp_impl_coroutine - friend drogon::orm::CoroMapper; -#endif - static const std::vector &insertColumns() noexcept; - void outputArgs(drogon::orm::internal::SqlBinder &binder) const; - const std::vector updateColumns() const; - void updateArgs(drogon::orm::internal::SqlBinder &binder) const; - ///For mysql or sqlite3 - void updateId(const uint64_t id); - std::shared_ptr id_; - std::shared_ptr gitlab_; - std::shared_ptr name_; - std::shared_ptr password_; - std::shared_ptr role_; - std::shared_ptr rootId_; - std::shared_ptr tfaType_; - std::shared_ptr> tfaSecret_; - std::shared_ptr gitlabAt_; - std::shared_ptr gitlabRt_; - struct MetaData - { - const std::string colName_; - const std::string colType_; - const std::string colDatabaseType_; - const ssize_t colLength_; - const bool isAutoVal_; - const bool isPrimaryKey_; - const bool notNull_; - }; - static const std::vector metaData_; - bool dirtyFlag_[10]={ false }; - public: - static const std::string &sqlForFindingByPrimaryKey() - { - static const std::string sql="select * from " + tableName + " where id = ?"; - return sql; - } - - static const std::string &sqlForDeletingByPrimaryKey() - { - static const std::string sql="delete from " + tableName + " where id = ?"; - return sql; - } - std::string sqlForInserting(bool &needSelection) const - { - std::string sql="insert into " + tableName + " ("; - size_t parametersCount = 0; - needSelection = false; - if(dirtyFlag_[1]) - { - sql += "gitlab,"; - ++parametersCount; - } - if(dirtyFlag_[2]) - { - sql += "name,"; - ++parametersCount; - } - if(dirtyFlag_[3]) - { - sql += "password,"; - ++parametersCount; - } - if(dirtyFlag_[4]) - { - sql += "role,"; - ++parametersCount; - } - if(dirtyFlag_[5]) - { - sql += "root_id,"; - ++parametersCount; - } - if(dirtyFlag_[6]) - { - sql += "tfa_type,"; - ++parametersCount; - } - if(dirtyFlag_[7]) - { - sql += "tfa_secret,"; - ++parametersCount; - } - if(dirtyFlag_[8]) - { - sql += "gitlab_at,"; - ++parametersCount; - } - if(dirtyFlag_[9]) - { - sql += "gitlab_rt,"; - ++parametersCount; - } - if(parametersCount > 0) - { - sql[sql.length()-1]=')'; - sql += " values ("; - } - else - sql += ") values ("; - - if(dirtyFlag_[1]) - { - sql.append("?,"); - - } - if(dirtyFlag_[2]) - { - sql.append("?,"); - - } - if(dirtyFlag_[3]) - { - sql.append("?,"); - - } - if(dirtyFlag_[4]) - { - sql.append("?,"); - - } - if(dirtyFlag_[5]) - { - sql.append("?,"); - - } - if(dirtyFlag_[6]) - { - sql.append("?,"); - - } - if(dirtyFlag_[7]) - { - sql.append("?,"); - - } - if(dirtyFlag_[8]) - { - sql.append("?,"); - - } - if(dirtyFlag_[9]) - { - sql.append("?,"); - - } - if(parametersCount > 0) - { - sql.resize(sql.length() - 1); - } - sql.append(1, ')'); - LOG_TRACE << sql; - return sql; - } -}; -} // namespace sqlite3 -} // namespace drogon_model diff --git a/backend/model/model.json b/backend/model/model.json deleted file mode 100644 index 1c40e75..0000000 --- a/backend/model/model.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "rdbms":"sqlite3", - "filename":"run/sqlite.db", - "tables":[] -} \ No newline at end of file diff --git a/backend/shl/msd/blocking_iterator.hpp b/backend/shl/msd/blocking_iterator.hpp deleted file mode 100644 index bec3c05..0000000 --- a/backend/shl/msd/blocking_iterator.hpp +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (C) 2022 Andrei Avram - -#ifndef MSD_CHANNEL_BLOCKING_ITERATOR_HPP_ -#define MSD_CHANNEL_BLOCKING_ITERATOR_HPP_ - -#include -#include - -namespace msd { - -/** - * @brief An iterator that block the current thread, - * waiting to fetch elements from the channel. - * - * Used to implement channel range-based for loop. - * - * @tparam Channel Instance of channel. - */ -template -class blocking_iterator { - public: - using value_type = typename channel::value_type; - - explicit blocking_iterator(channel& ch) : ch_{ch} {} - - /** - * Advances to next element in the channel. - */ - blocking_iterator operator++() const noexcept { return *this; } - - /** - * Returns an element from the channel. - */ - value_type operator*() const - { - value_type value; - value << ch_; - - return value; - } - - /** - * Makes iteration continue until the channel is closed and empty. - */ - bool operator!=(blocking_iterator) const - { - std::unique_lock lock{ch_.mtx_}; - ch_.waitBeforeRead(lock); - - return !(ch_.closed() && ch_.empty()); - } - - private: - channel& ch_; -}; - -} // namespace msd - -/** - * @brief Output iterator specialization - */ -template -struct std::iterator_traits> { - using value_type = typename msd::blocking_iterator::value_type; - using iterator_category = std::output_iterator_tag; -}; - -#endif // MSD_CHANNEL_BLOCKING_ITERATOR_HPP_ diff --git a/backend/shl/msd/channel.hpp b/backend/shl/msd/channel.hpp deleted file mode 100644 index 199004a..0000000 --- a/backend/shl/msd/channel.hpp +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright (C) 2022 Andrei Avram - -#ifndef MSD_CHANNEL_HPP_ -#define MSD_CHANNEL_HPP_ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "blocking_iterator.hpp" - -namespace msd { - -#if (__cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)) -#define NODISCARD [[nodiscard]] -#else -#define NODISCARD -#endif - -namespace detail { -template -struct remove_cvref { - using type = typename std::remove_cv::type>::type; -}; - -template -using remove_cvref_t = typename remove_cvref::type; -} // namespace detail - -/** - * @brief Exception thrown if trying to write on closed channel. - */ -class closed_channel : public std::runtime_error { - public: - explicit closed_channel(const char* msg) : std::runtime_error{msg} {} -}; - -/** - * @brief Thread-safe container for sharing data between threads. - * - * Implements a blocking input iterator. - * - * @tparam T The type of the elements. - */ -template -class channel { - public: - using value_type = T; - using iterator = blocking_iterator>; - using size_type = std::size_t; - - /** - * Creates a new channel. - * - * @param capacity Number of elements the channel can store before blocking. - */ - explicit constexpr channel(size_type capacity = 0); - - /** - * Pushes an element into the channel. - * - * @throws closed_channel if channel is closed. - */ - template - friend void operator>>(Type&&, channel>&); - - /** - * Pops an element from the channel. - * - * @tparam Type The type of the elements - */ - template - friend void operator<<(Type&, channel&); - - /** - * Returns the number of elements in the channel. - */ - NODISCARD inline size_type constexpr size() const noexcept; - - /** - * Returns true if there are no elements in channel. - */ - NODISCARD inline bool constexpr empty() const noexcept; - - /** - * Closes the channel. - */ - inline void close() noexcept; - - /** - * Returns true if the channel is closed. - */ - NODISCARD inline bool closed() const noexcept; - - /** - * Iterator - */ - iterator begin() noexcept; - iterator end() noexcept; - - /** - * Channel cannot be copied or moved. - */ - channel(const channel&) = delete; - channel& operator=(const channel&) = delete; - channel(channel&&) = delete; - channel& operator=(channel&&) = delete; - virtual ~channel() = default; - - private: - const size_type cap_; - std::queue queue_; - std::mutex mtx_; - std::condition_variable cnd_; - std::atomic is_closed_{false}; - - inline void waitBeforeRead(std::unique_lock&); - friend class blocking_iterator; -}; - -#include "channel_impl.hpp" - -} // namespace msd - -#endif // MSD_CHANNEL_HPP_ diff --git a/backend/shl/msd/channel_impl.hpp b/backend/shl/msd/channel_impl.hpp deleted file mode 100644 index 1e4f4c3..0000000 --- a/backend/shl/msd/channel_impl.hpp +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (C) 2022 Andrei Avram - -template -constexpr channel::channel(const size_type capacity) : cap_{capacity} -{ -} - -template -void operator>>(T&& in, channel>& ch) -{ - if (ch.closed()) { - throw closed_channel{"cannot write on closed channel"}; - } - - std::unique_lock lock{ch.mtx_}; - - if (ch.cap_ > 0 && ch.queue_.size() == ch.cap_) { - ch.cnd_.wait(lock, [&ch]() { return ch.queue_.size() < ch.cap_; }); - } - - ch.queue_.push(std::forward(in)); - - ch.cnd_.notify_one(); -} - -template -void operator<<(T& out, channel& ch) -{ - if (ch.closed() && ch.empty()) { - return; - } - - { - std::unique_lock lock{ch.mtx_}; - ch.waitBeforeRead(lock); - - if (ch.queue_.size() > 0) { - out = std::move(ch.queue_.front()); - ch.queue_.pop(); - } - } - - ch.cnd_.notify_one(); -} - -template -constexpr typename channel::size_type channel::size() const noexcept -{ - return queue_.size(); -} - -template -constexpr bool channel::empty() const noexcept -{ - return queue_.empty(); -} - -template -void channel::close() noexcept -{ - is_closed_.store(true); - cnd_.notify_all(); -} - -template -bool channel::closed() const noexcept -{ - return is_closed_.load(); -} - -template -blocking_iterator> channel::begin() noexcept -{ - return blocking_iterator>{*this}; -} - -template -blocking_iterator> channel::end() noexcept -{ - return blocking_iterator>{*this}; -} - -template -void channel::waitBeforeRead(std::unique_lock& lock) -{ - cnd_.wait(lock, [this] { return queue_.size() > 0 || closed(); }); -} diff --git a/backend/src/config.rs b/backend/src/config.rs new file mode 100644 index 0000000..1aef38b --- /dev/null +++ b/backend/src/config.rs @@ -0,0 +1,35 @@ +use lazy_static::lazy_static; + +lazy_static! { + pub static ref CONFIG: Config = Config::read(); +} + +pub struct Config { + pub gitlab_id: String, + pub gitlab_secret: String, + pub gitlab_url: String, + pub gitlab_api_url: String, + pub gitlab_redirect_url: String, + pub smtp_server: String, + pub smtp_port: u16, + pub smtp_user: String, + pub smtp_password: String +} + +impl Config { + fn read() -> Self { + let config = std::fs::read_to_string("config.json").expect("Failed to read config.json"); + let config = json::parse(config.as_str()).expect("Failed to parse config.json"); + Self { + gitlab_id: config["gitlab_id"].as_str().expect("Config is missing 'gitlab_id'").to_string(), + gitlab_secret: config["gitlab_secret"].as_str().expect("Config is missing 'gitlab_secret'").to_string(), + gitlab_url: config["gitlab_url"].as_str().expect("Config is missing 'gitlab_url'").to_string(), + gitlab_api_url: config["gitlab_api_url"].as_str().expect("Config is missing 'gitlab_api_url'").to_string(), + gitlab_redirect_url: config["gitlab_redirect_url"].as_str().expect("Config is missing 'gitlab_redirect_url'").to_string(), + smtp_server: config["smtp_server"].as_str().expect("Config is missing 'smtp_server'").to_string(), + smtp_port: config["smtp_port"].as_u16().expect("Config is missing 'smtp_port'"), + smtp_user: config["smtp_user"].as_str().expect("Config is missing 'smtp_user'").to_string(), + smtp_password: config["smtp_password"].as_str().expect("Config is missing 'smtp_password'").to_string() + } + } +} \ No newline at end of file diff --git a/backend/src/controllers/admin.cpp b/backend/src/controllers/admin.cpp deleted file mode 100644 index 010ddf6..0000000 --- a/backend/src/controllers/admin.cpp +++ /dev/null @@ -1,110 +0,0 @@ -#pragma clang diagnostic push -#pragma ide diagnostic ignored "performance-unnecessary-value-param" -#pragma ide diagnostic ignored "readability-convert-member-functions-to-static" - -#include "controllers.h" -#include "dto/dto.h" - -namespace api { - void admin::users(req_type, cbk_type cbk) { - db::MapperUser user_mapper(drogon::app().getDbClient()); - std::vector entries; - auto users = user_mapper.findAll(); - for (const db::User& user : users) - entries.emplace_back( - user.getValueOfId(), - user.getValueOfGitlab() != 0, - db::User_getEnumTfaType(user) != db::tfaTypes::NONE, - user.getValueOfName(), - db::User_getEnumRole(user) - ); - cbk(dto::Responses::get_admin_users_res(entries)); - } - - void admin::set_role(req_type req, cbk_type cbk) { - Json::Value& json = *req->jsonObject(); - try { - uint64_t user_id = dto::json_get(json, "user").value(); - db::UserRole role = (db::UserRole)dto::json_get(json, "role").value(); - - db::MapperUser user_mapper(drogon::app().getDbClient()); - auto user = user_mapper.findByPrimaryKey(user_id); - user.setRole(role); - user_mapper.update(user); - - cbk(dto::Responses::get_success_res()); - } catch (const std::exception&) { - cbk(dto::Responses::get_badreq_res("Validation error")); - } - } - - void admin::logout(req_type req, cbk_type cbk) { - Json::Value& json = *req->jsonObject(); - try { - uint64_t user_id = dto::json_get(json, "user").value(); - - db::MapperUser user_mapper(drogon::app().getDbClient()); - auto user = user_mapper.findByPrimaryKey(user_id); - auth::revoke_all(user); - - cbk(dto::Responses::get_success_res()); - } catch (const std::exception&) { - cbk(dto::Responses::get_badreq_res("Validation error")); - } - } - - void admin::delete_user(req_type req, cbk_type cbk) { - Json::Value& json = *req->jsonObject(); - msd::channel chan; - try { - uint64_t user_id = dto::json_get(json, "user").value(); - - db::MapperUser user_mapper(drogon::app().getDbClient()); - auto user = user_mapper.findByPrimaryKey(user_id); - auth::revoke_all(user); - - std::unique_lock lock(*fs::get_user_mutex(user.getValueOfId())); - - fs::delete_node(fs::get_node(user.getValueOfRootId()).value(), chan, true); - user_mapper.deleteOne(user); - cbk(dto::Responses::get_success_res()); - } catch (const std::exception&) { - cbk(dto::Responses::get_badreq_res("Validation error")); - } - } - - void admin::disable_2fa(req_type req, cbk_type cbk) { - Json::Value& json = *req->jsonObject(); - try { - uint64_t user_id = dto::json_get(json, "user").value(); - - db::MapperUser user_mapper(drogon::app().getDbClient()); - auto user = user_mapper.findByPrimaryKey(user_id); - user.setTfaType(db::tfaTypes::NONE); - user_mapper.update(user); - - cbk(dto::Responses::get_success_res()); - } catch (const std::exception&) { - cbk(dto::Responses::get_badreq_res("Validation error")); - } - } - - void admin::is_admin(req_type, cbk_type cbk) { - cbk(dto::Responses::get_success_res()); - } - - void admin::get_token(req_type, cbk_type cbk, uint64_t user) { - db::MapperUser user_mapper(drogon::app().getDbClient()); - - try { - const auto &db_user = user_mapper.findByPrimaryKey(user); - - const std::string &token = auth::get_token(db_user); - - cbk(dto::Responses::get_login_res(token)); - } catch (const std::exception&) { - cbk(dto::Responses::get_badreq_res("Bad user")); - } - } -} -#pragma clang diagnostic pop \ No newline at end of file diff --git a/backend/src/controllers/auth/auth_2fa.cpp b/backend/src/controllers/auth/auth_2fa.cpp deleted file mode 100644 index 8513b75..0000000 --- a/backend/src/controllers/auth/auth_2fa.cpp +++ /dev/null @@ -1,99 +0,0 @@ -#pragma clang diagnostic push -#pragma ide diagnostic ignored "readability-make-member-function-const" -#pragma ide diagnostic ignored "readability-convert-member-functions-to-static" - -#include -#include -#include -#include - -#include "controllers/controllers.h" -#include "db/db.h" -#include "dto/dto.h" - -std::string create_totp_qrcode(const db::User& user, const std::string& b32_secret) { - constexpr int qrcode_pixel_size = 4; - - std::stringstream code_ss; - code_ss << "otpauth://totp/MFileserver:" - << user.getValueOfName() - << "?secret=" - << b32_secret - << "&issuer=MFileserver"; - auto code = qrcodegen::QrCode::encodeText(code_ss.str().c_str(), qrcodegen::QrCode::Ecc::MEDIUM); - const int mod_count = code.getSize(); - - const int row_size = qrcode_pixel_size * mod_count; - cv::Mat image(mod_count, mod_count, CV_8UC1), scaled_image; - std::vector image_encoded; - for (int y = 0; y < mod_count; y++) for (int x = 0; x < mod_count; x++) - image.at(x, y) = code.getModule(x, y) ? 0 : 0xff; - cv::resize(image, scaled_image, cv::Size(), qrcode_pixel_size, qrcode_pixel_size, cv::INTER_NEAREST); - cv::imencode(".png", scaled_image, image_encoded); - - return "data:image/png;base64," + Botan::base64_encode(image_encoded); -} - -namespace api { - void auth::tfa_setup(req_type req, cbk_type cbk) { - db::User user = dto::get_user(req); - Json::Value &json = *req->jsonObject(); - try { - bool mail = dto::json_get(json, "mail").value(); - - auto secret_uchar = rng->random_vec(32); - std::vector secret(secret_uchar.data(), secret_uchar.data()+32); - user.setTfaSecret(secret); - - db::MapperUser user_mapper(drogon::app().getDbClient()); - user_mapper.update(user); - - if (mail) { - send_mail(user); - cbk(dto::Responses::get_success_res()); - } else { - std::string b32_secret = Botan::base32_encode(secret_uchar); - b32_secret.erase(std::remove(b32_secret.begin(), b32_secret.end(), '='), b32_secret.end()); - std::string code = create_totp_qrcode(user, b32_secret); - cbk(dto::Responses::get_tfa_setup_res(b32_secret, code)); - } - } catch (const std::exception&) { - cbk(dto::Responses::get_badreq_res("Validation error")); - } - } - - void auth::tfa_complete(req_type req, cbk_type cbk) { - db::User user = dto::get_user(req); - Json::Value &json = *req->jsonObject(); - try { - bool mail = dto::json_get(json, "mail").value(); - uint32_t code = std::stoi(dto::json_get(json, "code").value()); - - user.setTfaType(mail ? db::tfaTypes::EMAIL : db::tfaTypes::TOTP); - - if (!verify2fa(user, code)) - return cbk(dto::Responses::get_unauth_res("Incorrect 2fa")); - - db::MapperUser user_mapper(drogon::app().getDbClient()); - user_mapper.update(user); - - revoke_all(user); - cbk(dto::Responses::get_success_res()); - } catch (const std::exception&) { - cbk(dto::Responses::get_badreq_res("Validation error")); - } - } - - void auth::tfa_disable(req_type req, cbk_type cbk) { - db::User user = dto::get_user(req); - - db::MapperUser user_mapper(drogon::app().getDbClient()); - user.setTfaType(db::tfaTypes::NONE); - user_mapper.update(user); - - revoke_all(user); - cbk(dto::Responses::get_success_res()); - } -} - -#pragma clang diagnostic pop \ No newline at end of file diff --git a/backend/src/controllers/auth/auth_basic.cpp b/backend/src/controllers/auth/auth_basic.cpp deleted file mode 100644 index 49f9d76..0000000 --- a/backend/src/controllers/auth/auth_basic.cpp +++ /dev/null @@ -1,135 +0,0 @@ -#pragma clang diagnostic push -#pragma ide diagnostic ignored "readability-make-member-function-const" -#pragma ide diagnostic ignored "readability-convert-member-functions-to-static" - -#include -#include -#include -#include - -#include "controllers/controllers.h" -#include "db/db.h" -#include "dto/dto.h" - -namespace api { - void auth::login(req_type req, cbk_type cbk) { - Json::Value &json = *req->jsonObject(); - try { - std::string username = dto::json_get(json, "username").value(); - std::string password = dto::json_get(json, "password").value(); - std::optional otp = dto::json_get(json, "otp"); - - auto db = drogon::app().getDbClient(); - - db::MapperUser user_mapper(db); - auto db_users = user_mapper.findBy( - db::Criteria(db::User::Cols::_name, db::CompareOps::EQ, username) && - db::Criteria(db::User::Cols::_gitlab, db::CompareOps::EQ, 0) - ); - if (db_users.empty()) { - cbk(dto::Responses::get_unauth_res("Invalid username or password")); - return; - } - db::User &db_user = db_users.at(0); - if (!Botan::argon2_check_pwhash(password.c_str(), password.size(), db_user.getValueOfPassword())) { - cbk(dto::Responses::get_unauth_res("Invalid username or password")); - return; - } - if (db::User_getEnumRole(db_user) == db::UserRole::DISABLED) { - cbk(dto::Responses::get_unauth_res("Account is disabled")); - return; - } - - const auto tfa = db::User_getEnumTfaType(db_user); - if (tfa != db::tfaTypes::NONE) { - if (!otp.has_value()) { - if (tfa == db::tfaTypes::EMAIL) send_mail(db_user); - return cbk(dto::Responses::get_success_res()); - } - if (!verify2fa(db_user, std::stoi(otp.value()))) - return cbk(dto::Responses::get_unauth_res("Incorrect 2fa")); - } - - cbk(dto::Responses::get_login_res(get_token(db_user))); - } catch (const std::exception&) { - cbk(dto::Responses::get_badreq_res("Validation error")); - } - } - - void auth::signup(req_type req, cbk_type cbk) { - Json::Value &json = *req->jsonObject(); - try { - std::string username = dto::json_get(json, "username").value(); - std::string password = dto::json_get(json, "password").value(); - - db::MapperUser user_mapper(drogon::app().getDbClient()); - - auto existing_users = user_mapper.count( - db::Criteria(db::User::Cols::_name, db::CompareOps::EQ, username) && - db::Criteria(db::User::Cols::_gitlab, db::CompareOps::EQ, 0) - ); - if (existing_users != 0) { - cbk(dto::Responses::get_badreq_res("Username is already taken")); - return; - } - - //std::string hash = Botan::argon2_generate_pwhash(password.c_str(), password.size(), *rng, 1, 256*1024, 2); - std::string hash = Botan::argon2_generate_pwhash(password.c_str(), password.size(), *rng, 1, 16*1024, 1); - - db::User new_user; - new_user.setName(username); - new_user.setPassword(hash); - new_user.setGitlab(0); - new_user.setRole(db::UserRole::DISABLED); - new_user.setRootId(0); - new_user.setTfaType(db::tfaTypes::NONE); - - user_mapper.insert(new_user); - generate_root(new_user); - cbk(dto::Responses::get_success_res()); - } catch (const std::exception&) { - cbk(dto::Responses::get_badreq_res("Validation error")); - } - } - - void auth::refresh(req_type req, cbk_type cbk) { - db::User user = dto::get_user(req); - db::Token token = dto::get_token(req); - - db::MapperToken token_mapper(drogon::app().getDbClient()); - token_mapper.deleteOne(token); - cbk(dto::Responses::get_login_res( get_token(user))); - } - - void auth::logout_all(req_type req, cbk_type cbk) { - db::User user = dto::get_user(req); - revoke_all(user); - cbk(dto::Responses::get_success_res()); - } - - void auth::change_password(req_type req, cbk_type cbk) { - db::User user = dto::get_user(req); - Json::Value &json = *req->jsonObject(); - try { - std::string old_pw = dto::json_get(json, "oldPassword").value(); - std::string new_pw = dto::json_get(json, "newPassword").value(); - - auto db = drogon::app().getDbClient(); - db::MapperUser user_mapper(db); - - if (!Botan::argon2_check_pwhash(old_pw.c_str(), old_pw.size(), user.getValueOfPassword())) - return cbk(dto::Responses::get_unauth_res("Old password is wrong")); - - std::string hash = Botan::argon2_generate_pwhash(new_pw.c_str(), new_pw.size(), *rng, 1, 256*1024, 2); - - user.setPassword(hash); - user_mapper.update(user); - revoke_all(user); - cbk(dto::Responses::get_success_res()); - } catch (const std::exception&) { - cbk(dto::Responses::get_badreq_res("Validation error")); - } - } -} - -#pragma clang diagnostic pop \ No newline at end of file diff --git a/backend/src/controllers/auth/auth_common.cpp b/backend/src/controllers/auth/auth_common.cpp deleted file mode 100644 index d396a3f..0000000 --- a/backend/src/controllers/auth/auth_common.cpp +++ /dev/null @@ -1,110 +0,0 @@ -#pragma clang diagnostic push -#pragma ide diagnostic ignored "readability-make-member-function-const" -#pragma ide diagnostic ignored "readability-convert-member-functions-to-static" - -#include -#include -#include - -#include -#include - -#if defined(BOTAN_HAS_SYSTEM_RNG) -#include -#else -#include -#endif - -#include -#include -#include - -#include "controllers/controllers.h" -#include "db/db.h" - - -namespace api { -#if defined(BOTAN_HAS_SYSTEM_RNG) - std::unique_ptr auth::rng = std::make_unique(); -#else - std::unique_ptr auth::rng = std::make_unique(); -#endif - - bool auth::verify2fa(const db::User& user, uint32_t totp) { - size_t allowed_skew = db::User_getEnumTfaType(user) == db::tfaTypes::TOTP ? 0 : 10; - const auto& totp_secret = (const std::vector&) user.getValueOfTfaSecret(); - return Botan::TOTP(Botan::OctetString(totp_secret)).verify_totp(totp, std::chrono::system_clock::now(), allowed_skew); - } - - void auth::send_mail(const db::User& user) { - std::time_t t = std::time(nullptr); - const auto& totp_secret = (const std::vector&) user.getValueOfTfaSecret(); - char totp[16]; - std::snprintf(totp, 16, "%06d", Botan::TOTP(Botan::OctetString(totp_secret)).generate_totp(t)); - - auto config = drogon::app().getCustomConfig(); - - drogon::app().getPlugin()->sendEmail( - config["smtp_server"].asString(), - (uint16_t)config["smtp_port"].asUInt64(), - "fileserver@mattv.de", - user.getValueOfName(), - "MFileserver - Email 2fa code", - "Your code is: " + std::string(totp) +"\r\nIt is valid for 5 Minutes", - config["smtp_user"].asString(), - config["smtp_password"].asString(), - false - ); - } - - std::string auth::get_token(const db::User& user) { - auto db = drogon::app().getDbClient(); - - db::MapperToken token_mapper(db); - const auto iat = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); - const auto exp = iat + std::chrono::hours{24}; - - db::Token new_token; - new_token.setOwnerId(user.getValueOfId()); - new_token.setExp(exp.count()); - - token_mapper.insert(new_token); - - return jwt::create() - .set_type("JWT") - .set_payload_claim("sub", picojson::value((int64_t)user.getValueOfId())) - .set_payload_claim("jti", picojson::value((int64_t)new_token.getValueOfId())) - .set_issued_at(std::chrono::system_clock::from_time_t(iat.count())) - .set_expires_at(std::chrono::system_clock::from_time_t(exp.count())) - .sign(jwt::algorithm::hs256{get_jwt_secret()}); - } - - void auth::generate_root(db::User& user) { - db::MapperUser user_mapper(drogon::app().getDbClient()); - - auto node = fs::create_node("", user, false, std::nullopt, true); - user.setRootId(std::get(node).getValueOfId()); - user_mapper.update(user); - } - - void auth::revoke_all(const db::User& user) { - db::MapperToken token_mapper(drogon::app().getDbClient()); - token_mapper.deleteBy(db::Criteria(db::Token::Cols::_owner_id, db::CompareOps::EQ, user.getValueOfId())); - } - - std::string auth::get_jwt_secret() { - static std::string token; - if (token.empty()) { - if (!std::filesystem::exists("jwt.secret")) { - auto new_token = rng->random_vec(128); - std::ofstream file("jwt.secret", std::ofstream::binary); - file.write((const char*)new_token.data(), (std::streamsize)new_token.size()); - } - std::ifstream file("jwt.secret", std::ifstream::binary); - token = {std::istreambuf_iterator(file), std::istreambuf_iterator()}; - } - return token; - } -} - -#pragma clang diagnostic pop diff --git a/backend/src/controllers/auth/auth_gitlab.cpp b/backend/src/controllers/auth/auth_gitlab.cpp deleted file mode 100644 index 835eb16..0000000 --- a/backend/src/controllers/auth/auth_gitlab.cpp +++ /dev/null @@ -1,135 +0,0 @@ -#pragma clang diagnostic push -#pragma ide diagnostic ignored "performance-unnecessary-value-param" -#pragma ide diagnostic ignored "readability-make-member-function-const" -#pragma ide diagnostic ignored "readability-convert-member-functions-to-static" - -#include "controllers/controllers.h" -#include "dto/dto.h" - -namespace config { - std::string get_id() { - static std::string val = drogon::app().getCustomConfig()["gitlab_id"].asString(); - return val; - } - std::string get_secret() { - static std::string val = drogon::app().getCustomConfig()["gitlab_secret"].asString(); - return val; - } - std::string get_url() { - static std::string val = drogon::app().getCustomConfig()["gitlab_url"].asString(); - return val; - } - std::string get_api_url() { - static std::string val = drogon::app().getCustomConfig()["gitlab_api_url"].asString(); - return val; - } - std::string get_redirect_url() { - static std::string val = drogon::app().getCustomConfig()["gitlab_redirect_url"].asString(); - return val; - } -} - -std::string get_redirect_uri() { - std::stringstream ss; - ss << config::get_redirect_url() - << "/api/auth/gitlab_callback"; - return drogon::utils::urlEncode(ss.str()); -} - -const drogon::HttpClientPtr& get_gitlab_client() { - static drogon::HttpClientPtr client = drogon::HttpClient::newHttpClient(config::get_api_url(), drogon::app().getLoop(), false, false); - return client; -} - - -namespace api { - std::optional auth::get_gitlab_tokens(const std::string& code_or_token, bool token) { - std::stringstream ss; - ss << "/oauth/token" - << "?redirect_uri=" << get_redirect_uri() - << "&client_id=" << config::get_id() - << "&client_secret=" << config::get_secret() - << (token ? "&refresh_token=" : "&code=") << code_or_token - << "&grant_type=" << (token ? "refresh_token" : "authorization_code"); - auto gitlab_req = drogon::HttpRequest::newHttpRequest(); - gitlab_req->setPathEncode(false); - gitlab_req->setPath(ss.str()); - gitlab_req->setMethod(drogon::HttpMethod::Post); - auto res_tuple = get_gitlab_client()->sendRequest(gitlab_req); - auto res = res_tuple.second; - if ((res->statusCode() != drogon::HttpStatusCode::k200OK) && (res->statusCode() != drogon::HttpStatusCode::k201Created)) - return std::nullopt; - auto json = *res->jsonObject(); - return std::make_optional( - json["access_token"].as(), - json["refresh_token"].as() - ); - } - - std::optional auth::get_gitlab_user(const std::string& at) { - auto gitlab_req = drogon::HttpRequest::newHttpRequest(); - gitlab_req->setPath("/api/v4/user"); - gitlab_req->addHeader("Authorization", "Bearer " + at); - gitlab_req->setMethod(drogon::HttpMethod::Get); - auto res_tuple = get_gitlab_client()->sendRequest(gitlab_req); - auto res = res_tuple.second; - if (res->statusCode() != drogon::HttpStatusCode::k200OK) - return std::nullopt; - auto json = *res->jsonObject(); - return std::make_optional( - json["username"].as(), - json.get("is_admin", false).as() - ); - } - - void auth::gitlab(req_type, cbk_type cbk) { - std::stringstream ss; - ss << config::get_url() << "/oauth/authorize" - << "?redirect_uri=" << get_redirect_uri() - << "&client_id=" << config::get_id() - << "&scope=read_user&response_type=code"; - cbk(drogon::HttpResponse::newRedirectionResponse(ss.str())); - } - - std::string disabled_resp = "

Your account is disabled, please contact an admin. Go to login page

"; - void auth::gitlab_callback(req_type, cbk_type cbk, std::string code) { - auto tokens = get_gitlab_tokens(code, false); - if (!tokens.has_value()) - return cbk(dto::Responses::get_unauth_res("Invalid code")); - auto info = get_gitlab_user(tokens->at); - if (!info.has_value()) - return cbk(dto::Responses::get_unauth_res("Invalid code")); - - db::MapperUser user_mapper(drogon::app().getDbClient()); - auto db_users = user_mapper.findBy( - db::Criteria(db::User::Cols::_name, db::CompareOps::EQ, info->name) && - db::Criteria(db::User::Cols::_gitlab, db::CompareOps::EQ, 1) - ); - - if (db_users.empty()) { - db::User new_user; - new_user.setName(info->name); - new_user.setPassword(""); - new_user.setGitlab(1); - new_user.setRole(info->is_admin ? db::UserRole::ADMIN : db::UserRole::DISABLED); - new_user.setRootId(0); - new_user.setTfaType(db::tfaTypes::NONE); - - user_mapper.insert(new_user); - generate_root(new_user); - db_users.push_back(new_user); - } - db::User& db_user = db_users.at(0); - db_user.setGitlabAt(tokens->at); - db_user.setGitlabRt(tokens->rt); - user_mapper.update(db_user); - - if (db::User_getEnumRole(db_user) == db::UserRole::DISABLED) - return cbk(drogon::HttpResponse::newFileResponse((unsigned char*)disabled_resp.data(), disabled_resp.size(), "", drogon::ContentType::CT_TEXT_HTML)); - - const std::string& token = get_token(db_user); - cbk(drogon::HttpResponse::newRedirectionResponse("/set_token?token="+token)); - } -} - -#pragma clang diagnostic pop \ No newline at end of file diff --git a/backend/src/controllers/controllers.h b/backend/src/controllers/controllers.h deleted file mode 100644 index efb788b..0000000 --- a/backend/src/controllers/controllers.h +++ /dev/null @@ -1,161 +0,0 @@ -#ifndef BACKEND_CONTROLLERS_H -#define BACKEND_CONTROLLERS_H -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "db/db.h" - -using req_type = const drogon::HttpRequestPtr&; -using cbk_type = std::function&&; - -namespace api { -class admin : public drogon::HttpController { -public: - METHOD_LIST_BEGIN - METHOD_ADD(admin::users, "/users", drogon::Get, "Login", "Admin"); - METHOD_ADD(admin::set_role, "/set_role", drogon::Post, "Login", "Admin"); - METHOD_ADD(admin::logout, "/logout", drogon::Post, "Login", "Admin"); - METHOD_ADD(admin::delete_user, "/delete", drogon::Post, "Login", "Admin"); - METHOD_ADD(admin::disable_2fa, "/disable_2fa", drogon::Post, "Login", "Admin"); - METHOD_ADD(admin::is_admin, "/is_admin", drogon::Get, "Login", "Admin"); - METHOD_ADD(admin::get_token, "/get_token/{}", drogon::Get, "Login", "Admin"); - METHOD_LIST_END - - void users(req_type, cbk_type); - void set_role(req_type, cbk_type); - void logout(req_type, cbk_type); - void delete_user(req_type, cbk_type); - void disable_2fa(req_type, cbk_type); - void is_admin(req_type, cbk_type); - void get_token(req_type, cbk_type, uint64_t user); -}; - -class auth : public drogon::HttpController { -public: - METHOD_LIST_BEGIN - METHOD_ADD(auth::gitlab, "/gitlab", drogon::Get); - METHOD_ADD(auth::gitlab_callback, "/gitlab_callback?code={}", drogon::Get); - METHOD_ADD(auth::signup, "/signup", drogon::Post); - METHOD_ADD(auth::login, "/login", drogon::Post); - METHOD_ADD(auth::refresh, "/refresh", drogon::Post, "Login"); - METHOD_ADD(auth::tfa_setup, "/2fa/setup", drogon::Post, "Login"); - METHOD_ADD(auth::tfa_complete, "/2fa/complete", drogon::Post, "Login"); - METHOD_ADD(auth::tfa_disable, "/2fa/disable", drogon::Post, "Login"); - METHOD_ADD(auth::change_password, "/change_password", drogon::Post, "Login"); - METHOD_ADD(auth::logout_all, "/logout_all", drogon::Post, "Login"); - METHOD_LIST_END - - struct gitlab_tokens { - gitlab_tokens(std::string at, std::string rt) : at(std::move(at)), rt(std::move(rt)) {} - std::string at, rt; - }; - struct gitlab_user { - gitlab_user(std::string name, bool isAdmin) : name(std::move(name)), is_admin(isAdmin) {} - std::string name; - bool is_admin; - }; - - static std::unique_ptr rng; - - static std::optional get_gitlab_tokens(const std::string&, bool token); - static std::optional get_gitlab_user(const std::string&); - static bool verify2fa(const db::User&, uint32_t totp); - static void send_mail(const db::User&); - static std::string get_token(const db::User&); - static void generate_root(db::User&); - static void revoke_all(const db::User&); - static std::string get_jwt_secret(); - - void gitlab(req_type, cbk_type); - void gitlab_callback(req_type, cbk_type, std::string code); - void signup(req_type, cbk_type); - void login(req_type, cbk_type); - void refresh(req_type, cbk_type); - void tfa_setup(req_type, cbk_type); - void tfa_complete(req_type, cbk_type); - void tfa_disable(req_type, cbk_type); - void change_password(req_type, cbk_type); - void logout_all(req_type, cbk_type); -}; - -class fs : public drogon::HttpController { -public: - METHOD_LIST_BEGIN - METHOD_ADD(fs::root, "/root", drogon::Get, "Login"); - METHOD_ADD(fs::node, "/node/{}", drogon::Get, "Login"); - METHOD_ADD(fs::path, "/path/{}", drogon::Get, "Login"); - METHOD_ADD(fs::create_node_req, "/createFolder", drogon::Post, "Login"); - METHOD_ADD(fs::create_node_req, "/createFile", drogon::Post, "Login"); - METHOD_ADD(fs::delete_node_req, "/delete/{}", drogon::Post, "Login"); - METHOD_ADD(fs::upload, "/upload/{}", drogon::Post, "Login"); - METHOD_ADD(fs::create_zip, "/create_zip", drogon::Post, "Login"); - METHOD_ADD(fs::download, "/download", drogon::Post, "Login"); - METHOD_ADD(fs::download_multi, "/download_multi", drogon::Post, "Login"); - METHOD_ADD(fs::download_preview, "/download_preview/{}", drogon::Get, "Login"); - METHOD_ADD(fs::get_type, "/get_type/{}", drogon::Get, "Login"); - METHOD_LIST_END - - enum class create_node_error { - INVALID_NAME, - INVALID_PARENT, - FILE_PARENT - }; - - struct mutex_stream { - std::stringstream ss; - std::mutex mutex; - bool done = false; - }; - - static std::optional get_node(uint64_t node); - static std::optional get_node_and_validate(const db::User& user, uint64_t node); - static std::vector get_children(const db::INode& parent); - static std::variant> - create_node(std::string name, const db::User& owner, bool file, const std::optional &parent, bool force = false); - static void delete_node(db::INode node, msd::channel& chan, bool allow_root = false); - static std::shared_ptr get_user_mutex(uint64_t user_id); - - void root(req_type, cbk_type); - void node(req_type, cbk_type, uint64_t node); - void path(req_type, cbk_type, uint64_t node); - template void create_node_req(req_type req, cbk_type cbk); - void delete_node_req(req_type, cbk_type, uint64_t node); - void upload(req_type, cbk_type, uint64_t node); - void create_zip(req_type, cbk_type); - void download(req_type, cbk_type); - void download_multi(req_type, cbk_type); - void download_preview(req_type, cbk_type, uint64_t node); - void get_type(req_type, cbk_type, uint64_t node); - -private: - static trantor::EventLoop* get_zip_loop(); - static trantor::EventLoop* get_delete_loop(); - static void generate_path(db::INode node, std::string& str); - static Json::Value generate_path(db::INode node); - static uint64_t calc_total_size(const db::INode& base); - static void add_to_zip(struct zip_t* zip, const std::string& key, const db::INode& node, const std::string& path); - - static uint64_t next_temp_id; - static std::unordered_map zip_to_temp_map; - static std::unordered_map> in_progress_zips; -}; - -class user : public drogon::HttpController { -public: - METHOD_LIST_BEGIN - METHOD_ADD(user::info, "/info", drogon::Get, "Login"); - METHOD_ADD(user::delete_user, "/delete", drogon::Post, "Login"); - METHOD_LIST_END - - void info(req_type, cbk_type); - void delete_user(req_type, cbk_type); -}; -} -#endif //BACKEND_CONTROLLERS_H diff --git a/backend/src/controllers/fs/fs_functions.cpp b/backend/src/controllers/fs/fs_functions.cpp deleted file mode 100644 index ba707bf..0000000 --- a/backend/src/controllers/fs/fs_functions.cpp +++ /dev/null @@ -1,257 +0,0 @@ -#include -#include - -#include "controllers/controllers.h" -#include "dto/dto.h" - -char windows_invalid_chars[] = "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F<>:\"/\\|"; - -namespace api { - uint64_t fs::next_temp_id = 0; - std::unordered_map fs::zip_to_temp_map; - std::unordered_map> fs::in_progress_zips; - - std::optional fs::get_node(uint64_t node) { - db::MapperInode inode_mapper(drogon::app().getDbClient()); - try { - return inode_mapper.findByPrimaryKey(node); - } catch (const std::exception&) { - return std::nullopt; - } - } - - std::optional fs::get_node_and_validate(const db::User &user, uint64_t node) { - auto inode = get_node(node); - if (!inode.has_value()) return std::nullopt; - if (inode->getValueOfOwnerId() != user.getValueOfId()) return std::nullopt; - return inode; - } - - std::vector fs::get_children(const db::INode& parent) { - db::MapperInode inode_mapper(drogon::app().getDbClient()); - return inode_mapper.findBy(db::Criteria(db::INode::Cols::_parent_id, db::CompareOps::EQ, parent.getValueOfId())); - } - - std::variant> - fs::create_node(std::string name, const db::User& owner, bool file, const std::optional &parent, bool force) { - // Stolen from https://github.com/boostorg/filesystem/blob/develop/src/portability.cpp - if (!force) - if (name.empty() || name[0] == ' ' || name.find_first_of(windows_invalid_chars, 0, sizeof(windows_invalid_chars)) != std::string::npos || *(name.end() - 1) == ' ' || *(name.end() - 1) == '.' || name == "." || name == "..") - return {create_node_error::INVALID_NAME}; - - db::INode node; - node.setIsFile(file ? 1 : 0); - node.setName(name); - node.setOwnerId(owner.getValueOfId()); - node.setHasPreview(0); - if (parent.has_value()) { - auto parent_node = get_node_and_validate(owner, *parent); - if (!parent_node.has_value()) - return {create_node_error::INVALID_PARENT}; - if (parent_node->getValueOfIsFile() != 0) - return {create_node_error::FILE_PARENT}; - auto children = get_children(*parent_node); - for (const auto& child : children) - if (child.getValueOfName() == name) - return {std::make_tuple( - child.getValueOfIsFile() != 0, - child.getValueOfId() - )}; - node.setParentId(*parent); - } - db::MapperInode inode_mapper(drogon::app().getDbClient()); - inode_mapper.insert(node); - return {node}; - } - - void fs::delete_node(db::INode node, msd::channel& chan, bool allow_root) { - if (node.getValueOfParentId() == 0 && (!allow_root)) return; - - db::MapperInode inode_mapper(drogon::app().getDbClient()); - - const auto delete_file = [&chan, &inode_mapper](const db::INode& node) { - std::string entry = "Deleting "; - generate_path(node, entry); - entry >> chan; - std::filesystem::path p("./files"); - p /= std::to_string(node.getValueOfId()); - std::filesystem::remove(p); - if (node.getValueOfHasPreview() != 0) - std::filesystem::remove(p.string() + "_preview.png"); - inode_mapper.deleteOne(node); - std::string(" Done\n") >> chan; - }; - - std::stack queue, files, folders; - - if (node.getValueOfIsFile() == 0) queue.push(node); - else files.push(node); - - while (!queue.empty()) { - while (!files.empty()) { - delete_file(files.top()); - files.pop(); - } - std::string entry = "Deleting "; - generate_path(queue.top(), entry); - entry += "\n"; - entry >> chan; - auto children = get_children(queue.top()); - folders.push(queue.top()); - queue.pop(); - for (const auto& child : children) { - if (child.getValueOfIsFile() == 0) queue.push(child); - else files.push(child); - } - } - - while (!files.empty()) { - delete_file(files.top()); - files.pop(); - } - - while (!folders.empty()) { - inode_mapper.deleteOne(folders.top()); - folders.pop(); - } - } - - std::shared_ptr fs::get_user_mutex(uint64_t user_id) { - static std::unordered_map> mutexes; - static std::mutex mutexes_mutex; - std::lock_guard guard(mutexes_mutex); - return (*mutexes.try_emplace(user_id, std::make_shared()).first).second; - } - - trantor::EventLoop* fs::get_zip_loop() { - static bool init_done = false; - static trantor::EventLoopThread loop("ZipEventLoop"); - if (!init_done) { - init_done = true; - loop.run(); - loop.getLoop()->runEvery(30*60, []{ - for (const auto& entry : std::filesystem::directory_iterator("./temp")) { - if (!entry.is_regular_file()) continue; - const std::string file_name = "./temp/" + entry.path().filename().string(); - const auto& progress_pos = std::find_if(in_progress_zips.begin(), in_progress_zips.end(), - [&file_name](const std::pair>& entry) { - return std::get<0>(entry.second) == file_name; - } - ); - if (progress_pos != in_progress_zips.end()) return; - const auto& zip_map_pos = std::find_if(zip_to_temp_map.begin(), zip_to_temp_map.end(), - [&file_name](const std::pair& entry){ - return entry.second == file_name; - } - ); - if (zip_map_pos != zip_to_temp_map.end()) return; - std::filesystem::remove(entry.path()); - } - }); - } - return loop.getLoop(); - } - - trantor::EventLoop* fs::get_delete_loop() { - static bool init_done = false; - static trantor::EventLoopThread loop("DeleteEventLoop"); - if (!init_done) { - init_done = true; - loop.run(); - } - return loop.getLoop(); - } - - void fs::generate_path(db::INode node, std::string& str) { - db::MapperInode inode_mapper(drogon::app().getDbClient()); - std::stack path; - path.push(node); - while (node.getParentId() != nullptr) { - node = inode_mapper.findByPrimaryKey(node.getValueOfParentId()); - path.push(node); - } - while (!path.empty()) { - const db::INode& seg = path.top(); - str += seg.getValueOfName(); - if (seg.getValueOfIsFile() == 0) str += "/"; - path.pop(); - } - } - - Json::Value fs::generate_path(db::INode node) { - Json::Value segments = Json::Value(Json::ValueType::arrayValue); - db::MapperInode inode_mapper(drogon::app().getDbClient()); - std::stack path; - path.push(node); - while (node.getParentId() != nullptr) { - node = inode_mapper.findByPrimaryKey(node.getValueOfParentId()); - path.push(node); - } - while (!path.empty()) { - const db::INode& seg = path.top(); - if (seg.getParentId() == nullptr) { - Json::Value json_seg; - json_seg["path"] = "/"; - json_seg["node"] = seg.getValueOfId(); - segments.append(json_seg); - } else { - Json::Value json_seg; - json_seg["path"] = seg.getValueOfName(); - json_seg["node"] = seg.getValueOfId(); - segments.append(json_seg); - if (seg.getValueOfIsFile() == 0) { - json_seg.removeMember("node"); - json_seg["path"] = "/"; - segments.append(json_seg); - } - } - path.pop(); - } - Json::Value resp; - resp["segments"] = segments; - return resp; - } - - uint64_t fs::calc_total_size(const db::INode& base) { - uint64_t size = 0; - std::stack queue; - queue.push(base); - while (!queue.empty()) { - const db::INode& node = queue.top(); - if (node.getValueOfIsFile() == 0) { - auto children = api::fs::get_children(node); - queue.pop(); - for (const auto& child : children) { - if (child.getValueOfIsFile() == 0) queue.push(child); - else if (child.getSize()) size += child.getValueOfSize(); - } - } else { - size += node.getValueOfSize(); - queue.pop(); - } - } - return size; - } - - void fs::add_to_zip(struct zip_t* zip, const std::string& key, const db::INode& node, const std::string& path) { - if (node.getValueOfIsFile() == 0) { - std::string new_path = path + node.getValueOfName() + "/"; - zip_entry_opencasesensitive(zip, new_path.c_str()); - zip_entry_close(zip); - auto children = api::fs::get_children(node); - for (const auto& child : children) - add_to_zip(zip, key, child, new_path); - } else { - zip_entry_opencasesensitive(zip, (path + node.getValueOfName()).c_str()); - std::ifstream file("./files/" + std::to_string(node.getValueOfId()), std::ifstream::binary); - std::vector buffer(64*1024); - while (!file.eof()) { - file.read(buffer.data(), (std::streamsize)buffer.size()); - auto read = file.gcount(); - zip_entry_write(zip, buffer.data(), read); - std::get<1>(in_progress_zips[key]) += read; - } - zip_entry_close(zip); - } - } -} diff --git a/backend/src/controllers/fs/fs_routes.cpp b/backend/src/controllers/fs/fs_routes.cpp deleted file mode 100644 index d7e09e7..0000000 --- a/backend/src/controllers/fs/fs_routes.cpp +++ /dev/null @@ -1,349 +0,0 @@ -#include -#include - -#include -#include - -#include "controllers/controllers.h" -#include "dto/dto.h" - -// https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Image_types#common_image_file_types -const std::unordered_map mime_type_map = { - { ".apng" , "image/apng" }, - { ".avif" , "image/avif" }, - { ".bmp" , "image/bmp" }, - { ".gif" , "image/gif" }, - { ".jpg" , "image/jpeg" }, - { ".jpeg" , "image/jpeg" }, - { ".jfif" , "image/jpeg" }, - { ".pjpeg", "image/jpeg" }, - { ".pjp" , "image/jpeg" }, - { ".png" , "image/png" }, - { ".svg" , "image/svg" }, - { ".webp" , "image/webp" }, - - { ".aac" , "audio/aac" }, - { ".flac" , "audio/flac" }, - { ".mp3" , "audio/mp3" }, - { ".m4a" , "audio/mp4" }, - { ".oga" , "audio/ogg" }, - { ".ogg" , "audio/ogg" }, - { ".wav" , "audio/wav" }, - - { ".3gp" , "video/3gpp" }, - { ".mpg" , "video/mpeg" }, - { ".mpeg" , "video/mpeg" }, - { ".mp4" , "video/mp4" }, - { ".m4v" , "video/mp4" }, - { ".m4p" , "video/mp4" }, - { ".ogv" , "video/ogg" }, - { ".mov" , "video/quicktime" }, - { ".webm" , "video/webm" }, - { ".mkv" , "video/x-matroska" }, - { ".mk3d" , "video/x-matroska" }, - { ".mks" , "video/x-matroska" }, - - { ".pdf" , "application/pdf" } -}; - -template -std::string join_string(InputIt first, InputIt last, const std::string& separator = ",") { - std::ostringstream result; - if (first != last) { - result << *first; - while (++first != last) { - result << separator << *first; - } - } - return result.str(); -} - -namespace api { - void fs::root(req_type req, cbk_type cbk) { - db::User user = dto::get_user(req); - cbk(dto::Responses::get_root_res(user.getValueOfRootId())); - } - - void fs::node(req_type req, cbk_type cbk, uint64_t node) { - db::User user = dto::get_user(req); - std::shared_lock lock(*get_user_mutex(user.getValueOfId())); - auto inode = get_node_and_validate(user, node); - if (!inode.has_value()) - return cbk(dto::Responses::get_badreq_res("Unknown node")); - auto dto_node = dto::Responses::GetNodeEntry(*inode); - std::vector children; - if (!dto_node.is_file) for (const db::INode& child : get_children(*inode)) children.emplace_back(child); - cbk(dto::Responses::get_node_res(dto_node, children)); - } - - void fs::path(req_type req, cbk_type cbk, uint64_t node) { - db::User user = dto::get_user(req); - std::shared_lock lock(*get_user_mutex(user.getValueOfId())); - auto inode = get_node_and_validate(user, node); - if (!inode.has_value()) - cbk(dto::Responses::get_badreq_res("Unknown node")); - else { - auto path = generate_path(*inode); - cbk(dto::Responses::get_success_res(path)); - } - } - - template - void fs::create_node_req(req_type req, cbk_type cbk) { - db::User user = dto::get_user(req); - Json::Value& json = *req->jsonObject(); - try { - uint64_t parent = dto::json_get(json, "parent").value(); - std::string name = dto::json_get(json, "name").value(); - - std::shared_lock lock(*get_user_mutex(user.getValueOfId())); - - auto new_node = create_node(name, user, file, std::make_optional(parent)); - if (std::holds_alternative(new_node)) - cbk(dto::Responses::get_new_node_res(std::get(new_node).getValueOfId())); - else if (std::holds_alternative(new_node)) - switch (std::get(new_node)) { - case create_node_error::INVALID_NAME: return cbk(dto::Responses::get_badreq_res("Invalid name")); - case create_node_error::INVALID_PARENT: return cbk(dto::Responses::get_badreq_res("Invalid parent")); - case create_node_error::FILE_PARENT: return cbk(dto::Responses::get_badreq_res("Parent is file")); - } - else { - auto tuple = std::get>(new_node); - cbk(dto::Responses::get_node_exists_res(std::get<1>(tuple), std::get<0>(tuple))); - } - } catch (const std::exception&) { - cbk(dto::Responses::get_badreq_res("Validation error")); - } - } - - void fs::delete_node_req(req_type req, cbk_type cbk, uint64_t node) { - db::User user = dto::get_user(req); - std::unique_lock lock(*get_user_mutex(user.getValueOfId())); - auto inode = get_node_and_validate(user, node); - if (!inode.has_value()) - cbk(dto::Responses::get_badreq_res("Unknown node")); - else if (inode->getValueOfParentId() == 0) - cbk(dto::Responses::get_badreq_res("Can't delete root")); - else { - auto chan = std::make_shared>(); - std::string("Waiting in queue...\n") >> (*chan); - get_delete_loop()->queueInLoop([chan, inode=*inode, user=user.getValueOfId()]{ - std::unique_lock lock(*get_user_mutex(user)); - delete_node(inode, *chan); - chan->close(); - }); - cbk(drogon::HttpResponse::newStreamResponse([chan](char* buf, std::size_t size) -> std::size_t{ - if (buf == nullptr) return 0; - if (chan->closed() && chan->empty()) return 0; - std::string buffer; - buffer << *chan; - if (buffer.empty()) return 0; - std::size_t read = std::min(size, buffer.size()); - std::memcpy(buf, buffer.data(), read); // NOLINT(bugprone-not-null-terminated-result) - return read; - })); - } - } - - void fs::upload(req_type req, cbk_type cbk, uint64_t node) { - constexpr int image_height = 256; - db::User user = dto::get_user(req); - - std::shared_lock lock(*get_user_mutex(user.getValueOfId())); - - auto inode = get_node_and_validate(user, node); - if (!inode.has_value()) - return cbk(dto::Responses::get_badreq_res("Unknown node")); - if (inode->getValueOfIsFile() == 0) - return cbk(dto::Responses::get_badreq_res("Can't upload to a directory")); - - drogon::MultiPartParser mpp; - if (mpp.parse(req) != 0) - return cbk(dto::Responses::get_badreq_res("Failed to parse files")); - if (mpp.getFiles().size() != 1) - return cbk(dto::Responses::get_badreq_res("Exactly 1 file needed")); - - const drogon::HttpFile& file = mpp.getFiles().at(0); - - std::filesystem::path p("./files"); - p /= std::to_string(inode->getValueOfId()); - - file.saveAs(p.string()); - try { - if (file.fileLength() > 100 * 1024 * 1024) throw std::exception(); - std::filesystem::path filename(inode->getValueOfName()); - std::string ext = filename.extension().string(); - std::transform(ext.begin(), ext.end(), ext.begin(), tolower); - const std::string& mime = mime_type_map.at(ext); - if (!mime.starts_with("image")) throw std::exception(); - - cv::_InputArray image_arr(file.fileData(), (int) file.fileLength()); - cv::Mat image = cv::imdecode(image_arr, cv::IMREAD_COLOR); - if (!image.empty()) { - float h_ration = ((float) image_height) / ((float) image.rows); - cv::Mat preview; - cv::resize(image, preview, cv::Size((int) (((float) image.cols) * h_ration), image_height), 0, 0, cv::INTER_AREA); - cv::imwrite(p.string() + "_preview.png", preview); - inode->setHasPreview(1); - } - } catch (const std::exception&) {} - inode->setSize(file.fileLength()); - - db::MapperInode inode_mapper(drogon::app().getDbClient()); - inode_mapper.update(*inode); - - cbk(dto::Responses::get_success_res()); - } - - void fs::create_zip(req_type req, cbk_type cbk) { - db::User user = dto::get_user(req); - - std::shared_lock lock(*get_user_mutex(user.getValueOfId())); - - Json::Value& json = *req->jsonObject(); - try { - if (!json.isMember("nodes")) throw std::exception(); - Json::Value node_arr = json["nodes"]; - if (!node_arr.isArray()) throw std::exception(); - std::vector node_ids; - for (const auto& node : node_arr) - node_ids.push_back(node.asUInt64()); - - std::vector nodes; - std::transform(node_ids.begin(), node_ids.end(), std::back_inserter(nodes), [&user](uint64_t node) { - return api::fs::get_node_and_validate(user, node).value(); - }); - - std::string key = join_string(node_ids.begin(), node_ids.end()); - - if (zip_to_temp_map.contains(key)) return cbk(dto::Responses::get_create_zip_done_res()); - if (in_progress_zips.contains(key)) { - auto progress = in_progress_zips.at(key); - return cbk(dto::Responses::get_create_zip_done_res(std::get<1>(progress), std::get<2>(progress))); - } - std::string file_name = "./temp/fs_" + std::to_string(next_temp_id++) + ".zip"; - in_progress_zips.emplace(key, std::make_tuple(file_name, 0, 1)); - get_zip_loop()->queueInLoop([key = std::move(key), nodes = std::move(nodes), file_name = std::move(file_name), user=user.getValueOfId()]{ - { - std::shared_lock lock(*get_user_mutex(user)); - uint64_t size = 0; - for (const auto& node : nodes) size += calc_total_size(node); - std::get<2>(in_progress_zips.at(key)) = size; - struct zip_t* zip = zip_open(file_name.c_str(), ZIP_DEFAULT_COMPRESSION_LEVEL, 'w'); - for (const db::INode& node : nodes) - add_to_zip(zip, key, node, ""); - zip_close(zip); - } - zip_to_temp_map.emplace(key, file_name); - in_progress_zips.erase(key); - }); - return cbk(dto::Responses::get_create_zip_done_res(0, 1)); - } catch (const std::exception&) { - cbk(dto::Responses::get_badreq_res("Validation error")); - } - } - - void fs::download(req_type req, cbk_type cbk) { - db::User user = dto::get_user(req); - - std::shared_lock lock(*get_user_mutex(user.getValueOfId())); - - auto node_id = req->getOptionalParameter("id"); - if (!node_id.has_value()) { - cbk(dto::Responses::get_badreq_res("Invalid node")); - return; - } - auto inode = get_node_and_validate(user, *node_id); - if (!inode.has_value()) { - cbk(dto::Responses::get_badreq_res("Invalid node")); - return; - } - - if (inode->getValueOfIsFile() != 0) { - std::filesystem::path p("./files"); - p /= std::to_string(inode->getValueOfId()); - - cbk(drogon::HttpResponse::newFileResponse( - p.string(), - inode->getValueOfName() - )); - } else { - try { - std::string key = std::to_string(inode->getValueOfId()); - std::string file = zip_to_temp_map.at(key); - zip_to_temp_map.erase(key); - cbk(drogon::HttpResponse::newFileResponse( - file, - inode->getValueOfName() + ".zip" - )); - } catch (const std::exception&) { - cbk(dto::Responses::get_badreq_res("Invalid node")); - } - } - } - - void fs::download_multi(req_type req, cbk_type cbk) { - db::User user = dto::get_user(req); - - std::shared_lock lock(*get_user_mutex(user.getValueOfId())); - - auto node_ids_str = req->getOptionalParameter("id"); - if (!node_ids_str.has_value()) - return cbk(dto::Responses::get_badreq_res("No nodes")); - - std::stringstream node_ids_ss(*node_ids_str); - std::string temp; - try { - while (std::getline(node_ids_ss, temp, ',')) - if (!get_node_and_validate(user, std::stoull(temp)).has_value()) throw std::exception(); - - std::string file = zip_to_temp_map.at(*node_ids_str); - zip_to_temp_map.erase(*node_ids_str); - cbk(drogon::HttpResponse::newFileResponse( - file, - "files.zip" - )); - } catch (const std::exception&) { - cbk(dto::Responses::get_badreq_res("Invalid nodes")); - } - } - - void fs::download_preview(req_type req, cbk_type cbk, uint64_t node) { - db::User user = dto::get_user(req); - - std::shared_lock lock(*get_user_mutex(user.getValueOfId())); - - auto inode = get_node_and_validate(user, node); - if (!inode.has_value()) - return cbk(dto::Responses::get_badreq_res("Unknown node")); - if (inode->getValueOfHasPreview() == 0) - return cbk(dto::Responses::get_badreq_res("No preview")); - - std::filesystem::path p("./files"); - p /= std::to_string(inode->getValueOfId()) + "_preview.png"; - std::ifstream file(p, std::ios::in | std::ios::binary); - std::vector image((std::istreambuf_iterator(file)), std::istreambuf_iterator()); - - cbk(dto::Responses::get_download_base64_res("data:image/png;base64," + Botan::base64_encode(image))); - } - - void fs::get_type(req_type req, cbk_type cbk, uint64_t node){ - db::User user = dto::get_user(req); - - std::shared_lock lock(*get_user_mutex(user.getValueOfId())); - - auto inode = get_node_and_validate(user, node); - if (!inode.has_value()) - return cbk(dto::Responses::get_badreq_res("Unknown node")); - - std::filesystem::path name(inode->getValueOfName()); - std::string ext = name.extension().string(); - std::transform(ext.begin(), ext.end(), ext.begin(), tolower); - - try { - cbk(dto::Responses::get_type_res(mime_type_map.at(ext))); - } catch (const std::exception&) { - cbk(dto::Responses::get_badreq_res("Invalid file type")); - } - } -} \ No newline at end of file diff --git a/backend/src/controllers/user.cpp b/backend/src/controllers/user.cpp deleted file mode 100644 index b550cae..0000000 --- a/backend/src/controllers/user.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#pragma clang diagnostic push -#pragma ide diagnostic ignored "performance-unnecessary-value-param" -#pragma ide diagnostic ignored "readability-convert-member-functions-to-static" - -#include "controllers.h" -#include "dto/dto.h" - -namespace api { - void user::info(req_type req, cbk_type cbk) { - db::User user = dto::get_user(req); - cbk(dto::Responses::get_user_info_res( - user.getValueOfName(), - user.getValueOfGitlab() != 0, - db::User_getEnumTfaType(user) != db::tfaTypes::NONE) - ); - } - - void user::delete_user(req_type req, cbk_type cbk) { - db::MapperUser user_mapper(drogon::app().getDbClient()); - - msd::channel chan; - db::User user = dto::get_user(req); - auth::revoke_all(user); - - std::unique_lock lock(*fs::get_user_mutex(user.getValueOfId())); - - fs::delete_node((fs::get_node(user.getValueOfRootId())).value(), chan, true); - user_mapper.deleteOne(user); - - cbk(dto::Responses::get_success_res()); - } -} -#pragma clang diagnostic pop \ No newline at end of file diff --git a/backend/src/db/connection.rs b/backend/src/db/connection.rs new file mode 100644 index 0000000..d4069f8 --- /dev/null +++ b/backend/src/db/connection.rs @@ -0,0 +1,157 @@ +use diesel::prelude::*; +use crate::db::manager::DB_MANAGER; + +pub struct DBConnection { + db: super::RawDBConnection +} + +impl From for DBConnection { + fn from(conn: super::RawDBConnection) -> Self { + Self { + db: conn + } + } +} + +impl DBConnection { + // Users + pub fn create_user_password( + &mut self, + name: String, + password: String + ) -> super::User { + let mut new_user: super::User = diesel::insert_into(crate::schema::user::table) + .values(super::user::NewUser { + name, + password, + gitlab: false, + role: super::UserRole::Disabled, + root_id: 0, + tfa_type: super::TfaTypes::None, + tfa_secret: None, + gitlab_at: None, + gitlab_rt: None + }) + .get_result(&mut self.db) + .expect("Failed to insert new user"); + + let root_node = crate::routes::fs::create_node("".to_owned(), &new_user, false, None, true, self).expect("Couldn't create root node"); + new_user.root_id = root_node.id; + self.save_user(&new_user); + new_user + } + + pub fn create_user_gitlab( + &mut self, + name: String, + role: super::UserRole, + gitlab_at: String, + gitlab_rt: String + ) -> super::User { + let mut new_user: super::User = diesel::insert_into(crate::schema::user::table) + .values(super::user::NewUser { + name, + password: "".to_owned(), + gitlab: true, + role, + root_id: 0, + tfa_type: super::TfaTypes::None, + tfa_secret: None, + gitlab_at: Some(gitlab_at), + gitlab_rt: Some(gitlab_rt) + }) + .get_result(&mut self.db) + .expect("Failed to insert new user"); + + let root_node = crate::routes::fs::create_node("".to_owned(), &new_user, false, None, true, self).expect("Couldn't create root node"); + new_user.root_id = root_node.id; + self.save_user(&new_user); + new_user + } + + pub fn get_user(&mut self, _id: i32) -> Option { + use crate::schema::user::dsl::*; + user.find(_id).first(&mut self.db).ok() + } + + pub fn find_user(&mut self, _name: &str, _gitlab: bool) -> Option { + use crate::schema::user::dsl::*; + user.filter(name.eq(name)).filter(gitlab.eq(_gitlab)).first(&mut self.db).ok() + } + + pub fn get_users(&mut self) -> Vec { + crate::schema::user::table.load(&mut self.db).expect("Could not load users") + } + + pub fn save_user(&mut self, user: &super::User) { + diesel::update(user) + .set(user.clone()) + .execute(&mut self.db) + .expect("Failed to save user"); + } + + pub fn delete_user(&mut self, user: &super::User) { + diesel::delete(user).execute(&mut self.db).expect("Failed to delete user"); + } + + // Tokens + pub fn create_token(&mut self, _owner: i32, _exp: i64) -> super::Token { + diesel::insert_into(crate::schema::tokens::table) + .values(&super::token::NewToken { + owner_id: _owner, + exp: _exp + }) + .get_result(&mut self.db) + .expect("Failed to save new token to database") + } + + pub fn get_token(&mut self, _id: i32) -> Option { + use crate::schema::tokens::dsl::*; + tokens.find(_id).first(&mut self.db).ok() + } + + pub fn delete_token(&mut self, _id: i32) { + use crate::schema::tokens::dsl::*; + diesel::delete(tokens.find(_id)) + .execute(&mut self.db) + .expect("Failed to delete token"); + } + + pub fn delete_all_tokens(&mut self, _owner: i32) { + use crate::schema::tokens::dsl::*; + diesel::delete(tokens.filter(owner_id.eq(_owner))) + .execute(&mut self.db) + .expect("Failed to delete token"); + } + + pub fn cleanup_tokens(&mut self) { + use crate::schema::tokens::dsl::*; + let current_time = chrono::Utc::now().timestamp(); + diesel::delete(tokens.filter(exp.le(current_time))).execute(&mut self.db).expect("Failed to cleanup tokens"); + } + + // Nodes + pub async fn get_lock(user: i32) -> std::sync::Arc> { + DB_MANAGER.get_lock(user).await + } + + pub fn create_node(&mut self, file: bool, name: String, parent: Option, owner: i32) -> super::Inode { + DB_MANAGER.create_node(&mut self.db, file, name, parent, owner) + } + + pub fn get_node(&mut self, id: i32) -> Option { + DB_MANAGER.get_node(&mut self.db, id) + } + + pub fn get_children(&mut self, id: i32) -> Vec { + DB_MANAGER.get_children(&mut self.db, id) + } + + pub fn save_node(&mut self, node: &super::Inode) { + DB_MANAGER.save_node(&mut self.db, node); + } + + pub fn delete_node(&mut self, node: &super::Inode) { + DB_MANAGER.delete_node(&mut self.db, node); + } +} \ No newline at end of file diff --git a/backend/src/db/db.cpp b/backend/src/db/db.cpp deleted file mode 100644 index 88cf967..0000000 --- a/backend/src/db/db.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include "db.h" - -namespace db { - UserRole User_getEnumRole(const User& user) noexcept { - return (UserRole)user.getValueOfRole(); - } - - tfaTypes User_getEnumTfaType(const User& user) noexcept { - return (tfaTypes)user.getValueOfTfaType(); - } -} diff --git a/backend/src/db/db.h b/backend/src/db/db.h deleted file mode 100644 index 5807c01..0000000 --- a/backend/src/db/db.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef BACKEND_DB_H -#define BACKEND_DB_H - -#include - -#include -#include - -#include "Inode.h" -#include "Tokens.h" -#include "User.h" - -namespace db { - enum UserRole : int { - ADMIN = 2, - USER = 1, - DISABLED = 0 - }; - - enum tfaTypes : int { - NONE = 0, - EMAIL = 1, - TOTP = 2 - }; - - using INode = drogon_model::sqlite3::Inode; - using Token = drogon_model::sqlite3::Tokens; - using User = drogon_model::sqlite3::User; - - using MapperInode = drogon::orm::Mapper; - using MapperToken = drogon::orm::Mapper; - using MapperUser = drogon::orm::Mapper; - - using Criteria = drogon::orm::Criteria; - using CompareOps = drogon::orm::CompareOperator; - - UserRole User_getEnumRole(const User&) noexcept; - tfaTypes User_getEnumTfaType(const User&) noexcept; -} - -#endif //BACKEND_DB_H diff --git a/backend/src/db/inode.rs b/backend/src/db/inode.rs new file mode 100644 index 0000000..313aae3 --- /dev/null +++ b/backend/src/db/inode.rs @@ -0,0 +1,26 @@ +use diesel::prelude::*; + +#[derive(Queryable, Identifiable, Eq, PartialEq, Debug, Associations, AsChangeset, Clone)] +#[diesel(belongs_to(crate::db::User, foreign_key = owner_id))] +#[diesel(table_name = crate::schema::inode)] +#[diesel(treat_none_as_null = true)] +pub struct Inode { + pub id: i32, + pub is_file: bool, + pub name: String, + pub parent_id: Option, + pub owner_id: i32, + pub size: Option, + pub has_preview: bool +} + +#[derive(Insertable, Debug)] +#[diesel(table_name = crate::schema::inode)] +pub struct NewInode { + pub is_file: bool, + pub name: String, + pub parent_id: Option, + pub owner_id: i32, + pub size: Option, + pub has_preview: bool +} diff --git a/backend/src/db/manager.rs b/backend/src/db/manager.rs new file mode 100644 index 0000000..ba4f0a6 --- /dev/null +++ b/backend/src/db/manager.rs @@ -0,0 +1,109 @@ +use std::collections::HashMap; +use std::sync::Arc; +use lazy_static::lazy_static; +use stretto::Cache; +use tokio::sync::{Mutex, RwLock}; +use diesel::prelude::*; +use crate::db::Inode; + +lazy_static! { + pub(super) static ref DB_MANAGER: DBManager = DBManager::new(); +} + +pub(super) struct DBManager { + locks: Mutex>>>, + node_cache: Cache, + children_cache: Cache> +} + +impl DBManager { + fn new() -> Self { + Self { + locks: Mutex::new(HashMap::new()), + node_cache: Cache::new(10000, 1000).expect("Failed to create node cache"), + children_cache: Cache::new(1000, 100).expect("Failed to create child cache") + } + } + + pub fn create_node(&self, db: &mut super::RawDBConnection, file: bool, _name: String, parent: Option, owner: i32) -> Inode { + use crate::schema::inode::dsl::*; + let node: Inode = diesel::insert_into(inode) + .values(crate::db::inode::NewInode { + is_file: file, + name: _name, + parent_id: parent, + owner_id: owner, + size: None, + has_preview: false + }) + .get_result(db) + .expect("Failed to insert new inode"); + + self.node_cache.insert(node.id, node.clone(), 1); + if let Some(parent) = parent { + self.children_cache.remove(&parent); + } + + node + } + + pub fn get_node(&self, db: &mut super::RawDBConnection, node_id: i32) -> Option { + use crate::schema::inode::dsl::*; + let node = self.node_cache.get(&node_id); + match node { + Some(v) => Some(v.value().clone()), + None => { + let v: Inode = inode.find(node_id).first(db).ok()?; + self.node_cache.insert(node_id, v.clone(), 1); + Some(v) + } + } + } + + pub fn get_children(&self, db: &mut super::RawDBConnection, node_id: i32) -> Vec { + use crate::schema::inode::dsl::*; + let children = self.children_cache.get(&node_id); + match children { + Some(v) => v.value().clone(), + None => { + let v = inode.filter(parent_id.eq(node_id)).load(db).expect("Failed to get children of node"); + self.children_cache.insert(node_id, v.clone(), 1); + v + } + } + } + + pub fn save_node(&self, db: &mut super::RawDBConnection, node: &Inode) { + self.node_cache.insert(node.id, node.clone(), 1); + diesel::update(node) + .set(node.clone()) + .execute(db) + .expect("Failed to save node"); + } + + pub fn delete_node(&self, db: &mut super::RawDBConnection, node: &Inode) { + if node.is_file { + let file_name = format!("./files/{}", node.id); + let file = std::path::Path::new(&file_name); + let preview_name = format!("./files/{}_preview.jpg", node.id); + let preview = std::path::Path::new(&preview_name); + if file.exists() { + std::fs::remove_file(file).expect("Failed to delete file"); + } + if preview.exists() { + std::fs::remove_file(preview).expect("Failed to delete preview"); + } + } + diesel::delete(node).execute(db).expect("Failed to delete node"); + self.node_cache.remove(&node.id); + self.children_cache.remove(&node.id); + if let Some(p) = node.parent_id { self.children_cache.remove(&p); } + } + + pub async fn get_lock(&self, user: i32) -> Arc> { + self.locks.lock().await + .entry(user) + .or_insert_with(|| Arc::new(RwLock::new(()))) + .clone() + } +} diff --git a/backend/src/db/mod.rs b/backend/src/db/mod.rs new file mode 100644 index 0000000..3e80cba --- /dev/null +++ b/backend/src/db/mod.rs @@ -0,0 +1,54 @@ +mod inode; +mod token; +mod user; +pub mod manager; +mod connection; + +use diesel::connection::SimpleConnection; +use diesel::sqlite::SqliteConnection; +use diesel::r2d2::{ConnectionManager, Pool, PooledConnection}; +use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; +use warp::Filter; + +pub use inode::Inode; +pub use token::Token; +pub use user::{User, TfaTypes, UserRole}; +use crate::routes::AppError; + +type RawDBConnection = PooledConnection>; +pub type DBPool = Pool>; +pub use connection::DBConnection; + +pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("./migrations"); + +#[derive(Debug)] +pub struct ConnectionOptions {} + +impl diesel::r2d2::CustomizeConnection for ConnectionOptions { + fn on_acquire(&self, conn: &mut SqliteConnection) -> Result<(), diesel::r2d2::Error> { + conn.batch_execute("PRAGMA journal_mode = WAL; PRAGMA synchronous = NORMAL; PRAGMA busy_timeout = 15000;") + .map_err(diesel::r2d2::Error::QueryError) + } +} + +pub fn build_pool() -> Pool> { + Pool::builder() + .connection_customizer(Box::new(ConnectionOptions {})) + .build(ConnectionManager::::new("sqlite.db")) + .expect("Failed to open db") +} + +pub fn run_migrations(db: &mut RawDBConnection) { + db.run_pending_migrations(MIGRATIONS).expect("Failed to run migrations"); +} + +pub fn with_db(pool: DBPool) -> impl Filter + Clone { + warp::any() + .map(move || pool.clone()) + .and_then(|pool: DBPool| async move { + match pool.get() { + Ok(v) => Ok(DBConnection::from(v)), + Err(_) => AppError::InternalError("Failed to get a database connection").err() + } + }) +} diff --git a/backend/src/db/token.rs b/backend/src/db/token.rs new file mode 100644 index 0000000..f2978b0 --- /dev/null +++ b/backend/src/db/token.rs @@ -0,0 +1,18 @@ +use diesel::prelude::*; + +#[derive(Queryable, Identifiable, Eq, PartialEq, Debug, Associations, AsChangeset)] +#[diesel(belongs_to(crate::db::User, foreign_key = owner_id))] +#[diesel(table_name = crate::schema::tokens)] +#[diesel(treat_none_as_null = true)] +pub struct Token { + pub id: i32, + pub owner_id: i32, + pub exp: i64 +} + +#[derive(Insertable, Debug)] +#[diesel(table_name = crate::schema::tokens)] +pub struct NewToken { + pub owner_id: i32, + pub exp: i64 +} diff --git a/backend/src/db/user.rs b/backend/src/db/user.rs new file mode 100644 index 0000000..2dea9b5 --- /dev/null +++ b/backend/src/db/user.rs @@ -0,0 +1,113 @@ +use diesel::backend::RawValue; +use diesel::deserialize::{FromSql, FromSqlRow}; +use diesel::prelude::*; +use diesel::serialize::{IsNull, Output, ToSql}; +use diesel::sql_types::SmallInt; +use diesel::sqlite::Sqlite; +use serde_repr::{Deserialize_repr, Serialize_repr}; + +#[repr(i16)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Deserialize_repr, Serialize_repr, FromSqlRow)] +pub enum UserRole { + Disabled = 0, + User = 1, + Admin = 2 +} + +impl FromSql for UserRole { + fn from_sql(bytes: RawValue<'_, Sqlite>) -> diesel::deserialize::Result { + match i16::from_sql(bytes)? { + 1 => Ok(UserRole::User), + 2 => Ok(UserRole::Admin), + _ => Ok(UserRole::Disabled) + } + } +} + +impl ToSql for UserRole { + fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> diesel::serialize::Result { + let val: i16 = (*self).into(); + out.set_value(val as i32); + Ok(IsNull::No) + } +} + +impl From for i16 { + fn from(v: UserRole) -> Self { + match v { + UserRole::Disabled => 0, + UserRole::User => 1, + UserRole::Admin => 2 + } + } +} + +#[repr(i16)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Deserialize_repr, Serialize_repr, FromSqlRow)] +pub enum TfaTypes { + None = 0, + Email = 1, + Totp = 2 +} + +impl FromSql for TfaTypes { + fn from_sql(bytes: RawValue<'_, Sqlite>) -> diesel::deserialize::Result { + match i16::from_sql(bytes)? { + 1 => Ok(TfaTypes::Email), + 2 => Ok(TfaTypes::Totp), + _ => Ok(TfaTypes::None) + } + } +} + +impl ToSql for TfaTypes { + fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> diesel::serialize::Result { + let val: i16 = (*self).into(); + out.set_value(val as i32); + Ok(IsNull::No) + } +} + +impl From for i16 { + fn from(v: TfaTypes) -> Self { + match v { + TfaTypes::None => 0, + TfaTypes::Email => 1, + TfaTypes::Totp => 2 + } + } +} + +#[derive(Queryable, Identifiable, Eq, PartialEq, Debug, AsChangeset, Clone)] +#[diesel(table_name = crate::schema::user)] +#[diesel(treat_none_as_null = true)] +pub struct User { + pub id: i32, + pub gitlab: bool, + pub name: String, + pub password: String, + #[diesel(serialize_as = i16)] + pub role: UserRole, + pub root_id: i32, + #[diesel(serialize_as = i16)] + pub tfa_type: TfaTypes, + pub tfa_secret: Option>, + pub gitlab_at: Option, + pub gitlab_rt: Option +} + +#[derive(Insertable, Debug)] +#[diesel(table_name = crate::schema::user)] +pub struct NewUser { + pub gitlab: bool, + pub name: String, + pub password: String, + #[diesel(serialize_as = i16)] + pub role: UserRole, + pub root_id: i32, + #[diesel(serialize_as = i16)] + pub tfa_type: TfaTypes, + pub tfa_secret: Option>, + pub gitlab_at: Option, + pub gitlab_rt: Option +} diff --git a/backend/src/dto.rs b/backend/src/dto.rs new file mode 100644 index 0000000..3943916 --- /dev/null +++ b/backend/src/dto.rs @@ -0,0 +1,194 @@ +#[allow(non_snake_case)] +pub mod responses { + use serde::{self, Deserialize, Serialize}; + + #[derive(Debug, Deserialize, Serialize, Clone)] + pub struct Error { + pub statusCode: i32, + pub message: String + } + + #[derive(Debug, Deserialize, Serialize, Clone)] + pub struct Success { + pub statusCode: i32 + } + + #[derive(Debug, Deserialize, Serialize, Clone)] + pub struct Login { + pub statusCode: i32, + pub jwt: String + } + + #[derive(Debug, Deserialize, Serialize, Clone)] + pub struct TfaSetup { + pub statusCode: i32, + pub secret: String, + pub qrCode: String + } + + #[derive(Debug, Deserialize, Serialize, Clone)] + pub struct UserInfo { + pub statusCode: i32, + pub name: String, + pub gitlab: bool, + pub tfaEnabled: bool + } + + #[derive(Debug, Deserialize, Serialize, Clone)] + pub struct AdminUsersEntry { + pub id: i32, + pub gitlab: bool, + pub name: String, + pub role: crate::db::UserRole, + pub tfaEnabled: bool + } + + #[derive(Debug, Deserialize, Serialize, Clone)] + pub struct AdminUsers { + pub statusCode: i32, + pub users: Vec + } + + #[derive(Debug, Deserialize, Serialize, Clone)] + pub struct Root { + pub statusCode: i32, + pub rootId: i32 + } + + #[derive(Debug, Deserialize, Serialize, Clone)] + pub struct GetNodeEntry { + pub id: i32, + pub name: String, + pub isFile: bool, + pub preview: bool, + pub parent: Option, + pub size: Option + } + + #[derive(Debug, Deserialize, Serialize, Clone)] + pub struct GetNode { + pub statusCode: i32, + pub id: i32, + pub name: String, + pub isFile: bool, + pub preview: bool, + pub parent: Option, + pub size: Option, + pub children: Option> + } + + #[derive(Debug, Deserialize, Serialize, Clone)] + pub struct NewNode { + pub statusCode: i32, + pub id: i32 + } + + #[derive(Debug, Deserialize, Serialize, Clone)] + pub struct NodeExists { + pub statusCode: i32, + pub id: i32, + pub exists: bool, + pub isFile: bool + } + + #[derive(Debug, Deserialize, Serialize, Clone)] + pub struct DownloadBase64 { + pub statusCode: i32, + pub data: String + } + + #[derive(Debug, Deserialize, Serialize, Clone)] + pub struct Type { + pub statusCode: i32, + #[serde(rename = "type")] + pub _type: String + } + + #[derive(Debug, Deserialize, Serialize, Clone)] + pub struct CreateZipDone { + pub statusCode: i32, + pub done: bool, + pub progress: Option, + pub total: Option + } + + #[derive(Debug, Deserialize, Serialize, Clone)] + pub struct GetPathSegment { + pub path: String, + pub node: Option + } + + #[derive(Debug, Deserialize, Serialize, Clone)] + pub struct GetPath { + pub segments: Vec + } +} + +#[allow(non_snake_case)] +pub mod requests { + use serde::{self, Deserialize, Serialize}; + + #[derive(Debug, Deserialize, Serialize, Clone)] + pub struct Admin { + pub user: i32 + } + + #[derive(Debug, Deserialize, Serialize, Clone)] + pub struct AdminSetRole { + pub user: i32, + pub role: crate::db::UserRole + } + + #[derive(Debug, Deserialize, Serialize, Clone)] + pub struct SignUp { + pub username: String, + pub password: String + } + + #[derive(Debug, Deserialize, Serialize, Clone)] + pub struct Login { + pub username: String, + pub password: String, + pub otp: Option + } + + #[derive(Debug, Deserialize, Serialize, Clone)] + pub struct TfaSetup { + pub mail: bool + } + + #[derive(Debug, Deserialize, Serialize, Clone)] + pub struct TfaComplete { + pub mail: bool, + pub code: String + } + + #[derive(Debug, Deserialize, Serialize, Clone)] + pub struct ChangePassword { + pub oldPassword: String, + pub newPassword: String + } + + #[derive(Debug, Deserialize, Serialize, Clone)] + pub struct CreateNode { + pub parent: i32, + pub name: String + } + + #[derive(Debug, Deserialize, Serialize, Clone)] + pub struct CreateZip { + pub nodes: Vec + } + + #[derive(Debug, Deserialize, Serialize, Clone)] + pub struct Download { + pub jwtToken: String, + pub id: i32 + } + + #[derive(Debug, Deserialize, Serialize, Clone)] + pub struct DownloadMulti { + pub jwtToken: String, + pub id: String + } +} diff --git a/backend/src/dto/dto.h b/backend/src/dto/dto.h deleted file mode 100644 index b4d6348..0000000 --- a/backend/src/dto/dto.h +++ /dev/null @@ -1,69 +0,0 @@ -#ifndef BACKEND_DTO_H -#define BACKEND_DTO_H - -#include -#include "db/db.h" - -namespace dto { - template - std::optional json_get(const Json::Value& j, const std::string& key) { - return j.isMember(key) - ? std::make_optional(j[key].as()) - : std::nullopt; - } - - inline db::User get_user(const drogon::HttpRequestPtr& req) { - return req->attributes()->get("user"); - } - - inline db::Token get_token(const drogon::HttpRequestPtr& req) { - return req->attributes()->get("token"); - } - - namespace Responses { - struct GetUsersEntry { - GetUsersEntry(uint64_t id, bool gitlab, bool tfa, std::string name, db::UserRole role) - : id(id), gitlab(gitlab), tfa(tfa), name(std::move(name)), role(role) {} - uint64_t id; - bool gitlab, tfa; - std::string name; - db::UserRole role; - }; - - struct GetNodeEntry { - explicit GetNodeEntry(const db::INode& node) : id(node.getValueOfId()), name(node.getValueOfName()), is_file(node.getValueOfIsFile() != 0), has_preview(node.getValueOfHasPreview() != 0), parent(node.getParentId()) { - if (node.getValueOfIsFile() != 0) size = node.getValueOfSize(); - } - uint64_t id, size; - std::string name; - bool is_file, has_preview; - std::shared_ptr parent; - }; - - drogon::HttpResponsePtr get_error_res(drogon::HttpStatusCode, const std::string &msg); - drogon::HttpResponsePtr get_success_res(); - drogon::HttpResponsePtr get_success_res(Json::Value &); - - inline drogon::HttpResponsePtr get_badreq_res(const std::string &msg) { return get_error_res(drogon::HttpStatusCode::k400BadRequest, msg); } - inline drogon::HttpResponsePtr get_unauth_res(const std::string &msg) { return get_error_res(drogon::HttpStatusCode::k401Unauthorized, msg); } - inline drogon::HttpResponsePtr get_forbdn_res(const std::string &msg) { return get_error_res(drogon::HttpStatusCode::k403Forbidden, msg); } - - drogon::HttpResponsePtr get_login_res(const std::string &jwt); - drogon::HttpResponsePtr get_tfa_setup_res(const std::string& secret, const std::string& qrcode); - - drogon::HttpResponsePtr get_user_info_res(const std::string& name, bool gitlab, bool tfa); - - drogon::HttpResponsePtr get_admin_users_res(const std::vector& users); - - drogon::HttpResponsePtr get_root_res(uint64_t root); - drogon::HttpResponsePtr get_node_res(const GetNodeEntry& node, const std::vector& children); - drogon::HttpResponsePtr get_new_node_res(uint64_t id); - drogon::HttpResponsePtr get_node_exists_res(uint64_t id, bool file); - drogon::HttpResponsePtr get_download_base64_res(const std::string& data); - drogon::HttpResponsePtr get_type_res(const std::string& type); - drogon::HttpResponsePtr get_create_zip_done_res(); - drogon::HttpResponsePtr get_create_zip_done_res(uint64_t progress, uint64_t total); - } -} - -#endif //BACKEND_DTO_H diff --git a/backend/src/dto/responses.cpp b/backend/src/dto/responses.cpp deleted file mode 100644 index 4a0db1f..0000000 --- a/backend/src/dto/responses.cpp +++ /dev/null @@ -1,126 +0,0 @@ -#include "dto.h" - -namespace dto::Responses { - drogon::HttpResponsePtr get_error_res(drogon::HttpStatusCode code, const std::string& msg) { - Json::Value json; - json["statusCode"] = static_cast(code); - json["message"] = msg; - auto res = drogon::HttpResponse::newHttpJsonResponse(json); - res->setStatusCode(code); - return res; - } - - drogon::HttpResponsePtr get_success_res() { - Json::Value json; - return get_success_res(json); - } - - drogon::HttpResponsePtr get_success_res(Json::Value& json) { - json["statusCode"] = 200; - auto res = drogon::HttpResponse::newHttpJsonResponse(json); - res->setStatusCode(drogon::HttpStatusCode::k200OK); - return res; - } - - drogon::HttpResponsePtr get_login_res(const std::string &jwt) { - Json::Value json; - json["jwt"] = jwt; - return get_success_res(json); - } - - drogon::HttpResponsePtr get_tfa_setup_res(const std::string& secret, const std::string& qrcode) { - Json::Value json; - json["secret"] = secret; - json["qrCode"] = qrcode; - return get_success_res(json); - } - - drogon::HttpResponsePtr get_user_info_res(const std::string &name, bool gitlab, bool tfa) { - Json::Value json; - json["name"] = name; - json["gitlab"] = gitlab; - json["tfaEnabled"] = tfa; - return get_success_res(json); - } - - drogon::HttpResponsePtr get_admin_users_res(const std::vector& users) { - Json::Value json; - for (const GetUsersEntry& user : users) { - Json::Value entry; - entry["id"] = user.id; - entry["gitlab"] = user.gitlab; - entry["name"] = user.name; - entry["role"] = user.role; - entry["tfaEnabled"] = user.tfa; - json["users"].append(entry); - } - return get_success_res(json); - } - - drogon::HttpResponsePtr get_root_res(uint64_t root) { - Json::Value json; - json["rootId"] = root; - return get_success_res(json); - } - - Json::Value parse_node(const GetNodeEntry& node) { - Json::Value json; - json["id"] = node.id; - json["name"] = node.name; - json["isFile"] = node.is_file; - json["preview"] = node.has_preview; - json["parent"] = (node.parent != nullptr) ? *node.parent : Json::Value::nullSingleton(); - if (node.is_file) json["size"] = node.size; - return json; - } - - drogon::HttpResponsePtr get_node_res(const GetNodeEntry& node, const std::vector& children) { - Json::Value json = parse_node(node); - if (!node.is_file) { - json["children"] = Json::Value(Json::arrayValue); - for (const GetNodeEntry& child : children) - json["children"].append(parse_node(child)); - } - return get_success_res(json); - } - - drogon::HttpResponsePtr get_new_node_res(uint64_t id) { - Json::Value json; - json["id"] = id; - return get_success_res(json); - } - - drogon::HttpResponsePtr get_node_exists_res(uint64_t id, bool file) { - Json::Value json; - json["id"] = id; - json["exists"] = true; - json["isFile"] = file; - return get_success_res(json); - } - - drogon::HttpResponsePtr get_download_base64_res(const std::string &data) { - Json::Value json; - json["data"] = data; - return get_success_res(json); - } - - drogon::HttpResponsePtr get_type_res(const std::string &type) { - Json::Value json; - json["type"] = type; - return get_success_res(json); - } - - drogon::HttpResponsePtr get_create_zip_done_res() { - Json::Value json; - json["done"] = true; - return get_success_res(json); - } - - drogon::HttpResponsePtr get_create_zip_done_res(uint64_t progress, uint64_t total) { - Json::Value json; - json["done"] = false; - json["progress"] = progress; - json["total"] = total; - return get_success_res(json); - } -} diff --git a/backend/src/filters/filters.cpp b/backend/src/filters/filters.cpp deleted file mode 100644 index c446439..0000000 --- a/backend/src/filters/filters.cpp +++ /dev/null @@ -1,82 +0,0 @@ -#include "filters.h" - -#include -#include -#include -#include "db/db.h" -#include "dto/dto.h" -#include "controllers/controllers.h" - -void cleanup_tokens(db::MapperToken& mapper) { - const uint64_t now = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); - mapper.deleteBy( - db::Criteria(db::Token::Cols::_exp, db::CompareOps::LE, now) - ); -} - -void Login::doFilter(const drogon::HttpRequestPtr& req, drogon::FilterCallback&& cb, drogon::FilterChainCallback&& ccb) { - std::string token_str; - if (req->path() == "/api/fs/download" || req->path() == "/api/fs/download_multi") { - token_str = req->getParameter("jwtToken"); - } else { - std::string auth_header = req->getHeader("Authorization"); - if (auth_header.empty() || (!auth_header.starts_with("Bearer "))) - return cb(dto::Responses::get_unauth_res("Unauthorized")); - token_str = auth_header.substr(7); - } - try { - auto token = jwt::decode(token_str); - jwt::verify() - .allow_algorithm(jwt::algorithm::hs256{api::auth::get_jwt_secret()}) - .verify(token); - uint64_t token_id = token.get_payload_claim("jti").as_int(); - uint64_t user_id = token.get_payload_claim("sub").as_int(); - - auto db = drogon::app().getDbClient(); - - db::MapperUser user_mapper(db); - db::MapperToken token_mapper(db); - - cleanup_tokens(token_mapper); - - db::Token db_token = token_mapper.findByPrimaryKey(token_id); - db::User db_user = user_mapper.findByPrimaryKey(db_token.getValueOfOwnerId()); - - if (db_user.getValueOfId() != user_id) throw std::exception(); - if (db::User_getEnumRole(db_user) == db::UserRole::DISABLED) throw std::exception(); - - if (db_user.getValueOfGitlab() != 0) { - auto info = api::auth::get_gitlab_user(db_user.getValueOfGitlabAt()); - if (!info.has_value()) { - auto tokens = api::auth::get_gitlab_tokens(db_user.getValueOfGitlabRt(), true); - info = api::auth::get_gitlab_user(tokens->at); - if (!tokens.has_value() || !info.has_value()) { - api::auth::revoke_all(db_user); - throw std::exception(); - } - db_user.setGitlabAt(tokens->at); - db_user.setGitlabRt(tokens->rt); - user_mapper.update(db_user); - } - if (info->name != db_user.getValueOfName()) { - api::auth::revoke_all(db_user); - throw std::exception(); - } - } - - req->attributes()->insert("token", db_token); - req->attributes()->insert("user", db_user); - ccb(); - } catch (const std::exception&) { - cb(dto::Responses::get_unauth_res("Unauthorized")); - } -} - -void Admin::doFilter(const drogon::HttpRequestPtr& req, drogon::FilterCallback&& cb, drogon::FilterChainCallback&& ccb) { - db::User user = dto::get_user(req); - - if (db::User_getEnumRole(user) != db::UserRole::ADMIN) - cb(dto::Responses::get_forbdn_res("Forbidden")); - else - ccb(); -} diff --git a/backend/src/filters/filters.h b/backend/src/filters/filters.h deleted file mode 100644 index c6b3c5f..0000000 --- a/backend/src/filters/filters.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef BACKEND_FILTERS_H -#define BACKEND_FILTERS_H - -#include - -struct Login : public drogon::HttpFilter { - void doFilter(const drogon::HttpRequestPtr&, drogon::FilterCallback&&, drogon::FilterChainCallback&&) override; -}; - -struct Admin : public drogon::HttpFilter { - void doFilter(const drogon::HttpRequestPtr&, drogon::FilterCallback&&, drogon::FilterChainCallback&&) override; -}; - -#endif //BACKEND_FILTERS_H diff --git a/backend/src/main.cpp b/backend/src/main.cpp deleted file mode 100644 index c57349b..0000000 --- a/backend/src/main.cpp +++ /dev/null @@ -1,197 +0,0 @@ -#include -#include - -#include - -#include "dto/dto.h" - -bool dev_mode = false; - -void cleanup() { - std::cout << "Stopping..." << std::endl; - drogon::app().quit(); - std::cout << "Cleanup up uploads..."; - std::filesystem::remove_all("uploads"); - std::cout << " [Done]" << std::endl; - std::cout << "Removing temp folder..." << std::flush; - std::filesystem::remove_all("temp"); - std::cout << " [Done]" << std::endl; - std::cout << "Goodbye!" << std::endl; -} - -std::string get_index_content() { - std::ifstream file("./static/index.html"); - return {std::istreambuf_iterator(file), std::istreambuf_iterator()}; -} - -void default_handler(const drogon::HttpRequestPtr& req, std::function&& cbk) { - static std::string index_html = get_index_content(); - if (req->path().starts_with("/api")) { - std::cout << "Unknown api request: " << req->getMethodString() << " " << req->path() << std::endl; - cbk(drogon::HttpResponse::newNotFoundResponse()); - } else { - if (dev_mode) cbk(drogon::HttpResponse::newFileResponse("./static/index.html")); - else cbk(drogon::HttpResponse::newFileResponse((unsigned char*)index_html.data(), index_html.size(), "", drogon::CT_TEXT_HTML)); - } -} - -int main(int argc, char* argv[]) { - std::vector args(argv+1, argv+argc); - if (std::find(args.begin(), args.end(), "--dev") != args.end()) dev_mode = true; - if (dev_mode) std::cout << "Starting in development mode" << std::endl; - std::cout << "Setting up..." << std::endl; - if (!std::filesystem::exists("files")) { - std::cout << "Creating files..." << std::flush; - std::filesystem::create_directory("files"); - std::cout << " [Done]" << std::endl; - } - if (!std::filesystem::exists("logs")) { - std::cout << "Creating logs..." << std::flush; - std::filesystem::create_directory("logs"); - std::cout << " [Done]" << std::endl; - } - if (std::filesystem::exists("temp")) { - std::cout << "Removing existing temp folder..." << std::flush; - std::filesystem::remove_all("temp"); - std::cout << " [Done]" << std::endl; - } - std::cout << "Creating temp folder..." << std::flush; - std::filesystem::create_directory("temp"); - std::cout << " [Done]" << std::endl; - - - auto* loop = drogon::app().getLoop(); - loop->queueInLoop([]{ - std::cout << "Starting..." << std::endl; - std::cout << "Creating db tables..." << std::flush; - auto db = drogon::app().getDbClient(); - db->execSqlSync("CREATE TABLE IF NOT EXISTS 'tokens' (\n" - " 'id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\n" - " 'owner_id' INTEGER NOT NULL,\n" - " 'exp' INTEGER NOT NULL\n" - ")"); - db->execSqlSync("CREATE TABLE IF NOT EXISTS 'user' (\n" - " 'id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\n" - " 'gitlab' INTEGER NOT NULL,\n" - " 'name' TEXT NOT NULL,\n" - " 'password' TEXT NOT NULL,\n" - " 'role' INTEGER NOT NULL,\n" - " 'root_id' INTEGER NOT NULL,\n" - " 'tfa_type' INTEGER NOT NULL,\n" - " 'tfa_secret' BLOB,\n" - " 'gitlab_at' TEXT,\n" - " 'gitlab_rt' TEXT\n" - ")"); - db->execSqlSync("CREATE TABLE IF NOT EXISTS 'inode' (\n" - " 'id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\n" - " 'is_file' INTEGER NOT NULL,\n" - " 'name' TEXT,\n" - " 'parent_id' INTEGER,\n" - " 'owner_id' INTEGER NOT NULL,\n" - " 'size' INTEGER,\n" - " 'has_preview' INTEGER NOT NULL\n" - ")"); - std::cout << " [Done]" << std::endl; - std::cout << "Started!" << std::endl; - std::cout << "Registered paths: " << std::endl; - auto handlers = drogon::app().getHandlersInfo(); - for (const auto& handler : handlers) { - std::cout << " "; - if (std::get<1>(handler) == drogon::HttpMethod::Post) std::cout << "POST "; - else std::cout << "GET "; - std::string func = std::get<2>(handler).substr(16); - func.resize(30, ' '); - std::cout << '[' << func << "] "; - std::cout << std::get<0>(handler) << std::endl; - } - std::cout << "Listening on:" << std::endl; - auto listeners = drogon::app().getListeners(); - for (const auto& listener : listeners) { - std::cout << " " << listener.toIpPort() << std::endl; - } - }); - - Json::Value access_logger; - access_logger["name"] = "drogon::plugin::AccessLogger"; - - Json::Value smtp_mail; - smtp_mail["name"] = "SMTPMail"; - - Json::Value config; - config["plugins"].append(access_logger); - config["plugins"].append(smtp_mail); - - if (!std::filesystem::exists("config.json")) { - std::cerr << "config.json missing" << std::endl; - return 1; - } - - std::ifstream config_file("config.json"); - config_file >> config["custom_config"]; - - if (!config["custom_config"].isObject()) { - std::cerr << "config.json must be an object" << std::endl; - return 1; - } - if (!config["custom_config"].isMember("gitlab_id")) { - std::cerr << "config.json missing gitlab_id" << std::endl; - return 1; - } - if (!config["custom_config"].isMember("gitlab_secret")) { - std::cerr << "config.json missing gitlab_secret" << std::endl; - return 1; - } - if (!config["custom_config"].isMember("gitlab_url")) { - std::cerr << "config.json missing gitlab_url" << std::endl; - return 1; - } - if (!config["custom_config"].isMember("gitlab_api_url")) { - std::cerr << "config.json missing gitlab_api_url" << std::endl; - return 1; - } - if (!config["custom_config"].isMember("gitlab_redirect_url")) { - std::cerr << "config.json missing gitlab_redirect_url" << std::endl; - return 1; - } - if (!config["custom_config"].isMember("smtp_server")) { - std::cerr << "config.json missing smtp_server" << std::endl; - return 1; - } - if (!config["custom_config"].isMember("smtp_port")) { - std::cerr << "config.json missing smtp_port" << std::endl; - return 1; - } - if (!config["custom_config"].isMember("smtp_user")) { - std::cerr << "config.json missing smtp_user" << std::endl; - return 1; - } - if (!config["custom_config"].isMember("smtp_password")) { - std::cerr << "config.json missing smtp_password" << std::endl; - return 1; - } - - drogon::app() - .setClientMaxBodySize(std::numeric_limits::max()) - - .loadConfigJson(config) - - .createDbClient("sqlite3", "", 0, "", "", "", 1, "sqlite.db") - - .setDefaultHandler(default_handler) - - .setDocumentRoot("./static") - .setBrStatic(true) - .setStaticFilesCacheTime(dev_mode ? -1 : 0) - - .setLogPath("./logs") - .setLogLevel(trantor::Logger::LogLevel::kDebug) - - .setIntSignalHandler(cleanup) - .setTermSignalHandler(cleanup) - - .addListener("0.0.0.0", 2345) - .setThreadNum(8); - std::cout << "Setup done!" << std::endl; - - drogon::app().run(); -} \ No newline at end of file diff --git a/backend/src/main.rs b/backend/src/main.rs new file mode 100644 index 0000000..d069cdc --- /dev/null +++ b/backend/src/main.rs @@ -0,0 +1,34 @@ +mod db; +mod schema; +mod dto; +mod routes; +mod config; + +#[tokio::main] +async fn main() { + console_subscriber::init(); + + pretty_env_logger::formatted_builder().filter_level(log::LevelFilter::Info).init(); + + let _ = config::CONFIG; + + let pool: db::DBPool = db::build_pool(); + + db::run_migrations(&mut pool.get().unwrap()); + + if !std::path::Path::new("files").exists() { + std::fs::create_dir("files").expect("Failed to create files directory"); + } + + let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel(); + + let (_addr, server) = warp::serve(routes::build_routes(pool.clone())).bind_with_graceful_shutdown(([0, 0, 0, 0], 2345), async { + shutdown_rx.await.ok(); + }); + + tokio::task::spawn(server); + + tokio::signal::ctrl_c().await.expect("Failed to wait for ctrl-c"); + println!("Quitting"); + shutdown_tx.send(()).expect("Failed to shutdown server"); +} diff --git a/backend/src/routes/admin.rs b/backend/src/routes/admin.rs new file mode 100644 index 0000000..c6baac5 --- /dev/null +++ b/backend/src/routes/admin.rs @@ -0,0 +1,129 @@ +use warp::{Filter, Reply}; +use crate::db::{DBConnection, DBPool, with_db}; +use crate::dto; +use crate::routes::{AppError, get_reply}; +use crate::routes::filters::{admin, UserInfo}; + +pub fn build_routes(db: DBPool) -> impl Filter + Clone { + let users = warp::path!("admin" / "users") + .and(warp::get()) + .and(admin(db.clone())) + .and(with_db(db.clone())) + .and_then(users); + let set_role = warp::path!("admin" / "set_role") + .and(warp::post()) + .and(warp::body::json()) + .and(admin(db.clone())) + .and(with_db(db.clone())) + .and_then(set_role); + let logout = warp::path!("admin" / "logout") + .and(warp::post()) + .and(warp::body::json()) + .and(admin(db.clone())) + .and(with_db(db.clone())) + .and_then(logout); + let delete_user = warp::path!("admin" / "delete") + .and(warp::post()) + .and(warp::body::json()) + .and(admin(db.clone())) + .and(with_db(db.clone())) + .and_then(delete_user); + let disable_2fa = warp::path!("admin" / "disable_2fa") + .and(warp::post()) + .and(warp::body::json()) + .and(admin(db.clone())) + .and(with_db(db.clone())) + .and_then(disable_2fa); + let is_admin = warp::path!("admin" / "is_admin") + .and(warp::get()) + .and(admin(db.clone())) + .and_then(|_| async { get_reply(&dto::responses::Success { + statusCode: 200 + }) }); + let get_token = warp::path!("admin" / "get_token" / i32) + .and(warp::get()) + .and(admin(db.clone())) + .and(with_db(db)) + .and_then(get_token); + + users.or(set_role).or(logout).or(delete_user).or(disable_2fa).or(is_admin).or(get_token) +} + +async fn users(_: UserInfo, mut db: DBConnection) -> Result { + let users = db.get_users(); + + let mut res = dto::responses::AdminUsers { + statusCode: 200, + users: Vec::new() + }; + + for user in users { + res.users.push(dto::responses::AdminUsersEntry { + id: user.id, + gitlab: user.gitlab, + name: user.name, + role: user.role, + tfaEnabled: user.tfa_type != crate::db::TfaTypes::None + }); + } + + get_reply(&res) +} + +async fn set_role(data: dto::requests::AdminSetRole, _: UserInfo, mut db: DBConnection) -> Result { + let mut user = db.get_user(data.user) + .ok_or(AppError::Forbidden("Invalid user"))?; + user.role = data.role; + db.save_user(&user); + + get_reply(&dto::responses::Success { + statusCode: 200 + }) +} + +async fn logout(data: dto::requests::Admin, _: UserInfo, mut db: DBConnection) -> Result { + db.delete_all_tokens(data.user); + + get_reply(&dto::responses::Success { + statusCode: 200 + }) +} + +async fn delete_user(data: dto::requests::Admin, _: UserInfo, mut db: DBConnection) -> Result { + let user = db.get_user(data.user) + .ok_or(AppError::Forbidden("Invalid user"))?; + + db.delete_all_tokens(data.user); + + let root_node = super::fs::get_node_and_validate(&user, user.root_id, &mut db).expect("Failed to get root node for deleting"); + + super::fs::delete_node_root(&root_node, &mut db); + + db.delete_user(&user); + + get_reply(&dto::responses::Success { + statusCode: 200 + }) +} + +async fn disable_2fa(data: dto::requests::Admin, _: UserInfo, mut db: DBConnection) -> Result { + let mut user = db.get_user(data.user) + .ok_or(AppError::Forbidden("Invalid user"))?; + + user.tfa_type = crate::db::TfaTypes::None; + db.save_user(&user); + + get_reply(&dto::responses::Success { + statusCode: 200 + }) +} + +async fn get_token(user: i32, _: UserInfo, mut db: DBConnection) -> Result { + let user = db.get_user(user) + .ok_or(AppError::Forbidden("Invalid user"))?; + + get_reply(&dto::responses::Login { + statusCode: 200, + jwt: super::auth::get_token(&user, &mut db) + }) +} diff --git a/backend/src/routes/auth/basic.rs b/backend/src/routes/auth/basic.rs new file mode 100644 index 0000000..c04441b --- /dev/null +++ b/backend/src/routes/auth/basic.rs @@ -0,0 +1,115 @@ +use warp::Filter; +use crate::db::{DBConnection, DBPool, with_db}; +use crate::db::{TfaTypes, UserRole}; +use crate::dto; +use crate::dto::requests::ChangePassword; +use crate::routes::{AppError, get_reply}; +use crate::routes::filters::{authenticated, UserInfo}; + +pub fn build_routes(db: DBPool) -> impl Filter + Clone { + let login = warp::path!("auth" / "login") + .and(warp::post()) + .and(warp::body::json()) + .and(with_db(db.clone())) + .and_then(login); + let signup = warp::path!("auth" / "signup") + .and(warp::post()) + .and(warp::body::json()) + .and(with_db(db.clone())) + .and_then(signup); + let refresh = warp::path!("auth" / "refresh") + .and(warp::post()) + .and(authenticated(db.clone())) + .and(with_db(db.clone())) + .and_then(refresh); + let logout_all = warp::path!("auth" / "logout_all") + .and(warp::post()) + .and(authenticated(db.clone())) + .and(with_db(db.clone())) + .and_then(logout_all); + let change_password = warp::path!("auth" / "change_password") + .and(warp::post()) + .and(warp::body::json()) + .and(authenticated(db.clone())) + .and(with_db(db)) + .and_then(change_password); + + login.or(signup).or(refresh).or(logout_all).or(change_password) +} + +async fn login(data: dto::requests::Login, mut db: DBConnection) + -> Result { + let user = db.find_user(&data.username, false) + .ok_or(AppError::Unauthorized("Invalid username or password"))?; + + if !argon2::verify_encoded(user.password.as_str(), data.password.as_bytes()).unwrap_or(false) { + return AppError::Unauthorized("Invalid username or password").err(); + } + + if user.role == UserRole::Disabled { + return AppError::Unauthorized("Account is disabled").err(); + } + + if user.tfa_type != TfaTypes::None { + if let Some(otp) = data.otp { + if !super::tfa::verify2fa(&user, otp) { + return AppError::Unauthorized("Incorrect 2fa").err(); + } + } else { + if user.tfa_type == TfaTypes::Email { super::tfa::send_2fa_mail(&user); } + + return get_reply(&dto::responses::Success { + statusCode: 200 + }); + } + } + + get_reply(&dto::responses::Login { + statusCode: 200, + jwt: super::get_token(&user, &mut db) + }) +} + +async fn signup(data: dto::requests::SignUp, mut db: DBConnection) + -> Result { + if db.find_user(&data.username, false).is_some() { + return AppError::BadRequest("Username is already taken").err(); + } + + db.create_user_password(data.username, super::hash_password(&data.password)); + + get_reply(&dto::responses::Success { + statusCode: 200 + }) +} + +async fn refresh(info: UserInfo, mut db: DBConnection) -> Result { + db.delete_token(info.1.id); + + get_reply(&dto::responses::Login { + statusCode: 200, + jwt: super::get_token(&info.0, &mut db) + }) +} + +async fn logout_all(info: UserInfo, mut db: DBConnection) -> Result { + db.delete_all_tokens(info.0.id); + + get_reply(&dto::responses::Success { + statusCode: 200 + }) +} + +async fn change_password(data: ChangePassword, mut info: UserInfo, mut db: DBConnection) -> Result { + if !argon2::verify_encoded(info.0.password.as_str(), data.oldPassword.as_bytes()).unwrap_or(false) { + return AppError::Unauthorized("Old password is wrong").err(); + } + + info.0.password = super::hash_password(&data.newPassword); + db.save_user(&info.0); + db.delete_all_tokens(info.0.id); + + get_reply(&dto::responses::Success { + statusCode: 200 + }) +} diff --git a/backend/src/routes/auth/gitlab.rs b/backend/src/routes/auth/gitlab.rs new file mode 100644 index 0000000..470eca1 --- /dev/null +++ b/backend/src/routes/auth/gitlab.rs @@ -0,0 +1,108 @@ +use cached::proc_macro::cached; +use lazy_static::lazy_static; +use warp::{Filter, Reply}; +use crate::config::CONFIG; +use crate::db::{DBConnection, DBPool, with_db}; +use crate::routes::AppError; + +#[derive(serde::Deserialize, Clone, Debug)] +pub struct GitlabTokens { + pub access_token: String, + pub refresh_token: String +} + +#[derive(serde::Deserialize, Clone, Debug)] +pub struct GitlabUser { + pub username: String, + pub is_admin: bool +} + +#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] +pub struct GitlabCallbackQuery { + pub code: String +} + +lazy_static! { + static ref REDIRECT_URL: String = CONFIG.gitlab_redirect_url.clone() + "/api/auth/gitlab_callback"; + static ref TOKEN_URL: String = format!("{}/oauth/token", CONFIG.gitlab_api_url.clone()); + static ref USER_URL: String = format!("{}/api/v4/user", CONFIG.gitlab_api_url.clone()); + static ref AUTHORIZE_URL: String = format!("{}/oauth/authorize", CONFIG.gitlab_url.clone()); +} + +pub fn get_gitlab_token(code_or_token: String, token: bool) -> Option { + let mut req = ureq::post(&TOKEN_URL) + .query("redirect_uri", &REDIRECT_URL) + .query("client_id", &CONFIG.gitlab_id) + .query("client_secret", &CONFIG.gitlab_secret); + if token { + req = req + .query("refresh_token", &code_or_token) + .query("grant_type", "refresh_token"); + } else { + req = req + .query("code", &code_or_token) + .query("grant_type", "authorization_code"); + } + req.call().ok()?.into_json().ok() +} + +#[cached(time=300, time_refresh=false, option=true)] +pub fn get_gitlab_user(token: String) -> Option { + ureq::get(&USER_URL) + .set("Authorization", &format!("Bearer {}", token)) + .call() + .ok()? + .into_json().ok() +} + +pub fn build_routes(db: DBPool) -> impl Filter + Clone { + let gitlab = warp::path!("auth" / "gitlab") + .and(warp::get()) + .and_then(gitlab); + let gitlab_callback = warp::path!("auth" / "gitlab_callback") + .and(warp::get()) + .and(warp::query::query::()) + .and(with_db(db)) + .and_then(gitlab_callback); + + gitlab.or(gitlab_callback) +} + +async fn gitlab() -> Result { + let uri = format!("{}?redirect_uri={}&client_id={}&scope=read_user&response_type=code", AUTHORIZE_URL.as_str(), REDIRECT_URL.as_str(), CONFIG.gitlab_id); + Ok(warp::redirect::found(uri.parse::().expect("Failed to parse gitlab auth uri"))) +} + +async fn gitlab_callback(code: GitlabCallbackQuery, mut db: DBConnection) -> Result { + use crate::db::UserRole; + + let tokens = get_gitlab_token(code.code, false).ok_or(AppError::Unauthorized("Invalid code"))?; + let gitlab_user = get_gitlab_user(tokens.access_token.clone()).ok_or(AppError::Unauthorized("Invalid code"))?; + + let user = db.find_user(&gitlab_user.username, true); + + let user = match user { + Some(mut v) => { + v.gitlab_at = Some(tokens.access_token); + v.gitlab_rt = Some(tokens.refresh_token); + db.save_user(&v); + v + }, + None => { + db.create_user_gitlab( + gitlab_user.username, + if gitlab_user.is_admin { UserRole::Admin } else { UserRole::Disabled }, + tokens.access_token, + tokens.refresh_token + ) + } + }; + + if user.role == UserRole::Disabled { + Ok(warp::reply::html("

Your account is disabled, please contact an admin.
Go to login page

").into_response()) + } else { + let uri = format!("/set_token?token={}", super::get_token(&user, &mut db)); + Ok(warp::redirect::found(uri.parse::().expect("Failed to parse set_token uri")).into_response()) + } +} + diff --git a/backend/src/routes/auth/mod.rs b/backend/src/routes/auth/mod.rs new file mode 100644 index 0000000..aeb6446 --- /dev/null +++ b/backend/src/routes/auth/mod.rs @@ -0,0 +1,75 @@ +mod basic; +mod tfa; +pub mod gitlab; + +use std::ops::Add; +use lazy_static::lazy_static; +use ring::rand; +use ring::rand::SecureRandom; +use warp::Filter; +use crate::db::DBPool; + +pub fn build_routes(db: DBPool) -> impl Filter + Clone { + SEC_RANDOM.fill(&mut [0; 1]).expect("Failed to init secure random"); + basic::build_routes(db.clone()) + .or(tfa::build_routes(db.clone())) + .or(gitlab::build_routes(db)) +} + +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct JWTClaims { + pub exp: i64, + pub iat: i64, + pub jti: i32, + pub sub: i32 +} + +pub static JWT_ALGORITHM: jsonwebtoken::Algorithm = jsonwebtoken::Algorithm::HS512; + +lazy_static! { + pub static ref SEC_RANDOM: rand::SystemRandom = rand::SystemRandom::new(); + pub static ref JWT_SECRET: Vec = get_jwt_secret(); + pub static ref JWT_DECODE_KEY: jsonwebtoken::DecodingKey = jsonwebtoken::DecodingKey::from_secret(JWT_SECRET.as_slice()); + pub static ref JWT_ENCODE_KEY: jsonwebtoken::EncodingKey = jsonwebtoken::EncodingKey::from_secret(JWT_SECRET.as_slice()); +} + +fn get_jwt_secret() -> Vec { + let secret = std::fs::read("jwt.secret"); + if let Ok(secret) = secret { + secret + } else { + let mut secret: [u8; 128] = [0; 128]; + SEC_RANDOM.fill(&mut secret).expect("Failed to generate jwt secret"); + std::fs::write("jwt.secret", secret).expect("Failed to write jwt secret"); + Vec::from(secret) + } +} + +pub fn get_token(user: &crate::db::User, db: &mut crate::db::DBConnection) -> String { + let iat = chrono::Utc::now(); + let exp = iat.add(chrono::Duration::hours(24)).timestamp(); + let iat = iat.timestamp(); + + let token = db.create_token(user.id, exp); + + let claims = JWTClaims { + exp, + iat, + jti: token.id, + sub: user.id + }; + + jsonwebtoken::encode(&jsonwebtoken::Header::new(JWT_ALGORITHM), &claims, &JWT_ENCODE_KEY) + .expect("Failed to create JWT token") +} + +pub fn hash_password(password: &String) -> String { + let mut salt = [0_u8; 16]; + SEC_RANDOM.fill(&mut salt).expect("Failed to generate salt"); + let config = argon2::Config { + mem_cost: 64 * 1024, + variant: argon2::Variant::Argon2id, + ..Default::default() + }; + argon2::hash_encoded(password.as_bytes(), &salt, &config).expect("Failed to hash password") +} diff --git a/backend/src/routes/auth/tfa.rs b/backend/src/routes/auth/tfa.rs new file mode 100644 index 0000000..02d9f56 --- /dev/null +++ b/backend/src/routes/auth/tfa.rs @@ -0,0 +1,136 @@ +use lazy_static::lazy_static; +use lettre::Transport; +use ring::rand::SecureRandom; +use warp::Filter; +use crate::config::CONFIG; +use crate::db::{DBConnection, DBPool, with_db, TfaTypes}; +use crate::dto; +use crate::routes::{AppError, get_reply}; +use crate::routes::filters::{authenticated, UserInfo}; + +fn build_mail_sender() -> lettre::SmtpTransport { + lettre::SmtpTransport::builder_dangerous(CONFIG.smtp_server.clone()) + .port(CONFIG.smtp_port) + .tls( + lettre::transport::smtp::client::Tls::Required( + lettre::transport::smtp::client::TlsParameters::new( + CONFIG.smtp_server.clone() + ).unwrap() + ) + ) + .credentials(lettre::transport::smtp::authentication::Credentials::new(CONFIG.smtp_user.clone(), CONFIG.smtp_password.clone())) + .build() +} + +lazy_static! { + static ref MAIL_SENDER: lettre::SmtpTransport = build_mail_sender(); +} + +fn get_totp(user: &crate::db::User) -> totp_rs::TOTP { + totp_rs::TOTP::from_rfc6238( + totp_rs::Rfc6238::new( + 6, + user.tfa_secret.clone().unwrap(), + Some("MFileserver".to_owned()), + user.name.clone() + ).unwrap() + ).unwrap() +} + +pub fn verify2fa(user: &crate::db::User, code: String) -> bool { + let allowed_skew = if user.tfa_type == TfaTypes::Totp {0} else {10}; + let totp = get_totp(user); + let time = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs(); + let base_step = time / totp.step - allowed_skew; + for i in 0..allowed_skew + 1 { + let step = (base_step + i) * totp.step; + if totp.generate(step).eq(&code) { + return true; + } + } + false +} + +pub fn send_2fa_mail(user: &crate::db::User) { + let totp = get_totp(user); + let code = totp.generate_current().unwrap(); + let mail = lettre::Message::builder() + .from("fileserver@mattv.de".parse().unwrap()) + .to(user.name.parse().unwrap()) + .subject("MFileserver - Email 2fa code") + .body(format!("Your code is: {}\r\nIt is valid for 5 minutes", code)) + .unwrap(); + + MAIL_SENDER.send(&mail).expect("Failed to send mail"); +} + +pub fn build_routes(db: DBPool) -> impl Filter + Clone { + let tfa_setup = warp::path!("auth" / "2fa" / "setup") + .and(warp::post()) + .and(warp::body::json()) + .and(authenticated(db.clone())) + .and(with_db(db.clone())) + .and_then(tfa_setup); + let tfa_complete = warp::path!("auth" / "2fa" / "complete") + .and(warp::post()) + .and(warp::body::json()) + .and(authenticated(db.clone())) + .and(with_db(db.clone())) + .and_then(tfa_complete); + let tfa_disable = warp::path!("auth" / "2fa" / "disable") + .and(warp::post()) + .and(authenticated(db.clone())) + .and(with_db(db)) + .and_then(tfa_disable); + + tfa_setup.or(tfa_complete).or(tfa_disable) +} + +async fn tfa_setup(data: dto::requests::TfaSetup, mut info: UserInfo, mut db: DBConnection) + -> Result { + let mut secret: [u8; 32] = [0; 32]; + super::SEC_RANDOM.fill(&mut secret).expect("Failed to generate secret"); + let secret = Vec::from(secret); + info.0.tfa_secret = Some(secret); + db.save_user(&info.0); + + if data.mail { + send_2fa_mail(&info.0); + get_reply(&dto::responses::Success { + statusCode: 200 + }) + } else { + let totp = get_totp(&info.0); + get_reply(&dto::responses::TfaSetup { + statusCode: 200, + secret: totp.get_secret_base32(), + qrCode: "data:image/png;base64,".to_owned() + &totp.get_qr().expect("Failed to generate qr code") + }) + } +} + +async fn tfa_complete(data: dto::requests::TfaComplete, mut info: UserInfo, mut db: DBConnection) + -> Result { + info.0.tfa_type = if data.mail { TfaTypes::Email } else { TfaTypes::Totp }; + + if verify2fa(&info.0, data.code) { + db.save_user(&info.0); + db.delete_all_tokens(info.0.id); + get_reply(&dto::responses::Success { + statusCode: 200 + }) + } else { + AppError::BadRequest("Incorrect 2fa code").err() + } +} + +async fn tfa_disable(mut info: UserInfo, mut db: DBConnection) + -> Result { + info.0.tfa_secret = None; + info.0.tfa_type = TfaTypes::None; + db.save_user(&info.0); + db.delete_all_tokens(info.0.id); + get_reply(&dto::responses::Success { + statusCode: 200 + }) +} diff --git a/backend/src/routes/filters.rs b/backend/src/routes/filters.rs new file mode 100644 index 0000000..ff01a49 --- /dev/null +++ b/backend/src/routes/filters.rs @@ -0,0 +1,90 @@ +use warp::Filter; +use warp::http::{HeaderMap, HeaderValue}; +use crate::db::UserRole; +use crate::db::{DBConnection, DBPool, with_db}; +use crate::routes::AppError; +use crate::routes::auth; + +pub type UserInfo = (crate::db::User, crate::db::Token); + +pub fn authenticated(db: DBPool) -> impl Filter + Clone { + warp::header::headers_cloned() + .map(move |_headers: HeaderMap| _headers) + .and(with_db(db)) + .and_then(authorize) +} + +pub fn admin(db: DBPool) -> impl Filter + Clone { + warp::header::headers_cloned() + .map(move |_headers: HeaderMap| _headers) + .and(with_db(db)) + .and_then(|_headers, db| async { + let info = authorize(_headers, db).await?; + if info.0.role == UserRole::Admin { + Ok(info) + } else { + AppError::Forbidden("Forbidden").err() + } + }) +} + +async fn authorize(_headers: HeaderMap, mut db: DBConnection) -> Result { + authorize_jwt(extract_jwt(&_headers).map_err(|e| e.reject())?, &mut db).await +} + +pub async fn authorize_jwt(jwt: String, db: &mut DBConnection) -> Result { + let decoded = jsonwebtoken::decode::( + &jwt, + &crate::routes::auth::JWT_DECODE_KEY, + &jsonwebtoken::Validation::new(auth::JWT_ALGORITHM) + ).map_err(|_| AppError::Forbidden("Invalid token"))?; + + db.cleanup_tokens(); + + let mut user = db.get_user(decoded.claims.sub) + .ok_or(AppError::Forbidden("Invalid token"))?; + let token = db.get_token(decoded.claims.jti) + .ok_or(AppError::Forbidden("Invalid token"))?; + + if user.id != token.owner_id { + return AppError::Forbidden("Invalid token").err(); + } + if user.role == UserRole::Disabled { + return AppError::Forbidden("Account disabled").err(); + } + if user.gitlab { + let info = auth::gitlab::get_gitlab_user(user.gitlab_at.clone().unwrap()); + let info = match info { + Some(v) => Some(v), + None => { + let tokens = auth::gitlab::get_gitlab_token(user.gitlab_rt.clone().unwrap(), true); + if let Some(tokens) = tokens { + user.gitlab_at = Some(tokens.access_token.clone()); + user.gitlab_rt = Some(tokens.refresh_token); + db.save_user(&user); + auth::gitlab::get_gitlab_user(tokens.access_token) + } else { None } + } + }; + if info.is_none() || info.unwrap().username != user.name { + db.delete_all_tokens(token.owner_id); + db.delete_all_tokens(user.id); + return AppError::Forbidden("Invalid gitlab user").err(); + } + } + + Ok((user, token)) +} + +fn extract_jwt(_headers: &HeaderMap) -> Result { + let header = match _headers.get(warp::http::header::AUTHORIZATION) { + Some(v) => v, + None => return Err(AppError::Unauthorized("Missing token")) + }; + let header = header.to_str().map_err(|_| AppError::Unauthorized("Missing token"))?; + if !header.starts_with("Bearer ") { + Err(AppError::Unauthorized("Missing token")) + } else { + Ok(header.trim_start_matches("Bearer ").to_owned()) + } +} \ No newline at end of file diff --git a/backend/src/routes/fs/mod.rs b/backend/src/routes/fs/mod.rs new file mode 100644 index 0000000..2e622f1 --- /dev/null +++ b/backend/src/routes/fs/mod.rs @@ -0,0 +1,199 @@ +use std::collections::VecDeque; +use std::iter::Iterator; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering}; +use lazy_static::lazy_static; +use warp::Filter; +use futures::TryFutureExt; +use futures::TryStreamExt; +use crate::db::DBPool; + +mod routes; + +pub fn build_routes(db: DBPool) -> impl Filter + Clone { + { + if !std::path::Path::new("temp").is_dir() { + std::fs::create_dir("temp").expect("Failed to create temp dir"); + } + std::fs::read_dir("temp") + .expect("Failed to iter temp dir") + .for_each(|dir| { + std::fs::remove_file(dir.expect("Failed to retrieve temp dir entry").path()).expect("Failed to delete file in temp dir"); + }); + DELETE_RT.spawn(async {}); + ZIP_RT.spawn(async {}); + } + routes::build_routes(db) +} + +pub static WINDOWS_INVALID_CHARS: &str = "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x0C\x0D\x0E\x0F\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F<>:\"/\\|"; + +pub struct ZipProgressEntry { + temp_id: u64, + done: AtomicBool, + progress: AtomicU64, + total: AtomicU64, + delete_after: AtomicI64 +} + +#[derive(Debug)] +pub enum CreateNodeResult { + InvalidName, + InvalidParent, + Exists(bool, i32) +} + +lazy_static! { + static ref DELETE_RT: tokio::runtime::Runtime = tokio::runtime::Builder::new_multi_thread().worker_threads(1).enable_time().build().expect("Failed to create delete runtime"); + static ref ZIP_RT: tokio::runtime::Runtime = tokio::runtime::Builder::new_multi_thread().worker_threads(3).enable_time().build().expect("Failed to create zip runtime"); + pub static ref ZIP_TO_PROGRESS: tokio::sync::RwLock, Arc>> = tokio::sync::RwLock::new(std::collections::HashMap::new()); +} + +static NEXT_TEMP_ID: AtomicU64 = AtomicU64::new(0); + +async fn cleanup_temp_zips() { + let mut existing = ZIP_TO_PROGRESS.write().await; + existing.retain(|_, v| { + if Arc::strong_count(v) == 1 && v.done.load(Ordering::Relaxed) && v.delete_after.load(Ordering::Relaxed) <= chrono::Utc::now().timestamp() { + std::fs::remove_file(std::path::Path::new(&format!("./temp/{}", v.temp_id))).expect("Failed to delete temp file"); + false + } else { + true + } + }); +} + +fn get_nodes_recursive(root: crate::db::Inode, db: &mut crate::db::DBConnection) -> VecDeque { + let mut nodes = VecDeque::from(vec![root.clone()]); + if root.is_file { return nodes; } + let mut nodes_to_check = VecDeque::from(vec![root]); + while !nodes_to_check.is_empty() { + let node = nodes_to_check.pop_front().unwrap(); + db.get_children(node.id).iter().for_each(|node| { + nodes.push_back(node.clone()); + if !node.is_file { nodes_to_check.push_front(node.clone()); } + }); + } + nodes +} + +fn get_node_path(node: crate::db::Inode, db: &mut crate::db::DBConnection) -> VecDeque { + let mut path = VecDeque::from(vec![node.clone()]); + let mut node = node; + while let Some(parent) = node.parent_id { + node = db.get_node(parent).expect("Failed to get node parent"); + path.push_front(node.clone()); + } + path +} + +fn get_total_size(node: crate::db::Inode, db: &mut crate::db::DBConnection) -> u64 { + let nodes = get_nodes_recursive(node, db); + nodes.iter().fold(0_u64, |acc, node| acc + node.size.unwrap_or(0) as u64) +} + +pub fn get_node_and_validate(user: &crate::db::User, node: i32, db: &mut crate::db::DBConnection) -> Option { + let node = db.get_node(node)?; + if node.owner_id != user.id { + None + } else { + Some(node) + } +} + +pub fn create_node(name: String, owner: &crate::db::User, file: bool, parent: Option, force: bool, db: &mut crate::db::DBConnection) + -> Result { + if !force && (name.is_empty() || name.starts_with(' ') || name.contains(|c| { + WINDOWS_INVALID_CHARS.contains(c) + } || name.ends_with(' ') || name.ends_with('.') || name == "." || name == "..")) { + return Err(CreateNodeResult::InvalidName); + } + + if let Some(parent) = parent { + let parent = match get_node_and_validate(owner, parent, db) { + None => { return Err(CreateNodeResult::InvalidParent); } + Some(v) => v + }; + if parent.is_file { return Err(CreateNodeResult::InvalidParent); } + let children = db.get_children(parent.id); + for child in children { + if child.name == name { + return Err(CreateNodeResult::Exists(child.is_file, child.id)); + } + } + } + + Ok(db.create_node(file, name, parent, owner.id)) +} + +pub fn delete_node_root(node: &crate::db::Inode, db: &mut crate::db::DBConnection) { + get_nodes_recursive(node.clone(), db).iter().rev().for_each(|node| { + db.delete_node(node); + }); +} + +pub async fn delete_node(node: &crate::db::Inode, sender: &mut warp::hyper::body::Sender, db: &mut crate::db::DBConnection) { + if node.parent_id.is_none() { return; } + + for node in get_nodes_recursive(node.clone(), db).iter().rev() { + sender.send_data(warp::hyper::body::Bytes::from(format!("Deleting {}...", generate_path(node, db)))).await.unwrap(); + db.delete_node(node); + sender.send_data(warp::hyper::body::Bytes::from(" Done \n")).await.unwrap(); + } +} + +pub fn generate_path(node: &crate::db::Inode, db: &mut crate::db::DBConnection) -> String { + let mut path = String::new(); + + get_node_path(node.clone(), db).iter().for_each(|node| { + if node.parent_id.is_none() { + path += "/"; + } else { + path += &node.name; + if !node.is_file { + path += "/"; + } + } + }); + + path +} + +pub fn generate_path_dto(node: &crate::db::Inode, db: &mut crate::db::DBConnection) -> crate::dto::responses::GetPath { + let mut get_path = crate::dto::responses::GetPath { + segments: Vec::new() + }; + + get_node_path(node.clone(), db).iter().for_each(|node| { + if node.parent_id.is_none() { + get_path.segments.push(crate::dto::responses::GetPathSegment { + path: "/".to_owned(), + node: Some(node.id) + }); + } else { + get_path.segments.push(crate::dto::responses::GetPathSegment { + path: node.name.clone(), + node: Some(node.id) + }); + if !node.is_file { + get_path.segments.push(crate::dto::responses::GetPathSegment { + path: "/".to_owned(), + node: None + }); + } + } + }); + + get_path +} + +pub fn get_file_stream_body(path: String) -> warp::hyper::Body { + warp::hyper::Body::wrap_stream( + tokio::fs::File::open(path) + .map_ok(|file| + tokio_util::codec::FramedRead::new(file, tokio_util::codec::BytesCodec::new()) + .map_ok(bytes::BytesMut::freeze) + ) + .try_flatten_stream() + ) +} diff --git a/backend/src/routes/fs/routes.rs b/backend/src/routes/fs/routes.rs new file mode 100644 index 0000000..f6d8919 --- /dev/null +++ b/backend/src/routes/fs/routes.rs @@ -0,0 +1,444 @@ +use std::collections::{BTreeSet, HashMap}; +use std::io::{Read, Write}; +use std::sync::atomic::Ordering; +use futures::{Stream, StreamExt}; +use headers::HeaderMapExt; +use warp::{Filter, Reply}; +use crate::db::{DBConnection, DBPool, with_db}; +use crate::dto; +use crate::routes::{AppError, get_reply}; +use crate::routes::filters::{authenticated, UserInfo}; + +pub fn build_routes(db: DBPool) -> impl Filter + Clone { + let root = warp::path!("fs" / "root") + .and(warp::get()) + .and(authenticated(db.clone())) + .and_then(root); + let node = warp::path!("fs" / "node" / i32) + .and(warp::get()) + .and(authenticated(db.clone())) + .and(with_db(db.clone())) + .and_then(node) + .with(warp::compression::brotli()); + let path = warp::path!("fs" / "path" / i32) + .and(warp::get()) + .and(authenticated(db.clone())) + .and(with_db(db.clone())) + .and_then(path); + let create_folder = warp::path!("fs" / "create_folder") + .and(warp::post()) + .and(warp::body::json()) + .and(authenticated(db.clone())) + .and(with_db(db.clone())) + .and_then(|data, info, db| create_node(data, info, db, false)); + let create_file = warp::path!("fs" / "create_file") + .and(warp::post()) + .and(warp::body::json()) + .and(authenticated(db.clone())) + .and(with_db(db.clone())) + .and_then(|data, info, db| create_node(data, info, db, true)); + let delete_node = warp::path!("fs" / "delete" / i32) + .and(warp::post()) + .and(authenticated(db.clone())) + .and(with_db(db.clone())) + .and_then(delete_node); + let upload = warp::path!("fs" / "upload" / i32) + .and(warp::post()) + .and(warp::body::stream()) + .and(authenticated(db.clone())) + .and(with_db(db.clone())) + .and_then(upload); + let create_zip = warp::path!("fs" / "create_zip") + .and(warp::post()) + .and(warp::body::json()) + .and(authenticated(db.clone())) + .and(with_db(db.clone())) + .and_then(create_zip); + let download = warp::path!("fs" / "download") + .and(warp::post()) + .and(warp::body::form()) + .and(with_db(db.clone())) + .and_then(download); + let download_multi = warp::path!("fs" / "download_multi") + .and(warp::post()) + .and(warp::body::form()) + .and(with_db(db.clone())) + .and_then(download_multi); + let download_preview = warp::path!("fs" / "download_preview" / i32) + .and(warp::get()) + .and(authenticated(db.clone())) + .and(with_db(db.clone())) + .and_then(download_preview); + let get_type = warp::path!("fs" / "get_type" / i32) + .and(warp::get()) + .and(authenticated(db.clone())) + .and(with_db(db)) + .and_then(get_type); + + root.or(node).or(path).or(create_folder).or(create_file).or(delete_node).or(upload).or(create_zip).or(download).or(download_multi).or(download_preview).or(get_type) +} + +async fn root(info: UserInfo) -> Result { + get_reply(&dto::responses::Root { + statusCode: 200, + rootId: info.0.root_id + }) +} + +async fn node(node: i32, info: UserInfo, mut db: DBConnection) -> Result { + let guard_lock = DBConnection::get_lock(info.0.id).await; + let _guard = guard_lock.read().await; + let node = super::get_node_and_validate(&info.0, node, &mut db) + .ok_or(AppError::BadRequest("Unknown node"))?; + + get_reply(&dto::responses::GetNode { + statusCode: 200, + id: node.id, + name: node.name, + isFile: node.is_file, + preview: node.has_preview, + parent: node.parent_id, + size: node.size, + children: (!node.is_file).then(|| { + db.get_children(node.id).iter().map(|child| dto::responses::GetNodeEntry { + id: child.id, + name: child.name.clone(), + isFile: child.is_file, + preview: child.has_preview, + parent: child.parent_id, + size: child.size + }).collect() + }) + }) +} + +async fn path(node: i32, info: UserInfo, mut db: DBConnection) -> Result { + let guard_lock = DBConnection::get_lock(info.0.id).await; + let _guard = guard_lock.read().await; + let node = super::get_node_and_validate(&info.0, node, &mut db) + .ok_or(AppError::BadRequest("Unknown node"))?; + + get_reply(&super::generate_path_dto(&node, &mut db)) +} + +async fn create_node(data: dto::requests::CreateNode, info: UserInfo, mut db: DBConnection, file: bool) -> Result { + let guard_lock = DBConnection::get_lock(info.0.id).await; + let _guard = guard_lock.read().await; + let node = super::create_node(data.name, &info.0, file, Some(data.parent), false, &mut db); + + match node { + Ok(v) => get_reply(&dto::responses::NewNode { + statusCode: 200, + id: v.id + }), + Err(v) => { + match v { + super::CreateNodeResult::InvalidName => AppError::BadRequest("Invalid name").err(), + super::CreateNodeResult::InvalidParent => AppError::BadRequest("Invalid parent").err(), + super::CreateNodeResult::Exists(file, id) => get_reply(&dto::responses::NodeExists { + statusCode: 200, + id, + exists: true, + isFile: file + }) + } + } + } +} + +async fn delete_node(node: i32, info: UserInfo, mut db: DBConnection) -> Result { + let guard_lock = DBConnection::get_lock(info.0.id).await; + let inner_guard_lock = guard_lock.clone(); + let _guard = guard_lock.read().await; + let node = super::get_node_and_validate(&info.0, node, &mut db) + .ok_or(AppError::BadRequest("Unknown node"))?; + + if node.parent_id.is_none() { + return AppError::BadRequest("Can't delete root").err(); + } + + let (mut sender, body) = warp::hyper::Body::channel(); + + sender.send_data(warp::hyper::body::Bytes::from("Waiting in queue\n")).await.unwrap(); + super::DELETE_RT.spawn(async move { + let guard_lock = inner_guard_lock.clone(); + let _guard = guard_lock.write().await; + super::delete_node(&node, &mut sender, &mut db).await; + }); + + let mut resp = warp::reply::Response::new(body); + *resp.status_mut() = warp::http::StatusCode::OK; + resp.headers_mut().typed_insert( + headers::ContentType::text_utf8() + ); + + Ok(resp) +} + +async fn upload(node: i32, stream: S, info: UserInfo, mut db: DBConnection) -> Result + where + S: Stream>, + S: StreamExt, + B: warp::Buf +{ + let guard_lock = DBConnection::get_lock(info.0.id).await; + let _guard = guard_lock.read().await; + let mut node = super::get_node_and_validate(&info.0, node, &mut db) + .ok_or(AppError::BadRequest("Unknown node"))?; + + if !node.is_file { + return AppError::BadRequest("Can't upload to a directory").err(); + } + + let mut file_size = 0_i64; + let file_name = format!("./files/{}", node.id); + { + let mut file = std::fs::File::create(file_name.clone()).unwrap(); + + stream.for_each(|f| { + let mut buffer = f.unwrap(); + file_size += buffer.remaining() as i64; + while buffer.remaining() != 0 { + let chunk = buffer.chunk(); + buffer.advance(file.write(chunk).expect("Failed to write file")); + } + futures::future::ready(()) + }).await; + } + + let generate_preview = || -> Option<()> { + if file_size > 20 * 1024 * 1024 { return None; } + let mime = mime_guess::from_path(std::path::Path::new(&node.name)).first()?.to_string(); + let img = image::load( + std::io::BufReader::new(std::fs::File::open(file_name.clone()).unwrap()), + image::ImageFormat::from_mime_type(mime)? + ).ok()?; + let img = img.resize(300, 300, image::imageops::FilterType::Triangle); + img.save(std::path::Path::new(&(file_name + "_preview.jpg"))).expect("Failed to save preview image"); + Some(()) + }; + + node.has_preview = generate_preview().is_some(); + node.size = Some(file_size); + db.save_node(&node); + + get_reply(&dto::responses::Success { + statusCode: 200 + }) +} + +async fn create_zip(data: dto::requests::CreateZip, info: UserInfo, mut db: DBConnection) -> Result { + let guard_lock = DBConnection::get_lock(info.0.id).await; + let inner_guard_lock = guard_lock.clone(); + let _guard = guard_lock.read().await; + let mut nodes: Vec = Vec::new(); + for node in data.nodes.clone() { + nodes.push( + super::get_node_and_validate(&info.0, node, &mut db) + .ok_or(AppError::BadRequest("Unknown node"))? + ); + } + let zip_nodes = BTreeSet::from_iter(data.nodes.iter().copied()); + + { + let guard = super::ZIP_TO_PROGRESS.read().await; + if let Some(entry) = guard.get(&zip_nodes) { + return get_reply(&dto::responses::CreateZipDone { + statusCode: 200, + done: entry.done.load(Ordering::Relaxed), + progress: Some(entry.progress.load(Ordering::Relaxed)), + total: Some(entry.total.load(Ordering::Relaxed)) + }) + } + } + let entry = { + let mut guard = super::ZIP_TO_PROGRESS.write().await; + guard.insert(zip_nodes.clone(), std::sync::Arc::from(super::ZipProgressEntry { + temp_id: super::NEXT_TEMP_ID.fetch_add(1, Ordering::Relaxed), + done: std::sync::atomic::AtomicBool::new(false), + progress: std::sync::atomic::AtomicU64::new(0), + total: std::sync::atomic::AtomicU64::new(1), + delete_after: std::sync::atomic::AtomicI64::new(0) + })); + guard.get(&zip_nodes).unwrap().clone() + }; + super::ZIP_RT.spawn(async move { + type NodeMap = HashMap; + + super::cleanup_temp_zips().await; + + let _guard = inner_guard_lock.read().await; + + fn get_path(node: &crate::db::Inode, dirs: &NodeMap) -> String { + let mut path = node.name.clone(); + let mut _node = dirs.get(&node.parent_id.unwrap_or(-1)); + while let Some(node) = _node { + path.insert_str(0, &(node.name.clone() + "/")); + _node = dirs.get(&node.parent_id.unwrap_or(-1)); + } + path + } + + nodes.iter().for_each(|node| { + entry.total.fetch_add(super::get_total_size(node.clone(), &mut db), Ordering::Relaxed); + }); + entry.total.fetch_sub(1, Ordering::Relaxed); + { + let mut buf = vec![0_u8; 1024 * 1024 * 4]; + let file = std::fs::File::create(format!("./temp/{}", entry.temp_id)).expect("Failed to create temp file"); + let mut zip = zip::ZipWriter::new(file); + let zip_options = zip::write::FileOptions::default().large_file(true); + let (files, dirs): (NodeMap, NodeMap) = + nodes.iter() + .flat_map(|node| super::get_nodes_recursive(node.clone(), &mut db)) + .map(|node| (node.id, node)) + .partition(|v| v.1.is_file); + + dirs.values().for_each(|dir| { + zip.add_directory(get_path(dir, &dirs), zip_options).expect("Failed to add dir to zip"); + }); + files.values().for_each(|node| { + zip.start_file(get_path(node, &dirs), zip_options).expect("Failed to start zip file"); + let mut file = std::fs::File::open(format!("./files/{}", node.id)).expect("Failed to open file for zip"); + loop { + let count = file.read(&mut buf).expect("Failed to read file for zip"); + if count == 0 { break; } + zip.write_all(&buf[..count]).expect("Failed to write zip"); + entry.progress.fetch_add(count as u64, Ordering::Relaxed); + } + }); + + zip.finish().expect("Failed to finish zip"); + } + entry.done.store(true, Ordering::Relaxed); + entry.delete_after.store(chrono::Utc::now().timestamp() + 10 * 60, Ordering::Relaxed); + }); + get_reply(&dto::responses::CreateZipDone { + statusCode: 200, + done: false, + progress: Some(0), + total: Some(1) + }) +} + +async fn download(data: dto::requests::Download, mut db: DBConnection) -> Result { + let info = crate::routes::filters::authorize_jwt(data.jwtToken, &mut db).await?; + let guard_lock = DBConnection::get_lock(info.0.id).await; + let _guard = guard_lock.read().await; + + let node: crate::db::Inode = super::get_node_and_validate(&info.0, data.id, &mut db) + .ok_or(AppError::BadRequest("Unknown node"))?; + + if node.is_file { + let mut resp = warp::reply::Response::new(super::get_file_stream_body( + format!("./files/{}", node.id) + )); + *resp.status_mut() = warp::http::StatusCode::OK; + resp.headers_mut().typed_insert( + headers::ContentLength(node.size.unwrap() as u64) + ); + resp.headers_mut().typed_insert( + headers::ContentType::from( + mime_guess::from_path(std::path::Path::new(&node.name)).first_or_octet_stream() + ) + ); + resp.headers_mut().insert( + "Content-Disposition", + ("attachment; filename=".to_owned() + &node.name).parse().unwrap() + ); + Ok(resp) + } else { + let nodes_key = BTreeSet::from([node.id]); + let guard = super::ZIP_TO_PROGRESS.read().await; + let entry = guard.get(&nodes_key) + .ok_or(AppError::BadRequest("Unknown node"))?; + if !entry.done.load(Ordering::Relaxed) { + AppError::BadRequest("Unknown node").err() + } else { + let file = format!("./temp/{}", entry.temp_id); + let mut resp = warp::reply::Response::new(super::get_file_stream_body(file.clone())); + *resp.status_mut() = warp::http::StatusCode::OK; + resp.headers_mut().typed_insert( + headers::ContentLength(std::fs::metadata(std::path::Path::new(&file)).unwrap().len()) + ); + resp.headers_mut().typed_insert( + headers::ContentType::from( + mime_guess::from_ext("zip").first().unwrap() + ) + ); + resp.headers_mut().insert( + "Content-Disposition", + ("attachment; filename=".to_owned() + &node.name + ".zip").parse().unwrap() + ); + Ok(resp) + } + } +} + +async fn download_multi(data: dto::requests::DownloadMulti, mut db: DBConnection) -> Result { + let info = crate::routes::filters::authorize_jwt(data.jwtToken, &mut db).await?; + let guard_lock = DBConnection::get_lock(info.0.id).await; + let _guard = guard_lock.read().await; + + let mut nodes: Vec = Vec::new(); + for node in data.id.split(',').map(|v| v.parse::() + .map_err(|_| AppError::BadRequest("Failed to parse").reject()) + ) { + nodes.push( + super::get_node_and_validate(&info.0, node?, &mut db) + .ok_or(AppError::BadRequest("Unknown node"))? + ); + } + + let nodes_key = BTreeSet::from_iter(nodes.iter().map(|node| node.id)); + let guard = super::ZIP_TO_PROGRESS.read().await; + let entry = guard.get(&nodes_key) + .ok_or(AppError::BadRequest("Unknown zip"))?; + if !entry.done.load(Ordering::Relaxed) { + AppError::BadRequest("Unfinished zip").err() + } else { + let file = format!("./temp/{}", entry.temp_id); + let mut resp = warp::reply::Response::new(super::get_file_stream_body(file.clone())); + *resp.status_mut() = warp::http::StatusCode::OK; + resp.headers_mut().typed_insert( + headers::ContentLength(std::fs::metadata(std::path::Path::new(&file)).unwrap().len()) + ); + resp.headers_mut().typed_insert( + headers::ContentType::from( + mime_guess::from_ext("zip").first().unwrap() + ) + ); + resp.headers_mut().insert( + "Content-Disposition", + "attachment; filename=files.zip".parse().unwrap() + ); + Ok(resp) + } +} + +async fn download_preview(node: i32, info: UserInfo, mut db: DBConnection) -> Result { + let guard_lock = DBConnection::get_lock(info.0.id).await; + let _guard = guard_lock.read().await; + let node: crate::db::Inode = super::get_node_and_validate(&info.0, node, &mut db) + .ok_or(AppError::BadRequest("Unknown node"))?; + + if node.has_preview { + let file = format!("./files/{}_preview.jpg", node.id); + get_reply(&dto::responses::DownloadBase64 { + statusCode: 200, + data: "data:image/png;base64,".to_owned() + &base64::encode(std::fs::read(std::path::Path::new(&file)).unwrap()) + }) + } else { + AppError::BadRequest("No preview").err() + } +} + +async fn get_type(node: i32, info: UserInfo, mut db: DBConnection) -> Result { + let node: crate::db::Inode = super::get_node_and_validate(&info.0, node, &mut db) + .ok_or(AppError::BadRequest("Unknown node"))?; + + get_reply(&dto::responses::Type { + statusCode: 200, + _type: mime_guess::from_path(std::path::Path::new(&node.name)).first_or_octet_stream().to_string() + }) +} diff --git a/backend/src/routes/mod.rs b/backend/src/routes/mod.rs new file mode 100644 index 0000000..a9da831 --- /dev/null +++ b/backend/src/routes/mod.rs @@ -0,0 +1,114 @@ +mod filters; +mod auth; +mod admin; +mod user; +pub mod fs; + +use warp::{Filter, Reply}; +use crate::db::DBPool; +use crate::dto; + +pub fn build_routes(db: DBPool) -> impl Filter + Clone { + warp::path::path("api") + .and( + auth::build_routes(db.clone()) + .or(admin::build_routes(db.clone())) + .or(user::build_routes(db.clone())) + .or(fs::build_routes(db)) + .recover(error_handler) + ) + .or(warp::fs::dir("./static/")) + .or(warp::fs::file("./static/index.html")) +} + +pub fn get_reply(data: &T) -> Result where T: serde::Serialize { + Ok(warp::reply::with_status(warp::reply::json(data), warp::http::StatusCode::OK).into_response()) +} + +#[derive(thiserror::Error, Debug, Clone)] +pub enum AppError { + #[error("unauthorized")] + Unauthorized(&'static str), + #[error("forbidden")] + Forbidden(&'static str), + #[error("bad request")] + BadRequest(&'static str), + #[error("internal error")] + InternalError(&'static str) +} +impl warp::reject::Reject for AppError {} + +impl AppError { + pub fn reject(&self) -> warp::reject::Rejection { + warp::reject::custom(self.clone()) + } + + pub fn err(&self) -> Result { + Err(self.reject()) + } +} + +pub async fn error_handler(err: warp::reject::Rejection) -> Result { + if err.is_not_found() { + return Ok(warp::reply::with_status( + warp::reply::json(&dto::responses::Error { + statusCode: 404, + message: "bruh".to_owned() + }), + warp::http::StatusCode::NOT_FOUND + )); + } + if let Some(e) = err.find::() { + return Ok(warp::reply::with_status( + warp::reply::json(&dto::responses::Error { + statusCode: match e { + AppError::BadRequest(_) => 400, + AppError::Unauthorized(_) => 401, + AppError::Forbidden(_) => 403, + AppError::InternalError(_) => 500 + }, + message: match e { + AppError::BadRequest(v) => v.to_string(), + AppError::Unauthorized(v) => v.to_string(), + AppError::Forbidden(v) => v.to_string(), + AppError::InternalError(v) => v.to_string() + }, + }), + match e { + AppError::BadRequest(_) => warp::http::StatusCode::BAD_REQUEST, + AppError::Unauthorized(_) => warp::http::StatusCode::UNAUTHORIZED, + AppError::Forbidden(_) => warp::http::StatusCode::FORBIDDEN, + AppError::InternalError(_) => warp::http::StatusCode::INTERNAL_SERVER_ERROR + } + )); + } + if let Some(e) = err.find::() { + return Ok(warp::reply::with_status( + warp::reply::json(&dto::responses::Error { + statusCode: 400, + message: e.to_string(), + }), + warp::http::StatusCode::BAD_REQUEST + )) + } + if let Some(e) = err.find::() { + return Ok(warp::reply::with_status( + warp::reply::json(&dto::responses::Error { + statusCode: 400, + message: e.to_string(), + }), + warp::http::StatusCode::BAD_REQUEST + )) + } + if let Some(e) = err.find::() { + return Ok(warp::reply::with_status( + warp::reply::json(&dto::responses::Error { + statusCode: 405, + message: e.to_string(), + }), + warp::http::StatusCode::METHOD_NOT_ALLOWED + )) + } + + Err(err).expect("Can't handle error") +} \ No newline at end of file diff --git a/backend/src/routes/user.rs b/backend/src/routes/user.rs new file mode 100644 index 0000000..9cb2fdf --- /dev/null +++ b/backend/src/routes/user.rs @@ -0,0 +1,42 @@ +use warp::{Filter, Reply}; +use crate::db::{DBConnection, DBPool, with_db}; +use crate::dto; +use crate::routes::get_reply; +use crate::routes::filters::{authenticated, UserInfo}; + +pub fn build_routes(db: DBPool) -> impl Filter + Clone { + let info = warp::path!("user" / "info") + .and(warp::get()) + .and(authenticated(db.clone())) + .and_then(info); + let delete_user = warp::path!("user" / "delete") + .and(warp::post()) + .and(authenticated(db.clone())) + .and(with_db(db)) + .and_then(delete_user); + + info.or(delete_user) +} + +async fn info(info: UserInfo) -> Result { + get_reply(&dto::responses::UserInfo { + statusCode: info.0.id, + name: info.0.name, + gitlab: info.0.gitlab, + tfaEnabled: info.0.tfa_type != crate::db::TfaTypes::None + }) +} + +async fn delete_user(info: UserInfo, mut db: DBConnection) -> Result { + db.delete_all_tokens(info.0.id); + + let root_node = super::fs::get_node_and_validate(&info.0, info.0.root_id, &mut db).expect("Failed to get root node for deleting"); + + super::fs::delete_node_root(&root_node, &mut db); + + db.delete_user(&info.0); + + get_reply(&dto::responses::Success { + statusCode: 200 + }) +} \ No newline at end of file diff --git a/backend/src/schema.rs b/backend/src/schema.rs new file mode 100644 index 0000000..563fb80 --- /dev/null +++ b/backend/src/schema.rs @@ -0,0 +1,42 @@ +// @generated automatically by Diesel CLI. + +diesel::table! { + inode (id) { + id -> Integer, + is_file -> Bool, + name -> Text, + parent_id -> Nullable, + owner_id -> Integer, + size -> Nullable, + has_preview -> Bool, + } +} + +diesel::table! { + tokens (id) { + id -> Integer, + owner_id -> Integer, + exp -> BigInt, + } +} + +diesel::table! { + user (id) { + id -> Integer, + gitlab -> Bool, + name -> Text, + password -> Text, + role -> SmallInt, + root_id -> Integer, + tfa_type -> SmallInt, + tfa_secret -> Nullable, + gitlab_at -> Nullable, + gitlab_rt -> Nullable, + } +} + +diesel::allow_tables_to_appear_in_same_query!( + inode, + tokens, + user, +); diff --git a/backend/vcpkg.json b/backend/vcpkg.json deleted file mode 100644 index d7e7dae..0000000 --- a/backend/vcpkg.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json", - "name": "backend", - "version-string": "1.0.0", - "dependencies": [ - { - "name": "drogon", - "features": ["orm", "sqlite3"] - }, - { - "name": "opencv4", - "default-features": false, - "features": ["tiff", "png", "jpeg", "webp", "openexr"] - }, - "jwt-cpp", - "botan", - "nayuki-qr-code-generator", - "openssl", - "kubazip" - ] -} \ No newline at end of file diff --git a/frontend/src/api/fs.ts b/frontend/src/api/fs.ts index 83f6b82..b06b4cc 100644 --- a/frontend/src/api/fs.ts +++ b/frontend/src/api/fs.ts @@ -5,6 +5,7 @@ import { post_token_form, isErrorResponse } from './base'; +import axios from 'axios'; export const get_root = ( token: string @@ -31,7 +32,7 @@ export const create_folder = ( Responses.CreateFolder | Responses.CreateFolderExists | Responses.Error > => post_token( - '/api/fs/createFolder', + '/api/fs/create_folder', { parent: parent, name: name @@ -47,7 +48,7 @@ export const create_file = ( Responses.CreateFolder | Responses.CreateFolderExists | Responses.Error > => post_token( - '/api/fs/createFile', + '/api/fs/create_file', { parent: parent, name: name @@ -89,14 +90,22 @@ export async function upload_file( if ('exists' in node && !node.isFile) return { statusCode: 400, message: 'File exists as folder' }; - const form = new FormData(); - form.set('file', file.file); - return post_token_form( - `/api/fs/upload/${node.id}`, - form, - token, - onProgress - ); + return axios + .post(`/api/fs/upload/${node.id}`, file.file, { + headers: { + Authorization: 'Bearer ' + token, + 'Content-type': 'multipart/form-data' + }, + onUploadProgress: onProgress + }) + .then((res) => { + console.log(res); + return res.data; + }) + .catch((err) => { + console.log(err); + return err.response.data; + }); } export function download_file(token: string, id: number) { diff --git a/frontend/src/api/util.ts b/frontend/src/api/util.ts index f798baa..cd42f0d 100644 --- a/frontend/src/api/util.ts +++ b/frontend/src/api/util.ts @@ -18,12 +18,19 @@ export async function check_token( token: TokenInjectType ): Promise { if (!token.jwt.value) return token.logout(); - const payload = jwtDecode(token.jwt.value); - if (!payload) return token.logout(); - // Expires in more than 60 Minute - if (payload.exp && payload.exp > Math.floor(Date.now() / 1000 + 60 * 60)) - return token.jwt.value; - return update_token(token); + try { + const payload = jwtDecode(token.jwt.value); + if (!payload) return token.logout(); + // Expires in more than 60 Minute + if ( + payload.exp && + payload.exp > Math.floor(Date.now() / 1000 + 60 * 60) + ) + return token.jwt.value; + return update_token(token); + } catch { + return token.logout(); + } } export type TokenInjectType = { diff --git a/tokio-top.cmd b/tokio-top.cmd new file mode 100644 index 0000000..0cfea6a --- /dev/null +++ b/tokio-top.cmd @@ -0,0 +1 @@ +tokio-console http://127.0.0.1:9999/ --colorterm 24bit --retain-for "2s" \ No newline at end of file