diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..2501371 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +/build +/config.json +/users.json +/help.md +/services.md +/APIs.md +/README.md +/benchmarking.cpp \ No newline at end of file diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml new file mode 100644 index 0000000..997bdbc --- /dev/null +++ b/.github/workflows/deploy.yaml @@ -0,0 +1,31 @@ +name: Publish Staging +on: + push: + branches: + - main +jobs: + release: + name: Push Docker image to GitHub Packages + runs-on: ubuntu-latest + permissions: + packages: write + contents: read + steps: + - name: Checkout the repo + uses: actions/checkout@v2 + - name: Login to GitHub Docker Registry + uses: docker/login-action@v1 + with: + registry: docker.pkg.github.com + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Format repository + run: | + echo IMAGE_REPOSITORY=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]') >> $GITHUB_ENV + - name: Build container image + uses: docker/build-push-action@v2 + with: + push: true + tags: | + docker.pkg.github.com/${{ env.IMAGE_REPOSITORY }}/ccash:${{ github.sha }} + docker.pkg.github.com/${{ env.IMAGE_REPOSITORY }}/ccash:latest diff --git a/.gitignore b/.gitignore index 9d01538..928d28c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ .vscode build config.json -users.json \ No newline at end of file +users.json diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c14cd74 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +FROM debian:latest + +WORKDIR /ccash + +RUN apt update && apt -y install build-essential g++ cmake protobuf-compiler libjsoncpp-dev uuid-dev openssl libssl-dev zlib1g-dev + +COPY . . + +RUN mkdir build + +WORKDIR /ccash/build + +RUN cmake .. +RUN make -j$(nprov) + +CMD ["/ccash/build/bank", "$CCASH_ADMIN_PASSWORD", "$CCASH_SAVE_FREQUENCY", "$CCASH_THREAD_COUNT"] diff --git a/README.md b/README.md index 8eecbd6..7359770 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,11 @@ CCash solves these issues and adds a level of abstraction, the main philosophy o drogon depedencies (varies by OS/distro) ``` +# Debian sudo apt install libjsoncpp-dev uuid-dev openssl libssl-dev zlib1g-dev + +# macOS +brew install jsoncpp ossp-uuid openssl zlib ``` building the project @@ -44,7 +48,12 @@ sudo ./bank ## Connected Services -Go to [here](docs/help.md) to see the API's endpoints. Using the Bank's API allows (you/others) to (make/use) connected services that utilize the bank, a couple ideas can be found [here](docs/services.md) +Using the Bank's API allows (you/others) to (make/use) connected services that utilize the bank, a couple ideas can be found [here](docs/services.md) + +## Developing for +as a dev check out +* [APIs](https://github.com/EntireTwix/CCash/blob/main/docs/APIs.md) +* [endpoints](https://github.com/EntireTwix/CCash/blob/main/docs/help.md) ## FAQ **Q:** how is money initially injected into the economy @@ -54,12 +63,13 @@ Go to [here](docs/help.md) to see the API's endpoints. Using the Bank's API allo ## [Contributions](https://github.com/EntireTwix/CCash/graphs/contributors) Thank you to the contributors -| Name | Work | -| :------------------------------------------ | ----------------------------------------------------------------- | -| [Expand](https://github.com/Expand-sys) | Frontend | -| [React](https://github.com/Reactified) | CC {API, Shops, and ATM, Logo} | -| [Doggo](https://github.com/FearlessDoggo21) | Logs loading/adding Optimized, Python API, convention suggestions | - +| Name | Project Work | Connected Service Work | +| :------------------------------------------ | ----------------------------------------------------------------------- | ---------------------- | +| [Expand](https://github.com/Expand-sys) | Slight docker changes | Frontend | +| [React](https://github.com/Reactified) | CC API, Logo | CC Shop, CC ATM. | +| [Doggo](https://github.com/FearlessDoggo21) | Logs loading/adding Optimized, HTTP convention suggestions, Python API | `N/A` | +| [Luke](https://github.com/LukeeeeBennett) | JS API, Docker, Slight Doc edits | `N/A` | +| [Jolly](https://github.com/STBoyden) | Slight Doc edits | `N/A` | ## Features @@ -70,12 +80,18 @@ Thank you to the contributors - **multi-threaded** - **parallel hashmaps** a far [superior](https://greg7mdp.github.io/parallel-hashmap/) HashMap implementation to the STD, that also benefits from multi-threaded - **Drogon** is a very fast [web framework](https://www.techempower.com/benchmarks/#section=data-r20&hw=ph&test=composite) -- **Lightweight**, anecodotally I experienced 0.0% idle, <1% CPU usage on average, 7% at peak, 1000 requests in 0.85s - +- **xxHash** for the hashing of passwords, it is very fast: [graph](https://user-images.githubusercontent.com/750081/61976089-aedeab00-af9f-11e9-9239-e5375d6c080f.png) +- **Lightweight**, anecodotally I experienced (on my laptop's i7 6700K, 8 threads): + - memory usage of 8.5 MB (with 0 users) + - 0.0% CPU usage idle + - <1% CPU on average + - 1000 requests in parallel completed in 0.85s which spiked CPU usage to 7% + ### Safety - **Tamper Proof** relative to an in-game implementation - **Auto-Saving** and Saves on close +- All passwords are **Hashed** - **HTTPS** (OpenSSL) ### Accessibility @@ -84,6 +100,9 @@ Thank you to the contributors - able to be used millions of blocks away, across dimensions, servers, **vanilla or modded**. - **Logging** of all transactions, configurable in [consts.hpp](include/consts.hpp) +### Other +- **return balance on deletion**, configurable in [consts.hpp](include/consts.hpp) + ## Dependencies - [Parallel HashMap](https://github.com/greg7mdp/parallel-hashmap/tree/master) diff --git a/benchmarking.cpp b/benchmarking.cpp new file mode 100644 index 0000000..ae49f4c --- /dev/null +++ b/benchmarking.cpp @@ -0,0 +1,87 @@ +#include +#include +#include +#include +#include +#include "bank_f.h" + +#include +#include +#include +#include + +using namespace std::chrono; +using namespace drogon; + +static Bank bank; + +#include +#include +#include + +#define time_func_a(f, a, x) \ + { \ + using namespace std::chrono; \ + uint32_t timer = 0; \ + for (int i = 0; i < x; ++i) \ + { \ + auto t1 = high_resolution_clock::now().time_since_epoch(); \ + f; \ + auto t2 = high_resolution_clock::now().time_since_epoch(); \ + a; \ + timer += std::chrono::duration_cast((t2 - t1)).count(); \ + } \ + std::cout << timer / x << '\n'; \ + } + +#define time_func(f, x) \ + { \ + using namespace std::chrono; \ + uint32_t timer = 0; \ + for (int i = 0; i < x; ++i) \ + { \ + auto t1 = high_resolution_clock::now().time_since_epoch(); \ + f; \ + auto t2 = high_resolution_clock::now().time_since_epoch(); \ + timer += std::chrono::duration_cast((t2 - t1)).count(); \ + } \ + std::cout << timer / x << '\n'; \ + } + +#define Op_a(v, name, x, a) \ + { \ + std::cout << name; \ + time_func_a(v, a, x); \ + } + +#define Op(v, name, x) \ + { \ + std::cout << name; \ + time_func(v, x); \ + } + +int main(int argc, char **argv) +{ + bank.AddUser("twix", "root"); + bank.AddUser("jolly", "root"); + bank.admin_pass = "root"; + Op_a(bank.AddUser("", ""), "add user: ", 1000000, bank.DelUser("", "")); + Op_a(bank.AdminAddUser("root", "", 0, ""), "admin add user: ", 1000000, bank.DelUser("", "")); + Op(bank.SetBal("twix", "root", 1000000), "set bal: ", 1000000); + Op(bank.SendFunds("twix", "jolly", 1, "root"), "send funds: ", 1000000); + + bank.AddUser("", ""); + Op_a(bank.DelUser("", ""), "del user: ", 1000000, bank.AddUser("", "")); + Op_a(bank.AdminDelUser("", "root"), "admin del user: ", 1000000, bank.AddUser("", "")); + bank.DelUser("", ""); + + Op(bank.Contains("twix"), "contains: ", 1000000); + Op(bank.AdminVerifyPass("root"), "admin verify pass: ", 1000000); + Op(bank.GetBal("twix"), "get bal: ", 1000000); + Op(bank.VerifyPassword("twix", "root"), "verify pass: ", 1000000); + Op(bank.ChangePassword("twix", "root", "root"), "change pass: ", 1000000); + Op(bank.GetLogs("twix", "root"), "get logs: ", 10000); + Op(bank.Save(), "saving: ", 1); + + return 0; +} diff --git a/docs/APIs.md b/docs/APIs.md index 9ae0aff..360bc3b 100644 --- a/docs/APIs.md +++ b/docs/APIs.md @@ -1,2 +1,11 @@ -[CC API](https://github.com/Reactified/rpm/blob/main/packages/ccash-api/api.lua) -[Python API](https://github.com/fearlessdoggo21/ccashpythonclient) +# Language Specific APIs + +## Complete +* [JS API](https://github.com/LukeeeeBennett/ccash-client-js) +* [ComputerCraft (Lua) API](https://github.com/Reactified/rpm/blob/main/packages/ccash-api/api.lua) +* [Python API](https://github.com/fearlessdoggo21/ccashpythonclient) + +## In Dev +* [C API]() +* [CS API](https://github.com/Soverclysm/CCash-dotnet-api) +* [Rust API](https://git.stboyden.com/STBoyden/ccash-rs) diff --git a/docs/help.md b/docs/help.md index 63d323c..ec7d690 100644 --- a/docs/help.md +++ b/docs/help.md @@ -1,44 +1,45 @@ # Error Responses -| # | meaning | -| --- | ------------------ | -| -1 | UserNotFound | -| -2 | WrongPassword | -| -3 | InvalidRequest | -| -5 | NameTooLong | -| -6 | UserAlreadyExists | -| -7 | InsufficientFunds | +| # | meaning | +| --- | ----------------- | +| -1 | UserNotFound | +| -2 | WrongPassword | +| -3 | InvalidRequest | +| -4 | NameTooLong | +| -5 | UserAlreadyExists | +| -6 | InsufficientFunds | # Things of Note * all endpoints respond with **JSON** file type * "**A**" denotes requiring Authentication in the form of a header titled "**Password**" # Usage -| Name | Path | Method | A | Description | -| :------------: | :-------------------------------- | :----: | :---: | --------------------------------------------------------------------------------------------------------------------------- | -| GetBal | /{name}/bal | GET | false | returns the balance of a given user `{name}` | -| GetLog | /{name}/log | GET | true | returns a list of last `n` number of transactions (a configurable amount) of a given user `{name}` | -| SendFunds | /{name}/send/{to}/amount={amount} | POST | true | sends `{amount}` from user `{name}` to user `{to}` | -| VerifyPassword | /{name}/pass/verify | GET | true | returns `true` or `false` depending on if the supplied user `{name}`'s password matches the password supplied in the header | +| Name | Path | Method | A | Description | +| :------------: | :------------------------------------- | :----: | :---: | ------------------------------------------------------------------------------------------------------------------------------- | +| GetBal | BankF/{name}/bal | GET | false | returns the balance of a given user `{name}` | +| GetLog | BankF/{name}/log | GET | true | returns a list of last `n` number of transactions (a configurable amount when the program is compiled) of a given user `{name}` | +| SendFunds | BankF/{name}/send/{to}?amount={amount} | POST | true | sends `{amount}` from user `{name}` to user `{to}` | +| VerifyPassword | BankF/{name}/pass/verify | GET | true | returns `1` if the supplied user `{name}`'s password matches the password supplied in the header | # Meta Usage -| Name | Path | Method | A | Description | -| :------------: | :-------------------------------- | :----: | :---: | ---------------------------------------------------------------------------------------------------------------------------------------- | -| ChangePassword | /{name}/pass/change | PATCH | true | if the password supplied in the header matches the user `{name}`'s password, the user's password is changed to the one given in the body | -| SetBal | /admin/{name}/bal/amount={amount} | PATCH | true | sets the balance of a give user `{name}` if the supplied password matches the admin password | +| Name | Path | Method | A | Description | +| :------------: | :------------------------------------- | :----: | :---: | ---------------------------------------------------------------------------------------------------------------------------------------- | +| ChangePassword | BankF/{name}/pass/change | PATCH | true | if the password supplied in the header matches the user `{name}`'s password, the user's password is changed to the one given in the body | +| SetBal | BankF/admin/{name}/bal?amount={amount} | PATCH | true | sets the balance of a give user `{name}` if the supplied password matches the admin password | # System Usage -| Name | Path | Method | A | Description | -| :-------------: | :--------------- | :----: | :---: | -------------------------------------------------------------------------------------------------------- | -| Help | /help | GET | false | the page you're looking at right now! | -| Close | /admin/close | POST | true | saves and then closes the program if the supplied password matches the admin password | -| Contains | /contains/{name} | GET | false | returns `true` or `false` depending on if the supplied user `{name}` exists | -| AdminVerifyPass | /admin/verify | GET | true | returns `true` or `false` depending on if the password supplied in the header matches the admin password | +| Name | Path | Method | A | Description | +| :-------------: | :-------------------- | :----: | :---: | ------------------------------------------------------------------------------------- | +| Help | BankF/help | GET | false | the page you're looking at right now! | +| Ping | BankF/ping | GET | false | for pinging the server to see if its online | +| Close | BankF/admin/close | POST | true | saves and then closes the program if the supplied password matches the admin password | +| Contains | BankF/contains/{name} | GET | false | returns `1` if the supplied user `{name}` exists | +| AdminVerifyPass | BankF/admin/verify | GET | true | returns `1` if the password supplied in the header matches the admin password | # User Management -| Name | Path | Method | A | Description | -| :----------: | :------------------------------------- | :----: | :---: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| AddUser | /user/{name} | POST | true | registers a user with the name `{name}`, balance of 0 and a password of the password supplied in the header | -| AdminAddUser | /admin/user/{name}?init_bal={init_bal} | POST | true | if the password supplied in the header matches the admin password, then it registers a user with the name `{name}`, balance of `init_bal` and a password supplied by the body of the request | -| DelUser | /user/{name} | DELETE | true | if the password supplied in the header matches the user `{name}`'s password, then the user is deleted | -| AdminDelUser | /admin/user/{name} | DELETE | true | if the password supplied in the header matches the admin password, then the user is deleted | +| Name | Path | Method | A | Description | +| :----------: | :------------------------------------------ | :----: | :---: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| AddUser | BankF/user/{name} | POST | true | registers a user with the name `{name}`, balance of 0 and a password of the password supplied in the header | +| AdminAddUser | BankF/admin/user/{name}?init_bal={init_bal} | POST | true | if the password supplied in the header matches the admin password, then it registers a user with the name `{name}`, balance of `init_bal` and a password supplied by the body of the request | +| DelUser | BankF/user/{name} | DELETE | true | if the password supplied in the header matches the user `{name}`'s password, then the user is deleted | +| AdminDelUser | BankF/admin/user/{name} | DELETE | true | if the password supplied in the header matches the admin password, then the user is deleted | diff --git a/docs/services.md b/docs/services.md index 3d11d73..e570210 100644 --- a/docs/services.md +++ b/docs/services.md @@ -10,7 +10,7 @@ - [CC Shop](https://github.com/Reactified/rpm/tree/main/packages/ccash-shop) ![image](https://user-images.githubusercontent.com/31377881/120050327-de163700-bfd1-11eb-9d5a-f75c003e867c.png) ![image](https://user-images.githubusercontent.com/31377881/120050367-09992180-bfd2-11eb-9a22-449d73c196cf.png) -- [CC ATM](https://github.com/Reactified/misc/tree/main/lua/ccash-bank) an ATM for economies allowing for an initial exchange to start up +- [CC Reverse ATM](https://github.com/Reactified/misc/tree/main/lua/ccash-bank) an ATM for economies allowing for an initial exchange to start up ![image](https://user-images.githubusercontent.com/31377881/121277361-4d6b1100-c885-11eb-87c8-cfebcf58da4f.png) ### In-Dev: @@ -24,3 +24,6 @@ - Shipping - High-level bank operations such as loans - Some trust based system for transactions similiar to Paypal + +- a better version of one of these existing ideas +- something completely different diff --git a/include/bank.h b/include/bank.h index 1408233..f2343c5 100644 --- a/include/bank.h +++ b/include/bank.h @@ -33,28 +33,28 @@ private: public: std::string admin_pass; - int_fast8_t AddUser(const std::string &name, const std::string &init_pass); - int_fast8_t AdminAddUser(const std::string &attempt, std::string &&name, uint32_t init_bal, std::string &&init_pass); + int_fast8_t AddUser(const std::string &name, const std::string &init_pass) noexcept; + int_fast8_t AdminAddUser(const std::string &attempt, std::string &&name, uint32_t init_bal, std::string &&init_pass) noexcept; - int_fast8_t DelUser(const std::string &name, const std::string &attempt); - int_fast8_t AdminDelUser(const std::string &name, const std::string &attempt); + int_fast8_t DelUser(const std::string &name, const std::string &attempt) noexcept; + int_fast8_t AdminDelUser(const std::string &name, const std::string &attempt) noexcept; - int_fast8_t SendFunds(const std::string &a_name, const std::string &b_name, uint32_t amount, const std::string &attempt); + int_fast8_t SendFunds(const std::string &a_name, const std::string &b_name, uint32_t amount, const std::string &attempt) noexcept; - bool Contains(const std::string &name) const; - bool AdminVerifyPass(const std::string &attempt); + int_fast8_t Contains(const std::string &name) const noexcept; + int_fast8_t AdminVerifyPass(const std::string &attempt) noexcept; - int_fast8_t SetBal(const std::string &name, const std::string &attempt, uint32_t amount); - int_fast64_t GetBal(const std::string &name) const; + int_fast8_t SetBal(const std::string &name, const std::string &attempt, uint32_t amount) noexcept; + int_fast64_t GetBal(const std::string &name) const noexcept; - int_fast8_t VerifyPassword(const std::string &name, const std::string &attempt) const; - int_fast8_t ChangePassword(const std::string &name, const std::string &attempt, std::string &&new_pass); + int_fast8_t VerifyPassword(const std::string &name, const std::string &attempt) const noexcept; + int_fast8_t ChangePassword(const std::string &name, const std::string &attempt, std::string &&new_pass) noexcept; - Json::Value GetLogs(const std::string &name, const std::string &attempt); + Json::Value GetLogs(const std::string &name, const std::string &attempt) noexcept; void Save(); - //NOT THREAD SAFE, BY NO MEANS SHOULD THIS BE CALLED WHILE RECEIEVING REQUESTS + //NOT THREAD SAFE void Load(); }; diff --git a/include/bank_f.h b/include/bank_f.h index 99f8c52..fc3703f 100644 --- a/include/bank_f.h +++ b/include/bank_f.h @@ -13,6 +13,7 @@ class BankF : public HttpController public: BankF(Bank *b); void Help(req_args) const; + void Ping(req_args) const; void Close(req_args) const; void AddUser(req_args, const std::string &name) const; void AdminAddUser(req_args, std::string &&name, uint32_t init_bal) const; @@ -32,15 +33,16 @@ public: //Usage METHOD_ADD(BankF::GetBal, "/{name}/bal", Get, Options); METHOD_ADD(BankF::GetLog, "/{name}/log", Get, Options); - METHOD_ADD(BankF::SendFunds, "/{name}/send/{to}/amount={amount}", Post, Options); + METHOD_ADD(BankF::SendFunds, "/{name}/send/{to}?amount={amount}", Post, Options); METHOD_ADD(BankF::VerifyPassword, "/{name}/pass/verify", Get, Options); //Meta Usage METHOD_ADD(BankF::ChangePassword, "/{name}/pass/change", Patch, Options); - METHOD_ADD(BankF::SetBal, "/admin/{name}/bal/amount={amount}", Patch, Options); + METHOD_ADD(BankF::SetBal, "/admin/{name}/bal?amount={amount}", Patch, Options); //System Usage METHOD_ADD(BankF::Help, "/help", Get, Options); + METHOD_ADD(BankF::Ping, "/ping", Get, Options); METHOD_ADD(BankF::Close, "/admin/close", Post, Options); METHOD_ADD(BankF::Contains, "/contains/{name}", Get, Options); METHOD_ADD(BankF::AdminVerifyPass, "/admin/verify", Get, Options); diff --git a/include/consts.hpp b/include/consts.hpp index fc66c35..9545808 100644 --- a/include/consts.hpp +++ b/include/consts.hpp @@ -3,4 +3,12 @@ // Setting both values to 0 does not compile logging constexpr unsigned max_log_size = 100; constexpr unsigned pre_log_size = 10; -constexpr unsigned max_name_size = 50; \ No newline at end of file + +constexpr unsigned max_name_size = 50; + +constexpr const char *users_location = "../users.json"; +constexpr const char *config_location = "../config.json"; + +//returns money to an account on deletion +constexpr bool return_on_del = false; +constexpr const char *return_account = ""; \ No newline at end of file diff --git a/include/error_responses.hpp b/include/error_responses.hpp index 5050d33..6c4de41 100644 --- a/include/error_responses.hpp +++ b/include/error_responses.hpp @@ -4,8 +4,7 @@ enum ErrorResponse UserNotFound = -1, WrongPassword = -2, InvalidRequest = -3, - WrongAdminPassword = -4, - NameTooLong = -5, - UserAlreadyExists = -6, - InsufficientFunds = -7, + NameTooLong = -4, + UserAlreadyExists = -5, + InsufficientFunds = -6, }; \ No newline at end of file diff --git a/main.cpp b/main.cpp index d8f7283..9f75f55 100644 --- a/main.cpp +++ b/main.cpp @@ -55,7 +55,7 @@ int main(int argc, char **argv) bank.admin_pass = argv[1]; //Auto Saving - const unsigned long saving_freq = std::stoul(argv[2]); + const unsigned long saving_freq = std::stoul(std::string(argv[2])); if (saving_freq) //if saving frequency is 0 then auto saving is turned off { std::thread([saving_freq]() { @@ -73,7 +73,7 @@ int main(int argc, char **argv) [](const drogon::HttpRequestPtr &req, const drogon::HttpResponsePtr &resp) { resp->addHeader("Access-Control-Allow-Origin", "*"); }); - app().loadConfigFile("../config.json").registerController(API).setThreadNum(std::stoul(argv[3])).run(); + app().loadConfigFile(config_location).registerController(API).setThreadNum(std::stoul(std::string(argv[3]))).run(); return 0; } diff --git a/src/bank.cpp b/src/bank.cpp index 8c28aaa..5015488 100644 --- a/src/bank.cpp +++ b/src/bank.cpp @@ -1,25 +1,28 @@ #include "bank.h" -int_fast8_t Bank::AddUser(const std::string &name, const std::string &init_pass) +int_fast8_t Bank::AddUser(const std::string &name, const std::string &init_pass) noexcept { if (name.size() > max_name_size) { return ErrorResponse::NameTooLong; } + for (char c : name) { - std::shared_lock lock{size_l}; - if (users.try_emplace_l( - name, [](User &) {}, init_pass)) + if (c == ' ') { - return true; - } - else - { - return ErrorResponse::UserAlreadyExists; + return ErrorResponse::InvalidRequest; } } + + { + std::shared_lock lock{size_l}; + return (users.try_emplace_l( + name, [](User &) {}, init_pass)) + ? true + : ErrorResponse::UserAlreadyExists; + } } -int_fast8_t Bank::AdminAddUser(const std::string &attempt, std::string &&name, uint32_t init_bal, std::string &&init_pass) +int_fast8_t Bank::AdminAddUser(const std::string &attempt, std::string &&name, uint32_t init_bal, std::string &&init_pass) noexcept { if (name.size() > max_name_size) { @@ -27,161 +30,158 @@ int_fast8_t Bank::AdminAddUser(const std::string &attempt, std::string &&name, u } if (admin_pass != attempt) { - return ErrorResponse::WrongAdminPassword; - } - { - std::shared_lock lock{size_l}; - if (users.try_emplace_l( - name, [](User &) {}, std::move(init_pass))) - { - return true; - } - else - { - return ErrorResponse::UserAlreadyExists; - } + return ErrorResponse::WrongPassword; } + + std::shared_lock lock{size_l}; + return (users.try_emplace_l( + name, [](User &) {}, init_bal, std::move(init_pass))) + ? true + : ErrorResponse::UserAlreadyExists; } -int_fast8_t Bank::DelUser(const std::string &name, const std::string &attempt) +int_fast8_t Bank::DelUser(const std::string &name, const std::string &attempt) noexcept { std::shared_lock lock{size_l}; bool state = false; - if (!users.erase_if(name, [&state, &attempt](User &u) { return state = (XXH3_64bits(attempt.data(), attempt.size()) == u.password); })) + if (users.erase_if(name, [this, &name, &state, &attempt](User &u) { + if constexpr (return_on_del) + { + if (SendFunds(name, return_account, u.balance, attempt) == 1) + { + return true; + } + } + return state = (XXH3_64bits(attempt.data(), attempt.size()) == u.password); + })) { - return ErrorResponse::UserNotFound; + return (state) ? true : ErrorResponse::WrongPassword; } else { - if (state) - { - return true; - } - else - { - return ErrorResponse::WrongAdminPassword; - } + return ErrorResponse::UserNotFound; } } -int_fast8_t Bank::AdminDelUser(const std::string &name, const std::string &attempt) +int_fast8_t Bank::AdminDelUser(const std::string &name, const std::string &attempt) noexcept { std::shared_lock lock{size_l}; bool state = false; - if (!users.erase_if(name, [this, &state, &attempt](const User &) { return state = (admin_pass == attempt); })) + if (users.erase_if(name, [this, &name, &state, &attempt](const User &u) { + if constexpr (return_on_del) + { + if (SendFunds(name, return_account, u.balance, attempt) == 1) + { + return true; + } + } + return state = (XXH3_64bits(attempt.data(), attempt.size()) == u.password); + })) { - return ErrorResponse::UserNotFound; + return (state) ? true : ErrorResponse::WrongPassword; } else { - if (state) - { - return true; - } - else - { - return ErrorResponse::WrongAdminPassword; - } + return ErrorResponse::UserNotFound; } } -int_fast8_t Bank::SendFunds(const std::string &a_name, const std::string &b_name, uint32_t amount, const std::string &attempt) +int_fast8_t Bank::SendFunds(const std::string &a_name, const std::string &b_name, uint32_t amount, const std::string &attempt) noexcept { //cant send money to self, from self or amount is 0 if (a_name == b_name || !amount) { return ErrorResponse::InvalidRequest; } - - int_fast8_t state = false; - { - std::shared_lock lock{send_funds_l}; //because SendFunds requires 3 locking operations - if (users.modify_if(a_name, [&state, amount, &attempt](User &a) { - //if A exists, A can afford it, and A's password matches - if (a.balance < amount) - { - state = ErrorResponse::InsufficientFunds; - } - else - { - if (a.password != XXH3_64bits(attempt.data(), attempt.size())) - { - state = ErrorResponse::WrongPassword; - } - else - { - a.balance -= amount; - state = true; - } - } - })) - { - return ErrorResponse::UserNotFound; - } - else - { - if (state) - { - //if B doesnt exist - if (users.modify_if(b_name, [amount](User &b) { - b.balance += amount; - })) - { - if constexpr (max_log_size) - { - Transaction temp(a_name, b_name, amount); - Transaction temp2 = temp; - users.modify_if(a_name, [&temp](User &a) { - a.log.AddTrans(std::move(temp)); - }); - users.modify_if(b_name, [&temp2](User &b) { - b.log.AddTrans(std::move(temp2)); - }); - } - return true; - } - else - { - //attempt to refund if A exist - users.modify_if(a_name, [amount](User &a) { - a.balance += amount; - }); - return ErrorResponse::UserNotFound; //because had to refund transaction - } - } - else - { - return state; - } - } - } -} - -bool Bank::Contains(const std::string &name) const -{ - return users.contains(name); -} -bool Bank::AdminVerifyPass(const std::string &attempt) -{ - return (admin_pass == attempt); -} - -int_fast8_t Bank::SetBal(const std::string &name, const std::string &attempt, uint32_t amount) -{ - if (admin_pass != attempt) - { - return ErrorResponse::WrongAdminPassword; - } - if (users.modify_if(name, [amount](User &u) { - u.balance = amount; - })) - { - return true; - } - else + //as first modify_if checks a_name and grabs unique lock + if (!Contains(b_name)) { return ErrorResponse::UserNotFound; } + + int_fast8_t state = false; + if constexpr (max_log_size > 0) + { + Transaction temp(a_name, b_name, amount); + std::shared_lock lock{send_funds_l}; + users.modify_if(a_name, [&temp, &state, amount, &attempt](User &a) { + //if A can afford it and A's password matches attempt + if (a.balance < amount) + { + state = ErrorResponse::InsufficientFunds; + } + else if (a.password != XXH3_64bits(attempt.data(), attempt.size())) + { + + state = ErrorResponse::WrongPassword; + } + else + { + a.balance -= amount; + a.log.AddTrans(Transaction(temp)); + state = true; + } + }); + if (state > 0) + { + users.modify_if(b_name, [&a_name, &b_name, &temp, amount](User &b) { + b.balance += amount; + b.log.AddTrans(std::move(temp)); + }); + } + return state; + } + else + { + std::shared_lock lock{send_funds_l}; + users.modify_if(a_name, [&state, amount, &attempt](User &a) { + //if A can afford it and A's password matches attempt + if (a.balance < amount) + { + state = ErrorResponse::InsufficientFunds; + } + else if (a.password != XXH3_64bits(attempt.data(), attempt.size())) + { + + state = ErrorResponse::WrongPassword; + } + else + { + a.balance -= amount; + state = true; + } + }); + if (state > 0) + { + users.modify_if(b_name, [&a_name, &b_name, amount](User &b) { + b.balance += amount; + }); + } + return state; + } } -int_fast64_t Bank::GetBal(const std::string &name) const + +int_fast8_t Bank::Contains(const std::string &name) const noexcept +{ + return (users.contains(name)) ? true : ErrorResponse::UserNotFound; +} +int_fast8_t Bank::AdminVerifyPass(const std::string &attempt) noexcept +{ + return (admin_pass == attempt) ? true : ErrorResponse::WrongPassword; +} + +int_fast8_t Bank::SetBal(const std::string &name, const std::string &attempt, uint32_t amount) noexcept +{ + if (admin_pass != attempt) + { + return ErrorResponse::WrongPassword; + } + + return (users.modify_if(name, [amount](User &u) { + u.balance = amount; + })) + ? true + : ErrorResponse::UserNotFound; +} +int_fast64_t Bank::GetBal(const std::string &name) const noexcept { int_fast64_t res = ErrorResponse::UserNotFound; users.if_contains(name, [&res](const User &u) { @@ -190,15 +190,15 @@ int_fast64_t Bank::GetBal(const std::string &name) const return res; } -int_fast8_t Bank::VerifyPassword(const std::string &name, const std::string &attempt) const +int_fast8_t Bank::VerifyPassword(const std::string &name, const std::string &attempt) const noexcept { int_fast8_t res = ErrorResponse::UserNotFound; users.if_contains(name, [&res, &attempt](const User &u) { - res = u.password == XXH3_64bits(attempt.data(), attempt.size()); + res = (u.password == XXH3_64bits(attempt.data(), attempt.size())) ? true : ErrorResponse::WrongPassword; }); return res; } -int_fast8_t Bank::ChangePassword(const std::string &name, const std::string &attempt, std::string &&new_pass) +int_fast8_t Bank::ChangePassword(const std::string &name, const std::string &attempt, std::string &&new_pass) noexcept { int_fast8_t res = ErrorResponse::UserNotFound; users.modify_if(name, [&res, &attempt, &new_pass](User &u) { @@ -208,13 +208,14 @@ int_fast8_t Bank::ChangePassword(const std::string &name, const std::string &att } else { + res = true; u.password = XXH3_64bits(new_pass.data(), new_pass.size()); } }); return res; } -Json::Value Bank::GetLogs(const std::string &name, const std::string &attempt) +Json::Value Bank::GetLogs(const std::string &name, const std::string &attempt) noexcept { Json::Value res; if (!users.if_contains(name, [&res, &attempt](const User &u) { @@ -224,15 +225,13 @@ Json::Value Bank::GetLogs(const std::string &name, const std::string &attempt) } else { - Json::Value temp; for (uint32_t i = u.log.data.size(); i > 0; --i) { - temp[i - 1]["to"] = u.log.data[u.log.data.size() - i].to; - temp[i - 1]["from"] = u.log.data[u.log.data.size() - i].from; - temp[i - 1]["amount"] = (Json::UInt)u.log.data[u.log.data.size() - i].amount; - temp[i - 1]["time"] = (Json::UInt64)u.log.data[u.log.data.size() - i].time; + res[i - 1]["to"] = u.log.data[u.log.data.size() - i].to; + res[i - 1]["from"] = u.log.data[u.log.data.size() - i].from; + res[i - 1]["amount"] = (Json::UInt)u.log.data[u.log.data.size() - i].amount; + res[i - 1]["time"] = (Json::UInt64)u.log.data[u.log.data.size() - i].time; } - res = std::move(temp); } })) { @@ -262,7 +261,7 @@ void Bank::Save() } else { - std::ofstream user_save("../users.json"); + std::ofstream user_save(users_location); Json::StreamWriterBuilder builder; const std::unique_ptr writer(builder.newStreamWriter()); writer->write(temp, &user_save); @@ -276,7 +275,7 @@ void Bank::Load() Json::CharReaderBuilder builder; Json::Value temp; - std::ifstream user_save("../users.json"); + std::ifstream user_save(users_location); builder["collectComments"] = true; JSONCPP_STRING errs; if (!parseFromStream(builder, user_save, &temp, &errs)) @@ -290,7 +289,7 @@ void Bank::Load() user_save.close(); for (const auto &u : temp.getMemberNames()) { - if constexpr (max_log_size) + if constexpr (max_log_size > 0) { users.try_emplace(u, temp[u]["balance"].asUInt(), std::move(temp[u]["password"].asUInt64()), temp[u]["log"]); } diff --git a/src/bank_f.cpp b/src/bank_f.cpp index aea592a..08f30fd 100644 --- a/src/bank_f.cpp +++ b/src/bank_f.cpp @@ -1,29 +1,27 @@ #include "bank_f.h" -#define JSON(V) callback(HttpResponse::newHttpJsonResponse(JsonReturn(V))); -#define INLINE __attribute__((always_inline)) inline -#define GEN_BODY \ - const auto temp_req = req->getJsonObject(); \ - const auto body = temp_req ? *temp_req : Json::Value(); +#define JSON(V) callback(HttpResponse::newHttpJsonResponse(JsonCast(V))); #define PASS_HEADER req->getHeader("Password") template -INLINE Json::Value JsonReturn(T &&val) +constexpr Json::Value JsonCast(T &&val) { - Json::Value res; if constexpr (std::is_same_v) { - res["value"] = (int)val; //becuase of json lib interpreting 67 as 'A' for example + return (int)val; //becuase of json lib interpreting 67 as 'A' for example } else if constexpr (std::is_same_v) { - res["value"] = (Json::Int64)val; + return (Json::Int64)val; + } + else if constexpr (std::is_same_v) + { + return (Json::Int64)val; } else { - res["value"] = val; + return val; } - return res; } BankF::BankF(Bank *b) : bank(*b) {} @@ -31,8 +29,7 @@ BankF::BankF(Bank *b) : bank(*b) {} void BankF::Help(req_args) const { auto resp = HttpResponse::newHttpResponse(); - auto handlerInfo = app().getHandlersInfo(); - resp->setBody("

Error Responses

# meaning
-1 UserNotFound
-2 WrongPassword
-3 InvalidRequest
-4 WrongAdminPassword
-5 NameTooLong
-6 UserAlreadyExists
-7 InsufficientFunds

Things of Note

  • all endpoints respond with JSON file type
  • "A" denotes requiring Authentication in the form of a header titled "Password"

Usage

Name Path Method A Description
GetBal /{name}/bal GET false returns the balance of a given user {name}
GetLog /{name}/log GET true returns a list of last n number of transactions (a configurable amount) of a given user {name}
SendFunds /{name}/send/{to}/amount={amount} POST true sends {amount} from user {name} to user {to}
VerifyPassword /{name}/pass/verify GET true returns true or false depending on if the supplied user {name}'s password matches the password supplied in the header

Meta Usage

Name Path Method A Description
ChangePassword /{name}/pass/change PATCH true if the password supplied in the header matches the user {name}'s password, the user’s password is changed to the one given in the body
SetBal /admin/{name}/bal/amount={amount} PATCH true sets the balance of a give user {name} if the supplied password matches the admin password

System Usage

Name Path Method A Description
Help /help GET false the page you’re looking at right now!
Close /admin/close POST true saves and then closes the program if the supplied password matches the admin password
Contains /contains/{name} GET false returns true or false depending on if the supplied user {name} exists
AdminVerifyPass /admin/verify GET true returns true or false depending on if the password supplied in the header matches the admin password

User Management

Name Path Method A Description
AddUser /user/{name} POST true registers a user with the name {name}, balance of 0 and a password of the password supplied in the header
AdminAddUser /admin/user/{name}?init_bal={init_bal} POST true if the password supplied in the header matches the admin password, then it registers a user with the name {name}, balance of init_bal and a password supplied by the body of the request
DelUser /user/{name} DELETE true if the password supplied in the header matches the user {name}'s password, then the user is deleted
AdminDelUser /admin/user/{name} DELETE true if the password supplied in the header matches the admin password, then the user is deleted
"); + resp->setBody("# Meta Usage

Error Responses

# meaning
-1 UserNotFound
-2 WrongPassword
-3 InvalidRequest
-4 NameTooLong
-5 UserAlreadyExists
-6 InsufficientFunds

Things of Note

  • all endpoints respond with JSON file type
  • "A" denotes requiring Authentication in the form of a header titled "Password"

Usage

Name Path Method A Description
GetBal BankF/{name}/bal GET false returns the balance of a given user {name}
GetLog BankF/{name}/log GET true returns a list of last n number of transactions (a configurable amount) of a given user {name}
SendFunds BankF/{name}/send/{to}?amount={amount} POST true sends {amount} from user {name} to user {to}
VerifyPassword BankF/{name}/pass/verify GET true returns 1 if the supplied user {name}'s password matches the password supplied in the header

Meta Usage

Name Path Method A Description
ChangePassword BankF/{name}/pass/change PATCH true if the password supplied in the header matches the user {name}'s password, the user’s password is changed to the one given in the body
SetBal BankF/admin/{name}/bal?amount={amount} PATCH true sets the balance of a give user {name} if the supplied password matches the admin password

System Usage

Name Path Method A Description
Help BankF/help GET false the page you’re looking at right now!
Ping BankF/ping GET false for pinging the server to see if its online
Close BankF/admin/close POST true saves and then closes the program if the supplied password matches the admin password
Contains BankF/contains/{name} GET false returns 1 if the supplied user {name} exists
AdminVerifyPass BankF/admin/verify GET true returns 1 if the password supplied in the header matches the admin password

User Management

Name Path Method A Description
AddUser BankF/user/{name} POST true registers a user with the name {name}, balance of 0 and a password of the password supplied in the header
AdminAddUser BankF/admin/user/{name}?init_bal={init_bal} POST true if the password supplied in the header matches the admin password, then it registers a user with the name {name}, balance of init_bal and a password supplied by the body of the request
DelUser BankF/user/{name} DELETE true if the password supplied in the header matches the user {name}'s password, then the user is deleted
AdminDelUser BankF/admin/user/{name} DELETE true if the password supplied in the header matches the admin password, then the user is deleted
"); resp->setExpiredTime(0); callback(resp); } @@ -52,6 +49,13 @@ void BankF::Close(req_args) const } JSON(res); } +void BankF::Ping(req_args) const +{ + auto resp = HttpResponse::newHttpResponse(); + resp->setBody("pong"); + resp->setExpiredTime(0); + callback(resp); +} void BankF::AddUser(req_args, const std::string &name) const { JSON(bank.AddUser(std::move(name), PASS_HEADER)); @@ -98,14 +102,14 @@ void BankF::AdminVerifyPass(req_args) } void BankF::GetLog(req_args, const std::string &name) { - if constexpr (max_log_size) + if constexpr (max_log_size > 0) { JSON(bank.GetLogs(name, PASS_HEADER)); } else { - auto resp = HttpResponse::newHttpJsonResponse(JsonReturn("Logs are Disabled")); + auto resp = HttpResponse::newHttpJsonResponse("Logs are Disabled"); resp->setExpiredTime(0); //cached forever callback(resp); } -} \ No newline at end of file +} diff --git a/src/user.cpp b/src/user.cpp index ade67c2..bfa0aa5 100644 --- a/src/user.cpp +++ b/src/user.cpp @@ -29,11 +29,11 @@ User::User(uint32_t init_bal, uint64_t init_pass, const Json::Value &log_j) : ba log.data.reserve(std::min(pre_log_size * ((log_j.size() / pre_log_size) + 1), max_log_size)); for (uint32_t i = (log_j.size() - max_log_size) * (log_j.size() > max_log_size); i < log_j.size(); i++) { - log.data.push_back(std::move(Transaction( + log.data.push_back(Transaction( log_j[i]["from"].asCString(), log_j[i]["to"].asCString(), log_j[i]["amount"].asUInt(), - log_j[i]["time"].asUInt64()))); + log_j[i]["time"].asUInt64())); } } } @@ -43,7 +43,7 @@ Json::Value User::Serialize() const Json::Value res; res["balance"] = (Json::UInt)balance; res["password"] = (Json::UInt64)password; - if constexpr (max_log_size) + if constexpr (max_log_size > 0) { res["log"] = log.Serialize(); } diff --git a/users.json b/users.json index 2c63c08..b785785 100644 --- a/users.json +++ b/users.json @@ -1,2 +1,8 @@ { + "" : + { + "balance" : 0, + "log" : null, + "password" : 0 + }, }