From d822f73d861bfa56551784bb801edf0bb825512b Mon Sep 17 00:00:00 2001 From: Thibault Denizet Date: Wed, 21 Nov 2018 11:45:00 +0700 Subject: [PATCH 01/23] Audit for AccountUser --- .../v1/controllers/transaction_controller.ex | 4 +- .../v1/controllers/user_controller.ex | 2 +- ...ransaction_consumption_controller_test.exs | 8 ++- .../transaction_request_controller_test.exs | 17 +++--- .../admin_auth/user_controller_test.exs | 59 ++++++++++--------- .../admin_auth/wallet_controller_test.exs | 3 +- ...ransaction_consumption_controller_test.exs | 8 ++- .../transaction_request_controller_test.exs | 11 ++-- .../provider_auth/user_controller_test.exs | 29 ++++----- .../provider_auth/wallet_controller_test.exs | 3 +- .../lib/ewallet/gates/transaction_gate.ex | 14 ++--- apps/ewallet/lib/ewallet/web/inviter.ex | 2 +- ...action_consumption_confirmer_gate_test.exs | 11 ++-- .../gates/transaction_request_gate_test.exs | 7 ++- apps/ewallet_db/config/config.exs | 3 +- .../ewallet_db/lib/ewallet_db/account_user.ex | 24 ++++++-- apps/ewallet_db/lib/ewallet_db/audit.ex | 21 +++++-- apps/ewallet_db/lib/ewallet_db/invite.ex | 1 + apps/ewallet_db/lib/ewallet_db/user.ex | 16 +++-- apps/ewallet_db/priv/repo/seeds/01_user.exs | 3 +- .../priv/repo/seeds_sample/00_user.exs | 2 +- .../priv/repo/seeds_test/01_user.exs | 2 +- .../test/ewallet_db/account_user_test.exs | 21 ++++--- .../ewallet_db/test/ewallet_db/audit_test.exs | 4 +- apps/ewallet_db/test/support/factory.ex | 3 +- 25 files changed, 161 insertions(+), 117 deletions(-) diff --git a/apps/admin_api/lib/admin_api/v1/controllers/transaction_controller.ex b/apps/admin_api/lib/admin_api/v1/controllers/transaction_controller.ex index 8e1df82ed..64c03f816 100644 --- a/apps/admin_api/lib/admin_api/v1/controllers/transaction_controller.ex +++ b/apps/admin_api/lib/admin_api/v1/controllers/transaction_controller.ex @@ -7,7 +7,7 @@ defmodule AdminAPI.V1.TransactionController do alias Ecto.Changeset alias EWallet.TransactionGate alias EWallet.TransactionPolicy - alias EWallet.Web.{Orchestrator, Paginator, V1.TransactionOverlay} + alias EWallet.Web.{Originator, Orchestrator, Paginator, V1.TransactionOverlay} alias EWalletDB.{Account, Repo, Transaction, User} @doc """ @@ -115,6 +115,8 @@ defmodule AdminAPI.V1.TransactionController do @spec create(Plug.Conn.t(), map()) :: Plug.Conn.t() def create(conn, attrs) do with :ok <- permit(:create, conn.assigns, attrs), + originator <- Originator.extract(conn.assigns), + attrs <- Map.put(attrs, "originator", originator), {:ok, transaction} <- TransactionGate.create(attrs) do transaction |> Orchestrator.one(TransactionOverlay, attrs) diff --git a/apps/admin_api/lib/admin_api/v1/controllers/user_controller.ex b/apps/admin_api/lib/admin_api/v1/controllers/user_controller.ex index a6d74cf3a..9803c0526 100644 --- a/apps/admin_api/lib/admin_api/v1/controllers/user_controller.ex +++ b/apps/admin_api/lib/admin_api/v1/controllers/user_controller.ex @@ -95,7 +95,7 @@ defmodule AdminAPI.V1.UserController do attrs <- Map.put(attrs, "originator", originator), {:ok, user} <- User.insert(attrs), %Account{} = account <- AccountHelper.get_current_account(conn), - {:ok, _account_user} <- AccountUser.link(account.uuid, user.uuid) do + {:ok, _account_user} <- AccountUser.link(account.uuid, user.uuid, originator) do respond_single(user, conn) else error -> respond_single(error, conn) diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/transaction_consumption_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/transaction_consumption_controller_test.exs index 033e33665..e8e849e07 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/transaction_consumption_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/transaction_consumption_controller_test.exs @@ -27,13 +27,15 @@ defmodule AdminAPI.V1.AdminAuth.TransactionConsumptionControllerTest do alias AdminAPI.V1.Endpoint alias EWallet.TransactionConsumptionScheduler + alias EWalletConfig.System + setup do {:ok, _} = TestEndpoint.start_link() account = Account.get_master_account() {:ok, alice} = :user |> params_for() |> User.insert() bob = get_test_user() - {:ok, _} = AccountUser.link(account.uuid, bob.uuid) + {:ok, _} = AccountUser.link(account.uuid, bob.uuid, %System{}) %{ account: account, @@ -612,7 +614,7 @@ defmodule AdminAPI.V1.AdminAuth.TransactionConsumptionControllerTest do setup do account = insert(:account) wallet = insert(:wallet) - {:ok, _} = AccountUser.link(account.uuid, wallet.user_uuid) + {:ok, _} = AccountUser.link(account.uuid, wallet.user_uuid, %System{}) tc_1 = insert(:transaction_consumption, account_uuid: account.uuid, status: "pending") @@ -1946,7 +1948,7 @@ defmodule AdminAPI.V1.AdminAuth.TransactionConsumptionControllerTest do end test "sends an error when approved without enough funds", meta do - {:ok, _} = AccountUser.link(meta.account.uuid, meta.bob.uuid) + {:ok, _} = AccountUser.link(meta.account.uuid, meta.bob.uuid, %System{}) # Create a require_confirmation transaction request that will be consumed soon transaction_request = diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/transaction_request_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/transaction_request_controller_test.exs index bfaa3d9a1..9691d0f90 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/transaction_request_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/transaction_request_controller_test.exs @@ -3,12 +3,13 @@ defmodule AdminAPI.V1.AdminAuth.TransactionRequestControllerTest do alias EWallet.Web.Date alias EWallet.Web.V1.{AccountSerializer, TokenSerializer, UserSerializer, WalletSerializer} alias EWalletDB.{Account, AccountUser, Repo, TransactionRequest, User, Wallet} + alias EWalletConfig.System describe "/transaction_request.all" do setup do user = get_test_user() account = Account.get_master_account() - {:ok, _} = AccountUser.link(account.uuid, user.uuid) + {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) tr_1 = insert(:transaction_request, user_uuid: user.uuid, status: "valid") tr_2 = insert(:transaction_request, account_uuid: account.uuid, status: "valid") @@ -139,7 +140,7 @@ defmodule AdminAPI.V1.AdminAuth.TransactionRequestControllerTest do token = insert(:token) account_wallet = Account.get_primary_wallet(account) wallet = User.get_primary_wallet(user) - {:ok, _} = AccountUser.link(account.uuid, user.uuid) + {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) response = admin_user_request("/transaction_request.create", %{ @@ -203,7 +204,7 @@ defmodule AdminAPI.V1.AdminAuth.TransactionRequestControllerTest do token = insert(:token) account_wallet = Account.get_primary_wallet(account) wallet = User.get_primary_wallet(user) - {:ok, _} = AccountUser.link(account.uuid, user.uuid) + {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) response = admin_user_request("/transaction_request.create", %{ @@ -266,7 +267,7 @@ defmodule AdminAPI.V1.AdminAuth.TransactionRequestControllerTest do user = get_test_user() token = insert(:token) wallet = User.get_primary_wallet(user) - {:ok, _} = AccountUser.link(account.uuid, user.uuid) + {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) response = admin_user_request("/transaction_request.create", %{ @@ -324,7 +325,7 @@ defmodule AdminAPI.V1.AdminAuth.TransactionRequestControllerTest do user = get_test_user() token = insert(:token) wallet = User.get_primary_wallet(user) - {:ok, _} = AccountUser.link(account.uuid, user.uuid) + {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) response = admin_user_request("/transaction_request.create", %{ @@ -382,7 +383,7 @@ defmodule AdminAPI.V1.AdminAuth.TransactionRequestControllerTest do user = get_test_user() token = insert(:token) wallet = User.get_primary_wallet(user) - {:ok, _} = AccountUser.link(account.uuid, user.uuid) + {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) response = admin_user_request("/transaction_request.create", %{ @@ -469,7 +470,7 @@ defmodule AdminAPI.V1.AdminAuth.TransactionRequestControllerTest do user = get_test_user() token = insert(:token, enabled: false) wallet = User.get_primary_wallet(user) - {:ok, _} = AccountUser.link(account.uuid, user.uuid) + {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) response = admin_user_request("/transaction_request.create", %{ @@ -488,7 +489,7 @@ defmodule AdminAPI.V1.AdminAuth.TransactionRequestControllerTest do account = Account.get_master_account() user = get_test_user() token = insert(:token, enabled: false) - {:ok, _} = AccountUser.link(account.uuid, user.uuid) + {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) {:ok, wallet} = Wallet.insert_secondary_or_burn(%{ diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/user_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/user_controller_test.exs index 594ad1962..4f8dae6f2 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/user_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/user_controller_test.exs @@ -2,6 +2,7 @@ defmodule AdminAPI.V1.AdminAuth.UserControllerTest do use AdminAPI.ConnCase, async: true alias EWallet.Web.Date alias EWalletDB.{Account, AccountUser, User, AuthToken, Role, Membership} + alias EWalletConfig.System @owner_app :some_app @@ -29,10 +30,10 @@ defmodule AdminAPI.V1.AdminAuth.UserControllerTest do user_4 = insert(:user, %{username: "missed_user1"}) account = Account.get_master_account() - {:ok, _} = AccountUser.link(account.uuid, user_1.uuid) - {:ok, _} = AccountUser.link(account.uuid, user_2.uuid) - {:ok, _} = AccountUser.link(account.uuid, user_3.uuid) - {:ok, _} = AccountUser.link(account.uuid, user_4.uuid) + {:ok, _} = AccountUser.link(account.uuid, user_1.uuid, %System{}) + {:ok, _} = AccountUser.link(account.uuid, user_2.uuid, %System{}) + {:ok, _} = AccountUser.link(account.uuid, user_3.uuid, %System{}) + {:ok, _} = AccountUser.link(account.uuid, user_4.uuid, %System{}) attrs = %{ # Search is case-insensitive @@ -83,12 +84,12 @@ defmodule AdminAPI.V1.AdminAuth.UserControllerTest do account_2 = insert(:account) account_3 = insert(:account, parent: account_2) - {:ok, _} = AccountUser.link(account_1.uuid, user_1.uuid) + {:ok, _} = AccountUser.link(account_1.uuid, user_1.uuid, %System{}) - {:ok, _} = AccountUser.link(account_2.uuid, user_2.uuid) - {:ok, _} = AccountUser.link(account_2.uuid, user_3.uuid) - {:ok, _} = AccountUser.link(account_3.uuid, user_3.uuid) - {:ok, _} = AccountUser.link(account_3.uuid, user_4.uuid) + {:ok, _} = AccountUser.link(account_2.uuid, user_2.uuid, %System{}) + {:ok, _} = AccountUser.link(account_2.uuid, user_3.uuid, %System{}) + {:ok, _} = AccountUser.link(account_3.uuid, user_3.uuid, %System{}) + {:ok, _} = AccountUser.link(account_3.uuid, user_4.uuid, %System{}) attrs = %{ # Search is case-insensitive @@ -115,12 +116,12 @@ defmodule AdminAPI.V1.AdminAuth.UserControllerTest do account_2 = insert(:account) account_3 = insert(:account, parent: account_2) - {:ok, _} = AccountUser.link(account_1.uuid, user_1.uuid) + {:ok, _} = AccountUser.link(account_1.uuid, user_1.uuid, %System{}) - {:ok, _} = AccountUser.link(account_2.uuid, user_2.uuid) - {:ok, _} = AccountUser.link(account_2.uuid, user_3.uuid) - {:ok, _} = AccountUser.link(account_3.uuid, user_3.uuid) - {:ok, _} = AccountUser.link(account_3.uuid, user_4.uuid) + {:ok, _} = AccountUser.link(account_2.uuid, user_2.uuid, %System{}) + {:ok, _} = AccountUser.link(account_2.uuid, user_3.uuid, %System{}) + {:ok, _} = AccountUser.link(account_3.uuid, user_3.uuid, %System{}) + {:ok, _} = AccountUser.link(account_3.uuid, user_4.uuid, %System{}) attrs = %{ # Search is case-insensitive @@ -147,12 +148,12 @@ defmodule AdminAPI.V1.AdminAuth.UserControllerTest do account_2 = insert(:account) account_3 = insert(:account, parent: account_2) - {:ok, _} = AccountUser.link(account_1.uuid, user_1.uuid) + {:ok, _} = AccountUser.link(account_1.uuid, user_1.uuid, %System{}) - {:ok, _} = AccountUser.link(account_2.uuid, user_2.uuid) - {:ok, _} = AccountUser.link(account_2.uuid, user_3.uuid) - {:ok, _} = AccountUser.link(account_3.uuid, user_3.uuid) - {:ok, _} = AccountUser.link(account_3.uuid, user_4.uuid) + {:ok, _} = AccountUser.link(account_2.uuid, user_2.uuid, %System{}) + {:ok, _} = AccountUser.link(account_2.uuid, user_3.uuid, %System{}) + {:ok, _} = AccountUser.link(account_3.uuid, user_3.uuid, %System{}) + {:ok, _} = AccountUser.link(account_3.uuid, user_4.uuid, %System{}) attrs = %{ # Search is case-insensitive @@ -179,7 +180,7 @@ defmodule AdminAPI.V1.AdminAuth.UserControllerTest do target = Enum.at(users, 1) account = Account.get_master_account() - {:ok, _} = AccountUser.link(account.uuid, target.uuid) + {:ok, _} = AccountUser.link(account.uuid, target.uuid, %System{}) response = admin_user_request("/user.get", %{"id" => target.id}) @@ -211,7 +212,7 @@ defmodule AdminAPI.V1.AdminAuth.UserControllerTest do |> insert() account = Account.get_master_account() - {:ok, _} = AccountUser.link(account.uuid, inserted_user.uuid) + {:ok, _} = AccountUser.link(account.uuid, inserted_user.uuid, %System{}) request_data = %{provider_user_id: inserted_user.provider_user_id} response = admin_user_request("/user.get", request_data) @@ -376,7 +377,7 @@ defmodule AdminAPI.V1.AdminAuth.UserControllerTest do }) account = Account.get_master_account() - {:ok, _} = AccountUser.link(account.uuid, user.uuid) + {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) response = admin_user_request("/user.update", request_data) @@ -397,7 +398,7 @@ defmodule AdminAPI.V1.AdminAuth.UserControllerTest do {:ok, user} = :user |> params_for() |> User.insert() account = Account.get_master_account() - {:ok, _} = AccountUser.link(account.uuid, user.uuid) + {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) request_data = params_for(:user, %{ @@ -421,7 +422,7 @@ defmodule AdminAPI.V1.AdminAuth.UserControllerTest do }) account = Account.get_master_account() - {:ok, _} = AccountUser.link(account.uuid, user.uuid) + {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) response = admin_user_request("/user.update", %{ @@ -443,7 +444,7 @@ defmodule AdminAPI.V1.AdminAuth.UserControllerTest do }) account = Account.get_master_account() - {:ok, _} = AccountUser.link(account.uuid, user.uuid) + {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) response = admin_user_request("/user.update", %{ @@ -468,7 +469,7 @@ defmodule AdminAPI.V1.AdminAuth.UserControllerTest do |> User.insert() account = Account.get_master_account() - {:ok, _} = AccountUser.link(account.uuid, user.uuid) + {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) response = admin_user_request("/user.update", %{ @@ -532,7 +533,7 @@ defmodule AdminAPI.V1.AdminAuth.UserControllerTest do test "disable a user succeed and disable his tokens given his id" do user = insert(:user, %{enabled: true}) account = Account.get_master_account() - {:ok, _} = AccountUser.link(account.uuid, user.uuid) + {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) {:ok, token} = AuthToken.generate(user, @owner_app) token_string = token.token @@ -553,7 +554,7 @@ defmodule AdminAPI.V1.AdminAuth.UserControllerTest do test "disable a user succeed and disable his tokens given his provider user id" do user = insert(:user, %{enabled: true}) account = Account.get_master_account() - {:ok, _} = AccountUser.link(account.uuid, user.uuid) + {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) {:ok, token} = AuthToken.generate(user, @owner_app) token_string = token.token @@ -579,7 +580,7 @@ defmodule AdminAPI.V1.AdminAuth.UserControllerTest do {:ok, _m} = Membership.unassign(admin, master) user = insert(:user, %{enabled: true}) - {:ok, _} = AccountUser.link(master.uuid, user.uuid) + {:ok, _} = AccountUser.link(master.uuid, user.uuid, %System{}) sub_acc = insert(:account, parent: master, name: "Account 1") {:ok, _m} = Membership.assign(admin, sub_acc, role) diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/wallet_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/wallet_controller_test.exs index 3c1709be7..45a2d6214 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/wallet_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/wallet_controller_test.exs @@ -3,6 +3,7 @@ defmodule AdminAPI.V1.AdminAuth.WalletControllerTest do alias EWallet.Web.Date alias EWallet.Web.V1.UserSerializer alias EWalletDB.{Account, AccountUser, Repo, Token, User, Wallet} + alias EWalletConfig.System describe "/wallet.all" do test "returns a list of wallets and pagination data" do @@ -370,7 +371,7 @@ defmodule AdminAPI.V1.AdminAuth.WalletControllerTest do # Pick the 2nd inserted wallet target = Enum.at(wallets, 1) - {:ok, _} = AccountUser.link(account.uuid, target.user_uuid) + {:ok, _} = AccountUser.link(account.uuid, target.user_uuid, %System{}) response = admin_user_request("/wallet.get", %{"address" => target.address}) diff --git a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/transaction_consumption_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/transaction_consumption_controller_test.exs index a3c3e5333..cdb801f4c 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/transaction_consumption_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/transaction_consumption_controller_test.exs @@ -25,13 +25,15 @@ defmodule AdminAPI.V1.ProviderAuth.TransactionConsumptionControllerTest do alias AdminAPI.V1.Endpoint alias EWallet.TransactionConsumptionScheduler + alias EWalletConfig.System + setup do {:ok, _} = TestEndpoint.start_link() account = Account.get_master_account() {:ok, alice} = :user |> params_for() |> User.insert() bob = get_test_user() - {:ok, _} = AccountUser.link(account.uuid, bob.uuid) + {:ok, _} = AccountUser.link(account.uuid, bob.uuid, %System{}) %{ account: account, @@ -586,7 +588,7 @@ defmodule AdminAPI.V1.ProviderAuth.TransactionConsumptionControllerTest do setup do account = insert(:account) wallet = insert(:wallet) - {:ok, _} = AccountUser.link(account.uuid, wallet.user_uuid) + {:ok, _} = AccountUser.link(account.uuid, wallet.user_uuid, %System{}) tc_1 = insert(:transaction_consumption, account_uuid: account.uuid, status: "pending") @@ -1791,7 +1793,7 @@ defmodule AdminAPI.V1.ProviderAuth.TransactionConsumptionControllerTest do end test "sends an error when approved without enough funds", meta do - {:ok, _} = AccountUser.link(meta.account.uuid, meta.bob.uuid) + {:ok, _} = AccountUser.link(meta.account.uuid, meta.bob.uuid, %System{}) # Create a require_confirmation transaction request that will be consumed soon transaction_request = diff --git a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/transaction_request_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/transaction_request_controller_test.exs index 102715908..8df686638 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/transaction_request_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/transaction_request_controller_test.exs @@ -2,12 +2,13 @@ defmodule AdminAPI.V1.ProviderAuth.TransactionRequestControllerTest do use AdminAPI.ConnCase, async: true alias EWallet.Web.{Date, V1.TokenSerializer, V1.UserSerializer} alias EWalletDB.{Account, AccountUser, Repo, TransactionRequest, User} + alias EWalletConfig.System describe "/transaction_request.all" do setup do user = get_test_user() account = Account.get_master_account() - {:ok, _} = AccountUser.link(account.uuid, user.uuid) + {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) tr_1 = insert(:transaction_request, user_uuid: user.uuid, status: "valid") tr_2 = insert(:transaction_request, account_uuid: account.uuid, status: "valid") @@ -131,7 +132,7 @@ defmodule AdminAPI.V1.ProviderAuth.TransactionRequestControllerTest do user = get_test_user() token = insert(:token) wallet = User.get_primary_wallet(user) - {:ok, _} = AccountUser.link(account.uuid, user.uuid) + {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) response = provider_request("/transaction_request.create", %{ @@ -189,7 +190,7 @@ defmodule AdminAPI.V1.ProviderAuth.TransactionRequestControllerTest do user = get_test_user() token = insert(:token) wallet = User.get_primary_wallet(user) - {:ok, _} = AccountUser.link(account.uuid, user.uuid) + {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) response = provider_request("/transaction_request.create", %{ @@ -247,7 +248,7 @@ defmodule AdminAPI.V1.ProviderAuth.TransactionRequestControllerTest do user = get_test_user() token = insert(:token) wallet = User.get_primary_wallet(user) - {:ok, _} = AccountUser.link(account.uuid, user.uuid) + {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) response = provider_request("/transaction_request.create", %{ @@ -317,7 +318,7 @@ defmodule AdminAPI.V1.ProviderAuth.TransactionRequestControllerTest do account = Account.get_master_account() user = get_test_user() wallet = User.get_primary_wallet(user) - {:ok, _} = AccountUser.link(account.uuid, user.uuid) + {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) response = provider_request("/transaction_request.create", %{ diff --git a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/user_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/user_controller_test.exs index 2e5cac4ad..9de63b10f 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/user_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/user_controller_test.exs @@ -2,6 +2,7 @@ defmodule AdminAPI.V1.ProviderAuth.UserControllerTest do use AdminAPI.ConnCase, async: true alias EWallet.Web.Date alias EWalletDB.{Account, AccountUser, User, AuthToken} + alias EWalletConfig.System @owner_app :some_app @@ -29,10 +30,10 @@ defmodule AdminAPI.V1.ProviderAuth.UserControllerTest do user_4 = insert(:user, %{username: "missed_user1"}) account = Account.get_master_account() - {:ok, _} = AccountUser.link(account.uuid, user_1.uuid) - {:ok, _} = AccountUser.link(account.uuid, user_2.uuid) - {:ok, _} = AccountUser.link(account.uuid, user_3.uuid) - {:ok, _} = AccountUser.link(account.uuid, user_4.uuid) + {:ok, _} = AccountUser.link(account.uuid, user_1.uuid, %System{}) + {:ok, _} = AccountUser.link(account.uuid, user_2.uuid, %System{}) + {:ok, _} = AccountUser.link(account.uuid, user_3.uuid, %System{}) + {:ok, _} = AccountUser.link(account.uuid, user_4.uuid, %System{}) attrs = %{ # Search is case-insensitive @@ -62,7 +63,7 @@ defmodule AdminAPI.V1.ProviderAuth.UserControllerTest do target = Enum.at(users, 1) account = Account.get_master_account() - {:ok, _} = AccountUser.link(account.uuid, target.uuid) + {:ok, _} = AccountUser.link(account.uuid, target.uuid, %System{}) response = provider_request("/user.get", %{"id" => target.id}) @@ -94,7 +95,7 @@ defmodule AdminAPI.V1.ProviderAuth.UserControllerTest do |> insert() account = Account.get_master_account() - {:ok, _} = AccountUser.link(account.uuid, inserted_user.uuid) + {:ok, _} = AccountUser.link(account.uuid, inserted_user.uuid, %System{}) request_data = %{provider_user_id: inserted_user.provider_user_id} response = provider_request("/user.get", request_data) @@ -259,7 +260,7 @@ defmodule AdminAPI.V1.ProviderAuth.UserControllerTest do }) account = Account.get_master_account() - {:ok, _} = AccountUser.link(account.uuid, user.uuid) + {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) response = provider_request("/user.update", request_data) @@ -280,7 +281,7 @@ defmodule AdminAPI.V1.ProviderAuth.UserControllerTest do {:ok, user} = :user |> params_for() |> User.insert() account = Account.get_master_account() - {:ok, _} = AccountUser.link(account.uuid, user.uuid) + {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) request_data = params_for(:user, %{ @@ -304,7 +305,7 @@ defmodule AdminAPI.V1.ProviderAuth.UserControllerTest do }) account = Account.get_master_account() - {:ok, _} = AccountUser.link(account.uuid, user.uuid) + {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) response = provider_request("/user.update", %{ @@ -326,7 +327,7 @@ defmodule AdminAPI.V1.ProviderAuth.UserControllerTest do }) account = Account.get_master_account() - {:ok, _} = AccountUser.link(account.uuid, user.uuid) + {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) response = provider_request("/user.update", %{ @@ -349,7 +350,7 @@ defmodule AdminAPI.V1.ProviderAuth.UserControllerTest do }) account = Account.get_master_account() - {:ok, _} = AccountUser.link(account.uuid, user.uuid) + {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) response = provider_request("/user.update", %{ @@ -413,7 +414,7 @@ defmodule AdminAPI.V1.ProviderAuth.UserControllerTest do test "disable a user succeed and disable his tokens given his id" do user = insert(:user, %{enabled: true}) account = Account.get_master_account() - {:ok, _} = AccountUser.link(account.uuid, user.uuid) + {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) {:ok, token} = AuthToken.generate(user, @owner_app) token_string = token.token @@ -434,7 +435,7 @@ defmodule AdminAPI.V1.ProviderAuth.UserControllerTest do test "disable a user succeed and disable his tokens given his provider user id" do user = insert(:user, %{enabled: true}) account = Account.get_master_account() - {:ok, _} = AccountUser.link(account.uuid, user.uuid) + {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) {:ok, token} = AuthToken.generate(user, @owner_app) token_string = token.token @@ -456,7 +457,7 @@ defmodule AdminAPI.V1.ProviderAuth.UserControllerTest do master = Account.get_master_account() user = insert(:user, %{enabled: true}) - {:ok, _} = AccountUser.link(master.uuid, user.uuid) + {:ok, _} = AccountUser.link(master.uuid, user.uuid, %System{}) sub_acc = insert(:account, parent: master, name: "Account 1") key = insert(:key, %{account: sub_acc}) diff --git a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/wallet_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/wallet_controller_test.exs index c52f4746d..afb049694 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/wallet_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/wallet_controller_test.exs @@ -3,6 +3,7 @@ defmodule AdminAPI.V1.ProviderAuth.WalletControllerTest do alias EWallet.Web.Date alias EWallet.Web.V1.UserSerializer alias EWalletDB.{Account, AccountUser, Repo, Token, User, Wallet} + alias EWalletConfig.System describe "/wallet.all" do test "returns a list of wallets and pagination data" do @@ -321,7 +322,7 @@ defmodule AdminAPI.V1.ProviderAuth.WalletControllerTest do # Pick the 2nd inserted wallet target = Enum.at(wallets, 1) - {:ok, _} = AccountUser.link(account.uuid, target.user_uuid) + {:ok, _} = AccountUser.link(account.uuid, target.user_uuid, %System{}) response = provider_request("/wallet.get", %{"address" => target.address}) diff --git a/apps/ewallet/lib/ewallet/gates/transaction_gate.ex b/apps/ewallet/lib/ewallet/gates/transaction_gate.ex index 121be7ded..da768c5a7 100644 --- a/apps/ewallet/lib/ewallet/gates/transaction_gate.ex +++ b/apps/ewallet/lib/ewallet/gates/transaction_gate.ex @@ -21,7 +21,7 @@ defmodule EWallet.TransactionGate do {:ok, from, to, exchange} <- AmountFetcher.fetch(attrs, from, to), {:ok, exchange} <- AccountFetcher.fetch_exchange_account(attrs, exchange), {:ok, transaction} <- get_or_insert(from, to, exchange, attrs), - _ <- link(transaction) do + _ <- link(transaction, attrs["originator"]) do process_with_transaction(transaction) else error when is_atom(error) -> {:error, error} @@ -73,7 +73,7 @@ defmodule EWallet.TransactionGate do exchange_wallet_address: exchange[:exchange_wallet_address], metadata: attrs["metadata"] || %{}, encrypted_metadata: attrs["encrypted_metadata"] || %{}, - payload: attrs, + payload: Map.delete(attrs, "originator"), type: Transaction.internal() }) end @@ -99,15 +99,15 @@ defmodule EWallet.TransactionGate do Transaction.fail(transaction, code, description) end - defp link(%Transaction{from_account_uuid: account_uuid, to_user_uuid: user_uuid}) + defp link(%Transaction{from_account_uuid: account_uuid, to_user_uuid: user_uuid}, originator) when not is_nil(account_uuid) and not is_nil(user_uuid) do - AccountUser.link(account_uuid, user_uuid) + AccountUser.link(account_uuid, user_uuid, originator) end - defp link(%Transaction{from_user_uuid: user_uuid, to_account_uuid: account_uuid}) + defp link(%Transaction{from_user_uuid: user_uuid, to_account_uuid: account_uuid}, originator) when not is_nil(account_uuid) and not is_nil(user_uuid) do - AccountUser.link(account_uuid, user_uuid) + AccountUser.link(account_uuid, user_uuid, originator) end - defp link(_), do: nil + defp link(_, _), do: nil end diff --git a/apps/ewallet/lib/ewallet/web/inviter.ex b/apps/ewallet/lib/ewallet/web/inviter.ex index 160787577..31858eb2a 100644 --- a/apps/ewallet/lib/ewallet/web/inviter.ex +++ b/apps/ewallet/lib/ewallet/web/inviter.ex @@ -15,7 +15,7 @@ defmodule EWallet.Web.Inviter do with {:ok, user} <- get_or_insert_user(email, password, :self), {:ok, invite} <- Invite.generate(user, preload: :user, success_url: success_url), {:ok, account} <- Account.fetch_master_account(), - {:ok, _account_user} <- AccountUser.link(account.uuid, user.uuid) do + {:ok, _account_user} <- AccountUser.link(account.uuid, user.uuid, user) do send_email(invite, verification_url, create_email_func) else {:error, error} -> diff --git a/apps/ewallet/test/ewallet/gates/transaction_consumption_confirmer_gate_test.exs b/apps/ewallet/test/ewallet/gates/transaction_consumption_confirmer_gate_test.exs index ef62be885..3da20be2c 100644 --- a/apps/ewallet/test/ewallet/gates/transaction_consumption_confirmer_gate_test.exs +++ b/apps/ewallet/test/ewallet/gates/transaction_consumption_confirmer_gate_test.exs @@ -11,6 +11,7 @@ defmodule EWallet.TransactionConsumptionConfirmerGateTest do } alias EWalletDB.{AccountUser, TransactionConsumption, TransactionRequest, User} + alias EWalletConfig.System setup do {:ok, pid} = TestEndpoint.start_link() @@ -55,7 +56,7 @@ defmodule EWallet.TransactionConsumptionConfirmerGateTest do describe "confirm/3 with Account" do test "confirms the consumption if approved as account", meta do initialize_wallet(meta.sender_wallet, 200_000, meta.token) - {:ok, _} = AccountUser.link(meta.account.uuid, meta.receiver.uuid) + {:ok, _} = AccountUser.link(meta.account.uuid, meta.receiver.uuid, %System{}) transaction_request = insert( @@ -170,7 +171,7 @@ defmodule EWallet.TransactionConsumptionConfirmerGateTest do test "confirms a user's consumption if created and approved as account", meta do initialize_wallet(meta.sender_wallet, 200_000, meta.token) - {:ok, _} = AccountUser.link(meta.account.uuid, meta.receiver.uuid) + {:ok, _} = AccountUser.link(meta.account.uuid, meta.receiver.uuid, %System{}) {res, request} = TransactionRequestGate.create(%{ @@ -248,7 +249,7 @@ defmodule EWallet.TransactionConsumptionConfirmerGateTest do test "fails to confirm the consumption if expired", meta do initialize_wallet(meta.sender_wallet, 200_000, meta.token) - {:ok, _} = AccountUser.link(meta.account.uuid, meta.receiver.uuid) + {:ok, _} = AccountUser.link(meta.account.uuid, meta.receiver.uuid, %System{}) transaction_request = insert( @@ -289,7 +290,7 @@ defmodule EWallet.TransactionConsumptionConfirmerGateTest do test "rejects the consumption if not confirmed as account", meta do initialize_wallet(meta.sender_wallet, 200_000, meta.token) - {:ok, _} = AccountUser.link(meta.account.uuid, meta.receiver.uuid) + {:ok, _} = AccountUser.link(meta.account.uuid, meta.receiver.uuid, %System{}) transaction_request = insert( @@ -330,7 +331,7 @@ defmodule EWallet.TransactionConsumptionConfirmerGateTest do test "allows only one confirmation with two confirms at the same time", meta do initialize_wallet(meta.sender_wallet, 1_000_000, meta.token) - {:ok, _} = AccountUser.link(meta.account.uuid, meta.receiver.uuid) + {:ok, _} = AccountUser.link(meta.account.uuid, meta.receiver.uuid, %System{}) request = insert( diff --git a/apps/ewallet/test/ewallet/gates/transaction_request_gate_test.exs b/apps/ewallet/test/ewallet/gates/transaction_request_gate_test.exs index 1a6a5bd1b..ab59a44d2 100644 --- a/apps/ewallet/test/ewallet/gates/transaction_request_gate_test.exs +++ b/apps/ewallet/test/ewallet/gates/transaction_request_gate_test.exs @@ -2,6 +2,7 @@ defmodule EWallet.TransactionRequestGateTest do use EWallet.LocalLedgerCase, async: true alias EWallet.TransactionRequestGate alias EWalletDB.{AccountUser, Token, TransactionRequest, User, Wallet} + alias EWalletConfig.System setup do {:ok, user} = :user |> params_for() |> User.insert() @@ -107,7 +108,7 @@ defmodule EWallet.TransactionRequestGateTest do end test "with valid account_id, valid user and a valid address", meta do - {:ok, _} = AccountUser.link(meta.account.uuid, meta.user.uuid) + {:ok, _} = AccountUser.link(meta.account.uuid, meta.user.uuid, %System{}) {res, request} = TransactionRequestGate.create(%{ @@ -187,7 +188,7 @@ defmodule EWallet.TransactionRequestGateTest do end test "with valid provider_user_id and no address", meta do - {:ok, _} = AccountUser.link(meta.account.uuid, meta.user.uuid) + {:ok, _} = AccountUser.link(meta.account.uuid, meta.user.uuid, %System{}) {res, request} = TransactionRequestGate.create(%{ @@ -308,7 +309,7 @@ defmodule EWallet.TransactionRequestGateTest do describe "create/2 with %User{}" do test "creates a transaction request with all the params", meta do - {:ok, _} = AccountUser.link(meta.account.uuid, meta.user.uuid) + {:ok, _} = AccountUser.link(meta.account.uuid, meta.user.uuid, %System{}) {:ok, request} = TransactionRequestGate.create(meta.user, %{ diff --git a/apps/ewallet_db/config/config.exs b/apps/ewallet_db/config/config.exs index d8d29c2ea..a3f16ca96 100644 --- a/apps/ewallet_db/config/config.exs +++ b/apps/ewallet_db/config/config.exs @@ -6,7 +6,8 @@ audits = %{ EWalletDB.Invite => "invite", EWalletDB.Key => "key", EWalletDB.ForgetPasswordRequest => "forget_password_request", - EWalletDB.UpdateEmailRequest => "update_email_request" + EWalletDB.UpdateEmailRequest => "update_email_request", + EWalletDB.AccountUser => "account_user" } config :ewallet_db, diff --git a/apps/ewallet_db/lib/ewallet_db/account_user.ex b/apps/ewallet_db/lib/ewallet_db/account_user.ex index a3f550c71..d54fb95e9 100644 --- a/apps/ewallet_db/lib/ewallet_db/account_user.ex +++ b/apps/ewallet_db/lib/ewallet_db/account_user.ex @@ -6,11 +6,15 @@ defmodule EWalletDB.AccountUser do use Arc.Ecto.Schema import Ecto.Changeset alias Ecto.UUID - alias EWalletDB.{Account, AccountUser, Repo, User} + alias EWalletDB.{Audit, Account, AccountUser, User} + + alias EWalletConfig.Types.VirtualStruct @primary_key {:uuid, UUID, autogenerate: true} schema "account_user" do + field(:originator, VirtualStruct, virtual: true) + belongs_to( :account, Account, @@ -33,8 +37,8 @@ defmodule EWalletDB.AccountUser do @spec changeset(account :: %AccountUser{}, attrs :: map()) :: Ecto.Changeset.t() defp changeset(%AccountUser{} = account, attrs) do account - |> cast(attrs, [:account_uuid, :user_uuid]) - |> validate_required([:account_uuid, :user_uuid]) + |> cast(attrs, [:account_uuid, :user_uuid, :originator]) + |> validate_required([:account_uuid, :user_uuid, :originator]) |> unique_constraint(:account_uuid, name: :account_user_account_uuid_user_uuid_index) |> assoc_constraint(:account) |> assoc_constraint(:user) @@ -46,13 +50,21 @@ defmodule EWalletDB.AccountUser do %AccountUser{} |> changeset(attrs) - |> Repo.insert(opts) + |> Audit.insert_record_with_audit(opts) + |> case do + {:ok, result} -> + {:ok, result.record} + + error -> + error + end end - def link(account_uuid, user_uuid) do + def link(account_uuid, user_uuid, originator) do insert(%{ account_uuid: account_uuid, - user_uuid: user_uuid + user_uuid: user_uuid, + originator: originator }) end end diff --git a/apps/ewallet_db/lib/ewallet_db/audit.ex b/apps/ewallet_db/lib/ewallet_db/audit.ex index 910231287..398a9b88e 100644 --- a/apps/ewallet_db/lib/ewallet_db/audit.ex +++ b/apps/ewallet_db/lib/ewallet_db/audit.ex @@ -116,8 +116,8 @@ defmodule EWalletDB.Audit do | {:error, any()} | {:error, :no_originator_given} | {:error, Multi.name(), any(), %{optional(Multi.name()) => any()}} - def insert_record_with_audit(changeset, multi \\ Multi.new()) do - perform(:insert, changeset, multi) + def insert_record_with_audit(changeset, opts \\ [], multi \\ Multi.new()) do + perform(:insert, changeset, multi, opts) end @spec update_record_with_audit(%Changeset{}, Multi.t()) :: @@ -125,13 +125,13 @@ defmodule EWalletDB.Audit do | {:error, any()} | {:error, :no_originator_given} | {:error, Multi.name(), any(), %{optional(Multi.name()) => any()}} - def update_record_with_audit(changeset, multi \\ Multi.new()) do - perform(:update, changeset, multi) + def update_record_with_audit(changeset, opts \\ [], multi \\ Multi.new()) do + perform(:update, changeset, multi, opts) end - defp perform(action, changeset, multi) do + defp perform(action, changeset, multi, opts) do Multi - |> apply(action, [Multi.new(), :record, changeset]) + |> apply(action, [Multi.new(), :record, changeset, opts]) |> Multi.run(:audit, fn %{record: record} -> action |> build_attrs(changeset, record) @@ -139,6 +139,15 @@ defmodule EWalletDB.Audit do end) |> Multi.append(multi) |> Repo.transaction() + |> case do + {:ok, result} -> + {:ok, result} + + # Only the account insertion should fail. If the wallet insert fails, there is + # something wrong with our code. + {:error, _failed_operation, changeset, _changes_so_far} -> + {:error, changeset} + end end defp insert_audit(attrs) do diff --git a/apps/ewallet_db/lib/ewallet_db/invite.ex b/apps/ewallet_db/lib/ewallet_db/invite.ex index dc35936b9..fc5f693ed 100644 --- a/apps/ewallet_db/lib/ewallet_db/invite.ex +++ b/apps/ewallet_db/lib/ewallet_db/invite.ex @@ -115,6 +115,7 @@ defmodule EWalletDB.Invite do originator: originator }) |> Audit.insert_record_with_audit( + [], # Assign the invite to the user Multi.run(Multi.new(), :user, fn %{record: record} -> {:ok, _user} = diff --git a/apps/ewallet_db/lib/ewallet_db/user.ex b/apps/ewallet_db/lib/ewallet_db/user.ex index 83762e4ae..578aeab44 100644 --- a/apps/ewallet_db/lib/ewallet_db/user.ex +++ b/apps/ewallet_db/lib/ewallet_db/user.ex @@ -321,6 +321,7 @@ defmodule EWalletDB.User do %User{} |> changeset(attrs) |> Audit.insert_record_with_audit( + [], Multi.run(Multi.new(), :wallet, fn %{record: record} -> case User.admin?(record) do true -> {:ok, nil} @@ -329,14 +330,11 @@ defmodule EWalletDB.User do end) ) |> case do - {:ok, result} -> - user = Repo.preload(result.record, [:wallets]) - {:ok, user} + {:ok, %{record: user}} -> + {:ok, Repo.preload(user, [:wallets])} - # Only the account insertion should fail. If the wallet insert fails, there is - # something wrong with our code. - {:error, _failed_operation, changeset, _changes_so_far} -> - {:error, changeset} + error -> + error end end @@ -441,8 +439,8 @@ defmodule EWalletDB.User do {:ok, result} -> {:ok, get(result.record.id)} - {:error, _failed_operation, changeset, _changes_so_far} -> - {:error, changeset} + error -> + error end end diff --git a/apps/ewallet_db/priv/repo/seeds/01_user.exs b/apps/ewallet_db/priv/repo/seeds/01_user.exs index 35608aab9..84d8946ee 100644 --- a/apps/ewallet_db/priv/repo/seeds/01_user.exs +++ b/apps/ewallet_db/priv/repo/seeds/01_user.exs @@ -1,6 +1,7 @@ defmodule EWalletDB.Repo.Seeds.UserSeed do alias EWalletConfig.{System, Helpers.Crypto} alias EWalletDB.{Account, AccountUser, User} + alias EWalletConfig.System @argsline_desc """ This email and password combination is required for logging into the admin panel. @@ -34,7 +35,7 @@ defmodule EWalletDB.Repo.Seeds.UserSeed do nil -> case User.insert(data) do {:ok, user} -> - {:ok, _} = AccountUser.link(data.account_uuid, user.uuid) + {:ok, _} = AccountUser.link(data.account_uuid, user.uuid, %System{}) writer.success(""" ID : #{user.id} diff --git a/apps/ewallet_db/priv/repo/seeds_sample/00_user.exs b/apps/ewallet_db/priv/repo/seeds_sample/00_user.exs index 82ccc3e18..10920ffdf 100644 --- a/apps/ewallet_db/priv/repo/seeds_sample/00_user.exs +++ b/apps/ewallet_db/priv/repo/seeds_sample/00_user.exs @@ -38,7 +38,7 @@ defmodule EWalletDB.Repo.Seeds.UserSampleSeed do case User.insert(data) do {:ok, user} -> :ok = give_token(user, Token.all(), @minimum_token_amount) - {:ok, _} = AccountUser.link(data.account_uuid, user.uuid) + {:ok, _} = AccountUser.link(data.account_uuid, user.uuid, %System{}) writer.success(""" User ID : #{user.id} diff --git a/apps/ewallet_db/priv/repo/seeds_test/01_user.exs b/apps/ewallet_db/priv/repo/seeds_test/01_user.exs index a4797e42e..4933a6730 100644 --- a/apps/ewallet_db/priv/repo/seeds_test/01_user.exs +++ b/apps/ewallet_db/priv/repo/seeds_test/01_user.exs @@ -49,7 +49,7 @@ defmodule EWalletDB.Repo.Seeds.UserSeed do case User.insert(data) do {:ok, user} -> account = Account.get_by(name: data.account_name) - {:ok, _} = AccountUser.link(account.uuid, user.uuid) + {:ok, _} = AccountUser.link(account.uuid, user.uuid, %OriginatorSystem{}) writer.success(""" ID : #{user.id} diff --git a/apps/ewallet_db/test/ewallet_db/account_user_test.exs b/apps/ewallet_db/test/ewallet_db/account_user_test.exs index b5f386739..4d23b28ac 100644 --- a/apps/ewallet_db/test/ewallet_db/account_user_test.exs +++ b/apps/ewallet_db/test/ewallet_db/account_user_test.exs @@ -56,7 +56,9 @@ defmodule EWalletDB.AccountUserTest do test "links an account and a user" do account = insert(:account) user = insert(:user) - {res, account_user} = AccountUser.link(account.uuid, user.uuid) + admin = insert(:admin) + + {res, account_user} = AccountUser.link(account.uuid, user.uuid, admin) assert res == :ok assert account_user.account_uuid == account.uuid @@ -66,8 +68,10 @@ defmodule EWalletDB.AccountUserTest do test "prevents an account and a user from being linked more than once" do account = insert(:account) user = insert(:user) - {:ok, _account_user} = AccountUser.link(account.uuid, user.uuid) - {res, _changeset} = AccountUser.link(account.uuid, user.uuid) + admin = insert(:admin) + + {:ok, _account_user} = AccountUser.link(account.uuid, user.uuid, admin) + {res, _changeset} = AccountUser.link(account.uuid, user.uuid, admin) assert res == :ok assert AccountUser |> Repo.all() |> length() == 1 @@ -77,8 +81,10 @@ defmodule EWalletDB.AccountUserTest do account_1 = insert(:account) account_2 = insert(:account) user = insert(:user) - {res_1, _account_user} = AccountUser.link(account_1.uuid, user.uuid) - {res_2, _account_user} = AccountUser.link(account_2.uuid, user.uuid) + admin = insert(:admin) + + {res_1, _account_user} = AccountUser.link(account_1.uuid, user.uuid, admin) + {res_2, _account_user} = AccountUser.link(account_2.uuid, user.uuid, admin) assert res_1 == :ok assert res_2 == :ok @@ -90,9 +96,10 @@ defmodule EWalletDB.AccountUserTest do account = insert(:account) user_1 = insert(:user) user_2 = insert(:user) + admin = insert(:admin) - {res_1, _account_user} = AccountUser.link(account.uuid, user_1.uuid) - {res_2, _account_user} = AccountUser.link(account.uuid, user_2.uuid) + {res_1, _account_user} = AccountUser.link(account.uuid, user_1.uuid, admin) + {res_2, _account_user} = AccountUser.link(account.uuid, user_2.uuid, admin) assert res_1 == :ok assert res_2 == :ok diff --git a/apps/ewallet_db/test/ewallet_db/audit_test.exs b/apps/ewallet_db/test/ewallet_db/audit_test.exs index 16769ea69..811fec743 100644 --- a/apps/ewallet_db/test/ewallet_db/audit_test.exs +++ b/apps/ewallet_db/test/ewallet_db/audit_test.exs @@ -170,7 +170,7 @@ defmodule EWalletDB.AuditTest do end) {res, %{audit: audit, record: record, wow_user: wow_user}} = - Audit.insert_record_with_audit(changeset, multi) + Audit.insert_record_with_audit(changeset, [], multi) assert res == :ok @@ -233,7 +233,7 @@ defmodule EWalletDB.AuditTest do end) {res, %{audit: audit, record: record, wow_user: _}} = - Audit.update_record_with_audit(changeset, multi) + Audit.update_record_with_audit(changeset, [], multi) assert res == :ok diff --git a/apps/ewallet_db/test/support/factory.ex b/apps/ewallet_db/test/support/factory.ex index 1dd477b8b..d803877dd 100644 --- a/apps/ewallet_db/test/support/factory.ex +++ b/apps/ewallet_db/test/support/factory.ex @@ -224,7 +224,8 @@ defmodule EWalletDB.Factory do def account_user_factory do %AccountUser{ account_uuid: Account.get_master_account().uuid, - user_uuid: insert(:user).uuid + user_uuid: insert(:user).uuid, + originator: insert(:admin) } end From a367001d66340fec60a88c27a906a4021c68146f Mon Sep 17 00:00:00 2001 From: Thibault Denizet Date: Wed, 21 Nov 2018 22:53:06 +0700 Subject: [PATCH 02/23] Add Auditable to all schemas --- apps/ewallet_db/config/config.exs | 6 +- apps/ewallet_db/lib/ewallet_db/account.ex | 2 + .../ewallet_db/lib/ewallet_db/account_user.ex | 7 -- apps/ewallet_db/lib/ewallet_db/api_key.ex | 2 + apps/ewallet_db/lib/ewallet_db/audit.ex | 38 +++++--- apps/ewallet_db/lib/ewallet_db/auditable.ex | 43 +++++++++ apps/ewallet_db/lib/ewallet_db/auth_token.ex | 2 + apps/ewallet_db/lib/ewallet_db/category.ex | 12 ++- .../lib/ewallet_db/exchange_pair.ex | 17 ++-- .../lib/ewallet_db/forget_password_request.ex | 2 + apps/ewallet_db/lib/ewallet_db/invite.ex | 14 +-- apps/ewallet_db/lib/ewallet_db/key.ex | 2 + apps/ewallet_db/lib/ewallet_db/membership.ex | 2 + apps/ewallet_db/lib/ewallet_db/mint.ex | 44 +++++---- apps/ewallet_db/lib/ewallet_db/role.ex | 2 + apps/ewallet_db/lib/ewallet_db/token.ex | 2 + apps/ewallet_db/lib/ewallet_db/transaction.ex | 96 ++++++++++++------- .../lib/ewallet_db/transaction_consumption.ex | 3 + .../lib/ewallet_db/transaction_request.ex | 83 +++++++++------- .../lib/ewallet_db/update_email_request.ex | 2 + apps/ewallet_db/lib/ewallet_db/user.ex | 39 ++++---- apps/ewallet_db/lib/ewallet_db/wallet.ex | 9 +- .../test/ewallet_db/account_test.exs | 4 +- .../test/ewallet_db/api_key_test.exs | 3 +- .../ewallet_db/test/ewallet_db/audit_test.exs | 82 +++++++++------- .../test/ewallet_db/category_test.exs | 4 +- .../test/ewallet_db/exchange_pair_test.exs | 12 ++- apps/ewallet_db/test/ewallet_db/role_test.exs | 4 +- .../ewallet_db/transaction_request_test.exs | 19 ++-- .../test/ewallet_db/transaction_test.exs | 14 ++- apps/ewallet_db/test/ewallet_db/user_test.exs | 25 +++-- apps/ewallet_db/test/support/factory.ex | 64 ++++++++----- apps/ewallet_db/test/support/schema_case.ex | 16 ++-- 33 files changed, 428 insertions(+), 248 deletions(-) create mode 100644 apps/ewallet_db/lib/ewallet_db/auditable.ex diff --git a/apps/ewallet_db/config/config.exs b/apps/ewallet_db/config/config.exs index a3f16ca96..54dd772e4 100644 --- a/apps/ewallet_db/config/config.exs +++ b/apps/ewallet_db/config/config.exs @@ -7,7 +7,11 @@ audits = %{ EWalletDB.Key => "key", EWalletDB.ForgetPasswordRequest => "forget_password_request", EWalletDB.UpdateEmailRequest => "update_email_request", - EWalletDB.AccountUser => "account_user" + EWalletDB.AccountUser => "account_user", + EWalletDB.Transaction => "transaction", + EWalletDB.Mint => "mint", + EWalletDB.TransactionRequest => "transaction_request", + EWalletDB.TransactionConsumption => "transaction_consumption" } config :ewallet_db, diff --git a/apps/ewallet_db/lib/ewallet_db/account.ex b/apps/ewallet_db/lib/ewallet_db/account.ex index f29db85c6..a17db129c 100644 --- a/apps/ewallet_db/lib/ewallet_db/account.ex +++ b/apps/ewallet_db/lib/ewallet_db/account.ex @@ -5,6 +5,7 @@ defmodule EWalletDB.Account do use Ecto.Schema use Arc.Ecto.Schema use EWalletConfig.Types.ExternalID + use EWalletDB.Auditable import Ecto.{Changeset, Query} import EWalletDB.Helpers.Preloader import EWalletDB.AccountValidator @@ -97,6 +98,7 @@ defmodule EWalletDB.Account do ) timestamps() + auditable() end @spec changeset(%Account{}, map()) :: Ecto.Changeset.t() diff --git a/apps/ewallet_db/lib/ewallet_db/account_user.ex b/apps/ewallet_db/lib/ewallet_db/account_user.ex index d54fb95e9..15a03b4e9 100644 --- a/apps/ewallet_db/lib/ewallet_db/account_user.ex +++ b/apps/ewallet_db/lib/ewallet_db/account_user.ex @@ -51,13 +51,6 @@ defmodule EWalletDB.AccountUser do %AccountUser{} |> changeset(attrs) |> Audit.insert_record_with_audit(opts) - |> case do - {:ok, result} -> - {:ok, result.record} - - error -> - error - end end def link(account_uuid, user_uuid, originator) do diff --git a/apps/ewallet_db/lib/ewallet_db/api_key.ex b/apps/ewallet_db/lib/ewallet_db/api_key.ex index 263c3ff4e..890d5c9f4 100644 --- a/apps/ewallet_db/lib/ewallet_db/api_key.ex +++ b/apps/ewallet_db/lib/ewallet_db/api_key.ex @@ -5,6 +5,7 @@ defmodule EWalletDB.APIKey do use Ecto.Schema use EWalletDB.SoftDelete use EWalletConfig.Types.ExternalID + use EWalletDB.Auditable import Ecto.Changeset alias Ecto.UUID alias EWalletConfig.Helpers.Crypto @@ -39,6 +40,7 @@ defmodule EWalletDB.APIKey do field(:enabled, :boolean, default: true) timestamps() soft_delete() + auditable() end defp changeset(%APIKey{} = key, attrs) do diff --git a/apps/ewallet_db/lib/ewallet_db/audit.ex b/apps/ewallet_db/lib/ewallet_db/audit.ex index 398a9b88e..5870f61f0 100644 --- a/apps/ewallet_db/lib/ewallet_db/audit.ex +++ b/apps/ewallet_db/lib/ewallet_db/audit.ex @@ -111,25 +111,34 @@ defmodule EWalletDB.Audit do end end - @spec insert_record_with_audit(%Changeset{}, Multi.t()) :: + @spec insert_record_with_audit(%Changeset{}, Keyword.t(), Multi.t()) :: {:ok, any()} | {:error, any()} | {:error, :no_originator_given} | {:error, Multi.name(), any(), %{optional(Multi.name()) => any()}} def insert_record_with_audit(changeset, opts \\ [], multi \\ Multi.new()) do - perform(:insert, changeset, multi, opts) + :insert + |> perform(changeset, opts, multi) + |> handle_perform_result() end - @spec update_record_with_audit(%Changeset{}, Multi.t()) :: + @spec update_record_with_audit(%Changeset{}, Keyword.t(), Multi.t()) :: {:ok, any()} | {:error, any()} | {:error, :no_originator_given} | {:error, Multi.name(), any(), %{optional(Multi.name()) => any()}} def update_record_with_audit(changeset, opts \\ [], multi \\ Multi.new()) do - perform(:update, changeset, multi, opts) + :update + |> perform(changeset, opts, multi) + |> handle_perform_result() end - defp perform(action, changeset, multi, opts) do + @spec perform(Atom.t(), %Changeset{}, Keyword.t(), Multi.t()) :: + {:ok, any()} + | {:error, any()} + | {:error, :no_originator_given} + | {:error, Multi.name(), any(), %{optional(Multi.name()) => any()}} + def perform(action, changeset, opts \\ [], multi \\ Multi.new()) do Multi |> apply(action, [Multi.new(), :record, changeset, opts]) |> Multi.run(:audit, fn %{record: record} -> @@ -139,15 +148,16 @@ defmodule EWalletDB.Audit do end) |> Multi.append(multi) |> Repo.transaction() - |> case do - {:ok, result} -> - {:ok, result} - - # Only the account insertion should fail. If the wallet insert fails, there is - # something wrong with our code. - {:error, _failed_operation, changeset, _changes_so_far} -> - {:error, changeset} - end + end + + defp handle_perform_result({:ok, %{record: record}}) do + {:ok, record} + end + + # Only the account insertion should fail. If the wallet insert fails, there is + # something wrong with our code. + defp handle_perform_result({:error, _failed_operation, changeset, _changes_so_far}) do + {:error, changeset} end defp insert_audit(attrs) do diff --git a/apps/ewallet_db/lib/ewallet_db/auditable.ex b/apps/ewallet_db/lib/ewallet_db/auditable.ex new file mode 100644 index 000000000..3b8d1367b --- /dev/null +++ b/apps/ewallet_db/lib/ewallet_db/auditable.ex @@ -0,0 +1,43 @@ +defmodule EWalletDB.Auditable do + @moduledoc """ + Allows audit for Ecto records. + """ + import Ecto.Changeset + alias EWalletDB.Audit + alias EWalletConfig.Types.VirtualStruct + + @doc false + defmacro __using__(_) do + quote do + import EWalletDB.Auditable + alias EWalletDB.Auditable + end + end + + @doc """ + A macro that adds the `:originator` virtual field to a schema. + """ + defmacro auditable do + quote do + field(:originator, VirtualStruct, virtual: true) + end + end + + @doc """ + Prepares a changeset for audit. + """ + def cast_and_validate_required_for_audit(record, attrs, cast \\ [], required \\ []) do + record + |> Map.delete(:originator) + |> cast(attrs, [:originator | cast]) + |> validate_required([:originator | required]) + end + + def insert_with_audit(changeset, opts \\ [], multi \\ Multi.new()) do + Audit.insert_record_with_audit(changeset, opts, multi) + end + + def update_with_audit(changeset, opts \\ [], multi \\ Multi.new()) do + Audit.update_record_with_audit(changeset, opts, multi) + end +end diff --git a/apps/ewallet_db/lib/ewallet_db/auth_token.ex b/apps/ewallet_db/lib/ewallet_db/auth_token.ex index ddfb7b938..78aa51ef9 100644 --- a/apps/ewallet_db/lib/ewallet_db/auth_token.ex +++ b/apps/ewallet_db/lib/ewallet_db/auth_token.ex @@ -4,6 +4,7 @@ defmodule EWalletDB.AuthToken do """ use Ecto.Schema use EWalletConfig.Types.ExternalID + use EWalletDB.Auditable import Ecto.Changeset import Ecto.Query, only: [from: 2] alias Ecto.UUID @@ -37,6 +38,7 @@ defmodule EWalletDB.AuthToken do field(:expired, :boolean) timestamps() + auditable() end defp changeset(%AuthToken{} = token, attrs) do diff --git a/apps/ewallet_db/lib/ewallet_db/category.ex b/apps/ewallet_db/lib/ewallet_db/category.ex index 9b8462bf0..2c6616ed3 100644 --- a/apps/ewallet_db/lib/ewallet_db/category.ex +++ b/apps/ewallet_db/lib/ewallet_db/category.ex @@ -9,7 +9,8 @@ defmodule EWalletDB.Category do import EWalletDB.Helpers.Preloader alias Ecto.UUID alias EWalletConfig.Helpers.InputAttribute - alias EWalletDB.{Account, Repo} + alias EWalletDB.{Audit, Account, Repo} + alias EWalletConfig.Types.VirtualStruct @primary_key {:uuid, UUID, autogenerate: true} @@ -18,6 +19,7 @@ defmodule EWalletDB.Category do field(:name, :string) field(:description, :string) + field(:originator, VirtualStruct, virtual: true) timestamps() soft_delete() @@ -32,8 +34,8 @@ defmodule EWalletDB.Category do defp changeset(category, attrs) do category - |> cast(attrs, [:name, :description]) - |> validate_required(:name) + |> cast(attrs, [:name, :description, :originator]) + |> validate_required([:name, :originator]) |> unique_constraint(:name) |> put_accounts(attrs, :account_ids) end @@ -95,7 +97,7 @@ defmodule EWalletDB.Category do def insert(attrs) do %__MODULE__{} |> changeset(attrs) - |> Repo.insert() + |> Audit.insert_record_with_audit() end @doc """ @@ -105,7 +107,7 @@ defmodule EWalletDB.Category do def update(category, attrs) do category |> changeset(attrs) - |> Repo.update() + |> Audit.update_record_with_audit() end @doc """ diff --git a/apps/ewallet_db/lib/ewallet_db/exchange_pair.ex b/apps/ewallet_db/lib/ewallet_db/exchange_pair.ex index 544d413fa..7a17647de 100644 --- a/apps/ewallet_db/lib/ewallet_db/exchange_pair.ex +++ b/apps/ewallet_db/lib/ewallet_db/exchange_pair.ex @@ -21,6 +21,7 @@ defmodule EWalletDB.ExchangePair do """ use Ecto.Schema use EWalletDB.SoftDelete + use EWalletDB.Auditable use EWalletConfig.Types.ExternalID import Ecto.Changeset import EWalletDB.Helpers.Preloader @@ -53,12 +54,16 @@ defmodule EWalletDB.ExchangePair do field(:rate, :float) timestamps() soft_delete() + auditable() end defp changeset(exchange_pair, attrs) do exchange_pair - |> cast(attrs, [:from_token_uuid, :to_token_uuid, :rate, :deleted_at]) - |> validate_required([:from_token_uuid, :to_token_uuid, :rate]) + |> cast_and_validate_required_for_audit( + attrs, + [:from_token_uuid, :to_token_uuid, :rate, :deleted_at], + [:from_token_uuid, :to_token_uuid, :rate] + ) |> validate_different_values(:from_token_uuid, :to_token_uuid) |> validate_immutable(:from_token_uuid) |> validate_immutable(:to_token_uuid) @@ -73,7 +78,7 @@ defmodule EWalletDB.ExchangePair do defp restore_changeset(exchange_pair, attrs) do exchange_pair - |> cast(attrs, [:deleted_at]) + |> cast_and_validate_required_for_audit(attrs, [:deleted_at]) |> unique_constraint( :deleted_at, name: "exchange_pair_from_token_uuid_to_token_uuid_index" @@ -121,7 +126,7 @@ defmodule EWalletDB.ExchangePair do def insert(attrs) do %__MODULE__{} |> changeset(attrs) - |> Repo.insert() + |> insert_with_audit() end @doc """ @@ -131,7 +136,7 @@ defmodule EWalletDB.ExchangePair do def update(exchange_pair, attrs) do exchange_pair |> changeset(attrs) - |> Repo.update() + |> update_with_audit() end @doc """ @@ -169,7 +174,7 @@ defmodule EWalletDB.ExchangePair do def touch(exchange_pair) do exchange_pair |> change(updated_at: NaiveDateTime.utc_now()) - |> Repo.update() + |> update_with_audit() end @doc """ diff --git a/apps/ewallet_db/lib/ewallet_db/forget_password_request.ex b/apps/ewallet_db/lib/ewallet_db/forget_password_request.ex index e3a8362ec..ef817b08d 100644 --- a/apps/ewallet_db/lib/ewallet_db/forget_password_request.ex +++ b/apps/ewallet_db/lib/ewallet_db/forget_password_request.ex @@ -3,6 +3,7 @@ defmodule EWalletDB.ForgetPasswordRequest do Ecto Schema representing a password reset request. """ use Ecto.Schema + use EWalletDB.Auditable import Ecto.{Changeset, Query} alias Ecto.UUID alias EWalletConfig.Helpers.Crypto @@ -24,6 +25,7 @@ defmodule EWalletDB.ForgetPasswordRequest do ) timestamps() + auditable() end defp changeset(changeset, attrs) do diff --git a/apps/ewallet_db/lib/ewallet_db/invite.ex b/apps/ewallet_db/lib/ewallet_db/invite.ex index fc5f693ed..eadfb0583 100644 --- a/apps/ewallet_db/lib/ewallet_db/invite.ex +++ b/apps/ewallet_db/lib/ewallet_db/invite.ex @@ -32,12 +32,14 @@ defmodule EWalletDB.Invite do defp changeset_insert(changeset, attrs) do changeset + |> Map.delete(:originator) |> cast(attrs, [:user_uuid, :token, :success_url, :originator]) |> validate_required([:user_uuid, :token, :originator]) end defp changeset_accept(changeset, attrs) do changeset + |> Map.delete(:originator) |> cast(attrs, [:verified_at, :originator]) |> validate_required([:verified_at, :originator]) end @@ -125,8 +127,8 @@ defmodule EWalletDB.Invite do end) ) |> case do - {:ok, result} -> - {:ok, Repo.preload(result.record, opts[:preload] || [])} + {:ok, invite} -> + {:ok, Repo.preload(invite, opts[:preload] || [])} {:error, _failed_operation, changeset, _changes_so_far} -> {:error, changeset} @@ -143,8 +145,8 @@ defmodule EWalletDB.Invite do {:ok, _user} <- User.update(invite.user, attrs), invite_attrs <- %{verified_at: NaiveDateTime.utc_now(), originator: invite.user}, changeset <- changeset_accept(invite, invite_attrs), - {:ok, result} <- Audit.update_record_with_audit(changeset) do - {:ok, result.record} + {:ok, invite} <- Audit.update_record_with_audit(changeset) do + {:ok, invite} else {:error, _failed_operation, changeset, _changes_so_far} -> {:error, changeset} @@ -170,8 +172,8 @@ defmodule EWalletDB.Invite do {:ok, _user} <- User.update(user, user_attrs), invite_attrs <- %{verified_at: NaiveDateTime.utc_now(), originator: invite.user}, changeset <- changeset_accept(invite, invite_attrs), - {:ok, result} <- Audit.update_record_with_audit(changeset) do - {:ok, result.record} + {:ok, invite} <- Audit.update_record_with_audit(changeset) do + {:ok, invite} else {:error, _failed_operation, changeset, _changes_so_far} -> {:error, changeset} diff --git a/apps/ewallet_db/lib/ewallet_db/key.ex b/apps/ewallet_db/lib/ewallet_db/key.ex index 96ad32e8d..c848d53d9 100644 --- a/apps/ewallet_db/lib/ewallet_db/key.ex +++ b/apps/ewallet_db/lib/ewallet_db/key.ex @@ -5,6 +5,7 @@ defmodule EWalletDB.Key do use Ecto.Schema use EWalletDB.SoftDelete use EWalletConfig.Types.ExternalID + use EWalletDB.Auditable import Ecto.{Changeset, Query} alias Ecto.UUID alias EWalletConfig.Helpers.Crypto @@ -33,6 +34,7 @@ defmodule EWalletDB.Key do field(:enabled, :boolean, default: true) timestamps() soft_delete() + auditable() end defp insert_changeset(%Key{} = key, attrs) do diff --git a/apps/ewallet_db/lib/ewallet_db/membership.ex b/apps/ewallet_db/lib/ewallet_db/membership.ex index 7c2fe2516..0a154e72e 100644 --- a/apps/ewallet_db/lib/ewallet_db/membership.ex +++ b/apps/ewallet_db/lib/ewallet_db/membership.ex @@ -3,6 +3,7 @@ defmodule EWalletDB.Membership do Ecto Schema representing user memberships. """ use Ecto.Schema + use EWalletDB.Auditable import Ecto.Changeset import Ecto.Query, except: [update: 2] alias Ecto.UUID @@ -36,6 +37,7 @@ defmodule EWalletDB.Membership do ) timestamps() + auditable() end def changeset(%Membership{} = membership, attrs) do diff --git a/apps/ewallet_db/lib/ewallet_db/mint.ex b/apps/ewallet_db/lib/ewallet_db/mint.ex index 543897eec..bc0df4d85 100644 --- a/apps/ewallet_db/lib/ewallet_db/mint.ex +++ b/apps/ewallet_db/lib/ewallet_db/mint.ex @@ -7,7 +7,8 @@ defmodule EWalletDB.Mint do import Ecto.{Query, Changeset} import EWalletDB.Helpers.Preloader alias Ecto.UUID - alias EWalletDB.{Account, Mint, Repo, Token, Transaction} + alias EWalletDB.{Account, Audit, Mint, Repo, Token, Transaction} + alias EWalletConfig.Types.VirtualStruct @primary_key {:uuid, Ecto.UUID, autogenerate: true} @@ -17,6 +18,7 @@ defmodule EWalletDB.Mint do field(:description, :string) field(:amount, EWalletConfig.Types.Integer) field(:confirmed, :boolean, default: false) + field(:originator, VirtualStruct, virtual: true) belongs_to( :token, @@ -47,8 +49,16 @@ defmodule EWalletDB.Mint do defp changeset(%Mint{} = mint, attrs) do mint - |> cast(attrs, [:description, :amount, :account_uuid, :token_uuid, :confirmed]) - |> validate_required([:amount, :token_uuid]) + |> Map.delete(:originator) + |> cast(attrs, [ + :description, + :amount, + :account_uuid, + :token_uuid, + :confirmed, + :originator + ]) + |> validate_required([:amount, :token_uuid, :originator]) |> validate_number( :amount, greater_than: 0, @@ -64,8 +74,9 @@ defmodule EWalletDB.Mint do defp update_changeset(%Mint{} = mint, attrs) do mint - |> cast(attrs, [:transaction_uuid]) - |> validate_required([:transaction_uuid]) + |> Map.delete(:originator) + |> cast(attrs, [:transaction_uuid, :originator]) + |> validate_required([:transaction_uuid, :originator]) |> assoc_constraint(:transaction) end @@ -108,7 +119,7 @@ defmodule EWalletDB.Mint do def insert(attrs) do %Mint{} |> changeset(attrs) - |> Repo.insert() + |> Audit.insert_record_with_audit() end @doc """ @@ -116,15 +127,9 @@ defmodule EWalletDB.Mint do """ @spec update(mint :: %Mint{}, attrs :: map()) :: {:ok, %Mint{}} | {:error, Ecto.Changeset.t()} def update(%Mint{} = mint, attrs) do - changeset = update_changeset(mint, attrs) - - case Repo.update(changeset) do - {:ok, mint} -> - {:ok, mint} - - result -> - result - end + mint + |> update_changeset(attrs) + |> Audit.update_record_with_audit() end @doc """ @@ -132,11 +137,14 @@ defmodule EWalletDB.Mint do """ def confirm(%Mint{confirmed: true} = mint), do: mint - def confirm(%Mint{confirmed: false} = mint) do + def confirm(%Mint{confirmed: false} = mint, originator) do {:ok, mint} = mint - |> changeset(%{confirmed: true}) - |> Repo.update() + |> changeset(%{ + confirmed: true, + originator: originator + }) + |> Audit.update_record_with_audit() mint end diff --git a/apps/ewallet_db/lib/ewallet_db/role.ex b/apps/ewallet_db/lib/ewallet_db/role.ex index 0f9ef8e91..d80daa747 100644 --- a/apps/ewallet_db/lib/ewallet_db/role.ex +++ b/apps/ewallet_db/lib/ewallet_db/role.ex @@ -5,6 +5,7 @@ defmodule EWalletDB.Role do use Ecto.Schema use EWalletConfig.Types.ExternalID use EWalletDB.SoftDelete + use EWalletDB.Auditable import Ecto.{Changeset, Query} import EWalletDB.Helpers.Preloader alias Ecto.UUID @@ -28,6 +29,7 @@ defmodule EWalletDB.Role do timestamps() soft_delete() + auditable() end defp changeset(%Role{} = key, attrs) do diff --git a/apps/ewallet_db/lib/ewallet_db/token.ex b/apps/ewallet_db/lib/ewallet_db/token.ex index a627e0b4e..a46535260 100644 --- a/apps/ewallet_db/lib/ewallet_db/token.ex +++ b/apps/ewallet_db/lib/ewallet_db/token.ex @@ -4,6 +4,7 @@ defmodule EWalletDB.Token do """ use Ecto.Schema use EWalletConfig.Types.ExternalID + use EWalletDB.Auditable import Ecto.{Changeset, Query} import EWalletDB.Helpers.Preloader import EWalletDB.Validator @@ -55,6 +56,7 @@ defmodule EWalletDB.Token do ) timestamps() + auditable() end defp changeset(%Token{} = token, attrs) do diff --git a/apps/ewallet_db/lib/ewallet_db/transaction.ex b/apps/ewallet_db/lib/ewallet_db/transaction.ex index fdf2e971e..00c4f174b 100644 --- a/apps/ewallet_db/lib/ewallet_db/transaction.ex +++ b/apps/ewallet_db/lib/ewallet_db/transaction.ex @@ -8,7 +8,8 @@ defmodule EWalletDB.Transaction do import EWalletDB.Validator import EWalletDB.Validator alias Ecto.{Multi, UUID} - alias EWalletDB.{Account, ExchangePair, Repo, Token, Transaction, User, Wallet} + alias EWalletDB.{Account, Audit, ExchangePair, Repo, Token, Transaction, User, Wallet} + alias EWalletConfig.Types.VirtualStruct @pending "pending" @confirmed "confirmed" @@ -50,6 +51,8 @@ defmodule EWalletDB.Transaction do field(:metadata, :map, default: %{}) field(:encrypted_metadata, EWalletConfig.Encrypted.Map, default: %{}) + field(:originator, VirtualStruct, virtual: true) + belongs_to( :from_token, Token, @@ -143,6 +146,7 @@ defmodule EWalletDB.Transaction do defp changeset(%Transaction{} = transaction, attrs) do transaction + |> Map.delete(:originator) |> cast(attrs, [ :idempotency_token, :status, @@ -167,7 +171,8 @@ defmodule EWalletDB.Transaction do :error_code, :error_description, :exchange_pair_uuid, - :calculated_at + :calculated_at, + :originator ]) |> validate_required([ :idempotency_token, @@ -181,7 +186,8 @@ defmodule EWalletDB.Transaction do :to, :from, :metadata, - :encrypted_metadata + :encrypted_metadata, + :originator ]) |> validate_number(:from_amount, less_than: 100_000_000_000_000_000_000_000_000_000_000_000) |> validate_number(:to_amount, less_than: 100_000_000_000_000_000_000_000_000_000_000_000) @@ -209,22 +215,26 @@ defmodule EWalletDB.Transaction do defp confirm_changeset(%Transaction{} = transaction, attrs) do transaction - |> cast(attrs, [:status, :local_ledger_uuid]) - |> validate_required([:status, :local_ledger_uuid]) + |> Map.delete(:originator) + |> cast(attrs, [:status, :local_ledger_uuid, :originator]) + |> validate_required([:status, :local_ledger_uuid, :originator]) |> validate_inclusion(:status, @statuses) end defp fail_changeset(%Transaction{} = transaction, attrs) do transaction + |> Map.delete(:originator) |> cast(attrs, [ :status, :error_code, :error_description, - :error_data + :error_data, + :originator ]) |> validate_required([ :status, - :error_code + :error_code, + :originator ]) |> validate_inclusion(:status, @statuses) end @@ -317,62 +327,74 @@ defmodule EWalletDB.Transaction do opts = [on_conflict: :nothing, conflict_target: :idempotency_token] changeset = changeset(%Transaction{}, attrs) - Multi.new() - |> Multi.insert(:transaction, changeset, opts) - |> Multi.run(:transaction_1, fn %{transaction: transaction} -> - case get(transaction.id, preload: [:from_wallet, :to_wallet, :from_token, :to_token]) do - nil -> - {:ok, get_by_idempotency_token(transaction.idempotency_token)} - - transaction -> - {:ok, transaction} - end - end) - |> Repo.transaction() - |> case do - {:ok, %{transaction: _transaction, transaction_1: nil}} -> - {:error, :inserted_transaction_could_not_be_loaded} - - {:ok, %{transaction: _transaction, transaction_1: transaction_1}} -> - {:ok, transaction_1} - - {:error, _failed_operation, failed_value, _changes_so_far} -> - {:error, failed_value} - end + Audit.perform( + :insert, + changeset, + opts, + Multi.run(Multi.new(), :transaction_1, fn %{record: transaction} -> + case get(transaction.id, preload: [:from_wallet, :to_wallet, :from_token, :to_token]) do + nil -> + {:ok, get_by_idempotency_token(transaction.idempotency_token)} + + transaction -> + {:ok, transaction} + end + end) + ) + |> handle_insert_result() + end + + defp handle_insert_result({:ok, %{record: _transaction, transaction_1: nil}}) do + {:error, :inserted_transaction_could_not_be_loaded} + end + + defp handle_insert_result({:ok, %{record: _transaction, transaction_1: transaction_1}}) do + {:ok, transaction_1} + end + + defp handle_insert_result({:error, _failed_operation, changeset, _changes_so_far}) do + {:error, changeset} end @doc """ Confirms a transaction and saves the ledger's response. """ - def confirm(transaction, local_ledger_uuid) do + def confirm(transaction, local_ledger_uuid, originator) do transaction - |> confirm_changeset(%{status: @confirmed, local_ledger_uuid: local_ledger_uuid}) - |> Repo.update() + |> confirm_changeset(%{ + status: @confirmed, + local_ledger_uuid: local_ledger_uuid, + originator: originator + }) + |> Audit.update_record_with_audit() |> handle_update_result() end @doc """ Sets a transaction as failed and saves the ledger's response. """ - def fail(transaction, error_code, error_description) when is_map(error_description) do + def fail(transaction, error_code, error_description, originator) + when is_map(error_description) do do_fail( %{ status: @failed, error_code: error_code, error_description: nil, - error_data: error_description + error_data: error_description, + originator: originator }, transaction ) end - def fail(transaction, error_code, error_description) do + def fail(transaction, error_code, error_description, originator) do do_fail( %{ status: @failed, error_code: error_code, error_description: error_description, - error_data: nil + error_data: nil, + originator: originator }, transaction ) @@ -387,7 +409,7 @@ defmodule EWalletDB.Transaction do defp do_fail(data, transaction) do transaction |> fail_changeset(data) - |> Repo.update() + |> Audit.update_record_with_audit() |> handle_update_result() end diff --git a/apps/ewallet_db/lib/ewallet_db/transaction_consumption.ex b/apps/ewallet_db/lib/ewallet_db/transaction_consumption.ex index 475837344..805d3268e 100644 --- a/apps/ewallet_db/lib/ewallet_db/transaction_consumption.ex +++ b/apps/ewallet_db/lib/ewallet_db/transaction_consumption.ex @@ -6,6 +6,7 @@ defmodule EWalletDB.TransactionConsumption do use EWalletConfig.Types.ExternalID import Ecto.{Changeset, Query} alias Ecto.UUID + alias EWalletConfig.Types.VirtualStruct alias EWalletDB.{ Account, @@ -57,6 +58,8 @@ defmodule EWalletDB.TransactionConsumption do field(:metadata, :map, default: %{}) field(:encrypted_metadata, EWalletConfig.Encrypted.Map, default: %{}) + field(:originator, VirtualStruct, virtual: true) + belongs_to( :transaction, Transaction, diff --git a/apps/ewallet_db/lib/ewallet_db/transaction_request.ex b/apps/ewallet_db/lib/ewallet_db/transaction_request.ex index dfc0a5868..b17f8264e 100644 --- a/apps/ewallet_db/lib/ewallet_db/transaction_request.ex +++ b/apps/ewallet_db/lib/ewallet_db/transaction_request.ex @@ -7,9 +7,11 @@ defmodule EWalletDB.TransactionRequest do import Ecto.{Changeset, Query} import EWalletDB.Helpers.Preloader alias Ecto.{Changeset, Query, UUID} + alias EWalletConfig.Types.VirtualStruct alias EWalletDB.{ Account, + Audit, Repo, Token, TransactionConsumption, @@ -51,6 +53,8 @@ defmodule EWalletDB.TransactionRequest do field(:metadata, :map, default: %{}) field(:encrypted_metadata, EWalletConfig.Encrypted.Map, default: %{}) + field(:originator, VirtualStruct, virtual: true) + has_many( :consumptions, TransactionConsumption, @@ -111,6 +115,7 @@ defmodule EWalletDB.TransactionRequest do defp changeset(%TransactionRequest{} = transaction_request, attrs) do transaction_request + |> Map.delete(:originator) |> cast(attrs, [ :type, :amount, @@ -128,13 +133,15 @@ defmodule EWalletDB.TransactionRequest do :encrypted_metadata, :allow_amount_override, :exchange_account_uuid, - :exchange_wallet_address + :exchange_wallet_address, + :originator ]) |> validate_required([ :type, :status, :token_uuid, - :wallet_address + :wallet_address, + :originator ]) |> validate_amount_if_disallow_override() |> validate_number(:amount, less_than: 100_000_000_000_000_000_000_000_000_000_000_000) @@ -150,21 +157,24 @@ defmodule EWalletDB.TransactionRequest do defp consumptions_count_changeset(%TransactionRequest{} = transaction_request, attrs) do transaction_request - |> cast(attrs, [:consumptions_count]) - |> validate_required([:consumptions_count]) + |> Map.delete(:originator) + |> cast(attrs, [:consumptions_count, :originator]) + |> validate_required([:consumptions_count, :originator]) end defp expire_changeset(%TransactionRequest{} = transaction_request, attrs) do transaction_request - |> cast(attrs, [:status, :expired_at, :expiration_reason]) - |> validate_required([:status, :expired_at, :expiration_reason]) + |> Map.delete(:originator) + |> cast(attrs, [:status, :expired_at, :expiration_reason, :originator]) + |> validate_required([:status, :expired_at, :expiration_reason, :originator]) |> validate_inclusion(:status, @statuses) end defp touch_changeset(%TransactionRequest{} = transaction_request, attrs) do transaction_request - |> cast(attrs, [:updated_at]) - |> validate_required([:updated_at]) + |> Map.delete(:originator) + |> cast(attrs, [:updated_at, :originator]) + |> validate_required([:updated_at, :originator]) end defp validate_amount_if_disallow_override(changeset) do @@ -252,11 +262,14 @@ defmodule EWalletDB.TransactionRequest do @doc """ Touches a request by updating the `updated_at` field. """ - @spec touch(%TransactionRequest{}) :: {:ok, %TransactionRequest{}} | {:error, map()} - def touch(request) do + @spec touch(%TransactionRequest{}, Map.t()) :: {:ok, %TransactionRequest{}} | {:error, map()} + def touch(request, originator) do request - |> touch_changeset(%{updated_at: NaiveDateTime.utc_now()}) - |> Repo.update() + |> touch_changeset(%{ + updated_at: NaiveDateTime.utc_now(), + originator: originator + }) + |> Audit.update_record_with_audit() end @doc """ @@ -266,7 +279,7 @@ defmodule EWalletDB.TransactionRequest do def insert(attrs) do %TransactionRequest{} |> changeset(attrs) - |> Repo.insert() + |> Audit.insert_record_with_audit() end @doc """ @@ -275,7 +288,7 @@ defmodule EWalletDB.TransactionRequest do def update(%TransactionRequest{} = request, attrs) do request |> changeset(attrs) - |> Repo.update() + |> Audit.update_record_with_audit() end @spec valid?(%TransactionRequest{}) :: true | false @@ -307,29 +320,31 @@ defmodule EWalletDB.TransactionRequest do @doc """ Expires the given request with the specified reason. """ - @spec expire(%TransactionRequest{}) :: {:ok, %TransactionRequest{}} | {:error, map()} - def expire(request, reason \\ "expired_transaction_request") do + @spec expire(%TransactionRequest{}, Map.t(), String.t()) :: + {:ok, %TransactionRequest{}} | {:error, map()} + def expire(request, originator, reason \\ "expired_transaction_request") do request |> expire_changeset(%{ status: @expired, expired_at: NaiveDateTime.utc_now(), - expiration_reason: reason + expiration_reason: reason, + originator: originator }) - |> Repo.update() + |> Audit.update_record_with_audit() end @doc """ Expires the given request if the expiration date is past. """ - @spec expire_if_past_expiration_date(%TransactionRequest{}) :: + @spec expire_if_past_expiration_date(%TransactionRequest{}, Map.t()) :: {:ok, %TransactionRequest{}} | {:error, map()} - def expire_if_past_expiration_date(request) do + def expire_if_past_expiration_date(request, originator) do expired? = request.expiration_date && NaiveDateTime.compare(request.expiration_date, NaiveDateTime.utc_now()) == :lt case expired? do - true -> expire(request) + true -> expire(request, originator) _ -> {:ok, request} end end @@ -337,37 +352,41 @@ defmodule EWalletDB.TransactionRequest do @doc """ Expires the given request if the maximum number of consumptions has been reached. """ - @spec expire_if_max_consumption(%TransactionRequest{}) :: + @spec expire_if_max_consumption(%TransactionRequest{}, Map.t()) :: {:ok, %TransactionRequest{}} | {:error, map()} - def expire_if_max_consumption(request) do + def expire_if_max_consumption(request, originator) do consumptions = TransactionConsumption.all_active_for_request(request.uuid) - request = update_consumptions_count(request, consumptions) + request = update_consumptions_count(request, consumptions, originator) + request = %{request | originator: nil} case max_consumptions_reached?(request, consumptions) do - true -> expire(request, "max_consumptions_reached") - false -> touch(request) + true -> expire(request, originator, "max_consumptions_reached") + false -> touch(request, originator) end end - @spec load_consumptions_count(%TransactionRequest{}) :: Integer.t() - def load_consumptions_count(request) do + @spec load_consumptions_count(%TransactionRequest{}, Map.t()) :: Integer.t() + def load_consumptions_count(request, originator) do case request.consumptions_count do nil -> consumptions = TransactionConsumption.all_active_for_request(request.uuid) - update_consumptions_count(request, consumptions) + update_consumptions_count(request, consumptions, originator) _count -> request end end - @spec update_consumptions_count(%TransactionRequest{}, list(%TransactionConsumption{})) :: + @spec update_consumptions_count(%TransactionRequest{}, list(%TransactionConsumption{}), Map.t()) :: %TransactionRequest{} - defp update_consumptions_count(request, consumptions) do + defp update_consumptions_count(request, consumptions, originator) do {:ok, request} = request - |> consumptions_count_changeset(%{consumptions_count: length(consumptions)}) + |> consumptions_count_changeset(%{ + consumptions_count: length(consumptions), + originator: originator + }) |> Repo.update() request diff --git a/apps/ewallet_db/lib/ewallet_db/update_email_request.ex b/apps/ewallet_db/lib/ewallet_db/update_email_request.ex index 87d962029..3633e852d 100644 --- a/apps/ewallet_db/lib/ewallet_db/update_email_request.ex +++ b/apps/ewallet_db/lib/ewallet_db/update_email_request.ex @@ -3,6 +3,7 @@ defmodule EWalletDB.UpdateEmailRequest do Ecto Schema representing a change email request. """ use Ecto.Schema + use EWalletDB.Auditable import Ecto.{Changeset, Query} import EWalletDB.Validator alias Ecto.UUID @@ -26,6 +27,7 @@ defmodule EWalletDB.UpdateEmailRequest do ) timestamps() + auditable() end defp changeset(changeset, attrs) do diff --git a/apps/ewallet_db/lib/ewallet_db/user.ex b/apps/ewallet_db/lib/ewallet_db/user.ex index 578aeab44..d9f3ce9f0 100644 --- a/apps/ewallet_db/lib/ewallet_db/user.ex +++ b/apps/ewallet_db/lib/ewallet_db/user.ex @@ -5,15 +5,13 @@ defmodule EWalletDB.User do use Arc.Ecto.Schema use Ecto.Schema use EWalletConfig.Types.ExternalID + use EWalletDB.Auditable import Ecto.{Changeset, Query} import EWalletDB.Helpers.Preloader import EWalletDB.Validator - import EWalletDB.Validator alias Ecto.{Multi, UUID} alias EWalletConfig.Helpers.Crypto - alias EWalletConfig.Types.VirtualStruct - alias EWalletDB.{ Account, AccountUser, @@ -41,11 +39,11 @@ defmodule EWalletDB.User do field(:password_confirmation, :string, virtual: true) field(:password_hash, :string) field(:provider_user_id, :string) - field(:originator, VirtualStruct, virtual: true) field(:metadata, :map, default: %{}) field(:encrypted_metadata, EWalletConfig.Encrypted.Map, default: %{}) field(:avatar, EWalletDB.Uploaders.Avatar.Type) field(:enabled, :boolean, default: true) + auditable() belongs_to( :invite, @@ -97,6 +95,7 @@ defmodule EWalletDB.User do password_hash = attrs |> get_attr(:password) |> Crypto.hash_password() changeset + |> Map.delete(:originator) |> cast(attrs, [ :is_admin, :username, @@ -124,6 +123,7 @@ defmodule EWalletDB.User do defp update_user_changeset(user, attrs) do user + |> Map.delete(:originator) |> cast(attrs, [ :username, :full_name, @@ -144,6 +144,7 @@ defmodule EWalletDB.User do defp update_admin_changeset(user, attrs) do user + |> Map.delete(:originator) |> cast(attrs, [ :full_name, :calling_name, @@ -159,6 +160,7 @@ defmodule EWalletDB.User do defp avatar_changeset(user, attrs) do user + |> Map.delete(:originator) |> cast(attrs, [:originator]) |> cast_attachments(attrs, [:avatar]) |> validate_required([:originator]) @@ -168,6 +170,7 @@ defmodule EWalletDB.User do password_hash = attrs |> get_attr(:password) |> Crypto.hash_password() user + |> Map.delete(:originator) |> cast(attrs, [ :password, :password_confirmation, @@ -181,8 +184,9 @@ defmodule EWalletDB.User do defp enable_changeset(%User{} = user, attrs) do user - |> cast(attrs, [:enabled]) - |> validate_required([:enabled]) + |> Map.delete(:originator) + |> cast(attrs, [:enabled, :originator]) + |> validate_required([:enabled, :originator]) end defp get_attr(attrs, atom_field) do @@ -191,6 +195,7 @@ defmodule EWalletDB.User do defp email_changeset(user, attrs) do user + |> Map.delete(:originator) |> cast(attrs, [ :email, :originator @@ -330,7 +335,7 @@ defmodule EWalletDB.User do end) ) |> case do - {:ok, %{record: user}} -> + {:ok, user} -> {:ok, Repo.preload(user, [:wallets])} error -> @@ -363,7 +368,7 @@ defmodule EWalletDB.User do update_user_changeset(user, attrs) end - update_with_audit(changeset) + Audit.update_record_with_audit(changeset) end @doc """ @@ -400,7 +405,7 @@ defmodule EWalletDB.User do defp do_update_password(user, attrs) do user |> password_changeset(attrs) - |> update_with_audit() + |> Audit.update_record_with_audit() end @doc """ @@ -410,7 +415,7 @@ defmodule EWalletDB.User do def update_email(%User{} = user, attrs) do user |> email_changeset(attrs) - |> update_with_audit() + |> Audit.update_record_with_audit() end @doc """ @@ -429,19 +434,7 @@ defmodule EWalletDB.User do user |> avatar_changeset(updated_attrs) - |> update_with_audit() - end - - defp update_with_audit(changeset) do - changeset |> Audit.update_record_with_audit() - |> case do - {:ok, result} -> - {:ok, get(result.record.id)} - - error -> - error - end end @doc """ @@ -689,6 +682,6 @@ defmodule EWalletDB.User do def enable_or_disable(user, attrs) do user |> enable_changeset(attrs) - |> Repo.update() + |> Audit.update_record_with_audit() end end diff --git a/apps/ewallet_db/lib/ewallet_db/wallet.ex b/apps/ewallet_db/lib/ewallet_db/wallet.ex index 5c584a818..88a20946b 100644 --- a/apps/ewallet_db/lib/ewallet_db/wallet.ex +++ b/apps/ewallet_db/lib/ewallet_db/wallet.ex @@ -4,6 +4,7 @@ defmodule EWalletDB.Wallet do """ use Ecto.Schema use EWalletConfig.Types.WalletAddress + use EWalletDB.Auditable import Ecto.{Changeset, Query} import EWalletDB.Validator alias Ecto.UUID @@ -45,6 +46,7 @@ defmodule EWalletDB.Wallet do field(:metadata, :map, default: %{}) field(:encrypted_metadata, EWalletConfig.Encrypted.Map, default: %{}) field(:enabled, :boolean) + auditable() belongs_to( :user, @@ -67,8 +69,11 @@ defmodule EWalletDB.Wallet do defp changeset(%Wallet{} = wallet, attrs) do wallet - |> cast(attrs, @cast_attrs) - |> validate_required([:name, :identifier]) + |> cast_and_validate_required_for_audit( + attrs, + @cast_attrs, + [:name, :identifier] + ) |> validate_format( :identifier, ~r/#{@genesis}|#{@burn}|#{@burn}_.|#{@primary}|#{@secondary}_.*/ diff --git a/apps/ewallet_db/test/ewallet_db/account_test.exs b/apps/ewallet_db/test/ewallet_db/account_test.exs index 367d65a7e..53c3bb28a 100644 --- a/apps/ewallet_db/test/ewallet_db/account_test.exs +++ b/apps/ewallet_db/test/ewallet_db/account_test.exs @@ -85,8 +85,8 @@ defmodule EWalletDB.AccountTest do end describe "update/2" do - test_update_field_ok(Account, :name, insert(:admin)) - test_update_field_ok(Account, :description, insert(:admin)) + test_update_field_ok(Account, :name) + test_update_field_ok(Account, :description) end describe "update/2 with category_ids" do diff --git a/apps/ewallet_db/test/ewallet_db/api_key_test.exs b/apps/ewallet_db/test/ewallet_db/api_key_test.exs index c9069c600..e6276fa5c 100644 --- a/apps/ewallet_db/test/ewallet_db/api_key_test.exs +++ b/apps/ewallet_db/test/ewallet_db/api_key_test.exs @@ -52,12 +52,11 @@ defmodule EWalletDB.APIKeyTest do test_update_ignores_changing(APIKey, :key) test_update_ignores_changing(APIKey, :owner_app) - test_update_field_ok(APIKey, :enabled, insert(:admin), true, false) + test_update_field_ok(APIKey, :enabled, true, false) test_update_field_ok( APIKey, :exchange_address, - insert(:admin), insert(:wallet).address, insert(:wallet).address ) diff --git a/apps/ewallet_db/test/ewallet_db/audit_test.exs b/apps/ewallet_db/test/ewallet_db/audit_test.exs index 811fec743..67643c97a 100644 --- a/apps/ewallet_db/test/ewallet_db/audit_test.exs +++ b/apps/ewallet_db/test/ewallet_db/audit_test.exs @@ -131,7 +131,8 @@ defmodule EWalletDB.AuditTest do }) changeset = Changeset.change(%User{}, params) - {res, %{audit: audit, record: record}} = Audit.insert_record_with_audit(changeset) + {res, record} = Audit.insert_record_with_audit(changeset) + audit = record |> Audit.all_for_target() |> Enum.at(0) assert res == :ok @@ -141,77 +142,90 @@ defmodule EWalletDB.AuditTest do assert audit.target_type == "user" assert audit.target_uuid == record.uuid - changes = - changeset.changes - |> Map.delete(:originator) - |> Map.delete(:encrypted_metadata) + assert audit.target_changes == %{ + "calling_name" => record.calling_name, + "full_name" => record.full_name, + "metadata" => record.metadata, + "provider_user_id" => record.provider_user_id, + "username" => record.username + } - assert audit.target_changes == changes - assert audit.target_encrypted_metadata == %{something: "cool"} + assert audit.target_encrypted_metadata == %{"something" => "cool"} assert record |> Audit.all_for_target() |> length() == 1 end + end - test "inserts an audit and a user as well as a wallet" do + describe "Audit.update_record_with_audit/2" do + test "inserts an audit when updating a user" do admin = insert(:admin) + {:ok, user} = :user |> params_for() |> User.insert() params = params_for(:user, %{ - encrypted_metadata: %{something: "cool"}, + username: "test_username", originator: admin }) - changeset = Changeset.change(%User{}, params) - - multi = - Multi.new() - |> Multi.run(:wow_user, fn %{record: _record} -> - {:ok, insert(:user, username: "test_username")} - end) - - {res, %{audit: audit, record: record, wow_user: wow_user}} = - Audit.insert_record_with_audit(changeset, [], multi) + changeset = Changeset.change(user, params) + {res, record} = Audit.update_record_with_audit(changeset) + audit = record |> Audit.all_for_target() |> Enum.at(0) assert res == :ok - assert audit.action == "insert" + assert audit.action == "update" assert audit.originator_type == "user" assert audit.originator_uuid == admin.uuid assert audit.target_type == "user" assert audit.target_uuid == record.uuid - assert wow_user != nil - assert wow_user.username == "test_username" + assert audit.target_changes == %{ + "calling_name" => record.calling_name, + "full_name" => record.full_name, + "metadata" => record.metadata, + "provider_user_id" => record.provider_user_id, + "username" => record.username + } - assert record |> Audit.all_for_target() |> length() == 1 + assert audit.target_encrypted_metadata == %{} + + assert user |> Audit.all_for_target() |> length() == 2 end end - describe "Audit.update_record_with_audit/2" do - test "inserts an audit when updating a user" do + describe "perform/4" do + test "inserts an audit and a user as well as a wallet" do admin = insert(:admin) - {:ok, user} = :user |> params_for() |> User.insert() params = params_for(:user, %{ - username: "test_username", + encrypted_metadata: %{something: "cool"}, originator: admin }) - changeset = Changeset.change(user, params) - {res, %{audit: audit, record: record}} = Audit.update_record_with_audit(changeset) + changeset = Changeset.change(%User{}, params) + + multi = + Multi.new() + |> Multi.run(:wow_user, fn %{record: _record} -> + {:ok, insert(:user, username: "test_username")} + end) + + {res, %{audit: audit, record: record, wow_user: wow_user}} = + Audit.perform(:insert, changeset, [], multi) assert res == :ok - assert audit.action == "update" + assert audit.action == "insert" assert audit.originator_type == "user" assert audit.originator_uuid == admin.uuid assert audit.target_type == "user" assert audit.target_uuid == record.uuid - changes = Map.delete(changeset.changes, :originator) - assert audit.target_changes == changes - assert user |> Audit.all_for_target() |> length() == 2 + assert wow_user != nil + assert wow_user.username == "test_username" + + assert record |> Audit.all_for_target() |> length() == 1 end test "inserts an audit and updates a user as well as saving a wallet" do @@ -233,7 +247,7 @@ defmodule EWalletDB.AuditTest do end) {res, %{audit: audit, record: record, wow_user: _}} = - Audit.update_record_with_audit(changeset, [], multi) + Audit.perform(:update, changeset, [], multi) assert res == :ok diff --git a/apps/ewallet_db/test/ewallet_db/category_test.exs b/apps/ewallet_db/test/ewallet_db/category_test.exs index 84f872fff..35c4ccdef 100644 --- a/apps/ewallet_db/test/ewallet_db/category_test.exs +++ b/apps/ewallet_db/test/ewallet_db/category_test.exs @@ -40,8 +40,8 @@ defmodule EWalletDB.CategoryTest do end describe "update/1" do - test_update_field_ok(Category, :name, insert(:admin)) - test_update_field_ok(Category, :description, insert(:admin)) + test_update_field_ok(Category, :name) + test_update_field_ok(Category, :description) end describe "update/2 with account_ids" do diff --git a/apps/ewallet_db/test/ewallet_db/exchange_pair_test.exs b/apps/ewallet_db/test/ewallet_db/exchange_pair_test.exs index 460236d9c..7c605b155 100644 --- a/apps/ewallet_db/test/ewallet_db/exchange_pair_test.exs +++ b/apps/ewallet_db/test/ewallet_db/exchange_pair_test.exs @@ -1,6 +1,7 @@ defmodule EWalletDB.ExchangePairTest do use EWalletDB.SchemaCase alias EWalletDB.ExchangePair + alias EWalletConfig.System describe "ExchangePair factory" do test_has_valid_factory(ExchangePair) @@ -20,7 +21,8 @@ defmodule EWalletDB.ExchangePairTest do attrs = %{ from_token_uuid: pair.from_token_uuid, to_token_uuid: pair.to_token_uuid, - rate: 999 + rate: 999, + originator: %System{} } {res, inserted} = ExchangePair.insert(attrs) @@ -37,7 +39,8 @@ defmodule EWalletDB.ExchangePairTest do attrs = %{ from_token_uuid: omg.uuid, to_token_uuid: omg.uuid, - rate: 1.00 + rate: 1.00, + originator: %System{} } {res, changeset} = ExchangePair.insert(attrs) @@ -57,7 +60,8 @@ defmodule EWalletDB.ExchangePairTest do attrs = %{ from_token_uuid: pair.from_token_uuid, to_token_uuid: pair.to_token_uuid, - rate: 999 + rate: 999, + originator: %System{} } {res, changeset} = ExchangePair.insert(attrs) @@ -88,7 +92,7 @@ defmodule EWalletDB.ExchangePairTest do end describe "update/2" do - test_update_field_ok(ExchangePair, :rate, insert(:admin), 2.00, 9.99) + test_update_field_ok(ExchangePair, :rate, 2.00, 9.99) test_update_prevents_changing( ExchangePair, diff --git a/apps/ewallet_db/test/ewallet_db/role_test.exs b/apps/ewallet_db/test/ewallet_db/role_test.exs index 60aa5f7cb..2b0559cfc 100644 --- a/apps/ewallet_db/test/ewallet_db/role_test.exs +++ b/apps/ewallet_db/test/ewallet_db/role_test.exs @@ -33,8 +33,8 @@ defmodule EWalletDB.RoleTest do end describe "update/1" do - test_update_field_ok(Role, :name, insert(:admin)) - test_update_field_ok(Role, :display_name, insert(:admin)) + test_update_field_ok(Role, :name) + test_update_field_ok(Role, :display_name) end describe "deleted?/1" do diff --git a/apps/ewallet_db/test/ewallet_db/transaction_request_test.exs b/apps/ewallet_db/test/ewallet_db/transaction_request_test.exs index cbc6f9e20..53101a116 100644 --- a/apps/ewallet_db/test/ewallet_db/transaction_request_test.exs +++ b/apps/ewallet_db/test/ewallet_db/transaction_request_test.exs @@ -1,6 +1,7 @@ defmodule EWalletDB.TransactionRequestTest do use EWalletDB.SchemaCase alias EWalletDB.TransactionRequest + alias EWalletConfig.System describe "TransactionRequest factory" do test_has_valid_factory(TransactionRequest) @@ -92,7 +93,7 @@ defmodule EWalletDB.TransactionRequestTest do describe "touch/1" do test "updates the updated_at field" do request = insert(:transaction_request) - {:ok, updated} = TransactionRequest.touch(request) + {:ok, updated} = TransactionRequest.touch(request, %System{}) assert NaiveDateTime.compare(updated.updated_at, request.updated_at) == :gt end end @@ -215,7 +216,7 @@ defmodule EWalletDB.TransactionRequestTest do t = insert(:transaction_request, expiration_date: NaiveDateTime.add(now, -60, :seconds)) assert TransactionRequest.expired?(t) == false - TransactionRequest.expire(t, "testing") + TransactionRequest.expire(t, %System{}, "testing") t = TransactionRequest.get(t.id) assert TransactionRequest.expired?(t) == true @@ -227,7 +228,7 @@ defmodule EWalletDB.TransactionRequestTest do describe "expire_if_past_expiration_date/1" do test "does nothing if expiration date is not set" do request = insert(:transaction_request, expiration_date: nil) - {res, request} = TransactionRequest.expire_if_past_expiration_date(request) + {res, request} = TransactionRequest.expire_if_past_expiration_date(request, %System{}) assert res == :ok assert %TransactionRequest{} = request assert TransactionRequest.valid?(request) == true @@ -236,7 +237,7 @@ defmodule EWalletDB.TransactionRequestTest do test "does nothing if expiration date is not past" do future_date = NaiveDateTime.add(NaiveDateTime.utc_now(), 60, :second) request = insert(:transaction_request, expiration_date: future_date) - {res, request} = TransactionRequest.expire_if_past_expiration_date(request) + {res, request} = TransactionRequest.expire_if_past_expiration_date(request, %System{}) assert res == :ok assert %TransactionRequest{} = request assert TransactionRequest.valid?(request) == true @@ -245,7 +246,7 @@ defmodule EWalletDB.TransactionRequestTest do test "expires the request if expiration date is past" do past_date = NaiveDateTime.add(NaiveDateTime.utc_now(), -60, :second) request = insert(:transaction_request, expiration_date: past_date) - {res, request} = TransactionRequest.expire_if_past_expiration_date(request) + {res, request} = TransactionRequest.expire_if_past_expiration_date(request, %System{}) assert res == :ok assert TransactionRequest.expired?(request) == true end @@ -254,7 +255,7 @@ defmodule EWalletDB.TransactionRequestTest do describe "expire_if_max_consumption/1" do test "touches the request if max_consumptions is equal to nil" do request = insert(:transaction_request, max_consumptions: nil) - {res, updated_request} = TransactionRequest.expire_if_max_consumption(request) + {res, updated_request} = TransactionRequest.expire_if_max_consumption(request, %System{}) assert res == :ok assert %TransactionRequest{} = updated_request assert TransactionRequest.valid?(updated_request) == true @@ -264,7 +265,7 @@ defmodule EWalletDB.TransactionRequestTest do test "touches the request if max_consumptions is equal to 0" do request = insert(:transaction_request, max_consumptions: 0) - {res, updated_request} = TransactionRequest.expire_if_max_consumption(request) + {res, updated_request} = TransactionRequest.expire_if_max_consumption(request, %System{}) assert res == :ok assert %TransactionRequest{} = updated_request assert TransactionRequest.valid?(updated_request) == true @@ -273,7 +274,7 @@ defmodule EWalletDB.TransactionRequestTest do test "touches the request if max_consumptions has not been reached" do request = insert(:transaction_request, max_consumptions: 3) - {res, updated_request} = TransactionRequest.expire_if_max_consumption(request) + {res, updated_request} = TransactionRequest.expire_if_max_consumption(request, %System{}) assert res == :ok assert %TransactionRequest{} = updated_request assert TransactionRequest.valid?(updated_request) == true @@ -297,7 +298,7 @@ defmodule EWalletDB.TransactionRequestTest do status: "confirmed" ) - {res, updated_request} = TransactionRequest.expire_if_max_consumption(request) + {res, updated_request} = TransactionRequest.expire_if_max_consumption(request, %System{}) assert res == :ok assert %TransactionRequest{} = updated_request assert updated_request.expired_at != nil diff --git a/apps/ewallet_db/test/ewallet_db/transaction_test.exs b/apps/ewallet_db/test/ewallet_db/transaction_test.exs index cdd37da9a..1ea769369 100644 --- a/apps/ewallet_db/test/ewallet_db/transaction_test.exs +++ b/apps/ewallet_db/test/ewallet_db/transaction_test.exs @@ -2,6 +2,7 @@ defmodule EWalletDB.TransactionTest do use EWalletDB.SchemaCase alias Ecto.UUID alias EWalletDB.Transaction + alias EWalletConfig.System describe "Transaction factory" do test_has_valid_factory(Transaction) @@ -74,7 +75,10 @@ defmodule EWalletDB.TransactionTest do test "returns an error when passing invalid arguments" do assert Repo.all(Transaction) == [] - {res, changeset} = %{idempotency_token: nil, payload: %{}} |> Transaction.insert() + + {res, changeset} = + %{idempotency_token: nil, payload: %{}, originator: %System{}} |> Transaction.insert() + assert res == :error assert changeset.errors == [ @@ -118,7 +122,7 @@ defmodule EWalletDB.TransactionTest do {:ok, inserted_transaction} = :transaction |> params_for() |> Transaction.get_or_insert() assert inserted_transaction.status == Transaction.pending() local_ledger_uuid = UUID.generate() - transaction = Transaction.confirm(inserted_transaction, local_ledger_uuid) + transaction = Transaction.confirm(inserted_transaction, local_ledger_uuid, %System{}) assert transaction.id == inserted_transaction.id assert transaction.status == Transaction.confirmed() assert transaction.local_ledger_uuid == local_ledger_uuid @@ -129,7 +133,7 @@ defmodule EWalletDB.TransactionTest do test "sets a transaction as failed" do {:ok, inserted_transaction} = :transaction |> params_for() |> Transaction.get_or_insert() assert inserted_transaction.status == Transaction.pending() - transaction = Transaction.fail(inserted_transaction, "error", "desc") + transaction = Transaction.fail(inserted_transaction, "error", "desc", %System{}) assert transaction.id == inserted_transaction.id assert transaction.status == Transaction.failed() assert transaction.error_code == "error" @@ -140,7 +144,7 @@ defmodule EWalletDB.TransactionTest do test "sets a transaction as failed with atom error" do {:ok, inserted_transaction} = :transaction |> params_for() |> Transaction.get_or_insert() assert inserted_transaction.status == Transaction.pending() - transaction = Transaction.fail(inserted_transaction, :error, "desc") + transaction = Transaction.fail(inserted_transaction, :error, "desc", %System{}) assert transaction.id == inserted_transaction.id assert transaction.status == Transaction.failed() assert transaction.error_code == "error" @@ -151,7 +155,7 @@ defmodule EWalletDB.TransactionTest do test "sets a transaction as failed with error_data" do {:ok, inserted_transaction} = :transaction |> params_for() |> Transaction.get_or_insert() assert inserted_transaction.status == Transaction.pending() - transaction = Transaction.fail(inserted_transaction, "error", %{}) + transaction = Transaction.fail(inserted_transaction, "error", %{}, %System{}) assert transaction.id == inserted_transaction.id assert transaction.status == Transaction.failed() assert transaction.error_code == "error" diff --git a/apps/ewallet_db/test/ewallet_db/user_test.exs b/apps/ewallet_db/test/ewallet_db/user_test.exs index cfc68d8b5..630f9f3ff 100644 --- a/apps/ewallet_db/test/ewallet_db/user_test.exs +++ b/apps/ewallet_db/test/ewallet_db/user_test.exs @@ -2,6 +2,7 @@ defmodule EWalletDB.UserTest do use EWalletDB.SchemaCase alias EWalletConfig.Helpers.Crypto alias EWalletDB.{Account, Audit, Invite, User} + alias EWalletConfig.System describe "User factory" do test_has_valid_factory(User) @@ -68,13 +69,13 @@ defmodule EWalletDB.UserTest do end describe "update/2" do - test_update_field_ok(User, :username, insert(:admin)) - test_update_field_ok(User, :full_name, insert(:admin)) - test_update_field_ok(User, :calling_name, insert(:admin)) + test_update_field_ok(User, :username) + test_update_field_ok(User, :full_name) + test_update_field_ok(User, :calling_name) - test_update_field_ok(User, :metadata, insert(:admin), %{"field" => "old"}, %{"field" => "new"}) + test_update_field_ok(User, :metadata, %{"field" => "old"}, %{"field" => "new"}) - test_update_field_ok(User, :encrypted_metadata, insert(:admin), %{"field" => "old"}, %{ + test_update_field_ok(User, :encrypted_metadata, %{"field" => "old"}, %{ "field" => "new" }) @@ -544,7 +545,12 @@ defmodule EWalletDB.UserTest do user = insert(:user, %{enabled: false}) refute User.enabled?(user) - {:ok, user} = User.enable_or_disable(user, %{enabled: true}) + {:ok, user} = + User.enable_or_disable(user, %{ + enabled: true, + originator: %System{} + }) + assert User.enabled?(user) end @@ -552,7 +558,12 @@ defmodule EWalletDB.UserTest do user = insert(:user, %{enabled: true}) assert User.enabled?(user) - {:ok, user} = User.enable_or_disable(user, %{enabled: false}) + {:ok, user} = + User.enable_or_disable(user, %{ + enabled: false, + originator: %System{} + }) + refute User.enabled?(user) end end diff --git a/apps/ewallet_db/test/support/factory.ex b/apps/ewallet_db/test/support/factory.ex index d803877dd..0b88ef82c 100644 --- a/apps/ewallet_db/test/support/factory.ex +++ b/apps/ewallet_db/test/support/factory.ex @@ -50,7 +50,8 @@ defmodule EWalletDB.Factory do def category_factory do %Category{ name: sequence("Category name"), - description: sequence("description") + description: sequence("description"), + originator: %System{} } end @@ -58,7 +59,8 @@ defmodule EWalletDB.Factory do %ExchangePair{ from_token: insert(:token), to_token: insert(:token), - rate: 1.0 + rate: 1.0, + originator: %System{} } end @@ -71,7 +73,8 @@ defmodule EWalletDB.Factory do identifier: Wallet.primary(), user: insert(:user), enabled: true, - metadata: %{} + metadata: %{}, + originator: %System{} } end @@ -93,7 +96,8 @@ defmodule EWalletDB.Factory do smallest_denomination: 1, locked: false, account: insert(:account), - enabled: true + enabled: true, + originator: %System{} } end @@ -105,13 +109,13 @@ defmodule EWalletDB.Factory do full_name: sequence("John Doe"), calling_name: sequence("John"), provider_user_id: sequence("provider_id"), - originator: insert(:admin), enabled: true, metadata: %{ "first_name" => sequence("John"), "last_name" => sequence("Doe") }, - encrypted_metadata: %{} + encrypted_metadata: %{}, + originator: %System{} } end @@ -123,12 +127,12 @@ defmodule EWalletDB.Factory do email: sequence("johndoe") <> "@example.com", password: password, password_hash: Crypto.hash_password(password), - originator: :self, metadata: %{ "first_name" => sequence("John"), "last_name" => sequence("Doe") }, - encrypted_metadata: %{} + encrypted_metadata: %{}, + originator: %System{} } end @@ -141,11 +145,11 @@ defmodule EWalletDB.Factory do password: password, password_hash: Crypto.hash_password(password), invite: nil, - originator: %System{}, metadata: %{ "first_name" => sequence("John"), "last_name" => sequence("Doe") - } + }, + originator: %System{} } end @@ -185,7 +189,7 @@ defmodule EWalletDB.Factory do token: Crypto.generate_base64_key(32), success_url: nil, verified_at: nil, - originator: insert(:admin) + originator: %System{} } end @@ -193,7 +197,8 @@ defmodule EWalletDB.Factory do %Role{ name: sequence("role"), display_name: "Role display name", - priority: sequence("") + priority: sequence(""), + originator: %System{} } end @@ -201,7 +206,8 @@ defmodule EWalletDB.Factory do %Membership{ user: insert(:user), role: insert(:role), - account: insert(:account) + account: insert(:account), + originator: %System{} } end @@ -209,7 +215,8 @@ defmodule EWalletDB.Factory do %Mint{ amount: 100_000, token_uuid: insert(:token).uuid, - transaction_uuid: insert(:transaction).uuid + transaction_uuid: insert(:transaction).uuid, + originator: %System{} } end @@ -217,7 +224,8 @@ defmodule EWalletDB.Factory do %Account{ name: sequence("account"), description: sequence("description for account"), - parent: Account.get_master_account() + parent: Account.get_master_account(), + originator: %System{} } end @@ -225,7 +233,7 @@ defmodule EWalletDB.Factory do %AccountUser{ account_uuid: Account.get_master_account().uuid, user_uuid: insert(:user).uuid, - originator: insert(:admin) + originator: %System{} } end @@ -239,7 +247,8 @@ defmodule EWalletDB.Factory do secret_key_hash: Crypto.hash_secret(secret_key), account: insert(:account), enabled: true, - deleted_at: nil + deleted_at: nil, + originator: %System{} } end @@ -248,7 +257,8 @@ defmodule EWalletDB.Factory do key: sequence("api_key"), owner_app: "some_app_name", account: insert(:account), - enabled: true + enabled: true, + originator: %System{} } end @@ -258,7 +268,8 @@ defmodule EWalletDB.Factory do owner_app: "some_app_name", user: insert(:user), account: insert(:account), - expired: false + expired: false, + originator: %System{} } end @@ -281,14 +292,16 @@ defmodule EWalletDB.Factory do to_wallet: to_wallet, to_user_uuid: to_wallet.user_uuid, to_account_uuid: to_wallet.account_uuid, - exchange_account: nil + exchange_account: nil, + originator: %System{} } end def forget_password_request_factory do %ForgetPasswordRequest{ token: sequence("123"), - enabled: true + enabled: true, + originator: %System{} } end @@ -296,7 +309,8 @@ defmodule EWalletDB.Factory do %UpdateEmailRequest{ email: sequence("johndoe") <> "@example.com", token: sequence("123"), - enabled: true + enabled: true, + originator: %System{} } end @@ -308,7 +322,8 @@ defmodule EWalletDB.Factory do token_uuid: insert(:token).uuid, user_uuid: insert(:user).uuid, wallet: insert(:wallet), - consumptions_count: 0 + consumptions_count: 0, + originator: %System{} } end @@ -319,7 +334,8 @@ defmodule EWalletDB.Factory do user_uuid: insert(:user).uuid, wallet_address: insert(:wallet).address, amount: 100, - transaction_request_uuid: insert(:transaction_request).uuid + transaction_request_uuid: insert(:transaction_request).uuid, + originator: %System{} } end end diff --git a/apps/ewallet_db/test/support/schema_case.ex b/apps/ewallet_db/test/support/schema_case.ex index 6dc5485b0..a15f48efa 100644 --- a/apps/ewallet_db/test/support/schema_case.ex +++ b/apps/ewallet_db/test/support/schema_case.ex @@ -38,6 +38,7 @@ defmodule EWalletDB.SchemaCase do import EWalletDB.Factory alias Ecto.Adapters.SQL alias EWalletDB.{Account, User} + alias EWalletConfig.System defmacro __using__(_opts) do quote do @@ -431,12 +432,11 @@ defmodule EWalletDB.SchemaCase do @doc """ Test schema's update/2 does update the given field """ - defmacro test_update_field_ok(schema, field, originator, old \\ "old", new \\ "new") do + defmacro test_update_field_ok(schema, field, old \\ "old", new \\ "new") do quote do test "updates #{unquote(field)} successfully" do schema = unquote(schema) field = unquote(field) - originator = unquote(originator) old = unquote(old) new = unquote(new) @@ -448,7 +448,7 @@ defmodule EWalletDB.SchemaCase do {res, updated} = schema.update(original, %{ - :originator => originator, + :originator => %System{}, field => new }) @@ -475,7 +475,11 @@ defmodule EWalletDB.SchemaCase do |> params_for(%{field => old}) |> schema.insert() - {res, changeset} = schema.update(original, %{field => new}) + {res, changeset} = + schema.update(original, %{ + :originator => %System{}, + field => new + }) assert res == :error assert changeset.errors == [{field, {"can't be changed", []}}] @@ -617,7 +621,7 @@ defmodule EWalletDB.SchemaCase do {_, record} = schema |> get_factory() - |> params_for(%{}) + |> params_for() |> schema.insert() # Makes sure the record is already soft-deleted before testing @@ -625,7 +629,7 @@ defmodule EWalletDB.SchemaCase do assert schema.deleted?(record) {res, record} = schema.restore(record) - + IO.inspect(record) assert res == :ok refute schema.deleted?(record) end From 7195f334e4259903485f16970a61df4a801116a4 Mon Sep 17 00:00:00 2001 From: Thibault Denizet Date: Mon, 3 Dec 2018 12:49:34 +0700 Subject: [PATCH 03/23] Move utils modules from EWalletConfig to Utils sub app --- .../lib/ewallet_config/utils/encrypted/map.ex | 5 --- .../ewallet_config/utils/structs/system.ex | 6 ---- apps/utils/.formatter.exs | 4 +++ apps/utils/.gitignore | 24 ++++++++++++++ apps/utils/README.md | 21 +++++++++++++ apps/utils/config/config.exs | 30 ++++++++++++++++++ .../utils => utils/lib}/helpers/assoc.ex | 2 +- .../utils => utils/lib}/helpers/crypto.ex | 2 +- .../lib}/helpers/input_attribute.ex | 2 +- .../utils => utils/lib}/helpers/intersect.ex | 2 +- .../utils => utils/lib}/helpers/uuid.ex | 2 +- .../utils => utils/lib}/storage/local.ex | 2 +- .../utils => utils/lib}/types/external_id.ex | 8 ++--- .../utils => utils/lib}/types/integer.ex | 2 +- .../lib}/types/virtual_struct.ex | 2 +- .../lib}/types/wallet_address.ex | 8 ++--- apps/utils/lib/utils.ex | 18 +++++++++++ apps/utils/mix.exs | 31 +++++++++++++++++++ apps/utils/test/test_helper.exs | 1 + .../test}/utils/helpers/crypto_test.exs | 4 +-- .../utils/helpers/input_attribute_test.exs | 4 +-- .../test}/utils/types/external_id_test.exs | 4 +-- .../test}/utils/types/wallet_address_test.exs | 5 ++- apps/utils/test/utils_test.exs | 8 +++++ 24 files changed, 161 insertions(+), 36 deletions(-) delete mode 100644 apps/ewallet_config/lib/ewallet_config/utils/encrypted/map.ex delete mode 100644 apps/ewallet_config/lib/ewallet_config/utils/structs/system.ex create mode 100644 apps/utils/.formatter.exs create mode 100644 apps/utils/.gitignore create mode 100644 apps/utils/README.md create mode 100644 apps/utils/config/config.exs rename apps/{ewallet_config/lib/ewallet_config/utils => utils/lib}/helpers/assoc.ex (97%) rename apps/{ewallet_config/lib/ewallet_config/utils => utils/lib}/helpers/crypto.ex (97%) rename apps/{ewallet_config/lib/ewallet_config/utils => utils/lib}/helpers/input_attribute.ex (91%) rename apps/{ewallet_config/lib/ewallet_config/utils => utils/lib}/helpers/intersect.ex (71%) rename apps/{ewallet_config/lib/ewallet_config/utils => utils/lib}/helpers/uuid.ex (84%) rename apps/{ewallet_config/lib/ewallet_config/utils => utils/lib}/storage/local.ex (97%) rename apps/{ewallet_config/lib/ewallet_config/utils => utils/lib}/types/external_id.ex (94%) rename apps/{ewallet_config/lib/ewallet_config/utils => utils/lib}/types/integer.ex (94%) rename apps/{ewallet_config/lib/ewallet_config/utils => utils/lib}/types/virtual_struct.ex (87%) rename apps/{ewallet_config/lib/ewallet_config/utils => utils/lib}/types/wallet_address.ex (95%) create mode 100644 apps/utils/lib/utils.ex create mode 100644 apps/utils/mix.exs create mode 100644 apps/utils/test/test_helper.exs rename apps/{ewallet_config/test/ewallet_config => utils/test}/utils/helpers/crypto_test.exs (83%) rename apps/{ewallet_config/test/ewallet_config => utils/test}/utils/helpers/input_attribute_test.exs (87%) rename apps/{ewallet_config/test/ewallet_config => utils/test}/utils/types/external_id_test.exs (95%) rename apps/{ewallet_config/test/ewallet_config => utils/test}/utils/types/wallet_address_test.exs (95%) create mode 100644 apps/utils/test/utils_test.exs diff --git a/apps/ewallet_config/lib/ewallet_config/utils/encrypted/map.ex b/apps/ewallet_config/lib/ewallet_config/utils/encrypted/map.ex deleted file mode 100644 index 5ba82a461..000000000 --- a/apps/ewallet_config/lib/ewallet_config/utils/encrypted/map.ex +++ /dev/null @@ -1,5 +0,0 @@ -defmodule EWalletConfig.Encrypted.Map do - @moduledoc false - - use Cloak.Fields.Map, vault: EWalletConfig.Vault -end diff --git a/apps/ewallet_config/lib/ewallet_config/utils/structs/system.ex b/apps/ewallet_config/lib/ewallet_config/utils/structs/system.ex deleted file mode 100644 index 9fa79af80..000000000 --- a/apps/ewallet_config/lib/ewallet_config/utils/structs/system.ex +++ /dev/null @@ -1,6 +0,0 @@ -defmodule EWalletConfig.System do - @moduledoc """ - Module representing the system as originator. - """ - defstruct uuid: "00000000-0000-0000-0000-000000000000" -end diff --git a/apps/utils/.formatter.exs b/apps/utils/.formatter.exs new file mode 100644 index 000000000..525446d40 --- /dev/null +++ b/apps/utils/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/apps/utils/.gitignore b/apps/utils/.gitignore new file mode 100644 index 000000000..2215727e0 --- /dev/null +++ b/apps/utils/.gitignore @@ -0,0 +1,24 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where 3rd-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +utils-*.tar + diff --git a/apps/utils/README.md b/apps/utils/README.md new file mode 100644 index 000000000..c749301dc --- /dev/null +++ b/apps/utils/README.md @@ -0,0 +1,21 @@ +# Utils + +**TODO: Add description** + +## Installation + +If [available in Hex](https://hex.pm/docs/publish), the package can be installed +by adding `utils` to your list of dependencies in `mix.exs`: + +```elixir +def deps do + [ + {:utils, "~> 0.1.0"} + ] +end +``` + +Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) +and published on [HexDocs](https://hexdocs.pm). Once published, the docs can +be found at [https://hexdocs.pm/utils](https://hexdocs.pm/utils). + diff --git a/apps/utils/config/config.exs b/apps/utils/config/config.exs new file mode 100644 index 000000000..efe553cd5 --- /dev/null +++ b/apps/utils/config/config.exs @@ -0,0 +1,30 @@ +# This file is responsible for configuring your application +# and its dependencies with the aid of the Mix.Config module. +use Mix.Config + +# This configuration is loaded before any dependency and is restricted +# to this project. If another project depends on this project, this +# file won't be loaded nor affect the parent project. For this reason, +# if you want to provide default values for your application for +# 3rd-party users, it should be done in your "mix.exs" file. + +# You can configure your application as: +# +# config :utils, key: :value +# +# and access this configuration in your application as: +# +# Application.get_env(:utils, :key) +# +# You can also configure a 3rd-party app: +# +# config :logger, level: :info +# + +# It is also possible to import configuration files, relative to this +# directory. For example, you can emulate configuration per environment +# by uncommenting the line below and defining dev.exs, test.exs and such. +# Configuration from the imported file will override the ones defined +# here (which is why it is important to import them last). +# +# import_config "#{Mix.env}.exs" diff --git a/apps/ewallet_config/lib/ewallet_config/utils/helpers/assoc.ex b/apps/utils/lib/helpers/assoc.ex similarity index 97% rename from apps/ewallet_config/lib/ewallet_config/utils/helpers/assoc.ex rename to apps/utils/lib/helpers/assoc.ex index 38f079d30..2eb8eab74 100644 --- a/apps/ewallet_config/lib/ewallet_config/utils/helpers/assoc.ex +++ b/apps/utils/lib/helpers/assoc.ex @@ -1,4 +1,4 @@ -defmodule EWalletConfig.Helpers.Assoc do +defmodule Utils.Helpers.Assoc do @moduledoc """ The module that provides helpers for working with associations. diff --git a/apps/ewallet_config/lib/ewallet_config/utils/helpers/crypto.ex b/apps/utils/lib/helpers/crypto.ex similarity index 97% rename from apps/ewallet_config/lib/ewallet_config/utils/helpers/crypto.ex rename to apps/utils/lib/helpers/crypto.ex index b693f9f05..a865af7e4 100644 --- a/apps/ewallet_config/lib/ewallet_config/utils/helpers/crypto.ex +++ b/apps/utils/lib/helpers/crypto.ex @@ -1,4 +1,4 @@ -defmodule EWalletConfig.Helpers.Crypto do +defmodule Utils.Helpers.Crypto do @moduledoc """ A helper to perform crytographic operations """ diff --git a/apps/ewallet_config/lib/ewallet_config/utils/helpers/input_attribute.ex b/apps/utils/lib/helpers/input_attribute.ex similarity index 91% rename from apps/ewallet_config/lib/ewallet_config/utils/helpers/input_attribute.ex rename to apps/utils/lib/helpers/input_attribute.ex index 51f77ad10..4b2be050b 100644 --- a/apps/ewallet_config/lib/ewallet_config/utils/helpers/input_attribute.ex +++ b/apps/utils/lib/helpers/input_attribute.ex @@ -1,4 +1,4 @@ -defmodule EWalletConfig.Helpers.InputAttribute do +defmodule Utils.Helpers.InputAttribute do @moduledoc """ Helper functions to deal with input attributes. """ diff --git a/apps/ewallet_config/lib/ewallet_config/utils/helpers/intersect.ex b/apps/utils/lib/helpers/intersect.ex similarity index 71% rename from apps/ewallet_config/lib/ewallet_config/utils/helpers/intersect.ex rename to apps/utils/lib/helpers/intersect.ex index 7297dd9fd..48426620b 100644 --- a/apps/ewallet_config/lib/ewallet_config/utils/helpers/intersect.ex +++ b/apps/utils/lib/helpers/intersect.ex @@ -1,4 +1,4 @@ -defmodule EWalletConfig.Intersecter do +defmodule Utils.Intersecter do @moduledoc """ Module to intersect lists. """ diff --git a/apps/ewallet_config/lib/ewallet_config/utils/helpers/uuid.ex b/apps/utils/lib/helpers/uuid.ex similarity index 84% rename from apps/ewallet_config/lib/ewallet_config/utils/helpers/uuid.ex rename to apps/utils/lib/helpers/uuid.ex index c64c4dae0..54dbbeb7c 100644 --- a/apps/ewallet_config/lib/ewallet_config/utils/helpers/uuid.ex +++ b/apps/utils/lib/helpers/uuid.ex @@ -1,4 +1,4 @@ -defmodule EWalletConfig.Helpers.UUID do +defmodule Utils.Helpers.UUID do @moduledoc """ Helper module to check that a string is a valid UUID. """ diff --git a/apps/ewallet_config/lib/ewallet_config/utils/storage/local.ex b/apps/utils/lib/storage/local.ex similarity index 97% rename from apps/ewallet_config/lib/ewallet_config/utils/storage/local.ex rename to apps/utils/lib/storage/local.ex index dc2f9e390..bb343b573 100644 --- a/apps/ewallet_config/lib/ewallet_config/utils/storage/local.ex +++ b/apps/utils/lib/storage/local.ex @@ -1,4 +1,4 @@ -defmodule EWalletConfig.Storage.Local do +defmodule Utils.Storage.Local do @moduledoc """ Modified copy of the Arc local storage, needed to add the base URL before the file paths. diff --git a/apps/ewallet_config/lib/ewallet_config/utils/types/external_id.ex b/apps/utils/lib/types/external_id.ex similarity index 94% rename from apps/ewallet_config/lib/ewallet_config/utils/types/external_id.ex rename to apps/utils/lib/types/external_id.ex index f4085c338..cdecdbb3e 100644 --- a/apps/ewallet_config/lib/ewallet_config/utils/types/external_id.ex +++ b/apps/utils/lib/types/external_id.ex @@ -1,4 +1,4 @@ -defmodule EWalletConfig.Types.ExternalID do +defmodule Utils.Types.ExternalID do @moduledoc """ A custom Ecto type that handles the external ID. The external ID is a string that consists of a ULID prefixed with a 3-letter symbol representing the schema @@ -65,7 +65,7 @@ defmodule EWalletConfig.Types.ExternalID do ## Example defmodule ExampleSchema do - use EWalletConfig.Types.ExternalID + use Utils.Types.ExternalID schema "examples" do external_id prefix: "exp_" @@ -120,8 +120,8 @@ defmodule EWalletConfig.Types.ExternalID do defmacro __using__(_) do quote do - alias EWalletConfig.Types.ExternalID - import EWalletConfig.Types.ExternalID, only: [external_id: 1, is_external_id: 1] + alias Utils.Types.ExternalID + import Utils.Types.ExternalID, only: [external_id: 1, is_external_id: 1] end end end diff --git a/apps/ewallet_config/lib/ewallet_config/utils/types/integer.ex b/apps/utils/lib/types/integer.ex similarity index 94% rename from apps/ewallet_config/lib/ewallet_config/utils/types/integer.ex rename to apps/utils/lib/types/integer.ex index 9fdce05ed..bcec56bff 100644 --- a/apps/ewallet_config/lib/ewallet_config/utils/types/integer.ex +++ b/apps/utils/lib/types/integer.ex @@ -1,4 +1,4 @@ -defmodule EWalletConfig.Types.Integer do +defmodule Utils.Types.Integer do @moduledoc """ Custom Ecto type that converts DB's decimal value into integer. diff --git a/apps/ewallet_config/lib/ewallet_config/utils/types/virtual_struct.ex b/apps/utils/lib/types/virtual_struct.ex similarity index 87% rename from apps/ewallet_config/lib/ewallet_config/utils/types/virtual_struct.ex rename to apps/utils/lib/types/virtual_struct.ex index 04cbe545b..9d1d6dda8 100644 --- a/apps/ewallet_config/lib/ewallet_config/utils/types/virtual_struct.ex +++ b/apps/utils/lib/types/virtual_struct.ex @@ -1,4 +1,4 @@ -defmodule EWalletConfig.Types.VirtualStruct do +defmodule Utils.Types.VirtualStruct do @moduledoc """ Useless type used for the virtual struct "originator". """ diff --git a/apps/ewallet_config/lib/ewallet_config/utils/types/wallet_address.ex b/apps/utils/lib/types/wallet_address.ex similarity index 95% rename from apps/ewallet_config/lib/ewallet_config/utils/types/wallet_address.ex rename to apps/utils/lib/types/wallet_address.ex index a418b861c..07931b0f2 100644 --- a/apps/ewallet_config/lib/ewallet_config/utils/types/wallet_address.ex +++ b/apps/utils/lib/types/wallet_address.ex @@ -1,4 +1,4 @@ -defmodule EWalletConfig.Types.WalletAddress do +defmodule Utils.Types.WalletAddress do @moduledoc """ A custom Ecto type that handles wallet addresses. A wallet address is a string that consists of 4 case-insensitive letters followed by a 12-digit integer. @@ -13,7 +13,7 @@ defmodule EWalletConfig.Types.WalletAddress do """ @behaviour Ecto.Type alias Ecto.Schema - alias EWalletConfig.Helpers.UUID + alias Utils.Helpers.UUID # 4-char letters, 12-digit integers @type t :: <<_::16>> @@ -80,7 +80,7 @@ defmodule EWalletConfig.Types.WalletAddress do ## Example defmodule WalletSchema do - use EWalletConfig.Types.WalletAddress + use Utils.Types.WalletAddress schema "wallet" do wallet_address(:address) @@ -153,7 +153,7 @@ defmodule EWalletConfig.Types.WalletAddress do defmacro __using__(_) do quote do - import EWalletConfig.Types.WalletAddress, only: [wallet_address: 1, wallet_address: 2] + import Utils.Types.WalletAddress, only: [wallet_address: 1, wallet_address: 2] end end end diff --git a/apps/utils/lib/utils.ex b/apps/utils/lib/utils.ex new file mode 100644 index 000000000..2f1ab833d --- /dev/null +++ b/apps/utils/lib/utils.ex @@ -0,0 +1,18 @@ +defmodule Utils do + @moduledoc """ + Documentation for Utils. + """ + + @doc """ + Hello world. + + ## Examples + + iex> Utils.hello + :world + + """ + def hello do + :world + end +end diff --git a/apps/utils/mix.exs b/apps/utils/mix.exs new file mode 100644 index 000000000..cf79ae4b7 --- /dev/null +++ b/apps/utils/mix.exs @@ -0,0 +1,31 @@ +defmodule Utils.MixProject do + use Mix.Project + + def project do + [ + app: :utils, + version: "0.1.0", + build_path: "../../_build", + config_path: "../../config/config.exs", + deps_path: "../../deps", + lockfile: "../../mix.lock", + elixir: "~> 1.6", + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger] + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + {:ex_ulid, github: "omisego/ex_ulid"} + ] + end +end diff --git a/apps/utils/test/test_helper.exs b/apps/utils/test/test_helper.exs new file mode 100644 index 000000000..869559e70 --- /dev/null +++ b/apps/utils/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start() diff --git a/apps/ewallet_config/test/ewallet_config/utils/helpers/crypto_test.exs b/apps/utils/test/utils/helpers/crypto_test.exs similarity index 83% rename from apps/ewallet_config/test/ewallet_config/utils/helpers/crypto_test.exs rename to apps/utils/test/utils/helpers/crypto_test.exs index 8c1a1939e..78c7c80bc 100644 --- a/apps/ewallet_config/test/ewallet_config/utils/helpers/crypto_test.exs +++ b/apps/utils/test/utils/helpers/crypto_test.exs @@ -1,6 +1,6 @@ -defmodule EWalletConfig.Helpers.CryptoTest do +defmodule Utils.Helpers.CryptoTest do use ExUnit.Case - alias EWalletConfig.Helpers.Crypto + alias Utils.Helpers.Crypto describe "generate_base64_key/1" do test "returns a key with the specified length" do diff --git a/apps/ewallet_config/test/ewallet_config/utils/helpers/input_attribute_test.exs b/apps/utils/test/utils/helpers/input_attribute_test.exs similarity index 87% rename from apps/ewallet_config/test/ewallet_config/utils/helpers/input_attribute_test.exs rename to apps/utils/test/utils/helpers/input_attribute_test.exs index 1c90de616..e72b30b78 100644 --- a/apps/ewallet_config/test/ewallet_config/utils/helpers/input_attribute_test.exs +++ b/apps/utils/test/utils/helpers/input_attribute_test.exs @@ -1,6 +1,6 @@ -defmodule EWalletConfig.Helpers.InputAttributeTest do +defmodule Utils.Helpers.InputAttributeTest do use ExUnit.Case - alias EWalletConfig.Helpers.InputAttribute + alias Utils.Helpers.InputAttribute describe "get/2" do test "returns the value if the map key is atom and argument is atom" do diff --git a/apps/ewallet_config/test/ewallet_config/utils/types/external_id_test.exs b/apps/utils/test/utils/types/external_id_test.exs similarity index 95% rename from apps/ewallet_config/test/ewallet_config/utils/types/external_id_test.exs rename to apps/utils/test/utils/types/external_id_test.exs index b20bc8ac5..5449c7e1a 100644 --- a/apps/ewallet_config/test/ewallet_config/utils/types/external_id_test.exs +++ b/apps/utils/test/utils/types/external_id_test.exs @@ -1,6 +1,6 @@ -defmodule EWalletConfig.Types.ExternalIDTest do +defmodule Utils.Types.ExternalIDTest do use ExUnit.Case, async: true - alias EWalletConfig.Types.ExternalID + alias Utils.Types.ExternalID describe "cast/1" do test "casts input to lower case" do diff --git a/apps/ewallet_config/test/ewallet_config/utils/types/wallet_address_test.exs b/apps/utils/test/utils/types/wallet_address_test.exs similarity index 95% rename from apps/ewallet_config/test/ewallet_config/utils/types/wallet_address_test.exs rename to apps/utils/test/utils/types/wallet_address_test.exs index 06fc53142..538968a0c 100644 --- a/apps/ewallet_config/test/ewallet_config/utils/types/wallet_address_test.exs +++ b/apps/utils/test/utils/types/wallet_address_test.exs @@ -1,7 +1,6 @@ -defmodule EWalletConfig.Types.WalletAddressTest do +defmodule Utils.Types.WalletAddressTest do use ExUnit.Case, async: true - alias EWalletConfig.Types.WalletAddress - # alias EWalletDB.Wallet + alias Utils.Types.WalletAddress describe "cast/1" do test "casts input to lower case" do diff --git a/apps/utils/test/utils_test.exs b/apps/utils/test/utils_test.exs new file mode 100644 index 000000000..977b8ecb8 --- /dev/null +++ b/apps/utils/test/utils_test.exs @@ -0,0 +1,8 @@ +defmodule UtilsTest do + use ExUnit.Case + doctest Utils + + test "greets the world" do + assert Utils.hello() == :world + end +end From b303f0cc89d0e68b41ba7eff39b52578890fadad Mon Sep 17 00:00:00 2001 From: Thibault Denizet Date: Mon, 3 Dec 2018 12:53:42 +0700 Subject: [PATCH 04/23] Implement ActivityLogger sub application --- apps/activity_logger/.formatter.exs | 4 + apps/activity_logger/.gitignore | 24 ++ apps/activity_logger/README.md | 21 ++ apps/activity_logger/config/config.exs | 8 + apps/activity_logger/config/dev.exs | 5 + apps/activity_logger/config/prod.exs | 5 + apps/activity_logger/config/test.exs | 6 + apps/activity_logger/lib/activity_logger.ex | 25 ++ .../lib/activity_logger/activity_log.ex | 190 ++++++++++++++ .../lib/activity_logger/activity_logging.ex | 62 +++++ .../lib/activity_logger/activity_repo.ex | 78 ++++++ .../lib/activity_logger/application.ex | 19 ++ .../lib/activity_logger/repo.ex | 18 ++ .../lib/activity_logger/system.ex | 6 + .../lib/activity_logger/types/map.ex | 5 + .../lib/activity_logger/vault.ex | 32 +++ apps/activity_logger/mix.exs | 65 +++++ .../migrations/20180907173648_add_audit.exs | 27 ++ ...129045732_rename_audit_to_activity_log.exs | 7 + ...20181129054318_add_test_document_table.exs | 17 ++ .../20181129055336_add_test_users.exs | 15 ++ ...ncrypted_metadata_to_encrypted_changes.exs | 7 + .../activity_logger/activity_log_test.exs | 232 ++++++++++++++++++ apps/activity_logger/test/support/factory.ex | 57 +++++ .../test/support/schema_case.ex | 71 ++++++ .../test/support/test_document.ex | 42 ++++ .../activity_logger/test/support/test_user.ex | 61 +++++ apps/activity_logger/test/test_helper.exs | 3 + 28 files changed, 1112 insertions(+) create mode 100644 apps/activity_logger/.formatter.exs create mode 100644 apps/activity_logger/.gitignore create mode 100644 apps/activity_logger/README.md create mode 100644 apps/activity_logger/config/config.exs create mode 100644 apps/activity_logger/config/dev.exs create mode 100644 apps/activity_logger/config/prod.exs create mode 100644 apps/activity_logger/config/test.exs create mode 100644 apps/activity_logger/lib/activity_logger.ex create mode 100644 apps/activity_logger/lib/activity_logger/activity_log.ex create mode 100644 apps/activity_logger/lib/activity_logger/activity_logging.ex create mode 100644 apps/activity_logger/lib/activity_logger/activity_repo.ex create mode 100644 apps/activity_logger/lib/activity_logger/application.ex create mode 100644 apps/activity_logger/lib/activity_logger/repo.ex create mode 100644 apps/activity_logger/lib/activity_logger/system.ex create mode 100644 apps/activity_logger/lib/activity_logger/types/map.ex create mode 100644 apps/activity_logger/lib/activity_logger/vault.ex create mode 100644 apps/activity_logger/mix.exs create mode 100644 apps/activity_logger/priv/repo/migrations/20180907173648_add_audit.exs create mode 100644 apps/activity_logger/priv/repo/migrations/20181129045732_rename_audit_to_activity_log.exs create mode 100644 apps/activity_logger/priv/repo/migrations/20181129054318_add_test_document_table.exs create mode 100644 apps/activity_logger/priv/repo/migrations/20181129055336_add_test_users.exs create mode 100644 apps/activity_logger/priv/repo/migrations/20181129081628_rename_encrypted_metadata_to_encrypted_changes.exs create mode 100644 apps/activity_logger/test/activity_logger/activity_log_test.exs create mode 100644 apps/activity_logger/test/support/factory.ex create mode 100644 apps/activity_logger/test/support/schema_case.ex create mode 100644 apps/activity_logger/test/support/test_document.ex create mode 100644 apps/activity_logger/test/support/test_user.ex create mode 100644 apps/activity_logger/test/test_helper.exs diff --git a/apps/activity_logger/.formatter.exs b/apps/activity_logger/.formatter.exs new file mode 100644 index 000000000..525446d40 --- /dev/null +++ b/apps/activity_logger/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/apps/activity_logger/.gitignore b/apps/activity_logger/.gitignore new file mode 100644 index 000000000..3f0bc9854 --- /dev/null +++ b/apps/activity_logger/.gitignore @@ -0,0 +1,24 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where 3rd-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +activity_logger-*.tar + diff --git a/apps/activity_logger/README.md b/apps/activity_logger/README.md new file mode 100644 index 000000000..c5f5eacdb --- /dev/null +++ b/apps/activity_logger/README.md @@ -0,0 +1,21 @@ +# ActivityLogger + +**TODO: Add description** + +## Installation + +If [available in Hex](https://hex.pm/docs/publish), the package can be installed +by adding `activity_logger` to your list of dependencies in `mix.exs`: + +```elixir +def deps do + [ + {:activity_logger, "~> 0.1.0"} + ] +end +``` + +Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) +and published on [HexDocs](https://hexdocs.pm). Once published, the docs can +be found at [https://hexdocs.pm/activity_logger](https://hexdocs.pm/activity_logger). + diff --git a/apps/activity_logger/config/config.exs b/apps/activity_logger/config/config.exs new file mode 100644 index 000000000..f89a7a416 --- /dev/null +++ b/apps/activity_logger/config/config.exs @@ -0,0 +1,8 @@ +# This file is responsible for configuring your application +# and its dependencies with the aid of the Mix.Config module. +use Mix.Config + +config :activity_logger, + ecto_repos: [ActivityLogger.Repo] + +import_config "#{Mix.env()}.exs" diff --git a/apps/activity_logger/config/dev.exs b/apps/activity_logger/config/dev.exs new file mode 100644 index 000000000..bd50f52f5 --- /dev/null +++ b/apps/activity_logger/config/dev.exs @@ -0,0 +1,5 @@ +use Mix.Config + +config :activity_logger, ActivityLogger.Repo, + adapter: Ecto.Adapters.Postgres, + url: {:system, "DATABASE_URL", "postgres://localhost/ewallet_dev"} diff --git a/apps/activity_logger/config/prod.exs b/apps/activity_logger/config/prod.exs new file mode 100644 index 000000000..470c53ad6 --- /dev/null +++ b/apps/activity_logger/config/prod.exs @@ -0,0 +1,5 @@ +use Mix.Config + +config :activity_logger, ActivityLogger.Repo, + adapter: Ecto.Adapters.Postgres, + url: {:system, "DATABASE_URL", "postgres://localhost/ewallet_prod"} diff --git a/apps/activity_logger/config/test.exs b/apps/activity_logger/config/test.exs new file mode 100644 index 000000000..ae5109cfb --- /dev/null +++ b/apps/activity_logger/config/test.exs @@ -0,0 +1,6 @@ +use Mix.Config + +config :activity_logger, ActivityLogger.Repo, + adapter: Ecto.Adapters.Postgres, + pool: Ecto.Adapters.SQL.Sandbox, + url: {:system, "DATABASE_URL", "postgres://localhost/ewallet_test"} diff --git a/apps/activity_logger/lib/activity_logger.ex b/apps/activity_logger/lib/activity_logger.ex new file mode 100644 index 000000000..fd09f2c2e --- /dev/null +++ b/apps/activity_logger/lib/activity_logger.ex @@ -0,0 +1,25 @@ +defmodule ActivityLogger do + @moduledoc """ + Documentation for ActivityLogger. + """ + + def configure(schemas_to_activity_log_types) do + update_config(:schemas_to_activity_log_types, schemas_to_activity_log_types) + + update_config( + :activity_log_types_to_schemas, + to_activity_log_types(schemas_to_activity_log_types) + ) + end + + defp to_activity_log_types(schemas_to_activity_log_types) do + Enum.into(schemas_to_activity_log_types, %{}, fn {key, value} -> + {value, key} + end) + end + + defp update_config(name, config) do + current = Application.get_env(:activity_logger, name, %{}) + Application.put_env(:activity_logger, name, Map.merge(current, config)) + end +end diff --git a/apps/activity_logger/lib/activity_logger/activity_log.ex b/apps/activity_logger/lib/activity_logger/activity_log.ex new file mode 100644 index 000000000..b8ea660f8 --- /dev/null +++ b/apps/activity_logger/lib/activity_logger/activity_log.ex @@ -0,0 +1,190 @@ +defmodule ActivityLogger.ActivityLog do + @moduledoc """ + Ecto Schema representing activity_logs. + """ + use Ecto.Schema + use Utils.Types.ExternalID + import Ecto.{Changeset, Query} + alias Ecto.{Changeset, UUID} + + alias ActivityLogger.{ + ActivityLog, + Repo + } + + @primary_key {:uuid, UUID, autogenerate: true} + @primary_key {:uuid, UUID, autogenerate: true} + + schema "activity_log" do + external_id(prefix: "adt_") + + field(:action, :string) + + field(:target_type, :string) + field(:target_uuid, UUID) + field(:target_changes, :map) + field(:target_encrypted_changes, ActivityLogger.Encrypted.Map, default: %{}) + + field(:originator_uuid, UUID) + field(:originator_type, :string) + + field(:metadata, :map, default: %{}) + + field(:inserted_at, :naive_datetime) + end + + defp changeset(changeset, attrs) do + changeset + |> cast(attrs, [ + :action, + :target_type, + :target_uuid, + :target_changes, + :target_encrypted_changes, + :originator_uuid, + :originator_type, + :metadata, + :inserted_at + ]) + |> validate_required([ + :action, + :target_type, + :target_uuid, + :target_changes, + :originator_uuid, + :originator_type, + :inserted_at + ]) + end + + @spec get_schema(String.t()) :: Atom.t() + def get_schema(type) do + config = Application.get_env(:activity_logger, :activity_log_types_to_schemas) + Map.fetch!(config, type) + end + + @spec get_type(Atom.t()) :: String.t() + def get_type(schema) do + config = Application.get_env(:activity_logger, :schemas_to_activity_log_types) + Map.fetch!(config, schema) + end + + @spec all_for_target(map()) :: [%ActivityLog{}] + def all_for_target(record) do + all_for_target(record.__struct__, record.uuid) + end + + @spec all_for_target(String.t(), UUID.t()) :: [%ActivityLog{}] + def all_for_target(type, uuid) when is_binary(type) do + ActivityLog + |> where([a], a.target_type == ^type and a.target_uuid == ^uuid) + |> Repo.all() + end + + @spec all_for_target(Atom.t(), UUID.t()) :: [%ActivityLog{}] + def all_for_target(schema, uuid) do + schema + |> get_type() + |> all_for_target(uuid) + end + + @spec get_initial_activity_log(String.t(), UUID.t()) :: %ActivityLog{} + def get_initial_activity_log(type, uuid) do + Repo.get_by( + ActivityLog, + action: "insert", + target_type: type, + target_uuid: uuid + ) + end + + @spec get_initial_originator(map()) :: map() + def get_initial_originator(record) do + activity_log_type = get_type(record.__struct__) + activity_log = ActivityLog.get_initial_activity_log(activity_log_type, record.uuid) + originator_schema = ActivityLog.get_schema(activity_log.originator_type) + + case originator_schema do + ActivityLogger.System -> + %ActivityLogger.System{uuid: activity_log.originator_uuid} + + schema -> + Repo.get(schema, activity_log.originator_uuid) + end + end + + def insert(action, changeset, record) do + action + |> build_attrs(changeset, record) + |> handle_insert(action) + end + + defp handle_insert(:no_changes, :insert), do: {:ok, nil} + defp handle_insert(:no_changes, :update), do: {:ok, nil} + + defp handle_insert(attrs, _) do + %ActivityLog{} + |> changeset(attrs) + |> Repo.insert() + end + + defp build_attrs(action, changeset, record) do + with {:ok, originator} <- get_originator(changeset, record), + originator_type <- get_type(originator.__struct__), + target_type <- get_type(record.__struct__), + changes <- Map.delete(changeset.changes, :originator), + true <- action == :delete || changes != %{} || :no_changes, + encrypted_changes <- changes[:encrypted_changes], + changes <- Map.delete(changes, :encrypted_changes), + changes <- format_changes(changes) do + %{ + action: Atom.to_string(action), + target_type: target_type, + target_uuid: record.uuid, + target_changes: changes, + target_encrypted_changes: encrypted_changes || %{}, + originator_uuid: originator.uuid, + originator_type: originator_type, + inserted_at: NaiveDateTime.utc_now() + } + else + error -> error + end + end + + defp format_changes(changes) do + changes + |> Enum.into(%{}, fn {field, value} -> + format_change(field, value) + end) + end + + defp format_change(field, values) when is_list(values) do + {field, + Enum.map(values, fn value -> + format_value(value) + end)} + end + + defp format_change(field, value) do + {field, value} + end + + defp format_value(%Changeset{} = value) do + value.data.uuid + end + + defp format_value(value), do: value + + defp get_originator(%Changeset{changes: %{originator: :self}}, record) do + {:ok, record} + end + + defp get_originator(%Changeset{changes: %{originator: originator}}, _) do + {:ok, originator} + end + + defp get_originator(_, _) do + {:error, :no_originator_given} + end +end diff --git a/apps/activity_logger/lib/activity_logger/activity_logging.ex b/apps/activity_logger/lib/activity_logger/activity_logging.ex new file mode 100644 index 000000000..7b8c374a0 --- /dev/null +++ b/apps/activity_logger/lib/activity_logger/activity_logging.ex @@ -0,0 +1,62 @@ +defmodule ActivityLogger.ActivityLogging do + @moduledoc """ + Allows activity_log for Ecto records. + """ + import Ecto.Changeset + alias ActivityLogger.ActivityLog + alias Utils.Types.VirtualStruct + + @doc false + defmacro __using__(_) do + quote do + import ActivityLogger.ActivityLogging + alias ActivityLogger.ActivityLogging + end + end + + @doc """ + A macro that adds the `:originator` virtual field to a schema. + """ + defmacro activity_logging do + quote do + field(:originator, VirtualStruct, virtual: true) + end + end + + @doc """ + Prepares a changeset for activity_log. + """ + def cast_and_validate_required_for_activity_log( + record, + attrs, + cast \\ [], + required \\ [], + encrypted_fields \\ [] + ) do + record + |> Map.delete(:originator) + |> cast(attrs, [:originator | cast]) + |> validate_required([:originator | required]) + |> put_encrypted_changes(encrypted_fields) + end + + def insert_log(action, changeset, record) do + ActivityLog.insert(action, changeset, record) + end + + defp put_encrypted_changes(changeset, []), do: changeset + + defp put_encrypted_changes(changeset, encrypted_fields) when is_list(encrypted_fields) do + {changeset, encrypted_changes} = + Enum.reduce(encrypted_fields, {changeset, %{}}, fn encrypted_field, {changeset, map} -> + { + delete_change(changeset, encrypted_field), + Map.put(map, encrypted_field, get_change(changeset, encrypted_field)) + } + end) + + put_change(changeset, :encrypted_changes, encrypted_changes) + end + + defp put_encrypted_changes(changeset, _), do: changeset +end diff --git a/apps/activity_logger/lib/activity_logger/activity_repo.ex b/apps/activity_logger/lib/activity_logger/activity_repo.ex new file mode 100644 index 000000000..a73ec810f --- /dev/null +++ b/apps/activity_logger/lib/activity_logger/activity_repo.ex @@ -0,0 +1,78 @@ +defmodule ActivityLogger.ActivityRepo do + @moduledoc """ + Module meant to be used inside an Ecto.Repo module to add the + functions generating activity logs. + """ + defmacro __using__(opts) do + quote bind_quoted: [opts: opts] do + @repo opts[:repo] + + import Ecto.{Changeset, Query} + alias ActivityLogger.ActivityLog + alias Ecto.{Multi, Changeset} + + def __repo__ do + @repo + end + + @spec insert_record_with_activity_log(%Changeset{}, Keyword.t(), Multi.t()) :: + {:ok, any()} + | {:error, any()} + | {:error, :no_originator_given} + | {:error, Multi.name(), any(), %{optional(Multi.name()) => any()}} + def insert_record_with_activity_log(changeset, opts \\ [], multi \\ Multi.new()) do + :insert + |> perform(changeset, opts, multi) + |> handle_perform_result(:insert, changeset) + end + + @spec update_record_with_activity_log(%Changeset{}, Keyword.t(), Multi.t()) :: + {:ok, any()} + | {:error, any()} + | {:error, :no_originator_given} + | {:error, Multi.name(), any(), %{optional(Multi.name()) => any()}} + def update_record_with_activity_log(changeset, opts \\ [], multi \\ Multi.new()) do + :update + |> perform(changeset, opts, multi) + |> handle_perform_result(:update, changeset) + end + + @spec delete_record_with_activity_log(map(), Keyword.t(), Multi.t()) :: + {:ok, any()} + | {:error, any()} + | {:error, :no_originator_given} + | {:error, Multi.name(), any(), %{optional(Multi.name()) => any()}} + def delete_record_with_activity_log(changeset, opts \\ [], multi \\ Multi.new()) do + :delete + |> perform(changeset, opts, multi) + |> handle_perform_result(:delete, changeset) + end + + @spec perform(Atom.t(), %Changeset{}, Keyword.t(), Multi.t()) :: + {:ok, any()} + | {:error, any()} + | {:error, :no_originator_given} + | {:error, Multi.name(), any(), %{optional(Multi.name()) => any()}} + def perform(action, changeset, opts \\ [], multi \\ Multi.new()) do + Multi + |> apply(action, [Multi.new(), :record, changeset, opts]) + |> Multi.append(multi) + |> __repo__().transaction() + end + + defp handle_perform_result({:ok, %{record: record}}, action, changeset) do + {:ok, activity_log} = ActivityLog.insert(action, changeset, record) + + {:ok, record} + end + + defp handle_perform_result( + {:error, _failed_operation, changeset, _changes_so_far}, + _action, + _changeset + ) do + {:error, changeset} + end + end + end +end diff --git a/apps/activity_logger/lib/activity_logger/application.ex b/apps/activity_logger/lib/activity_logger/application.ex new file mode 100644 index 000000000..a4a047f10 --- /dev/null +++ b/apps/activity_logger/lib/activity_logger/application.ex @@ -0,0 +1,19 @@ +defmodule ActivityLogger.Application do + # See https://hexdocs.pm/elixir/Application.html + # for more information on OTP Applications + @moduledoc false + + use Application + + def start(_type, _args) do + import Supervisor.Spec + DeferredConfig.populate(:activity_logger) + + children = [ + supervisor(ActivityLogger.Repo, []) + ] + + opts = [strategy: :one_for_one, name: ActivityLogger.Supervisor] + Supervisor.start_link(children, opts) + end +end diff --git a/apps/activity_logger/lib/activity_logger/repo.ex b/apps/activity_logger/lib/activity_logger/repo.ex new file mode 100644 index 000000000..b1044f1a2 --- /dev/null +++ b/apps/activity_logger/lib/activity_logger/repo.ex @@ -0,0 +1,18 @@ +defmodule ActivityLogger.Repo do + use Ecto.Repo, otp_app: :activity_logger + use ActivityLogger.ActivityRepo, repo: ActivityLogger.Repo + + # Workaround an issue where ecto.migrate task won't start the app + # thus DeferredConfig.populate is not getting called. + # + # Ecto itself only supports {:system, ENV_VAR} tuple, but not + # DeferredConfig's {:system, ENV_VAR, DEFAULT} tuple nor the + # {:apply, MFA} tuple. + # + # See also: https://github.com/mrluc/deferred_config/issues/2 + def init(_, config) do + config + |> DeferredConfig.transform_cfg() + |> (fn updated -> {:ok, updated} end).() + end +end diff --git a/apps/activity_logger/lib/activity_logger/system.ex b/apps/activity_logger/lib/activity_logger/system.ex new file mode 100644 index 000000000..162138d1c --- /dev/null +++ b/apps/activity_logger/lib/activity_logger/system.ex @@ -0,0 +1,6 @@ +defmodule ActivityLogger.System do + @moduledoc """ + Module representing the system as originator. + """ + defstruct uuid: "00000000-0000-0000-0000-000000000000" +end diff --git a/apps/activity_logger/lib/activity_logger/types/map.ex b/apps/activity_logger/lib/activity_logger/types/map.ex new file mode 100644 index 000000000..13f842067 --- /dev/null +++ b/apps/activity_logger/lib/activity_logger/types/map.ex @@ -0,0 +1,5 @@ +defmodule ActivityLogger.Encrypted.Map do + @moduledoc false + + use Cloak.Fields.Map, vault: ActivityLogger.Vault +end diff --git a/apps/activity_logger/lib/activity_logger/vault.ex b/apps/activity_logger/lib/activity_logger/vault.ex new file mode 100644 index 000000000..583c736a8 --- /dev/null +++ b/apps/activity_logger/lib/activity_logger/vault.ex @@ -0,0 +1,32 @@ +defmodule ActivityLogger.Vault do + @moduledoc false + + use Cloak.Vault, otp_app: :activity_logger + + @impl Cloak.Vault + def init(config) do + env = Mix.env() + + config = + Keyword.put( + config, + :ciphers, + default: {Cloak.Ciphers.AES.GCM, tag: "AES.GCM.V1", key: secret_key(env)} + ) + + {:ok, config} + end + + defp secret_key(:prod), do: decode_env("EWALLET_SECRET_KEY") + + defp secret_key(_), + do: + <<126, 194, 0, 33, 217, 227, 143, 82, 252, 80, 133, 89, 70, 211, 139, 150, 209, 103, 94, + 240, 194, 108, 166, 100, 48, 144, 207, 242, 93, 244, 27, 144>> + + defp decode_env(var) do + var + |> System.get_env() + |> Base.decode64!() + end +end diff --git a/apps/activity_logger/mix.exs b/apps/activity_logger/mix.exs new file mode 100644 index 000000000..09328ff48 --- /dev/null +++ b/apps/activity_logger/mix.exs @@ -0,0 +1,65 @@ +defmodule ActivityLogger.MixProject do + use Mix.Project + + def project do + [ + app: :activity_logger, + version: "0.1.0", + build_path: "../../_build", + config_path: "../../config/config.exs", + deps_path: "../../deps", + lockfile: "../../mix.lock", + elixir: "~> 1.6", + elixirc_paths: elixirc_paths(Mix.env()), + start_permanent: Mix.env() == :prod, + test_coverage: [tool: ExCoveralls], + preferred_cli_env: [ + coveralls: :test, + "coveralls.detail": :test, + "coveralls.post": :test, + "coveralls.html": :test + ], + aliases: aliases(), + deps: deps() + ] + end + + # Specifies which paths to compile per environment. + defp elixirc_paths(:test), do: ["lib", "test/support"] + defp elixirc_paths(_), do: ["lib"] + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger], + mod: {ActivityLogger.Application, []} + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + {:postgrex, ">= 0.0.0"}, + {:poison, "~> 3.1"}, + {:ecto, "~> 2.1.6"}, + {:deferred_config, "~> 0.1.0"}, + {:ex_machina, "~> 2.2", only: :test}, + {:cloak, "~> 0.7.0-alpha"}, + {:utils, in_umbrella: true} + ] + end + + # Aliases are shortcuts or tasks specific to the current project. + # For example, to create, migrate and run the seeds file at once: + # + # $ mix ecto.setup + # + # See the documentation for `Mix` for more info on aliases. + defp aliases do + [ + "ecto.setup": ["ecto.create", "ecto.migrate"], + "ecto.reset": ["ecto.drop", "ecto.setup"], + test: ["ecto.create --quiet", "ecto.migrate", "test"] + ] + end +end diff --git a/apps/activity_logger/priv/repo/migrations/20180907173648_add_audit.exs b/apps/activity_logger/priv/repo/migrations/20180907173648_add_audit.exs new file mode 100644 index 000000000..38ba78561 --- /dev/null +++ b/apps/activity_logger/priv/repo/migrations/20180907173648_add_audit.exs @@ -0,0 +1,27 @@ +defmodule ActivityLogger.Repo.Migrations.AddAudit do + use Ecto.Migration + + def change do + create table(:audit, primary_key: false) do + add :uuid, :uuid, primary_key: true + add :id, :string, null: false + + add :action, :string, null: false + + add :target_uuid, :uuid, null: false + add :target_type, :string, null: false + add :target_changes, :map, null: false + add :target_encrypted_metadata, :binary + + add :originator_uuid, :uuid + add :originator_type, :string + + add :metadata, :map + + add :inserted_at, :naive_datetime + end + + create index(:audit, [:target_uuid, :target_type]) + create index(:audit, [:originator_uuid, :originator_type]) + end +end diff --git a/apps/activity_logger/priv/repo/migrations/20181129045732_rename_audit_to_activity_log.exs b/apps/activity_logger/priv/repo/migrations/20181129045732_rename_audit_to_activity_log.exs new file mode 100644 index 000000000..c85fe2f75 --- /dev/null +++ b/apps/activity_logger/priv/repo/migrations/20181129045732_rename_audit_to_activity_log.exs @@ -0,0 +1,7 @@ +defmodule ActivityLogger.Repo.Migrations.RenameAuditToActivityLogger do + use Ecto.Migration + + def change do + rename table(:audit), to: table(:activity_log) + end +end diff --git a/apps/activity_logger/priv/repo/migrations/20181129054318_add_test_document_table.exs b/apps/activity_logger/priv/repo/migrations/20181129054318_add_test_document_table.exs new file mode 100644 index 000000000..9713f5510 --- /dev/null +++ b/apps/activity_logger/priv/repo/migrations/20181129054318_add_test_document_table.exs @@ -0,0 +1,17 @@ +defmodule ActivityLogger.Repo.Migrations.AddTestDocumentTable do + use Ecto.Migration + + def change do + if Mix.env == :test do + create table(:test_document, primary_key: false) do + add :uuid, :uuid, primary_key: true + add :id, :string, null: false + add :title, :string + add :body, :string + add :secret_data, :binary + + timestamps() + end + end + end +end diff --git a/apps/activity_logger/priv/repo/migrations/20181129055336_add_test_users.exs b/apps/activity_logger/priv/repo/migrations/20181129055336_add_test_users.exs new file mode 100644 index 000000000..d34c6fcf7 --- /dev/null +++ b/apps/activity_logger/priv/repo/migrations/20181129055336_add_test_users.exs @@ -0,0 +1,15 @@ +defmodule ActivityLogger.Repo.Migrations.AddTestUsers do + use Ecto.Migration + + def change do + if Mix.env == :test do + create table(:test_user, primary_key: false) do + add :uuid, :uuid, primary_key: true + add :id, :string, null: false + add :username, :string + + timestamps() + end + end + end +end diff --git a/apps/activity_logger/priv/repo/migrations/20181129081628_rename_encrypted_metadata_to_encrypted_changes.exs b/apps/activity_logger/priv/repo/migrations/20181129081628_rename_encrypted_metadata_to_encrypted_changes.exs new file mode 100644 index 000000000..60b9e7d78 --- /dev/null +++ b/apps/activity_logger/priv/repo/migrations/20181129081628_rename_encrypted_metadata_to_encrypted_changes.exs @@ -0,0 +1,7 @@ +defmodule ActivityLogger.Repo.Migrations.RenameEncryptedMetadataToEncryptedChanges do + use Ecto.Migration + + def change do + rename table(:activity_log), :target_encrypted_metadata, to: :target_encrypted_changes + end +end diff --git a/apps/activity_logger/test/activity_logger/activity_log_test.exs b/apps/activity_logger/test/activity_logger/activity_log_test.exs new file mode 100644 index 000000000..b797f1ef2 --- /dev/null +++ b/apps/activity_logger/test/activity_logger/activity_log_test.exs @@ -0,0 +1,232 @@ +defmodule ActivityLogger.ActivityLogTest do + use ExUnit.Case + import ActivityLogger.Factory + alias Ecto.Adapters.SQL.Sandbox + + alias ActivityLogger.{ + System, + ActivityLog, + TestDocument, + TestUser + } + + setup do + :ok = Sandbox.checkout(ActivityLogger.Repo) + + ActivityLogger.configure(%{ + ActivityLogger.System => "system", + ActivityLogger.TestDocument => "test_document", + ActivityLogger.TestUser => "test_user" + }) + end + + describe "ActivityLog.get_schema/1" do + test "gets the schema from a type" do + assert ActivityLog.get_schema("system") == System + end + end + + describe "ActivityLog.get_type/1" do + test "gets the type from a schema" do + assert ActivityLog.get_type(ActivityLogger.System) == "system" + end + end + + describe "ActivityLog.all_for_target/1" do + test "returns all activity_logs for a target" do + {:ok, _user} = :test_user |> params_for() |> TestUser.insert() + {:ok, _user} = :test_user |> params_for() |> TestUser.insert() + {:ok, user} = :test_user |> params_for() |> TestUser.insert() + + {:ok, user} = + TestUser.update(user, %{ + username: "Johnny", + originator: %System{} + }) + + activity_logs = ActivityLog.all_for_target(user) + + assert length(activity_logs) == 2 + + results = Enum.map(activity_logs, fn a -> {a.action, a.originator_type, a.target_type} end) + + assert Enum.member?(results, {"insert", "system", "test_user"}) + assert Enum.member?(results, {"update", "system", "test_user"}) + end + end + + describe "ActivityLog.all_for_target/2" do + test "returns all activity_logs for a target when given a string" do + {:ok, _user} = :test_user |> params_for() |> TestUser.insert() + {:ok, _user} = :test_user |> params_for() |> TestUser.insert() + {:ok, user} = :test_user |> params_for() |> TestUser.insert() + + {:ok, user} = + TestUser.update(user, %{ + username: "Johnny", + originator: %System{} + }) + + activity_logs = ActivityLog.all_for_target("test_user", user.uuid) + + assert length(activity_logs) == 2 + + results = Enum.map(activity_logs, fn a -> {a.action, a.originator_type, a.target_type} end) + assert Enum.member?(results, {"insert", "system", "test_user"}) + assert Enum.member?(results, {"update", "system", "test_user"}) + end + + test "returns all activity_logs for a target when given a module name" do + {:ok, _user} = :test_user |> params_for() |> TestUser.insert() + {:ok, _user} = :test_user |> params_for() |> TestUser.insert() + {:ok, user} = :test_user |> params_for() |> TestUser.insert() + + {:ok, user} = + TestUser.update(user, %{ + username: "Johnny", + originator: %System{} + }) + + activity_logs = ActivityLog.all_for_target(TestUser, user.uuid) + + assert length(activity_logs) == 2 + + results = Enum.map(activity_logs, fn a -> {a.action, a.originator_type, a.target_type} end) + assert Enum.member?(results, {"insert", "system", "test_user"}) + assert Enum.member?(results, {"update", "system", "test_user"}) + end + end + + describe "ActivityLog.get_initial_activity_log/2" do + test "gets the initial activity_log for a record" do + initial_originator = insert(:test_user) + + {:ok, user} = + :test_user |> params_for(%{originator: initial_originator}) |> TestUser.insert() + + {:ok, user} = + TestUser.update(user, %{ + username: "Johnny", + originator: %System{} + }) + + activity_log = ActivityLog.get_initial_activity_log("test_user", user.uuid) + + assert activity_log.originator_type == "test_user" + assert activity_log.originator_uuid == initial_originator.uuid + assert activity_log.target_type == "test_user" + assert activity_log.target_uuid == user.uuid + assert activity_log.action == "insert" + assert activity_log.inserted_at != nil + end + end + + describe "ActivityLog.get_initial_originator/2" do + test "gets the initial originator for a record" do + initial_originator = insert(:test_user) + + {:ok, user} = + :test_user |> params_for(%{originator: initial_originator}) |> TestUser.insert() + + {:ok, user} = + TestUser.update(user, %{ + username: "Johnny", + originator: %System{} + }) + + originator = ActivityLog.get_initial_originator(user) + + assert originator.__struct__ == TestUser + assert originator.uuid == initial_originator.uuid + end + end + + describe "ActivityLog.insert_record_with_activity_log/2" do + test "inserts an activity_log and a document with encrypted metadata" do + admin = insert(:test_user) + + {res, record} = + :test_document + |> params_for(%{ + secret_data: %{something: "cool"}, + originator: admin + }) + |> TestDocument.insert() + + activity_log = record |> ActivityLog.all_for_target() |> Enum.at(0) + + assert res == :ok + + assert activity_log.action == "insert" + assert activity_log.originator_type == "test_user" + assert activity_log.originator_uuid == admin.uuid + assert activity_log.target_type == "test_document" + assert activity_log.target_uuid == record.uuid + + assert activity_log.target_changes == %{ + "title" => record.title, + "body" => record.body + } + + assert activity_log.target_encrypted_changes == %{ + "secret_data" => %{"something" => "cool"} + } + + assert record |> ActivityLog.all_for_target() |> length() == 1 + end + end + + describe "ActivityLog.update_record_with_activity_log/2" do + test "does not insert an activity_log when updating a user with no changes" do + admin = insert(:test_user) + {:ok, user} = :test_user |> params_for() |> TestUser.insert() + params = params_for(:test_user, %{username: "John", originator: admin}) + {res, _record} = TestUser.update(user, params) + + assert res == :ok + assert user |> ActivityLog.all_for_target() |> length() == 1 + end + + test "inserts an activity_log when updating a user" do + admin = insert(:test_user) + {:ok, user} = :test_user |> params_for() |> TestUser.insert() + params = params_for(:test_user, %{username: "Johnny", originator: admin}) + {res, record} = TestUser.update(user, params) + activity_log = record |> ActivityLog.all_for_target() |> Enum.at(0) + + assert res == :ok + + assert activity_log.action == "update" + assert activity_log.originator_type == "test_user" + assert activity_log.originator_uuid == admin.uuid + assert activity_log.target_type == "test_user" + assert activity_log.target_uuid == record.uuid + + assert activity_log.target_changes == %{ + "username" => record.username + } + + assert activity_log.target_encrypted_changes == %{} + + assert user |> ActivityLog.all_for_target() |> length() == 2 + end + end + + describe "perform/4" do + test "inserts an activity_log and a user as well as a document" do + admin = insert(:test_user) + {res, record} = :test_user |> params_for(%{originator: admin}) |> TestUser.insert() + activity_log = record |> ActivityLog.all_for_target() |> Enum.at(0) + + assert res == :ok + + assert activity_log.action == "insert" + assert activity_log.originator_type == "test_user" + assert activity_log.originator_uuid == admin.uuid + assert activity_log.target_type == "test_user" + assert activity_log.target_uuid == record.uuid + + assert record |> ActivityLog.all_for_target() |> length() == 1 + end + end +end diff --git a/apps/activity_logger/test/support/factory.ex b/apps/activity_logger/test/support/factory.ex new file mode 100644 index 000000000..e363f3686 --- /dev/null +++ b/apps/activity_logger/test/support/factory.ex @@ -0,0 +1,57 @@ +defmodule ActivityLogger.Factory do + @moduledoc """ + Factories used for testing. + """ + use ExMachina.Ecto, repo: ActivityLogger.Repo + alias ExMachina.Strategy + + alias ActivityLogger.{ + System, + ActivityLog, + TestDocument, + TestUser + } + + @doc """ + Get factory name (as atom) from schema. + + The function should explicitly handle schemas that produce incorrect factory name, + e.g. when APIKey becomes :a_p_i_key + """ + def get_factory(APIKey), do: :api_key + + def get_factory(schema) when is_atom(schema) do + schema + |> struct + |> Strategy.name_from_struct() + end + + def activity_log_factory do + system = %System{} + + %ActivityLog{ + action: "insert", + target_type: ActivityLog.get_type(system.__struct__), + target_uuid: system.uuid, + target_changes: %{some: "change"}, + originator_uuid: system.uuid, + originator_type: ActivityLog.get_type(system.__struct__) + } + end + + def test_document_factory do + %TestDocument{ + title: "My Document", + body: "Some content", + secret_data: %{some: "secret"}, + originator: %System{} + } + end + + def test_user_factory do + %TestUser{ + username: "John", + originator: %System{} + } + end +end diff --git a/apps/activity_logger/test/support/schema_case.ex b/apps/activity_logger/test/support/schema_case.ex new file mode 100644 index 000000000..edd1c9750 --- /dev/null +++ b/apps/activity_logger/test/support/schema_case.ex @@ -0,0 +1,71 @@ +defmodule ActivityLogger.SchemaCase do + @moduledoc """ + This module defines common behaviors shared for ActivityLogger schema tests. + + Note that all macros below are quoted at `test ... do ... end` level + with macro names starting with `test_`. This is so that in test cases, + when these macros are used, they still resemble the original code. + + ## Example + + ### Original code: + + ``` + describe "SomeSchema.insert/1" do + test "generates a UUID for SomeSchema" do + # ... + # lots of test code here + # ... + end + + test "generates inserted_at and updated_at values" do + # ... + # lots of test code here + # ... + end + end + ``` + + ### Using macro: + + ``` + describe "SomeSchema.insert/1" do + test_insert_generate_uuid SomeSchema + test_insert_generate_timestamps SomeSchema + end + ``` + """ + + defmacro __using__(_opts) do + quote do + use ExUnit.Case + import ActivityLogger.{Factory, SchemaCase} + alias Ecto.Adapters.SQL + alias Ecto.Adapters.SQL.Sandbox + alias ActivityLogger.Repo + + setup do + Sandbox.checkout(ActivityLogger.Repo) + end + end + end + + @doc """ + Test schema's factory produces params that can be inserted successfully. + """ + defmacro test_has_valid_factory(schema) do + quote do + test "produces valid params and inserts successfully" do + schema = unquote(schema) + + {res, val} = + schema + |> get_factory + |> params_for + |> schema.insert() + + assert res == :ok + end + end + end +end diff --git a/apps/activity_logger/test/support/test_document.ex b/apps/activity_logger/test/support/test_document.ex new file mode 100644 index 000000000..3c8faa7a8 --- /dev/null +++ b/apps/activity_logger/test/support/test_document.ex @@ -0,0 +1,42 @@ +defmodule ActivityLogger.TestDocument do + @moduledoc """ + Ecto Schema representing test documents. + """ + use Ecto.Schema + use Utils.Types.ExternalID + use ActivityLogger.ActivityLogging + alias Ecto.UUID + alias ActivityLogger.Repo + + alias ActivityLogger.TestDocument + + @primary_key {:uuid, UUID, autogenerate: true} + + schema "test_document" do + external_id(prefix: "tdc_") + + field(:title, :string) + field(:body, :string) + field(:secret_data, ActivityLogger.Encrypted.Map, default: %{}) + + timestamps() + activity_logging() + end + + defp changeset(changeset, attrs) do + changeset + |> cast_and_validate_required_for_activity_log( + attrs, + [:title, :body, :secret_data], + [:title], + [:secret_data] + ) + end + + @spec insert(map()) :: {:ok, %TestDocument{}} | {:error, Ecto.Changeset.t()} + def insert(attrs) do + %TestDocument{} + |> changeset(attrs) + |> Repo.insert_record_with_activity_log([]) + end +end diff --git a/apps/activity_logger/test/support/test_user.ex b/apps/activity_logger/test/support/test_user.ex new file mode 100644 index 000000000..80621e7b6 --- /dev/null +++ b/apps/activity_logger/test/support/test_user.ex @@ -0,0 +1,61 @@ +defmodule ActivityLogger.TestUser do + @moduledoc """ + Ecto Schema representing test documents. + """ + use Ecto.Schema + use Utils.Types.ExternalID + use ActivityLogger.ActivityLogging + alias Ecto.{Multi, UUID} + alias ActivityLogger.{TestDocument, Repo, TestUser} + + @primary_key {:uuid, UUID, autogenerate: true} + + schema "test_user" do + external_id(prefix: "tus_") + + field(:username, :string) + + timestamps() + activity_logging() + end + + defp changeset(changeset, attrs) do + changeset + |> cast_and_validate_required_for_activity_log( + attrs, + [:username], + [:username] + ) + end + + @spec insert(map()) :: {:ok, %TestUser{}} | {:error, Ecto.Changeset.t()} + def insert(attrs) do + %TestUser{} + |> changeset(attrs) + |> Repo.insert_record_with_activity_log([]) + end + + def insert_with_document(attrs) do + %TestUser{} + |> changeset(attrs) + |> Repo.insert_record_with_activity_log( + [], + Multi.run(Multi.new(), :document, fn %{record: record} -> + TestDocument.insert(%{ + title: record.username, + originator: record + }) + end) + ) + end + + @doc """ + Updates a user with the provided attributes. + """ + @spec update(%TestUser{}, map()) :: {:ok, %TestUser{}} | {:error, Ecto.Changeset.t()} + def update(%TestUser{} = user, attrs) do + user + |> changeset(attrs) + |> Repo.update_record_with_activity_log() + end +end diff --git a/apps/activity_logger/test/test_helper.exs b/apps/activity_logger/test/test_helper.exs new file mode 100644 index 000000000..2746d3dec --- /dev/null +++ b/apps/activity_logger/test/test_helper.exs @@ -0,0 +1,3 @@ +{:ok, _} = Application.ensure_all_started(:ex_machina) +ExUnit.start() +Ecto.Adapters.SQL.Sandbox.mode(ActivityLogger.Repo, :manual) From 4d33658c4fac4637fdb99242081cdd0c5408f8d7 Mon Sep 17 00:00:00 2001 From: Thibault Denizet Date: Mon, 3 Dec 2018 12:54:32 +0700 Subject: [PATCH 05/23] Integrate ActivityLogger in EWalletDB --- apps/ewallet_db/config/config.exs | 16 -- apps/ewallet_db/lib/ewallet_db/account.ex | 74 +++-- .../ewallet_db/lib/ewallet_db/account_user.ex | 18 +- apps/ewallet_db/lib/ewallet_db/api_key.ex | 41 +-- apps/ewallet_db/lib/ewallet_db/application.ex | 23 ++ apps/ewallet_db/lib/ewallet_db/audit.ex | 202 ------------- apps/ewallet_db/lib/ewallet_db/auditable.ex | 43 --- apps/ewallet_db/lib/ewallet_db/auth_token.ex | 58 ++-- apps/ewallet_db/lib/ewallet_db/category.ex | 36 +-- .../lib/ewallet_db/exchange_pair.ex | 38 +-- .../lib/ewallet_db/forget_password_request.ex | 24 +- apps/ewallet_db/lib/ewallet_db/invite.ex | 41 +-- apps/ewallet_db/lib/ewallet_db/key.ex | 33 +-- apps/ewallet_db/lib/ewallet_db/membership.ex | 43 +-- .../lib/ewallet_db/membership_checker.ex | 16 +- apps/ewallet_db/lib/ewallet_db/mint.ex | 48 ++-- apps/ewallet_db/lib/ewallet_db/repo.ex | 1 + apps/ewallet_db/lib/ewallet_db/role.ex | 26 +- apps/ewallet_db/lib/ewallet_db/soft_delete.ex | 33 ++- apps/ewallet_db/lib/ewallet_db/token.ex | 97 ++++--- apps/ewallet_db/lib/ewallet_db/transaction.ex | 158 ++++++----- .../lib/ewallet_db/transaction_consumption.ex | 164 ++++++----- .../lib/ewallet_db/transaction_request.ex | 117 ++++---- .../lib/ewallet_db/update_email_request.ex | 25 +- apps/ewallet_db/lib/ewallet_db/user.ex | 152 +++++----- apps/ewallet_db/lib/ewallet_db/vault.ex | 32 +++ apps/ewallet_db/lib/ewallet_db/wallet.ex | 49 ++-- apps/ewallet_db/mix.exs | 3 +- ...2_change_secret_key_to_secret_key_hash.exs | 2 +- .../migrations/20180907173648_add_audit.exs | 27 -- apps/ewallet_db/priv/repo/seeds/01_user.exs | 2 +- .../repo/seeds_sample/00_admin_panel_user.exs | 4 +- .../priv/repo/seeds_sample/00_user.exs | 2 +- .../priv/repo/seeds_test/01_user.exs | 2 +- .../test/ewallet_db/account_test.exs | 43 ++- .../test/ewallet_db/api_key_test.exs | 47 +++- .../ewallet_db/test/ewallet_db/audit_test.exs | 265 ------------------ .../test/ewallet_db/auth_token_test.exs | 33 +-- .../test/ewallet_db/category_test.exs | 27 +- .../test/ewallet_db/exchange_pair_test.exs | 8 +- .../test/ewallet_db/invite_test.exs | 21 +- apps/ewallet_db/test/ewallet_db/key_test.exs | 70 ++++- .../test/ewallet_db/membership_test.exs | 57 ++-- apps/ewallet_db/test/ewallet_db/role_test.exs | 5 +- .../test/ewallet_db/soft_delete_test.exs | 13 +- .../ewallet_db/test/ewallet_db/token_test.exs | 11 +- .../transaction_consumption_test.exs | 20 +- .../ewallet_db/transaction_request_test.exs | 2 +- .../test/ewallet_db/transaction_test.exs | 2 +- apps/ewallet_db/test/ewallet_db/user_test.exs | 14 +- .../test/ewallet_db/wallet_test.exs | 2 +- apps/ewallet_db/test/support/factory.ex | 35 +-- apps/ewallet_db/test/support/schema_case.ex | 19 +- .../ewallet_db/test/support/validator_case.ex | 1 + apps/ewallet_db/test/test_helper.exs | 1 + 55 files changed, 1074 insertions(+), 1272 deletions(-) delete mode 100644 apps/ewallet_db/lib/ewallet_db/audit.ex delete mode 100644 apps/ewallet_db/lib/ewallet_db/auditable.ex create mode 100644 apps/ewallet_db/lib/ewallet_db/vault.ex delete mode 100644 apps/ewallet_db/priv/repo/migrations/20180907173648_add_audit.exs delete mode 100644 apps/ewallet_db/test/ewallet_db/audit_test.exs diff --git a/apps/ewallet_db/config/config.exs b/apps/ewallet_db/config/config.exs index 54dd772e4..c7728038e 100644 --- a/apps/ewallet_db/config/config.exs +++ b/apps/ewallet_db/config/config.exs @@ -1,24 +1,8 @@ use Mix.Config -audits = %{ - EWalletConfig.System => "system", - EWalletDB.User => "user", - EWalletDB.Invite => "invite", - EWalletDB.Key => "key", - EWalletDB.ForgetPasswordRequest => "forget_password_request", - EWalletDB.UpdateEmailRequest => "update_email_request", - EWalletDB.AccountUser => "account_user", - EWalletDB.Transaction => "transaction", - EWalletDB.Mint => "mint", - EWalletDB.TransactionRequest => "transaction_request", - EWalletDB.TransactionConsumption => "transaction_consumption" -} - config :ewallet_db, ecto_repos: [EWalletDB.Repo], env: Mix.env(), - schemas_to_audit_types: audits, - audit_types_to_schemas: Enum.into(audits, %{}, fn {key, value} -> {value, key} end), settings: [ :base_url, :min_password_length, diff --git a/apps/ewallet_db/lib/ewallet_db/account.ex b/apps/ewallet_db/lib/ewallet_db/account.ex index a35871e94..5ec2f9160 100644 --- a/apps/ewallet_db/lib/ewallet_db/account.ex +++ b/apps/ewallet_db/lib/ewallet_db/account.ex @@ -4,12 +4,12 @@ defmodule EWalletDB.Account do """ use Ecto.Schema use Arc.Ecto.Schema - use EWalletConfig.Types.ExternalID - use EWalletDB.Auditable + use Utils.Types.ExternalID + use ActivityLogger.ActivityLogging import Ecto.{Changeset, Query} import EWalletDB.Helpers.Preloader import EWalletDB.AccountValidator - alias EWalletConfig.{Intersecter, Helpers.InputAttribute} + alias Utils.{Intersecter, Helpers.InputAttribute} alias Ecto.{Multi, UUID} alias EWalletDB.{ @@ -98,14 +98,17 @@ defmodule EWalletDB.Account do ) timestamps() - auditable() + activity_logging() end @spec changeset(%Account{}, map()) :: Ecto.Changeset.t() defp changeset(%Account{} = account, attrs) do account - |> cast(attrs, [:name, :description, :parent_uuid, :metadata, :encrypted_metadata]) - |> validate_required([:name]) + |> cast_and_validate_required_for_activity_log( + attrs, + [:name, :description, :parent_uuid, :metadata, :encrypted_metadata], + [:name] + ) |> validate_parent_uuid() |> validate_account_level(@child_level_limit) |> unique_constraint(:name) @@ -133,7 +136,9 @@ defmodule EWalletDB.Account do @spec avatar_changeset(Ecto.Changeset.t() | %Account{}, map()) :: Ecto.Changeset.t() | %Account{} | no_return() defp avatar_changeset(changeset, attrs) do - cast_attachments(changeset, attrs, [:avatar]) + changeset + |> cast_and_validate_required_for_activity_log(attrs, []) + |> cast_attachments(attrs, [:avatar]) end @doc """ @@ -141,23 +146,22 @@ defmodule EWalletDB.Account do """ @spec insert(map()) :: {:ok, %Account{}} | {:error, Ecto.Changeset.t()} def insert(attrs) do - multi = + %Account{} + |> changeset(attrs) + |> Repo.insert_record_with_activity_log( + [], Multi.new() - |> Multi.insert(:account, changeset(%Account{}, attrs)) - |> Multi.run(:wallet, fn %{account: account} -> + |> Multi.run(:wallet, fn %{record: account} -> _ = insert_wallet(account, Wallet.primary()) insert_wallet(account, Wallet.burn()) end) + ) + |> case do + {:ok, account} -> + {:ok, Repo.preload(account, [:wallets])} - case Repo.transaction(multi) do - {:ok, result} -> - account = result.account |> Repo.preload([:wallets]) - {:ok, account} - - # Only the account insertion should fail. If the wallet insert fails, there is - # something wrong with our code. - {:error, _failed_operation, changeset, _changes_so_far} -> - {:error, changeset} + error -> + error end end @@ -168,7 +172,7 @@ defmodule EWalletDB.Account do def update(%Account{} = account, attrs) do changeset = changeset(account, attrs) - case Repo.update(changeset) do + case Repo.update_record_with_activity_log(changeset) do {:ok, account} -> {:ok, get(account.id)} @@ -186,7 +190,8 @@ defmodule EWalletDB.Account do account_uuid: account.uuid, name: identifier, identifier: identifier, - metadata: %{} + metadata: %{}, + originator: account } |> Wallet.insert() end @@ -195,17 +200,19 @@ defmodule EWalletDB.Account do Stores an avatar for the given account. """ @spec store_avatar(%Account{}, map()) :: %Account{} | nil | no_return() - def store_avatar(%Account{} = account, attrs) do + def store_avatar(%Account{} = account, %{"originator" => originator} = attrs) do attrs = - case attrs["avatar"] do + attrs["avatar"] + |> case do "" -> %{avatar: nil} "null" -> %{avatar: nil} avatar -> %{avatar: avatar} end + |> Map.put(:originator, originator) changeset = avatar_changeset(account, attrs) - case Repo.update(changeset) do + case Repo.update_record_with_activity_log(changeset) do {:ok, account} -> get(account.id) result -> result end @@ -532,8 +539,9 @@ defmodule EWalletDB.Account do end) end - @spec add_category(%Account{}, %Category{}) :: {:ok, %Account{}} | {:error, Ecto.Changeset.t()} - def add_category(account, category) do + @spec add_category(%Account{}, %Category{}, map()) :: + {:ok, %Account{}} | {:error, Ecto.Changeset.t()} + def add_category(account, category, originator) do account = Repo.preload(account, :categories) category_ids = @@ -542,12 +550,15 @@ defmodule EWalletDB.Account do |> Enum.map(fn existing -> existing.id end) |> List.insert_at(0, category.id) - Account.update(account, %{category_ids: category_ids}) + Account.update(account, %{ + category_ids: category_ids, + originator: originator + }) end - @spec remove_category(%Account{}, %Category{}) :: + @spec remove_category(%Account{}, %Category{}, map()) :: {:ok, %Account{}} | {:error, Ecto.Changeset.t()} - def remove_category(account, category) do + def remove_category(account, category, originator) do account = Repo.preload(account, :categories) remaining = @@ -557,6 +568,9 @@ defmodule EWalletDB.Account do category_ids = Enum.map(remaining, fn c -> c.id end) - Account.update(account, %{category_ids: category_ids}) + Account.update(account, %{ + category_ids: category_ids, + originator: originator + }) end end diff --git a/apps/ewallet_db/lib/ewallet_db/account_user.ex b/apps/ewallet_db/lib/ewallet_db/account_user.ex index 15a03b4e9..bb1b2a520 100644 --- a/apps/ewallet_db/lib/ewallet_db/account_user.ex +++ b/apps/ewallet_db/lib/ewallet_db/account_user.ex @@ -4,17 +4,15 @@ defmodule EWalletDB.AccountUser do """ use Ecto.Schema use Arc.Ecto.Schema + use ActivityLogger.ActivityLogging import Ecto.Changeset alias Ecto.UUID - alias EWalletDB.{Audit, Account, AccountUser, User} - - alias EWalletConfig.Types.VirtualStruct + alias EWalletDB.{Account, AccountUser, User} + alias EWalletDB.Repo @primary_key {:uuid, UUID, autogenerate: true} schema "account_user" do - field(:originator, VirtualStruct, virtual: true) - belongs_to( :account, Account, @@ -32,13 +30,17 @@ defmodule EWalletDB.AccountUser do ) timestamps() + activity_logging() end @spec changeset(account :: %AccountUser{}, attrs :: map()) :: Ecto.Changeset.t() defp changeset(%AccountUser{} = account, attrs) do account - |> cast(attrs, [:account_uuid, :user_uuid, :originator]) - |> validate_required([:account_uuid, :user_uuid, :originator]) + |> cast_and_validate_required_for_activity_log( + attrs, + [:account_uuid, :user_uuid], + [:account_uuid, :user_uuid, :originator] + ) |> unique_constraint(:account_uuid, name: :account_user_account_uuid_user_uuid_index) |> assoc_constraint(:account) |> assoc_constraint(:user) @@ -50,7 +52,7 @@ defmodule EWalletDB.AccountUser do %AccountUser{} |> changeset(attrs) - |> Audit.insert_record_with_audit(opts) + |> Repo.insert_record_with_activity_log(opts) end def link(account_uuid, user_uuid, originator) do diff --git a/apps/ewallet_db/lib/ewallet_db/api_key.ex b/apps/ewallet_db/lib/ewallet_db/api_key.ex index 890d5c9f4..2cfacc8ea 100644 --- a/apps/ewallet_db/lib/ewallet_db/api_key.ex +++ b/apps/ewallet_db/lib/ewallet_db/api_key.ex @@ -4,11 +4,11 @@ defmodule EWalletDB.APIKey do """ use Ecto.Schema use EWalletDB.SoftDelete - use EWalletConfig.Types.ExternalID - use EWalletDB.Auditable + use Utils.Types.ExternalID + use ActivityLogger.ActivityLogging import Ecto.Changeset alias Ecto.UUID - alias EWalletConfig.Helpers.Crypto + alias Utils.Helpers.Crypto alias EWalletDB.{Account, APIKey, Repo} @primary_key {:uuid, UUID, autogenerate: true} @@ -40,13 +40,16 @@ defmodule EWalletDB.APIKey do field(:enabled, :boolean, default: true) timestamps() soft_delete() - auditable() + activity_logging() end defp changeset(%APIKey{} = key, attrs) do key - |> cast(attrs, [:key, :owner_app, :account_uuid, :enabled, :exchange_address]) - |> validate_required([:key, :owner_app, :account_uuid]) + |> cast_and_validate_required_for_activity_log( + attrs, + [:key, :owner_app, :account_uuid, :enabled, :exchange_address], + [:key, :owner_app, :account_uuid] + ) |> unique_constraint(:key) |> assoc_constraint(:account) |> assoc_constraint(:exchange_wallet) @@ -54,14 +57,20 @@ defmodule EWalletDB.APIKey do defp enable_changeset(%APIKey{} = key, attrs) do key - |> cast(attrs, [:enabled]) - |> validate_required([:enabled]) + |> cast_and_validate_required_for_activity_log( + attrs, + [:enabled], + [:enabled] + ) end defp update_changeset(%APIKey{} = key, attrs) do key - |> cast(attrs, [:enabled, :exchange_address]) - |> validate_required([:enabled]) + |> cast_and_validate_required_for_activity_log( + attrs, + [:enabled, :exchange_address], + [:enabled] + ) end @doc """ @@ -88,7 +97,7 @@ defmodule EWalletDB.APIKey do %APIKey{} |> changeset(attrs) - |> Repo.insert() + |> Repo.insert_record_with_activity_log() end @doc """ @@ -99,13 +108,13 @@ defmodule EWalletDB.APIKey do api_key |> update_changeset(attrs) - |> Repo.update() + |> Repo.update_record_with_activity_log() end def update(%APIKey{} = api_key, attrs) do api_key |> update_changeset(attrs) - |> Repo.update() + |> Repo.update_record_with_activity_log() end @doc """ @@ -114,7 +123,7 @@ defmodule EWalletDB.APIKey do def enable_or_disable(%APIKey{} = api_key, attrs) do api_key |> enable_changeset(attrs) - |> Repo.update() + |> Repo.update_record_with_activity_log() end defp get_master_account_uuid do @@ -194,10 +203,10 @@ defmodule EWalletDB.APIKey do @doc """ Soft-deletes the given API key. """ - def delete(api_key), do: SoftDelete.delete(api_key) + def delete(api_key, originator), do: SoftDelete.delete(api_key, originator) @doc """ Restores the given API key from soft-delete. """ - def restore(api_key), do: SoftDelete.restore(api_key) + def restore(api_key, originator), do: SoftDelete.restore(api_key, originator) end diff --git a/apps/ewallet_db/lib/ewallet_db/application.ex b/apps/ewallet_db/lib/ewallet_db/application.ex index b681f97d2..7ac442e1c 100644 --- a/apps/ewallet_db/lib/ewallet_db/application.ex +++ b/apps/ewallet_db/lib/ewallet_db/application.ex @@ -14,6 +14,29 @@ defmodule EWalletDB.Application do settings = Application.get_env(:ewallet_db, :settings) Config.register_and_load(:ewallet_db, settings) + ActivityLogger.configure(%{ + ActivityLogger.System => "system", + EWalletDB.User => "user", + EWalletDB.Invite => "invite", + EWalletDB.Key => "key", + EWalletDB.ForgetPasswordRequest => "forget_password_request", + EWalletDB.UpdateEmailRequest => "update_email_request", + EWalletDB.AccountUser => "account_user", + EWalletDB.Transaction => "transaction", + EWalletDB.Mint => "mint", + EWalletDB.TransactionRequest => "transaction_request", + EWalletDB.TransactionConsumption => "transaction_consumption", + EWalletDB.Account => "account", + EWalletDB.Category => "category", + EWalletDB.ExchangePair => "exchange_pair", + EWalletDB.Wallet => "wallet", + EWalletDB.Membership => "membership", + EWalletDB.AuthToken => "auth_token", + EWalletDB.APIKey => "api_key", + EWalletDB.Token => "token", + EWalletDB.Role => "role" + }) + # Config.configure_file_storage() # List all child processes to be supervised diff --git a/apps/ewallet_db/lib/ewallet_db/audit.ex b/apps/ewallet_db/lib/ewallet_db/audit.ex deleted file mode 100644 index 5870f61f0..000000000 --- a/apps/ewallet_db/lib/ewallet_db/audit.ex +++ /dev/null @@ -1,202 +0,0 @@ -defmodule EWalletDB.Audit do - @moduledoc """ - Ecto Schema representing audits. - """ - use Arc.Ecto.Schema - use Ecto.Schema - use EWalletConfig.Types.ExternalID - import Ecto.{Changeset, Query} - alias Ecto.{Changeset, Multi, UUID} - - alias EWalletDB.{ - Audit, - Repo - } - - @primary_key {:uuid, UUID, autogenerate: true} - - schema "audit" do - external_id(prefix: "adt_") - - field(:action, :string) - - field(:target_type, :string) - field(:target_uuid, UUID) - field(:target_changes, :map) - field(:target_encrypted_metadata, EWalletConfig.Encrypted.Map, default: %{}) - - field(:originator_uuid, UUID) - field(:originator_type, :string) - - field(:metadata, :map, default: %{}) - - field(:inserted_at, :naive_datetime) - end - - defp changeset(changeset, attrs) do - changeset - |> cast(attrs, [ - :action, - :target_type, - :target_uuid, - :target_changes, - :target_encrypted_metadata, - :originator_uuid, - :originator_type, - :metadata, - :inserted_at - ]) - |> validate_required([ - :action, - :target_type, - :target_uuid, - :target_changes, - :originator_uuid, - :originator_type, - :inserted_at - ]) - end - - @spec get_schema(String.t()) :: Atom.t() - def get_schema(type) do - Application.get_env(:ewallet_db, :audit_types_to_schemas)[type] - end - - @spec get_type(Atom.t()) :: String.t() - def get_type(schema) do - Application.get_env(:ewallet_db, :schemas_to_audit_types)[schema] - end - - @spec all_for_target(Map.t()) :: [%Audit{}] - def all_for_target(record) do - all_for_target(record.__struct__, record.uuid) - end - - @spec all_for_target(String.t(), UUID.t()) :: [%Audit{}] - def all_for_target(type, uuid) when is_binary(type) do - Audit - |> where([a], a.target_type == ^type and a.target_uuid == ^uuid) - |> Repo.all() - end - - @spec all_for_target(Atom.t(), UUID.t()) :: [%Audit{}] - def all_for_target(schema, uuid) do - schema - |> get_type() - |> all_for_target(uuid) - end - - @spec get_initial_audit(String.t(), UUID.t()) :: %Audit{} - def get_initial_audit(type, uuid) do - Repo.get_by( - Audit, - action: "insert", - target_type: type, - target_uuid: uuid - ) - end - - @spec get_initial_originator(Map.t()) :: Map.t() - def get_initial_originator(record) do - audit_type = get_type(record.__struct__) - audit = Audit.get_initial_audit(audit_type, record.uuid) - originator_schema = Audit.get_schema(audit.originator_type) - - case originator_schema do - EWalletConfig.System -> - %EWalletConfig.System{uuid: audit.originator_uuid} - - schema -> - Repo.get(schema, audit.originator_uuid) - end - end - - @spec insert_record_with_audit(%Changeset{}, Keyword.t(), Multi.t()) :: - {:ok, any()} - | {:error, any()} - | {:error, :no_originator_given} - | {:error, Multi.name(), any(), %{optional(Multi.name()) => any()}} - def insert_record_with_audit(changeset, opts \\ [], multi \\ Multi.new()) do - :insert - |> perform(changeset, opts, multi) - |> handle_perform_result() - end - - @spec update_record_with_audit(%Changeset{}, Keyword.t(), Multi.t()) :: - {:ok, any()} - | {:error, any()} - | {:error, :no_originator_given} - | {:error, Multi.name(), any(), %{optional(Multi.name()) => any()}} - def update_record_with_audit(changeset, opts \\ [], multi \\ Multi.new()) do - :update - |> perform(changeset, opts, multi) - |> handle_perform_result() - end - - @spec perform(Atom.t(), %Changeset{}, Keyword.t(), Multi.t()) :: - {:ok, any()} - | {:error, any()} - | {:error, :no_originator_given} - | {:error, Multi.name(), any(), %{optional(Multi.name()) => any()}} - def perform(action, changeset, opts \\ [], multi \\ Multi.new()) do - Multi - |> apply(action, [Multi.new(), :record, changeset, opts]) - |> Multi.run(:audit, fn %{record: record} -> - action - |> build_attrs(changeset, record) - |> insert_audit() - end) - |> Multi.append(multi) - |> Repo.transaction() - end - - defp handle_perform_result({:ok, %{record: record}}) do - {:ok, record} - end - - # Only the account insertion should fail. If the wallet insert fails, there is - # something wrong with our code. - defp handle_perform_result({:error, _failed_operation, changeset, _changes_so_far}) do - {:error, changeset} - end - - defp insert_audit(attrs) do - %Audit{} - |> changeset(attrs) - |> Repo.insert() - end - - defp build_attrs(action, changeset, record) do - with {:ok, originator} <- get_originator(changeset, record), - originator_type <- get_type(originator.__struct__), - target_type <- get_type(record.__struct__), - changes <- Map.delete(changeset.changes, :originator), - encrypted_metadata <- changes[:encrypted_metadata], - changes <- Map.delete(changes, :encrypted_metadata) do - %{ - action: Atom.to_string(action), - target_type: target_type, - target_uuid: record.uuid, - target_changes: changes, - target_encrypted_metadata: encrypted_metadata || %{}, - originator_uuid: originator.uuid, - originator_type: originator_type, - inserted_at: NaiveDateTime.utc_now() - } - else - error -> error - end - end - - defp get_originator(%Changeset{changes: %{originator: :self}}, record) do - {:ok, record} - end - - defp get_originator(%Changeset{changes: %{originator: originator}}, _) do - {:ok, originator} - end - - defp get_originator(_, _) do - {:error, :no_originator_given} - end -end diff --git a/apps/ewallet_db/lib/ewallet_db/auditable.ex b/apps/ewallet_db/lib/ewallet_db/auditable.ex deleted file mode 100644 index 3b8d1367b..000000000 --- a/apps/ewallet_db/lib/ewallet_db/auditable.ex +++ /dev/null @@ -1,43 +0,0 @@ -defmodule EWalletDB.Auditable do - @moduledoc """ - Allows audit for Ecto records. - """ - import Ecto.Changeset - alias EWalletDB.Audit - alias EWalletConfig.Types.VirtualStruct - - @doc false - defmacro __using__(_) do - quote do - import EWalletDB.Auditable - alias EWalletDB.Auditable - end - end - - @doc """ - A macro that adds the `:originator` virtual field to a schema. - """ - defmacro auditable do - quote do - field(:originator, VirtualStruct, virtual: true) - end - end - - @doc """ - Prepares a changeset for audit. - """ - def cast_and_validate_required_for_audit(record, attrs, cast \\ [], required \\ []) do - record - |> Map.delete(:originator) - |> cast(attrs, [:originator | cast]) - |> validate_required([:originator | required]) - end - - def insert_with_audit(changeset, opts \\ [], multi \\ Multi.new()) do - Audit.insert_record_with_audit(changeset, opts, multi) - end - - def update_with_audit(changeset, opts \\ [], multi \\ Multi.new()) do - Audit.update_record_with_audit(changeset, opts, multi) - end -end diff --git a/apps/ewallet_db/lib/ewallet_db/auth_token.ex b/apps/ewallet_db/lib/ewallet_db/auth_token.ex index 78aa51ef9..c44b7888d 100644 --- a/apps/ewallet_db/lib/ewallet_db/auth_token.ex +++ b/apps/ewallet_db/lib/ewallet_db/auth_token.ex @@ -3,12 +3,12 @@ defmodule EWalletDB.AuthToken do Ecto Schema representing an authentication token. """ use Ecto.Schema - use EWalletConfig.Types.ExternalID - use EWalletDB.Auditable + use Utils.Types.ExternalID + use ActivityLogger.ActivityLogging import Ecto.Changeset import Ecto.Query, only: [from: 2] alias Ecto.UUID - alias EWalletConfig.Helpers.Crypto + alias Utils.Helpers.Crypto alias EWalletDB.{Account, AuthToken, Repo, User} @primary_key {:uuid, UUID, autogenerate: true} @@ -38,35 +38,47 @@ defmodule EWalletDB.AuthToken do field(:expired, :boolean) timestamps() - auditable() + activity_logging() end defp changeset(%AuthToken{} = token, attrs) do token - |> cast(attrs, [:token, :owner_app, :user_uuid, :account_uuid, :expired]) - |> validate_required([:token, :owner_app, :user_uuid]) + |> cast_and_validate_required_for_activity_log( + attrs, + [:token, :owner_app, :user_uuid, :account_uuid, :expired], + [:token, :owner_app, :user_uuid] + ) |> unique_constraint(:token) |> assoc_constraint(:user) end defp expire_changeset(%AuthToken{} = token, attrs) do token - |> cast(attrs, [:expired]) - |> validate_required([:expired]) + |> cast_and_validate_required_for_activity_log( + attrs, + [:expired], + [:expired] + ) end defp switch_account_changeset(%AuthToken{} = token, attrs) do token - |> cast(attrs, [:account_uuid]) - |> validate_required([:account_uuid]) + |> cast_and_validate_required_for_activity_log( + attrs, + [:account_uuid], + [:account_uuid] + ) end - @spec switch_account(%__MODULE__{}, %Account{}) :: + @spec switch_account(%__MODULE__{}, %Account{}, map()) :: {:ok, %__MODULE__{}} | {:error, Ecto.Changeset.t()} - def switch_account(token, account) do + def switch_account(token, account, originator) do token - |> switch_account_changeset(%{account_uuid: account.uuid}) - |> Repo.update() + |> switch_account_changeset(%{ + account_uuid: account.uuid, + originator: originator + }) + |> Repo.update_record_with_activity_log() end @doc """ @@ -80,7 +92,8 @@ defmodule EWalletDB.AuthToken do owner_app: Atom.to_string(owner_app), user_uuid: user.uuid, account_uuid: if(account, do: account.uuid, else: nil), - token: Crypto.generate_base64_key(@key_length) + token: Crypto.generate_base64_key(@key_length), + originator: user } insert(attrs) @@ -161,18 +174,21 @@ defmodule EWalletDB.AuthToken do defp insert(attrs) do %AuthToken{} |> changeset(attrs) - |> Repo.insert() + |> Repo.insert_record_with_activity_log() end # Expires the given token. - def expire(token, owner_app) when is_binary(token) and is_atom(owner_app) do + def expire(token, owner_app, originator) when is_binary(token) and is_atom(owner_app) do token |> get_by_token(owner_app) - |> expire() + |> expire(originator) end - def expire(%AuthToken{} = token) do - update(token, %{expired: true}) + def expire(%AuthToken{} = token, originator) do + update(token, %{ + expired: true, + originator: originator + }) end def expire_for_user(%{enabled: true}), do: :ok @@ -194,6 +210,6 @@ defmodule EWalletDB.AuthToken do defp update(%AuthToken{} = token, attrs) do token |> expire_changeset(attrs) - |> Repo.update() + |> Repo.update_record_with_activity_log() end end diff --git a/apps/ewallet_db/lib/ewallet_db/category.ex b/apps/ewallet_db/lib/ewallet_db/category.ex index 2c6616ed3..b1f086fdf 100644 --- a/apps/ewallet_db/lib/ewallet_db/category.ex +++ b/apps/ewallet_db/lib/ewallet_db/category.ex @@ -4,13 +4,13 @@ defmodule EWalletDB.Category do """ use Ecto.Schema use EWalletDB.SoftDelete - use EWalletConfig.Types.ExternalID + use Utils.Types.ExternalID + use ActivityLogger.ActivityLogging import Ecto.{Changeset, Query} import EWalletDB.Helpers.Preloader alias Ecto.UUID - alias EWalletConfig.Helpers.InputAttribute - alias EWalletDB.{Audit, Account, Repo} - alias EWalletConfig.Types.VirtualStruct + alias Utils.Helpers.InputAttribute + alias EWalletDB.{Account, Repo} @primary_key {:uuid, UUID, autogenerate: true} @@ -19,9 +19,6 @@ defmodule EWalletDB.Category do field(:name, :string) field(:description, :string) - field(:originator, VirtualStruct, virtual: true) - timestamps() - soft_delete() many_to_many( :accounts, @@ -30,12 +27,19 @@ defmodule EWalletDB.Category do join_keys: [category_uuid: :uuid, account_uuid: :uuid], on_replace: :delete ) + + timestamps() + soft_delete() + activity_logging() end defp changeset(category, attrs) do category - |> cast(attrs, [:name, :description, :originator]) - |> validate_required([:name, :originator]) + |> cast_and_validate_required_for_activity_log( + attrs, + [:name, :description], + [:name] + ) |> unique_constraint(:name) |> put_accounts(attrs, :account_ids) end @@ -97,7 +101,7 @@ defmodule EWalletDB.Category do def insert(attrs) do %__MODULE__{} |> changeset(attrs) - |> Audit.insert_record_with_audit() + |> Repo.insert_record_with_activity_log() end @doc """ @@ -107,7 +111,7 @@ defmodule EWalletDB.Category do def update(category, attrs) do category |> changeset(attrs) - |> Audit.update_record_with_audit() + |> Repo.update_record_with_activity_log() end @doc """ @@ -120,9 +124,9 @@ defmodule EWalletDB.Category do Soft-deletes the given category. The operation fails if the category has one more more accounts associated. """ - @spec delete(%__MODULE__{}) :: + @spec delete(%__MODULE__{}, map()) :: {:ok, %__MODULE__{}} | {:error, Ecto.Changeset.t()} | {:error, atom()} - def delete(category) do + def delete(category, originator) do empty? = category |> Repo.preload(:accounts) @@ -130,7 +134,7 @@ defmodule EWalletDB.Category do |> Enum.empty?() case empty? do - true -> SoftDelete.delete(category) + true -> SoftDelete.delete(category, originator) false -> {:error, :category_not_empty} end end @@ -138,6 +142,6 @@ defmodule EWalletDB.Category do @doc """ Restores the given category from soft-delete. """ - @spec restore(%__MODULE__{}) :: {:ok, %__MODULE__{}} | {:error, Ecto.Changeset.t()} - def restore(category), do: SoftDelete.restore(category) + @spec restore(%__MODULE__{}, map()) :: {:ok, %__MODULE__{}} | {:error, Ecto.Changeset.t()} + def restore(category, originator), do: SoftDelete.restore(category, originator) end diff --git a/apps/ewallet_db/lib/ewallet_db/exchange_pair.ex b/apps/ewallet_db/lib/ewallet_db/exchange_pair.ex index 7a17647de..b8d8829a7 100644 --- a/apps/ewallet_db/lib/ewallet_db/exchange_pair.ex +++ b/apps/ewallet_db/lib/ewallet_db/exchange_pair.ex @@ -21,8 +21,8 @@ defmodule EWalletDB.ExchangePair do """ use Ecto.Schema use EWalletDB.SoftDelete - use EWalletDB.Auditable - use EWalletConfig.Types.ExternalID + use ActivityLogger.ActivityLogging + use Utils.Types.ExternalID import Ecto.Changeset import EWalletDB.Helpers.Preloader import EWalletDB.Validator @@ -54,12 +54,12 @@ defmodule EWalletDB.ExchangePair do field(:rate, :float) timestamps() soft_delete() - auditable() + activity_logging() end defp changeset(exchange_pair, attrs) do exchange_pair - |> cast_and_validate_required_for_audit( + |> cast_and_validate_required_for_activity_log( attrs, [:from_token_uuid, :to_token_uuid, :rate, :deleted_at], [:from_token_uuid, :to_token_uuid, :rate] @@ -78,13 +78,17 @@ defmodule EWalletDB.ExchangePair do defp restore_changeset(exchange_pair, attrs) do exchange_pair - |> cast_and_validate_required_for_audit(attrs, [:deleted_at]) + |> cast_and_validate_required_for_activity_log(attrs, [:deleted_at]) |> unique_constraint( :deleted_at, name: "exchange_pair_from_token_uuid_to_token_uuid_index" ) end + defp touch_changeset(exchange_pair, attrs) do + cast_and_validate_required_for_activity_log(exchange_pair, attrs, [:updated_at]) + end + @doc """ Get all exchange pairs. """ @@ -126,7 +130,7 @@ defmodule EWalletDB.ExchangePair do def insert(attrs) do %__MODULE__{} |> changeset(attrs) - |> insert_with_audit() + |> Repo.insert_record_with_activity_log() end @doc """ @@ -136,7 +140,7 @@ defmodule EWalletDB.ExchangePair do def update(exchange_pair, attrs) do exchange_pair |> changeset(attrs) - |> update_with_audit() + |> Repo.update_record_with_activity_log() end @doc """ @@ -148,17 +152,17 @@ defmodule EWalletDB.ExchangePair do @doc """ Soft-deletes the given exchange pair. """ - @spec delete(%__MODULE__{}) :: {:ok, %__MODULE__{}} | {:error, Ecto.Changeset.t()} - def delete(exchange_pair), do: SoftDelete.delete(exchange_pair) + @spec delete(%__MODULE__{}, map()) :: {:ok, %__MODULE__{}} | {:error, Ecto.Changeset.t()} + def delete(exchange_pair, originator), do: SoftDelete.delete(exchange_pair, originator) @doc """ Restores the given exchange pair from soft-delete. """ - @spec restore(%__MODULE__{}) :: {:ok, %__MODULE__{}} | {:error, Ecto.Changeset.t()} - def restore(exchange_pair) do - changeset = restore_changeset(exchange_pair, %{deleted_at: nil}) + @spec restore(%__MODULE__{}, map()) :: {:ok, %__MODULE__{}} | {:error, Ecto.Changeset.t()} + def restore(exchange_pair, originator) do + changeset = restore_changeset(exchange_pair, %{deleted_at: nil, originator: originator}) - case Repo.update(changeset) do + case Repo.update_record_with_activity_log(changeset) do {:error, %{errors: [deleted_at: {"has already been taken", []}]}} -> {:error, :exchange_pair_already_exists} @@ -170,11 +174,11 @@ defmodule EWalletDB.ExchangePair do @doc """ Touches the given exchange pair and updates `updated_at` to the current date & time. """ - @spec touch(%__MODULE__{}) :: {:ok, %__MODULE__{}} | {:error, Ecto.Changeset.t()} - def touch(exchange_pair) do + @spec touch(%__MODULE__{}, map()) :: {:ok, %__MODULE__{}} | {:error, Ecto.Changeset.t()} + def touch(exchange_pair, originator) do exchange_pair - |> change(updated_at: NaiveDateTime.utc_now()) - |> update_with_audit() + |> touch_changeset(%{updated_at: NaiveDateTime.utc_now(), originator: originator}) + |> Repo.update_record_with_activity_log() end @doc """ diff --git a/apps/ewallet_db/lib/ewallet_db/forget_password_request.ex b/apps/ewallet_db/lib/ewallet_db/forget_password_request.ex index ef817b08d..788884182 100644 --- a/apps/ewallet_db/lib/ewallet_db/forget_password_request.ex +++ b/apps/ewallet_db/lib/ewallet_db/forget_password_request.ex @@ -3,11 +3,12 @@ defmodule EWalletDB.ForgetPasswordRequest do Ecto Schema representing a password reset request. """ use Ecto.Schema - use EWalletDB.Auditable + use ActivityLogger.ActivityLogging import Ecto.{Changeset, Query} alias Ecto.UUID - alias EWalletConfig.Helpers.Crypto alias EWalletDB.{ForgetPasswordRequest, Repo, User} + alias Utils.Helpers.Crypto + alias ActivityLogger.System @primary_key {:uuid, UUID, autogenerate: true} @token_length 32 @@ -25,13 +26,15 @@ defmodule EWalletDB.ForgetPasswordRequest do ) timestamps() - auditable() + activity_logging() end defp changeset(changeset, attrs) do changeset - |> cast(attrs, [:token, :user_uuid]) - |> validate_required([:token, :user_uuid]) + |> cast_and_validate_required_for_activity_log(attrs, [:token, :user_uuid], [ + :token, + :user_uuid + ]) |> assoc_constraint(:user) end @@ -88,13 +91,20 @@ defmodule EWalletDB.ForgetPasswordRequest do @spec generate(%User{}) :: %ForgetPasswordRequest{} | {:error, Ecto.Changeset.t()} def generate(user) do token = Crypto.generate_base64_key(@token_length) - {:ok, _} = insert(%{token: token, user_uuid: user.uuid}) + + {:ok, _} = + insert(%{ + token: token, + user_uuid: user.uuid, + originator: %System{} + }) + ForgetPasswordRequest.get(user, token) end defp insert(attrs) do %ForgetPasswordRequest{} |> changeset(attrs) - |> Repo.insert() + |> Repo.insert_record_with_activity_log() end end diff --git a/apps/ewallet_db/lib/ewallet_db/invite.ex b/apps/ewallet_db/lib/ewallet_db/invite.ex index eadfb0583..da8bfdc99 100644 --- a/apps/ewallet_db/lib/ewallet_db/invite.ex +++ b/apps/ewallet_db/lib/ewallet_db/invite.ex @@ -3,11 +3,11 @@ defmodule EWalletDB.Invite do Ecto Schema representing invite. """ use Ecto.Schema + use ActivityLogger.ActivityLogging import Ecto.{Changeset, Query} alias Ecto.{Multi, UUID} - alias EWalletConfig.Types.VirtualStruct - alias EWalletConfig.Helpers.Crypto - alias EWalletDB.{Audit, Invite, Repo, User} + alias Utils.Helpers.Crypto + alias EWalletDB.{Invite, Repo, User} @primary_key {:uuid, UUID, autogenerate: true} @token_length 32 @@ -17,7 +17,6 @@ defmodule EWalletDB.Invite do field(:token, :string) field(:success_url, :string) field(:verified_at, :naive_datetime) - field(:originator, VirtualStruct, virtual: true) belongs_to( :user, @@ -28,20 +27,25 @@ defmodule EWalletDB.Invite do ) timestamps() + activity_logging() end defp changeset_insert(changeset, attrs) do changeset - |> Map.delete(:originator) - |> cast(attrs, [:user_uuid, :token, :success_url, :originator]) - |> validate_required([:user_uuid, :token, :originator]) + |> cast_and_validate_required_for_activity_log( + attrs, + [:user_uuid, :token, :success_url], + [:user_uuid, :token] + ) end defp changeset_accept(changeset, attrs) do changeset - |> Map.delete(:originator) - |> cast(attrs, [:verified_at, :originator]) - |> validate_required([:verified_at, :originator]) + |> cast_and_validate_required_for_activity_log( + attrs, + [:verified_at], + [:verified_at] + ) end @doc """ @@ -105,9 +109,7 @@ defmodule EWalletDB.Invite do @doc """ Generates an invite for the given user. """ - def generate(user, opts \\ []) do - originator = Audit.get_initial_originator(user) - + def generate(user, originator, opts \\ []) do # Insert a new invite %Invite{} |> changeset_insert(%{ @@ -116,14 +118,17 @@ defmodule EWalletDB.Invite do success_url: opts[:success_url], originator: originator }) - |> Audit.insert_record_with_audit( + |> Repo.insert_record_with_activity_log( [], # Assign the invite to the user Multi.run(Multi.new(), :user, fn %{record: record} -> {:ok, _user} = user - |> change(%{invite_uuid: record.uuid}) - |> Repo.update() + |> change(%{ + invite_uuid: record.uuid, + originator: record + }) + |> Repo.update_record_with_activity_log() end) ) |> case do @@ -145,7 +150,7 @@ defmodule EWalletDB.Invite do {:ok, _user} <- User.update(invite.user, attrs), invite_attrs <- %{verified_at: NaiveDateTime.utc_now(), originator: invite.user}, changeset <- changeset_accept(invite, invite_attrs), - {:ok, invite} <- Audit.update_record_with_audit(changeset) do + {:ok, invite} <- Repo.update_record_with_activity_log(changeset) do {:ok, invite} else {:error, _failed_operation, changeset, _changes_so_far} -> @@ -172,7 +177,7 @@ defmodule EWalletDB.Invite do {:ok, _user} <- User.update(user, user_attrs), invite_attrs <- %{verified_at: NaiveDateTime.utc_now(), originator: invite.user}, changeset <- changeset_accept(invite, invite_attrs), - {:ok, invite} <- Audit.update_record_with_audit(changeset) do + {:ok, invite} <- Repo.update_record_with_activity_log(changeset) do {:ok, invite} else {:error, _failed_operation, changeset, _changes_so_far} -> diff --git a/apps/ewallet_db/lib/ewallet_db/key.ex b/apps/ewallet_db/lib/ewallet_db/key.ex index c848d53d9..c10b8e95e 100644 --- a/apps/ewallet_db/lib/ewallet_db/key.ex +++ b/apps/ewallet_db/lib/ewallet_db/key.ex @@ -4,11 +4,11 @@ defmodule EWalletDB.Key do """ use Ecto.Schema use EWalletDB.SoftDelete - use EWalletConfig.Types.ExternalID - use EWalletDB.Auditable + use Utils.Types.ExternalID + use ActivityLogger.ActivityLogging import Ecto.{Changeset, Query} alias Ecto.UUID - alias EWalletConfig.Helpers.Crypto + alias Utils.Helpers.Crypto alias EWalletDB.{Account, Key, Repo} @primary_key {:uuid, UUID, autogenerate: true} @@ -34,13 +34,16 @@ defmodule EWalletDB.Key do field(:enabled, :boolean, default: true) timestamps() soft_delete() - auditable() + activity_logging() end defp insert_changeset(%Key{} = key, attrs) do key - |> cast(attrs, [:access_key, :secret_key, :account_uuid, :enabled]) - |> validate_required([:access_key, :secret_key, :account_uuid]) + |> cast_and_validate_required_for_activity_log( + attrs, + [:access_key, :secret_key, :account_uuid, :enabled], + [:access_key, :secret_key, :account_uuid] + ) |> unique_constraint(:access_key, name: :key_access_key_index) |> put_change(:secret_key_hash, Crypto.hash_secret(attrs[:secret_key])) |> put_change(:secret_key, Base.url_encode64(attrs[:secret_key], padding: false)) @@ -48,9 +51,7 @@ defmodule EWalletDB.Key do end defp enable_changeset(%Key{} = key, attrs) do - key - |> cast(attrs, [:enabled]) - |> validate_required([:enabled]) + cast_and_validate_required_for_activity_log(key, attrs, [:enabled], [:enabled]) end @doc """ @@ -107,7 +108,7 @@ defmodule EWalletDB.Key do %Key{} |> insert_changeset(attrs) - |> Repo.insert() + |> Repo.insert_record_with_activity_log() end defp get_master_account_uuid do @@ -127,14 +128,14 @@ defmodule EWalletDB.Key do key |> enable_changeset(attrs) - |> Repo.update() + |> Repo.update_record_with_activity_log() end @spec enable_or_disable(%Key{}, map()) :: {:ok, %Key{}} | {:error, Ecto.Changeset.t()} def enable_or_disable(%Key{} = key, attrs) do key |> enable_changeset(attrs) - |> Repo.update() + |> Repo.update_record_with_activity_log() end @doc """ @@ -189,14 +190,14 @@ defmodule EWalletDB.Key do @doc """ Soft-deletes the given key. """ - @spec delete(%Key{}) :: {:ok, %Key{}} | {:error, Ecto.Changeset.t()} - def delete(key), do: SoftDelete.delete(key) + @spec delete(%Key{}, map()) :: {:ok, %Key{}} | {:error, Ecto.Changeset.t()} + def delete(key, originator), do: SoftDelete.delete(key, originator) @doc """ Restores the given key from soft-delete. """ - @spec restore(%Key{}) :: {:ok, %Key{}} | {:error, Ecto.Changeset.t()} - def restore(key), do: SoftDelete.restore(key) + @spec restore(%Key{}, map()) :: {:ok, %Key{}} | {:error, Ecto.Changeset.t()} + def restore(key, originator), do: SoftDelete.restore(key, originator) @doc """ Retrieves all account uuids that are accessible by the given key. diff --git a/apps/ewallet_db/lib/ewallet_db/membership.ex b/apps/ewallet_db/lib/ewallet_db/membership.ex index 0a154e72e..89162fe54 100644 --- a/apps/ewallet_db/lib/ewallet_db/membership.ex +++ b/apps/ewallet_db/lib/ewallet_db/membership.ex @@ -3,7 +3,7 @@ defmodule EWalletDB.Membership do Ecto Schema representing user memberships. """ use Ecto.Schema - use EWalletDB.Auditable + use ActivityLogger.ActivityLogging import Ecto.Changeset import Ecto.Query, except: [update: 2] alias Ecto.UUID @@ -37,13 +37,16 @@ defmodule EWalletDB.Membership do ) timestamps() - auditable() + activity_logging() end def changeset(%Membership{} = membership, attrs) do membership - |> cast(attrs, [:user_uuid, :account_uuid, :role_uuid]) - |> validate_required([:user_uuid, :account_uuid, :role_uuid]) + |> cast_and_validate_required_for_activity_log( + attrs, + [:user_uuid, :account_uuid, :role_uuid], + [:user_uuid, :account_uuid, :role_uuid] + ) |> unique_constraint(:user_uuid, name: :membership_user_id_account_id_index) |> assoc_constraint(:user) |> assoc_constraint(:account) @@ -95,25 +98,26 @@ defmodule EWalletDB.Membership do @doc """ Assigns the user to the given account and role. """ - def assign(user, account, role_name) when is_binary(role_name) do + def assign(user, account, role_name, originator) when is_binary(role_name) do case Role.get_by(name: role_name) do nil -> {:error, :role_not_found} role -> - assign(user, account, role) + assign(user, account, role, originator) end end - def assign(%User{} = user, %Account{} = account, %Role{} = role) do + def assign(%User{} = user, %Account{} = account, %Role{} = role, originator) do case get_by_user_and_account(user, account) do nil -> - case MembershipChecker.allowed?(user, account, role) do + case MembershipChecker.allowed?(user, account, role, originator) do true -> insert(%{ account_uuid: account.uuid, user_uuid: user.uuid, - role_uuid: role.uuid + role_uuid: role.uuid, + originator: originator }) false -> @@ -121,36 +125,43 @@ defmodule EWalletDB.Membership do end existing -> - update(existing, %{role_uuid: role.uuid}) + update(existing, %{ + role_uuid: role.uuid, + originator: originator + }) end end @doc """ Unassigns the user from the given account. """ - def unassign(%User{} = user, %Account{} = account) do + def unassign(%User{} = user, %Account{} = account, originator) do case get_by_user_and_account(user, account) do nil -> {:error, :membership_not_found} membership -> - delete(membership) + delete(membership, originator) end end defp insert(attrs) do %Membership{} |> changeset(attrs) - |> Repo.insert() + |> Repo.insert_record_with_activity_log() end defp update(%Membership{} = membership, attrs) do membership |> changeset(attrs) - |> Repo.update() + |> Repo.update_record_with_activity_log() end - defp delete(%Membership{} = membership) do - Repo.delete(membership) + defp delete(%Membership{} = membership, originator) do + membership + |> changeset(%{ + originator: originator + }) + |> Repo.delete_record_with_activity_log() end end diff --git a/apps/ewallet_db/lib/ewallet_db/membership_checker.ex b/apps/ewallet_db/lib/ewallet_db/membership_checker.ex index 6e5d44356..0a9cf266f 100644 --- a/apps/ewallet_db/lib/ewallet_db/membership_checker.ex +++ b/apps/ewallet_db/lib/ewallet_db/membership_checker.ex @@ -5,7 +5,7 @@ defmodule EWalletDB.MembershipChecker do """ alias EWalletDB.{Account, Membership} - def allowed?(user, account, role) do + def allowed?(user, account, role, originator) do memberships = Membership.all_by_user(user, [:role, :account]) membership_accounts_uuids = load_uuids(memberships, :account_uuid) @@ -16,7 +16,8 @@ defmodule EWalletDB.MembershipChecker do user, role, memberships, - membership_accounts_uuids + membership_accounts_uuids, + originator ) end @@ -67,20 +68,21 @@ defmodule EWalletDB.MembershipChecker do user, role, memberships, - membership_accounts_uuids + membership_accounts_uuids, + originator ) do descendants_uuids = account |> Account.get_all_descendants() |> load_uuids(:uuid) descendants_uuids |> intersect(membership_accounts_uuids) - |> search_descendants(user, role, memberships) + |> search_descendants(user, role, memberships, originator) end - defp init_descendants_search_or_return(allowed?, _, _, _, _, _), do: allowed? + defp init_descendants_search_or_return(allowed?, _, _, _, _, _, _), do: allowed? def search_descendants([]), do: true - def search_descendants(uuids_intersect, user, role, memberships) do + def search_descendants(uuids_intersect, user, role, memberships, originator) do Enum.each(uuids_intersect, fn matching_descendant_uuid -> membership = Enum.find(memberships, fn membership -> @@ -88,7 +90,7 @@ defmodule EWalletDB.MembershipChecker do end) if role.priority <= membership.role.priority do - Membership.unassign(user, membership.account) + Membership.unassign(user, membership.account, originator) end end) diff --git a/apps/ewallet_db/lib/ewallet_db/mint.ex b/apps/ewallet_db/lib/ewallet_db/mint.ex index bc0df4d85..36813cdfa 100644 --- a/apps/ewallet_db/lib/ewallet_db/mint.ex +++ b/apps/ewallet_db/lib/ewallet_db/mint.ex @@ -3,12 +3,13 @@ defmodule EWalletDB.Mint do Ecto Schema representing mints. """ use Ecto.Schema - use EWalletConfig.Types.ExternalID + use Utils.Types.ExternalID + use ActivityLogger.ActivityLogging import Ecto.{Query, Changeset} import EWalletDB.Helpers.Preloader alias Ecto.UUID - alias EWalletDB.{Account, Audit, Mint, Repo, Token, Transaction} - alias EWalletConfig.Types.VirtualStruct + alias EWalletDB.{Account, Mint, Repo, Token, Transaction} + alias Utils.Types.VirtualStruct @primary_key {:uuid, Ecto.UUID, autogenerate: true} @@ -16,7 +17,7 @@ defmodule EWalletDB.Mint do external_id(prefix: "mnt_") field(:description, :string) - field(:amount, EWalletConfig.Types.Integer) + field(:amount, Utils.Types.Integer) field(:confirmed, :boolean, default: false) field(:originator, VirtualStruct, virtual: true) @@ -49,16 +50,17 @@ defmodule EWalletDB.Mint do defp changeset(%Mint{} = mint, attrs) do mint - |> Map.delete(:originator) - |> cast(attrs, [ - :description, - :amount, - :account_uuid, - :token_uuid, - :confirmed, - :originator - ]) - |> validate_required([:amount, :token_uuid, :originator]) + |> cast_and_validate_required_for_activity_log( + attrs, + [ + :description, + :amount, + :account_uuid, + :token_uuid, + :confirmed + ], + [:amount, :token_uuid, :originator] + ) |> validate_number( :amount, greater_than: 0, @@ -74,9 +76,11 @@ defmodule EWalletDB.Mint do defp update_changeset(%Mint{} = mint, attrs) do mint - |> Map.delete(:originator) - |> cast(attrs, [:transaction_uuid, :originator]) - |> validate_required([:transaction_uuid, :originator]) + |> cast_and_validate_required_for_activity_log( + attrs, + [:transaction_uuid], + [:transaction_uuid] + ) |> assoc_constraint(:transaction) end @@ -89,7 +93,7 @@ defmodule EWalletDB.Mint do |> where([m], m.token_uuid == ^token.uuid) |> select([m], sum(m.amount)) |> Repo.one() - |> EWalletConfig.Types.Integer.load!() + |> Utils.Types.Integer.load!() end @doc """ @@ -119,7 +123,7 @@ defmodule EWalletDB.Mint do def insert(attrs) do %Mint{} |> changeset(attrs) - |> Audit.insert_record_with_audit() + |> Repo.insert_record_with_activity_log() end @doc """ @@ -129,13 +133,13 @@ defmodule EWalletDB.Mint do def update(%Mint{} = mint, attrs) do mint |> update_changeset(attrs) - |> Audit.update_record_with_audit() + |> Repo.update_record_with_activity_log() end @doc """ Confirms a mint. """ - def confirm(%Mint{confirmed: true} = mint), do: mint + def confirm(%Mint{confirmed: true} = mint, _), do: mint def confirm(%Mint{confirmed: false} = mint, originator) do {:ok, mint} = @@ -144,7 +148,7 @@ defmodule EWalletDB.Mint do confirmed: true, originator: originator }) - |> Audit.update_record_with_audit() + |> Repo.update_record_with_activity_log() mint end diff --git a/apps/ewallet_db/lib/ewallet_db/repo.ex b/apps/ewallet_db/lib/ewallet_db/repo.ex index 4f911284e..d9fdd2f12 100644 --- a/apps/ewallet_db/lib/ewallet_db/repo.ex +++ b/apps/ewallet_db/lib/ewallet_db/repo.ex @@ -1,5 +1,6 @@ defmodule EWalletDB.Repo do use Ecto.Repo, otp_app: :ewallet_db + use ActivityLogger.ActivityRepo, repo: EWalletDB.Repo # Workaround an issue where ecto.migrate task won't start the app # thus DeferredConfig.populate is not getting called. diff --git a/apps/ewallet_db/lib/ewallet_db/role.ex b/apps/ewallet_db/lib/ewallet_db/role.ex index d80daa747..752a1c862 100644 --- a/apps/ewallet_db/lib/ewallet_db/role.ex +++ b/apps/ewallet_db/lib/ewallet_db/role.ex @@ -3,9 +3,9 @@ defmodule EWalletDB.Role do Ecto Schema representing user roles. """ use Ecto.Schema - use EWalletConfig.Types.ExternalID + use Utils.Types.ExternalID use EWalletDB.SoftDelete - use EWalletDB.Auditable + use ActivityLogger.ActivityLogging import Ecto.{Changeset, Query} import EWalletDB.Helpers.Preloader alias Ecto.UUID @@ -29,12 +29,16 @@ defmodule EWalletDB.Role do timestamps() soft_delete() - auditable() + activity_logging() end defp changeset(%Role{} = key, attrs) do key - |> cast(attrs, [:priority, :name, :display_name]) + |> cast_and_validate_required_for_activity_log( + attrs, + [:priority, :name, :display_name], + [:name, :priority] + ) |> validate_required([:name, :priority]) |> unique_constraint(:name) end @@ -92,7 +96,7 @@ defmodule EWalletDB.Role do %Role{} |> changeset(attrs) - |> Repo.insert() + |> Repo.insert_record_with_activity_log() end @doc """ @@ -102,7 +106,7 @@ defmodule EWalletDB.Role do def update(role, attrs) do role |> changeset(attrs) - |> Repo.update() + |> Repo.update_record_with_activity_log() end @doc """ @@ -115,9 +119,9 @@ defmodule EWalletDB.Role do Soft-deletes the given role. The operation fails if the role has one more more users associated. """ - @spec delete(%__MODULE__{}) :: + @spec delete(%__MODULE__{}, map()) :: {:ok, %__MODULE__{}} | {:error, Ecto.Changeset.t()} | {:error, atom()} - def delete(role) do + def delete(role, originator) do empty? = role |> Repo.preload(:users) @@ -125,7 +129,7 @@ defmodule EWalletDB.Role do |> Enum.empty?() case empty? do - true -> SoftDelete.delete(role) + true -> SoftDelete.delete(role, originator) false -> {:error, :role_not_empty} end end @@ -133,8 +137,8 @@ defmodule EWalletDB.Role do @doc """ Restores the given role from soft-delete. """ - @spec restore(%__MODULE__{}) :: {:ok, %__MODULE__{}} | {:error, Ecto.Changeset.t()} - def restore(role), do: SoftDelete.restore(role) + @spec restore(%__MODULE__{}, map()) :: {:ok, %__MODULE__{}} | {:error, Ecto.Changeset.t()} + def restore(role, originator), do: SoftDelete.restore(role, originator) @doc """ Compares that the given string value is equivalent to the given role. diff --git a/apps/ewallet_db/lib/ewallet_db/soft_delete.ex b/apps/ewallet_db/lib/ewallet_db/soft_delete.ex index 190862f4b..04841a23a 100644 --- a/apps/ewallet_db/lib/ewallet_db/soft_delete.ex +++ b/apps/ewallet_db/lib/ewallet_db/soft_delete.ex @@ -61,7 +61,8 @@ defmodule EWalletDB.SoftDelete do end ``` """ - import Ecto.{Changeset, Query} + use ActivityLogger.ActivityLogging + import Ecto.Query alias EWalletDB.Repo @doc false @@ -85,6 +86,14 @@ defmodule EWalletDB.SoftDelete do end end + defp soft_delete_changeset(record, attrs) do + cast_and_validate_required_for_activity_log( + record, + attrs, + [:deleted_at] + ) + end + @doc """ Scopes a query down to only records that are not deleted. """ @@ -104,20 +113,26 @@ defmodule EWalletDB.SoftDelete do @doc """ Soft-deletes the given struct. """ - @spec delete(struct()) :: any() - def delete(struct) do + @spec delete(struct(), map()) :: any() + def delete(struct, originator) do struct - |> change(deleted_at: NaiveDateTime.utc_now()) - |> Repo.update() + |> soft_delete_changeset(%{ + deleted_at: NaiveDateTime.utc_now(), + originator: originator + }) + |> Repo.update_record_with_activity_log() end @doc """ Restores the given struct from soft-delete. """ - @spec restore(struct()) :: any() - def restore(struct) do + @spec restore(struct(), map()) :: any() + def restore(struct, originator) do struct - |> change(deleted_at: nil) - |> Repo.update() + |> soft_delete_changeset(%{ + deleted_at: nil, + originator: originator + }) + |> Repo.update_record_with_activity_log() end end diff --git a/apps/ewallet_db/lib/ewallet_db/token.ex b/apps/ewallet_db/lib/ewallet_db/token.ex index 263accfce..fa9f12f5a 100644 --- a/apps/ewallet_db/lib/ewallet_db/token.ex +++ b/apps/ewallet_db/lib/ewallet_db/token.ex @@ -3,8 +3,8 @@ defmodule EWalletDB.Token do Ecto Schema representing tokens. """ use Ecto.Schema - use EWalletConfig.Types.ExternalID - use EWalletDB.Auditable + use Utils.Types.ExternalID + use ActivityLogger.ActivityLogging import Ecto.{Changeset, Query} import EWalletDB.Helpers.Preloader import EWalletDB.Validator @@ -31,7 +31,7 @@ defmodule EWalletDB.Token do # "Cent" field(:subunit, :string) # 100 - field(:subunit_to_unit, EWalletConfig.Types.Integer) + field(:subunit_to_unit, Utils.Types.Integer) # true field(:symbol_first, :boolean) # "€" @@ -56,34 +56,37 @@ defmodule EWalletDB.Token do ) timestamps() - auditable() + activity_logging() end defp changeset(%Token{} = token, attrs) do token - |> cast(attrs, [ - :symbol, - :iso_code, - :name, - :description, - :short_symbol, - :subunit, - :subunit_to_unit, - :symbol_first, - :html_entity, - :iso_numeric, - :smallest_denomination, - :locked, - :account_uuid, - :metadata, - :encrypted_metadata - ]) - |> validate_required([ - :symbol, - :name, - :subunit_to_unit, - :account_uuid - ]) + |> cast_and_validate_required_for_activity_log( + attrs, + [ + :symbol, + :iso_code, + :name, + :description, + :short_symbol, + :subunit, + :subunit_to_unit, + :symbol_first, + :html_entity, + :iso_numeric, + :smallest_denomination, + :locked, + :account_uuid, + :metadata, + :encrypted_metadata + ], + [ + :symbol, + :name, + :subunit_to_unit, + :account_uuid + ] + ) |> validate_number( :subunit_to_unit, greater_than: 0, @@ -102,20 +105,23 @@ defmodule EWalletDB.Token do defp update_changeset(%Token{} = token, attrs) do token - |> cast(attrs, [ - :iso_code, - :name, - :description, - :short_symbol, - :symbol_first, - :html_entity, - :iso_numeric, - :metadata, - :encrypted_metadata - ]) - |> validate_required([ - :name - ]) + |> cast_and_validate_required_for_activity_log( + attrs, + [ + :iso_code, + :name, + :description, + :short_symbol, + :symbol_first, + :html_entity, + :iso_numeric, + :metadata, + :encrypted_metadata + ], + [ + :name + ] + ) |> unique_constraint(:iso_code) |> unique_constraint(:name) |> unique_constraint(:short_symbol) @@ -124,8 +130,7 @@ defmodule EWalletDB.Token do defp enable_changeset(%Token{} = token, attrs) do token - |> cast(attrs, [:enabled]) - |> validate_required([:enabled]) + |> cast_and_validate_required_for_activity_log(attrs, [:enabled], [:enabled]) end defp set_id(changeset, opts) do @@ -163,7 +168,7 @@ defmodule EWalletDB.Token do def insert(attrs) do changeset = changeset(%Token{}, attrs) - case Repo.insert(changeset) do + case Repo.insert_record_with_activity_log(changeset) do {:ok, token} -> {:ok, get(token.id)} @@ -178,7 +183,7 @@ defmodule EWalletDB.Token do def update(token, attrs) do token |> update_changeset(attrs) - |> Repo.update() + |> Repo.update_record_with_activity_log() end @doc """ @@ -215,6 +220,6 @@ defmodule EWalletDB.Token do def enable_or_disable(token, attrs) do token |> enable_changeset(attrs) - |> Repo.update() + |> Repo.update_record_with_activity_log() end end diff --git a/apps/ewallet_db/lib/ewallet_db/transaction.ex b/apps/ewallet_db/lib/ewallet_db/transaction.ex index fdf3b4bc4..2dca3689b 100644 --- a/apps/ewallet_db/lib/ewallet_db/transaction.ex +++ b/apps/ewallet_db/lib/ewallet_db/transaction.ex @@ -3,13 +3,14 @@ defmodule EWalletDB.Transaction do Ecto Schema representing transactions. """ use Ecto.Schema - use EWalletConfig.Types.ExternalID + use Utils.Types.ExternalID + use ActivityLogger.ActivityLogging import Ecto.{Changeset, Query} import EWalletDB.Validator import EWalletDB.Validator alias Ecto.{Multi, UUID} - alias EWalletDB.{Account, Audit, ExchangePair, Repo, Token, Transaction, User, Wallet} - alias EWalletConfig.Types.VirtualStruct + alias EWalletDB.{Account, ExchangePair, Repo, Token, Transaction, User, Wallet} + alias Utils.Types.VirtualStruct @pending "pending" @confirmed "confirmed" @@ -31,8 +32,8 @@ defmodule EWalletDB.Transaction do external_id(prefix: "txn_") field(:idempotency_token, :string) - field(:from_amount, EWalletConfig.Types.Integer) - field(:to_amount, EWalletConfig.Types.Integer) + field(:from_amount, Utils.Types.Integer) + field(:to_amount, Utils.Types.Integer) # pending -> confirmed field(:status, :string, default: @pending) # internal / external @@ -146,46 +147,49 @@ defmodule EWalletDB.Transaction do defp changeset(%Transaction{} = transaction, attrs) do transaction - |> Map.delete(:originator) - |> cast(attrs, [ - :idempotency_token, - :status, - :type, - :payload, - :metadata, - :encrypted_metadata, - :from_account_uuid, - :to_account_uuid, - :from_user_uuid, - :to_user_uuid, - :from_token_uuid, - :to_token_uuid, - :from_amount, - :to_amount, - :exchange_account_uuid, - :exchange_wallet_address, - :to, - :from, - :rate, - :local_ledger_uuid, - :error_code, - :error_description, - :exchange_pair_uuid, - :calculated_at, - :originator - ]) - |> validate_required([ - :idempotency_token, - :status, - :type, - :payload, - :from_amount, - :from_token_uuid, - :to_amount, - :to_token_uuid, - :to, - :from - ]) + |> cast_and_validate_required_for_activity_log( + attrs, + [ + :idempotency_token, + :status, + :type, + :payload, + :metadata, + :encrypted_metadata, + :from_account_uuid, + :to_account_uuid, + :from_user_uuid, + :to_user_uuid, + :from_token_uuid, + :to_token_uuid, + :from_amount, + :to_amount, + :exchange_account_uuid, + :exchange_wallet_address, + :to, + :from, + :rate, + :local_ledger_uuid, + :error_code, + :error_description, + :exchange_pair_uuid, + :calculated_at, + :originator + ], + [ + :idempotency_token, + :status, + :type, + :payload, + :from_amount, + :from_token_uuid, + :to_amount, + :to_token_uuid, + :to, + :from, + :originator + ] + ) |> validate_number(:from_amount, less_than: 100_000_000_000_000_000_000_000_000_000_000_000) |> validate_number(:to_amount, less_than: 100_000_000_000_000_000_000_000_000_000_000_000) |> validate_from_wallet_identifier() @@ -212,27 +216,31 @@ defmodule EWalletDB.Transaction do defp confirm_changeset(%Transaction{} = transaction, attrs) do transaction - |> Map.delete(:originator) - |> cast(attrs, [:status, :local_ledger_uuid, :originator]) - |> validate_required([:status, :local_ledger_uuid, :originator]) + |> cast_and_validate_required_for_activity_log( + attrs, + [:status, :local_ledger_uuid, :originator], + [:status, :local_ledger_uuid, :originator] + ) |> validate_inclusion(:status, @statuses) end defp fail_changeset(%Transaction{} = transaction, attrs) do transaction - |> Map.delete(:originator) - |> cast(attrs, [ - :status, - :error_code, - :error_description, - :error_data, - :originator - ]) - |> validate_required([ - :status, - :error_code, - :originator - ]) + |> cast_and_validate_required_for_activity_log( + attrs, + [ + :status, + :error_code, + :error_description, + :error_data, + :originator + ], + [ + :status, + :error_code, + :originator + ] + ) |> validate_inclusion(:status, @statuses) end @@ -324,7 +332,7 @@ defmodule EWalletDB.Transaction do opts = [on_conflict: :nothing, conflict_target: :idempotency_token] changeset = changeset(%Transaction{}, attrs) - Audit.perform( + Repo.perform( :insert, changeset, opts, @@ -338,18 +346,32 @@ defmodule EWalletDB.Transaction do end end) ) - |> handle_insert_result() + |> handle_insert_result(:insert, changeset) end - defp handle_insert_result({:ok, %{record: _transaction, transaction_1: nil}}) do + defp handle_insert_result( + {:ok, %{record: _transaction, transaction_1: nil}}, + _action, + _changeset + ) do {:error, :inserted_transaction_could_not_be_loaded} end - defp handle_insert_result({:ok, %{record: _transaction, transaction_1: transaction_1}}) do + defp handle_insert_result( + {:ok, %{record: _transaction, transaction_1: transaction_1}}, + action, + changeset + ) do + insert_log(action, changeset, transaction_1) + {:ok, transaction_1} end - defp handle_insert_result({:error, _failed_operation, changeset, _changes_so_far}) do + defp handle_insert_result( + {:error, _failed_operation, changeset, _changes_so_far}, + _action, + _changeset + ) do {:error, changeset} end @@ -363,7 +385,7 @@ defmodule EWalletDB.Transaction do local_ledger_uuid: local_ledger_uuid, originator: originator }) - |> Audit.update_record_with_audit() + |> Repo.update_record_with_activity_log() |> handle_update_result() end @@ -406,7 +428,7 @@ defmodule EWalletDB.Transaction do defp do_fail(data, transaction) do transaction |> fail_changeset(data) - |> Audit.update_record_with_audit() + |> Repo.update_record_with_activity_log() |> handle_update_result() end diff --git a/apps/ewallet_db/lib/ewallet_db/transaction_consumption.ex b/apps/ewallet_db/lib/ewallet_db/transaction_consumption.ex index 805d3268e..a6a0d81fb 100644 --- a/apps/ewallet_db/lib/ewallet_db/transaction_consumption.ex +++ b/apps/ewallet_db/lib/ewallet_db/transaction_consumption.ex @@ -3,10 +3,10 @@ defmodule EWalletDB.TransactionConsumption do Ecto Schema representing transaction request consumptions. """ use Ecto.Schema - use EWalletConfig.Types.ExternalID + use Utils.Types.ExternalID + use ActivityLogger.ActivityLogging import Ecto.{Changeset, Query} alias Ecto.UUID - alias EWalletConfig.Types.VirtualStruct alias EWalletDB.{ Account, @@ -20,7 +20,7 @@ defmodule EWalletDB.TransactionConsumption do Wallet } - alias EWalletConfig.Helpers.Assoc + alias Utils.Helpers.Assoc @pending "pending" @confirmed "confirmed" @@ -35,9 +35,9 @@ defmodule EWalletDB.TransactionConsumption do schema "transaction_consumption" do external_id(prefix: "txc_") - field(:amount, EWalletConfig.Types.Integer) - field(:estimated_consumption_amount, EWalletConfig.Types.Integer) - field(:estimated_request_amount, EWalletConfig.Types.Integer) + field(:amount, Utils.Types.Integer) + field(:estimated_consumption_amount, Utils.Types.Integer) + field(:estimated_request_amount, Utils.Types.Integer) field(:estimated_rate, :float) field(:correlation_id, :string) field(:idempotency_token, :string) @@ -58,8 +58,6 @@ defmodule EWalletDB.TransactionConsumption do field(:metadata, :map, default: %{}) field(:encrypted_metadata, EWalletConfig.Encrypted.Map, default: %{}) - field(:originator, VirtualStruct, virtual: true) - belongs_to( :transaction, Transaction, @@ -133,37 +131,41 @@ defmodule EWalletDB.TransactionConsumption do ) timestamps() + activity_logging() end defp changeset(%TransactionConsumption{} = consumption, attrs) do consumption - |> cast(attrs, [ - :amount, - :estimated_request_amount, - :estimated_consumption_amount, - :idempotency_token, - :correlation_id, - :user_uuid, - :account_uuid, - :transaction_request_uuid, - :wallet_address, - :token_uuid, - :metadata, - :encrypted_metadata, - :expiration_date, - :exchange_account_uuid, - :exchange_wallet_address, - :exchange_pair_uuid, - :estimated_at, - :estimated_rate - ]) - |> validate_required([ - :status, - :idempotency_token, - :transaction_request_uuid, - :wallet_address, - :token_uuid - ]) + |> cast_and_validate_required_for_activity_log( + attrs, + [ + :amount, + :estimated_request_amount, + :estimated_consumption_amount, + :idempotency_token, + :correlation_id, + :user_uuid, + :account_uuid, + :transaction_request_uuid, + :wallet_address, + :token_uuid, + :metadata, + :encrypted_metadata, + :expiration_date, + :exchange_account_uuid, + :exchange_wallet_address, + :exchange_pair_uuid, + :estimated_at, + :estimated_rate + ], + [ + :status, + :idempotency_token, + :transaction_request_uuid, + :wallet_address, + :token_uuid + ] + ) |> validate_number( :amount, greater_than: 0, @@ -183,40 +185,58 @@ defmodule EWalletDB.TransactionConsumption do def approved_changeset(%TransactionConsumption{} = consumption, attrs) do consumption - |> cast(attrs, [:status, :approved_at]) - |> validate_required([:status, :approved_at]) + |> cast_and_validate_required_for_activity_log( + attrs, + [:status, :approved_at], + [:status, :approved_at] + ) end def rejected_changeset(%TransactionConsumption{} = consumption, attrs) do consumption - |> cast(attrs, [:status, :rejected_at]) - |> validate_required([:status, :rejected_at]) + |> cast_and_validate_required_for_activity_log( + attrs, + [:status, :rejected_at], + [:status, :rejected_at] + ) end def confirmed_changeset(%TransactionConsumption{} = consumption, attrs) do consumption - |> cast(attrs, [:status, :confirmed_at, :transaction_uuid]) - |> validate_required([:status, :confirmed_at, :transaction_uuid]) + |> cast_and_validate_required_for_activity_log( + attrs, + [:status, :confirmed_at, :transaction_uuid], + [:status, :confirmed_at, :transaction_uuid] + ) |> assoc_constraint(:transaction) end def failed_changeset(%TransactionConsumption{} = consumption, attrs) do consumption - |> cast(attrs, [:status, :failed_at, :transaction_uuid]) - |> validate_required([:status, :failed_at, :transaction_uuid]) + |> cast_and_validate_required_for_activity_log( + attrs, + [:status, :failed_at, :transaction_uuid], + [:status, :failed_at, :transaction_uuid] + ) |> assoc_constraint(:transaction) end def transaction_failure_changeset(%TransactionConsumption{} = consumption, attrs) do consumption - |> cast(attrs, [:status, :failed_at, :error_code, :error_description]) - |> validate_required([:status, :failed_at, :error_code]) + |> cast_and_validate_required_for_activity_log( + attrs, + [:status, :failed_at, :error_code, :error_description], + [:status, :failed_at, :error_code] + ) end def expired_changeset(%TransactionConsumption{} = consumption, attrs) do consumption - |> cast(attrs, [:status, :expired_at]) - |> validate_required([:status, :expired_at]) + |> cast_and_validate_required_for_activity_log( + attrs, + [:status, :expired_at], + [:status, :expired_at] + ) end @doc """ @@ -277,28 +297,30 @@ defmodule EWalletDB.TransactionConsumption do @doc """ Expires the given consumption. """ - @spec expire(%TransactionConsumption{}) :: {:ok, %TransactionConsumption{}} | {:error, map()} - def expire(consumption) do + @spec expire(%TransactionConsumption{}, map()) :: + {:ok, %TransactionConsumption{}} | {:error, map()} + def expire(consumption, originator) do consumption |> expired_changeset(%{ status: @expired, - expired_at: NaiveDateTime.utc_now() + expired_at: NaiveDateTime.utc_now(), + originator: originator }) - |> Repo.update() + |> Repo.update_record_with_activity_log() end @doc """ Expires the given consumption if the expiration date is past. """ - @spec expire_if_past_expiration_date(%TransactionConsumption{}) :: + @spec expire_if_past_expiration_date(%TransactionConsumption{}, map()) :: {:ok, %TransactionConsumption{}} | {:error, map()} - def expire_if_past_expiration_date(consumption) do + def expire_if_past_expiration_date(consumption, originator) do expired? = consumption.expiration_date && NaiveDateTime.compare(consumption.expiration_date, NaiveDateTime.utc_now()) == :lt case expired? do - true -> expire(consumption) + true -> expire(consumption, originator) _ -> {:ok, consumption} end end @@ -354,7 +376,7 @@ defmodule EWalletDB.TransactionConsumption do changeset = changeset(%TransactionConsumption{}, attrs) opts = [on_conflict: :nothing, conflict_target: :idempotency_token] - case Repo.insert(changeset, opts) do + case Repo.insert_record_with_activity_log(changeset, opts) do {:ok, consumption} -> {:ok, get_by(%{idempotency_token: consumption.idempotency_token})} @@ -366,15 +388,15 @@ defmodule EWalletDB.TransactionConsumption do @doc """ Approves a consumption. """ - @spec approve(%TransactionConsumption{}) :: %TransactionConsumption{} - def approve(consumption), do: state_transition(consumption, @approved) + @spec approve(%TransactionConsumption{}, map()) :: %TransactionConsumption{} + def approve(consumption, originator), do: state_transition(consumption, @approved, originator) @doc """ Rejects a consumption. """ - @spec reject(%TransactionConsumption{}) :: %TransactionConsumption{} - def reject(consumption) do - state_transition(consumption, @rejected) + @spec reject(%TransactionConsumption{}, map()) :: %TransactionConsumption{} + def reject(consumption, originator) do + state_transition(consumption, @rejected, originator) end @doc """ @@ -382,7 +404,7 @@ defmodule EWalletDB.TransactionConsumption do """ @spec confirm(%TransactionConsumption{}, %Transaction{}) :: %TransactionConsumption{} def confirm(consumption, transaction) do - state_transition(consumption, @confirmed, transaction.uuid) + state_transition(consumption, @confirmed, transaction, transaction.uuid) end @doc """ @@ -390,27 +412,28 @@ defmodule EWalletDB.TransactionConsumption do """ @spec fail(%TransactionConsumption{}, %Transaction{}) :: %TransactionConsumption{} def fail(consumption, %Transaction{} = transaction) do - state_transition(consumption, @failed, transaction.uuid) + state_transition(consumption, @failed, transaction, transaction.uuid) end - def fail(consumption, error_code, error_description) when is_atom(error_code) do + def fail(consumption, error_code, error_description, originator) when is_atom(error_code) do error_code = Atom.to_string(error_code) - fail(consumption, error_code, error_description) + fail(consumption, error_code, error_description, originator) end - def fail(consumption, error_code, error_description) when is_binary(error_code) do + def fail(consumption, error_code, error_description, originator) when is_binary(error_code) do data = %{ status: @failed, error_code: error_code, - error_description: error_description + error_description: error_description, + originator: originator } |> Map.put(:failed_at, NaiveDateTime.utc_now()) {:ok, consumption} = consumption |> transaction_failure_changeset(data) - |> Repo.update() + |> Repo.update_record_with_activity_log() consumption end @@ -429,21 +452,22 @@ defmodule EWalletDB.TransactionConsumption do Enum.member?([@rejected, @confirmed, @failed, @expired], consumption.status) end - defp state_transition(consumption, status, transaction_uuid \\ nil) do + defp state_transition(consumption, status, originator, transaction_uuid \\ nil) do fun = String.to_existing_atom("#{status}_changeset") timestamp_column = String.to_existing_atom("#{status}_at") data = %{ status: status, - transaction_uuid: transaction_uuid + transaction_uuid: transaction_uuid, + originator: originator } |> Map.put(timestamp_column, NaiveDateTime.utc_now()) {:ok, consumption} = __MODULE__ |> apply(fun, [consumption, data]) - |> Repo.update() + |> Repo.update_record_with_activity_log() consumption end diff --git a/apps/ewallet_db/lib/ewallet_db/transaction_request.ex b/apps/ewallet_db/lib/ewallet_db/transaction_request.ex index b17f8264e..6a4d5c82a 100644 --- a/apps/ewallet_db/lib/ewallet_db/transaction_request.ex +++ b/apps/ewallet_db/lib/ewallet_db/transaction_request.ex @@ -3,15 +3,15 @@ defmodule EWalletDB.TransactionRequest do Ecto Schema representing transaction requests. """ use Ecto.Schema - use EWalletConfig.Types.ExternalID + use Utils.Types.ExternalID + use ActivityLogger.ActivityLogging import Ecto.{Changeset, Query} import EWalletDB.Helpers.Preloader alias Ecto.{Changeset, Query, UUID} - alias EWalletConfig.Types.VirtualStruct + alias Utils.Types.VirtualStruct alias EWalletDB.{ Account, - Audit, Repo, Token, TransactionConsumption, @@ -34,7 +34,7 @@ defmodule EWalletDB.TransactionRequest do external_id(prefix: "txr_") field(:type, :string) - field(:amount, EWalletConfig.Types.Integer) + field(:amount, Utils.Types.Integer) # valid -> expired field(:status, :string, default: @valid) field(:correlation_id, :string) @@ -115,34 +115,36 @@ defmodule EWalletDB.TransactionRequest do defp changeset(%TransactionRequest{} = transaction_request, attrs) do transaction_request - |> Map.delete(:originator) - |> cast(attrs, [ - :type, - :amount, - :correlation_id, - :user_uuid, - :account_uuid, - :token_uuid, - :wallet_address, - :require_confirmation, - :max_consumptions, - :max_consumptions_per_user, - :consumption_lifetime, - :expiration_date, - :metadata, - :encrypted_metadata, - :allow_amount_override, - :exchange_account_uuid, - :exchange_wallet_address, - :originator - ]) - |> validate_required([ - :type, - :status, - :token_uuid, - :wallet_address, - :originator - ]) + |> cast_and_validate_required_for_activity_log( + attrs, + [ + :type, + :amount, + :correlation_id, + :user_uuid, + :account_uuid, + :token_uuid, + :wallet_address, + :require_confirmation, + :max_consumptions, + :max_consumptions_per_user, + :consumption_lifetime, + :expiration_date, + :metadata, + :encrypted_metadata, + :allow_amount_override, + :exchange_account_uuid, + :exchange_wallet_address, + :originator + ], + [ + :type, + :status, + :token_uuid, + :wallet_address, + :originator + ] + ) |> validate_amount_if_disallow_override() |> validate_number(:amount, less_than: 100_000_000_000_000_000_000_000_000_000_000_000) |> validate_inclusion(:type, @types) @@ -157,24 +159,30 @@ defmodule EWalletDB.TransactionRequest do defp consumptions_count_changeset(%TransactionRequest{} = transaction_request, attrs) do transaction_request - |> Map.delete(:originator) - |> cast(attrs, [:consumptions_count, :originator]) - |> validate_required([:consumptions_count, :originator]) + |> cast_and_validate_required_for_activity_log( + attrs, + [:consumptions_count], + [:consumptions_count] + ) end defp expire_changeset(%TransactionRequest{} = transaction_request, attrs) do transaction_request - |> Map.delete(:originator) - |> cast(attrs, [:status, :expired_at, :expiration_reason, :originator]) - |> validate_required([:status, :expired_at, :expiration_reason, :originator]) + |> cast_and_validate_required_for_activity_log( + attrs, + [:status, :expired_at, :expiration_reason], + [:status, :expired_at, :expiration_reason, :originator] + ) |> validate_inclusion(:status, @statuses) end defp touch_changeset(%TransactionRequest{} = transaction_request, attrs) do transaction_request - |> Map.delete(:originator) - |> cast(attrs, [:updated_at, :originator]) - |> validate_required([:updated_at, :originator]) + |> cast_and_validate_required_for_activity_log( + attrs, + [:updated_at, :originator], + [:updated_at, :originator] + ) end defp validate_amount_if_disallow_override(changeset) do @@ -262,14 +270,14 @@ defmodule EWalletDB.TransactionRequest do @doc """ Touches a request by updating the `updated_at` field. """ - @spec touch(%TransactionRequest{}, Map.t()) :: {:ok, %TransactionRequest{}} | {:error, map()} + @spec touch(%TransactionRequest{}, map()) :: {:ok, %TransactionRequest{}} | {:error, map()} def touch(request, originator) do request |> touch_changeset(%{ updated_at: NaiveDateTime.utc_now(), originator: originator }) - |> Audit.update_record_with_audit() + |> Repo.update_record_with_activity_log() end @doc """ @@ -279,7 +287,7 @@ defmodule EWalletDB.TransactionRequest do def insert(attrs) do %TransactionRequest{} |> changeset(attrs) - |> Audit.insert_record_with_audit() + |> Repo.insert_record_with_activity_log() end @doc """ @@ -288,7 +296,7 @@ defmodule EWalletDB.TransactionRequest do def update(%TransactionRequest{} = request, attrs) do request |> changeset(attrs) - |> Audit.update_record_with_audit() + |> Repo.update_record_with_activity_log() end @spec valid?(%TransactionRequest{}) :: true | false @@ -320,7 +328,7 @@ defmodule EWalletDB.TransactionRequest do @doc """ Expires the given request with the specified reason. """ - @spec expire(%TransactionRequest{}, Map.t(), String.t()) :: + @spec expire(%TransactionRequest{}, map(), String.t()) :: {:ok, %TransactionRequest{}} | {:error, map()} def expire(request, originator, reason \\ "expired_transaction_request") do request @@ -330,13 +338,13 @@ defmodule EWalletDB.TransactionRequest do expiration_reason: reason, originator: originator }) - |> Audit.update_record_with_audit() + |> Repo.update_record_with_activity_log() end @doc """ Expires the given request if the expiration date is past. """ - @spec expire_if_past_expiration_date(%TransactionRequest{}, Map.t()) :: + @spec expire_if_past_expiration_date(%TransactionRequest{}, map()) :: {:ok, %TransactionRequest{}} | {:error, map()} def expire_if_past_expiration_date(request, originator) do expired? = @@ -352,7 +360,7 @@ defmodule EWalletDB.TransactionRequest do @doc """ Expires the given request if the maximum number of consumptions has been reached. """ - @spec expire_if_max_consumption(%TransactionRequest{}, Map.t()) :: + @spec expire_if_max_consumption(%TransactionRequest{}, map()) :: {:ok, %TransactionRequest{}} | {:error, map()} def expire_if_max_consumption(request, originator) do @@ -366,7 +374,7 @@ defmodule EWalletDB.TransactionRequest do end end - @spec load_consumptions_count(%TransactionRequest{}, Map.t()) :: Integer.t() + @spec load_consumptions_count(%TransactionRequest{}, map()) :: Integer.t() def load_consumptions_count(request, originator) do case request.consumptions_count do nil -> @@ -378,8 +386,11 @@ defmodule EWalletDB.TransactionRequest do end end - @spec update_consumptions_count(%TransactionRequest{}, list(%TransactionConsumption{}), Map.t()) :: - %TransactionRequest{} + @spec update_consumptions_count( + %TransactionRequest{}, + list(%TransactionConsumption{}), + map() + ) :: %TransactionRequest{} defp update_consumptions_count(request, consumptions, originator) do {:ok, request} = request @@ -387,7 +398,7 @@ defmodule EWalletDB.TransactionRequest do consumptions_count: length(consumptions), originator: originator }) - |> Repo.update() + |> Repo.update_record_with_activity_log() request end diff --git a/apps/ewallet_db/lib/ewallet_db/update_email_request.ex b/apps/ewallet_db/lib/ewallet_db/update_email_request.ex index 3633e852d..f180199f7 100644 --- a/apps/ewallet_db/lib/ewallet_db/update_email_request.ex +++ b/apps/ewallet_db/lib/ewallet_db/update_email_request.ex @@ -3,11 +3,11 @@ defmodule EWalletDB.UpdateEmailRequest do Ecto Schema representing a change email request. """ use Ecto.Schema - use EWalletDB.Auditable + use ActivityLogger.ActivityLogging import Ecto.{Changeset, Query} import EWalletDB.Validator alias Ecto.UUID - alias EWalletConfig.Helpers.Crypto + alias Utils.Helpers.Crypto alias EWalletDB.{UpdateEmailRequest, Repo, User} @primary_key {:uuid, UUID, autogenerate: true} @@ -27,13 +27,16 @@ defmodule EWalletDB.UpdateEmailRequest do ) timestamps() - auditable() + activity_logging() end defp changeset(changeset, attrs) do changeset - |> cast(attrs, [:email, :token, :user_uuid]) - |> validate_required([:email, :token, :user_uuid]) + |> cast_and_validate_required_for_activity_log( + attrs, + [:email, :token, :user_uuid], + [:email, :token, :user_uuid] + ) |> validate_email(:email) |> unique_constraint(:token) |> assoc_constraint(:user) @@ -90,13 +93,21 @@ defmodule EWalletDB.UpdateEmailRequest do @spec generate(%User{}, String.t()) :: %UpdateEmailRequest{} | {:error, Changeset.t()} def generate(user, email) do token = Crypto.generate_base64_key(@token_length) - {:ok, _} = insert(%{token: token, email: email, user_uuid: user.uuid}) + + {:ok, _} = + insert(%{ + token: token, + email: email, + user_uuid: user.uuid, + originator: user + }) + UpdateEmailRequest.get(email, token) end defp insert(attrs) do %UpdateEmailRequest{} |> changeset(attrs) - |> Repo.insert() + |> Repo.insert_record_with_activity_log() end end diff --git a/apps/ewallet_db/lib/ewallet_db/user.ex b/apps/ewallet_db/lib/ewallet_db/user.ex index 52cc1449d..8af717e6c 100644 --- a/apps/ewallet_db/lib/ewallet_db/user.ex +++ b/apps/ewallet_db/lib/ewallet_db/user.ex @@ -4,18 +4,17 @@ defmodule EWalletDB.User do """ use Arc.Ecto.Schema use Ecto.Schema - use EWalletConfig.Types.ExternalID - use EWalletDB.Auditable + use Utils.Types.ExternalID + use ActivityLogger.ActivityLogging import Ecto.{Changeset, Query} import EWalletDB.Helpers.Preloader import EWalletDB.Validator alias Ecto.{Multi, UUID} - alias EWalletConfig.Helpers.Crypto + alias Utils.Helpers.Crypto alias EWalletDB.{ Account, AccountUser, - Audit, AuthToken, Invite, Membership, @@ -43,7 +42,6 @@ defmodule EWalletDB.User do field(:encrypted_metadata, EWalletConfig.Encrypted.Map, default: %{}) field(:avatar, EWalletDB.Uploaders.Avatar.Type) field(:enabled, :boolean, default: true) - auditable() belongs_to( :invite, @@ -89,28 +87,30 @@ defmodule EWalletDB.User do ) timestamps() + activity_logging() end defp changeset(changeset, attrs) do password_hash = attrs |> get_attr(:password) |> Crypto.hash_password() changeset - |> Map.delete(:originator) - |> cast(attrs, [ - :is_admin, - :username, - :full_name, - :calling_name, - :provider_user_id, - :email, - :password, - :password_confirmation, - :metadata, - :encrypted_metadata, - :invite_uuid, - :originator - ]) - |> validate_required([:originator]) + |> cast_and_validate_required_for_activity_log( + attrs, + [ + :is_admin, + :username, + :full_name, + :calling_name, + :provider_user_id, + :email, + :password, + :password_confirmation, + :metadata, + :encrypted_metadata, + :invite_uuid, + :originator + ] + ) |> validate_confirmation(:password, message: "does not match password") |> validate_immutable(:provider_user_id) |> unique_constraint(:username) @@ -123,18 +123,19 @@ defmodule EWalletDB.User do defp update_user_changeset(user, attrs) do user - |> Map.delete(:originator) - |> cast(attrs, [ - :username, - :full_name, - :calling_name, - :provider_user_id, - :metadata, - :encrypted_metadata, - :invite_uuid, - :originator - ]) - |> validate_required([:originator]) + |> cast_and_validate_required_for_activity_log( + attrs, + [ + :username, + :full_name, + :calling_name, + :provider_user_id, + :metadata, + :encrypted_metadata, + :invite_uuid, + :originator + ] + ) |> validate_immutable(:provider_user_id) |> unique_constraint(:username) |> unique_constraint(:provider_user_id) @@ -144,24 +145,28 @@ defmodule EWalletDB.User do defp update_admin_changeset(user, attrs) do user - |> Map.delete(:originator) - |> cast(attrs, [ - :full_name, - :calling_name, - :metadata, - :encrypted_metadata, - :invite_uuid, - :originator - ]) - |> validate_required([:originator]) + |> cast_and_validate_required_for_activity_log( + attrs, + [ + :full_name, + :calling_name, + :metadata, + :encrypted_metadata, + :invite_uuid, + :originator + ] + ) |> assoc_constraint(:invite) |> validate_by_roles(attrs) end + defp set_admin_changeset(user, attrs) do + cast_and_validate_required_for_activity_log(user, attrs, [:is_admin]) + end + defp avatar_changeset(user, attrs) do user - |> Map.delete(:originator) - |> cast(attrs, [:originator]) + |> cast_and_validate_required_for_activity_log(attrs, []) |> cast_attachments(attrs, [:avatar]) |> validate_required([:originator]) end @@ -170,23 +175,21 @@ defmodule EWalletDB.User do password_hash = attrs |> get_attr(:password) |> Crypto.hash_password() user - |> Map.delete(:originator) - |> cast(attrs, [ - :password, - :password_confirmation, - :originator - ]) - |> validate_required([:originator]) + |> cast_and_validate_required_for_activity_log( + attrs, + [ + :password, + :password_confirmation, + :originator + ] + ) |> validate_confirmation(:password, message: "does not match password") |> validate_password(:password) |> put_change(:password_hash, password_hash) end defp enable_changeset(%User{} = user, attrs) do - user - |> Map.delete(:originator) - |> cast(attrs, [:enabled, :originator]) - |> validate_required([:enabled, :originator]) + cast_and_validate_required_for_activity_log(user, attrs, [:enabled], [:enabled]) end defp get_attr(attrs, atom_field) do @@ -195,12 +198,11 @@ defmodule EWalletDB.User do defp email_changeset(user, attrs) do user - |> Map.delete(:originator) - |> cast(attrs, [ - :email, - :originator - ]) - |> validate_required([:email, :originator]) + |> cast_and_validate_required_for_activity_log( + attrs, + [:email, :originator], + [:email, :originator] + ) |> validate_email(:email) |> unique_constraint(:email) end @@ -325,7 +327,7 @@ defmodule EWalletDB.User do def insert(attrs) do %User{} |> changeset(attrs) - |> Audit.insert_record_with_audit( + |> Repo.insert_record_with_activity_log( [], Multi.run(Multi.new(), :wallet, fn %{record: record} -> case User.admin?(record) do @@ -351,7 +353,8 @@ defmodule EWalletDB.User do %{ user_uuid: user.uuid, name: identifier, - identifier: identifier + identifier: identifier, + originator: user } |> Wallet.insert() end @@ -368,7 +371,7 @@ defmodule EWalletDB.User do update_user_changeset(user, attrs) end - Audit.update_record_with_audit(changeset) + Repo.update_record_with_activity_log(changeset) end @doc """ @@ -405,7 +408,7 @@ defmodule EWalletDB.User do defp do_update_password(user, attrs) do user |> password_changeset(attrs) - |> Audit.update_record_with_audit() + |> Repo.update_record_with_activity_log() end @doc """ @@ -415,7 +418,7 @@ defmodule EWalletDB.User do def update_email(%User{} = user, attrs) do user |> email_changeset(attrs) - |> Audit.update_record_with_audit() + |> Repo.update_record_with_activity_log() end @doc """ @@ -434,7 +437,7 @@ defmodule EWalletDB.User do user |> avatar_changeset(updated_attrs) - |> Audit.update_record_with_audit() + |> Repo.update_record_with_activity_log() end @doc """ @@ -576,11 +579,14 @@ defmodule EWalletDB.User do @doc """ Sets the user's admin status. """ - @spec set_admin(%User{}, boolean()) :: {:ok, %User{}} | {:error, Ecto.Changeset.t()} - def set_admin(user, boolean) do + @spec set_admin(%User{}, boolean(), map()) :: {:ok, %User{}} | {:error, Ecto.Changeset.t()} + def set_admin(user, boolean, originator) do user - |> change(is_admin: boolean) - |> Repo.update() + |> set_admin_changeset(%{ + is_admin: boolean, + originator: originator + }) + |> Repo.update_record_with_activity_log() end @doc """ @@ -682,6 +688,6 @@ defmodule EWalletDB.User do def enable_or_disable(user, attrs) do user |> enable_changeset(attrs) - |> Audit.update_record_with_audit() + |> Repo.update_record_with_activity_log() end end diff --git a/apps/ewallet_db/lib/ewallet_db/vault.ex b/apps/ewallet_db/lib/ewallet_db/vault.ex new file mode 100644 index 000000000..af164ff97 --- /dev/null +++ b/apps/ewallet_db/lib/ewallet_db/vault.ex @@ -0,0 +1,32 @@ +defmodule EWalletDB.Vault do + @moduledoc false + + use Cloak.Vault, otp_app: :ewallet_db + + @impl Cloak.Vault + def init(config) do + env = Mix.env() + + config = + Keyword.put( + config, + :ciphers, + default: {Cloak.Ciphers.AES.GCM, tag: "AES.GCM.V1", key: secret_key(env)} + ) + + {:ok, config} + end + + defp secret_key(:prod), do: decode_env("EWALLET_SECRET_KEY") + + defp secret_key(_), + do: + <<126, 194, 0, 33, 217, 227, 143, 82, 252, 80, 133, 89, 70, 211, 139, 150, 209, 103, 94, + 240, 194, 108, 166, 100, 48, 144, 207, 242, 93, 244, 27, 144>> + + defp decode_env(var) do + var + |> System.get_env() + |> Base.decode64!() + end +end diff --git a/apps/ewallet_db/lib/ewallet_db/wallet.ex b/apps/ewallet_db/lib/ewallet_db/wallet.ex index 88a20946b..977fefa91 100644 --- a/apps/ewallet_db/lib/ewallet_db/wallet.ex +++ b/apps/ewallet_db/lib/ewallet_db/wallet.ex @@ -3,14 +3,15 @@ defmodule EWalletDB.Wallet do Ecto Schema representing wallet. """ use Ecto.Schema - use EWalletConfig.Types.WalletAddress - use EWalletDB.Auditable + use Utils.Types.WalletAddress + use ActivityLogger.ActivityLogging import Ecto.{Changeset, Query} import EWalletDB.Validator alias Ecto.UUID - alias EWalletConfig.Types.WalletAddress + alias Utils.Types.WalletAddress alias EWalletDB.{Account, Repo, User, Wallet} alias ExULID.ULID + alias ActivityLogger.System @genesis "genesis" @burn "burn" @@ -46,7 +47,7 @@ defmodule EWalletDB.Wallet do field(:metadata, :map, default: %{}) field(:encrypted_metadata, EWalletConfig.Encrypted.Map, default: %{}) field(:enabled, :boolean) - auditable() + activity_logging() belongs_to( :user, @@ -69,7 +70,7 @@ defmodule EWalletDB.Wallet do defp changeset(%Wallet{} = wallet, attrs) do wallet - |> cast_and_validate_required_for_audit( + |> cast_and_validate_required_for_activity_log( attrs, @cast_attrs, [:name, :identifier] @@ -84,16 +85,22 @@ defmodule EWalletDB.Wallet do defp secondary_changeset(%Wallet{} = wallet, attrs) do wallet - |> cast(attrs, @cast_attrs) - |> validate_required([:name, :identifier, :account_uuid]) + |> cast_and_validate_required_for_activity_log( + attrs, + @cast_attrs, + [:name, :identifier, :account_uuid] + ) |> validate_format(:identifier, ~r/#{@secondary}_.*/) |> shared_changeset() end defp burn_changeset(%Wallet{} = wallet, attrs) do wallet - |> cast(attrs, @cast_attrs) - |> validate_required([:name, :identifier, :account_uuid]) + |> cast_and_validate_required_for_activity_log( + attrs, + @cast_attrs, + [:name, :identifier, :account_uuid] + ) |> validate_format(:identifier, ~r/#{@burn}|#{@burn}_.*/) |> shared_changeset() end @@ -112,8 +119,7 @@ defmodule EWalletDB.Wallet do defp enable_changeset(%Wallet{} = wallet, attrs) do wallet - |> cast(attrs, [:enabled]) - |> validate_required([:enabled]) + |> cast_and_validate_required_for_activity_log(attrs, [:enabled], [:enabled]) end @spec all_for(any()) :: Ecto.Query.t() | nil @@ -162,7 +168,7 @@ defmodule EWalletDB.Wallet do def insert(attrs) do %Wallet{} |> changeset(attrs) - |> Repo.insert() + |> Repo.insert_record_with_activity_log() end @spec insert_secondary_or_burn(map()) :: {:ok, %__MODULE__{}} | {:error, Ecto.Changeset.t()} @@ -177,12 +183,12 @@ defmodule EWalletDB.Wallet do def insert_secondary_or_burn(attrs), do: insert_secondary_or_burn(attrs, nil) def insert_secondary_or_burn(attrs, "burn") do - %Wallet{} |> burn_changeset(attrs) |> Repo.insert() + %Wallet{} |> burn_changeset(attrs) |> Repo.insert_record_with_activity_log() end # "secondary" and anything else will go in there. def insert_secondary_or_burn(attrs, _) do - %Wallet{} |> secondary_changeset(attrs) |> Repo.insert() + %Wallet{} |> secondary_changeset(attrs) |> Repo.insert_record_with_activity_log() end defp build_identifier("genesis"), do: @genesis @@ -211,12 +217,17 @@ defmodule EWalletDB.Wallet do """ @spec insert_genesis :: {:ok, %__MODULE__{}} | {:ok, nil} | {:error, Ecto.Changeset.t()} def insert_genesis do - changeset = - changeset(%Wallet{}, %{address: @genesis_address, name: @genesis, identifier: @genesis}) - opts = [on_conflict: :nothing, conflict_target: :address] - case Repo.insert(changeset, opts) do + %Wallet{} + |> changeset(%{ + address: @genesis_address, + name: @genesis, + identifier: @genesis, + originator: %System{} + }) + |> Repo.insert_record_with_activity_log(opts) + |> case do {:ok, _wallet} -> {:ok, get(@genesis_address)} @@ -242,6 +253,6 @@ defmodule EWalletDB.Wallet do def enable_or_disable(wallet, attrs) do wallet |> enable_changeset(attrs) - |> Repo.update() + |> Repo.update_record_with_activity_log() end end diff --git a/apps/ewallet_db/mix.exs b/apps/ewallet_db/mix.exs index 81af33f8b..1faa971fa 100644 --- a/apps/ewallet_db/mix.exs +++ b/apps/ewallet_db/mix.exs @@ -64,7 +64,8 @@ defmodule EWalletDB.Mixfile do {:hackney, "~> 1.6"}, {:sweet_xml, "~> 0.6"}, - {:ewallet_config, in_umbrella: true} + {:ewallet_config, in_umbrella: true}, + {:activity_logger, in_umbrella: true} ] end diff --git a/apps/ewallet_db/priv/repo/migrations/20171106104102_change_secret_key_to_secret_key_hash.exs b/apps/ewallet_db/priv/repo/migrations/20171106104102_change_secret_key_to_secret_key_hash.exs index b78eac33d..c189e19a2 100644 --- a/apps/ewallet_db/priv/repo/migrations/20171106104102_change_secret_key_to_secret_key_hash.exs +++ b/apps/ewallet_db/priv/repo/migrations/20171106104102_change_secret_key_to_secret_key_hash.exs @@ -1,7 +1,7 @@ defmodule EWalletDB.Repo.Migrations.ChangeSecretKeyToSecretKeyHash do use Ecto.Migration import Ecto.Query - alias EWalletConfig.Helpers.Crypto + alias Utils.Helpers.Crypto alias EWalletDB.Repo def up do diff --git a/apps/ewallet_db/priv/repo/migrations/20180907173648_add_audit.exs b/apps/ewallet_db/priv/repo/migrations/20180907173648_add_audit.exs deleted file mode 100644 index 121683632..000000000 --- a/apps/ewallet_db/priv/repo/migrations/20180907173648_add_audit.exs +++ /dev/null @@ -1,27 +0,0 @@ -defmodule EWalletDB.Repo.Migrations.AddAudit do - use Ecto.Migration - - def change do - create table(:audit, primary_key: false) do - add :uuid, :uuid, primary_key: true - add :id, :string, null: false - - add :action, :string, null: false - - add :target_uuid, :uuid, null: false - add :target_type, :string, null: false - add :target_changes, :map, null: false - add :target_encrypted_metadata, :binary - - add :originator_uuid, :uuid - add :originator_type, :string - - add :metadata, :map - - add :inserted_at, :naive_datetime - end - - create index(:audit, [:target_uuid, :target_type]) - create index(:audit, [:originator_uuid, :originator_type]) - end -end diff --git a/apps/ewallet_db/priv/repo/seeds/01_user.exs b/apps/ewallet_db/priv/repo/seeds/01_user.exs index 84d8946ee..622ecbc0a 100644 --- a/apps/ewallet_db/priv/repo/seeds/01_user.exs +++ b/apps/ewallet_db/priv/repo/seeds/01_user.exs @@ -1,7 +1,7 @@ defmodule EWalletDB.Repo.Seeds.UserSeed do alias EWalletConfig.{System, Helpers.Crypto} alias EWalletDB.{Account, AccountUser, User} - alias EWalletConfig.System + alias ActivityLogger.System @argsline_desc """ This email and password combination is required for logging into the admin panel. diff --git a/apps/ewallet_db/priv/repo/seeds_sample/00_admin_panel_user.exs b/apps/ewallet_db/priv/repo/seeds_sample/00_admin_panel_user.exs index 6def0f349..dbad16746 100644 --- a/apps/ewallet_db/priv/repo/seeds_sample/00_admin_panel_user.exs +++ b/apps/ewallet_db/priv/repo/seeds_sample/00_admin_panel_user.exs @@ -1,7 +1,7 @@ defmodule EWalletDB.Repo.Seeds.AdminPanelUserSampleSeed do - import EWalletConfig.Helpers.Crypto, only: [generate_base64_key: 1] + import Utils.Helpers.Crypto, only: [generate_base64_key: 1] alias EWalletDB.User - alias EWalletConfig.System + alias ActivityLogger.System @seed_data [ %{ diff --git a/apps/ewallet_db/priv/repo/seeds_sample/00_user.exs b/apps/ewallet_db/priv/repo/seeds_sample/00_user.exs index 10920ffdf..fce6aaffc 100644 --- a/apps/ewallet_db/priv/repo/seeds_sample/00_user.exs +++ b/apps/ewallet_db/priv/repo/seeds_sample/00_user.exs @@ -2,7 +2,7 @@ defmodule EWalletDB.Repo.Seeds.UserSampleSeed do alias Ecto.UUID alias EWallet.TransactionGate alias EWalletDB.{Account, AccountUser, Token, User} - alias EWalletConfig.System + alias ActivityLogger.System @users_count 5 @username_prefix "user" diff --git a/apps/ewallet_db/priv/repo/seeds_test/01_user.exs b/apps/ewallet_db/priv/repo/seeds_test/01_user.exs index 4933a6730..784779807 100644 --- a/apps/ewallet_db/priv/repo/seeds_test/01_user.exs +++ b/apps/ewallet_db/priv/repo/seeds_test/01_user.exs @@ -1,7 +1,7 @@ defmodule EWalletDB.Repo.Seeds.UserSeed do alias EWalletDB.{Account, AccountUser, User} # credo:disable-for-next-line Credo.Check.Readability.AliasOrder - alias EWalletConfig.System, as: OriginatorSystem + alias ActivityLogger.System, as: OriginatorSystem @seed_data [ %{ diff --git a/apps/ewallet_db/test/ewallet_db/account_test.exs b/apps/ewallet_db/test/ewallet_db/account_test.exs index 53c3bb28a..4a2b40ed5 100644 --- a/apps/ewallet_db/test/ewallet_db/account_test.exs +++ b/apps/ewallet_db/test/ewallet_db/account_test.exs @@ -2,6 +2,7 @@ defmodule EWalletDB.AccountTest do use EWalletDB.SchemaCase alias EWalletDB.Helpers.Preloader alias EWalletDB.{Account, Repo} + alias ActivityLogger.System describe "Account factory" do test_has_valid_factory(Account) @@ -22,7 +23,7 @@ defmodule EWalletDB.AccountTest do end test "inserts and associates categories when provided a list of category_ids" do - [cat1, cat2] = insert_list(2, :category) + [cat1, cat2] = insert_list(2, :category, originator: nil) {:ok, account} = :account @@ -92,7 +93,7 @@ defmodule EWalletDB.AccountTest do describe "update/2 with category_ids" do test "associates the category if it's been added to category_ids" do # Prepare 4 categories. We will start off the account with 2, add 1, and leave one behind. - [cat1, cat2, cat3, _not_used] = insert_list(4, :category) + [cat1, cat2, cat3, _not_used] = insert_list(4, :category, originator: nil) {:ok, account} = :account @@ -104,14 +105,18 @@ defmodule EWalletDB.AccountTest do assert_categories(account, [cat1, cat2]) # Now update with additional category_ids - {:ok, updated} = Account.update(account, %{category_ids: [cat1.id, cat2.id, cat3.id]}) + {:ok, updated} = + Account.update(account, %{ + category_ids: [cat1.id, cat2.id, cat3.id], + originator: %System{} + }) # Assert that the 3rd category is added assert_categories(updated, [cat1, cat2, cat3]) end test "removes the category if it's no longer in the category_ids" do - [cat1, cat2] = insert_list(2, :category) + [cat1, cat2] = insert_list(2, :category, originator: nil) {:ok, account} = :account @@ -123,14 +128,18 @@ defmodule EWalletDB.AccountTest do assert_categories(account, [cat1, cat2]) # Now update by removing a category from category_ids - {:ok, updated} = Account.update(account, %{category_ids: [cat1.id]}) + {:ok, updated} = + Account.update(account, %{ + category_ids: [cat1.id], + originator: %System{} + }) # Only one category should be left assert_categories(updated, [cat1]) end test "removes all categories if category_ids is an empty list" do - [cat1, cat2] = insert_list(2, :category) + [cat1, cat2] = insert_list(2, :category, originator: nil) {:ok, account} = :account @@ -142,14 +151,18 @@ defmodule EWalletDB.AccountTest do assert_categories(account, [cat1, cat2]) # Now update by setting category_ids to an empty list - {:ok, updated} = Account.update(account, %{category_ids: []}) + {:ok, updated} = + Account.update(account, %{ + category_ids: [], + originator: %System{} + }) # No category should be left assert_categories(updated, []) end test "does nothing if category_ids is nil" do - [cat1, cat2] = insert_list(2, :category) + [cat1, cat2] = insert_list(2, :category, originator: nil) {:ok, account} = :account @@ -161,7 +174,11 @@ defmodule EWalletDB.AccountTest do assert_categories(account, [cat1, cat2]) # Now update by passing a nil category_ids - {:ok, updated} = Account.update(account, %{category_ids: nil}) + {:ok, updated} = + Account.update(account, %{ + category_ids: nil, + originator: %System{} + }) # The categories should remain the same assert_categories(updated, [cat1, cat2]) @@ -416,7 +433,7 @@ defmodule EWalletDB.AccountTest do describe "add_category/2" do test "returns an account with the added category" do - [cat1, cat2] = insert_list(2, :category) + [cat1, cat2] = insert_list(2, :category, originator: nil) account = :account @@ -425,7 +442,7 @@ defmodule EWalletDB.AccountTest do assert account.categories == [cat1] - {:ok, account} = Account.add_category(account, cat2) + {:ok, account} = Account.add_category(account, cat2, %System{}) account = Account.get(account.id, preload: :categories) assert Enum.member?(account.categories, cat1) @@ -436,7 +453,7 @@ defmodule EWalletDB.AccountTest do describe "remove_category/2" do test "returns an account with the removed category" do - [cat1, cat2] = insert_list(2, :category) + [cat1, cat2] = insert_list(2, :category, originator: nil) account = :account @@ -447,7 +464,7 @@ defmodule EWalletDB.AccountTest do assert Enum.member?(account.categories, cat2) assert Enum.count(account.categories) == 2 - {:ok, account} = Account.remove_category(account, cat1) + {:ok, account} = Account.remove_category(account, cat1, %System{}) account = Account.get(account.id, preload: :categories) assert Enum.member?(account.categories, cat2) diff --git a/apps/ewallet_db/test/ewallet_db/api_key_test.exs b/apps/ewallet_db/test/ewallet_db/api_key_test.exs index e6276fa5c..b9225b286 100644 --- a/apps/ewallet_db/test/ewallet_db/api_key_test.exs +++ b/apps/ewallet_db/test/ewallet_db/api_key_test.exs @@ -2,6 +2,7 @@ defmodule EWalletDB.APIKeyTest do use EWalletDB.SchemaCase alias Ecto.UUID alias EWalletDB.APIKey + alias ActivityLogger.System @owner_app :some_app @@ -17,7 +18,7 @@ defmodule EWalletDB.APIKeyTest do end test "does not return a soft-deleted API key" do - {:ok, api_key} = :api_key |> insert() |> APIKey.delete() + {:ok, api_key} = :api_key |> insert() |> APIKey.delete(%System{}) assert APIKey.get(api_key.id) == nil end @@ -65,7 +66,12 @@ defmodule EWalletDB.APIKeyTest do {:ok, api_key} = APIKey.insert(params_for(:api_key)) assert api_key.enabled == true - {:ok, api_key} = APIKey.update(api_key, %{"expired" => true}) + {:ok, api_key} = + APIKey.update(api_key, %{ + "expired" => true, + "originator" => %System{} + }) + assert api_key.enabled == false end end @@ -75,7 +81,12 @@ defmodule EWalletDB.APIKeyTest do {:ok, key} = APIKey.insert(params_for(:api_key)) assert key.enabled == true - {:ok, updated} = APIKey.enable_or_disable(key, %{enabled: false}) + {:ok, updated} = + APIKey.enable_or_disable(key, %{ + enabled: false, + originator: %System{} + }) + assert updated.enabled == false end @@ -83,18 +94,40 @@ defmodule EWalletDB.APIKeyTest do {:ok, key} = APIKey.insert(params_for(:api_key)) assert key.enabled == true - {:ok, updated1} = APIKey.enable_or_disable(key, %{enabled: false}) + {:ok, updated1} = + APIKey.enable_or_disable(key, %{ + enabled: false, + originator: %System{} + }) + assert updated1.enabled == false - {:ok, updated2} = APIKey.enable_or_disable(key, %{enabled: false}) + {:ok, updated2} = + APIKey.enable_or_disable(key, %{ + enabled: false, + originator: %System{} + }) + assert updated2.enabled == false end test "enable an api key successfuly" do - {:ok, key} = APIKey.insert(params_for(:api_key, %{enabled: false})) + {:ok, key} = + APIKey.insert( + params_for(:api_key, %{ + enabled: false, + originator: %System{} + }) + ) + assert key.enabled == false - {:ok, updated} = APIKey.enable_or_disable(key, %{enabled: true}) + {:ok, updated} = + APIKey.enable_or_disable(key, %{ + enabled: true, + originator: %System{} + }) + assert updated.enabled == true end end diff --git a/apps/ewallet_db/test/ewallet_db/audit_test.exs b/apps/ewallet_db/test/ewallet_db/audit_test.exs deleted file mode 100644 index 67643c97a..000000000 --- a/apps/ewallet_db/test/ewallet_db/audit_test.exs +++ /dev/null @@ -1,265 +0,0 @@ -defmodule EWalletDB.AuditTest do - use EWalletDB.SchemaCase - alias Ecto.{Changeset, Multi} - alias EWalletConfig.System - alias EWalletDB.{Audit, User} - - describe "Audit.get_schema/1" do - test "gets the schema from a type" do - assert Audit.get_schema("user") == EWalletDB.User - end - end - - describe "Audit.get_type/1" do - test "gets the type from a schema" do - assert Audit.get_type(EWalletDB.User) == "user" - end - end - - describe "Audit.all_for_target/1" do - test "returns all audits for a target" do - {:ok, _user} = :user |> params_for() |> User.insert() - {:ok, _user} = :user |> params_for() |> User.insert() - {:ok, user} = :user |> params_for() |> User.insert() - - {:ok, user} = - User.update(user, %{ - username: "test_username", - originator: %System{} - }) - - audits = Audit.all_for_target(user) - - assert length(audits) == 2 - - results = Enum.map(audits, fn a -> {a.action, a.originator_type, a.target_type} end) - assert Enum.member?(results, {"insert", "user", "user"}) - assert Enum.member?(results, {"update", "system", "user"}) - end - end - - describe "Audit.all_for_target/2" do - test "returns all audits for a target when given a string" do - {:ok, _user} = :user |> params_for() |> User.insert() - {:ok, _user} = :user |> params_for() |> User.insert() - {:ok, user} = :user |> params_for() |> User.insert() - - {:ok, user} = - User.update(user, %{ - username: "test_username", - originator: %System{} - }) - - audits = Audit.all_for_target("user", user.uuid) - - assert length(audits) == 2 - - results = Enum.map(audits, fn a -> {a.action, a.originator_type, a.target_type} end) - assert Enum.member?(results, {"insert", "user", "user"}) - assert Enum.member?(results, {"update", "system", "user"}) - end - - test "returns all audits for a target when given a module name" do - {:ok, _user} = :user |> params_for() |> User.insert() - {:ok, _user} = :user |> params_for() |> User.insert() - {:ok, user} = :user |> params_for() |> User.insert() - - {:ok, user} = - User.update(user, %{ - username: "test_username", - originator: %System{} - }) - - audits = Audit.all_for_target(User, user.uuid) - - assert length(audits) == 2 - - results = Enum.map(audits, fn a -> {a.action, a.originator_type, a.target_type} end) - assert Enum.member?(results, {"insert", "user", "user"}) - assert Enum.member?(results, {"update", "system", "user"}) - end - end - - describe "Audit.get_initial_audit/2" do - test "gets the initial audit for a record" do - initial_originator = insert(:admin) - {:ok, user} = :user |> params_for(%{originator: initial_originator}) |> User.insert() - - {:ok, user} = - User.update(user, %{ - username: "test_username", - originator: %System{} - }) - - audit = Audit.get_initial_audit("user", user.uuid) - - assert audit.originator_type == "user" - assert audit.originator_uuid == initial_originator.uuid - assert audit.target_type == "user" - assert audit.target_uuid == user.uuid - assert audit.action == "insert" - assert audit.inserted_at != nil - end - end - - describe "Audit.get_initial_originator/2" do - test "gets the initial originator for a record" do - initial_originator = insert(:admin) - {:ok, user} = :user |> params_for(%{originator: initial_originator}) |> User.insert() - - {:ok, user} = - User.update(user, %{ - username: "test_username", - originator: %System{} - }) - - originator = Audit.get_initial_originator(user) - - assert originator.__struct__ == User - assert originator.uuid == initial_originator.uuid - end - end - - describe "Audit.insert_record_with_audit/2" do - test "inserts an audit and a user with encrypted metadata" do - admin = insert(:admin) - - params = - params_for(:user, %{ - encrypted_metadata: %{something: "cool"}, - originator: admin - }) - - changeset = Changeset.change(%User{}, params) - {res, record} = Audit.insert_record_with_audit(changeset) - audit = record |> Audit.all_for_target() |> Enum.at(0) - - assert res == :ok - - assert audit.action == "insert" - assert audit.originator_type == "user" - assert audit.originator_uuid == admin.uuid - assert audit.target_type == "user" - assert audit.target_uuid == record.uuid - - assert audit.target_changes == %{ - "calling_name" => record.calling_name, - "full_name" => record.full_name, - "metadata" => record.metadata, - "provider_user_id" => record.provider_user_id, - "username" => record.username - } - - assert audit.target_encrypted_metadata == %{"something" => "cool"} - - assert record |> Audit.all_for_target() |> length() == 1 - end - end - - describe "Audit.update_record_with_audit/2" do - test "inserts an audit when updating a user" do - admin = insert(:admin) - {:ok, user} = :user |> params_for() |> User.insert() - - params = - params_for(:user, %{ - username: "test_username", - originator: admin - }) - - changeset = Changeset.change(user, params) - {res, record} = Audit.update_record_with_audit(changeset) - audit = record |> Audit.all_for_target() |> Enum.at(0) - - assert res == :ok - - assert audit.action == "update" - assert audit.originator_type == "user" - assert audit.originator_uuid == admin.uuid - assert audit.target_type == "user" - assert audit.target_uuid == record.uuid - - assert audit.target_changes == %{ - "calling_name" => record.calling_name, - "full_name" => record.full_name, - "metadata" => record.metadata, - "provider_user_id" => record.provider_user_id, - "username" => record.username - } - - assert audit.target_encrypted_metadata == %{} - - assert user |> Audit.all_for_target() |> length() == 2 - end - end - - describe "perform/4" do - test "inserts an audit and a user as well as a wallet" do - admin = insert(:admin) - - params = - params_for(:user, %{ - encrypted_metadata: %{something: "cool"}, - originator: admin - }) - - changeset = Changeset.change(%User{}, params) - - multi = - Multi.new() - |> Multi.run(:wow_user, fn %{record: _record} -> - {:ok, insert(:user, username: "test_username")} - end) - - {res, %{audit: audit, record: record, wow_user: wow_user}} = - Audit.perform(:insert, changeset, [], multi) - - assert res == :ok - - assert audit.action == "insert" - assert audit.originator_type == "user" - assert audit.originator_uuid == admin.uuid - assert audit.target_type == "user" - assert audit.target_uuid == record.uuid - - assert wow_user != nil - assert wow_user.username == "test_username" - - assert record |> Audit.all_for_target() |> length() == 1 - end - - test "inserts an audit and updates a user as well as saving a wallet" do - admin = insert(:admin) - {:ok, user} = :user |> params_for() |> User.insert() - - params = - params_for(:user, %{ - username: "test_username", - originator: admin - }) - - changeset = Changeset.change(user, params) - - multi = - Multi.new() - |> Multi.run(:wow_user, fn %{record: _record} -> - {:ok, insert(:user, username: "test_another_username")} - end) - - {res, %{audit: audit, record: record, wow_user: _}} = - Audit.perform(:update, changeset, [], multi) - - assert res == :ok - - assert audit.action == "update" - assert audit.originator_type == "user" - assert audit.originator_uuid == admin.uuid - assert audit.target_type == "user" - assert audit.target_uuid == record.uuid - changes = Map.delete(changeset.changes, :originator) - assert audit.target_changes == changes - - assert user |> Audit.all_for_target() |> length() == 2 - end - end -end diff --git a/apps/ewallet_db/test/ewallet_db/auth_token_test.exs b/apps/ewallet_db/test/ewallet_db/auth_token_test.exs index e49d725bb..521230c67 100644 --- a/apps/ewallet_db/test/ewallet_db/auth_token_test.exs +++ b/apps/ewallet_db/test/ewallet_db/auth_token_test.exs @@ -1,6 +1,7 @@ defmodule EWalletDB.AuthTokenTest do use EWalletDB.SchemaCase alias EWalletDB.{AuthToken, Membership} + alias ActivityLogger.System @owner_app :some_app @@ -9,7 +10,7 @@ defmodule EWalletDB.AuthTokenTest do user = insert(:user) account = insert(:account) role = insert(:role, name: "admin") - {:ok, _} = Membership.assign(user, account, role) + {:ok, _} = Membership.assign(user, account, role, %System{}) {res, auth_token} = AuthToken.generate(user, @owner_app) @@ -29,7 +30,7 @@ defmodule EWalletDB.AuthTokenTest do user = insert(:user) account = insert(:account) role = insert(:role, name: "admin") - {:ok, _} = Membership.assign(user, account, role) + {:ok, _} = Membership.assign(user, account, role, %System{}) {:ok, token1} = AuthToken.generate(user, @owner_app) {:ok, token2} = AuthToken.generate(user, @owner_app) @@ -50,7 +51,7 @@ defmodule EWalletDB.AuthTokenTest do user = insert(:user) account = insert(:account) role = insert(:role, name: "admin") - {:ok, _} = Membership.assign(user, account, role) + {:ok, _} = Membership.assign(user, account, role, %System{}) {:ok, auth_token} = AuthToken.generate(user, @owner_app) auth_user = AuthToken.authenticate(auth_token.token, @owner_app) @@ -61,7 +62,7 @@ defmodule EWalletDB.AuthTokenTest do {:ok, token} = :auth_token |> insert(%{owner_app: Atom.to_string(@owner_app)}) - |> AuthToken.expire() + |> AuthToken.expire(%System{}) assert AuthToken.authenticate(token.token, @owner_app) == :token_expired end @@ -70,7 +71,7 @@ defmodule EWalletDB.AuthTokenTest do {:ok, token} = :auth_token |> insert(%{owner_app: "wrong_app"}) - |> AuthToken.expire() + |> AuthToken.expire(%System{}) assert AuthToken.authenticate(token.token, @owner_app) == false end @@ -89,7 +90,7 @@ defmodule EWalletDB.AuthTokenTest do user = insert(:user) account = insert(:account) role = insert(:role, name: "admin") - {:ok, _} = Membership.assign(user, account, role) + {:ok, _} = Membership.assign(user, account, role, %System{}) {:ok, auth_token} = AuthToken.generate(user, @owner_app) @@ -101,7 +102,7 @@ defmodule EWalletDB.AuthTokenTest do user = insert(:admin) account = insert(:account) role = insert(:role, name: "admin") - {:ok, _} = Membership.assign(user, account, role) + {:ok, _} = Membership.assign(user, account, role, %System{}) {:ok, token1} = AuthToken.generate(user, @owner_app) {:ok, token2} = AuthToken.generate(user, @owner_app) @@ -111,7 +112,7 @@ defmodule EWalletDB.AuthTokenTest do test "returns :token_expired if token exists but expired" do token = insert(:auth_token, %{owner_app: Atom.to_string(@owner_app)}) - AuthToken.expire(token) + AuthToken.expire(token, %System{}) assert AuthToken.authenticate(token.user.id, token.token, @owner_app) == :token_expired end @@ -120,7 +121,7 @@ defmodule EWalletDB.AuthTokenTest do user = insert(:admin) account = insert(:account) role = insert(:role, name: "admin") - {:ok, _} = Membership.assign(user, account, role) + {:ok, _} = Membership.assign(user, account, role, %System{}) {:ok, auth_token} = AuthToken.generate(user, @owner_app) another_user = insert(:admin) @@ -131,7 +132,7 @@ defmodule EWalletDB.AuthTokenTest do user = insert(:admin) account = insert(:account) role = insert(:role, name: "admin") - {:ok, _} = Membership.assign(user, account, role) + {:ok, _} = Membership.assign(user, account, role, %System{}) {:ok, auth_token} = AuthToken.generate(user, :different_app) @@ -142,7 +143,7 @@ defmodule EWalletDB.AuthTokenTest do user = insert(:admin) account = insert(:account) role = insert(:role, name: "admin") - {:ok, _} = Membership.assign(user, account, role) + {:ok, _} = Membership.assign(user, account, role, %System{}) {:ok, _} = AuthToken.generate(user, @owner_app) assert AuthToken.authenticate(user.id, "unmatched", @owner_app) == false @@ -152,7 +153,7 @@ defmodule EWalletDB.AuthTokenTest do user = insert(:admin) account = insert(:account) role = insert(:role, name: "admin") - {:ok, _} = Membership.assign(user, account, role) + {:ok, _} = Membership.assign(user, account, role, %System{}) {:ok, _} = AuthToken.generate(user, @owner_app) assert AuthToken.authenticate(user.id, nil, @owner_app) == false @@ -166,7 +167,7 @@ defmodule EWalletDB.AuthTokenTest do # Ensure token is usable. assert AuthToken.authenticate(token_string, @owner_app) - AuthToken.expire(token) + AuthToken.expire(token, %System{}) assert AuthToken.authenticate(token_string, @owner_app) == :token_expired end @@ -176,7 +177,7 @@ defmodule EWalletDB.AuthTokenTest do # Ensure token is usable. assert AuthToken.authenticate(token_string, @owner_app) - AuthToken.expire(token_string, @owner_app) + AuthToken.expire(token_string, @owner_app, %System{}) assert AuthToken.authenticate(token_string, @owner_app) == :token_expired end end @@ -186,7 +187,7 @@ defmodule EWalletDB.AuthTokenTest do user = insert(:user, %{enabled: true}) account = insert(:account) role = insert(:role, name: "admin") - {:ok, _} = Membership.assign(user, account, role) + {:ok, _} = Membership.assign(user, account, role, %System{}) {:ok, token1} = AuthToken.generate(user, @owner_app) token1_string = token1.token @@ -205,7 +206,7 @@ defmodule EWalletDB.AuthTokenTest do user = insert(:user, %{enabled: false}) account = insert(:account) role = insert(:role, name: "admin") - {:ok, _} = Membership.assign(user, account, role) + {:ok, _} = Membership.assign(user, account, role, %System{}) {:ok, token1} = AuthToken.generate(user, @owner_app) token1_string = token1.token diff --git a/apps/ewallet_db/test/ewallet_db/category_test.exs b/apps/ewallet_db/test/ewallet_db/category_test.exs index 35c4ccdef..fedd2b3e3 100644 --- a/apps/ewallet_db/test/ewallet_db/category_test.exs +++ b/apps/ewallet_db/test/ewallet_db/category_test.exs @@ -1,6 +1,7 @@ defmodule EWalletDB.CategoryTest do use EWalletDB.SchemaCase alias EWalletDB.Category + alias ActivityLogger.System defp insert_category(accounts) do account_ids = Enum.map(accounts, fn account -> account.id end) @@ -54,7 +55,11 @@ defmodule EWalletDB.CategoryTest do assert_accounts(category, [acc1, acc2]) # Now update with additional account_ids - {:ok, updated} = Category.update(category, %{account_ids: [acc1.id, acc2.id, acc3.id]}) + {:ok, updated} = + Category.update(category, %{ + account_ids: [acc1.id, acc2.id, acc3.id], + originator: %System{} + }) # Assert that the 3rd account is added assert_accounts(updated, [acc1, acc2, acc3]) @@ -68,7 +73,11 @@ defmodule EWalletDB.CategoryTest do assert_accounts(category, [acc1, acc2]) # Now update by removing a account from category_ids - {:ok, updated} = Category.update(category, %{account_ids: [acc1.id]}) + {:ok, updated} = + Category.update(category, %{ + account_ids: [acc1.id], + originator: %System{} + }) # Only one account should be left assert_accounts(updated, [acc1]) @@ -82,7 +91,11 @@ defmodule EWalletDB.CategoryTest do assert_accounts(category, [acc1, acc2]) # Now update by setting account_ids to an empty list - {:ok, updated} = Category.update(category, %{account_ids: []}) + {:ok, updated} = + Category.update(category, %{ + account_ids: [], + originator: %System{} + }) # No category should be left assert_accounts(updated, []) @@ -96,7 +109,11 @@ defmodule EWalletDB.CategoryTest do assert_accounts(category, [acc1, acc2]) # Now update by passing a nil account_ids - {:ok, updated} = Category.update(category, %{account_ids: nil}) + {:ok, updated} = + Category.update(category, %{ + account_ids: nil, + originator: %System{} + }) # The categories should remain the same assert_accounts(updated, [acc1, acc2]) @@ -131,7 +148,7 @@ defmodule EWalletDB.CategoryTest do # Make sure that the category has an account assert_accounts(category, [account]) - {res, code} = Category.delete(category) + {res, code} = Category.delete(category, %System{}) assert res == :error assert code == :category_not_empty diff --git a/apps/ewallet_db/test/ewallet_db/exchange_pair_test.exs b/apps/ewallet_db/test/ewallet_db/exchange_pair_test.exs index 7c605b155..6b3ed42b7 100644 --- a/apps/ewallet_db/test/ewallet_db/exchange_pair_test.exs +++ b/apps/ewallet_db/test/ewallet_db/exchange_pair_test.exs @@ -1,7 +1,7 @@ defmodule EWalletDB.ExchangePairTest do use EWalletDB.SchemaCase alias EWalletDB.ExchangePair - alias EWalletConfig.System + alias ActivityLogger.System describe "ExchangePair factory" do test_has_valid_factory(ExchangePair) @@ -16,7 +16,7 @@ defmodule EWalletDB.ExchangePairTest do test_insert_generate_timestamps(ExchangePair) test "allows inserting existing pairs if the existing pairs are soft-deleted" do - {:ok, pair} = :exchange_pair |> insert() |> ExchangePair.delete() + {:ok, pair} = :exchange_pair |> insert() |> ExchangePair.delete(%System{}) attrs = %{ from_token_uuid: pair.from_token_uuid, @@ -134,7 +134,7 @@ defmodule EWalletDB.ExchangePairTest do deleted_at: NaiveDateTime.utc_now() ) - {res, code} = ExchangePair.restore(deleted) + {res, code} = ExchangePair.restore(deleted, %System{}) assert res == :error assert code == :exchange_pair_already_exists @@ -144,7 +144,7 @@ defmodule EWalletDB.ExchangePairTest do describe "touch/1" do test "touches the exchange pair's updated_at" do inserted = insert(:exchange_pair) - {res, touched} = ExchangePair.touch(inserted) + {res, touched} = ExchangePair.touch(inserted, %System{}) assert res == :ok assert NaiveDateTime.compare(touched.updated_at, inserted.updated_at) == :gt diff --git a/apps/ewallet_db/test/ewallet_db/invite_test.exs b/apps/ewallet_db/test/ewallet_db/invite_test.exs index 91746e3e5..20a8a0d21 100644 --- a/apps/ewallet_db/test/ewallet_db/invite_test.exs +++ b/apps/ewallet_db/test/ewallet_db/invite_test.exs @@ -1,8 +1,9 @@ defmodule EWalletDB.InviteTest do use EWalletDB.SchemaCase alias Ecto.UUID - alias EWalletConfig.Helpers.Crypto + alias Utils.Helpers.Crypto alias EWalletDB.{Invite, User} + alias ActivityLogger.System describe "Invite.get/1" do test "returns an Invite if the given id is found" do @@ -87,7 +88,7 @@ defmodule EWalletDB.InviteTest do describe "Invite.generate/2" do test "returns {:ok, invite} for the given user" do {:ok, admin} = :admin |> params_for() |> User.insert() - {result, invite} = Invite.generate(admin) + {result, invite} = Invite.generate(admin, %System{}) assert result == :ok assert %Invite{} = invite @@ -97,7 +98,7 @@ defmodule EWalletDB.InviteTest do test "associates the invite_uuid to the user" do {:ok, admin} = :admin |> params_for() |> User.insert() - {:ok, invite} = Invite.generate(admin) + {:ok, invite} = Invite.generate(admin, %System{}) user = User.get(admin.id) assert user.invite_uuid == invite.uuid @@ -105,14 +106,14 @@ defmodule EWalletDB.InviteTest do test "sets the success_url if the option is given" do {:ok, admin} = :admin |> params_for() |> User.insert() - {:ok, invite} = Invite.generate(admin, success_url: "http://some_url") + {:ok, invite} = Invite.generate(admin, %System{}, success_url: "http://some_url") assert invite.success_url == "http://some_url" end test "preloads the invite if the option is given" do {:ok, admin} = :admin |> params_for() |> User.insert() - {:ok, invite} = Invite.generate(admin, preload: :user) + {:ok, invite} = Invite.generate(admin, %System{}, preload: :user) assert invite.user.uuid == admin.uuid end @@ -121,7 +122,7 @@ defmodule EWalletDB.InviteTest do describe "Invite.accept/2" do test "sets user to :active status" do {:ok, admin} = :admin |> params_for() |> User.insert() - {:ok, invite} = Invite.generate(admin) + {:ok, invite} = Invite.generate(admin, %System{}) user = User.get_by(uuid: invite.user_uuid) assert User.get_status(user) == :pending_confirmation @@ -134,7 +135,7 @@ defmodule EWalletDB.InviteTest do test "sets user with the given password" do {:ok, admin} = :admin |> params_for() |> User.insert() - {:ok, invite} = Invite.generate(admin) + {:ok, invite} = Invite.generate(admin, %System{}) {res, _invite} = Invite.accept(invite, "some_password", "some_password") admin = User.get(admin.id) @@ -145,7 +146,7 @@ defmodule EWalletDB.InviteTest do test "disassociates the invite_uuid from the user" do {:ok, admin} = :admin |> params_for() |> User.insert() - {:ok, invite} = Invite.generate(admin) + {:ok, invite} = Invite.generate(admin, %System{}) {res, _invite} = Invite.accept(invite, "some_password", "some_password") @@ -155,7 +156,7 @@ defmodule EWalletDB.InviteTest do test "sets verified_at date time" do {:ok, admin} = :admin |> params_for() |> User.insert() - {:ok, invite} = Invite.generate(admin) + {:ok, invite} = Invite.generate(admin, %System{}) {res, invite} = Invite.accept(invite, "some_password", "some_password") @@ -165,7 +166,7 @@ defmodule EWalletDB.InviteTest do test "returns an {:error, changeset} tuple if passwords do not match" do {:ok, admin} = :admin |> params_for() |> User.insert() - {:ok, invite} = Invite.generate(admin) + {:ok, invite} = Invite.generate(admin, %System{}) {res, changeset} = Invite.accept(invite, "some_password", "a_different_password") diff --git a/apps/ewallet_db/test/ewallet_db/key_test.exs b/apps/ewallet_db/test/ewallet_db/key_test.exs index e7aab88b8..316d6272e 100644 --- a/apps/ewallet_db/test/ewallet_db/key_test.exs +++ b/apps/ewallet_db/test/ewallet_db/key_test.exs @@ -2,6 +2,7 @@ defmodule EWalletDB.KeyTest do use EWalletDB.SchemaCase alias Ecto.UUID alias EWalletDB.Key + alias ActivityLogger.System describe "Key factory" do test_has_valid_factory(Key) @@ -19,7 +20,7 @@ defmodule EWalletDB.KeyTest do assert Enum.empty?(Key.all()) keys = insert_list(5, :key) # Soft delete d key - {:ok, _key} = keys |> Enum.at(0) |> Key.delete() + {:ok, _key} = keys |> Enum.at(0) |> Key.delete(%System{}) assert length(Key.all()) == 4 end @@ -33,7 +34,7 @@ defmodule EWalletDB.KeyTest do end test "does not return a soft-deleted key" do - {:ok, key} = :key |> insert() |> Key.delete() + {:ok, key} = :key |> insert() |> Key.delete(%System{}) assert Key.get(key.id) == nil end @@ -54,7 +55,7 @@ defmodule EWalletDB.KeyTest do end test "does not return a soft-deleted key" do - {:ok, key} = :key |> insert() |> Key.delete() + {:ok, key} = :key |> insert() |> Key.delete(%System{}) assert Key.get(:access_key, key.access_key) == nil end @@ -95,7 +96,12 @@ defmodule EWalletDB.KeyTest do {:ok, key} = Key.insert(params_for(:key)) assert key.enabled == true - {:ok, updated} = Key.enable_or_disable(key, %{"expired" => true}) + {:ok, updated} = + Key.enable_or_disable(key, %{ + "expired" => true, + "originator" => %System{} + }) + assert updated.enabled == false end @@ -103,7 +109,12 @@ defmodule EWalletDB.KeyTest do {:ok, key} = Key.insert(params_for(:key)) assert key.enabled == true - {:ok, updated} = Key.enable_or_disable(key, %{enabled: false}) + {:ok, updated} = + Key.enable_or_disable(key, %{ + enabled: false, + originator: %System{} + }) + assert updated.enabled == false end @@ -111,26 +122,60 @@ defmodule EWalletDB.KeyTest do {:ok, key} = Key.insert(params_for(:key)) assert key.enabled == true - {:ok, updated1} = Key.enable_or_disable(key, %{enabled: false}) + {:ok, updated1} = + Key.enable_or_disable(key, %{ + enabled: false, + originator: %System{} + }) + assert updated1.enabled == false - {:ok, updated2} = Key.enable_or_disable(key, %{enabled: false}) + {:ok, updated2} = + Key.enable_or_disable(key, %{ + enabled: false, + originator: %System{} + }) + assert updated2.enabled == false end test "enable a key successfuly when given 'expired' attribute" do - {:ok, key} = Key.insert(params_for(:key, %{enabled: false})) + {:ok, key} = + Key.insert( + params_for(:key, %{ + enabled: false, + originator: %System{} + }) + ) + assert key.enabled == false - {:ok, updated} = Key.enable_or_disable(key, %{"expired" => false}) + {:ok, updated} = + Key.enable_or_disable(key, %{ + "expired" => false, + "originator" => %System{} + }) + assert updated.enabled == true end test "enable a key successfuly when given 'enabled' attribute" do - {:ok, key} = Key.insert(params_for(:key, %{enabled: false})) + {:ok, key} = + Key.insert( + params_for(:key, %{ + enabled: false, + originator: %System{} + }) + ) + assert key.enabled == false - {:ok, updated} = Key.enable_or_disable(key, %{enabled: true}) + {:ok, updated} = + Key.enable_or_disable(key, %{ + enabled: true, + originator: %System{} + }) + assert updated.enabled == true end end @@ -167,7 +212,8 @@ defmodule EWalletDB.KeyTest do {:ok, _key} = Key.enable_or_disable(key, %{ - enabled: false + enabled: false, + originator: %System{} }) res = Key.authenticate("access123", Base.url_encode64("secret321")) diff --git a/apps/ewallet_db/test/ewallet_db/membership_test.exs b/apps/ewallet_db/test/ewallet_db/membership_test.exs index 4434e2bae..8258c7f00 100644 --- a/apps/ewallet_db/test/ewallet_db/membership_test.exs +++ b/apps/ewallet_db/test/ewallet_db/membership_test.exs @@ -1,6 +1,7 @@ defmodule EWalletDB.MembershipTest do use EWalletDB.SchemaCase alias EWalletDB.{Account, Membership, Repo, User} + alias ActivityLogger.System describe "Membership factory" do # Not using `test_has_valid_factory/1` macro here because `Membership.insert/1` is private. @@ -46,7 +47,7 @@ defmodule EWalletDB.MembershipTest do account = insert(:account) role = insert(:role, %{name: "some_role"}) - {res, membership} = Membership.assign(user, account, "some_role") + {res, membership} = Membership.assign(user, account, "some_role", %System{}) assert res == :ok assert membership.user_uuid == user.uuid @@ -61,8 +62,8 @@ defmodule EWalletDB.MembershipTest do insert(:role, %{name: "old_role"}) insert(:role, %{name: "new_role"}) - {:ok, _membership} = Membership.assign(user, account, "old_role") - {:ok, _membership} = Membership.assign(user, account, "new_role") + {:ok, _membership} = Membership.assign(user, account, "old_role", %System{}) + {:ok, _membership} = Membership.assign(user, account, "new_role", %System{}) user = Repo.preload(user, :roles, force: true) assert User.get_roles(user) == ["new_role"] @@ -78,10 +79,10 @@ defmodule EWalletDB.MembershipTest do viewer = insert(:role, name: "viewer", priority: 1) # We assign to the master account - {:ok, inserted_membership} = Membership.assign(user, level_0, admin) + {:ok, inserted_membership} = Membership.assign(user, level_0, admin, %System{}) # So we can't assign the role viewer here on a lower account - {res, reason} = Membership.assign(user, level_2, viewer) + {res, reason} = Membership.assign(user, level_2, viewer, %System{}) assert res == :error assert reason == :user_already_has_rights @@ -100,8 +101,8 @@ defmodule EWalletDB.MembershipTest do admin = insert(:role, name: "admin", priority: 0) viewer = insert(:role, name: "viewer", priority: 1) - {:ok, _membership} = Membership.assign(user, level_0, viewer) - {res, membership} = Membership.assign(user, level_2, admin) + {:ok, _membership} = Membership.assign(user, level_0, viewer, %System{}) + {res, membership} = Membership.assign(user, level_2, admin, %System{}) assert res == :ok assert membership.user_uuid == user.uuid @@ -122,9 +123,9 @@ defmodule EWalletDB.MembershipTest do viewer = insert(:role, name: "viewer", priority: 1) grunt = insert(:role, name: "grunt", priority: 2) - {:ok, _membership} = Membership.assign(user, level_0, viewer) - {:ok, _membership} = Membership.assign(user, level_2, admin) - {res, reason} = Membership.assign(user, level_3, grunt) + {:ok, _membership} = Membership.assign(user, level_0, viewer, %System{}) + {:ok, _membership} = Membership.assign(user, level_2, admin, %System{}) + {res, reason} = Membership.assign(user, level_3, grunt, %System{}) assert res == :error assert reason == :user_already_has_rights @@ -141,9 +142,9 @@ defmodule EWalletDB.MembershipTest do viewer = insert(:role, name: "viewer", priority: 1) grunt = insert(:role, name: "grunt", priority: 2) - {:ok, _membership} = Membership.assign(user, level_0, grunt) - {:ok, _membership} = Membership.assign(user, level_2, viewer) - {res, membership} = Membership.assign(user, level_3, admin) + {:ok, _membership} = Membership.assign(user, level_0, grunt, %System{}) + {:ok, _membership} = Membership.assign(user, level_2, viewer, %System{}) + {res, membership} = Membership.assign(user, level_3, admin, %System{}) assert res == :ok assert membership.user_uuid == user.uuid @@ -162,8 +163,8 @@ defmodule EWalletDB.MembershipTest do admin = insert(:role, name: "admin", priority: 0) viewer = insert(:role, name: "viewer", priority: 1) - {:ok, _membership} = Membership.assign(user, level_2, admin) - {res, membership} = Membership.assign(user, level_0, viewer) + {:ok, _membership} = Membership.assign(user, level_2, admin, %System{}) + {res, membership} = Membership.assign(user, level_0, viewer, %System{}) assert res == :ok assert membership.user_uuid == user.uuid @@ -182,8 +183,8 @@ defmodule EWalletDB.MembershipTest do admin = insert(:role, name: "admin", priority: 0) viewer = insert(:role, name: "viewer", priority: 1) - {:ok, inserted_membership} = Membership.assign(user, level_2, viewer) - {res, membership} = Membership.assign(user, level_0, admin) + {:ok, inserted_membership} = Membership.assign(user, level_2, viewer, %System{}) + {res, membership} = Membership.assign(user, level_0, admin, %System{}) assert res == :ok assert membership.user_uuid == user.uuid @@ -201,8 +202,8 @@ defmodule EWalletDB.MembershipTest do level_2 = insert(:account, parent: level_1) admin = insert(:role, name: "admin", priority: 0) - {:ok, inserted_membership} = Membership.assign(user, level_2, admin) - {res, membership} = Membership.assign(user, level_0, admin) + {:ok, inserted_membership} = Membership.assign(user, level_2, admin, %System{}) + {res, membership} = Membership.assign(user, level_0, admin, %System{}) assert res == :ok assert membership.user_uuid == user.uuid @@ -225,8 +226,8 @@ defmodule EWalletDB.MembershipTest do admin = insert(:role, name: "admin", priority: 0) - {:ok, inserted_membership} = Membership.assign(user, level_2_1, admin) - {res, membership} = Membership.assign(user, level_2_2, admin) + {:ok, inserted_membership} = Membership.assign(user, level_2_1, admin, %System{}) + {res, membership} = Membership.assign(user, level_2_2, admin, %System{}) assert res == :ok assert membership.user_uuid == user.uuid @@ -249,9 +250,9 @@ defmodule EWalletDB.MembershipTest do admin = insert(:role, name: "admin", priority: 0) - {:ok, _inserted_membership_1} = Membership.assign(user, level_2_1, admin) - {:ok, _inserted_membership_2} = Membership.assign(user, level_2_2, admin) - {res, membership} = Membership.assign(user, level_0, admin) + {:ok, _inserted_membership_1} = Membership.assign(user, level_2_1, admin, %System{}) + {:ok, _inserted_membership_2} = Membership.assign(user, level_2_2, admin, %System{}) + {res, membership} = Membership.assign(user, level_0, admin, %System{}) assert res == :ok assert membership.user_uuid == user.uuid @@ -266,7 +267,7 @@ defmodule EWalletDB.MembershipTest do account = insert(:account) role = insert(:role, %{name: "some_role"}) - {res, membership} = Membership.assign(user, account, "some_role") + {res, membership} = Membership.assign(user, account, "some_role", %System{}) assert res == :ok assert membership.user_uuid == user.uuid @@ -278,7 +279,7 @@ defmodule EWalletDB.MembershipTest do user = insert(:user) account = insert(:account) - {res, reason} = Membership.assign(user, account, "missing_role") + {res, reason} = Membership.assign(user, account, "missing_role", %System{}) assert res == :error assert reason == :role_not_found end @@ -292,7 +293,7 @@ defmodule EWalletDB.MembershipTest do {user, account} = insert_user_with_role("some_role") assert User.get_roles(user) == ["some_role"] - {:ok, _} = Membership.unassign(user, account) + {:ok, _} = Membership.unassign(user, account, %System{}) assert User.get_roles(user) == [] end @@ -300,7 +301,7 @@ defmodule EWalletDB.MembershipTest do user = insert(:user) account = insert(:account) - assert Membership.unassign(user, account) == {:error, :membership_not_found} + assert Membership.unassign(user, account, %System{}) == {:error, :membership_not_found} end end end diff --git a/apps/ewallet_db/test/ewallet_db/role_test.exs b/apps/ewallet_db/test/ewallet_db/role_test.exs index 2b0559cfc..49443384f 100644 --- a/apps/ewallet_db/test/ewallet_db/role_test.exs +++ b/apps/ewallet_db/test/ewallet_db/role_test.exs @@ -1,6 +1,7 @@ defmodule EWalletDB.RoleTest do use EWalletDB.SchemaCase alias EWalletDB.{Membership, Role} + alias ActivityLogger.System describe "Role factory" do test_has_valid_factory(Role) @@ -48,12 +49,12 @@ defmodule EWalletDB.RoleTest do user = insert(:admin) account = insert(:account) role = insert(:role, name: "test_role_not_empty") - {:ok, _membership} = Membership.assign(user, account, role) + {:ok, _membership} = Membership.assign(user, account, role, %System{}) users = role.id |> Role.get(preload: :users) |> Map.get(:users) assert Enum.count(users) > 0 - {res, code} = Role.delete(role) + {res, code} = Role.delete(role, %System{}) assert res == :error assert code == :role_not_empty diff --git a/apps/ewallet_db/test/ewallet_db/soft_delete_test.exs b/apps/ewallet_db/test/ewallet_db/soft_delete_test.exs index e0570681f..81ecf3a46 100644 --- a/apps/ewallet_db/test/ewallet_db/soft_delete_test.exs +++ b/apps/ewallet_db/test/ewallet_db/soft_delete_test.exs @@ -2,11 +2,12 @@ defmodule EWalletDB.SoftDeleteTest do use EWalletDB.SchemaCase import EWalletDB.SoftDelete alias EWalletDB.{Key, Repo} + alias ActivityLogger.System describe "exclude_deleted/1" do test "returns records that are not soft-deleted" do key = insert(:key) - {:ok, deleted} = :key |> insert() |> Key.delete() + {:ok, deleted} = :key |> insert() |> Key.delete(%System{}) result = Key @@ -20,7 +21,7 @@ defmodule EWalletDB.SoftDeleteTest do describe "deleted?/1" do test "returns true if record is soft-deleted" do - {:ok, key} = :key |> insert() |> Key.delete() + {:ok, key} = :key |> insert() |> Key.delete(%System{}) assert Key.deleted?(key) end @@ -32,28 +33,28 @@ defmodule EWalletDB.SoftDeleteTest do describe "delete/2" do test "returns an :ok with the soft-deleted record" do - {res, key} = :key |> insert() |> Key.delete() + {res, key} = :key |> insert() |> Key.delete(%System{}) assert res == :ok assert Key.deleted?(key) end test "populates :deleted_at field" do - {:ok, key} = :key |> insert() |> Key.delete() + {:ok, key} = :key |> insert() |> Key.delete(%System{}) assert key.deleted_at != nil end end describe "restore/2" do test "returns an :ok with the record not soft-deleted" do - {res, key} = :key |> insert() |> Key.restore() + {res, key} = :key |> insert() |> Key.restore(%System{}) assert res == :ok refute Key.deleted?(key) end test "set :deleted_at field to nil" do - {:ok, key} = :key |> insert() |> Key.restore() + {:ok, key} = :key |> insert() |> Key.restore(%System{}) assert key.deleted_at == nil end end diff --git a/apps/ewallet_db/test/ewallet_db/token_test.exs b/apps/ewallet_db/test/ewallet_db/token_test.exs index 7a9dfbfb7..1360ba28d 100644 --- a/apps/ewallet_db/test/ewallet_db/token_test.exs +++ b/apps/ewallet_db/test/ewallet_db/token_test.exs @@ -1,6 +1,7 @@ defmodule EWalletDB.TokenTest do use EWalletDB.SchemaCase alias EWalletDB.Token + alias ActivityLogger.System describe "Token factory" do test_has_valid_factory(Token) @@ -91,7 +92,8 @@ defmodule EWalletDB.TokenTest do symbol_first: false, html_entity: "some updated html entity", iso_numeric: "100 updated", - encrypted_metadata: %{} + encrypted_metadata: %{}, + originator: %System{} }) assert updated_token.name == "OmiseGO updated" @@ -144,7 +146,12 @@ defmodule EWalletDB.TokenTest do {:ok, token} = :token |> params_for() |> Token.insert() assert token.enabled == true - {:ok, token} = Token.enable_or_disable(token, %{enabled: false}) + {:ok, token} = + Token.enable_or_disable(token, %{ + enabled: false, + originator: %System{} + }) + assert token.enabled == false end end diff --git a/apps/ewallet_db/test/ewallet_db/transaction_consumption_test.exs b/apps/ewallet_db/test/ewallet_db/transaction_consumption_test.exs index 866608cb0..436e0c088 100644 --- a/apps/ewallet_db/test/ewallet_db/transaction_consumption_test.exs +++ b/apps/ewallet_db/test/ewallet_db/transaction_consumption_test.exs @@ -1,6 +1,7 @@ defmodule EWalletDB.TransactionConsumptionTest do use EWalletDB.SchemaCase alias EWalletDB.TransactionConsumption + alias ActivityLogger.System describe "TransactionConsumption factory" do test_has_valid_factory(TransactionConsumption) @@ -90,35 +91,40 @@ defmodule EWalletDB.TransactionConsumptionTest do insert( :transaction_consumption, transaction_request_uuid: request.uuid, - status: "pending" + status: "pending", + originator: nil ) consumption_2 = insert( :transaction_consumption, transaction_request_uuid: request.uuid, - status: "confirmed" + status: "confirmed", + originator: nil ) _consumption_3 = insert( :transaction_consumption, transaction_request_uuid: request.uuid, - status: "failed" + status: "failed", + originator: nil ) _consumption_4 = insert( :transaction_consumption, transaction_request_uuid: request.uuid, - status: "expired" + status: "expired", + originator: nil ) consumption_5 = insert( :transaction_consumption, transaction_request_uuid: request.uuid, - status: "confirmed" + status: "confirmed", + originator: nil ) consumptions = TransactionConsumption.all_active_for_request(request.uuid) @@ -165,7 +171,7 @@ defmodule EWalletDB.TransactionConsumptionTest do test "approves the consumption" do consumption = insert(:transaction_consumption) assert consumption.status == "pending" - consumption = TransactionConsumption.approve(consumption) + consumption = TransactionConsumption.approve(consumption, %System{}) assert consumption.status == "approved" end end @@ -174,7 +180,7 @@ defmodule EWalletDB.TransactionConsumptionTest do test "rejects the consumption" do consumption = insert(:transaction_consumption) assert consumption.status == "pending" - consumption = TransactionConsumption.reject(consumption) + consumption = TransactionConsumption.reject(consumption, %System{}) assert consumption.status == "rejected" end end diff --git a/apps/ewallet_db/test/ewallet_db/transaction_request_test.exs b/apps/ewallet_db/test/ewallet_db/transaction_request_test.exs index 53101a116..b83ef5218 100644 --- a/apps/ewallet_db/test/ewallet_db/transaction_request_test.exs +++ b/apps/ewallet_db/test/ewallet_db/transaction_request_test.exs @@ -1,7 +1,7 @@ defmodule EWalletDB.TransactionRequestTest do use EWalletDB.SchemaCase alias EWalletDB.TransactionRequest - alias EWalletConfig.System + alias ActivityLogger.System describe "TransactionRequest factory" do test_has_valid_factory(TransactionRequest) diff --git a/apps/ewallet_db/test/ewallet_db/transaction_test.exs b/apps/ewallet_db/test/ewallet_db/transaction_test.exs index 1ea769369..d079392b1 100644 --- a/apps/ewallet_db/test/ewallet_db/transaction_test.exs +++ b/apps/ewallet_db/test/ewallet_db/transaction_test.exs @@ -2,7 +2,7 @@ defmodule EWalletDB.TransactionTest do use EWalletDB.SchemaCase alias Ecto.UUID alias EWalletDB.Transaction - alias EWalletConfig.System + alias ActivityLogger.System describe "Transaction factory" do test_has_valid_factory(Transaction) diff --git a/apps/ewallet_db/test/ewallet_db/user_test.exs b/apps/ewallet_db/test/ewallet_db/user_test.exs index 630f9f3ff..8cd6e9b07 100644 --- a/apps/ewallet_db/test/ewallet_db/user_test.exs +++ b/apps/ewallet_db/test/ewallet_db/user_test.exs @@ -1,8 +1,8 @@ defmodule EWalletDB.UserTest do use EWalletDB.SchemaCase - alias EWalletConfig.Helpers.Crypto - alias EWalletDB.{Account, Audit, Invite, User} - alias EWalletConfig.System + alias Utils.Helpers.Crypto + alias EWalletDB.{Account, Invite, User} + alias ActivityLogger.{System, ActivityLog} describe "User factory" do test_has_valid_factory(User) @@ -22,12 +22,12 @@ defmodule EWalletDB.UserTest do assert user.metadata["first_name"] == inserted_user.metadata["first_name"] assert user.metadata["last_name"] == inserted_user.metadata["last_name"] - audits = Audit.all_for_target(User, user.uuid) + audits = ActivityLog.all_for_target(User, user.uuid) assert length(audits) == 1 audit = Enum.at(audits, 0) assert audit.originator_uuid != nil - assert audit.originator_type == "user" + assert audit.originator_type == "system" end test_insert_generate_uuid(User, :uuid) @@ -448,7 +448,7 @@ defmodule EWalletDB.UserTest do user = insert(:user) refute User.admin?(user) - {:ok, user} = User.set_admin(user, true) + {:ok, user} = User.set_admin(user, true, %System{}) assert User.admin?(user) end @@ -456,7 +456,7 @@ defmodule EWalletDB.UserTest do user = insert(:admin) assert User.admin?(user) - {:ok, user} = User.set_admin(user, false) + {:ok, user} = User.set_admin(user, false, %System{}) refute User.admin?(user) end end diff --git a/apps/ewallet_db/test/ewallet_db/wallet_test.exs b/apps/ewallet_db/test/ewallet_db/wallet_test.exs index 0520a7df2..829a76250 100644 --- a/apps/ewallet_db/test/ewallet_db/wallet_test.exs +++ b/apps/ewallet_db/test/ewallet_db/wallet_test.exs @@ -1,7 +1,7 @@ defmodule EWalletDB.WalletTest do use EWalletDB.SchemaCase alias Ecto.UUID - alias EWalletConfig.Types.WalletAddress + alias Utils.Types.WalletAddress alias EWalletDB.{Account, User, Wallet} describe "Wallet factory" do diff --git a/apps/ewallet_db/test/support/factory.ex b/apps/ewallet_db/test/support/factory.ex index 0b88ef82c..29de9d1c3 100644 --- a/apps/ewallet_db/test/support/factory.ex +++ b/apps/ewallet_db/test/support/factory.ex @@ -4,14 +4,13 @@ defmodule EWalletDB.Factory do """ use ExMachina.Ecto, repo: EWalletDB.Repo alias ExMachina.Strategy - alias EWalletConfig.{Types.WalletAddress, Helpers.Crypto} - alias EWalletConfig.System + alias Utils.{Types.WalletAddress, Helpers.Crypto} + alias ActivityLogger.System alias EWalletDB.{ Account, AccountUser, APIKey, - Audit, AuthToken, Category, ExchangePair, @@ -153,36 +152,6 @@ defmodule EWalletDB.Factory do } end - def audit_factory do - params = params_for(:user) - user = insert(:user, params) - originator = insert(:admin) - - %Audit{ - action: "insert", - target_type: Audit.get_type(User), - target_uuid: user.uuid, - target_changes: params, - originator_uuid: originator.uuid, - originator_type: Audit.get_type(Admin) - } - end - - def sytem_audit_factory do - params = params_for(:admin) - admin = insert(params) - originator = %System{} - - %Audit{ - action: "insert", - target_type: Audit.get_type(admin.__struct__), - target_uuid: admin.uuid, - target_changes: params, - originator_uuid: originator.uuid, - originator_type: Audit.get_type(originator.__struct__) - } - end - def invite_factory do %Invite{ user: nil, diff --git a/apps/ewallet_db/test/support/schema_case.ex b/apps/ewallet_db/test/support/schema_case.ex index a15f48efa..53ce7192d 100644 --- a/apps/ewallet_db/test/support/schema_case.ex +++ b/apps/ewallet_db/test/support/schema_case.ex @@ -38,7 +38,7 @@ defmodule EWalletDB.SchemaCase do import EWalletDB.Factory alias Ecto.Adapters.SQL alias EWalletDB.{Account, User} - alias EWalletConfig.System + alias ActivityLogger.System defmacro __using__(_opts) do quote do @@ -51,6 +51,7 @@ defmodule EWalletDB.SchemaCase do setup do :ok = Sandbox.checkout(EWalletDB.Repo) :ok = Sandbox.checkout(EWalletConfig.Repo) + :ok = Sandbox.checkout(ActivityLogger.Repo) %{} = get_or_insert_master_account() :ok @@ -504,7 +505,11 @@ defmodule EWalletDB.SchemaCase do |> params_for(%{field => old}) |> schema.insert() - {res, updated} = schema.update(original, %{field => new}) + {res, updated} = + schema.update(original, %{ + field => new, + originator: %System{} + }) assert res == :ok assert Map.fetch!(updated, field) == old @@ -584,7 +589,7 @@ defmodule EWalletDB.SchemaCase do |> params_for(%{}) |> schema.insert() - {:ok, record} = schema.delete(record) + {:ok, record} = schema.delete(record, %System{}) assert record.deleted_at != nil assert schema.deleted?(record) @@ -606,7 +611,7 @@ defmodule EWalletDB.SchemaCase do # Makes sure the record is not already deleted before testing refute schema.deleted?(record) - {res, record} = schema.delete(record) + {res, record} = schema.delete(record, %System{}) assert res == :ok assert schema.deleted?(record) end @@ -625,11 +630,11 @@ defmodule EWalletDB.SchemaCase do |> schema.insert() # Makes sure the record is already soft-deleted before testing - {:ok, record} = schema.delete(record) + {:ok, record} = schema.delete(record, %System{}) assert schema.deleted?(record) - {res, record} = schema.restore(record) - IO.inspect(record) + {res, record} = schema.restore(record, %System{}) + assert res == :ok refute schema.deleted?(record) end diff --git a/apps/ewallet_db/test/support/validator_case.ex b/apps/ewallet_db/test/support/validator_case.ex index 23e68c10f..2721747f1 100644 --- a/apps/ewallet_db/test/support/validator_case.ex +++ b/apps/ewallet_db/test/support/validator_case.ex @@ -13,6 +13,7 @@ defmodule EWalletDB.ValidatorCase do setup do :ok = Sandbox.checkout(EWalletDB.Repo) :ok = Sandbox.checkout(EWalletConfig.Repo) + :ok = Sandbox.checkout(ActivityLogger.Repo) :ok end diff --git a/apps/ewallet_db/test/test_helper.exs b/apps/ewallet_db/test/test_helper.exs index 023a4f866..998e781a2 100644 --- a/apps/ewallet_db/test/test_helper.exs +++ b/apps/ewallet_db/test/test_helper.exs @@ -2,3 +2,4 @@ ExUnit.start() Ecto.Adapters.SQL.Sandbox.mode(EWalletDB.Repo, :manual) Ecto.Adapters.SQL.Sandbox.mode(EWalletConfig.Repo, :manual) +Ecto.Adapters.SQL.Sandbox.mode(ActivityLogger.Repo, :manual) From 884d65902f8773ae3521f6058f2f3206abd7d2c8 Mon Sep 17 00:00:00 2001 From: Thibault Denizet Date: Mon, 3 Dec 2018 12:55:11 +0700 Subject: [PATCH 06/23] Add missing dependency to local ledger sub app --- apps/local_ledger/mix.exs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/local_ledger/mix.exs b/apps/local_ledger/mix.exs index 42fc150c2..c069f01ab 100644 --- a/apps/local_ledger/mix.exs +++ b/apps/local_ledger/mix.exs @@ -33,10 +33,11 @@ defmodule LocalLedger.Mixfile do # Run "mix help deps" to learn about dependencies. defp deps do [ - {:local_ledger_db, in_umbrella: true}, {:deferred_config, "~> 0.1.0"}, {:quantum, ">= 2.2.6"}, - {:timex, "~> 3.0"} + {:timex, "~> 3.0"}, + {:local_ledger_db, in_umbrella: true}, + {:ewallet_config, in_umbrella: true} ] end end From fb46496661d9365b0ad28a533bb310357b572dca Mon Sep 17 00:00:00 2001 From: Thibault Denizet Date: Mon, 3 Dec 2018 12:55:38 +0700 Subject: [PATCH 07/23] Integrate ActivityLogger in EWallet sub app --- .../lib/ewallet/fetchers/amount_fetcher.ex | 2 +- .../lib/ewallet/gates/exchange_pair_gate.ex | 29 +- .../ewallet/lib/ewallet/gates/genesis_gate.ex | 8 +- apps/ewallet/lib/ewallet/gates/mint_gate.ex | 19 +- .../transaction_consumption_confirmer_gate.ex | 41 +-- .../transaction_consumption_consumer_gate.ex | 9 +- .../lib/ewallet/gates/transaction_gate.ex | 18 +- .../ewallet/gates/transaction_request_gate.ex | 11 +- .../lib/ewallet/policies/policy_helper.ex | 2 +- .../transaction_consumption_validator.ex | 12 +- apps/ewallet/lib/ewallet/web/inviter.ex | 8 +- apps/ewallet/lib/ewallet/web/originator.ex | 20 +- .../transaction_consumption_event_handler.ex | 2 +- .../web/v1/serializers/account_serializer.ex | 2 +- .../serializers/exchange_pair_serializer.ex | 2 +- .../web/v1/serializers/mint_serializer.ex | 2 +- .../transaction_consumption_serializer.ex | 2 +- .../transaction_request_serializer.ex | 6 +- .../v1/serializers/transaction_serializer.ex | 2 +- .../serializers/user_auth_token_serializer.ex | 2 +- .../web/v1/serializers/wallet_serializer.ex | 2 +- .../test/ewallet/emails/invite_email_test.exs | 3 +- .../emails/verification_email_test.exs | 3 +- .../transaction_consumption_fetcher_test.exs | 4 +- .../transaction_request_fetcher_test.exs | 4 +- .../ewallet/gates/exchange_pair_gate_test.exs | 30 +- .../test/ewallet/gates/mint_gate_test.exs | 10 +- .../test/ewallet/gates/signup_gate_test.exs | 9 +- ...action_consumption_confirmer_gate_test.exs | 166 ++++++++--- ...saction_consumption_consumer_gate_test.exs | 270 ++++++++++++------ .../ewallet/gates/transaction_gate_test.exs | 37 ++- .../gates/transaction_request_gate_test.exs | 128 ++++++--- ...transaction_consumption_validator_test.exs | 3 +- .../web/filters/match_all_parser_test.exs | 3 +- .../web/filters/match_any_parser_test.exs | 3 +- .../ewallet/test/ewallet/web/inviter_test.exs | 3 +- .../serializers/account_serializer_test.exs | 3 +- ...ransaction_consumption_serializer_test.exs | 2 +- .../transaction_request_serializer_test.exs | 2 +- .../transaction_serializer_test.exs | 2 +- apps/ewallet/test/support/db_case.ex | 2 + .../ewallet/test/support/local_ledger_case.ex | 12 +- apps/ewallet/test/support/serializer_case.ex | 3 +- apps/ewallet/test/test_helper.exs | 1 + 44 files changed, 622 insertions(+), 282 deletions(-) diff --git a/apps/ewallet/lib/ewallet/fetchers/amount_fetcher.ex b/apps/ewallet/lib/ewallet/fetchers/amount_fetcher.ex index 8b941470b..714385f91 100644 --- a/apps/ewallet/lib/ewallet/fetchers/amount_fetcher.ex +++ b/apps/ewallet/lib/ewallet/fetchers/amount_fetcher.ex @@ -3,7 +3,7 @@ defmodule EWallet.AmountFetcher do Handles retrieval of amount from params for transactions. """ alias EWallet.{Exchange, Helper} - alias EWalletConfig.Helpers.Assoc + alias Utils.Helpers.Assoc # # Handles same-token transaction format diff --git a/apps/ewallet/lib/ewallet/gates/exchange_pair_gate.ex b/apps/ewallet/lib/ewallet/gates/exchange_pair_gate.ex index 09732b7c0..74ccd02f4 100644 --- a/apps/ewallet/lib/ewallet/gates/exchange_pair_gate.ex +++ b/apps/ewallet/lib/ewallet/gates/exchange_pair_gate.ex @@ -79,7 +79,11 @@ defmodule EWallet.ExchangePairGate do end # Only updates the opposite pair if explicitly requested - defp update(:opposite, direct_pair, %{"sync_opposite" => true} = direct_attrs) do + defp update( + :opposite, + direct_pair, + %{"sync_opposite" => true, "originator" => originator} = direct_attrs + ) do case get_opposite_pair(direct_pair) do nil -> {:error, :exchange_opposite_pair_not_found} @@ -90,7 +94,10 @@ defmodule EWallet.ExchangePairGate do {:ok, opposite_pair} rate -> - ExchangePair.update(opposite_pair, %{"rate" => 1 / rate}) + ExchangePair.update(opposite_pair, %{ + "rate" => 1 / rate, + "originator" => originator + }) end end end @@ -100,12 +107,12 @@ defmodule EWallet.ExchangePairGate do @doc """ Deletes an exchange pair. """ - @spec delete(String.t(), map()) :: + @spec delete(String.t(), map(), map()) :: {:ok, [%ExchangePair{}]} | {:error, atom() | Ecto.Changeset.t()} - def delete(id, attrs) do + def delete(id, attrs, originator) do Repo.transaction(fn -> - with {:ok, direct} <- delete(:direct, id, attrs), - {:ok, opposite} <- delete(:opposite, direct, attrs), + with {:ok, direct} <- delete(:direct, id, attrs, originator), + {:ok, opposite} <- delete(:opposite, direct, attrs, originator), pairs <- [direct, opposite], pairs <- Enum.reject(pairs, &is_nil/1) do pairs @@ -116,28 +123,28 @@ defmodule EWallet.ExchangePairGate do end # Deletes the direct pair - defp delete(:direct, id, _attrs) do + defp delete(:direct, id, _attrs, originator) do case ExchangePair.get(id) do nil -> {:error, :exchange_pair_id_not_found} pair -> - ExchangePair.delete(pair) + ExchangePair.delete(pair, originator) end end # Deletes the opposite pair if explicitly requested - defp delete(:opposite, direct_pair, %{"sync_opposite" => true}) do + defp delete(:opposite, direct_pair, %{"sync_opposite" => true}, originator) do case get_opposite_pair(direct_pair) do nil -> {:error, :exchange_opposite_pair_not_found} opposite_pair -> - ExchangePair.delete(opposite_pair) + ExchangePair.delete(opposite_pair, originator) end end - defp delete(:opposite, _, _), do: {:ok, nil} + defp delete(:opposite, _, _, _), do: {:ok, nil} defp get_opposite_pair(pair) do ExchangePair.get_by(from_token_uuid: pair.to_token_uuid, to_token_uuid: pair.from_token_uuid) diff --git a/apps/ewallet/lib/ewallet/gates/genesis_gate.ex b/apps/ewallet/lib/ewallet/gates/genesis_gate.ex index 0001922c6..f5c8da4f0 100644 --- a/apps/ewallet/lib/ewallet/gates/genesis_gate.ex +++ b/apps/ewallet/lib/ewallet/gates/genesis_gate.ex @@ -11,7 +11,8 @@ defmodule EWallet.GenesisGate do account: account, token: token, amount: amount, - attrs: attrs + attrs: attrs, + originator: originator }) do Transaction.get_or_insert(%{ idempotency_token: idempotency_token, @@ -24,7 +25,8 @@ defmodule EWallet.GenesisGate do to_amount: amount, metadata: attrs["metadata"] || %{}, encrypted_metadata: attrs["encrypted_metadata"] || %{}, - payload: attrs + payload: Map.delete(attrs, "originator"), + originator: originator }) end @@ -54,7 +56,7 @@ defmodule EWallet.GenesisGate do do: {:error, code, description, mint} defp confirm_and_return(transaction, mint) do - mint = Mint.confirm(mint) + mint = Mint.confirm(mint, transaction) {:ok, mint, transaction} end end diff --git a/apps/ewallet/lib/ewallet/gates/mint_gate.ex b/apps/ewallet/lib/ewallet/gates/mint_gate.ex index 29f38488c..6bb923250 100644 --- a/apps/ewallet/lib/ewallet/gates/mint_gate.ex +++ b/apps/ewallet/lib/ewallet/gates/mint_gate.ex @@ -35,7 +35,8 @@ defmodule EWallet.MintGate do "idempotency_token" => attrs["idempotency_token"] || UUID.generate(), "token_id" => token.id, "amount" => amount, - "description" => attrs["description"] + "description" => attrs["description"], + "originator" => attrs["originator"] } |> insert() |> case do @@ -81,7 +82,8 @@ defmodule EWallet.MintGate do "idempotency_token" => idempotency_token, "token_id" => token_id, "amount" => amount, - "description" => description + "description" => description, + "originator" => originator } = attrs ) do with {:ok, token} <- TokenFetcher.fetch(%{"token_id" => token_id}), @@ -93,20 +95,25 @@ defmodule EWallet.MintGate do token_uuid: token.uuid, amount: amount, account_uuid: account.uuid, - description: description + description: description, + originator: originator }) end) - |> Multi.run(:transaction, fn _ -> + |> Multi.run(:transaction, fn %{mint: mint} -> GenesisGate.create(%{ idempotency_token: idempotency_token, amount: amount, token: token, account: account, - attrs: attrs + attrs: attrs, + originator: mint }) end) |> Multi.run(:mint_with_transaction, fn %{transaction: transaction, mint: mint} -> - Mint.update(mint, %{transaction_uuid: transaction.uuid}) + Mint.update(mint, %{ + transaction_uuid: transaction.uuid, + originator: transaction + }) end) case Repo.transaction(multi) do diff --git a/apps/ewallet/lib/ewallet/gates/transaction_consumption_confirmer_gate.ex b/apps/ewallet/lib/ewallet/gates/transaction_consumption_confirmer_gate.ex index adb1d179e..afa5cebca 100644 --- a/apps/ewallet/lib/ewallet/gates/transaction_consumption_confirmer_gate.ex +++ b/apps/ewallet/lib/ewallet/gates/transaction_consumption_confirmer_gate.ex @@ -14,24 +14,25 @@ defmodule EWallet.TransactionConsumptionConfirmerGate do } alias Ecto.Changeset - alias EWalletConfig.Helpers.Assoc + alias Utils.Helpers.Assoc alias EWalletDB.{Repo, TransactionConsumption, TransactionRequest} + alias ActivityLogger.System - @spec approve_and_confirm(%TransactionRequest{}, %TransactionConsumption{}) :: + @spec approve_and_confirm(%TransactionRequest{}, %TransactionConsumption{}, map()) :: {:error, %TransactionConsumption{}, atom(), String.t()} | {:error, %TransactionConsumption{}, String.t(), String.t()} - def approve_and_confirm(request, consumption) do + def approve_and_confirm(request, consumption, originator) do consumption - |> TransactionConsumption.approve() + |> TransactionConsumption.approve(originator) |> transfer(request) end - @spec confirm(String.t(), boolean(), map()) :: + @spec confirm(String.t(), boolean(), map(), map()) :: {:ok, %TransactionConsumption{}} | {:error, atom()} | {:error, %TransactionConsumption{}, atom(), String.t()} - def confirm(id, approved, confirmer) do - transaction = Repo.transaction(fn -> do_confirm(id, approved, confirmer) end) + def confirm(id, approved, creator, originator) do + transaction = Repo.transaction(fn -> do_confirm(id, approved, creator, originator) end) case transaction do {:ok, res} -> res @@ -40,20 +41,20 @@ defmodule EWallet.TransactionConsumptionConfirmerGate do end end - defp do_confirm(id, approved, confirmer) do + defp do_confirm(id, approved, creator, originator) do with {v, f} <- {TransactionConsumptionValidator, TransactionConsumptionFetcher}, {:ok, consumption} <- f.get(id), request <- consumption.transaction_request, {:ok, request} <- TransactionRequestFetcher.get_with_lock(request.id), - {:ok, consumption} <- v.validate_before_confirmation(consumption, confirmer) do + {:ok, consumption} <- v.validate_before_confirmation(consumption, creator) do case approved do true -> consumption - |> TransactionConsumption.approve() + |> TransactionConsumption.approve(originator) |> transfer(request) false -> - consumption = TransactionConsumption.reject(consumption) + consumption = TransactionConsumption.reject(consumption, originator) {:ok, consumption} end else @@ -166,7 +167,8 @@ defmodule EWallet.TransactionConsumptionConfirmerGate do Assoc.get(consumption, [:exchange_account, :id]), "exchange_wallet_address" => Assoc.get(request, [:exchange_wallet, :address]) || - Assoc.get(consumption, [:exchange_wallet, :address]) + Assoc.get(consumption, [:exchange_wallet, :address]), + "originator" => consumption } case TransactionGate.create(attrs) do @@ -174,9 +176,8 @@ defmodule EWallet.TransactionConsumptionConfirmerGate do # Expires the request if it has reached the max number of consumptions (only CONFIRMED # SUCCESSFUL) consumptions are accounted for. consumption = TransactionConsumption.confirm(consumption, transaction) - request = consumption.transaction_request - {:ok, request} = TransactionRequest.expire_if_max_consumption(request) + {:ok, request} = TransactionRequest.expire_if_max_consumption(request, %System{}) consumption = consumption @@ -187,16 +188,22 @@ defmodule EWallet.TransactionConsumptionConfirmerGate do {:error, %Changeset{} = changeset} -> error = ErrorHandler.build_error(:invalid_parameter, changeset, ErrorHandler.errors()) - consumption = TransactionConsumption.fail(consumption, error.code, error.description) + + consumption = + TransactionConsumption.fail(consumption, error.code, error.description, %System{}) + {:error, consumption, :invalid_parameter, error.description} {:error, code} -> error = ErrorHandler.build_error(code, ErrorHandler.errors()) - consumption = TransactionConsumption.fail(consumption, error.code, error.description) + + consumption = + TransactionConsumption.fail(consumption, error.code, error.description, %System{}) + {:error, consumption, code} {:error, code, description} -> - consumption = TransactionConsumption.fail(consumption, code, description) + consumption = TransactionConsumption.fail(consumption, code, description, %System{}) {:error, consumption, code, description} {:error, transaction, code, description} -> diff --git a/apps/ewallet/lib/ewallet/gates/transaction_consumption_consumer_gate.ex b/apps/ewallet/lib/ewallet/gates/transaction_consumption_consumer_gate.ex index 7dbd56afe..7bd072ed4 100644 --- a/apps/ewallet/lib/ewallet/gates/transaction_consumption_consumer_gate.ex +++ b/apps/ewallet/lib/ewallet/gates/transaction_consumption_consumer_gate.ex @@ -16,7 +16,7 @@ defmodule EWallet.TransactionConsumptionConsumerGate do } alias EWallet.Web.V1.Event - alias EWalletConfig.Helpers.Assoc + alias Utils.Helpers.Assoc alias EWalletDB.{ Account, @@ -27,6 +27,8 @@ defmodule EWallet.TransactionConsumptionConsumerGate do Wallet } + alias ActivityLogger.System + @spec consume(map()) :: {:ok, %TransactionConsumption{}} | {:error, %TransactionConsumption{}} @@ -223,7 +225,7 @@ defmodule EWallet.TransactionConsumptionConsumerGate do {:ok, consumption} false -> - TransactionConsumptionConfirmerGate.approve_and_confirm(request, consumption) + TransactionConsumptionConfirmerGate.approve_and_confirm(request, consumption, %System{}) end else {:idempotent_call, consumption} -> @@ -258,7 +260,8 @@ defmodule EWallet.TransactionConsumptionConsumerGate do estimated_at: Map.get(calculation, :calculated_at), estimated_rate: Map.get(calculation, :actual_rate), metadata: attrs["metadata"] || %{}, - encrypted_metadata: attrs["encrypted_metadata"] || %{} + encrypted_metadata: attrs["encrypted_metadata"] || %{}, + originator: attrs["originator"] }) error -> diff --git a/apps/ewallet/lib/ewallet/gates/transaction_gate.ex b/apps/ewallet/lib/ewallet/gates/transaction_gate.ex index da768c5a7..119995e24 100644 --- a/apps/ewallet/lib/ewallet/gates/transaction_gate.ex +++ b/apps/ewallet/lib/ewallet/gates/transaction_gate.ex @@ -12,6 +12,7 @@ defmodule EWallet.TransactionGate do } alias EWalletDB.{AccountUser, Transaction} + alias ActivityLogger.System alias LocalLedger.Transaction, as: LedgerTransaction def create(attrs) do @@ -21,7 +22,7 @@ defmodule EWallet.TransactionGate do {:ok, from, to, exchange} <- AmountFetcher.fetch(attrs, from, to), {:ok, exchange} <- AccountFetcher.fetch_exchange_account(attrs, exchange), {:ok, transaction} <- get_or_insert(from, to, exchange, attrs), - _ <- link(transaction, attrs["originator"]) do + _ <- link(transaction) do process_with_transaction(transaction) else error when is_atom(error) -> {:error, error} @@ -55,6 +56,7 @@ defmodule EWallet.TransactionGate do } = attrs ) do Transaction.get_or_insert(%{ + originator: attrs["originator"], idempotency_token: idempotency_token, from_account_uuid: from[:from_account_uuid], from_user_uuid: from[:from_user_uuid], @@ -92,22 +94,22 @@ defmodule EWallet.TransactionGate do end def update_transaction({:ok, ledger_transaction}, transaction) do - Transaction.confirm(transaction, ledger_transaction.uuid) + Transaction.confirm(transaction, ledger_transaction.uuid, %System{}) end def update_transaction({:error, code, description}, transaction) do - Transaction.fail(transaction, code, description) + Transaction.fail(transaction, code, description, %System{}) end - defp link(%Transaction{from_account_uuid: account_uuid, to_user_uuid: user_uuid}, originator) + defp link(%Transaction{from_account_uuid: account_uuid, to_user_uuid: user_uuid} = transaction) when not is_nil(account_uuid) and not is_nil(user_uuid) do - AccountUser.link(account_uuid, user_uuid, originator) + AccountUser.link(account_uuid, user_uuid, transaction) end - defp link(%Transaction{from_user_uuid: user_uuid, to_account_uuid: account_uuid}, originator) + defp link(%Transaction{from_user_uuid: user_uuid, to_account_uuid: account_uuid} = transaction) when not is_nil(account_uuid) and not is_nil(user_uuid) do - AccountUser.link(account_uuid, user_uuid, originator) + AccountUser.link(account_uuid, user_uuid, transaction) end - defp link(_, _), do: nil + defp link(_), do: nil end diff --git a/apps/ewallet/lib/ewallet/gates/transaction_request_gate.ex b/apps/ewallet/lib/ewallet/gates/transaction_request_gate.ex index 65cc4bf35..ce3e26a5d 100644 --- a/apps/ewallet/lib/ewallet/gates/transaction_request_gate.ex +++ b/apps/ewallet/lib/ewallet/gates/transaction_request_gate.ex @@ -15,7 +15,7 @@ defmodule EWallet.TransactionRequestGate do WalletFetcher } - alias EWalletConfig.Helpers.Assoc + alias Utils.Helpers.Assoc alias EWalletDB.{Account, TransactionRequest, User, Wallet} @spec create(map()) :: {:ok, %TransactionRequest{}} | {:error, atom()} @@ -175,12 +175,12 @@ defmodule EWallet.TransactionRequestGate do def create(_, _attrs), do: {:error, :invalid_parameter} - @spec expire_if_past_expiration_date(%TransactionRequest{}) :: + @spec expire_if_past_expiration_date(%TransactionRequest{}, map()) :: {:ok, %TransactionRequest{}} | {:error, atom()} | {:error, map()} - def expire_if_past_expiration_date(request) do - res = TransactionRequest.expire_if_past_expiration_date(request) + def expire_if_past_expiration_date(request, originator) do + res = TransactionRequest.expire_if_past_expiration_date(request, originator) case res do {:ok, %TransactionRequest{status: "expired"} = request} -> @@ -215,7 +215,8 @@ defmodule EWallet.TransactionRequestGate do max_consumptions: attrs["max_consumptions"], max_consumptions_per_user: attrs["max_consumptions_per_user"], exchange_account_uuid: Assoc.get_if_exists(exchange_wallet, [:account_uuid]), - exchange_wallet_address: Assoc.get_if_exists(exchange_wallet, [:address]) + exchange_wallet_address: Assoc.get_if_exists(exchange_wallet, [:address]), + originator: attrs["originator"] }) end diff --git a/apps/ewallet/lib/ewallet/policies/policy_helper.ex b/apps/ewallet/lib/ewallet/policies/policy_helper.ex index 8546193a6..0599e2db1 100644 --- a/apps/ewallet/lib/ewallet/policies/policy_helper.ex +++ b/apps/ewallet/lib/ewallet/policies/policy_helper.ex @@ -2,7 +2,7 @@ defmodule EWallet.PolicyHelper do @moduledoc """ A policy helper containing the actual authorization. """ - alias EWalletConfig.Intersecter + alias Utils.Intersecter alias EWalletDB.{Account, Membership, Role} def admin_authorize(user, account_id_or_uuids) do diff --git a/apps/ewallet/lib/ewallet/validators/transaction_consumption_validator.ex b/apps/ewallet/lib/ewallet/validators/transaction_consumption_validator.ex index 78457c016..d1fd002c8 100644 --- a/apps/ewallet/lib/ewallet/validators/transaction_consumption_validator.ex +++ b/apps/ewallet/lib/ewallet/validators/transaction_consumption_validator.ex @@ -6,6 +6,7 @@ defmodule EWallet.TransactionConsumptionValidator do alias EWallet.{Helper, TokenFetcher, TransactionConsumptionPolicy} alias EWallet.Web.V1.Event alias EWalletDB.{ExchangePair, Repo, Token, TransactionConsumption, TransactionRequest, Wallet} + alias ActivityLogger.System @spec validate_before_consumption( %TransactionRequest{}, @@ -20,7 +21,7 @@ defmodule EWallet.TransactionConsumptionValidator do token_id <- attrs["token_id"], true <- wallet.enabled || {:error, :wallet_is_disabled}, :ok <- validate_only_one_exchange_address_in_pair(request, wallet_exchange), - {:ok, request} <- TransactionRequest.expire_if_past_expiration_date(request), + {:ok, request} <- TransactionRequest.expire_if_past_expiration_date(request, %System{}), true <- TransactionRequest.valid?(request) || request.expiration_reason, {:ok, amount} <- validate_amount(request, amount), {:ok, _wallet} <- validate_max_consumptions_per_user(request, wallet), @@ -45,14 +46,15 @@ defmodule EWallet.TransactionConsumptionValidator do {:ok, %TransactionConsumption{}} | {:error, atom()} | no_return() - def validate_before_confirmation(consumption, confirmer) do + def validate_before_confirmation(consumption, originator) do with {request, wallet} <- {consumption.transaction_request, consumption.wallet}, request <- Repo.preload(request, [:wallet]), - :ok <- Bodyguard.permit(TransactionConsumptionPolicy, :confirm, confirmer, request), - {:ok, request} <- TransactionRequest.expire_if_past_expiration_date(request), + :ok <- Bodyguard.permit(TransactionConsumptionPolicy, :confirm, originator, request), + {:ok, request} <- TransactionRequest.expire_if_past_expiration_date(request, %System{}), {:ok, _wallet} <- validate_max_consumptions_per_user(request, wallet), true <- TransactionRequest.valid?(request) || request.expiration_reason, - {:ok, consumption} = TransactionConsumption.expire_if_past_expiration_date(consumption) do + {:ok, consumption} = + TransactionConsumption.expire_if_past_expiration_date(consumption, %System{}) do case TransactionConsumption.expired?(consumption) do false -> {:ok, consumption} diff --git a/apps/ewallet/lib/ewallet/web/inviter.ex b/apps/ewallet/lib/ewallet/web/inviter.ex index 31858eb2a..d42002469 100644 --- a/apps/ewallet/lib/ewallet/web/inviter.ex +++ b/apps/ewallet/lib/ewallet/web/inviter.ex @@ -3,7 +3,7 @@ defmodule EWallet.Web.Inviter do This module handles user invite and confirmation of their emails. """ alias EWallet.Mailer - alias EWalletConfig.Helpers.Crypto + alias Utils.Helpers.Crypto alias EWalletDB.{Account, AccountUser, Invite, Membership, Role, User} @doc """ @@ -13,7 +13,7 @@ defmodule EWallet.Web.Inviter do {:ok, %Invite{}} | {:error, atom()} | {:error, atom(), String.t()} def invite_user(email, password, verification_url, success_url, create_email_func) do with {:ok, user} <- get_or_insert_user(email, password, :self), - {:ok, invite} <- Invite.generate(user, preload: :user, success_url: success_url), + {:ok, invite} <- Invite.generate(user, user, preload: :user, success_url: success_url), {:ok, account} <- Account.fetch_master_account(), {:ok, _account_user} <- AccountUser.link(account.uuid, user.uuid, user) do send_email(invite, verification_url, create_email_func) @@ -34,8 +34,8 @@ defmodule EWallet.Web.Inviter do {:ok, %Invite{}} | {:error, atom()} def invite_admin(email, account, role, redirect_url, originator, create_email_func) do with {:ok, user} <- get_or_insert_user(email, nil, originator), - {:ok, invite} <- Invite.generate(user, preload: :user), - {:ok, _membership} <- Membership.assign(invite.user, account, role) do + {:ok, invite} <- Invite.generate(user, originator, preload: :user), + {:ok, _membership} <- Membership.assign(invite.user, account, role, originator) do send_email(invite, redirect_url, create_email_func) else {:error, error} -> diff --git a/apps/ewallet/lib/ewallet/web/originator.ex b/apps/ewallet/lib/ewallet/web/originator.ex index f37c0750c..4e85713eb 100644 --- a/apps/ewallet/lib/ewallet/web/originator.ex +++ b/apps/ewallet/lib/ewallet/web/originator.ex @@ -3,14 +3,30 @@ defmodule EWallet.Web.Originator do Module to extract the originator from the conn.assigns. """ alias EWalletDB.{Key, User} + alias ActivityLogger.ActivityLog - @spec extract(Map.t()) :: [%Key{}] + @spec extract(map()) :: %Key{} def extract(%{key: key}) do key end - @spec extract(Map.t()) :: [%User{}] + @spec extract(map()) :: %User{} def extract(%{admin_user: admin_user}) do admin_user end + + @spec extract(map()) :: %User{} + def extract(%{user: user}) do + user + end + + @spec set_in_attrs(map(), map()) :: map() + def set_in_attrs(attrs, originator, key \\ "originator") do + Map.put(attrs, key, extract(originator)) + end + + @spec get_initial_originator(map()) :: map() + def get_initial_originator(record) do + ActivityLog.get_initial_originator(record) + end end diff --git a/apps/ewallet/lib/ewallet/web/v1/event_handlers/transaction_consumption_event_handler.ex b/apps/ewallet/lib/ewallet/web/v1/event_handlers/transaction_consumption_event_handler.ex index faf6fb89f..adc5fdde3 100644 --- a/apps/ewallet/lib/ewallet/web/v1/event_handlers/transaction_consumption_event_handler.ex +++ b/apps/ewallet/lib/ewallet/web/v1/event_handlers/transaction_consumption_event_handler.ex @@ -4,7 +4,7 @@ defmodule EWallet.Web.V1.TransactionConsumptionEventHandler do """ alias EWallet.Web.Orchestrator alias EWallet.Web.V1.{Event, TransactionConsumptionSerializer, TransactionConsumptionOverlay} - alias EWalletConfig.Helpers.Assoc + alias Utils.Helpers.Assoc alias EWalletDB.{Helpers.Preloader, TransactionConsumption} @spec broadcast(atom(), %{:consumption => %TransactionConsumption{}}) :: diff --git a/apps/ewallet/lib/ewallet/web/v1/serializers/account_serializer.ex b/apps/ewallet/lib/ewallet/web/v1/serializers/account_serializer.ex index 4fb131f4c..d7d114305 100644 --- a/apps/ewallet/lib/ewallet/web/v1/serializers/account_serializer.ex +++ b/apps/ewallet/lib/ewallet/web/v1/serializers/account_serializer.ex @@ -6,7 +6,7 @@ defmodule EWallet.Web.V1.AccountSerializer do alias EWallet.Web.{Date, Paginator} alias EWallet.Web.V1.{CategorySerializer, PaginatorSerializer} alias EWalletDB.Account - alias EWalletConfig.Helpers.Assoc + alias Utils.Helpers.Assoc alias EWalletDB.Uploaders.Avatar def serialize(%Paginator{} = paginator) do diff --git a/apps/ewallet/lib/ewallet/web/v1/serializers/exchange_pair_serializer.ex b/apps/ewallet/lib/ewallet/web/v1/serializers/exchange_pair_serializer.ex index 1b156d5f2..3056db8d4 100644 --- a/apps/ewallet/lib/ewallet/web/v1/serializers/exchange_pair_serializer.ex +++ b/apps/ewallet/lib/ewallet/web/v1/serializers/exchange_pair_serializer.ex @@ -6,7 +6,7 @@ defmodule EWallet.Web.V1.ExchangePairSerializer do alias EWallet.Web.{Date, Paginator} alias EWallet.Web.V1.{PaginatorSerializer, TokenSerializer} alias EWalletDB.ExchangePair - alias EWalletConfig.Helpers.Assoc + alias Utils.Helpers.Assoc def serialize(%Paginator{} = paginator) do PaginatorSerializer.serialize(paginator, &serialize/1) diff --git a/apps/ewallet/lib/ewallet/web/v1/serializers/mint_serializer.ex b/apps/ewallet/lib/ewallet/web/v1/serializers/mint_serializer.ex index 37f5db0eb..6261f1bed 100644 --- a/apps/ewallet/lib/ewallet/web/v1/serializers/mint_serializer.ex +++ b/apps/ewallet/lib/ewallet/web/v1/serializers/mint_serializer.ex @@ -12,7 +12,7 @@ defmodule EWallet.Web.V1.MintSerializer do TransactionSerializer } - alias EWalletConfig.Helpers.Assoc + alias Utils.Helpers.Assoc alias EWalletDB.Mint def serialize(%Paginator{} = paginator) do diff --git a/apps/ewallet/lib/ewallet/web/v1/serializers/transaction_consumption_serializer.ex b/apps/ewallet/lib/ewallet/web/v1/serializers/transaction_consumption_serializer.ex index 799fba9e6..01499114e 100644 --- a/apps/ewallet/lib/ewallet/web/v1/serializers/transaction_consumption_serializer.ex +++ b/apps/ewallet/lib/ewallet/web/v1/serializers/transaction_consumption_serializer.ex @@ -15,7 +15,7 @@ defmodule EWallet.Web.V1.TransactionConsumptionSerializer do WalletSerializer } - alias EWalletConfig.Helpers.Assoc + alias Utils.Helpers.Assoc alias EWalletDB.TransactionConsumption def serialize(%Paginator{} = paginator) do diff --git a/apps/ewallet/lib/ewallet/web/v1/serializers/transaction_request_serializer.ex b/apps/ewallet/lib/ewallet/web/v1/serializers/transaction_request_serializer.ex index f706b2441..edd1d2c39 100644 --- a/apps/ewallet/lib/ewallet/web/v1/serializers/transaction_request_serializer.ex +++ b/apps/ewallet/lib/ewallet/web/v1/serializers/transaction_request_serializer.ex @@ -13,15 +13,17 @@ defmodule EWallet.Web.V1.TransactionRequestSerializer do } alias EWallet.Web.{Date, Paginator} - alias EWalletConfig.Helpers.Assoc + alias Utils.Helpers.Assoc alias EWalletDB.TransactionRequest + alias ActivityLogger.System def serialize(%Paginator{} = paginator) do PaginatorSerializer.serialize(paginator, &serialize/1) end def serialize(%TransactionRequest{} = transaction_request) do - transaction_request = TransactionRequest.load_consumptions_count(transaction_request) + transaction_request = + TransactionRequest.load_consumptions_count(transaction_request, %System{}) %{ object: "transaction_request", diff --git a/apps/ewallet/lib/ewallet/web/v1/serializers/transaction_serializer.ex b/apps/ewallet/lib/ewallet/web/v1/serializers/transaction_serializer.ex index 3258e3bb1..b9ab78cce 100644 --- a/apps/ewallet/lib/ewallet/web/v1/serializers/transaction_serializer.ex +++ b/apps/ewallet/lib/ewallet/web/v1/serializers/transaction_serializer.ex @@ -15,7 +15,7 @@ defmodule EWallet.Web.V1.TransactionSerializer do } alias EWallet.Web.{Date, Paginator} - alias EWalletConfig.Helpers.Assoc + alias Utils.Helpers.Assoc alias EWalletDB.Transaction def serialize(%Paginator{} = paginator) do diff --git a/apps/ewallet/lib/ewallet/web/v1/serializers/user_auth_token_serializer.ex b/apps/ewallet/lib/ewallet/web/v1/serializers/user_auth_token_serializer.ex index f948372ca..836f151c7 100644 --- a/apps/ewallet/lib/ewallet/web/v1/serializers/user_auth_token_serializer.ex +++ b/apps/ewallet/lib/ewallet/web/v1/serializers/user_auth_token_serializer.ex @@ -3,7 +3,7 @@ defmodule EWallet.Web.V1.UserAuthTokenSerializer do Serializes auth token data into V1 JSON response format. """ alias EWallet.Web.V1.UserSerializer - alias EWalletConfig.Helpers.Assoc + alias Utils.Helpers.Assoc def serialize(auth_token) do %{ diff --git a/apps/ewallet/lib/ewallet/web/v1/serializers/wallet_serializer.ex b/apps/ewallet/lib/ewallet/web/v1/serializers/wallet_serializer.ex index 6881f12ea..89fe28655 100644 --- a/apps/ewallet/lib/ewallet/web/v1/serializers/wallet_serializer.ex +++ b/apps/ewallet/lib/ewallet/web/v1/serializers/wallet_serializer.ex @@ -15,7 +15,7 @@ defmodule EWallet.Web.V1.WalletSerializer do alias EWallet.BalanceFetcher alias EWalletDB.{Wallet, Helpers.Preloader} - alias EWalletConfig.Helpers.Assoc + alias Utils.Helpers.Assoc def serialize(%Paginator{} = paginator) do PaginatorSerializer.serialize(paginator, &serialize/1) diff --git a/apps/ewallet/test/ewallet/emails/invite_email_test.exs b/apps/ewallet/test/ewallet/emails/invite_email_test.exs index ca7456848..f745b9fe9 100644 --- a/apps/ewallet/test/ewallet/emails/invite_email_test.exs +++ b/apps/ewallet/test/ewallet/emails/invite_email_test.exs @@ -2,10 +2,11 @@ defmodule EWallet.InviteEmailTest do use EWallet.DBCase alias EWallet.InviteEmail alias EWalletDB.{Invite, User} + alias ActivityLogger.System defp create_email(email) do {:ok, admin} = :admin |> params_for(email: email) |> User.insert() - {:ok, invite} = Invite.generate(admin) + {:ok, invite} = Invite.generate(admin, %System{}) {InviteEmail.create(invite, "https://invite_url/?email={email}&token={token}"), invite.token} end diff --git a/apps/ewallet/test/ewallet/emails/verification_email_test.exs b/apps/ewallet/test/ewallet/emails/verification_email_test.exs index 947756c2c..5b6402781 100644 --- a/apps/ewallet/test/ewallet/emails/verification_email_test.exs +++ b/apps/ewallet/test/ewallet/emails/verification_email_test.exs @@ -2,10 +2,11 @@ defmodule EWallet.VerificationEmailTest do use EWallet.DBCase, async: true alias EWallet.VerificationEmail alias EWalletDB.{Invite, User} + alias ActivityLogger.System defp create_email(email) do {:ok, user} = :standalone_user |> params_for(email: email) |> User.insert() - {:ok, invite} = Invite.generate(user) + {:ok, invite} = Invite.generate(user, %System{}) {VerificationEmail.create(invite, "https://invite_url/?email={email}&token={token}"), invite.token} diff --git a/apps/ewallet/test/ewallet/fetchers/transaction_consumption_fetcher_test.exs b/apps/ewallet/test/ewallet/fetchers/transaction_consumption_fetcher_test.exs index 2e968a170..def819c8b 100644 --- a/apps/ewallet/test/ewallet/fetchers/transaction_consumption_fetcher_test.exs +++ b/apps/ewallet/test/ewallet/fetchers/transaction_consumption_fetcher_test.exs @@ -1,6 +1,7 @@ defmodule EWallet.TransactionConsumptionFetcherTest do use EWallet.LocalLedgerCase, async: true alias Ecto.Adapters.SQL.Sandbox + alias ActivityLogger.System alias EWallet.{ TestEndpoint, @@ -62,7 +63,8 @@ defmodule EWallet.TransactionConsumptionFetcherTest do "address" => nil, "metadata" => nil, "idempotency_token" => "123", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) assert res == :ok diff --git a/apps/ewallet/test/ewallet/fetchers/transaction_request_fetcher_test.exs b/apps/ewallet/test/ewallet/fetchers/transaction_request_fetcher_test.exs index 089fd14bb..68e14d21e 100644 --- a/apps/ewallet/test/ewallet/fetchers/transaction_request_fetcher_test.exs +++ b/apps/ewallet/test/ewallet/fetchers/transaction_request_fetcher_test.exs @@ -2,6 +2,7 @@ defmodule EWallet.TransactionRequestFetcherTest do use EWallet.LocalLedgerCase, async: true alias EWallet.{TransactionRequestFetcher, TransactionRequestGate} alias EWalletDB.{TransactionRequest, User} + alias ActivityLogger.System setup do {:ok, user} = :user |> params_for() |> User.insert() @@ -27,7 +28,8 @@ defmodule EWallet.TransactionRequestFetcherTest do "token_id" => meta.token.id, "correlation_id" => "123", "amount" => 1_000, - "address" => meta.user_wallet.address + "address" => meta.user_wallet.address, + "originator" => %System{} }) assert {:ok, request} = TransactionRequestFetcher.get(request.id) diff --git a/apps/ewallet/test/ewallet/gates/exchange_pair_gate_test.exs b/apps/ewallet/test/ewallet/gates/exchange_pair_gate_test.exs index 0968447f4..d68496bd2 100644 --- a/apps/ewallet/test/ewallet/gates/exchange_pair_gate_test.exs +++ b/apps/ewallet/test/ewallet/gates/exchange_pair_gate_test.exs @@ -2,6 +2,7 @@ defmodule EWallet.ExchangePairGateTest do use EWallet.LocalLedgerCase, async: true alias EWallet.ExchangePairGate alias EWalletDB.{ExchangePair, Repo} + alias ActivityLogger.System describe "insert/2" do test "inserts an exchange pair" do @@ -12,7 +13,8 @@ defmodule EWallet.ExchangePairGateTest do ExchangePairGate.insert(%{ "rate" => 2.0, "from_token_id" => eth.id, - "to_token_id" => omg.id + "to_token_id" => omg.id, + "originator" => %System{} }) assert res == :ok @@ -33,7 +35,8 @@ defmodule EWallet.ExchangePairGateTest do "rate" => 2.0, "from_token_id" => eth.id, "to_token_id" => omg.id, - "sync_opposite" => true + "sync_opposite" => true, + "originator" => %System{} }) assert res == :ok @@ -64,7 +67,8 @@ defmodule EWallet.ExchangePairGateTest do "rate" => 927_361, "from_token_id" => eth.id, "to_token_id" => omg.id, - "sync_opposite" => true + "sync_opposite" => true, + "originator" => %System{} }) assert res == :error @@ -79,7 +83,8 @@ defmodule EWallet.ExchangePairGateTest do {res, pairs} = ExchangePairGate.update(pair.id, %{ - "rate" => 999 + "rate" => 999, + "originator" => %System{} }) assert res == :ok @@ -95,7 +100,8 @@ defmodule EWallet.ExchangePairGateTest do {res, pairs} = ExchangePairGate.update(pair.id, %{ "rate" => 777, - "sync_opposite" => true + "sync_opposite" => true, + "originator" => %System{} }) assert res == :ok @@ -111,7 +117,8 @@ defmodule EWallet.ExchangePairGateTest do {res, code} = ExchangePairGate.update(pair.id, %{ "rate" => 999, - "sync_opposite" => true + "sync_opposite" => true, + "originator" => %System{} }) assert res == :error @@ -125,7 +132,8 @@ defmodule EWallet.ExchangePairGateTest do test "returns :exchange_pair_id_not_found error if the exchange pair is not found" do {res, code} = ExchangePairGate.update("wrong_id", %{ - "rate" => 999 + "rate" => 999, + "originator" => %System{} }) assert res == :error @@ -137,7 +145,7 @@ defmodule EWallet.ExchangePairGateTest do test "deletes the exchange pair" do pair = insert(:exchange_pair) - {res, deleted} = ExchangePairGate.delete(pair.id, %{"sync_opposite" => false}) + {res, deleted} = ExchangePairGate.delete(pair.id, %{"sync_opposite" => false}, %System{}) assert res == :ok assert Enum.count(deleted) == 1 @@ -149,7 +157,7 @@ defmodule EWallet.ExchangePairGateTest do pair = insert(:exchange_pair) opposite = insert(:exchange_pair, from_token: pair.to_token, to_token: pair.from_token) - {res, deleted} = ExchangePairGate.delete(pair.id, %{"sync_opposite" => true}) + {res, deleted} = ExchangePairGate.delete(pair.id, %{"sync_opposite" => true}, %System{}) assert res == :ok assert Enum.count(deleted) == 2 @@ -161,7 +169,7 @@ defmodule EWallet.ExchangePairGateTest do # Create a pair without the opposite so a deletion with `sync_opposite: true` should fail pair = insert(:exchange_pair, rate: 2.0) - {res, code} = ExchangePairGate.delete(pair.id, %{"sync_opposite" => true}) + {res, code} = ExchangePairGate.delete(pair.id, %{"sync_opposite" => true}, %System{}) assert res == :error assert code == :exchange_opposite_pair_not_found @@ -173,7 +181,7 @@ defmodule EWallet.ExchangePairGateTest do end test "returns :exchange_pair_id_not_found error if the exchange pair is not found" do - {res, code} = ExchangePairGate.delete("wrong_id", %{}) + {res, code} = ExchangePairGate.delete("wrong_id", %{}, %System{}) assert res == :error assert code == :exchange_pair_id_not_found diff --git a/apps/ewallet/test/ewallet/gates/mint_gate_test.exs b/apps/ewallet/test/ewallet/gates/mint_gate_test.exs index 5752d0165..08e644bd5 100644 --- a/apps/ewallet/test/ewallet/gates/mint_gate_test.exs +++ b/apps/ewallet/test/ewallet/gates/mint_gate_test.exs @@ -4,6 +4,7 @@ defmodule EWallet.MintGateTest do alias Ecto.UUID alias EWallet.MintGate alias EWalletDB.Token + alias ActivityLogger.System describe "insert/2" do test "inserts a new confirmed mint" do @@ -15,7 +16,8 @@ defmodule EWallet.MintGateTest do "token_id" => btc.id, "amount" => 10_000 * btc.subunit_to_unit, "description" => "Minting 10_000 #{btc.symbol}", - "metadata" => %{} + "metadata" => %{}, + "originator" => %System{} }) assert res == :ok @@ -33,7 +35,8 @@ defmodule EWallet.MintGateTest do "token_id" => btc.id, "amount" => :math.pow(10, 35), "description" => "Minting 10_000 #{btc.symbol}", - "metadata" => %{} + "metadata" => %{}, + "originator" => %System{} }) assert res == :ok @@ -51,7 +54,8 @@ defmodule EWallet.MintGateTest do "token_id" => token.id, "amount" => nil, "description" => "description", - "metadata" => %{} + "metadata" => %{}, + "originator" => %System{} }) assert res == :error diff --git a/apps/ewallet/test/ewallet/gates/signup_gate_test.exs b/apps/ewallet/test/ewallet/gates/signup_gate_test.exs index 68ceeed1b..fdc3adb13 100644 --- a/apps/ewallet/test/ewallet/gates/signup_gate_test.exs +++ b/apps/ewallet/test/ewallet/gates/signup_gate_test.exs @@ -3,6 +3,7 @@ defmodule EWallet.SignupGateTest do alias EWallet.SignupGate alias EWallet.VerificationEmail alias EWalletDB.{Invite, User} + alias ActivityLogger.System @verification_url "http://localhost:4000/verification_url?email={email}&token={token}" @success_url "http://localhost:4000/success_url" @@ -138,7 +139,7 @@ defmodule EWallet.SignupGateTest do describe "verify_email/1" do test "returns the invite when the verification is successful" do {:ok, user} = :standalone_user |> params_for() |> User.insert() - {:ok, invite} = Invite.generate(user) + {:ok, invite} = Invite.generate(user, %System{}) {res, invite} = SignupGate.verify_email(%{ @@ -152,7 +153,7 @@ defmodule EWallet.SignupGateTest do test "returns an error when the email format is invalid" do {:ok, user} = :standalone_user |> params_for() |> User.insert() - {:ok, invite} = Invite.generate(user) + {:ok, invite} = Invite.generate(user, %System{}) {res, code} = SignupGate.verify_email(%{ @@ -166,7 +167,7 @@ defmodule EWallet.SignupGateTest do test "returns :missing_token error when the token is not provided" do {:ok, user} = :standalone_user |> params_for() |> User.insert() - {:ok, _invite} = Invite.generate(user) + {:ok, _invite} = Invite.generate(user, %System{}) {res, code} = SignupGate.verify_email(%{ @@ -179,7 +180,7 @@ defmodule EWallet.SignupGateTest do test "returns :email_token_not_found error when the email and token do not match" do {:ok, user} = :standalone_user |> params_for() |> User.insert() - {:ok, _invite} = Invite.generate(user) + {:ok, _invite} = Invite.generate(user, %System{}) {res, code} = SignupGate.verify_email(%{ diff --git a/apps/ewallet/test/ewallet/gates/transaction_consumption_confirmer_gate_test.exs b/apps/ewallet/test/ewallet/gates/transaction_consumption_confirmer_gate_test.exs index 3da20be2c..45302d75d 100644 --- a/apps/ewallet/test/ewallet/gates/transaction_consumption_confirmer_gate_test.exs +++ b/apps/ewallet/test/ewallet/gates/transaction_consumption_confirmer_gate_test.exs @@ -11,7 +11,7 @@ defmodule EWallet.TransactionConsumptionConfirmerGateTest do } alias EWalletDB.{AccountUser, TransactionConsumption, TransactionRequest, User} - alias EWalletConfig.System + alias ActivityLogger.System setup do {:ok, pid} = TestEndpoint.start_link() @@ -78,7 +78,8 @@ defmodule EWallet.TransactionConsumptionConfirmerGateTest do "idempotency_token" => "123", "token_id" => nil, "user_id" => meta.sender.id, - "address" => meta.sender_wallet.address + "address" => meta.sender_wallet.address, + "originator" => %System{} }) assert res == :ok @@ -87,7 +88,14 @@ defmodule EWallet.TransactionConsumptionConfirmerGateTest do assert consumption.approved_at == nil {:ok, consumption} = - TransactionConsumptionConfirmerGate.confirm(consumption.id, true, %{account: meta.account}) + TransactionConsumptionConfirmerGate.confirm( + consumption.id, + true, + %{ + account: meta.account + }, + %System{} + ) assert consumption.status == "confirmed" assert consumption.approved_at != nil @@ -118,7 +126,8 @@ defmodule EWallet.TransactionConsumptionConfirmerGateTest do "idempotency_token" => "123", "token_id" => nil, "user_id" => meta.sender.id, - "address" => meta.sender_wallet.address + "address" => meta.sender_wallet.address, + "originator" => %System{} }) assert res == :ok @@ -127,7 +136,14 @@ defmodule EWallet.TransactionConsumptionConfirmerGateTest do assert consumption.approved_at == nil {:ok, consumption} = - TransactionConsumptionConfirmerGate.confirm(consumption.id, true, %{account: meta.account}) + TransactionConsumptionConfirmerGate.confirm( + consumption.id, + true, + %{ + account: meta.account + }, + %System{} + ) assert consumption.status == "confirmed" assert consumption.approved_at != nil @@ -157,7 +173,8 @@ defmodule EWallet.TransactionConsumptionConfirmerGateTest do "idempotency_token" => "123", "token_id" => nil, "user_id" => meta.sender.id, - "address" => meta.sender_wallet.address + "address" => meta.sender_wallet.address, + "originator" => %System{} }) assert res == :ok @@ -166,7 +183,14 @@ defmodule EWallet.TransactionConsumptionConfirmerGateTest do assert consumption.approved_at == nil {:error, :unauthorized} = - TransactionConsumptionConfirmerGate.confirm(consumption.id, true, %{account: account}) + TransactionConsumptionConfirmerGate.confirm( + consumption.id, + true, + %{ + account: account + }, + %System{} + ) end test "confirms a user's consumption if created and approved as account", meta do @@ -183,7 +207,8 @@ defmodule EWallet.TransactionConsumptionConfirmerGateTest do "provider_user_id" => meta.receiver.provider_user_id, "address" => meta.receiver_wallet.address, "require_confirmation" => true, - "creator" => %{account: meta.account} + "creator" => %{account: meta.account}, + "originator" => %System{} }) assert res == :ok @@ -197,7 +222,8 @@ defmodule EWallet.TransactionConsumptionConfirmerGateTest do "idempotency_token" => "123", "token_id" => nil, "user_id" => meta.sender.id, - "address" => meta.sender_wallet.address + "address" => meta.sender_wallet.address, + "originator" => %System{} }) assert res == :ok @@ -206,7 +232,14 @@ defmodule EWallet.TransactionConsumptionConfirmerGateTest do assert consumption.approved_at == nil {:ok, consumption} = - TransactionConsumptionConfirmerGate.confirm(consumption.id, true, %{account: meta.account}) + TransactionConsumptionConfirmerGate.confirm( + consumption.id, + true, + %{ + account: meta.account + }, + %System{} + ) assert consumption.status == "confirmed" assert consumption.approved_at != nil @@ -235,7 +268,8 @@ defmodule EWallet.TransactionConsumptionConfirmerGateTest do "idempotency_token" => "123", "token_id" => nil, "user_id" => meta.sender.id, - "address" => meta.sender_wallet.address + "address" => meta.sender_wallet.address, + "originator" => %System{} }) assert res == :ok @@ -243,7 +277,16 @@ defmodule EWallet.TransactionConsumptionConfirmerGateTest do assert consumption.status == "pending" assert consumption.approved_at == nil - res = TransactionConsumptionConfirmerGate.confirm(consumption.id, true, %{account: account}) + res = + TransactionConsumptionConfirmerGate.confirm( + consumption.id, + true, + %{ + account: account + }, + %System{} + ) + assert res == {:error, :unauthorized} end @@ -271,7 +314,8 @@ defmodule EWallet.TransactionConsumptionConfirmerGateTest do "idempotency_token" => "123", "token_id" => nil, "user_id" => meta.sender.id, - "address" => meta.sender_wallet.address + "address" => meta.sender_wallet.address, + "originator" => %System{} }) assert res == :ok @@ -279,11 +323,18 @@ defmodule EWallet.TransactionConsumptionConfirmerGateTest do assert consumption.status == "pending" assert consumption.approved_at == nil - {:ok, transaction_request} = TransactionRequest.expire(transaction_request) + {:ok, transaction_request} = TransactionRequest.expire(transaction_request, %System{}) assert transaction_request.expired_at != nil res = - TransactionConsumptionConfirmerGate.confirm(consumption.id, true, %{account: meta.account}) + TransactionConsumptionConfirmerGate.confirm( + consumption.id, + true, + %{ + account: meta.account + }, + %System{} + ) assert res == {:error, :expired_transaction_request} end @@ -312,7 +363,8 @@ defmodule EWallet.TransactionConsumptionConfirmerGateTest do "idempotency_token" => "123", "token_id" => nil, "user_id" => meta.sender.id, - "address" => meta.sender_wallet.address + "address" => meta.sender_wallet.address, + "originator" => %System{} }) assert res == :ok @@ -321,9 +373,14 @@ defmodule EWallet.TransactionConsumptionConfirmerGateTest do assert consumption.approved_at == nil {:ok, consumption} = - TransactionConsumptionConfirmerGate.confirm(consumption.id, false, %{ - account: meta.account - }) + TransactionConsumptionConfirmerGate.confirm( + consumption.id, + false, + %{ + account: meta.account + }, + %System{} + ) assert consumption.status == "rejected" assert consumption.approved_at == nil @@ -354,7 +411,8 @@ defmodule EWallet.TransactionConsumptionConfirmerGateTest do "idempotency_token" => UUID.generate(), "token_id" => nil, "user_id" => meta.sender.id, - "address" => meta.sender_wallet.address + "address" => meta.sender_wallet.address, + "originator" => %System{} } end @@ -382,7 +440,14 @@ defmodule EWallet.TransactionConsumptionConfirmerGateTest do Sandbox.allow(LocalLedgerDB.Repo, pid, self()) {res, response} = - TransactionConsumptionConfirmerGate.confirm(c.id, true, %{account: meta.account}) + TransactionConsumptionConfirmerGate.confirm( + c.id, + true, + %{ + account: meta.account + }, + %System{} + ) send(pid, {String.to_atom("updated_#{i + 1}"), res, response}) end) @@ -424,7 +489,8 @@ defmodule EWallet.TransactionConsumptionConfirmerGateTest do "idempotency_token" => "123", "token_id" => nil, "user_id" => meta.sender.id, - "address" => meta.sender_wallet.address + "address" => meta.sender_wallet.address, + "originator" => %System{} }) assert res == :ok @@ -433,9 +499,14 @@ defmodule EWallet.TransactionConsumptionConfirmerGateTest do assert consumption.approved_at == nil {:ok, consumption} = - TransactionConsumptionConfirmerGate.confirm(consumption.id, true, %{ - end_user: meta.receiver - }) + TransactionConsumptionConfirmerGate.confirm( + consumption.id, + true, + %{ + end_user: meta.receiver + }, + %System{} + ) assert consumption.status == "confirmed" assert consumption.approved_at != nil @@ -454,7 +525,8 @@ defmodule EWallet.TransactionConsumptionConfirmerGateTest do "provider_user_id" => meta.receiver.provider_user_id, "address" => meta.receiver_wallet.address, "require_confirmation" => true, - "creator" => %{end_user: meta.receiver} + "creator" => %{end_user: meta.receiver}, + "originator" => %System{} }) assert res == :ok @@ -468,7 +540,8 @@ defmodule EWallet.TransactionConsumptionConfirmerGateTest do "idempotency_token" => "123", "token_id" => nil, "user_id" => meta.sender.id, - "address" => meta.sender_wallet.address + "address" => meta.sender_wallet.address, + "originator" => %System{} }) assert res == :ok @@ -477,9 +550,14 @@ defmodule EWallet.TransactionConsumptionConfirmerGateTest do assert consumption.approved_at == nil {:ok, consumption} = - TransactionConsumptionConfirmerGate.confirm(consumption.id, true, %{ - end_user: meta.receiver - }) + TransactionConsumptionConfirmerGate.confirm( + consumption.id, + true, + %{ + end_user: meta.receiver + }, + %System{} + ) assert consumption.status == "confirmed" assert consumption.approved_at != nil @@ -507,7 +585,8 @@ defmodule EWallet.TransactionConsumptionConfirmerGateTest do "idempotency_token" => "123", "token_id" => nil, "user_id" => meta.sender.id, - "address" => meta.sender_wallet.address + "address" => meta.sender_wallet.address, + "originator" => %System{} }) assert res == :ok @@ -516,7 +595,14 @@ defmodule EWallet.TransactionConsumptionConfirmerGateTest do assert consumption.approved_at == nil res = - TransactionConsumptionConfirmerGate.confirm(consumption.id, true, %{end_user: meta.sender}) + TransactionConsumptionConfirmerGate.confirm( + consumption.id, + true, + %{ + end_user: meta.sender + }, + %System{} + ) assert res == {:error, :unauthorized} end @@ -544,7 +630,8 @@ defmodule EWallet.TransactionConsumptionConfirmerGateTest do "idempotency_token" => "123", "token_id" => nil, "user_id" => meta.sender.id, - "address" => meta.sender_wallet.address + "address" => meta.sender_wallet.address, + "originator" => %System{} }) assert res == :ok @@ -552,13 +639,18 @@ defmodule EWallet.TransactionConsumptionConfirmerGateTest do assert consumption.status == "pending" assert consumption.approved_at == nil - {:ok, transaction_request} = TransactionRequest.expire(transaction_request) + {:ok, transaction_request} = TransactionRequest.expire(transaction_request, %System{}) assert transaction_request.expired_at != nil res = - TransactionConsumptionConfirmerGate.confirm(consumption.id, true, %{ - end_user: meta.receiver - }) + TransactionConsumptionConfirmerGate.confirm( + consumption.id, + true, + %{ + end_user: meta.receiver + }, + %System{} + ) assert res == {:error, :expired_transaction_request} end diff --git a/apps/ewallet/test/ewallet/gates/transaction_consumption_consumer_gate_test.exs b/apps/ewallet/test/ewallet/gates/transaction_consumption_consumer_gate_test.exs index 34902179b..af22ebe91 100644 --- a/apps/ewallet/test/ewallet/gates/transaction_consumption_consumer_gate_test.exs +++ b/apps/ewallet/test/ewallet/gates/transaction_consumption_consumer_gate_test.exs @@ -8,6 +8,7 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do } alias EWalletDB.{Token, TransactionConsumption, TransactionRequest, User, Wallet} + alias ActivityLogger.System setup do {:ok, pid} = TestEndpoint.start_link() @@ -59,7 +60,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "metadata" => nil, "idempotency_token" => "123", "token_id" => nil, - "account_id" => nil + "account_id" => nil, + "originator" => %System{} }) assert res == {:error, :account_id_not_found} @@ -74,7 +76,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "metadata" => nil, "idempotency_token" => "123", "token_id" => nil, - "account_id" => "fake" + "account_id" => "fake", + "originator" => %System{} }) assert res == {:error, :account_id_not_found} @@ -90,7 +93,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "idempotency_token" => "123", "token_id" => nil, "account_id" => meta.account.id, - "address" => nil + "address" => nil, + "originator" => %System{} }) assert res == :ok @@ -106,7 +110,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "metadata" => nil, "idempotency_token" => "123", "token_id" => nil, - "account_id" => meta.account.id + "account_id" => meta.account.id, + "originator" => %System{} }) assert res == :ok @@ -123,7 +128,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "idempotency_token" => "123", "token_id" => nil, "account_id" => meta.account.id, - "address" => meta.account_wallet.address + "address" => meta.account_wallet.address, + "originator" => %System{} }) assert res == :ok @@ -144,7 +150,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "token_id" => nil, "account_id" => meta.account.id, "provider_user_id" => meta.sender.provider_user_id, - "address" => meta.sender_wallet.address + "address" => meta.sender_wallet.address, + "originator" => %System{} }) assert res == :ok @@ -163,7 +170,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "token_id" => nil, "account_id" => meta.account.id, "provider_user_id" => meta.sender.provider_user_id, - "address" => meta.account_wallet.address + "address" => meta.account_wallet.address, + "originator" => %System{} }) assert res == {:error, :user_wallet_mismatch} @@ -179,7 +187,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "idempotency_token" => "123", "token_id" => nil, "account_id" => meta.account.id, - "address" => "fake-0000-0000-0000" + "address" => "fake-0000-0000-0000", + "originator" => %System{} }) assert res == {:error, :account_wallet_not_found} @@ -195,7 +204,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "idempotency_token" => "123", "token_id" => nil, "account_id" => meta.account.id, - "address" => meta.sender_wallet.address + "address" => meta.sender_wallet.address, + "originator" => %System{} }) assert res == {:error, :account_wallet_mismatch} @@ -222,7 +232,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => nil, "metadata" => nil, "idempotency_token" => "123", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) assert res == :ok @@ -237,7 +248,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => nil, "metadata" => nil, "idempotency_token" => "1234", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) assert res == :ok @@ -253,7 +265,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "token_id" => meta.token.id, "correlation_id" => "123", "amount" => 1_000, - "provider_user_id" => nil + "provider_user_id" => nil, + "originator" => %System{} }) assert res == {:error, :provider_user_id_not_found} @@ -268,7 +281,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "metadata" => nil, "idempotency_token" => "123", "token_id" => nil, - "provider_user_id" => "fake" + "provider_user_id" => "fake", + "originator" => %System{} }) assert res == {:error, :provider_user_id_not_found} @@ -285,7 +299,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "metadata" => nil, "idempotency_token" => "123", "token_id" => nil, - "provider_user_id" => meta.sender.provider_user_id + "provider_user_id" => meta.sender.provider_user_id, + "originator" => %System{} }) assert res == :ok @@ -295,7 +310,7 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do test "with valid provider_user_id and a valid address", meta do initialize_wallet(meta.sender_wallet, 200_000, meta.token) - {res, request} = + {res, consumption} = TransactionConsumptionConsumerGate.consume(%{ "formatted_transaction_request_id" => meta.request.id, "correlation_id" => nil, @@ -304,11 +319,12 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "idempotency_token" => "123", "token_id" => nil, "provider_user_id" => meta.sender.provider_user_id, - "address" => meta.sender_wallet.address + "address" => meta.sender_wallet.address, + "originator" => %System{} }) assert res == :ok - assert %TransactionConsumption{} = request + assert %TransactionConsumption{} = consumption end test "with valid provider_user_id and an invalid address", meta do @@ -321,7 +337,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "idempotency_token" => "123", "token_id" => nil, "provider_user_id" => meta.sender.provider_user_id, - "address" => "fake-0000-0000-0000" + "address" => "fake-0000-0000-0000", + "originator" => %System{} }) assert res == {:error, :user_wallet_not_found} @@ -337,7 +354,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "idempotency_token" => "123", "token_id" => nil, "provider_user_id" => meta.sender.provider_user_id, - "address" => meta.receiver_wallet.address + "address" => meta.receiver_wallet.address, + "originator" => %System{} }) assert res == {:error, :user_wallet_mismatch} @@ -354,7 +372,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "metadata" => nil, "idempotency_token" => "123", "token_id" => nil, - "address" => nil + "address" => nil, + "originator" => %System{} }) assert res == {:error, :wallet_not_found} @@ -369,7 +388,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "metadata" => nil, "idempotency_token" => "123", "token_id" => nil, - "address" => meta.account_wallet.address + "address" => meta.account_wallet.address, + "originator" => %System{} }) assert res == :ok @@ -385,14 +405,19 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "metadata" => nil, "idempotency_token" => "123", "token_id" => nil, - "address" => "fake-0000-0000-0000" + "address" => "fake-0000-0000-0000", + "originator" => %System{} }) assert res == {:error, :wallet_not_found} end test "receives an error when the token is disabled", meta do - {:ok, token} = Token.enable_or_disable(meta.token, %{enabled: false}) + {:ok, token} = + Token.enable_or_disable(meta.token, %{ + enabled: false, + originator: %System{} + }) {res, code} = TransactionConsumptionConsumerGate.consume(%{ @@ -402,7 +427,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "metadata" => nil, "idempotency_token" => "123", "token_id" => token.id, - "address" => meta.account_wallet.address + "address" => meta.account_wallet.address, + "originator" => %System{} }) assert res == :error @@ -414,10 +440,15 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do Wallet.insert_secondary_or_burn(%{ "account_uuid" => meta.account.uuid, "name" => "MySecondary", - "identifier" => "secondary" + "identifier" => "secondary", + "originator" => %System{} }) - {:ok, wallet} = Wallet.enable_or_disable(wallet, %{enabled: false}) + {:ok, wallet} = + Wallet.enable_or_disable(wallet, %{ + enabled: false, + originator: %System{} + }) {res, code} = TransactionConsumptionConsumerGate.consume(%{ @@ -427,7 +458,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "metadata" => nil, "idempotency_token" => "123", "token_id" => nil, - "address" => wallet.address + "address" => wallet.address, + "originator" => %System{} }) assert res == :error @@ -444,7 +476,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "amount" => nil, "metadata" => nil, "idempotency_token" => "123", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) assert res == {:error, :invalid_parameter} @@ -460,7 +493,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do {res, consumption_1} = TransactionConsumptionConsumerGate.consume(meta.sender, %{ "formatted_transaction_request_id" => meta.request.id, - "idempotency_token" => "123" + "idempotency_token" => "123", + "originator" => %System{} }) assert res == :ok @@ -475,7 +509,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do {res, consumption_2} = TransactionConsumptionConsumerGate.consume(meta.sender, %{ "formatted_transaction_request_id" => meta.request.id, - "idempotency_token" => "123" + "idempotency_token" => "123", + "originator" => %System{} }) assert res == :ok @@ -503,7 +538,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => nil, "metadata" => nil, "idempotency_token" => "123", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) assert res == :ok @@ -520,7 +556,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => nil, "metadata" => nil, "idempotency_token" => "123", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) assert res == :ok @@ -551,7 +588,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => nil, "metadata" => nil, "idempotency_token" => "123", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) assert res == :error @@ -567,7 +605,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => nil, "metadata" => nil, "idempotency_token" => "123", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) assert res == :error @@ -601,7 +640,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => nil, "metadata" => nil, "idempotency_token" => "123", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) assert res == :ok @@ -628,7 +668,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => meta.sender_wallet.address, "metadata" => %{}, "idempotency_token" => "123", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) assert res == :ok @@ -640,7 +681,7 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do test "returns an 'expired_transaction_request' error when the request is expired", meta do initialize_wallet(meta.sender_wallet, 200_000, meta.token) - {:ok, request} = TransactionRequest.expire(meta.request) + {:ok, request} = TransactionRequest.expire(meta.request, %System{}) {res, error} = TransactionConsumptionConsumerGate.consume(meta.sender, %{ @@ -650,7 +691,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => meta.sender_wallet.address, "metadata" => %{}, "idempotency_token" => "123", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) assert res == :error @@ -681,7 +723,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => nil, "metadata" => nil, "idempotency_token" => "123", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) assert res == :ok @@ -695,7 +738,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => nil, "metadata" => nil, "idempotency_token" => "123", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) assert res == :ok @@ -728,7 +772,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => nil, "metadata" => nil, "idempotency_token" => "123", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) assert res == :ok @@ -742,7 +787,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => nil, "metadata" => nil, "idempotency_token" => "1234", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) assert res == :error @@ -751,7 +797,12 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do test "allows only one consume per user with four consumes at the same time", meta do initialize_wallet(meta.sender_wallet, 200_000, meta.token) - {:ok, request} = TransactionRequest.update(meta.request, %{max_consumptions_per_user: 1}) + + {:ok, request} = + TransactionRequest.update(meta.request, %{ + max_consumptions_per_user: 1, + originator: %System{} + }) pid = self() @@ -770,7 +821,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => nil, "metadata" => nil, "idempotency_token" => "1", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) send(pid, {:updated_1, res, response}) @@ -791,7 +843,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => nil, "metadata" => nil, "idempotency_token" => "2", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) send(pid, {:updated_2, res, response}) @@ -812,7 +865,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => nil, "metadata" => nil, "idempotency_token" => "3", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) send(pid, {:updated_3, res, response}) @@ -833,7 +887,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => nil, "metadata" => nil, "idempotency_token" => "4", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) send(pid, {:updated_4, res, response}) @@ -877,7 +932,12 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do same idempotency_token", meta do initialize_wallet(meta.sender_wallet, 200_000, meta.token) - {:ok, request} = TransactionRequest.update(meta.request, %{max_consumptions: 1}) + + {:ok, request} = + TransactionRequest.update(meta.request, %{ + max_consumptions: 1, + originator: %System{} + }) {res, consumption} = TransactionConsumptionConsumerGate.consume(meta.sender, %{ @@ -887,7 +947,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => nil, "metadata" => nil, "idempotency_token" => "123", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) assert res == :ok @@ -901,7 +962,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => nil, "metadata" => nil, "idempotency_token" => "123", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) assert res == :ok @@ -913,7 +975,12 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do consumptions has been reached", meta do initialize_wallet(meta.sender_wallet, 200_000, meta.token) - {:ok, request} = TransactionRequest.update(meta.request, %{max_consumptions: 1}) + + {:ok, request} = + TransactionRequest.update(meta.request, %{ + max_consumptions: 1, + originator: %System{} + }) {res, consumption} = TransactionConsumptionConsumerGate.consume(meta.sender, %{ @@ -923,7 +990,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => nil, "metadata" => nil, "idempotency_token" => "123", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) assert res == :ok @@ -937,7 +1005,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => nil, "metadata" => nil, "idempotency_token" => "1234", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) assert res == :error @@ -946,7 +1015,12 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do test "allows only one consume with four consumes at the same time", meta do initialize_wallet(meta.sender_wallet, 200_000, meta.token) - {:ok, request} = TransactionRequest.update(meta.request, %{max_consumptions: 1}) + + {:ok, request} = + TransactionRequest.update(meta.request, %{ + max_consumptions: 1, + originator: %System{} + }) pid = self() @@ -965,7 +1039,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => nil, "metadata" => nil, "idempotency_token" => "1", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) send(pid, {:updated_1, res, response}) @@ -986,7 +1061,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => nil, "metadata" => nil, "idempotency_token" => "2", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) send(pid, {:updated_2, res, response}) @@ -1007,7 +1083,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => nil, "metadata" => nil, "idempotency_token" => "3", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) send(pid, {:updated_3, res, response}) @@ -1028,7 +1105,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => nil, "metadata" => nil, "idempotency_token" => "4", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) send(pid, {:updated_4, res, response}) @@ -1053,7 +1131,12 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do increment it", meta do initialize_wallet(meta.sender_wallet, 200_000, meta.token) - {:ok, request} = TransactionRequest.update(meta.request, %{max_consumptions: 2}) + + {:ok, request} = + TransactionRequest.update(meta.request, %{ + max_consumptions: 2, + originator: %System{} + }) {res, consumption} = TransactionConsumptionConsumerGate.consume(meta.sender, %{ @@ -1063,7 +1146,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => nil, "metadata" => nil, "idempotency_token" => "123", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) assert res == :ok @@ -1077,7 +1161,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => nil, "metadata" => nil, "idempotency_token" => "1234", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) assert res == :ok @@ -1095,7 +1180,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do {:ok, request} = TransactionRequest.update(meta.request, %{ - max_consumptions: 1 + max_consumptions: 1, + originator: %System{} }) {res, consumption} = @@ -1106,7 +1192,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => meta.sender_wallet.address, "metadata" => %{}, "idempotency_token" => "123", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) assert res == :ok @@ -1120,7 +1207,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => nil, "metadata" => nil, "idempotency_token" => "1234", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) assert res == :error @@ -1134,7 +1222,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do {:ok, request} = TransactionRequest.update(meta.request, %{ - require_confirmation: true + require_confirmation: true, + originator: %System{} }) {res, consumption_1} = @@ -1145,7 +1234,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => meta.sender_wallet.address, "metadata" => %{}, "idempotency_token" => "123", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) assert res == :ok @@ -1159,7 +1249,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => meta.sender_wallet.address, "metadata" => %{}, "idempotency_token" => "123", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) assert res == :ok @@ -1176,7 +1267,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do TransactionRequest.update(meta.request, %{ require_confirmation: true, # 60 seconds - consumption_lifetime: 60_000 + consumption_lifetime: 60_000, + originator: %System{} }) {res, consumption} = @@ -1187,7 +1279,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => meta.sender_wallet.address, "metadata" => %{}, "idempotency_token" => "123", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) assert res == :ok @@ -1203,7 +1296,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do {:ok, request} = TransactionRequest.update(meta.request, %{ # 60 seconds - consumption_lifetime: 60_000 + consumption_lifetime: 60_000, + originator: %System{} }) {res, consumption} = @@ -1214,7 +1308,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => meta.sender_wallet.address, "metadata" => %{}, "idempotency_token" => "123", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) assert res == :ok @@ -1227,7 +1322,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do {:ok, request} = TransactionRequest.update(meta.request, %{ - allow_amount_override: true + allow_amount_override: true, + originator: %System{} }) {res, consumption} = @@ -1238,7 +1334,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => meta.sender_wallet.address, "metadata" => %{}, "idempotency_token" => "123", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) assert res == :ok @@ -1253,7 +1350,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do {:ok, request} = TransactionRequest.update(meta.request, %{ - allow_amount_override: false + allow_amount_override: false, + originator: %System{} }) {res, error} = @@ -1264,7 +1362,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => meta.sender_wallet.address, "metadata" => %{}, "idempotency_token" => "123", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) assert res == :error @@ -1301,7 +1400,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => meta.sender_wallet.address, "metadata" => %{}, "idempotency_token" => "123", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) assert res == :error @@ -1322,7 +1422,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => nil, "metadata" => nil, "idempotency_token" => "123", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) assert res == :ok @@ -1335,7 +1436,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => nil, "metadata" => nil, "idempotency_token" => "123", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) assert res == :ok @@ -1362,7 +1464,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => nil, "metadata" => nil, "idempotency_token" => "123", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) assert res == :error @@ -1383,7 +1486,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => "fake-0000-0000-0000", "metadata" => nil, "idempotency_token" => "123", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) assert res == :error @@ -1402,7 +1506,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do "address" => wallet.address, "metadata" => nil, "idempotency_token" => "123", - "token_id" => nil + "token_id" => nil, + "originator" => %System{} }) assert res == :error @@ -1414,7 +1519,8 @@ defmodule EWallet.TransactionConsumptionConsumerGateTest do TransactionConsumptionConsumerGate.consume(meta.sender, %{ "correlation_id" => nil, "amount" => nil, - "metadata" => nil + "metadata" => nil, + "originator" => %System{} }) assert res == :error diff --git a/apps/ewallet/test/ewallet/gates/transaction_gate_test.exs b/apps/ewallet/test/ewallet/gates/transaction_gate_test.exs index 9bb929fb8..47ce3fe70 100644 --- a/apps/ewallet/test/ewallet/gates/transaction_gate_test.exs +++ b/apps/ewallet/test/ewallet/gates/transaction_gate_test.exs @@ -5,6 +5,7 @@ defmodule EWallet.TransactionGateTest do alias Ecto.UUID alias EWallet.{BalanceFetcher, TransactionGate} alias EWalletDB.{Account, Token, Transaction, User, Wallet} + alias ActivityLogger.System def init_wallet(address, token, amount \\ 1_000) do master_account = Account.get_master_account() @@ -32,7 +33,8 @@ defmodule EWallet.TransactionGateTest do "token_id" => token.id, "amount" => 100 * token.subunit_to_unit, "metadata" => %{some: "data"}, - "idempotency_token" => idempotency_token + "idempotency_token" => idempotency_token, + "originator" => %System{} } end @@ -63,7 +65,8 @@ defmodule EWallet.TransactionGateTest do error_description: response["description"], error_data: nil, status: status, - type: Transaction.internal() + type: Transaction.internal(), + originator: %System{} }) {idempotency_token, transaction, attrs} @@ -205,7 +208,8 @@ defmodule EWallet.TransactionGateTest do "token_id" => token.id, "amount" => 0, "metadata" => %{some: "data"}, - "idempotency_token" => idempotency_token + "idempotency_token" => idempotency_token, + "originator" => %System{} }) assert res == :error @@ -234,7 +238,11 @@ defmodule EWallet.TransactionGateTest do attrs = build_addresses_attrs(idempotency_token, wallet1, wallet2, token) init_wallet(wallet1.address, token, 1_000) - {:ok, _token} = Token.enable_or_disable(token, %{enabled: false}) + {:ok, _token} = + Token.enable_or_disable(token, %{ + enabled: false, + originator: %System{} + }) {status, code} = TransactionGate.create(attrs) assert status == :error @@ -250,13 +258,18 @@ defmodule EWallet.TransactionGateTest do Wallet.insert_secondary_or_burn(%{ "account_uuid" => account.uuid, "name" => "MySecondary", - "identifier" => "secondary" + "identifier" => "secondary", + "originator" => %System{} }) attrs = build_addresses_attrs(idempotency_token, wallet3, wallet2, token) init_wallet(wallet3.address, token, 1_000) - {:ok, _wallet3} = Wallet.enable_or_disable(wallet3, %{enabled: false}) + {:ok, _wallet3} = + Wallet.enable_or_disable(wallet3, %{ + enabled: false, + originator: %System{} + }) {status, code} = TransactionGate.create(attrs) assert status == :error @@ -272,13 +285,18 @@ defmodule EWallet.TransactionGateTest do Wallet.insert_secondary_or_burn(%{ "account_uuid" => account.uuid, "name" => "MySecondary", - "identifier" => "secondary" + "identifier" => "secondary", + "originator" => %System{} }) attrs = build_addresses_attrs(idempotency_token, wallet1, wallet3, token) init_wallet(wallet1.address, token, 1_000) - {:ok, _wallet3} = Wallet.enable_or_disable(wallet3, %{enabled: false}) + {:ok, _wallet3} = + Wallet.enable_or_disable(wallet3, %{ + enabled: false, + originator: %System{} + }) {status, code} = TransactionGate.create(attrs) assert status == :error @@ -315,7 +333,8 @@ defmodule EWallet.TransactionGateTest do "to_amount" => 200 * token_1.subunit_to_unit, "exchange_account_id" => account.id, "metadata" => %{something: "interesting"}, - "encrypted_metadata" => %{something: "secret"} + "encrypted_metadata" => %{something: "secret"}, + "originator" => %System{} }) {:ok, b1} = BalanceFetcher.get(token_1.id, wallet_1) diff --git a/apps/ewallet/test/ewallet/gates/transaction_request_gate_test.exs b/apps/ewallet/test/ewallet/gates/transaction_request_gate_test.exs index ab59a44d2..1643da753 100644 --- a/apps/ewallet/test/ewallet/gates/transaction_request_gate_test.exs +++ b/apps/ewallet/test/ewallet/gates/transaction_request_gate_test.exs @@ -2,7 +2,7 @@ defmodule EWallet.TransactionRequestGateTest do use EWallet.LocalLedgerCase, async: true alias EWallet.TransactionRequestGate alias EWalletDB.{AccountUser, Token, TransactionRequest, User, Wallet} - alias EWalletConfig.System + alias ActivityLogger.System setup do {:ok, user} = :user |> params_for() |> User.insert() @@ -28,7 +28,8 @@ defmodule EWallet.TransactionRequestGateTest do "token_id" => meta.token.id, "correlation_id" => "123", "amount" => 1_000, - "account_id" => nil + "account_id" => nil, + "originator" => %System{} }) assert res == {:error, :account_id_not_found} @@ -41,7 +42,8 @@ defmodule EWallet.TransactionRequestGateTest do "token_id" => meta.token.id, "correlation_id" => "123", "amount" => 1_000, - "account_id" => "fake" + "account_id" => "fake", + "originator" => %System{} }) assert res == {:error, :account_id_not_found} @@ -55,7 +57,8 @@ defmodule EWallet.TransactionRequestGateTest do "correlation_id" => "123", "amount" => 1_000, "account_id" => "fake", - "address" => nil + "address" => nil, + "originator" => %System{} }) assert res == {:error, :account_id_not_found} @@ -69,7 +72,8 @@ defmodule EWallet.TransactionRequestGateTest do "correlation_id" => "123", "amount" => 1_000, "account_id" => meta.account.id, - "creator" => %{account: meta.account} + "creator" => %{account: meta.account}, + "originator" => %System{} }) assert res == :ok @@ -85,7 +89,8 @@ defmodule EWallet.TransactionRequestGateTest do "amount" => 1_000, "account_id" => meta.account.id, "address" => meta.account_wallet.address, - "creator" => %{account: meta.account} + "creator" => %{account: meta.account}, + "originator" => %System{} }) assert res == :ok @@ -101,7 +106,8 @@ defmodule EWallet.TransactionRequestGateTest do "correlation_id" => "123", "amount" => 1_000, "account_id" => meta.account.id, - "address" => "fake-0000-0000-0000" + "address" => "fake-0000-0000-0000", + "originator" => %System{} }) assert res == {:error, :account_wallet_not_found} @@ -119,7 +125,8 @@ defmodule EWallet.TransactionRequestGateTest do "account_id" => meta.account.id, "provider_user_id" => meta.user.provider_user_id, "address" => meta.user_wallet.address, - "creator" => %{account: meta.account} + "creator" => %{account: meta.account}, + "originator" => %System{} }) assert res == :ok @@ -139,7 +146,8 @@ defmodule EWallet.TransactionRequestGateTest do "amount" => 1_000, "account_id" => meta.account.id, "provider_user_id" => meta.user.provider_user_id, - "address" => meta.account_wallet.address + "address" => meta.account_wallet.address, + "originator" => %System{} }) assert res == {:error, :user_wallet_mismatch} @@ -153,7 +161,8 @@ defmodule EWallet.TransactionRequestGateTest do "correlation_id" => "123", "amount" => 1_000, "account_id" => meta.account.id, - "address" => meta.user_wallet.address + "address" => meta.user_wallet.address, + "originator" => %System{} }) assert res == {:error, :account_wallet_mismatch} @@ -168,7 +177,8 @@ defmodule EWallet.TransactionRequestGateTest do "token_id" => meta.token.id, "correlation_id" => "123", "amount" => 1_000, - "provider_user_id" => nil + "provider_user_id" => nil, + "originator" => %System{} }) assert res == {:error, :provider_user_id_not_found} @@ -181,7 +191,8 @@ defmodule EWallet.TransactionRequestGateTest do "token_id" => meta.token.id, "correlation_id" => "123", "amount" => 1_000, - "provider_user_id" => "fake" + "provider_user_id" => "fake", + "originator" => %System{} }) assert res == {:error, :provider_user_id_not_found} @@ -197,7 +208,8 @@ defmodule EWallet.TransactionRequestGateTest do "correlation_id" => "123", "amount" => 1_000, "provider_user_id" => meta.user.provider_user_id, - "creator" => %{account: meta.account} + "creator" => %{account: meta.account}, + "originator" => %System{} }) assert res == :ok @@ -213,7 +225,8 @@ defmodule EWallet.TransactionRequestGateTest do "amount" => 1_000, "provider_user_id" => meta.user.provider_user_id, "address" => meta.user_wallet.address, - "creator" => %{end_user: meta.user} + "creator" => %{end_user: meta.user}, + "originator" => %System{} }) assert res == :ok @@ -228,7 +241,8 @@ defmodule EWallet.TransactionRequestGateTest do "correlation_id" => "123", "amount" => 1_000, "provider_user_id" => meta.user.provider_user_id, - "address" => "fake-0000-0000-0000" + "address" => "fake-0000-0000-0000", + "originator" => %System{} }) assert res == {:error, :user_wallet_not_found} @@ -242,7 +256,8 @@ defmodule EWallet.TransactionRequestGateTest do "correlation_id" => "123", "amount" => 1_000, "provider_user_id" => meta.user.provider_user_id, - "address" => meta.account_wallet.address + "address" => meta.account_wallet.address, + "originator" => %System{} }) assert res == {:error, :user_wallet_mismatch} @@ -257,7 +272,8 @@ defmodule EWallet.TransactionRequestGateTest do "token_id" => meta.token.id, "correlation_id" => "123", "amount" => 1_000, - "address" => nil + "address" => nil, + "originator" => %System{} }) assert res == {:error, :wallet_not_found} @@ -271,7 +287,8 @@ defmodule EWallet.TransactionRequestGateTest do "correlation_id" => "123", "amount" => 1_000, "address" => meta.user_wallet.address, - "creator" => %{end_user: meta.user} + "creator" => %{end_user: meta.user}, + "originator" => %System{} }) assert res == :ok @@ -286,7 +303,8 @@ defmodule EWallet.TransactionRequestGateTest do "correlation_id" => "123", "amount" => 1_000, "address" => "fake-0000-0000-0000", - "creator" => %{account: meta.account} + "creator" => %{account: meta.account}, + "originator" => %System{} }) assert res == {:error, :wallet_not_found} @@ -300,7 +318,8 @@ defmodule EWallet.TransactionRequestGateTest do "type" => "receive", "token_id" => meta.token.id, "correlation_id" => "123", - "amount" => 1_000 + "amount" => 1_000, + "originator" => %System{} }) assert res == {:error, :invalid_parameter} @@ -318,7 +337,8 @@ defmodule EWallet.TransactionRequestGateTest do "correlation_id" => "123", "amount" => 1_000, "address" => meta.user_wallet.address, - "creator" => %{account: meta.account} + "creator" => %{account: meta.account}, + "originator" => %System{} }) assert %TransactionRequest{} = request @@ -338,7 +358,8 @@ defmodule EWallet.TransactionRequestGateTest do "correlation_id" => nil, "amount" => nil, "address" => meta.user_wallet.address, - "creator" => %{end_user: meta.user} + "creator" => %{end_user: meta.user}, + "originator" => %System{} }) assert changeset.errors == [type: {"is invalid", [validation: :inclusion]}] @@ -351,7 +372,8 @@ defmodule EWallet.TransactionRequestGateTest do "token_id" => meta.token.id, "correlation_id" => nil, "amount" => nil, - "address" => "fake-0000-0000-0000" + "address" => "fake-0000-0000-0000", + "originator" => %System{} }) assert error == :user_wallet_not_found @@ -367,7 +389,8 @@ defmodule EWallet.TransactionRequestGateTest do "token_id" => meta.token.id, "correlation_id" => nil, "amount" => nil, - "address" => wallet.address + "address" => wallet.address, + "originator" => %System{} }) assert error == :user_wallet_mismatch @@ -380,7 +403,8 @@ defmodule EWallet.TransactionRequestGateTest do "token_id" => "fake", "correlation_id" => nil, "amount" => nil, - "address" => nil + "address" => nil, + "originator" => %System{} }) assert res == {:error, :token_not_found} @@ -407,7 +431,8 @@ defmodule EWallet.TransactionRequestGateTest do "expiration_reason" => "test", "expired_at" => "something", "max_consumptions" => 3, - "creator" => %{end_user: meta.user} + "creator" => %{end_user: meta.user}, + "originator" => %System{} }) assert %TransactionRequest{} = request @@ -436,14 +461,19 @@ defmodule EWallet.TransactionRequestGateTest do "token_id" => meta.token.id, "correlation_id" => nil, "amount" => nil, - "creator" => %{end_user: meta.user} + "creator" => %{end_user: meta.user}, + "originator" => %System{} }) assert %TransactionRequest{} = request end test "receives an error when the token is disabled", meta do - {:ok, token} = Token.enable_or_disable(meta.token, %{enabled: false}) + {:ok, token} = + Token.enable_or_disable(meta.token, %{ + enabled: false, + originator: %System{} + }) {:error, code} = TransactionRequestGate.create(meta.user_wallet, %{ @@ -451,7 +481,8 @@ defmodule EWallet.TransactionRequestGateTest do "token_id" => token.id, "correlation_id" => nil, "amount" => nil, - "creator" => %{end_user: meta.user} + "creator" => %{end_user: meta.user}, + "originator" => %System{} }) assert code == :token_is_disabled @@ -462,10 +493,15 @@ defmodule EWallet.TransactionRequestGateTest do Wallet.insert_secondary_or_burn(%{ "account_uuid" => meta.account.uuid, "name" => "MySecondary", - "identifier" => "secondary" + "identifier" => "secondary", + "originator" => %System{} }) - {:ok, wallet} = Wallet.enable_or_disable(wallet, %{enabled: false}) + {:ok, wallet} = + Wallet.enable_or_disable(wallet, %{ + enabled: false, + originator: %System{} + }) {:error, code} = TransactionRequestGate.create(wallet, %{ @@ -473,7 +509,8 @@ defmodule EWallet.TransactionRequestGateTest do "token_id" => meta.token.id, "correlation_id" => nil, "amount" => nil, - "creator" => %{end_user: meta.user} + "creator" => %{end_user: meta.user}, + "originator" => %System{} }) assert code == :wallet_is_disabled @@ -486,7 +523,8 @@ defmodule EWallet.TransactionRequestGateTest do "token_id" => meta.token.id, "correlation_id" => nil, "amount" => nil, - "creator" => %{end_user: meta.user} + "creator" => %{end_user: meta.user}, + "originator" => %System{} }) assert changeset.errors == [type: {"is invalid", [validation: :inclusion]}] @@ -498,7 +536,8 @@ defmodule EWallet.TransactionRequestGateTest do "type" => "receive", "token_id" => meta.token.id, "correlation_id" => nil, - "amount" => nil + "amount" => nil, + "originator" => %System{} }) assert error == :invalid_parameter @@ -511,7 +550,8 @@ defmodule EWallet.TransactionRequestGateTest do "token_id" => "fake", "correlation_id" => nil, "amount" => nil, - "creator" => %{end_user: meta.user} + "creator" => %{end_user: meta.user}, + "originator" => %System{} }) assert res == {:error, :token_not_found} @@ -554,10 +594,10 @@ defmodule EWallet.TransactionRequestGateTest do end end - describe "expire_if_past_expiration_date/1" do + describe "expire_if_past_expiration_date/2" do test "does nothing if expiration date is not set" do request = insert(:transaction_request, expiration_date: nil) - {res, request} = TransactionRequestGate.expire_if_past_expiration_date(request) + {res, request} = TransactionRequestGate.expire_if_past_expiration_date(request, %System{}) assert res == :ok assert %TransactionRequest{} = request assert TransactionRequest.valid?(request) == true @@ -566,7 +606,7 @@ defmodule EWallet.TransactionRequestGateTest do test "does nothing if expiration date is not past" do future_date = NaiveDateTime.add(NaiveDateTime.utc_now(), 60, :second) request = insert(:transaction_request, expiration_date: future_date) - {res, request} = TransactionRequestGate.expire_if_past_expiration_date(request) + {res, request} = TransactionRequestGate.expire_if_past_expiration_date(request, %System{}) assert res == :ok assert %TransactionRequest{} = request assert TransactionRequest.valid?(request) == true @@ -575,7 +615,7 @@ defmodule EWallet.TransactionRequestGateTest do test "expires the request if expiration date is past" do past_date = NaiveDateTime.add(NaiveDateTime.utc_now(), -60, :second) request = insert(:transaction_request, expiration_date: past_date) - {res, error} = TransactionRequestGate.expire_if_past_expiration_date(request) + {res, error} = TransactionRequestGate.expire_if_past_expiration_date(request, %System{}) request = TransactionRequest.get(request.id) assert res == :error assert error == :expired_transaction_request @@ -584,10 +624,10 @@ defmodule EWallet.TransactionRequestGateTest do end end - describe "expire_if_max_consumption/1" do + describe "expire_if_max_consumption/2" do test "touches the request if max_consumptions is equal to nil" do request = insert(:transaction_request, max_consumptions: nil) - {res, updated_request} = TransactionRequest.expire_if_max_consumption(request) + {res, updated_request} = TransactionRequest.expire_if_max_consumption(request, %System{}) assert res == :ok assert %TransactionRequest{} = updated_request assert TransactionRequest.valid?(updated_request) == true @@ -596,7 +636,7 @@ defmodule EWallet.TransactionRequestGateTest do test "touches the request if max_consumptions is equal to 0" do request = insert(:transaction_request, max_consumptions: 0) - {res, updated_request} = TransactionRequest.expire_if_max_consumption(request) + {res, updated_request} = TransactionRequest.expire_if_max_consumption(request, %System{}) assert res == :ok assert %TransactionRequest{} = updated_request assert TransactionRequest.valid?(updated_request) == true @@ -605,7 +645,7 @@ defmodule EWallet.TransactionRequestGateTest do test "touches the request if max_consumptions has not been reached" do request = insert(:transaction_request, max_consumptions: 3) - {res, updated_request} = TransactionRequest.expire_if_max_consumption(request) + {res, updated_request} = TransactionRequest.expire_if_max_consumption(request, %System{}) assert res == :ok assert %TransactionRequest{} = updated_request assert TransactionRequest.valid?(updated_request) == true @@ -629,7 +669,7 @@ defmodule EWallet.TransactionRequestGateTest do status: "confirmed" ) - {res, updated_request} = TransactionRequest.expire_if_max_consumption(request) + {res, updated_request} = TransactionRequest.expire_if_max_consumption(request, %System{}) assert res == :ok assert %TransactionRequest{} = updated_request assert updated_request.expired_at != nil diff --git a/apps/ewallet/test/ewallet/validators/transaction_consumption_validator_test.exs b/apps/ewallet/test/ewallet/validators/transaction_consumption_validator_test.exs index 043fde916..08ce2cc35 100644 --- a/apps/ewallet/test/ewallet/validators/transaction_consumption_validator_test.exs +++ b/apps/ewallet/test/ewallet/validators/transaction_consumption_validator_test.exs @@ -2,6 +2,7 @@ defmodule EWallet.TransactionConsumptionValidatorTest do use EWallet.LocalLedgerCase, async: true alias EWallet.{TestEndpoint, TransactionConsumptionValidator} alias EWalletDB.{Account, Repo, TransactionConsumption, TransactionRequest, User} + alias ActivityLogger.System describe "validate_before_consumption/3" do test "expires a transaction request if past expiration date" do @@ -19,7 +20,7 @@ defmodule EWallet.TransactionConsumptionValidatorTest do end test "returns expiration reason if transaction request has expired" do - {:ok, request} = :transaction_request |> insert() |> TransactionRequest.expire() + {:ok, request} = :transaction_request |> insert() |> TransactionRequest.expire(%System{}) wallet = request.wallet {:error, error} = diff --git a/apps/ewallet/test/ewallet/web/filters/match_all_parser_test.exs b/apps/ewallet/test/ewallet/web/filters/match_all_parser_test.exs index 4736d7eb4..57dea589e 100644 --- a/apps/ewallet/test/ewallet/web/filters/match_all_parser_test.exs +++ b/apps/ewallet/test/ewallet/web/filters/match_all_parser_test.exs @@ -3,6 +3,7 @@ defmodule EWallet.Web.MatchAllParserTest do import EWalletDB.Factory alias EWallet.Web.{MatchAllParser, Preloader} alias EWalletDB.{Account, Repo, Transaction, User} + alias ActivityLogger.System describe "to_query/3" do test "filter for mapped field" do @@ -249,7 +250,7 @@ defmodule EWallet.Web.MatchAllParserTest do txn_3 = insert(:transaction) {:ok, txn_2} = Preloader.preload_one(txn_2, :from_user) - {:ok, _user} = User.set_admin(txn_2.from_user, true) + {:ok, _user} = User.set_admin(txn_2.from_user, true, %System{}) attrs = %{ "match_all" => [ diff --git a/apps/ewallet/test/ewallet/web/filters/match_any_parser_test.exs b/apps/ewallet/test/ewallet/web/filters/match_any_parser_test.exs index 7ab525f96..90a2e60fc 100644 --- a/apps/ewallet/test/ewallet/web/filters/match_any_parser_test.exs +++ b/apps/ewallet/test/ewallet/web/filters/match_any_parser_test.exs @@ -3,6 +3,7 @@ defmodule EWallet.Web.MatchAnyParserTest do import EWalletDB.Factory alias EWallet.Web.{MatchAnyParser, Preloader} alias EWalletDB.{Account, Repo, Transaction, User} + alias ActivityLogger.System describe "to_query/3" do test "filter for boolean true when given 'true' as value" do @@ -222,7 +223,7 @@ defmodule EWallet.Web.MatchAnyParserTest do txn_3 = insert(:transaction) {:ok, txn_2} = Preloader.preload_one(txn_2, :from_user) - {:ok, _user} = User.set_admin(txn_2.from_user, true) + {:ok, _user} = User.set_admin(txn_2.from_user, true, %System{}) attrs = %{ "match_any" => [ diff --git a/apps/ewallet/test/ewallet/web/inviter_test.exs b/apps/ewallet/test/ewallet/web/inviter_test.exs index e8435e04f..fa14d1655 100644 --- a/apps/ewallet/test/ewallet/web/inviter_test.exs +++ b/apps/ewallet/test/ewallet/web/inviter_test.exs @@ -3,6 +3,7 @@ defmodule EWallet.Web.InviterTest do use Bamboo.Test alias EWallet.Web.{Inviter, MockInviteEmail, Preloader} alias EWalletDB.{Account, Invite, Membership, User} + alias ActivityLogger.System @user_redirect_url "http://localhost:4000/some_redirect_url?email={email}&token={token}" @user_success_url "http://localhost:4000/some_success_url" @@ -177,7 +178,7 @@ defmodule EWallet.Web.InviterTest do describe "send_email/3" do test "creates and sends the invite email" do {:ok, user} = :admin |> params_for() |> User.insert() - {:ok, invite} = Invite.generate(user) + {:ok, invite} = Invite.generate(user, %System{}) {res, _} = Inviter.send_email(invite, @admin_redirect_url, &MockInviteEmail.create/2) diff --git a/apps/ewallet/test/ewallet/web/v1/serializers/account_serializer_test.exs b/apps/ewallet/test/ewallet/web/v1/serializers/account_serializer_test.exs index 459f66f6a..e24769b09 100644 --- a/apps/ewallet/test/ewallet/web/v1/serializers/account_serializer_test.exs +++ b/apps/ewallet/test/ewallet/web/v1/serializers/account_serializer_test.exs @@ -4,12 +4,13 @@ defmodule EWallet.Web.V1.AccountSerializerTest do alias EWallet.Web.{Date, Orchestrator, Paginator} alias EWallet.Web.V1.{AccountOverlay, AccountSerializer, CategorySerializer} alias EWalletDB.Account + alias ActivityLogger.System describe "AccountSerializer.serialize/1" do test "serializes an account into V1 response format" do master = :account |> insert() category = :category |> insert() - {:ok, account} = :account |> insert() |> Account.add_category(category) + {:ok, account} = :account |> insert() |> Account.add_category(category, %System{}) {:ok, account} = Orchestrator.one(account, AccountOverlay) assert AccountSerializer.serialize(account) == %{ diff --git a/apps/ewallet/test/ewallet/web/v1/serializers/transaction_consumption_serializer_test.exs b/apps/ewallet/test/ewallet/web/v1/serializers/transaction_consumption_serializer_test.exs index dacfb2927..42a1ccd8c 100644 --- a/apps/ewallet/test/ewallet/web/v1/serializers/transaction_consumption_serializer_test.exs +++ b/apps/ewallet/test/ewallet/web/v1/serializers/transaction_consumption_serializer_test.exs @@ -1,7 +1,7 @@ defmodule EWallet.Web.V1.TransactionConsumptionSerializerTest do use EWallet.Web.SerializerCase, :v1 alias EWallet.Web.Date - alias EWalletConfig.Helpers.Assoc + alias Utils.Helpers.Assoc alias EWalletDB.TransactionConsumption alias EWallet.Web.V1.{ diff --git a/apps/ewallet/test/ewallet/web/v1/serializers/transaction_request_serializer_test.exs b/apps/ewallet/test/ewallet/web/v1/serializers/transaction_request_serializer_test.exs index cde29f11e..ea8967d1c 100644 --- a/apps/ewallet/test/ewallet/web/v1/serializers/transaction_request_serializer_test.exs +++ b/apps/ewallet/test/ewallet/web/v1/serializers/transaction_request_serializer_test.exs @@ -1,6 +1,6 @@ defmodule EWallet.Web.V1.TransactionRequestSerializerTest do use EWallet.Web.SerializerCase, :v1 - alias EWalletConfig.Helpers.Assoc + alias Utils.Helpers.Assoc alias EWalletDB.TransactionRequest alias EWallet.Web.V1.{ diff --git a/apps/ewallet/test/ewallet/web/v1/serializers/transaction_serializer_test.exs b/apps/ewallet/test/ewallet/web/v1/serializers/transaction_serializer_test.exs index 6a608aa05..daa53478e 100644 --- a/apps/ewallet/test/ewallet/web/v1/serializers/transaction_serializer_test.exs +++ b/apps/ewallet/test/ewallet/web/v1/serializers/transaction_serializer_test.exs @@ -3,7 +3,7 @@ defmodule EWallet.Web.V1.TransactionSerializerTest do alias Ecto.Association.NotLoaded alias EWallet.Web.Date alias EWallet.Web.V1.{AccountSerializer, TokenSerializer, TransactionSerializer, UserSerializer} - alias EWalletConfig.Helpers.Assoc + alias Utils.Helpers.Assoc alias EWalletDB.{Repo, Token} describe "serialize/1 for single transaction" do diff --git a/apps/ewallet/test/support/db_case.ex b/apps/ewallet/test/support/db_case.ex index df88a8676..ff01915c7 100644 --- a/apps/ewallet/test/support/db_case.ex +++ b/apps/ewallet/test/support/db_case.ex @@ -19,11 +19,13 @@ defmodule EWallet.DBCase do :ok = Sandbox.checkout(EWalletDB.Repo) :ok = Sandbox.checkout(LocalLedgerDB.Repo) :ok = Sandbox.checkout(EWalletConfig.Repo) + :ok = Sandbox.checkout(ActivityLogger.Repo) unless tags[:async] do Sandbox.mode(EWalletConfig.Repo, {:shared, self()}) Sandbox.mode(EWalletDB.Repo, {:shared, self()}) Sandbox.mode(LocalLedgerDB.Repo, {:shared, self()}) + Sandbox.mode(ActivityLogger.Repo, {:shared, self()}) end pid = diff --git a/apps/ewallet/test/support/local_ledger_case.ex b/apps/ewallet/test/support/local_ledger_case.ex index d65d6742b..408f98cc3 100644 --- a/apps/ewallet/test/support/local_ledger_case.ex +++ b/apps/ewallet/test/support/local_ledger_case.ex @@ -7,6 +7,7 @@ defmodule EWallet.LocalLedgerCase do alias EWallet.{MintGate, TransactionGate} alias EWalletDB.Account alias EWalletConfig.ConfigTestHelper + alias ActivityLogger.System defmacro __using__(_opts) do quote do @@ -20,11 +21,13 @@ defmodule EWallet.LocalLedgerCase do :ok = Sandbox.checkout(EWalletConfig.Repo) :ok = Sandbox.checkout(EWalletDB.Repo) :ok = Sandbox.checkout(LocalLedgerDB.Repo) + :ok = Sandbox.checkout(ActivityLogger.Repo) unless tags[:async] do Sandbox.mode(EWalletConfig.Repo, {:shared, self()}) Sandbox.mode(EWalletDB.Repo, {:shared, self()}) Sandbox.mode(LocalLedgerDB.Repo, {:shared, self()}) + Sandbox.mode(ActivityLogger.Repo, {:shared, self()}) end ConfigTestHelper.restart_config_genserver( @@ -52,7 +55,8 @@ defmodule EWallet.LocalLedgerCase do "token_id" => token.id, "amount" => amount * token.subunit_to_unit, "description" => "Minting #{amount} #{token.symbol}", - "metadata" => %{} + "metadata" => %{}, + "originator" => %System{} }) assert mint.confirmed == true @@ -67,7 +71,8 @@ defmodule EWallet.LocalLedgerCase do "token_id" => token.id, "amount" => amount, "metadata" => %{}, - "idempotency_token" => UUID.generate() + "idempotency_token" => UUID.generate(), + "originator" => %System{} }) transaction @@ -84,7 +89,8 @@ defmodule EWallet.LocalLedgerCase do "token_id" => token.id, "amount" => amount * token.subunit_to_unit, "metadata" => %{}, - "idempotency_token" => UUID.generate() + "idempotency_token" => UUID.generate(), + "originator" => %System{} }) transaction diff --git a/apps/ewallet/test/support/serializer_case.ex b/apps/ewallet/test/support/serializer_case.ex index 986fb3c43..edf2adf8f 100644 --- a/apps/ewallet/test/support/serializer_case.ex +++ b/apps/ewallet/test/support/serializer_case.ex @@ -11,7 +11,8 @@ defmodule EWallet.Web.SerializerCase do alias EWalletDB.Repo setup do - :ok = Sandbox.checkout(Repo) + :ok = Sandbox.checkout(EWalletDB.Repo) + :ok = Sandbox.checkout(ActivityLogger.Repo) end end end diff --git a/apps/ewallet/test/test_helper.exs b/apps/ewallet/test/test_helper.exs index ce4aa0197..e69f26b26 100644 --- a/apps/ewallet/test/test_helper.exs +++ b/apps/ewallet/test/test_helper.exs @@ -3,3 +3,4 @@ ExUnit.start() Ecto.Adapters.SQL.Sandbox.mode(EWalletConfig.Repo, :manual) Ecto.Adapters.SQL.Sandbox.mode(EWalletDB.Repo, :manual) Ecto.Adapters.SQL.Sandbox.mode(LocalLedgerDB.Repo, :manual) +Ecto.Adapters.SQL.Sandbox.mode(ActivityLogger.Repo, :manual) From dc2fc575deea16b51400d363b1b0b65a0c566418 Mon Sep 17 00:00:00 2001 From: Thibault Denizet Date: Mon, 3 Dec 2018 12:55:59 +0700 Subject: [PATCH 08/23] Integrate ActivityLogger in EWalletAPI sub app --- .../controllers/transaction_consumption_controller.ex | 11 +++++++++-- .../v1/controllers/transaction_controller.ex | 3 ++- .../v1/controllers/transaction_request_controller.ex | 5 ++++- .../lib/ewallet_api/v1/end_user_authenticator.ex | 2 +- .../lib/ewallet_api/v1/plugs/client_auth_plug.ex | 5 ++++- .../test/ewallet_api/v1/auth/client_auth_test.exs | 3 ++- .../v1/controllers/auth_controller_test.exs | 2 +- .../pages/verify_email_controller_test.exs | 11 +++++++---- .../v1/controllers/signup_controller_test.exs | 3 ++- .../ewallet_api/v1/end_user_authenticator_test.exs | 5 +++-- .../ewallet_api/v1/plugs/client_auth_plug_test.exs | 3 ++- .../ewallet_api/v1/views/transaction_view_test.exs | 2 +- apps/ewallet_api/test/support/channel_case.ex | 2 ++ apps/ewallet_api/test/support/conn_case.ex | 11 ++++++++--- apps/ewallet_api/test/support/view_case.ex | 1 + 15 files changed, 49 insertions(+), 20 deletions(-) diff --git a/apps/ewallet_api/lib/ewallet_api/v1/controllers/transaction_consumption_controller.ex b/apps/ewallet_api/lib/ewallet_api/v1/controllers/transaction_consumption_controller.ex index 07c795eb7..e282489a0 100644 --- a/apps/ewallet_api/lib/ewallet_api/v1/controllers/transaction_consumption_controller.ex +++ b/apps/ewallet_api/lib/ewallet_api/v1/controllers/transaction_consumption_controller.ex @@ -1,7 +1,7 @@ defmodule EWalletAPI.V1.TransactionConsumptionController do use EWalletAPI, :controller import EWalletAPI.V1.ErrorHandler - alias EWallet.Web.{Orchestrator, V1.TransactionConsumptionOverlay} + alias EWallet.Web.{Orchestrator, Originator, V1.TransactionConsumptionOverlay} alias EWallet.{ TransactionConsumptionConfirmerGate, @@ -17,6 +17,7 @@ defmodule EWalletAPI.V1.TransactionConsumptionController do attrs |> Map.delete("exchange_account_id") |> Map.delete("exchange_wallet_address") + |> Map.put("originator", Originator.extract(conn.assigns)) with {:ok, consumption} <- TransactionConsumptionConsumerGate.consume(conn.assigns.user, attrs) do @@ -37,7 +38,13 @@ defmodule EWalletAPI.V1.TransactionConsumptionController do def reject_for_user(conn, attrs), do: confirm(conn, conn.assigns.user, attrs, false) defp confirm(conn, user, %{"id" => id} = attrs, approved) do - case TransactionConsumptionConfirmerGate.confirm(id, approved, %{end_user: user}) do + id + |> TransactionConsumptionConfirmerGate.confirm( + approved, + %{end_user: user}, + Originator.extract(conn.assigns) + ) + |> case do {:ok, consumption} -> consumption |> Orchestrator.one(TransactionConsumptionOverlay, attrs) diff --git a/apps/ewallet_api/lib/ewallet_api/v1/controllers/transaction_controller.ex b/apps/ewallet_api/lib/ewallet_api/v1/controllers/transaction_controller.ex index da312e4ba..ee8bbd084 100644 --- a/apps/ewallet_api/lib/ewallet_api/v1/controllers/transaction_controller.ex +++ b/apps/ewallet_api/lib/ewallet_api/v1/controllers/transaction_controller.ex @@ -2,7 +2,7 @@ defmodule EWalletAPI.V1.TransactionController do use EWalletAPI, :controller import EWalletAPI.V1.ErrorHandler alias EWallet.{TransactionGate, WalletFetcher} - alias EWallet.Web.{Orchestrator, Paginator, V1.TransactionOverlay} + alias EWallet.Web.{Orchestrator, Originator, Paginator, V1.TransactionOverlay} alias EWalletDB.{Repo, Transaction, User} @allowed_fields [ @@ -84,6 +84,7 @@ defmodule EWalletAPI.V1.TransactionController do |> Enum.filter(fn {k, _v} -> Enum.member?(@allowed_fields, k) end) |> Enum.into(%{}) |> Map.put("from_user_id", conn.assigns.user.id) + |> Map.put("originator", Originator.extract(conn.assigns)) |> TransactionGate.create() case res do diff --git a/apps/ewallet_api/lib/ewallet_api/v1/controllers/transaction_request_controller.ex b/apps/ewallet_api/lib/ewallet_api/v1/controllers/transaction_request_controller.ex index 83984c08b..473d63707 100644 --- a/apps/ewallet_api/lib/ewallet_api/v1/controllers/transaction_request_controller.ex +++ b/apps/ewallet_api/lib/ewallet_api/v1/controllers/transaction_request_controller.ex @@ -5,11 +5,14 @@ defmodule EWalletAPI.V1.TransactionRequestController do alias EWallet.{ TransactionRequestFetcher, TransactionRequestGate, - TransactionRequestPolicy + TransactionRequestPolicy, + Web.Originator } @spec create_for_user(Plug.Conn.t(), map()) :: Plug.Conn.t() def create_for_user(conn, attrs) do + attrs = Map.put(attrs, "originator", Originator.extract(conn.assigns)) + conn.assigns.user |> TransactionRequestGate.create(attrs) |> respond(conn) diff --git a/apps/ewallet_api/lib/ewallet_api/v1/end_user_authenticator.ex b/apps/ewallet_api/lib/ewallet_api/v1/end_user_authenticator.ex index f667711e4..b6be76edc 100644 --- a/apps/ewallet_api/lib/ewallet_api/v1/end_user_authenticator.ex +++ b/apps/ewallet_api/lib/ewallet_api/v1/end_user_authenticator.ex @@ -4,7 +4,7 @@ defmodule EWalletAPI.V1.EndUserAuthenticator do It returns the associated user if authenticated, `false` otherwise. """ import Plug.Conn - alias EWalletConfig.Helpers.Crypto + alias Utils.Helpers.Crypto alias EWalletDB.{AuthToken, User} def authenticate(conn, email, password) when is_binary(email) do diff --git a/apps/ewallet_api/lib/ewallet_api/v1/plugs/client_auth_plug.ex b/apps/ewallet_api/lib/ewallet_api/v1/plugs/client_auth_plug.ex index 5460246de..53efa1888 100644 --- a/apps/ewallet_api/lib/ewallet_api/v1/plugs/client_auth_plug.ex +++ b/apps/ewallet_api/lib/ewallet_api/v1/plugs/client_auth_plug.ex @@ -9,6 +9,7 @@ defmodule EWalletAPI.V1.ClientAuthPlug do import Plug.Conn import EWalletAPI.V1.ErrorHandler alias EWalletAPI.V1.ClientAuth + alias EWallet.Web.Originator alias EWalletDB.AuthToken def init(opts), do: opts @@ -39,7 +40,9 @@ defmodule EWalletAPI.V1.ClientAuthPlug do """ def expire_token(conn) do token_string = conn.private[:auth_auth_token] - AuthToken.expire(token_string, :ewallet_api) + originator = Originator.extract(conn.assigns) + + AuthToken.expire(token_string, :ewallet_api, originator) conn |> assign(:authenticated, false) diff --git a/apps/ewallet_api/test/ewallet_api/v1/auth/client_auth_test.exs b/apps/ewallet_api/test/ewallet_api/v1/auth/client_auth_test.exs index 9ca123a43..cc1a187d6 100644 --- a/apps/ewallet_api/test/ewallet_api/v1/auth/client_auth_test.exs +++ b/apps/ewallet_api/test/ewallet_api/v1/auth/client_auth_test.exs @@ -2,6 +2,7 @@ defmodule EWalletAPI.V1.ClientAuthTest do use EWallet.LocalLedgerCase, async: true alias EWalletAPI.V1.ClientAuth alias EWalletDB.AuthToken + alias ActivityLogger.System def auth_header(key, token) do encoded_key = Base.encode64(key <> ":" <> token) @@ -70,7 +71,7 @@ defmodule EWalletAPI.V1.ClientAuthTest do end test "halts with :auth_token_expired if auth_token exists but expired", meta do - {:ok, auth_token} = AuthToken.expire(meta.auth_token.token, :ewallet_api) + {:ok, auth_token} = AuthToken.expire(meta.auth_token.token, :ewallet_api, %System{}) auth = auth_header(meta.api_key.key, auth_token.token) assert auth.authenticated == false diff --git a/apps/ewallet_api/test/ewallet_api/v1/controllers/auth_controller_test.exs b/apps/ewallet_api/test/ewallet_api/v1/controllers/auth_controller_test.exs index f4016fcbb..fac3f47ff 100644 --- a/apps/ewallet_api/test/ewallet_api/v1/controllers/auth_controller_test.exs +++ b/apps/ewallet_api/test/ewallet_api/v1/controllers/auth_controller_test.exs @@ -1,6 +1,6 @@ defmodule EWalletAPI.V1.AuthControllerTest do use EWalletAPI.ConnCase, async: true - alias EWalletConfig.Helpers.Crypto + alias Utils.Helpers.Crypto alias EWalletDB.User describe "/user.login" do diff --git a/apps/ewallet_api/test/ewallet_api/v1/controllers/pages/verify_email_controller_test.exs b/apps/ewallet_api/test/ewallet_api/v1/controllers/pages/verify_email_controller_test.exs index 2ebec8f3c..630734234 100644 --- a/apps/ewallet_api/test/ewallet_api/v1/controllers/pages/verify_email_controller_test.exs +++ b/apps/ewallet_api/test/ewallet_api/v1/controllers/pages/verify_email_controller_test.exs @@ -1,6 +1,7 @@ defmodule EWalletAPI.V1.VerifyEmailControllerTest do use EWalletAPI.ConnCase, async: true alias EWalletDB.{Invite, User} + alias ActivityLogger.System describe "verify/2" do defp verify_email(email, token) do @@ -10,7 +11,7 @@ defmodule EWalletAPI.V1.VerifyEmailControllerTest do test "redirects to the default success_url when invite.success_url is not given" do {:ok, user} = :standalone_user |> params_for() |> User.insert() - {:ok, invite} = Invite.generate(user) + {:ok, invite} = Invite.generate(user, %System{}) conn = verify_email(user.email, invite.token) @@ -19,7 +20,9 @@ defmodule EWalletAPI.V1.VerifyEmailControllerTest do test "redirects to the invite.success_url on success" do {:ok, user} = :standalone_user |> params_for() |> User.insert() - {:ok, invite} = Invite.generate(user, success_url: "https://example.com/success_url") + + {:ok, invite} = + Invite.generate(user, %System{}, success_url: "https://example.com/success_url") conn = verify_email(user.email, invite.token) @@ -28,7 +31,7 @@ defmodule EWalletAPI.V1.VerifyEmailControllerTest do test "returns an error when the email is invalid" do {:ok, user} = :standalone_user |> params_for() |> User.insert() - {:ok, invite} = Invite.generate(user) + {:ok, invite} = Invite.generate(user, %System{}) conn = verify_email("wrong@example.com", invite.token) response = text_response(conn, :ok) @@ -39,7 +42,7 @@ defmodule EWalletAPI.V1.VerifyEmailControllerTest do test "returns an error when the token is invalid" do {:ok, user} = :standalone_user |> params_for() |> User.insert() - {:ok, _invite} = Invite.generate(user) + {:ok, _invite} = Invite.generate(user, %System{}) conn = verify_email(user.email, "wrong_token") response = text_response(conn, :ok) diff --git a/apps/ewallet_api/test/ewallet_api/v1/controllers/signup_controller_test.exs b/apps/ewallet_api/test/ewallet_api/v1/controllers/signup_controller_test.exs index 27de26fab..f56c7b3e6 100644 --- a/apps/ewallet_api/test/ewallet_api/v1/controllers/signup_controller_test.exs +++ b/apps/ewallet_api/test/ewallet_api/v1/controllers/signup_controller_test.exs @@ -5,6 +5,7 @@ defmodule EWalletAPI.V1.SignupControllerTest do alias EWalletAPI.V1.VerifyEmailController alias EWallet.VerificationEmail alias EWalletDB.{Invite, User} + alias ActivityLogger.System describe "/user.signup" do test "returns success with an empty response" do @@ -104,7 +105,7 @@ defmodule EWalletAPI.V1.SignupControllerTest do {:ok, user} = :standalone_user |> params_for(email: "verify_email@example.com") |> User.insert() - {:ok, invite} = Invite.generate(user, preload: :user) + {:ok, invite} = Invite.generate(user, %System{}, preload: :user) %{ user: invite.user, diff --git a/apps/ewallet_api/test/ewallet_api/v1/end_user_authenticator_test.exs b/apps/ewallet_api/test/ewallet_api/v1/end_user_authenticator_test.exs index b3d521c51..9b1320ab6 100644 --- a/apps/ewallet_api/test/ewallet_api/v1/end_user_authenticator_test.exs +++ b/apps/ewallet_api/test/ewallet_api/v1/end_user_authenticator_test.exs @@ -1,8 +1,9 @@ defmodule EWalletAPI.Web.V1.EndUserAuthenticatorTest do use EWalletAPI.ConnCase, async: true alias EWalletAPI.V1.EndUserAuthenticator - alias EWalletConfig.Helpers.Crypto + alias Utils.Helpers.Crypto alias EWalletDB.User + alias ActivityLogger.System setup do email = "end_user_auth@example.com" @@ -61,7 +62,7 @@ defmodule EWalletAPI.Web.V1.EndUserAuthenticatorTest do end test "returns conn with authenticated:false if user is disabled", context do - User.enable_or_disable(context.user, %{enabled: false}) + {:ok, _} = User.enable_or_disable(context.user, %{enabled: false, originator: %System{}}) conn = EndUserAuthenticator.authenticate(build_conn(), context.email, context.password) assert_error(conn) end diff --git a/apps/ewallet_api/test/ewallet_api/v1/plugs/client_auth_plug_test.exs b/apps/ewallet_api/test/ewallet_api/v1/plugs/client_auth_plug_test.exs index 34239e0af..145bb477d 100644 --- a/apps/ewallet_api/test/ewallet_api/v1/plugs/client_auth_plug_test.exs +++ b/apps/ewallet_api/test/ewallet_api/v1/plugs/client_auth_plug_test.exs @@ -2,6 +2,7 @@ defmodule EWalletAPI.V1.ClientAuthPlugTest do use EWalletAPI.ConnCase, async: true alias EWalletAPI.V1.ClientAuthPlug alias EWalletDB.AuthToken + alias ActivityLogger.System describe "ClientAuthPlug.call/2" do test "assigns user if api key and auth token are correct" do @@ -34,7 +35,7 @@ defmodule EWalletAPI.V1.ClientAuthPlugTest do end test "halts with :auth_token_expired if auth_token exists but expired" do - AuthToken.expire(@auth_token, :ewallet_api) + AuthToken.expire(@auth_token, :ewallet_api, %System{}) conn = invoke_conn(@api_key, @auth_token) assert_error(conn, "user:auth_token_expired") end diff --git a/apps/ewallet_api/test/ewallet_api/v1/views/transaction_view_test.exs b/apps/ewallet_api/test/ewallet_api/v1/views/transaction_view_test.exs index d419a5abb..4c66a96a8 100644 --- a/apps/ewallet_api/test/ewallet_api/v1/views/transaction_view_test.exs +++ b/apps/ewallet_api/test/ewallet_api/v1/views/transaction_view_test.exs @@ -2,7 +2,7 @@ defmodule EWalletAPI.V1.TransactionViewTest do use EWalletAPI.ViewCase, :v1 alias EWallet.Web.{Date, V1.AccountSerializer, V1.TokenSerializer, V1.UserSerializer} alias EWalletAPI.V1.TransactionView - alias EWalletConfig.Helpers.Assoc + alias Utils.Helpers.Assoc describe "EWalletAPI.V1.TransactionView.render/2" do test "renders transaction.json with correct structure" do diff --git a/apps/ewallet_api/test/support/channel_case.ex b/apps/ewallet_api/test/support/channel_case.ex index 25d44ec41..a85921071 100644 --- a/apps/ewallet_api/test/support/channel_case.ex +++ b/apps/ewallet_api/test/support/channel_case.ex @@ -33,11 +33,13 @@ defmodule EWalletAPI.ChannelCase do :ok = Sandbox.checkout(EWalletConfig.Repo) :ok = Sandbox.checkout(EWalletDB.Repo) :ok = Sandbox.checkout(LocalLedgerDB.Repo) + :ok = Sandbox.checkout(ActivityLogger.Repo) unless tags[:async] do Sandbox.mode(EWalletConfig.Repo, {:shared, self()}) Sandbox.mode(EWalletDB.Repo, {:shared, self()}) Sandbox.mode(LocalLedgerDB.Repo, {:shared, self()}) + Sandbox.mode(ActivityLogger.Repo, {:shared, self()}) end ConfigTestHelper.restart_config_genserver( diff --git a/apps/ewallet_api/test/support/conn_case.ex b/apps/ewallet_api/test/support/conn_case.ex index bd24ae806..020e916fc 100644 --- a/apps/ewallet_api/test/support/conn_case.ex +++ b/apps/ewallet_api/test/support/conn_case.ex @@ -13,6 +13,7 @@ defmodule EWalletAPI.ConnCase do of the test unless the test case is marked as async. """ use ExUnit.CaseTemplate + use Phoenix.ConnTest import EWalletDB.Factory import Ecto.Query alias Ecto.Adapters.SQL.Sandbox @@ -20,7 +21,7 @@ defmodule EWalletAPI.ConnCase do alias EWallet.{MintGate, TransactionGate} alias EWalletDB.{Account, Repo, User} alias EWalletConfig.ConfigTestHelper - use Phoenix.ConnTest + alias ActivityLogger.System # Attributes required by Phoenix.ConnTest @endpoint EWalletAPI.Endpoint @@ -63,11 +64,13 @@ defmodule EWalletAPI.ConnCase do :ok = Sandbox.checkout(EWalletDB.Repo) :ok = Sandbox.checkout(LocalLedgerDB.Repo) :ok = Sandbox.checkout(EWalletConfig.Repo) + :ok = Sandbox.checkout(ActivityLogger.Repo) unless tags[:async] do Sandbox.mode(EWalletConfig.Repo, {:shared, self()}) Sandbox.mode(EWalletDB.Repo, {:shared, self()}) Sandbox.mode(LocalLedgerDB.Repo, {:shared, self()}) + Sandbox.mode(ActivityLogger.Repo, {:shared, self()}) end pid = @@ -158,7 +161,8 @@ defmodule EWalletAPI.ConnCase do "token_id" => token.id, "amount" => amount * token.subunit_to_unit, "description" => "Minting #{amount} #{token.symbol}", - "metadata" => %{} + "metadata" => %{}, + "originator" => %System{} }) assert mint.confirmed == true @@ -173,7 +177,8 @@ defmodule EWalletAPI.ConnCase do "token_id" => token.id, "amount" => amount, "metadata" => %{}, - "idempotency_token" => UUID.generate() + "idempotency_token" => UUID.generate(), + "originator" => %System{} }) transaction diff --git a/apps/ewallet_api/test/support/view_case.ex b/apps/ewallet_api/test/support/view_case.ex index 628017bc3..5f655c3da 100644 --- a/apps/ewallet_api/test/support/view_case.ex +++ b/apps/ewallet_api/test/support/view_case.ex @@ -13,6 +13,7 @@ defmodule EWalletAPI.ViewCase do setup do :ok = Sandbox.checkout(Repo) + :ok = Sandbox.checkout(ActivityLogger.Repo) end # The expected response version From 944846a7d2e865a8d0c212ebceb218af18aae40f Mon Sep 17 00:00:00 2001 From: Thibault Denizet Date: Mon, 3 Dec 2018 12:56:29 +0700 Subject: [PATCH 09/23] Integrate ActivityLogger in AdminAPI --- .../v1/auth/admin_user_authenticator.ex | 6 +++--- .../v1/controllers/account_controller.ex | 5 ++++- .../account_membership_controller.ex | 7 ++++--- .../v1/controllers/admin_auth_controller.ex | 10 ++++++---- .../v1/controllers/admin_user_controller.ex | 3 ++- .../v1/controllers/api_key_controller.ex | 14 +++++++++++--- .../v1/controllers/category_controller.ex | 7 +++++-- .../controllers/exchange_pair_controller.ex | 7 +++++-- .../v1/controllers/invite_controller.ex | 5 +++-- .../v1/controllers/key_controller.ex | 11 ++++++++--- .../v1/controllers/mint_controller.ex | 4 +++- .../v1/controllers/role_controller.ex | 6 ++++-- .../v1/controllers/self_controller.ex | 6 ++---- .../v1/controllers/token_controller.ex | 17 ++++++++++++++--- .../transaction_consumption_controller.ex | 13 ++++++++++--- .../v1/controllers/transaction_controller.ex | 3 +-- .../transaction_request_controller.ex | 3 ++- .../v1/controllers/user_auth_controller.ex | 5 +++-- .../v1/controllers/user_controller.ex | 7 +++---- .../v1/controllers/wallet_controller.ex | 6 ++++-- .../admin_api/v1/helpers/account_helper.ex | 2 +- .../v1/serializers/auth_token_serializer.ex | 2 +- .../v1/auth/admin_user_auth_test.exs | 3 ++- .../v1/auth/admin_user_authenticator_test.exs | 5 +++-- .../admin_auth/account_controller_test.exs | 15 ++++++++------- .../admin_auth/admin_auth_controller_test.exs | 8 ++++---- .../admin_auth/admin_user_controller_test.exs | 5 +++-- .../admin_auth/category_controller_test.exs | 6 +++++- .../exchange_pair_controller_test.exs | 4 +++- .../admin_auth/invite_controller_test.exs | 11 ++++++----- .../admin_auth/mint_controller_test.exs | 7 +++++-- .../reset_password_controller_test.exs | 2 +- .../admin_auth/role_controller_test.exs | 3 ++- .../admin_auth/self_controller_test.exs | 5 +++-- .../admin_auth/token_controller_test.exs | 4 +++- ...ransaction_consumption_controller_test.exs | 9 +++++---- .../transaction_controller_test.exs | 19 ++++++++++++++----- .../transaction_request_controller_test.exs | 7 ++++--- .../admin_auth/user_auth_controller_test.exs | 3 ++- .../admin_auth/user_controller_test.exs | 6 +++--- .../admin_auth/wallet_controller_test.exs | 8 +++++--- .../category_controller_test.exs | 6 +++++- .../provider_auth/mint_controller_test.exs | 7 +++++-- .../provider_auth/role_controller_test.exs | 3 ++- .../provider_auth/self_controller_test.exs | 3 ++- ...ransaction_consumption_controller_test.exs | 2 +- .../transaction_controller_test.exs | 13 +++++++++---- .../transaction_request_controller_test.exs | 2 +- .../user_auth_controller_test.exs | 3 ++- .../provider_auth/user_controller_test.exs | 2 +- .../provider_auth/wallet_controller_test.exs | 2 +- .../v1/views/transaction_view_test.exs | 2 +- apps/admin_api/test/support/channel_case.ex | 2 ++ apps/admin_api/test/support/conn_case.ex | 16 +++++++++++----- apps/admin_api/test/support/view_case.ex | 1 + .../controllers/page_controller_test.exs | 9 ++++++++- 56 files changed, 232 insertions(+), 120 deletions(-) diff --git a/apps/admin_api/lib/admin_api/v1/auth/admin_user_authenticator.ex b/apps/admin_api/lib/admin_api/v1/auth/admin_user_authenticator.ex index c6edd5800..14140dee8 100644 --- a/apps/admin_api/lib/admin_api/v1/auth/admin_user_authenticator.ex +++ b/apps/admin_api/lib/admin_api/v1/auth/admin_user_authenticator.ex @@ -4,7 +4,7 @@ defmodule AdminAPI.V1.AdminUserAuthenticator do It returns the associated user if authenticated, `false` otherwise. """ import Plug.Conn - alias EWalletConfig.Helpers.Crypto + alias Utils.Helpers.Crypto alias EWalletDB.{AuthToken, User} def authenticate(conn, email, password) when is_binary(email) do @@ -41,9 +41,9 @@ defmodule AdminAPI.V1.AdminUserAuthenticator do @doc """ Expires the authentication token used in this connection. """ - def expire_token(conn) do + def expire_token(conn, originator) do token_string = conn.private[:auth_auth_token] - AuthToken.expire(token_string, :admin_api) + AuthToken.expire(token_string, :admin_api, originator) handle_fail_auth(conn, :auth_token_expired) end diff --git a/apps/admin_api/lib/admin_api/v1/controllers/account_controller.ex b/apps/admin_api/lib/admin_api/v1/controllers/account_controller.ex index 03920189f..4eb5f50b8 100644 --- a/apps/admin_api/lib/admin_api/v1/controllers/account_controller.ex +++ b/apps/admin_api/lib/admin_api/v1/controllers/account_controller.ex @@ -3,7 +3,7 @@ defmodule AdminAPI.V1.AccountController do import AdminAPI.V1.ErrorHandler alias AdminAPI.V1.AccountHelper alias EWallet.AccountPolicy - alias EWallet.Web.{Orchestrator, Paginator, V1.AccountOverlay} + alias EWallet.Web.{Orchestrator, Originator, Paginator, V1.AccountOverlay} alias EWalletDB.Account @doc """ @@ -74,6 +74,7 @@ defmodule AdminAPI.V1.AccountController do with :ok <- permit(:create, conn.assigns, parent.id), attrs <- Map.put(attrs, "parent_uuid", parent.uuid), + attrs <- Originator.set_in_attrs(attrs, conn.assigns), {:ok, account} <- Account.insert(attrs), {:ok, account} <- Orchestrator.one(account, AccountOverlay, attrs) do render(conn, :account, %{account: account}) @@ -95,6 +96,7 @@ defmodule AdminAPI.V1.AccountController do def update(conn, %{"id" => account_id} = attrs) do with %Account{} = original <- Account.get(account_id) || {:error, :unauthorized}, :ok <- permit(:update, conn.assigns, original.id), + attrs <- Originator.set_in_attrs(attrs, conn.assigns), {:ok, updated} <- Account.update(original, attrs), {:ok, updated} <- Orchestrator.one(updated, AccountOverlay, attrs) do render(conn, :account, %{account: updated}) @@ -116,6 +118,7 @@ defmodule AdminAPI.V1.AccountController do def upload_avatar(conn, %{"id" => id, "avatar" => _} = attrs) do with %Account{} = account <- Account.get(id) || {:error, :unauthorized}, :ok <- permit(:update, conn.assigns, account.id), + attrs <- Originator.set_in_attrs(attrs, conn.assigns), %{} = saved <- Account.store_avatar(account, attrs), {:ok, saved} <- Orchestrator.one(saved, AccountOverlay, attrs) do render(conn, :account, %{account: saved}) diff --git a/apps/admin_api/lib/admin_api/v1/controllers/account_membership_controller.ex b/apps/admin_api/lib/admin_api/v1/controllers/account_membership_controller.ex index 7900f9c35..f025f02ca 100644 --- a/apps/admin_api/lib/admin_api/v1/controllers/account_membership_controller.ex +++ b/apps/admin_api/lib/admin_api/v1/controllers/account_membership_controller.ex @@ -109,7 +109,7 @@ defmodule AdminAPI.V1.AccountMembershipController do end end - defp assign_or_invite(user, account, role, redirect_url, _originator) do + defp assign_or_invite(user, account, role, redirect_url, originator) do case User.get_status(user) do :pending_confirmation -> user @@ -117,7 +117,7 @@ defmodule AdminAPI.V1.AccountMembershipController do |> Inviter.send_email(redirect_url, &InviteEmail.create/2) :active -> - Membership.assign(user, account, role) + Membership.assign(user, account, role, originator) end end @@ -131,7 +131,8 @@ defmodule AdminAPI.V1.AccountMembershipController do with %Account{} = account <- Account.get(account_id) || {:error, :unauthorized}, :ok <- permit(:delete, conn.assigns, account.id), %User{} = user <- User.get(user_id) || {:error, :user_id_not_found}, - {:ok, _} <- Membership.unassign(user, account) do + originator <- Originator.extract(conn.assigns), + {:ok, _} <- Membership.unassign(user, account, originator) do render(conn, :empty, %{success: true}) else {:error, error} -> handle_error(conn, error) diff --git a/apps/admin_api/lib/admin_api/v1/controllers/admin_auth_controller.ex b/apps/admin_api/lib/admin_api/v1/controllers/admin_auth_controller.ex index c805b305c..df184111a 100644 --- a/apps/admin_api/lib/admin_api/v1/controllers/admin_auth_controller.ex +++ b/apps/admin_api/lib/admin_api/v1/controllers/admin_auth_controller.ex @@ -3,7 +3,7 @@ defmodule AdminAPI.V1.AdminAuthController do import AdminAPI.V1.ErrorHandler alias AdminAPI.V1.AdminUserAuthenticator alias EWallet.AccountPolicy - alias EWallet.Web.{Orchestrator, V1.AuthTokenOverlay} + alias EWallet.Web.{Orchestrator, Originator, V1.AuthTokenOverlay} alias EWalletDB.{Account, AuthToken, User} @doc """ @@ -32,7 +32,8 @@ defmodule AdminAPI.V1.AdminAuthController do token <- conn.private.auth_auth_token, %AuthToken{} = token <- AuthToken.get_by_token(token, :admin_api) || {:error, :auth_token_not_found}, - {:ok, token} <- AuthToken.switch_account(token, account), + originator <- Originator.extract(conn.assigns), + {:ok, token} <- AuthToken.switch_account(token, account, originator), {:ok, token} <- Orchestrator.one(token, AuthTokenOverlay, attrs) do render_token(conn, token) else @@ -51,9 +52,10 @@ defmodule AdminAPI.V1.AdminAuthController do Invalidates the authentication token used in this request. """ def logout(conn, _attrs) do - with {:ok, _current_user} <- permit(:update, conn.assigns) do + with {:ok, _current_user} <- permit(:update, conn.assigns), + originator <- Originator.extract(conn.assigns) do conn - |> AdminUserAuthenticator.expire_token() + |> AdminUserAuthenticator.expire_token(originator) |> render(:empty_response, %{}) else {:error, code} -> diff --git a/apps/admin_api/lib/admin_api/v1/controllers/admin_user_controller.ex b/apps/admin_api/lib/admin_api/v1/controllers/admin_user_controller.ex index 78644121e..c358202da 100644 --- a/apps/admin_api/lib/admin_api/v1/controllers/admin_user_controller.ex +++ b/apps/admin_api/lib/admin_api/v1/controllers/admin_user_controller.ex @@ -4,7 +4,7 @@ defmodule AdminAPI.V1.AdminUserController do alias AdminAPI.V1.AccountHelper alias AdminAPI.V1.UserView alias EWallet.{AdminUserPolicy, UserFetcher} - alias EWallet.Web.{Orchestrator, Paginator, V1.UserOverlay} + alias EWallet.Web.{Orchestrator, Originator, Paginator, V1.UserOverlay} alias EWalletDB.{User, UserQuery, AuthToken} @doc """ @@ -43,6 +43,7 @@ defmodule AdminAPI.V1.AdminUserController do def enable_or_disable(conn, attrs) do with {:ok, %User{} = user} <- UserFetcher.fetch(attrs), :ok <- permit(:enable_or_disable, conn.assigns, user), + attrs <- Originator.set_in_attrs(attrs, conn.assigns), {:ok, updated} <- User.enable_or_disable(user, attrs), :ok <- AuthToken.expire_for_user(updated) do respond_single(updated, conn) diff --git a/apps/admin_api/lib/admin_api/v1/controllers/api_key_controller.ex b/apps/admin_api/lib/admin_api/v1/controllers/api_key_controller.ex index cfd9eaeb1..5f5e2036f 100644 --- a/apps/admin_api/lib/admin_api/v1/controllers/api_key_controller.ex +++ b/apps/admin_api/lib/admin_api/v1/controllers/api_key_controller.ex @@ -3,7 +3,7 @@ defmodule AdminAPI.V1.APIKeyController do import AdminAPI.V1.ErrorHandler alias Ecto.Changeset alias EWallet.APIKeyPolicy - alias EWallet.Web.{Orchestrator, Paginator, V1.APIKeyOverlay} + alias EWallet.Web.{Orchestrator, Originator, Paginator, V1.APIKeyOverlay} alias EWalletDB.APIKey @doc """ @@ -36,7 +36,11 @@ defmodule AdminAPI.V1.APIKeyController do def create(conn, attrs) do with :ok <- permit(:create, conn.assigns, nil), # Admin API doesn't use API Keys anymore. Defaulting to "ewallet_api". - {:ok, api_key} <- APIKey.insert(%{owner_app: "ewallet_api"}), + {:ok, api_key} <- + APIKey.insert(%{ + owner_app: "ewallet_api", + originator: Originator.extract(conn.assigns) + }), {:ok, api_key} <- Orchestrator.one(api_key, APIKeyOverlay, attrs) do render(conn, :api_key, %{api_key: api_key}) else @@ -52,6 +56,7 @@ defmodule AdminAPI.V1.APIKeyController do def update(conn, %{"id" => id} = attrs) do with :ok <- permit(:update, conn.assigns, id), %APIKey{} = api_key <- APIKey.get(id) || {:error, :api_key_not_found}, + attrs <- Originator.set_in_attrs(attrs, conn.assigns), {:ok, api_key} <- APIKey.update(api_key, attrs), {:ok, api_key} <- Orchestrator.one(api_key, APIKeyOverlay, attrs) do render(conn, :api_key, %{api_key: api_key}) @@ -75,6 +80,7 @@ defmodule AdminAPI.V1.APIKeyController do def enable_or_disable(conn, %{"id" => id, "enabled" => _} = attrs) do with :ok <- permit(:enable_or_disable, conn.assigns, id), %APIKey{} = api_key <- APIKey.get(id) || {:error, :api_key_not_found}, + attrs <- Originator.set_in_attrs(attrs, conn.assigns), {:ok, api_key} <- APIKey.enable_or_disable(api_key, attrs), {:ok, api_key} <- Orchestrator.one(api_key, APIKeyOverlay, attrs) do render(conn, :api_key, %{api_key: api_key}) @@ -111,7 +117,9 @@ defmodule AdminAPI.V1.APIKeyController do def delete(conn, _), do: handle_error(conn, :invalid_parameter) defp do_delete(conn, %APIKey{} = key) do - case APIKey.delete(key) do + originator = Originator.extract(conn.assigns) + + case APIKey.delete(key, originator) do {:ok, _key} -> render(conn, :empty_response) diff --git a/apps/admin_api/lib/admin_api/v1/controllers/category_controller.ex b/apps/admin_api/lib/admin_api/v1/controllers/category_controller.ex index 9fb3ad003..a95d7095d 100644 --- a/apps/admin_api/lib/admin_api/v1/controllers/category_controller.ex +++ b/apps/admin_api/lib/admin_api/v1/controllers/category_controller.ex @@ -2,7 +2,7 @@ defmodule AdminAPI.V1.CategoryController do use AdminAPI, :controller import AdminAPI.V1.ErrorHandler alias EWallet.CategoryPolicy - alias EWallet.Web.{Orchestrator, Paginator, V1.CategoryOverlay} + alias EWallet.Web.{Orchestrator, Originator, Paginator, V1.CategoryOverlay} alias EWalletDB.Category @doc """ @@ -46,6 +46,7 @@ defmodule AdminAPI.V1.CategoryController do @spec create(Plug.Conn.t(), map()) :: Plug.Conn.t() def create(conn, attrs) do with :ok <- permit(:create, conn.assigns, nil), + attrs <- Originator.set_in_attrs(attrs, conn.assigns), {:ok, category} <- Category.insert(attrs), {:ok, category} <- Orchestrator.one(category, CategoryOverlay, attrs) do render(conn, :category, %{category: category}) @@ -65,6 +66,7 @@ defmodule AdminAPI.V1.CategoryController do def update(conn, %{"id" => id} = attrs) do with :ok <- permit(:update, conn.assigns, id), %Category{} = original <- Category.get(id) || {:error, :category_id_not_found}, + attrs <- Originator.set_in_attrs(attrs, conn.assigns), {:ok, updated} <- Category.update(original, attrs), {:ok, updated} <- Orchestrator.one(updated, CategoryOverlay, attrs) do render(conn, :category, %{category: updated}) @@ -86,7 +88,8 @@ defmodule AdminAPI.V1.CategoryController do def delete(conn, %{"id" => id} = attrs) do with :ok <- permit(:delete, conn.assigns, id), %Category{} = category <- Category.get(id) || {:error, :category_id_not_found}, - {:ok, deleted} <- Category.delete(category), + originator <- Originator.extract(conn.assigns), + {:ok, deleted} <- Category.delete(category, originator), {:ok, deleted} <- Orchestrator.one(deleted, CategoryOverlay, attrs) do render(conn, :category, %{category: deleted}) else diff --git a/apps/admin_api/lib/admin_api/v1/controllers/exchange_pair_controller.ex b/apps/admin_api/lib/admin_api/v1/controllers/exchange_pair_controller.ex index 4807db90f..cc90698bc 100644 --- a/apps/admin_api/lib/admin_api/v1/controllers/exchange_pair_controller.ex +++ b/apps/admin_api/lib/admin_api/v1/controllers/exchange_pair_controller.ex @@ -2,7 +2,7 @@ defmodule AdminAPI.V1.ExchangePairController do use AdminAPI, :controller import AdminAPI.V1.ErrorHandler alias EWallet.{ExchangePairGate, ExchangePairPolicy} - alias EWallet.Web.{Orchestrator, Paginator, V1.ExchangePairOverlay} + alias EWallet.Web.{Orchestrator, Originator, Paginator, V1.ExchangePairOverlay} alias EWalletDB.ExchangePair @doc """ @@ -46,6 +46,7 @@ defmodule AdminAPI.V1.ExchangePairController do @spec create(Plug.Conn.t(), map()) :: Plug.Conn.t() def create(conn, attrs) do with :ok <- permit(:create, conn.assigns, nil), + attrs <- Originator.set_in_attrs(attrs, conn.assigns), {:ok, pairs} <- ExchangePairGate.insert(attrs), {:ok, pairs} <- Orchestrator.all(pairs, ExchangePairOverlay) do render(conn, :exchange_pairs, %{exchange_pairs: pairs}) @@ -64,6 +65,7 @@ defmodule AdminAPI.V1.ExchangePairController do @spec update(Plug.Conn.t(), map()) :: Plug.Conn.t() def update(conn, %{"id" => id} = attrs) do with :ok <- permit(:update, conn.assigns, id), + attrs <- Originator.set_in_attrs(attrs, conn.assigns), {:ok, pairs} <- ExchangePairGate.update(id, attrs), {:ok, pairs} <- Orchestrator.all(pairs, ExchangePairOverlay) do render(conn, :exchange_pairs, %{exchange_pairs: pairs}) @@ -84,7 +86,8 @@ defmodule AdminAPI.V1.ExchangePairController do @spec delete(Plug.Conn.t(), map()) :: Plug.Conn.t() def delete(conn, %{"id" => id} = attrs) do with :ok <- permit(:delete, conn.assigns, id), - {:ok, deleted_pairs} <- ExchangePairGate.delete(id, attrs), + originator <- Originator.extract(conn.assigns), + {:ok, deleted_pairs} <- ExchangePairGate.delete(id, attrs, originator), {:ok, deleted_pairs} <- Orchestrator.all(deleted_pairs, ExchangePairOverlay) do render(conn, :exchange_pairs, %{exchange_pairs: deleted_pairs}) else diff --git a/apps/admin_api/lib/admin_api/v1/controllers/invite_controller.ex b/apps/admin_api/lib/admin_api/v1/controllers/invite_controller.ex index 21f4ec920..7aeb0c734 100644 --- a/apps/admin_api/lib/admin_api/v1/controllers/invite_controller.ex +++ b/apps/admin_api/lib/admin_api/v1/controllers/invite_controller.ex @@ -2,7 +2,7 @@ defmodule AdminAPI.V1.InviteController do use AdminAPI, :controller import AdminAPI.V1.ErrorHandler alias AdminAPI.V1.UserView - alias EWallet.Web.Preloader + alias EWallet.Web.{Originator, Preloader} alias EWalletDB.{Invite, User} @doc """ @@ -18,7 +18,8 @@ defmodule AdminAPI.V1.InviteController do with %Invite{} = invite <- Invite.get(email, token) || {:error, :invite_not_found}, {:ok, invite} <- Preloader.preload_one(invite, :user), {:ok, _} <- Invite.accept(invite, password, password_confirmation), - {:ok, _} <- User.set_admin(invite.user, true) do + originator <- Originator.get_initial_originator(invite), + {:ok, _} <- User.set_admin(invite.user, true, originator) do render(conn, UserView, :user, %{user: invite.user}) else {:error, error_code} when is_atom(error_code) -> diff --git a/apps/admin_api/lib/admin_api/v1/controllers/key_controller.ex b/apps/admin_api/lib/admin_api/v1/controllers/key_controller.ex index 843e8147e..9490401ef 100644 --- a/apps/admin_api/lib/admin_api/v1/controllers/key_controller.ex +++ b/apps/admin_api/lib/admin_api/v1/controllers/key_controller.ex @@ -3,7 +3,7 @@ defmodule AdminAPI.V1.KeyController do import AdminAPI.V1.ErrorHandler alias AdminAPI.V1.AccountHelper alias EWallet.KeyPolicy - alias EWallet.Web.{Orchestrator, Paginator, V1.KeyOverlay} + alias EWallet.Web.{Orchestrator, Originator, Paginator, V1.KeyOverlay} alias EWalletDB.Key @doc """ @@ -38,7 +38,8 @@ defmodule AdminAPI.V1.KeyController do @spec create(Plug.Conn.t(), map()) :: Plug.Conn.t() def create(conn, attrs) do with :ok <- permit(:create, conn.assigns, nil), - {:ok, key} <- Key.insert(%{}), + attrs <- Originator.set_in_attrs(attrs, conn.assigns, :originator), + {:ok, key} <- Key.insert(attrs), {:ok, key} <- Orchestrator.one(key, KeyOverlay, attrs) do render(conn, :key, %{key: key}) else @@ -57,6 +58,7 @@ defmodule AdminAPI.V1.KeyController do def update(conn, %{"id" => id} = attrs) do with :ok <- permit(:update, conn.assigns, id), %Key{} = key <- Key.get(id) || {:error, :key_not_found}, + attrs <- Originator.set_in_attrs(attrs, conn.assigns), {:ok, key} <- Key.enable_or_disable(key, attrs), {:ok, key} <- Orchestrator.one(key, KeyOverlay, attrs) do render(conn, :key, %{key: key}) @@ -80,6 +82,7 @@ defmodule AdminAPI.V1.KeyController do def enable_or_disable(conn, %{"id" => id, "enabled" => _} = attrs) do with :ok <- permit(:enable_or_disable, conn.assigns, id), %Key{} = key <- Key.get(id) || {:error, :key_not_found}, + attrs <- Originator.set_in_attrs(attrs, conn.assigns), {:ok, key} <- Key.enable_or_disable(key, attrs), {:ok, key} <- Orchestrator.one(key, KeyOverlay, attrs) do render(conn, :key, %{key: key}) @@ -123,7 +126,9 @@ defmodule AdminAPI.V1.KeyController do def delete(conn, _), do: handle_error(conn, :invalid_parameter) defp do_delete(conn, %Key{} = key) do - case Key.delete(key) do + originator = Originator.extract(conn.assigns) + + case Key.delete(key, originator) do {:ok, _key} -> render(conn, :empty_response) diff --git a/apps/admin_api/lib/admin_api/v1/controllers/mint_controller.ex b/apps/admin_api/lib/admin_api/v1/controllers/mint_controller.ex index 19b12a252..8813a4321 100644 --- a/apps/admin_api/lib/admin_api/v1/controllers/mint_controller.ex +++ b/apps/admin_api/lib/admin_api/v1/controllers/mint_controller.ex @@ -6,7 +6,7 @@ defmodule AdminAPI.V1.MintController do import AdminAPI.V1.ErrorHandler alias Ecto.Changeset alias EWallet.{MintGate, MintPolicy} - alias EWallet.Web.{Orchestrator, Paginator, V1.MintOverlay} + alias EWallet.Web.{Originator, Orchestrator, Paginator, V1.MintOverlay} alias EWalletDB.{Mint, Token} alias Plug.Conn @@ -39,6 +39,8 @@ defmodule AdminAPI.V1.MintController do } = attrs ) do with :ok <- permit(:create, conn.assigns, token_id), + originator <- Originator.extract(conn.assigns), + attrs <- Map.put(attrs, "originator", originator), %Token{} = token <- Token.get(token_id) || :token_not_found, {:ok, mint, _token} <- MintGate.mint_token(token, attrs), {:ok, mint} <- Orchestrator.one(mint, MintOverlay, attrs) do diff --git a/apps/admin_api/lib/admin_api/v1/controllers/role_controller.ex b/apps/admin_api/lib/admin_api/v1/controllers/role_controller.ex index d71e143b1..b45a84272 100644 --- a/apps/admin_api/lib/admin_api/v1/controllers/role_controller.ex +++ b/apps/admin_api/lib/admin_api/v1/controllers/role_controller.ex @@ -2,7 +2,7 @@ defmodule AdminAPI.V1.RoleController do use AdminAPI, :controller import AdminAPI.V1.ErrorHandler alias EWallet.RolePolicy - alias EWallet.Web.{Orchestrator, Paginator, V1.RoleOverlay} + alias EWallet.Web.{Orchestrator, Originator, Paginator, V1.RoleOverlay} alias EWalletDB.Role @doc """ @@ -46,6 +46,7 @@ defmodule AdminAPI.V1.RoleController do @spec create(Plug.Conn.t(), map()) :: Plug.Conn.t() def create(conn, attrs) do with :ok <- permit(:create, conn.assigns, nil), + attrs <- Originator.set_in_attrs(attrs, conn.assigns), {:ok, role} <- Role.insert(attrs), {:ok, role} <- Orchestrator.one(role, RoleOverlay, attrs) do render(conn, :role, %{role: role}) @@ -86,7 +87,8 @@ defmodule AdminAPI.V1.RoleController do def delete(conn, %{"id" => id} = attrs) do with :ok <- permit(:delete, conn.assigns, id), %Role{} = role <- Role.get(id) || {:error, :role_id_not_found}, - {:ok, deleted} <- Role.delete(role), + originator <- Originator.extract(conn.assigns), + {:ok, deleted} <- Role.delete(role, originator), {:ok, deleted} <- Orchestrator.one(deleted, RoleOverlay, attrs) do render(conn, :role, %{role: deleted}) else diff --git a/apps/admin_api/lib/admin_api/v1/controllers/self_controller.ex b/apps/admin_api/lib/admin_api/v1/controllers/self_controller.ex index 98cac2774..7db8ff4f4 100644 --- a/apps/admin_api/lib/admin_api/v1/controllers/self_controller.ex +++ b/apps/admin_api/lib/admin_api/v1/controllers/self_controller.ex @@ -27,8 +27,7 @@ defmodule AdminAPI.V1.SelfController do """ def update(conn, attrs) do with {:ok, current_user} <- permit(:update, conn.assigns), - originator <- Originator.extract(conn.assigns), - attrs <- Map.put(attrs, "originator", originator), + attrs <- Originator.set_in_attrs(attrs, conn.assigns), {:ok, user} <- User.update(current_user, attrs) do respond_single(user, conn) else @@ -42,8 +41,7 @@ defmodule AdminAPI.V1.SelfController do """ def update_password(conn, attrs) do with {:ok, current_user} <- permit(:update_password, conn.assigns), - originator <- Originator.extract(conn.assigns), - attrs <- Map.put(attrs, "originator", originator), + attrs <- Originator.set_in_attrs(attrs, conn.assigns), {:ok, user} <- User.update_password(current_user, attrs) do respond_single(user, conn) else diff --git a/apps/admin_api/lib/admin_api/v1/controllers/token_controller.ex b/apps/admin_api/lib/admin_api/v1/controllers/token_controller.ex index 37d030728..070c2460e 100644 --- a/apps/admin_api/lib/admin_api/v1/controllers/token_controller.ex +++ b/apps/admin_api/lib/admin_api/v1/controllers/token_controller.ex @@ -5,7 +5,7 @@ defmodule AdminAPI.V1.TokenController do use AdminAPI, :controller import AdminAPI.V1.ErrorHandler alias EWallet.{Helper, MintGate, TokenPolicy} - alias EWallet.Web.{Orchestrator, Paginator, V1.TokenOverlay} + alias EWallet.Web.{Orchestrator, Originator, Paginator, V1.TokenOverlay} alias EWalletDB.{Account, Mint, Token} @doc """ @@ -80,15 +80,23 @@ defmodule AdminAPI.V1.TokenController do defp do_create(conn, %{"amount" => amount} = attrs) when is_number(amount) and amount > 0 do attrs |> Map.put("account_uuid", Account.get_master_account().uuid) + |> Originator.set_in_attrs(conn.assigns) |> Token.insert() - |> MintGate.mint_token(%{"amount" => amount}) + |> MintGate.mint_token(%{ + "amount" => amount, + "originator" => Originator.extract(conn.assigns) + }) |> respond_single(conn) end defp do_create(conn, %{"amount" => amount} = attrs) when is_binary(amount) do case Helper.string_to_integer(amount) do {:ok, amount} -> - attrs = Map.put(attrs, "amount", amount) + attrs = + attrs + |> Map.put("amount", amount) + |> Originator.set_in_attrs(conn.assigns) + create(conn, attrs) {:error, code, description} -> @@ -101,6 +109,7 @@ defmodule AdminAPI.V1.TokenController do nil -> attrs |> Map.put("account_uuid", Account.get_master_account().uuid) + |> Originator.set_in_attrs(conn.assigns) |> Token.insert() |> respond_single(conn) @@ -116,6 +125,7 @@ defmodule AdminAPI.V1.TokenController do def update(conn, %{"id" => id} = attrs) do with :ok <- permit(:update, conn.assigns, id), %Token{} = token <- Token.get(id) || :token_not_found, + attrs <- Originator.set_in_attrs(attrs, conn.assigns), {:ok, updated} <- Token.update(token, attrs) do respond_single(updated, conn) else @@ -134,6 +144,7 @@ defmodule AdminAPI.V1.TokenController do def enable_or_disable(conn, %{"id" => id} = attrs) do with :ok <- permit(:enable_or_disable, conn.assigns, id), %Token{} = token <- Token.get(id) || :token_not_found, + attrs <- Originator.set_in_attrs(attrs, conn.assigns), {:ok, updated} <- Token.enable_or_disable(token, attrs) do respond_single(updated, conn) else diff --git a/apps/admin_api/lib/admin_api/v1/controllers/transaction_consumption_controller.ex b/apps/admin_api/lib/admin_api/v1/controllers/transaction_consumption_controller.ex index 5b6ba121d..bbe2a61d7 100644 --- a/apps/admin_api/lib/admin_api/v1/controllers/transaction_consumption_controller.ex +++ b/apps/admin_api/lib/admin_api/v1/controllers/transaction_consumption_controller.ex @@ -3,7 +3,7 @@ defmodule AdminAPI.V1.TransactionConsumptionController do import AdminAPI.V1.ErrorHandler alias AdminAPI.V1.AccountHelper alias EWallet.TransactionConsumptionPolicy - alias EWallet.Web.{Orchestrator, Paginator, V1.TransactionConsumptionOverlay} + alias EWallet.Web.{Orchestrator, Originator, Paginator, V1.TransactionConsumptionOverlay} alias EWallet.{ TransactionConsumptionConfirmerGate, @@ -137,7 +137,8 @@ defmodule AdminAPI.V1.TransactionConsumptionController do def consume(conn, %{"idempotency_token" => idempotency_token} = attrs) when idempotency_token != nil do - with {:ok, consumption} <- TransactionConsumptionConsumerGate.consume(attrs) do + with attrs <- Originator.set_in_attrs(attrs, conn.assigns), + {:ok, consumption} <- TransactionConsumptionConsumerGate.consume(attrs) do consumption |> Orchestrator.one(TransactionConsumptionOverlay, attrs) |> respond(conn, true) @@ -155,7 +156,13 @@ defmodule AdminAPI.V1.TransactionConsumptionController do def reject(conn, attrs), do: confirm(conn, conn.assigns, attrs, false) defp confirm(conn, confirmer, %{"id" => id} = attrs, approved) do - case TransactionConsumptionConfirmerGate.confirm(id, approved, confirmer) do + id + |> TransactionConsumptionConfirmerGate.confirm( + approved, + confirmer, + Originator.extract(conn.assigns) + ) + |> case do {:ok, consumption} -> consumption |> Orchestrator.one(TransactionConsumptionOverlay, attrs) diff --git a/apps/admin_api/lib/admin_api/v1/controllers/transaction_controller.ex b/apps/admin_api/lib/admin_api/v1/controllers/transaction_controller.ex index 64c03f816..ce53326c4 100644 --- a/apps/admin_api/lib/admin_api/v1/controllers/transaction_controller.ex +++ b/apps/admin_api/lib/admin_api/v1/controllers/transaction_controller.ex @@ -115,8 +115,7 @@ defmodule AdminAPI.V1.TransactionController do @spec create(Plug.Conn.t(), map()) :: Plug.Conn.t() def create(conn, attrs) do with :ok <- permit(:create, conn.assigns, attrs), - originator <- Originator.extract(conn.assigns), - attrs <- Map.put(attrs, "originator", originator), + attrs <- Originator.set_in_attrs(attrs, conn.assigns), {:ok, transaction} <- TransactionGate.create(attrs) do transaction |> Orchestrator.one(TransactionOverlay, attrs) diff --git a/apps/admin_api/lib/admin_api/v1/controllers/transaction_request_controller.ex b/apps/admin_api/lib/admin_api/v1/controllers/transaction_request_controller.ex index 4a10d552e..5be1d0da6 100644 --- a/apps/admin_api/lib/admin_api/v1/controllers/transaction_request_controller.ex +++ b/apps/admin_api/lib/admin_api/v1/controllers/transaction_request_controller.ex @@ -3,7 +3,7 @@ defmodule AdminAPI.V1.TransactionRequestController do import AdminAPI.V1.ErrorHandler alias AdminAPI.V1.AccountHelper alias EWallet.TransactionRequestPolicy - alias EWallet.Web.{Orchestrator, Paginator, V1.TransactionRequestOverlay} + alias EWallet.Web.{Orchestrator, Originator, Paginator, V1.TransactionRequestOverlay} alias EWallet.{ TransactionRequestFetcher, @@ -82,6 +82,7 @@ defmodule AdminAPI.V1.TransactionRequestController do @spec create(Plug.Conn.t(), map()) :: Plug.Conn.t() def create(conn, attrs) do attrs + |> Map.put("originator", Originator.extract(conn.assigns)) |> Map.put("creator", conn.assigns) |> TransactionRequestGate.create() |> respond(conn) diff --git a/apps/admin_api/lib/admin_api/v1/controllers/user_auth_controller.ex b/apps/admin_api/lib/admin_api/v1/controllers/user_auth_controller.ex index 7f93a2c92..11b8b736d 100644 --- a/apps/admin_api/lib/admin_api/v1/controllers/user_auth_controller.ex +++ b/apps/admin_api/lib/admin_api/v1/controllers/user_auth_controller.ex @@ -2,7 +2,7 @@ defmodule AdminAPI.V1.UserAuthController do use AdminAPI, :controller import AdminAPI.V1.ErrorHandler alias EWallet.UserFetcher - alias EWallet.Web.{Orchestrator, V1.AuthTokenOverlay} + alias EWallet.Web.{Orchestrator, Originator, V1.AuthTokenOverlay} alias EWalletDB.{AuthToken, User} @doc """ @@ -31,7 +31,8 @@ defmodule AdminAPI.V1.UserAuthController do Invalidates the authentication token used in this request. """ def logout(conn, %{"auth_token" => auth_token}) do - auth_token |> AuthToken.expire(:ewallet_api) + originator = Originator.extract(conn.assigns) + AuthToken.expire(auth_token, :ewallet_api, originator) render(conn, :empty_response, %{}) end diff --git a/apps/admin_api/lib/admin_api/v1/controllers/user_controller.ex b/apps/admin_api/lib/admin_api/v1/controllers/user_controller.ex index 9803c0526..a19322bd9 100644 --- a/apps/admin_api/lib/admin_api/v1/controllers/user_controller.ex +++ b/apps/admin_api/lib/admin_api/v1/controllers/user_controller.ex @@ -118,8 +118,7 @@ defmodule AdminAPI.V1.UserController do when is_binary(id) and byte_size(id) > 0 do with %User{} = user <- User.get(id) || {:error, :unauthorized}, :ok <- permit(:update, conn.assigns, user), - originator <- Originator.extract(conn.assigns), - attrs <- Map.put(attrs, "originator", originator) do + attrs <- Originator.set_in_attrs(attrs, conn.assigns) do user |> User.update(attrs) |> respond_single(conn) @@ -138,8 +137,7 @@ defmodule AdminAPI.V1.UserController do when is_binary(id) and byte_size(id) > 0 do with %User{} = user <- User.get_by_provider_user_id(id) || {:error, :unauthorized}, :ok <- permit(:update, conn.assigns, user), - originator <- Originator.extract(conn.assigns), - attrs <- Map.put(attrs, "originator", originator) do + attrs <- Originator.set_in_attrs(attrs, conn.assigns) do user |> User.update(attrs) |> respond_single(conn) @@ -157,6 +155,7 @@ defmodule AdminAPI.V1.UserController do def enable_or_disable(conn, attrs) do with {:ok, %User{} = user} <- UserFetcher.fetch(attrs), :ok <- permit(:enable_or_disable, conn.assigns, user), + attrs <- Originator.set_in_attrs(attrs, conn.assigns), {:ok, updated} <- User.enable_or_disable(user, attrs), :ok <- AuthToken.expire_for_user(updated) do respond_single(updated, conn) diff --git a/apps/admin_api/lib/admin_api/v1/controllers/wallet_controller.ex b/apps/admin_api/lib/admin_api/v1/controllers/wallet_controller.ex index 8755e0d58..22811fca6 100644 --- a/apps/admin_api/lib/admin_api/v1/controllers/wallet_controller.ex +++ b/apps/admin_api/lib/admin_api/v1/controllers/wallet_controller.ex @@ -6,7 +6,7 @@ defmodule AdminAPI.V1.WalletController do import AdminAPI.V1.ErrorHandler alias AdminAPI.V1.AccountHelper alias EWallet.{UUIDFetcher, WalletPolicy} - alias EWallet.Web.{Orchestrator, Paginator, V1.WalletOverlay} + alias EWallet.Web.{Orchestrator, Originator, Paginator, V1.WalletOverlay} alias EWalletDB.{Account, User, Wallet} @doc """ @@ -102,7 +102,8 @@ defmodule AdminAPI.V1.WalletController do @spec create(Plug.Conn.t(), map()) :: Plug.Conn.t() def create(conn, attrs) do - with :ok <- permit(:create, conn.assigns, attrs) do + with :ok <- permit(:create, conn.assigns, attrs), + attrs <- Originator.set_in_attrs(attrs, conn.assigns) do attrs |> UUIDFetcher.replace_external_ids() |> Wallet.insert_secondary_or_burn() @@ -119,6 +120,7 @@ defmodule AdminAPI.V1.WalletController do def enable_or_disable(conn, %{"address" => address} = attrs) do with %Wallet{} = wallet <- Wallet.get(address) || {:error, :unauthorized}, :ok <- permit(:enable_or_disable, conn.assigns, wallet), + attrs <- Originator.set_in_attrs(attrs, conn.assigns), {:ok, updated} <- Wallet.enable_or_disable(wallet, attrs) do respond_single(updated, conn, attrs) else diff --git a/apps/admin_api/lib/admin_api/v1/helpers/account_helper.ex b/apps/admin_api/lib/admin_api/v1/helpers/account_helper.ex index 81970ebdb..277055ee1 100644 --- a/apps/admin_api/lib/admin_api/v1/helpers/account_helper.ex +++ b/apps/admin_api/lib/admin_api/v1/helpers/account_helper.ex @@ -2,7 +2,7 @@ defmodule AdminAPI.V1.AccountHelper do @moduledoc """ Simple helper module to access accounts from controllers. """ - alias EWalletConfig.Helpers.Assoc + alias Utils.Helpers.Assoc alias EWalletDB.{Account, AuthToken, Key, User} alias Plug.Conn diff --git a/apps/admin_api/lib/admin_api/v1/serializers/auth_token_serializer.ex b/apps/admin_api/lib/admin_api/v1/serializers/auth_token_serializer.ex index 5595d16ca..4f4c6cde6 100644 --- a/apps/admin_api/lib/admin_api/v1/serializers/auth_token_serializer.ex +++ b/apps/admin_api/lib/admin_api/v1/serializers/auth_token_serializer.ex @@ -3,7 +3,7 @@ defmodule AdminAPI.V1.AuthTokenSerializer do Serializes authentication token data into V1 response format. """ alias EWallet.Web.V1.{AccountSerializer, UserSerializer} - alias EWalletConfig.Helpers.Assoc + alias Utils.Helpers.Assoc alias EWalletDB.User def serialize(auth_token) do diff --git a/apps/admin_api/test/admin_api/v1/auth/admin_user_auth_test.exs b/apps/admin_api/test/admin_api/v1/auth/admin_user_auth_test.exs index 583ac3900..6e88d0113 100644 --- a/apps/admin_api/test/admin_api/v1/auth/admin_user_auth_test.exs +++ b/apps/admin_api/test/admin_api/v1/auth/admin_user_auth_test.exs @@ -2,6 +2,7 @@ defmodule AdminAPI.Web.V1.AdminUserAuthTest do use AdminAPI.ConnCase, async: true alias AdminAPI.V1.AdminUserAuth alias EWalletDB.{AuthToken, User} + alias ActivityLogger.System def auth_header(user_id, token) do encoded_key = Base.encode64(user_id <> ":" <> token) @@ -51,7 +52,7 @@ defmodule AdminAPI.Web.V1.AdminUserAuthTest do test "returns authenticated: false when given expired token", meta do auth_token = insert(:auth_token, user: meta.user, owner_app: "admin_api") - AuthToken.expire(auth_token.token, :admin_api) + AuthToken.expire(auth_token.token, :admin_api, %System{}) auth = auth_header(meta.user.id, auth_token.token) assert auth.authenticated == false diff --git a/apps/admin_api/test/admin_api/v1/auth/admin_user_authenticator_test.exs b/apps/admin_api/test/admin_api/v1/auth/admin_user_authenticator_test.exs index 22786fc16..3d0d29cca 100644 --- a/apps/admin_api/test/admin_api/v1/auth/admin_user_authenticator_test.exs +++ b/apps/admin_api/test/admin_api/v1/auth/admin_user_authenticator_test.exs @@ -1,8 +1,9 @@ defmodule AdminAPI.Web.V1.AdminUserAuthenticatorTest do use AdminAPI.ConnCase, async: true alias AdminAPI.V1.AdminUserAuthenticator - alias EWalletConfig.Helpers.Crypto alias EWalletDB.User + alias Utils.Helpers.Crypto + alias ActivityLogger.System describe "authenticate/3" do test "returns authenticated:true if email and password are valid" do @@ -54,7 +55,7 @@ defmodule AdminAPI.Web.V1.AdminUserAuthenticatorTest do test "returns authenticated:false if user is disabled" do admin = get_test_admin() - User.enable_or_disable(admin, %{enabled: false}) + User.enable_or_disable(admin, %{enabled: false, originator: %System{}}) conn = AdminUserAuthenticator.authenticate(build_conn(), @user_email, @password) assert_error(conn) end diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/account_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/account_controller_test.exs index 953e4328f..6d8e16c4b 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/account_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/account_controller_test.exs @@ -1,6 +1,7 @@ defmodule AdminAPI.V1.AdminAuth.AccountControllerTest do use AdminAPI.ConnCase, async: true alias EWalletDB.{Account, Membership, Repo, Role, User} + alias ActivityLogger.System describe "/account.all" do test "returns a list of accounts and pagination data" do @@ -48,7 +49,7 @@ defmodule AdminAPI.V1.AdminAuth.AccountControllerTest do test "returns a list of accounts that the current user can access" do master = Account.get_master_account() user = get_test_admin() - {:ok, _m} = Membership.unassign(user, master) + {:ok, _m} = Membership.unassign(user, master, %System{}) role = Role.get_by(name: "admin") @@ -60,7 +61,7 @@ defmodule AdminAPI.V1.AdminAuth.AccountControllerTest do # We add user to acc_2, so he should have access to # acc_2 and its descendants: acc_3, acc_4, acc_5 - {:ok, _m} = Membership.assign(user, acc_2, role) + {:ok, _m} = Membership.assign(user, acc_2, role, %System{}) response = admin_user_request("/account.all", %{}) accounts = response["data"]["data"] @@ -76,7 +77,7 @@ defmodule AdminAPI.V1.AdminAuth.AccountControllerTest do test "returns only one account if the user is at the last level" do master = Account.get_master_account() user = get_test_admin() - {:ok, _m} = Membership.unassign(user, master) + {:ok, _m} = Membership.unassign(user, master, %System{}) role = Role.get_by(name: "admin") @@ -86,7 +87,7 @@ defmodule AdminAPI.V1.AdminAuth.AccountControllerTest do _acc_4 = insert(:account, parent: acc_3, name: "Account 4") acc_5 = insert(:account, parent: acc_3, name: "Account 5") - {:ok, _m} = Membership.assign(user, acc_5, role) + {:ok, _m} = Membership.assign(user, acc_5, role, %System{}) response = admin_user_request("/account.all", %{}) accounts = response["data"]["data"] @@ -180,12 +181,12 @@ defmodule AdminAPI.V1.AdminAuth.AccountControllerTest do user = get_test_admin() role = Role.get_by(name: "admin") - {:ok, _m} = Membership.unassign(user, master) + {:ok, _m} = Membership.unassign(user, master, %System{}) accounts = insert_list(3, :account) # Pick the 2nd inserted account target = Enum.at(accounts, 1) - Membership.assign(user, target, role) + Membership.assign(user, target, role, %System{}) response = admin_user_request("/account.get", %{"id" => target.id}) assert response["success"] @@ -196,7 +197,7 @@ defmodule AdminAPI.V1.AdminAuth.AccountControllerTest do test "gets unauthorized if the user doesn't have access" do master = Account.get_master_account() user = get_test_admin() - {:ok, _m} = Membership.unassign(user, master) + {:ok, _m} = Membership.unassign(user, master, %System{}) accounts = insert_list(3, :account) # Pick the 2nd inserted account target = Enum.at(accounts, 1) diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/admin_auth_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/admin_auth_controller_test.exs index 9e55bcc6d..6a81ed0dc 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/admin_auth_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/admin_auth_controller_test.exs @@ -1,7 +1,7 @@ defmodule AdminAPI.V1.AdminAuth.AdminAuthControllerTest do use AdminAPI.ConnCase, async: true alias EWallet.Web.V1.{AccountSerializer, UserSerializer} - alias EWalletConfig.System + alias ActivityLogger.System alias EWalletDB.{Account, AuthToken, Membership, Repo, Role, User} describe "/admin.login" do @@ -31,7 +31,7 @@ defmodule AdminAPI.V1.AdminAuth.AdminAuthControllerTest do test "responds with a new auth token if credentials are valid but user is not master_admin" do user = get_test_admin() |> Repo.preload([:accounts]) - {:ok, _} = Membership.unassign(user, Enum.at(user.accounts, 0)) + {:ok, _} = Membership.unassign(user, Enum.at(user.accounts, 0), %System{}) account = insert(:account) role = Role.get_by(name: "admin") _membership = insert(:membership, %{user: user, role: role, account: account}) @@ -61,7 +61,7 @@ defmodule AdminAPI.V1.AdminAuth.AdminAuthControllerTest do test "responds with a new auth token if credentials are valid and user is a viewer" do user = get_test_admin() |> Repo.preload([:accounts]) - {:ok, _} = Membership.unassign(user, Enum.at(user.accounts, 0)) + {:ok, _} = Membership.unassign(user, Enum.at(user.accounts, 0), %System{}) account = insert(:account) role = insert(:role, %{name: "viewer"}) _membership = insert(:membership, %{user: user, role: role, account: account}) @@ -198,7 +198,7 @@ defmodule AdminAPI.V1.AdminAuth.AdminAuthControllerTest do test "returns a permission error when trying to switch to an invalid account" do user = get_test_admin() |> Repo.preload([:accounts]) - {:ok, _} = Membership.unassign(user, Enum.at(user.accounts, 0)) + {:ok, _} = Membership.unassign(user, Enum.at(user.accounts, 0), %System{}) account = insert(:account) response = diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/admin_user_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/admin_user_controller_test.exs index 3c255d977..7f68bf80f 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/admin_user_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/admin_user_controller_test.exs @@ -2,6 +2,7 @@ defmodule AdminAPI.V1.AdminAuth.AdminUserControllerTest do use AdminAPI.ConnCase, async: true alias Ecto.UUID alias EWalletDB.{User, Account, AuthToken, Role, Membership} + alias ActivityLogger.System @owner_app :some_app @@ -198,13 +199,13 @@ defmodule AdminAPI.V1.AdminAuth.AdminUserControllerTest do role = Role.get_by(name: "admin") admin = get_test_admin() - {:ok, _m} = Membership.unassign(admin, master) + {:ok, _m} = Membership.unassign(admin, master, %System{}) master_admin = insert(:admin, %{email: "admin@omise.co"}) _membership = insert(:membership, %{user: master_admin, account: master, role: role}) sub_acc = insert(:account, parent: master, name: "Account 1") - {:ok, _m} = Membership.assign(admin, sub_acc, role) + {:ok, _m} = Membership.assign(admin, sub_acc, role, %System{}) response = admin_user_request("/user.enable_or_disable", %{ diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/category_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/category_controller_test.exs index e8332ce69..60ee4cc86 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/category_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/category_controller_test.exs @@ -2,6 +2,7 @@ defmodule AdminAPI.V1.AdminAuth.CategoryControllerTest do use AdminAPI.ConnCase, async: true alias EWalletDB.Category alias EWalletDB.Helpers.Preloader + alias ActivityLogger.System describe "/category.all" do test "returns a list of categories and pagination data" do @@ -181,7 +182,10 @@ defmodule AdminAPI.V1.AdminAuth.CategoryControllerTest do {:ok, category} = :category |> insert() - |> Category.update(%{account_ids: [account.id]}) + |> Category.update(%{ + account_ids: [account.id], + originator: %System{} + }) response = admin_user_request("/category.delete", %{id: category.id}) diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/exchange_pair_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/exchange_pair_controller_test.exs index 6331b64ca..c6da35fe3 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/exchange_pair_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/exchange_pair_controller_test.exs @@ -1,6 +1,7 @@ defmodule AdminAPI.V1.AdminAuth.ExchangePairControllerTest do use AdminAPI.ConnCase, async: true alias EWalletDB.{ExchangePair, Repo} + alias ActivityLogger.System describe "/exchange_pair.all" do test "returns a list of exchange pairs and pagination data" do @@ -89,7 +90,8 @@ defmodule AdminAPI.V1.AdminAuth.ExchangePairControllerTest do from_token_id: insert(:token).id, to_token_id: insert(:token).id, rate: 2, - sync_opposite: false + sync_opposite: false, + originator: %System{} } end diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/invite_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/invite_controller_test.exs index efee28c7b..e4b16d5cb 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/invite_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/invite_controller_test.exs @@ -2,6 +2,7 @@ defmodule AdminAPI.V1.AdminAuth.InviteControllerTest do use AdminAPI.ConnCase, async: true alias EWallet.Web.Date alias EWalletDB.{Invite, User} + alias ActivityLogger.System defp request(email, token, password, password_confirmation) do unauthenticated_request("/invite.accept", %{ @@ -15,7 +16,7 @@ defmodule AdminAPI.V1.AdminAuth.InviteControllerTest do describe "InviteController.accept/2" do test "returns success if invite is accepted successfully" do {:ok, user} = :admin |> params_for(is_admin: false) |> User.insert() - {:ok, invite} = Invite.generate(user, preload: :user) + {:ok, invite} = Invite.generate(user, %System{}, preload: :user) response = request(invite.user.email, invite.token, "some_password", "some_password") @@ -48,7 +49,7 @@ defmodule AdminAPI.V1.AdminAuth.InviteControllerTest do test "returns :invite_not_found error if the email has not been invited" do {:ok, user} = :admin |> params_for() |> User.insert() - {:ok, invite} = Invite.generate(user) + {:ok, invite} = Invite.generate(user, %System{}) response = request("unknown@example.com", invite.token, "some_password", "some_password") @@ -62,7 +63,7 @@ defmodule AdminAPI.V1.AdminAuth.InviteControllerTest do test "returns :invite_not_found error if the token is incorrect" do {:ok, user} = :admin |> params_for() |> User.insert() - {:ok, _invite} = Invite.generate(user) + {:ok, _invite} = Invite.generate(user, %System{}) response = request(user.email, "wrong_token", "some_password", "some_password") @@ -76,7 +77,7 @@ defmodule AdminAPI.V1.AdminAuth.InviteControllerTest do test "returns client:invalid_parameter error if the password has less than 8 characters" do {:ok, user} = :admin |> params_for() |> User.insert() - {:ok, invite} = Invite.generate(user) + {:ok, invite} = Invite.generate(user, %System{}) response = request(user.email, invite.token, "short", "short") @@ -90,7 +91,7 @@ defmodule AdminAPI.V1.AdminAuth.InviteControllerTest do test "returns :invalid_parameter error if a required parameter is missing" do {:ok, user} = :admin |> params_for() |> User.insert() - {:ok, invite} = Invite.generate(user) + {:ok, invite} = Invite.generate(user, %System{}) # Missing passwords response = diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/mint_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/mint_controller_test.exs index 78a0a39b3..173397569 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/mint_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/mint_controller_test.exs @@ -4,6 +4,7 @@ defmodule AdminAPI.V1.AdminAuth.MintControllerTest do alias EWallet.Web.Date alias EWallet.Web.V1.{AccountSerializer, TokenSerializer, TransactionSerializer} alias EWalletDB.{Mint, Repo} + alias ActivityLogger.System describe "/token.get_mints" do test "returns a list of mints and pagination data" do @@ -14,7 +15,8 @@ defmodule AdminAPI.V1.AdminAuth.MintControllerTest do "idempotency_token" => "123", "token_id" => token.id, "amount" => 100_000, - "description" => "desc." + "description" => "desc.", + "originator" => %System{} }) inserted_mint = Repo.preload(inserted_mint, [:account, :token, :transaction]) @@ -24,7 +26,8 @@ defmodule AdminAPI.V1.AdminAuth.MintControllerTest do "idempotency_token" => "123", "token_id" => token.id, "amount" => 100_000, - "description" => "desc." + "description" => "desc.", + "originator" => %System{} }) response = diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/reset_password_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/reset_password_controller_test.exs index 4f63aca10..1e6770100 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/reset_password_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/reset_password_controller_test.exs @@ -2,7 +2,7 @@ defmodule AdminAPI.V1.AdminAuth.ResetPasswordControllerTest do use AdminAPI.ConnCase, async: true use Bamboo.Test alias EWallet.ForgetPasswordEmail - alias EWalletConfig.Helpers.Crypto + alias Utils.Helpers.Crypto alias EWalletDB.{ForgetPasswordRequest, Repo, User} @redirect_url "http://localhost:4000/reset_password?email={email}&token={token}" diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/role_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/role_controller_test.exs index 44409f186..382559f9c 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/role_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/role_controller_test.exs @@ -1,6 +1,7 @@ defmodule AdminAPI.V1.AdminAuth.RoleControllerTest do use AdminAPI.ConnCase, async: true alias EWalletDB.{Membership, Role} + alias ActivityLogger.System describe "/role.all" do test "returns a list of roles and pagination data" do @@ -159,7 +160,7 @@ defmodule AdminAPI.V1.AdminAuth.RoleControllerTest do user = insert(:admin) account = insert(:account) role = insert(:role, name: "test_role_not_empty") - {:ok, _membership} = Membership.assign(user, account, role) + {:ok, _membership} = Membership.assign(user, account, role, %System{}) users = role.id |> Role.get(preload: :users) |> Map.get(:users) assert Enum.count(users) > 0 diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/self_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/self_controller_test.exs index 1eae34e60..b48d5e3cb 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/self_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/self_controller_test.exs @@ -4,9 +4,10 @@ defmodule AdminAPI.V1.AdminAuth.SelfControllerTest do import Ecto.Query alias AdminAPI.UpdateEmailAddressEmail alias EWallet.Web.Date - alias EWalletConfig.Helpers.{Crypto, Assoc} alias EWalletDB.{Account, Membership, Repo, User} alias EWalletDB.{Account, Membership, Repo, UpdateEmailRequest, User} + alias Utils.Helpers.{Crypto, Assoc} + alias ActivityLogger.System @update_email_url "http://localhost:4000/update_email?email={email}&token={token}" @@ -498,7 +499,7 @@ defmodule AdminAPI.V1.AdminAuth.SelfControllerTest do # Clear all memberships for this user then add just one for precision Repo.delete_all(from(m in Membership, where: m.user_uuid == ^user.uuid)) - Membership.assign(user, account, "admin") + Membership.assign(user, account, "admin", %System{}) assert admin_user_request("/me.get_accounts") == %{ diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/token_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/token_controller_test.exs index 0aec56fa1..008ab1bcd 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/token_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/token_controller_test.exs @@ -2,6 +2,7 @@ defmodule AdminAPI.V1.AdminAuth.TokenControllerTest do use AdminAPI.ConnCase, async: true alias EWallet.Web.V1.TokenSerializer alias EWalletDB.{Mint, Repo, Token} + alias ActivityLogger.System describe "/token.all" do test "returns a list of tokens and pagination data" do @@ -360,7 +361,8 @@ defmodule AdminAPI.V1.AdminAuth.TokenControllerTest do end test "Raises invalid_parameter error if id is missing" do - response = admin_user_request("/token.enable_or_disable", %{enabled: false}) + response = + admin_user_request("/token.enable_or_disable", %{enabled: false, originator: %System{}}) refute response["success"] diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/transaction_consumption_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/transaction_consumption_controller_test.exs index e8e849e07..2e5121a7f 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/transaction_consumption_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/transaction_consumption_controller_test.exs @@ -27,7 +27,7 @@ defmodule AdminAPI.V1.AdminAuth.TransactionConsumptionControllerTest do alias AdminAPI.V1.Endpoint alias EWallet.TransactionConsumptionScheduler - alias EWalletConfig.System + alias ActivityLogger.System setup do {:ok, _} = TestEndpoint.start_link() @@ -1482,7 +1482,7 @@ defmodule AdminAPI.V1.AdminAuth.TransactionConsumptionControllerTest do amount: 100_000 * meta.token.subunit_to_unit ) - {:ok, token} = Token.enable_or_disable(meta.token, %{enabled: false}) + {:ok, token} = Token.enable_or_disable(meta.token, %{enabled: false, originator: %System{}}) response = admin_user_request("/transaction_request.consume", %{ @@ -1505,10 +1505,11 @@ defmodule AdminAPI.V1.AdminAuth.TransactionConsumptionControllerTest do Wallet.insert_secondary_or_burn(%{ "account_uuid" => meta.account.uuid, "name" => "MySecondary", - "identifier" => "secondary" + "identifier" => "secondary", + "originator" => %System{} }) - {:ok, wallet} = Wallet.enable_or_disable(wallet, %{enabled: false}) + {:ok, wallet} = Wallet.enable_or_disable(wallet, %{enabled: false, originator: %System{}}) transaction_request = insert( diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/transaction_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/transaction_controller_test.exs index 236a3f482..978005399 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/transaction_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/transaction_controller_test.exs @@ -2,6 +2,7 @@ defmodule AdminAPI.V1.AdminAuth.TransactionControllerTest do use AdminAPI.ConnCase, async: true alias EWallet.TransactionGate alias EWalletDB.{Account, Repo, Token, Transaction, User} + alias ActivityLogger.System # credo:disable-for-next-line setup do @@ -23,7 +24,8 @@ defmodule AdminAPI.V1.AdminAuth.TransactionControllerTest do "to_address" => wallet_2.address, "amount" => 1, "token_id" => token.id, - "idempotency_token" => "1231" + "idempotency_token" => "1231", + "originator" => %System{} }) assert transaction_1.status == "confirmed" @@ -37,7 +39,8 @@ defmodule AdminAPI.V1.AdminAuth.TransactionControllerTest do "to_address" => wallet_1.address, "amount" => 1, "token_id" => token.id, - "idempotency_token" => "1232" + "idempotency_token" => "1232", + "originator" => %System{} }) assert transaction_2.status == "confirmed" @@ -48,7 +51,8 @@ defmodule AdminAPI.V1.AdminAuth.TransactionControllerTest do "to_address" => wallet_3.address, "amount" => 1, "token_id" => token.id, - "idempotency_token" => "1233" + "idempotency_token" => "1233", + "originator" => %System{} }) assert transaction_3.status == "confirmed" @@ -74,7 +78,8 @@ defmodule AdminAPI.V1.AdminAuth.TransactionControllerTest do "to_address" => wallet_2.address, "amount" => 1, "token_id" => token.id, - "idempotency_token" => "1237" + "idempotency_token" => "1237", + "originator" => %System{} }) assert transaction_7.status == "confirmed" @@ -795,7 +800,11 @@ defmodule AdminAPI.V1.AdminAuth.TransactionControllerTest do amount: 2_000_000 }) - {:ok, token} = Token.enable_or_disable(token, %{enabled: false}) + {:ok, token} = + Token.enable_or_disable(token, %{ + enabled: false, + originator: %System{} + }) response = admin_user_request("/transaction.create", %{ diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/transaction_request_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/transaction_request_controller_test.exs index 9691d0f90..4bdce497c 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/transaction_request_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/transaction_request_controller_test.exs @@ -3,7 +3,7 @@ defmodule AdminAPI.V1.AdminAuth.TransactionRequestControllerTest do alias EWallet.Web.Date alias EWallet.Web.V1.{AccountSerializer, TokenSerializer, UserSerializer, WalletSerializer} alias EWalletDB.{Account, AccountUser, Repo, TransactionRequest, User, Wallet} - alias EWalletConfig.System + alias ActivityLogger.System describe "/transaction_request.all" do setup do @@ -495,10 +495,11 @@ defmodule AdminAPI.V1.AdminAuth.TransactionRequestControllerTest do Wallet.insert_secondary_or_burn(%{ "account_uuid" => account.uuid, "name" => "MySecondary", - "identifier" => "secondary" + "identifier" => "secondary", + "originator" => %System{} }) - {:ok, wallet} = Wallet.enable_or_disable(wallet, %{enabled: false}) + {:ok, wallet} = Wallet.enable_or_disable(wallet, %{enabled: false, originator: %System{}}) response = admin_user_request("/transaction_request.create", %{ diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/user_auth_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/user_auth_controller_test.exs index 3aeb7ca18..34e745c63 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/user_auth_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/user_auth_controller_test.exs @@ -1,6 +1,7 @@ defmodule AdminAPI.V1.AdminAuth.UserAuthControllerTest do use AdminAPI.ConnCase, async: true alias EWalletDB.{AuthToken, User} + alias ActivityLogger.System describe "/user.login" do test "responds with a new auth token if id is valid" do @@ -80,7 +81,7 @@ defmodule AdminAPI.V1.AdminAuth.UserAuthControllerTest do end test "returns user:disabled if the user is disabled" do - user = insert(:user, %{enabled: false}) + user = insert(:user, %{enabled: false, originator: %System{}}) response = admin_user_request("/user.login", %{id: user.id}) expected = %{ diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/user_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/user_controller_test.exs index 178feec23..ba97d7c27 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/user_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/user_controller_test.exs @@ -2,7 +2,7 @@ defmodule AdminAPI.V1.AdminAuth.UserControllerTest do use AdminAPI.ConnCase, async: true alias EWallet.Web.Date alias EWalletDB.{Account, AccountUser, User, AuthToken, Role, Membership} - alias EWalletConfig.System + alias ActivityLogger.System @owner_app :some_app @@ -573,13 +573,13 @@ defmodule AdminAPI.V1.AdminAuth.UserControllerTest do role = Role.get_by(name: "admin") admin = get_test_admin() - {:ok, _m} = Membership.unassign(admin, master) + {:ok, _m} = Membership.unassign(admin, master, %System{}) user = insert(:user, %{enabled: true}) {:ok, _} = AccountUser.link(master.uuid, user.uuid, %System{}) sub_acc = insert(:account, parent: master, name: "Account 1") - {:ok, _m} = Membership.assign(admin, sub_acc, role) + {:ok, _m} = Membership.assign(admin, sub_acc, role, %System{}) response = admin_user_request("/user.enable_or_disable", %{ diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/wallet_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/wallet_controller_test.exs index 45a2d6214..73dc5761e 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/wallet_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/wallet_controller_test.exs @@ -3,7 +3,7 @@ defmodule AdminAPI.V1.AdminAuth.WalletControllerTest do alias EWallet.Web.Date alias EWallet.Web.V1.UserSerializer alias EWalletDB.{Account, AccountUser, Repo, Token, User, Wallet} - alias EWalletConfig.System + alias ActivityLogger.System describe "/wallet.all" do test "returns a list of wallets and pagination data" do @@ -571,7 +571,8 @@ defmodule AdminAPI.V1.AdminAuth.WalletControllerTest do Wallet.insert_secondary_or_burn(%{ "account_uuid" => account.uuid, "name" => "MyBurn", - "identifier" => "burn" + "identifier" => "burn", + "originator" => %System{} }) response = @@ -592,7 +593,8 @@ defmodule AdminAPI.V1.AdminAuth.WalletControllerTest do Wallet.insert_secondary_or_burn(%{ "account_uuid" => account.uuid, "name" => "MySecondary", - "identifier" => "secondary" + "identifier" => "secondary", + "originator" => %System{} }) response = diff --git a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/category_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/category_controller_test.exs index a15bddf3b..978ce65d0 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/category_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/category_controller_test.exs @@ -2,6 +2,7 @@ defmodule AdminAPI.V1.ProviderAuth.CategoryControllerTest do use AdminAPI.ConnCase, async: true alias EWalletDB.Category alias EWalletDB.Helpers.Preloader + alias ActivityLogger.System describe "/category.all" do test "returns a list of categories and pagination data" do @@ -181,7 +182,10 @@ defmodule AdminAPI.V1.ProviderAuth.CategoryControllerTest do {:ok, category} = :category |> insert() - |> Category.update(%{account_ids: [account.id]}) + |> Category.update(%{ + originator: %System{}, + account_ids: [account.id] + }) response = admin_user_request("/category.delete", %{id: category.id}) diff --git a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/mint_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/mint_controller_test.exs index 31cbaa101..7a2d5ac2a 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/mint_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/mint_controller_test.exs @@ -4,6 +4,7 @@ defmodule AdminAPI.V1.ProviderAuth.MintControllerTest do alias EWallet.Web.Date alias EWallet.Web.V1.{AccountSerializer, TokenSerializer, TransactionSerializer} alias EWalletDB.{Mint, Repo} + alias ActivityLogger.System describe "/token.get_mints" do test "returns a list of mints and pagination data" do @@ -14,7 +15,8 @@ defmodule AdminAPI.V1.ProviderAuth.MintControllerTest do "idempotency_token" => "123", "token_id" => token.id, "amount" => 100_000, - "description" => "desc." + "description" => "desc.", + "originator" => %System{} }) inserted_mint = Repo.preload(inserted_mint, [:account, :token, :transaction]) @@ -24,7 +26,8 @@ defmodule AdminAPI.V1.ProviderAuth.MintControllerTest do "idempotency_token" => "123", "token_id" => token.id, "amount" => 100_000, - "description" => "desc." + "description" => "desc.", + "originator" => %System{} }) response = diff --git a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/role_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/role_controller_test.exs index ba5ce5671..a1e63e6aa 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/role_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/role_controller_test.exs @@ -1,6 +1,7 @@ defmodule AdminAPI.V1.ProviderAuth.RoleControllerTest do use AdminAPI.ConnCase, async: true alias EWalletDB.{Membership, Role} + alias ActivityLogger.System describe "/role.all" do test "returns a list of roles and pagination data" do @@ -159,7 +160,7 @@ defmodule AdminAPI.V1.ProviderAuth.RoleControllerTest do user = insert(:admin) account = insert(:account) role = insert(:role, name: "test_role_not_empty") - {:ok, _membership} = Membership.assign(user, account, role) + {:ok, _membership} = Membership.assign(user, account, role, %System{}) users = role.id |> Role.get(preload: :users) |> Map.get(:users) assert Enum.count(users) > 0 diff --git a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/self_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/self_controller_test.exs index e3621f607..799941f50 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/self_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/self_controller_test.exs @@ -2,6 +2,7 @@ defmodule AdminAPI.V1.ProviderAuth.SelfControllerTest do use AdminAPI.ConnCase, async: true import Ecto.Query alias EWalletDB.{Membership, Repo} + alias ActivityLogger.System @update_email_url "http://localhost:4000/update_email?email={email}&token={token}" @@ -92,7 +93,7 @@ defmodule AdminAPI.V1.ProviderAuth.SelfControllerTest do # Clear all memberships for this user then add just one for precision Repo.delete_all(from(m in Membership, where: m.user_uuid == ^user.uuid)) - Membership.assign(user, account, "admin") + Membership.assign(user, account, "admin", %System{}) response = provider_request("/me.get_accounts") diff --git a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/transaction_consumption_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/transaction_consumption_controller_test.exs index cdb801f4c..780f2849d 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/transaction_consumption_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/transaction_consumption_controller_test.exs @@ -25,7 +25,7 @@ defmodule AdminAPI.V1.ProviderAuth.TransactionConsumptionControllerTest do alias AdminAPI.V1.Endpoint alias EWallet.TransactionConsumptionScheduler - alias EWalletConfig.System + alias ActivityLogger.System setup do {:ok, _} = TestEndpoint.start_link() diff --git a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/transaction_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/transaction_controller_test.exs index cf1899c17..33cddc1ad 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/transaction_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/transaction_controller_test.exs @@ -2,6 +2,7 @@ defmodule AdminAPI.V1.ProviderAuth.TransactionControllerTest do use AdminAPI.ConnCase, async: true alias EWallet.TransactionGate alias EWalletDB.{Account, Repo, Transaction, User} + alias ActivityLogger.System # credo:disable-for-next-line setup do @@ -23,7 +24,8 @@ defmodule AdminAPI.V1.ProviderAuth.TransactionControllerTest do "to_address" => wallet_2.address, "amount" => 1, "token_id" => token.id, - "idempotency_token" => "1231" + "idempotency_token" => "1231", + "originator" => %System{} }) assert transaction_1.status == "confirmed" @@ -37,7 +39,8 @@ defmodule AdminAPI.V1.ProviderAuth.TransactionControllerTest do "to_address" => wallet_1.address, "amount" => 1, "token_id" => token.id, - "idempotency_token" => "1232" + "idempotency_token" => "1232", + "originator" => %System{} }) assert transaction_2.status == "confirmed" @@ -48,7 +51,8 @@ defmodule AdminAPI.V1.ProviderAuth.TransactionControllerTest do "to_address" => wallet_3.address, "amount" => 1, "token_id" => token.id, - "idempotency_token" => "1233" + "idempotency_token" => "1233", + "originator" => %System{} }) assert transaction_3.status == "confirmed" @@ -74,7 +78,8 @@ defmodule AdminAPI.V1.ProviderAuth.TransactionControllerTest do "to_address" => wallet_2.address, "amount" => 1, "token_id" => token.id, - "idempotency_token" => "1237" + "idempotency_token" => "1237", + "originator" => %System{} }) assert transaction_7.status == "confirmed" diff --git a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/transaction_request_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/transaction_request_controller_test.exs index 8df686638..bda1e8b50 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/transaction_request_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/transaction_request_controller_test.exs @@ -2,7 +2,7 @@ defmodule AdminAPI.V1.ProviderAuth.TransactionRequestControllerTest do use AdminAPI.ConnCase, async: true alias EWallet.Web.{Date, V1.TokenSerializer, V1.UserSerializer} alias EWalletDB.{Account, AccountUser, Repo, TransactionRequest, User} - alias EWalletConfig.System + alias ActivityLogger.System describe "/transaction_request.all" do setup do diff --git a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/user_auth_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/user_auth_controller_test.exs index 921672c8b..cb19b127f 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/user_auth_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/user_auth_controller_test.exs @@ -1,6 +1,7 @@ defmodule AdminAPI.V1.ProviderAuth.UserAuthControllerTest do use AdminAPI.ConnCase, async: true alias EWalletDB.{AuthToken, User} + alias ActivityLogger.System describe "/user.login" do test "responds with a new auth token if id is valid" do @@ -80,7 +81,7 @@ defmodule AdminAPI.V1.ProviderAuth.UserAuthControllerTest do end test "returns user:disabled if the user is disabled" do - user = insert(:user, %{enabled: false}) + user = insert(:user, %{enabled: false, originator: %System{}}) response = provider_request("/user.login", %{id: user.id}) expected = %{ diff --git a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/user_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/user_controller_test.exs index 0760b3fd3..7851bf8e3 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/user_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/user_controller_test.exs @@ -2,7 +2,7 @@ defmodule AdminAPI.V1.ProviderAuth.UserControllerTest do use AdminAPI.ConnCase, async: true alias EWallet.Web.Date alias EWalletDB.{Account, AccountUser, User, AuthToken} - alias EWalletConfig.System + alias ActivityLogger.System @owner_app :some_app diff --git a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/wallet_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/wallet_controller_test.exs index afb049694..3dee82811 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/wallet_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/wallet_controller_test.exs @@ -3,7 +3,7 @@ defmodule AdminAPI.V1.ProviderAuth.WalletControllerTest do alias EWallet.Web.Date alias EWallet.Web.V1.UserSerializer alias EWalletDB.{Account, AccountUser, Repo, Token, User, Wallet} - alias EWalletConfig.System + alias ActivityLogger.System describe "/wallet.all" do test "returns a list of wallets and pagination data" do diff --git a/apps/admin_api/test/admin_api/v1/views/transaction_view_test.exs b/apps/admin_api/test/admin_api/v1/views/transaction_view_test.exs index 43ee150ec..76635d7c4 100644 --- a/apps/admin_api/test/admin_api/v1/views/transaction_view_test.exs +++ b/apps/admin_api/test/admin_api/v1/views/transaction_view_test.exs @@ -2,7 +2,7 @@ defmodule AdminAPI.V1.TransactionViewTest do use AdminAPI.ViewCase, :v1 alias AdminAPI.V1.TransactionView alias EWallet.Web.{Date, Paginator, V1.AccountSerializer, V1.TokenSerializer, V1.UserSerializer} - alias EWalletConfig.Helpers.Assoc + alias Utils.Helpers.Assoc describe "AdminAPI.V1.TransactionView.render/2" do test "renders transaction.json with correct response structure" do diff --git a/apps/admin_api/test/support/channel_case.ex b/apps/admin_api/test/support/channel_case.ex index 254ca5d95..38ad3844b 100644 --- a/apps/admin_api/test/support/channel_case.ex +++ b/apps/admin_api/test/support/channel_case.ex @@ -33,11 +33,13 @@ defmodule AdminAPI.ChannelCase do :ok = Sandbox.checkout(EWalletDB.Repo) :ok = Sandbox.checkout(LocalLedgerDB.Repo) :ok = Sandbox.checkout(EWalletConfig.Repo) + :ok = Sandbox.checkout(ActivityLogger.Repo) unless tags[:async] do Sandbox.mode(EWalletConfig.Repo, {:shared, self()}) Sandbox.mode(EWalletDB.Repo, {:shared, self()}) Sandbox.mode(LocalLedgerDB.Repo, {:shared, self()}) + Sandbox.mode(ActivityLogger.Repo, {:shared, self()}) end pid = diff --git a/apps/admin_api/test/support/conn_case.ex b/apps/admin_api/test/support/conn_case.ex index 8fb282ab5..42d11146a 100644 --- a/apps/admin_api/test/support/conn_case.ex +++ b/apps/admin_api/test/support/conn_case.ex @@ -20,8 +20,10 @@ defmodule AdminAPI.ConnCase do alias Ecto.UUID alias EWallet.{MintGate, TransactionGate} alias EWallet.Web.Date - alias EWalletConfig.{ConfigTestHelper, Helpers.Crypto, Types.ExternalID} + alias EWalletConfig.ConfigTestHelper alias EWalletDB.{Account, Key, Repo, User} + alias ActivityLogger.System + alias Utils.{Types.ExternalID, Helpers.Crypto} # Attributes required by Phoenix.ConnTest @endpoint AdminAPI.Endpoint @@ -85,11 +87,13 @@ defmodule AdminAPI.ConnCase do :ok = Sandbox.checkout(EWalletDB.Repo) :ok = Sandbox.checkout(LocalLedgerDB.Repo) :ok = Sandbox.checkout(EWalletConfig.Repo) + :ok = Sandbox.checkout(ActivityLogger.Repo) unless tags[:async] do Sandbox.mode(EWalletConfig.Repo, {:shared, self()}) Sandbox.mode(EWalletDB.Repo, {:shared, self()}) Sandbox.mode(LocalLedgerDB.Repo, {:shared, self()}) + Sandbox.mode(ActivityLogger.Repo, {:shared, self()}) end pid = @@ -177,14 +181,15 @@ defmodule AdminAPI.ConnCase do |> Repo.one() end - def mint!(token, amount \\ 1_000_000) do + def mint!(token, amount \\ 1_000_000, originator \\ %System{}) do {:ok, mint, _transaction} = MintGate.insert(%{ "idempotency_token" => UUID.generate(), "token_id" => token.id, "amount" => amount * token.subunit_to_unit, "description" => "Minting #{amount} #{token.symbol}", - "metadata" => %{} + "metadata" => %{}, + "originator" => originator }) assert mint.confirmed == true @@ -214,7 +219,7 @@ defmodule AdminAPI.ConnCase do ) end - def transfer!(from, to, token, amount) do + def transfer!(from, to, token, amount, originator \\ %System{}) do {:ok, transaction} = TransactionGate.create(%{ "from_address" => from, @@ -222,7 +227,8 @@ defmodule AdminAPI.ConnCase do "token_id" => token.id, "amount" => amount, "metadata" => %{}, - "idempotency_token" => UUID.generate() + "idempotency_token" => UUID.generate(), + "originator" => originator }) transaction diff --git a/apps/admin_api/test/support/view_case.ex b/apps/admin_api/test/support/view_case.ex index 74f9b15aa..eb6131e90 100644 --- a/apps/admin_api/test/support/view_case.ex +++ b/apps/admin_api/test/support/view_case.ex @@ -14,6 +14,7 @@ defmodule AdminAPI.ViewCase do setup do :ok = Sandbox.checkout(Repo) + :ok = Sandbox.checkout(ActivityLogger.Repo) end # The expected response version diff --git a/apps/admin_panel/test/admin_panel/controllers/page_controller_test.exs b/apps/admin_panel/test/admin_panel/controllers/page_controller_test.exs index 4be6db968..f289a65f5 100644 --- a/apps/admin_panel/test/admin_panel/controllers/page_controller_test.exs +++ b/apps/admin_panel/test/admin_panel/controllers/page_controller_test.exs @@ -4,12 +4,14 @@ defmodule AdminPanel.PageControllerTest do import EWalletDB.Factory alias Ecto.Adapters.SQL.Sandbox alias EWalletDB.APIKey + alias ActivityLogger.System # Attributes required by Phoenix.ConnTest @endpoint AdminPanel.Endpoint setup do Sandbox.checkout(EWalletDB.Repo) + Sandbox.checkout(ActivityLogger.Repo) end describe "GET request to /admin" do @@ -25,7 +27,12 @@ defmodule AdminPanel.PageControllerTest do test "returns the main front-end app with the API key" do _account = insert(:account) - {:ok, api_key} = APIKey.insert(%{owner_app: "admin_api"}) + + {:ok, api_key} = + APIKey.insert(%{ + owner_app: "admin_api", + originator: %System{} + }) response = build_conn() From 2e19f73a20a7391739bbc8c35d14f67c22c2475d Mon Sep 17 00:00:00 2001 From: Thibault Denizet Date: Mon, 3 Dec 2018 16:26:32 +0700 Subject: [PATCH 10/23] Add ActivityLogger logic to EWalletConfig --- apps/activity_logger/config/config.exs | 4 +- .../controllers/configuration_controller.ex | 23 +- .../test/ewallet/web/paginator_test.exs | 10 +- .../test/ewallet/web/url_validator_test.exs | 8 +- .../test/ewallet_api/config_test.exs | 19 +- .../v1/plugs/standalone_plug_test.exs | 19 +- .../lib/ewallet_config/application.ex | 4 + .../lib/ewallet_config/config.ex | 12 +- .../ewallet_config/lib/ewallet_config/repo.ex | 1 + .../lib/ewallet_config/setting.ex | 70 +++++-- .../lib/ewallet_config}/storage/local.ex | 2 +- .../lib/ewallet_config/stored_setting.ex | 39 ++-- .../lib/ewallet_config/types/map.ex | 5 + .../lib/ewallet_config/vault.ex | 2 +- apps/ewallet_config/mix.exs | 5 +- .../test/ewallet_config/config_test.exs | 52 ++++- .../ewallet_config/setting_loader_test.exs | 42 +++- .../test/ewallet_config/setting_test.exs | 197 ++++++++++++------ .../test/support/config_test_helper.ex | 4 +- .../test/support/schema_case.ex | 1 + apps/ewallet_db/lib/ewallet_db/application.ex | 1 - 21 files changed, 389 insertions(+), 131 deletions(-) rename apps/{utils/lib => ewallet_config/lib/ewallet_config}/storage/local.ex (97%) create mode 100644 apps/ewallet_config/lib/ewallet_config/types/map.ex diff --git a/apps/activity_logger/config/config.exs b/apps/activity_logger/config/config.exs index f89a7a416..b65978b1d 100644 --- a/apps/activity_logger/config/config.exs +++ b/apps/activity_logger/config/config.exs @@ -3,6 +3,8 @@ use Mix.Config config :activity_logger, - ecto_repos: [ActivityLogger.Repo] + ecto_repos: [ActivityLogger.Repo], + schemas_to_activity_log_types: %{ActivityLogger.System => "system"}, + activity_log_types_to_schemas: %{"system" => ActivityLogger.System} import_config "#{Mix.env()}.exs" diff --git a/apps/admin_api/lib/admin_api/v1/controllers/configuration_controller.ex b/apps/admin_api/lib/admin_api/v1/controllers/configuration_controller.ex index c4877f195..4f564d80e 100644 --- a/apps/admin_api/lib/admin_api/v1/controllers/configuration_controller.ex +++ b/apps/admin_api/lib/admin_api/v1/controllers/configuration_controller.ex @@ -2,7 +2,7 @@ defmodule AdminAPI.V1.ConfigurationController do use AdminAPI, :controller import AdminAPI.V1.ErrorHandler - alias EWallet.Web.{Orchestrator, V1.SettingOverlay} + alias EWallet.Web.{Orchestrator, Originator, V1.SettingOverlay} alias EWalletConfig.{Config, Repo} def get(conn, attrs) do @@ -14,11 +14,30 @@ defmodule AdminAPI.V1.ConfigurationController do end def update(conn, attrs) do - with {:ok, settings} <- Config.update(attrs) do + with attrs <- put_originator(conn, attrs), + {:ok, settings} <- Config.update(attrs) do render(conn, :settings_with_errors, %{settings: settings}) else {:error, code} -> handle_error(conn, code) end end + + defp put_originator(conn, attrs) when is_list(attrs) do + originator = Originator.extract(conn.assigns) + + case Keyword.keyword?(attrs) do + true -> + [{:originator, originator} | attrs] + + false -> + Enum.map(attrs, fn setting_map -> + Map.put(setting_map, :originator, originator) + end) + end + end + + defp put_originator(conn, attrs) when is_map(attrs) do + Map.put(attrs, :originator, Originator.extract(conn.assigns)) + end end diff --git a/apps/ewallet/test/ewallet/web/paginator_test.exs b/apps/ewallet/test/ewallet/web/paginator_test.exs index 5c039d4a8..7fa390267 100644 --- a/apps/ewallet/test/ewallet/web/paginator_test.exs +++ b/apps/ewallet/test/ewallet/web/paginator_test.exs @@ -4,6 +4,7 @@ defmodule EWallet.Web.PaginatorTest do alias EWallet.Web.Paginator alias EWalletConfig.Config alias EWalletDB.{Account, Repo} + alias ActivityLogger.System describe "EWallet.Web.Paginator.paginate_attrs/2" do test "paginates with default values if attrs not given" do @@ -46,7 +47,14 @@ defmodule EWallet.Web.PaginatorTest do end test "returns per_page but never greater than the system's _defined_ maximum", meta do - {:ok, _setting} = Config.update([max_per_page: 20], meta[:config_pid]) + {:ok, [max_per_page: {:ok, _}]} = + Config.update( + [ + max_per_page: 20, + originator: %System{} + ], + meta[:config_pid] + ) paginator = Paginator.paginate_attrs(Account, %{"per_page" => 100}) assert paginator.pagination.per_page == 20 diff --git a/apps/ewallet/test/ewallet/web/url_validator_test.exs b/apps/ewallet/test/ewallet/web/url_validator_test.exs index e984e0bb9..83fc9c14e 100644 --- a/apps/ewallet/test/ewallet/web/url_validator_test.exs +++ b/apps/ewallet/test/ewallet/web/url_validator_test.exs @@ -2,12 +2,16 @@ defmodule EWallet.Web.UrlValidatorTest do use EWallet.DBCase alias EWallet.Web.UrlValidator alias EWalletConfig.Config + alias ActivityLogger.System describe "allowed_redirect_url?/1" do test "returns true if the given url has the whitelisted prefix", meta do - {:ok, _settings} = + {:ok, [redirect_url_prefixes: {:ok, _}]} = Config.update( - [redirect_url_prefixes: ["http://test_redirect_prefix"]], + [ + redirect_url_prefixes: ["http://test_redirect_prefix"], + originator: %System{} + ], meta[:config_pid] ) diff --git a/apps/ewallet_api/test/ewallet_api/config_test.exs b/apps/ewallet_api/test/ewallet_api/config_test.exs index ebd4077a4..88b985f74 100644 --- a/apps/ewallet_api/test/ewallet_api/config_test.exs +++ b/apps/ewallet_api/test/ewallet_api/config_test.exs @@ -1,10 +1,18 @@ defmodule EWalletAPI.ConfigTest do use EWalletAPI.ConnCase, async: true alias EWalletConfig.Config + alias ActivityLogger.System describe "ewallet_api.enable_standalone" do test "allows /user.signup when configured to true", meta do - {:ok, _} = Config.update(%{enable_standalone: true}, meta[:config_pid]) + {:ok, [enable_standalone: {:ok, _}]} = + Config.update( + %{ + enable_standalone: true, + originator: %System{} + }, + meta[:config_pid] + ) response = client_request("/user.signup") @@ -14,7 +22,14 @@ defmodule EWalletAPI.ConfigTest do end test "prohibits /user.signup when configured to false", meta do - {:ok, _} = Config.update(%{enable_standalone: false}, meta[:config_pid]) + {:ok, [enable_standalone: {:ok, _}]} = + Config.update( + %{ + enable_standalone: false, + originator: %System{} + }, + meta[:config_pid] + ) response = client_request("/user.signup") diff --git a/apps/ewallet_api/test/ewallet_api/v1/plugs/standalone_plug_test.exs b/apps/ewallet_api/test/ewallet_api/v1/plugs/standalone_plug_test.exs index 771aba22d..683f1a168 100644 --- a/apps/ewallet_api/test/ewallet_api/v1/plugs/standalone_plug_test.exs +++ b/apps/ewallet_api/test/ewallet_api/v1/plugs/standalone_plug_test.exs @@ -2,17 +2,32 @@ defmodule EWalletAPI.V1.StandalonePlugTest do use EWalletAPI.ConnCase, async: true alias EWalletAPI.V1.StandalonePlug alias EWalletConfig.Config + alias ActivityLogger.System describe "call/2" do test "does not halt if ewallet_api.enable_standalone is true", meta do - {:ok, _} = Config.update(%{enable_standalone: true}, meta[:config_pid]) + {:ok, [enable_standalone: {:ok, _}]} = + Config.update( + %{ + enable_standalone: true, + originator: %System{} + }, + meta[:config_pid] + ) conn = StandalonePlug.call(build_conn(), []) refute conn.halted end test "halts if ewallet_api.enable_standalone is false", meta do - {:ok, _} = Config.update(%{enable_standalone: false}, meta[:config_pid]) + {:ok, [enable_standalone: {:ok, _}]} = + Config.update( + %{ + enable_standalone: false, + originator: %System{} + }, + meta[:config_pid] + ) conn = StandalonePlug.call(build_conn(), []) assert conn.halted diff --git a/apps/ewallet_config/lib/ewallet_config/application.ex b/apps/ewallet_config/lib/ewallet_config/application.ex index 84295cdd4..b7e7b7f55 100644 --- a/apps/ewallet_config/lib/ewallet_config/application.ex +++ b/apps/ewallet_config/lib/ewallet_config/application.ex @@ -9,6 +9,10 @@ defmodule EWalletConfig.Application do import Supervisor.Spec DeferredConfig.populate(:ewallet_config) + ActivityLogger.configure(%{ + EWalletConfig.StoredSetting => "setting" + }) + # List all child processes to be supervised children = [ # Starts a worker by calling: EWalletConfig.Worker.start_link(arg) diff --git a/apps/ewallet_config/lib/ewallet_config/config.ex b/apps/ewallet_config/lib/ewallet_config/config.ex index 1926f8bb6..dd8366247 100644 --- a/apps/ewallet_config/lib/ewallet_config/config.ex +++ b/apps/ewallet_config/lib/ewallet_config/config.ex @@ -15,7 +15,7 @@ defmodule EWalletConfig.Config do SettingLoader } - @spec start_link(Map.t()) :: {:ok, pid()} | {:error, Atom.t()} + @spec start_link(map()) :: {:ok, pid()} | {:error, Atom.t()} def start_link(named: true) do GenServer.start_link(__MODULE__, [], name: __MODULE__) end @@ -30,7 +30,7 @@ defmodule EWalletConfig.Config do GenServer.stop(pid) end - @spec start_link(Map.t()) :: {:ok, []} + @spec start_link(map()) :: {:ok, []} def init(_args) do {:ok, []} end @@ -100,7 +100,7 @@ defmodule EWalletConfig.Config do end) end - @spec update(Map.t(), Atom.t()) :: [{:ok, %Setting{}} | {:error, Atom.t()}] + @spec update(map(), Atom.t()) :: [{:ok, %Setting{}} | {:error, Atom.t()}] def update(attrs, pid \\ __MODULE__) do {config_pid, attrs} = get_config_pid(attrs) @@ -131,9 +131,9 @@ defmodule EWalletConfig.Config do defp get_config_pid(attrs), do: {nil, attrs} - @spec insert_all_defaults(Map.t(), Atom.t()) :: :ok - def insert_all_defaults(opts \\ %{}, pid \\ __MODULE__) do - :ok = Setting.insert_all_defaults(opts) + @spec insert_all_defaults(map(), map(), Atom.t()) :: :ok + def insert_all_defaults(originator, opts \\ %{}, pid \\ __MODULE__) do + :ok = Setting.insert_all_defaults(originator, opts) reload_config(pid) end diff --git a/apps/ewallet_config/lib/ewallet_config/repo.ex b/apps/ewallet_config/lib/ewallet_config/repo.ex index 079962c3b..c28a5e9bd 100644 --- a/apps/ewallet_config/lib/ewallet_config/repo.ex +++ b/apps/ewallet_config/lib/ewallet_config/repo.ex @@ -1,5 +1,6 @@ defmodule EWalletConfig.Repo do use Ecto.Repo, otp_app: :ewallet_config + use ActivityLogger.ActivityRepo, repo: EWalletConfig.Repo # Workaround an issue where ecto.migrate task won't start the app # thus DeferredConfig.populate is not getting called. diff --git a/apps/ewallet_config/lib/ewallet_config/setting.ex b/apps/ewallet_config/lib/ewallet_config/setting.ex index 3e11244c0..92e84f061 100644 --- a/apps/ewallet_config/lib/ewallet_config/setting.ex +++ b/apps/ewallet_config/lib/ewallet_config/setting.ex @@ -24,6 +24,7 @@ defmodule EWalletConfig.Setting do as a parent were parent_value=gcs. """ require Ecto.Query + use ActivityLogger.ActivityLogging alias EWalletConfig.{Repo, StoredSetting, Setting} alias Ecto.{Changeset, Query} @@ -43,10 +44,10 @@ defmodule EWalletConfig.Setting do :updated_at ] - @spec get_setting_mappings() :: [Map.t()] + @spec get_setting_mappings() :: [map()] def get_setting_mappings, do: Application.get_env(:ewallet_config, :settings_mappings) - @spec get_default_settings() :: [Map.t()] + @spec get_default_settings() :: [map()] def get_default_settings, do: Application.get_env(:ewallet_config, :default_settings) @spec types() :: [String.t()] @@ -108,30 +109,34 @@ defmodule EWalletConfig.Setting do @doc """ Creates a new setting with the passed attributes. """ - @spec insert(Map.t()) :: {:ok, %Setting{}} | {:error, %Changeset{}} + @spec insert(map()) :: {:ok, %Setting{}} | {:error, %Changeset{}} def insert(attrs) do attrs = cast_attrs(attrs) %StoredSetting{} |> StoredSetting.changeset(attrs) - |> Repo.insert() + |> Repo.insert_record_with_activity_log() |> return_from_change() end @doc """ Inserts all the default settings. """ - @spec insert_all_defaults(Map.t()) :: [{:ok, %Setting{}}] | [{:error, %Changeset{}}] - def insert_all_defaults(overrides \\ %{}) do + @spec insert_all_defaults(map(), map()) :: [{:ok, %Setting{}}] | [{:error, %Changeset{}}] + def insert_all_defaults(originator, overrides \\ %{}) do Repo.transaction(fn -> get_default_settings() - |> Enum.map(fn data -> insert_default(data, overrides) end) + |> Enum.map(fn data -> + insert_default(data, originator, overrides) + end) |> all_defaults_inserted?() end) |> return_tx_result() end - defp insert_default({key, data}, overrides) do + defp insert_default({key, data}, originator, overrides) do + data = Map.put(data, :originator, originator) + case overrides[key] do nil -> insert(data) @@ -156,7 +161,7 @@ defmodule EWalletConfig.Setting do defp return_tx_result({:ok, false}), do: :error defp return_tx_result({:error, _}), do: {:error, :setting_insert_failed} - @spec update(String.t(), Map.t()) :: + @spec update(String.t(), map()) :: {:ok, %Setting{}} | {:error, Atom.t()} | {:error, Changeset.t()} def update(nil, _), do: {:error, :setting_not_found} @@ -176,29 +181,31 @@ defmodule EWalletConfig.Setting do setting |> StoredSetting.update_changeset(attrs) - |> Repo.update() + |> Repo.update_record_with_activity_log() |> return_from_change() end end @spec update_all(List.t()) :: [{:ok, %Setting{}} | {:error, Atom.t()} | {:error, Changeset.t()}] def update_all(attrs) when is_list(attrs) do - Enum.map(attrs, fn data -> - case data do - {key, value} -> - {key, update(key, %{value: value})} - - data -> - key = data[:key] || data["key"] - {key, update(key, data)} - end - end) + case Keyword.keyword?(attrs) do + true -> update_all_with_keyword_list(attrs) + false -> update_all_with_map_list(attrs) + end end - @spec update_all(Map.t()) :: [{:ok, %Setting{}} | {:error, Atom.t()} | {:error, Changeset.t()}] + @spec update_all(map()) :: [{:ok, %Setting{}} | {:error, Atom.t()} | {:error, Changeset.t()}] def update_all(attrs) do - Enum.map(attrs, fn {key, value} -> - {key, update(key, %{value: value})} + originator = attrs[:originator] + + attrs + |> Map.delete(:originator) + |> Enum.map(fn {key, value} -> + {key, + update(key, %{ + value: value, + originator: originator + })} end) end @@ -233,6 +240,23 @@ defmodule EWalletConfig.Setting do } end + defp update_all_with_keyword_list(attrs) do + originator = attrs[:originator] + + attrs + |> Keyword.delete(:originator) + |> Enum.map(fn {key, value} -> + {key, update(key, %{value: value, originator: originator})} + end) + end + + defp update_all_with_map_list(attrs) do + Enum.map(attrs, fn data -> + key = data[:key] || data["key"] + {key, update(key, data)} + end) + end + defp cast_attrs(attrs) do attrs |> cast_value() diff --git a/apps/utils/lib/storage/local.ex b/apps/ewallet_config/lib/ewallet_config/storage/local.ex similarity index 97% rename from apps/utils/lib/storage/local.ex rename to apps/ewallet_config/lib/ewallet_config/storage/local.ex index bb343b573..dc2f9e390 100644 --- a/apps/utils/lib/storage/local.ex +++ b/apps/ewallet_config/lib/ewallet_config/storage/local.ex @@ -1,4 +1,4 @@ -defmodule Utils.Storage.Local do +defmodule EWalletConfig.Storage.Local do @moduledoc """ Modified copy of the Arc local storage, needed to add the base URL before the file paths. diff --git a/apps/ewallet_config/lib/ewallet_config/stored_setting.ex b/apps/ewallet_config/lib/ewallet_config/stored_setting.ex index 0a075735a..70555d38a 100644 --- a/apps/ewallet_config/lib/ewallet_config/stored_setting.ex +++ b/apps/ewallet_config/lib/ewallet_config/stored_setting.ex @@ -3,7 +3,8 @@ defmodule EWalletConfig.StoredSetting do Ecto Schema representing stored settings. """ use Ecto.Schema - use EWalletConfig.Types.ExternalID + use Utils.Types.ExternalID + use ActivityLogger.ActivityLogging import Ecto.Changeset import EWalletConfig.{Validator, SettingValidator} alias Ecto.UUID @@ -34,23 +35,31 @@ defmodule EWalletConfig.StoredSetting do field(:position, :integer) timestamps() + activity_logging() end def changeset(%StoredSetting{} = setting, attrs) do setting - |> cast(attrs, [ - :key, - :data, - :encrypted_data, - :type, - :description, - :parent, - :parent_value, - :options, - :secret, - :position - ]) - |> validate_required([:key, :type, :position]) + |> cast_and_validate_required_for_activity_log( + attrs, + [ + :key, + :data, + :encrypted_data, + :type, + :description, + :parent, + :parent_value, + :options, + :secret, + :position + ], + [ + :key, + :type, + :position + ] + ) |> validate_immutable(:key) |> validate_inclusion(:type, @types) |> validate_required_exclusive([:data, :encrypted_data]) @@ -61,7 +70,7 @@ defmodule EWalletConfig.StoredSetting do def update_changeset(%StoredSetting{} = setting, attrs) do setting - |> cast(attrs, [ + |> cast_and_validate_required_for_activity_log(attrs, [ :data, :encrypted_data, :description diff --git a/apps/ewallet_config/lib/ewallet_config/types/map.ex b/apps/ewallet_config/lib/ewallet_config/types/map.ex new file mode 100644 index 000000000..5ba82a461 --- /dev/null +++ b/apps/ewallet_config/lib/ewallet_config/types/map.ex @@ -0,0 +1,5 @@ +defmodule EWalletConfig.Encrypted.Map do + @moduledoc false + + use Cloak.Fields.Map, vault: EWalletConfig.Vault +end diff --git a/apps/ewallet_config/lib/ewallet_config/vault.ex b/apps/ewallet_config/lib/ewallet_config/vault.ex index ffbacc7c4..72465e829 100644 --- a/apps/ewallet_config/lib/ewallet_config/vault.ex +++ b/apps/ewallet_config/lib/ewallet_config/vault.ex @@ -1,7 +1,7 @@ defmodule EWalletConfig.Vault do @moduledoc false - use Cloak.Vault, otp_app: :ewallet_db + use Cloak.Vault, otp_app: :ewallet_config @impl Cloak.Vault def init(config) do diff --git a/apps/ewallet_config/mix.exs b/apps/ewallet_config/mix.exs index 98299c949..3edb7a2c7 100644 --- a/apps/ewallet_config/mix.exs +++ b/apps/ewallet_config/mix.exs @@ -48,8 +48,9 @@ defmodule EWalletConfig.MixProject do {:arc, "~> 0.8.0"}, {:arc_ecto, "~> 0.7.0"}, {:bcrypt_elixir, "~> 1.0"}, - {:ex_ulid, github: "omisego/ex_ulid"}, - {:plug, "~> 1.0"} + {:plug, "~> 1.0"}, + {:utils, in_umbrella: true}, + {:activity_logger, in_umbrella: true} ] end diff --git a/apps/ewallet_config/test/ewallet_config/config_test.exs b/apps/ewallet_config/test/ewallet_config/config_test.exs index 4108fa6a5..6187091b3 100644 --- a/apps/ewallet_config/test/ewallet_config/config_test.exs +++ b/apps/ewallet_config/test/ewallet_config/config_test.exs @@ -2,11 +2,21 @@ defmodule EWalletConfig.ConfigTest do use EWalletConfig.SchemaCase, async: true alias EWalletConfig.{Config, Repo, Setting} alias Ecto.Adapters.SQL.Sandbox + alias ActivityLogger.System def default_loading do {:ok, pid} = Config.start_link() Sandbox.allow(Repo, self(), pid) - {:ok, _} = Config.insert(%{key: "my_setting", value: "some_value", type: "string"}) + Sandbox.allow(ActivityLogger.Repo, self(), pid) + + {:ok, _} = + Config.insert(%{ + key: "my_setting", + value: "some_value", + type: "string", + originator: %System{} + }) + assert Config.register_and_load(:my_app, [:my_setting], pid) == :ok assert Application.get_env(:my_app, :my_setting) == "some_value" @@ -17,6 +27,8 @@ defmodule EWalletConfig.ConfigTest do {:ok, pid} = Task.start_link(fn -> Sandbox.allow(Repo, pid, self()) + Sandbox.allow(ActivityLogger.Repo, self(), pid) + assert_receive :select_for_update, 5000 _res = callback.(value) @@ -57,7 +69,7 @@ defmodule EWalletConfig.ConfigTest do assert Config.register_and_load(:my_app, [:my_setting], pid) == :ok assert Application.get_env(:my_app, :my_setting) == "some_value" - {:ok, _} = Setting.update("my_setting", %{value: "new_value"}) + {:ok, _} = Setting.update("my_setting", %{value: "new_value", originator: %System{}}) assert Application.get_env(:my_app, :my_setting) == "some_value" :ok = Config.reload_config(pid) @@ -68,7 +80,7 @@ defmodule EWalletConfig.ConfigTest do describe "update/3" do test "updates all settings and reload" do pid = default_loading() - {:ok, settings} = Config.update([my_setting: "new_value"], pid) + {:ok, settings} = Config.update([my_setting: "new_value", originator: %System{}], pid) assert {_key, {:ok, _}} = Enum.at(settings, 0) assert Application.get_env(:my_app, :my_setting) == "new_value" @@ -79,7 +91,15 @@ defmodule EWalletConfig.ConfigTest do {:ok, config_pid} = Config.start_link() Sandbox.allow(Repo, self(), config_pid) - {:ok, _} = Config.insert(%{key: "my_setting", value: "value_0", type: "string"}) + + {:ok, _} = + Config.insert(%{ + key: "my_setting", + value: "value_0", + type: "string", + originator: %System{} + }) + assert Config.register_and_load(:my_app, [:my_setting], config_pid) == :ok assert Application.get_env(:my_app, :my_setting) == "value_0" @@ -150,7 +170,7 @@ defmodule EWalletConfig.ConfigTest do test "insert all default settings" do {:ok, pid} = Config.start_link() Sandbox.allow(Repo, self(), pid) - :ok = Config.insert_all_defaults(%{}, pid) + :ok = Config.insert_all_defaults(%System{}, %{}, pid) assert length(Config.settings()) == 19 end @@ -160,7 +180,14 @@ defmodule EWalletConfig.ConfigTest do test "gets all the settings" do {:ok, pid} = Config.start_link() Sandbox.allow(Repo, self(), pid) - {:ok, _} = Config.insert(%{key: "my_setting", value: "some_value", type: "string"}) + + {:ok, _} = + Config.insert(%{ + key: "my_setting", + value: "some_value", + type: "string", + originator: %System{} + }) assert length(Config.settings()) == 1 end @@ -170,7 +197,14 @@ defmodule EWalletConfig.ConfigTest do test "gets a setting by key" do {:ok, pid} = Config.start_link() Sandbox.allow(Repo, self(), pid) - {:ok, _} = Config.insert(%{key: "my_setting", value: "some_value", type: "string"}) + + {:ok, _} = + Config.insert(%{ + key: "my_setting", + value: "some_value", + type: "string", + originator: %System{} + }) value = Config.get("my_setting") assert value == "some_value" @@ -179,7 +213,9 @@ defmodule EWalletConfig.ConfigTest do test "returns the default value when setting value is nil" do {:ok, pid} = Config.start_link() Sandbox.allow(Repo, self(), pid) - {:ok, _} = Config.insert(%{key: "my_setting", value: nil, type: "string"}) + + {:ok, _} = + Config.insert(%{key: "my_setting", value: nil, type: "string", originator: %System{}}) value = Config.get("my_setting", "default_value") assert value == "default_value" diff --git a/apps/ewallet_config/test/ewallet_config/setting_loader_test.exs b/apps/ewallet_config/test/ewallet_config/setting_loader_test.exs index 794e4490f..b2e94f6a5 100644 --- a/apps/ewallet_config/test/ewallet_config/setting_loader_test.exs +++ b/apps/ewallet_config/test/ewallet_config/setting_loader_test.exs @@ -1,11 +1,26 @@ defmodule EWalletConfig.SettingLoaderTest do use EWalletConfig.SchemaCase, async: true alias EWalletConfig.{Config, SettingLoader} + alias ActivityLogger.System describe "load_settings/2" do test "loads the settings" do - {:ok, _} = Config.insert(%{key: "my_setting_1", value: "value_1", type: "string"}) - {:ok, _} = Config.insert(%{key: "my_setting_2", value: "value_2", type: "string"}) + {:ok, _} = + Config.insert(%{ + key: "my_setting_1", + value: "value_1", + type: "string", + originator: %System{} + }) + + {:ok, _} = + Config.insert(%{ + key: "my_setting_2", + value: "value_2", + type: "string", + originator: %System{} + }) + SettingLoader.load_settings(:my_app, [:my_setting_1, :my_setting_2]) assert Application.get_env(:my_app, :my_setting_1) == "value_1" @@ -20,21 +35,38 @@ defmodule EWalletConfig.SettingLoaderTest do end test "load one setting when not secret" do - {:ok, _} = Config.insert(%{key: "my_setting", value: "value", type: "string"}) + {:ok, _} = + Config.insert(%{key: "my_setting", value: "value", type: "string", originator: %System{}}) + SettingLoader.load_settings(:my_app, [:my_setting]) assert Application.get_env(:my_app, :my_setting) == "value" end test "load one setting when secret" do - {:ok, _} = Config.insert(%{key: "my_setting", value: "value", secret: true, type: "string"}) + {:ok, _} = + Config.insert(%{ + key: "my_setting", + value: "value", + secret: true, + type: "string", + originator: %System{} + }) + SettingLoader.load_settings(:my_app, [:my_setting]) assert Application.get_env(:my_app, :my_setting) == "value" end test "load key with mapped name" do - {:ok, _} = Config.insert(%{key: "email_adapter", value: "smtp", type: "string"}) + {:ok, _} = + Config.insert(%{ + key: "email_adapter", + value: "smtp", + type: "string", + originator: %System{} + }) + SettingLoader.load_settings(:my_app, [:email_adapter]) assert Application.get_env(:my_app, :email_adapter) == Bamboo.SMTPAdapter diff --git a/apps/ewallet_config/test/ewallet_config/setting_test.exs b/apps/ewallet_config/test/ewallet_config/setting_test.exs index c29877f2e..d2aeace85 100644 --- a/apps/ewallet_config/test/ewallet_config/setting_test.exs +++ b/apps/ewallet_config/test/ewallet_config/setting_test.exs @@ -1,16 +1,17 @@ defmodule EWalletConfig.SettingTest do use EWalletConfig.SchemaCase alias EWalletConfig.{Repo, Setting, StoredSetting} + alias ActivityLogger.System def get_attrs do - %{key: "my_key", value: "test", type: "string"} + %{key: "my_key", value: "test", type: "string", originator: %System{}} end describe "all/0" do test "returns all settings" do - {:ok, _} = Setting.insert(%{key: "k1", value: "v", type: "string"}) - {:ok, _} = Setting.insert(%{key: "k2", value: "v", type: "string"}) - {:ok, _} = Setting.insert(%{key: "k3", value: "v", type: "string"}) + {:ok, _} = Setting.insert(%{key: "k1", value: "v", type: "string", originator: %System{}}) + {:ok, _} = Setting.insert(%{key: "k2", value: "v", type: "string", originator: %System{}}) + {:ok, _} = Setting.insert(%{key: "k3", value: "v", type: "string", originator: %System{}}) settings = Setting.all() |> Enum.map(fn s -> {s.key, s.value} end) assert Enum.member?(settings, {"k1", "v"}) @@ -39,7 +40,15 @@ defmodule EWalletConfig.SettingTest do end test "it returns the setting encrypted_value" do - {:ok, _} = Setting.insert(%{key: "my_key", value: "test", type: "string", secret: true}) + {:ok, _} = + Setting.insert(%{ + key: "my_key", + value: "test", + type: "string", + secret: true, + originator: %System{} + }) + assert Setting.get_value("my_key") == "test" end @@ -74,7 +83,8 @@ defmodule EWalletConfig.SettingTest do key: "my_key", value: "test", type: "string", - description: "My Description" + description: "My Description", + originator: %System{} }) assert res == :ok @@ -87,7 +97,8 @@ defmodule EWalletConfig.SettingTest do assert res == :ok assert setting.position == 0 - {res, setting} = Setting.insert(%{key: "my_key_2", value: "test", type: "string"}) + {res, setting} = + Setting.insert(%{key: "my_key_2", value: "test", type: "string", originator: %System{}}) assert res == :ok assert setting.position == 1 @@ -102,7 +113,7 @@ defmodule EWalletConfig.SettingTest do end test "inserts a setting with an array value" do - attrs = %{key: "array_key", value: ["a", "b", "c"], type: "array"} + attrs = %{key: "array_key", value: ["a", "b", "c"], type: "array", originator: %System{}} {res, setting} = Setting.insert(attrs) assert res == :ok @@ -112,7 +123,12 @@ defmodule EWalletConfig.SettingTest do test "inserts a CRON schedule" do {res, setting} = - Setting.insert(%{key: "balance_caching_schedule", value: "* * * * *", type: "string"}) + Setting.insert(%{ + key: "balance_caching_schedule", + value: "* * * * *", + type: "string", + originator: %System{} + }) assert res == :ok assert setting.key == "balance_caching_schedule" @@ -120,7 +136,7 @@ defmodule EWalletConfig.SettingTest do end test "inserts a setting with a string value" do - attrs = %{key: "my_key", value: "cool", type: "string"} + attrs = %{key: "my_key", value: "cool", type: "string", originator: %System{}} {res, setting} = Setting.insert(attrs) assert res == :ok @@ -129,7 +145,14 @@ defmodule EWalletConfig.SettingTest do end test "inserts a setting with a string value and options" do - attrs = %{key: "my_key", value: "def", type: "string", options: ["abc", "def"]} + attrs = %{ + key: "my_key", + value: "def", + type: "string", + options: ["abc", "def"], + originator: %System{} + } + {res, setting} = Setting.insert(attrs) assert res == :ok @@ -138,7 +161,14 @@ defmodule EWalletConfig.SettingTest do end test "fails to insert a setting with an invalid value and options" do - attrs = %{key: "my_key", value: "xyz", type: "string", options: ["abc", "def"]} + attrs = %{ + key: "my_key", + value: "xyz", + type: "string", + options: ["abc", "def"], + originator: %System{} + } + {res, changeset} = Setting.insert(attrs) assert res == :error @@ -149,7 +179,14 @@ defmodule EWalletConfig.SettingTest do end test "inserts a setting with an encrypted json" do - attrs = %{key: "my_key", value: %{key: "value"}, secret: true, type: "map"} + attrs = %{ + key: "my_key", + value: %{key: "value"}, + secret: true, + type: "map", + originator: %System{} + } + {:ok, setting} = Setting.insert(attrs) stored_setting = Repo.get_by(StoredSetting, key: "my_key") @@ -161,7 +198,7 @@ defmodule EWalletConfig.SettingTest do end test "inserts a setting with an integer value" do - attrs = %{key: "my_key", value: 5, type: "integer"} + attrs = %{key: "my_key", value: 5, type: "integer", originator: %System{}} {res, setting} = Setting.insert(attrs) assert res == :ok @@ -170,7 +207,7 @@ defmodule EWalletConfig.SettingTest do end test "inserts a setting with a map value" do - attrs = %{key: "my_key", value: %{a: "b"}, type: "map"} + attrs = %{key: "my_key", value: %{a: "b"}, type: "map", originator: %System{}} {res, setting} = Setting.insert(attrs) assert res == :ok @@ -179,7 +216,7 @@ defmodule EWalletConfig.SettingTest do end test "inserts a setting with a boolean value" do - attrs = %{key: "my_key", value: true, type: "boolean"} + attrs = %{key: "my_key", value: true, type: "boolean", originator: %System{}} {res, setting} = Setting.insert(attrs) assert res == :ok @@ -188,7 +225,7 @@ defmodule EWalletConfig.SettingTest do end test "inserts when value is nil" do - attrs = %{key: "my_key", type: "string"} + attrs = %{key: "my_key", type: "string", originator: %System{}} {res, setting} = Setting.insert(attrs) assert res == :ok @@ -197,7 +234,7 @@ defmodule EWalletConfig.SettingTest do end test "fails to insert when key is not present" do - attrs = %{value: "abc", type: "string"} + attrs = %{value: "abc", type: "string", originator: %System{}} {res, changeset} = Setting.insert(attrs) assert res == :error @@ -205,14 +242,15 @@ defmodule EWalletConfig.SettingTest do assert changeset.changes == %{ type: "string", data: %{value: "abc"}, - position: 0 + position: 0, + originator: %ActivityLogger.System{uuid: "00000000-0000-0000-0000-000000000000"} } assert changeset.errors == [key: {"can't be blank", [validation: :required]}] end test "fails to insert when type is not valid" do - attrs = %{key: "my_key", value: true, type: "fake"} + attrs = %{key: "my_key", value: true, type: "fake", originator: %System{}} {res, changeset} = Setting.insert(attrs) assert res == :error @@ -221,20 +259,23 @@ defmodule EWalletConfig.SettingTest do key: "my_key", type: "fake", data: %{value: true}, - position: 0 + position: 0, + originator: %ActivityLogger.System{uuid: "00000000-0000-0000-0000-000000000000"} } assert changeset.errors == [type: {"is invalid", [validation: :inclusion]}] assert changeset.valid? == false assert changeset.action == :insert - assert changeset.data == %StoredSetting{} + assert %StoredSetting{} = changeset.data end end describe "update/2" do test "updates a setting" do {:ok, setting} = Setting.insert(get_attrs()) - {res, updated_setting} = Setting.update("my_key", %{value: "new_value"}) + + {res, updated_setting} = + Setting.update("my_key", %{value: "new_value", originator: %System{}}) assert res == :ok assert setting.uuid == updated_setting.uuid @@ -247,7 +288,7 @@ defmodule EWalletConfig.SettingTest do end test "fails to update when the setting is not found" do - {res, error} = Setting.update("fake", %{value: "new_value"}) + {res, error} = Setting.update("fake", %{value: "new_value", originator: %System{}}) assert res == :error assert error == :setting_not_found @@ -259,10 +300,11 @@ defmodule EWalletConfig.SettingTest do key: "my_key", value: "abc", type: "string", - options: ["abc", "def", "xyz"] + options: ["abc", "def", "xyz"], + originator: %System{} }) - {res, setting} = Setting.update("my_key", %{value: "xyz"}) + {res, setting} = Setting.update("my_key", %{value: "xyz", originator: %System{}}) assert res == :ok assert setting.value == "xyz" @@ -274,10 +316,12 @@ defmodule EWalletConfig.SettingTest do key: "my_key", value: "abc", type: "string", - options: ["abc", "def", "xyz"] + options: ["abc", "def", "xyz"], + originator: %System{} }) - {res, changeset} = Setting.update("my_key", %{value: "something_else"}) + {res, changeset} = + Setting.update("my_key", %{value: "something_else", originator: %System{}}) assert res == :error @@ -287,8 +331,10 @@ defmodule EWalletConfig.SettingTest do end test "fails to update a setting when the value is not of the right type (string)" do - {:ok, _} = Setting.insert(%{key: "my_key", value: "abc", type: "string"}) - {res, changeset} = Setting.update("my_key", %{value: 123}) + {:ok, _} = + Setting.insert(%{key: "my_key", value: "abc", type: "string", originator: %System{}}) + + {res, changeset} = Setting.update("my_key", %{value: 123, originator: %System{}}) assert res == :error @@ -298,8 +344,10 @@ defmodule EWalletConfig.SettingTest do end test "fails to update a setting when the value is not of the right type (integer)" do - {:ok, _} = Setting.insert(%{key: "my_key", value: 123, type: "integer"}) - {res, changeset} = Setting.update("my_key", %{value: "some_string"}) + {:ok, _} = + Setting.insert(%{key: "my_key", value: 123, type: "integer", originator: %System{}}) + + {res, changeset} = Setting.update("my_key", %{value: "some_string", originator: %System{}}) assert res == :error @@ -309,8 +357,15 @@ defmodule EWalletConfig.SettingTest do end test "fails to update a setting when the value is not of the right type (map)" do - {:ok, _} = Setting.insert(%{key: "my_key", value: %{key: "value"}, type: "map"}) - {res, changeset} = Setting.update("my_key", %{value: "some_string"}) + {:ok, _} = + Setting.insert(%{ + key: "my_key", + value: %{key: "value"}, + type: "map", + originator: %System{} + }) + + {res, changeset} = Setting.update("my_key", %{value: "some_string", originator: %System{}}) assert res == :error @@ -320,8 +375,10 @@ defmodule EWalletConfig.SettingTest do end test "fails to update a setting when the value is not of the right type (array)" do - {:ok, _} = Setting.insert(%{key: "my_key", value: [1, 2, 3], type: "array"}) - {res, changeset} = Setting.update("my_key", %{value: "some_string"}) + {:ok, _} = + Setting.insert(%{key: "my_key", value: [1, 2, 3], type: "array", originator: %System{}}) + + {res, changeset} = Setting.update("my_key", %{value: "some_string", originator: %System{}}) assert res == :error @@ -331,8 +388,10 @@ defmodule EWalletConfig.SettingTest do end test "fails to update a setting when the value is not of the right type (boolean)" do - {:ok, _} = Setting.insert(%{key: "my_key", value: true, type: "boolean"}) - {res, changeset} = Setting.update("my_key", %{value: "some_string"}) + {:ok, _} = + Setting.insert(%{key: "my_key", value: true, type: "boolean", originator: %System{}}) + + {res, changeset} = Setting.update("my_key", %{value: "some_string", originator: %System{}}) assert res == :error @@ -344,14 +403,19 @@ defmodule EWalletConfig.SettingTest do describe "updated_all/1" do test "updates all the given settings with list" do - {:ok, _} = Setting.insert(%{key: "my_key_1", value: "test_1", type: "string"}) - {:ok, _} = Setting.insert(%{key: "my_key_2", value: "test_2", type: "string"}) - {:ok, _} = Setting.insert(%{key: "my_key_3", value: "test_3", type: "string"}) + {:ok, _} = + Setting.insert(%{key: "my_key_1", value: "test_1", type: "string", originator: %System{}}) + + {:ok, _} = + Setting.insert(%{key: "my_key_2", value: "test_2", type: "string", originator: %System{}}) + + {:ok, _} = + Setting.insert(%{key: "my_key_3", value: "test_3", type: "string", originator: %System{}}) res = Setting.update_all([ - %{key: "my_key_1", value: "new_value_1"}, - %{key: "my_key_3", value: "new_value_3"} + %{key: "my_key_1", value: "new_value_1", originator: %System{}}, + %{key: "my_key_3", value: "new_value_3", originator: %System{}} ]) {_key1, {res1, s1}} = Enum.at(res, 0) @@ -365,14 +429,20 @@ defmodule EWalletConfig.SettingTest do end test "updates all the given settings with map" do - {:ok, _} = Setting.insert(%{key: "my_key_1", value: "test_1", type: "string"}) - {:ok, _} = Setting.insert(%{key: "my_key_2", value: "test_2", type: "string"}) - {:ok, _} = Setting.insert(%{key: "my_key_3", value: "test_3", type: "string"}) + {:ok, _} = + Setting.insert(%{key: "my_key_1", value: "test_1", type: "string", originator: %System{}}) + + {:ok, _} = + Setting.insert(%{key: "my_key_2", value: "test_2", type: "string", originator: %System{}}) + + {:ok, _} = + Setting.insert(%{key: "my_key_3", value: "test_3", type: "string", originator: %System{}}) res = Setting.update_all(%{ my_key_1: "new_value_1", - my_key_3: "new_value_3" + my_key_3: "new_value_3", + originator: %System{} }) {_key1, {res1, s1}} = Enum.at(res, 0) @@ -386,14 +456,20 @@ defmodule EWalletConfig.SettingTest do end test "updates all the given settings with keyword list" do - {:ok, _} = Setting.insert(%{key: "my_key_1", value: "test_1", type: "string"}) - {:ok, _} = Setting.insert(%{key: "my_key_2", value: "test_2", type: "string"}) - {:ok, _} = Setting.insert(%{key: "my_key_3", value: "test_3", type: "string"}) + {:ok, _} = + Setting.insert(%{key: "my_key_1", value: "test_1", type: "string", originator: %System{}}) + + {:ok, _} = + Setting.insert(%{key: "my_key_2", value: "test_2", type: "string", originator: %System{}}) + + {:ok, _} = + Setting.insert(%{key: "my_key_3", value: "test_3", type: "string", originator: %System{}}) res = Setting.update_all( my_key_1: "new_value_1", - my_key_3: "new_value_3" + my_key_3: "new_value_3", + originator: %System{} ) {_key1, {res1, s1}} = Enum.at(res, 0) @@ -407,14 +483,19 @@ defmodule EWalletConfig.SettingTest do end test "fails to update some of the settings" do - {:ok, _} = Setting.insert(%{key: "my_key_1", value: "test_1", type: "string"}) - {:ok, _} = Setting.insert(%{key: "my_key_2", value: "test_2", type: "string"}) - {:ok, _} = Setting.insert(%{key: "my_key_3", value: "test_3", type: "string"}) + {:ok, _} = + Setting.insert(%{key: "my_key_1", value: "test_1", type: "string", originator: %System{}}) + + {:ok, _} = + Setting.insert(%{key: "my_key_2", value: "test_2", type: "string", originator: %System{}}) + + {:ok, _} = + Setting.insert(%{key: "my_key_3", value: "test_3", type: "string", originator: %System{}}) res = Setting.update_all([ - %{key: "my_key_1", value: "new_value_1"}, - %{key: "my_key_3z", value: "new_value_3"} + %{key: "my_key_1", value: "new_value_1", originator: %System{}}, + %{key: "my_key_3z", value: "new_value_3", originator: %System{}} ]) {_key1, {res1, s1}} = Enum.at(res, 0) @@ -430,7 +511,7 @@ defmodule EWalletConfig.SettingTest do describe "insert_all_defaults/1" do test "insert all defaults without overrides" do - assert Setting.insert_all_defaults() == :ok + assert Setting.insert_all_defaults(%System{}) == :ok settings = Setting.all() assert length(settings) == 19 @@ -440,7 +521,7 @@ defmodule EWalletConfig.SettingTest do end test "insert all defaults with overrides" do - assert Setting.insert_all_defaults(%{ + assert Setting.insert_all_defaults(%System{}, %{ "base_url" => "fake_url" }) == :ok diff --git a/apps/ewallet_config/test/support/config_test_helper.ex b/apps/ewallet_config/test/support/config_test_helper.ex index 796c267c8..b883a897e 100644 --- a/apps/ewallet_config/test/support/config_test_helper.ex +++ b/apps/ewallet_config/test/support/config_test_helper.ex @@ -4,12 +4,14 @@ defmodule EWalletConfig.ConfigTestHelper do """ alias EWalletConfig.Config alias Ecto.Adapters.SQL.Sandbox + alias ActivityLogger.System def restart_config_genserver(parent, repo, apps, attrs) do {:ok, pid} = Config.start_link() Sandbox.allow(repo, parent, pid) + Sandbox.allow(ActivityLogger.Repo, parent, pid) - Config.insert_all_defaults(attrs, pid) + Config.insert_all_defaults(%System{}, attrs, pid) Enum.each(apps, fn app -> settings = Application.get_env(app, :settings) diff --git a/apps/ewallet_config/test/support/schema_case.ex b/apps/ewallet_config/test/support/schema_case.ex index a00cc456c..5f06d89b3 100644 --- a/apps/ewallet_config/test/support/schema_case.ex +++ b/apps/ewallet_config/test/support/schema_case.ex @@ -11,6 +11,7 @@ defmodule EWalletConfig.SchemaCase do setup do Sandbox.checkout(Repo) + Sandbox.checkout(ActivityLogger.Repo) end end end diff --git a/apps/ewallet_db/lib/ewallet_db/application.ex b/apps/ewallet_db/lib/ewallet_db/application.ex index 7ac442e1c..b5155dbad 100644 --- a/apps/ewallet_db/lib/ewallet_db/application.ex +++ b/apps/ewallet_db/lib/ewallet_db/application.ex @@ -15,7 +15,6 @@ defmodule EWalletDB.Application do Config.register_and_load(:ewallet_db, settings) ActivityLogger.configure(%{ - ActivityLogger.System => "system", EWalletDB.User => "user", EWalletDB.Invite => "invite", EWalletDB.Key => "key", From af80f74e2415a77f2c2d017c8e0cbc9fb34a52c9 Mon Sep 17 00:00:00 2001 From: Thibault Denizet Date: Mon, 3 Dec 2018 18:24:29 +0700 Subject: [PATCH 11/23] Updated seeds to work with ActivityLogger --- apps/ewallet_db/lib/ewallet_db/application.ex | 1 + apps/ewallet_db/lib/ewallet_db/seeder.ex | 6 ++++++ .../ewallet_db/priv/repo/seeds/00_account.exs | 2 ++ apps/ewallet_db/priv/repo/seeds/00_role.exs | 5 +++-- apps/ewallet_db/priv/repo/seeds/01_user.exs | 8 ++++---- apps/ewallet_db/priv/repo/seeds/02_key.exs | 3 ++- .../priv/repo/seeds/02_membership.exs | 3 ++- .../priv/repo/seeds_sample/00_account.exs | 9 +++++---- .../repo/seeds_sample/00_admin_panel_user.exs | 12 ++++++------ .../priv/repo/seeds_sample/00_api_key.exs | 4 +++- .../priv/repo/seeds_sample/00_key.exs | 3 ++- .../priv/repo/seeds_sample/00_token.exs | 19 +++++++++++++------ .../priv/repo/seeds_sample/00_user.exs | 11 ++++++----- .../repo/seeds_sample/01_exchange_pair.exs | 4 +++- .../priv/repo/seeds_sample/01_membership.exs | 3 ++- .../priv/repo/seeds_sample/02_category.exs | 7 ++++--- ...30_transaction_request_and_consumption.exs | 7 +++++-- .../priv/repo/seeds_settings/00_setting.exs | 5 ++++- .../priv/repo/seeds_test/00_account.exs | 2 ++ .../priv/repo/seeds_test/00_role.exs | 5 +++-- .../priv/repo/seeds_test/01_user.exs | 10 +++++----- .../priv/repo/seeds_test/02_membership.exs | 4 ++-- .../priv/repo/seeds_test/10_setting.exs | 6 ++++-- 23 files changed, 89 insertions(+), 50 deletions(-) create mode 100644 apps/ewallet_db/lib/ewallet_db/seeder.ex diff --git a/apps/ewallet_db/lib/ewallet_db/application.ex b/apps/ewallet_db/lib/ewallet_db/application.ex index b5155dbad..48f8c3f71 100644 --- a/apps/ewallet_db/lib/ewallet_db/application.ex +++ b/apps/ewallet_db/lib/ewallet_db/application.ex @@ -15,6 +15,7 @@ defmodule EWalletDB.Application do Config.register_and_load(:ewallet_db, settings) ActivityLogger.configure(%{ + EWalletDB.Seeder => "seeder", EWalletDB.User => "user", EWalletDB.Invite => "invite", EWalletDB.Key => "key", diff --git a/apps/ewallet_db/lib/ewallet_db/seeder.ex b/apps/ewallet_db/lib/ewallet_db/seeder.ex new file mode 100644 index 000000000..f52d09657 --- /dev/null +++ b/apps/ewallet_db/lib/ewallet_db/seeder.ex @@ -0,0 +1,6 @@ +defmodule EWalletDB.Seeder do + @moduledoc """ + Module representing the seeder as originator. + """ + defstruct uuid: "11111111-1111-1111-1111-111111111111" +end diff --git a/apps/ewallet_db/priv/repo/seeds/00_account.exs b/apps/ewallet_db/priv/repo/seeds/00_account.exs index 8424bc209..74af62b96 100644 --- a/apps/ewallet_db/priv/repo/seeds/00_account.exs +++ b/apps/ewallet_db/priv/repo/seeds/00_account.exs @@ -1,10 +1,12 @@ defmodule EWalletDB.Repo.Seeds.AccountSeed do alias EWalletDB.Account + alias EWalletDB.Seeder @seed_data %{ name: "master_account", description: "Master Account", parent_id: nil, + originator: %Seeder{} } def seed do diff --git a/apps/ewallet_db/priv/repo/seeds/00_role.exs b/apps/ewallet_db/priv/repo/seeds/00_role.exs index 55d187a03..c52b86bdc 100644 --- a/apps/ewallet_db/priv/repo/seeds/00_role.exs +++ b/apps/ewallet_db/priv/repo/seeds/00_role.exs @@ -1,9 +1,10 @@ defmodule EWalletDB.Repo.Seeds.RoleSeed do alias EWalletDB.Role + alias EWalletDB.Seeder @seed_data [ - %{name: "admin", display_name: "Admin"}, - %{name: "viewer", display_name: "Viewer"}, + %{name: "admin", display_name: "Admin", originator: %Seeder{}}, + %{name: "viewer", display_name: "Viewer", originator: %Seeder{}}, ] def seed do diff --git a/apps/ewallet_db/priv/repo/seeds/01_user.exs b/apps/ewallet_db/priv/repo/seeds/01_user.exs index 622ecbc0a..5ee8b4d5b 100644 --- a/apps/ewallet_db/priv/repo/seeds/01_user.exs +++ b/apps/ewallet_db/priv/repo/seeds/01_user.exs @@ -1,7 +1,7 @@ defmodule EWalletDB.Repo.Seeds.UserSeed do - alias EWalletConfig.{System, Helpers.Crypto} alias EWalletDB.{Account, AccountUser, User} - alias ActivityLogger.System + alias Utils.Helpers.Crypto + alias EWalletDB.Seeder @argsline_desc """ This email and password combination is required for logging into the admin panel. @@ -28,14 +28,14 @@ defmodule EWalletDB.Repo.Seeds.UserSeed do metadata: %{}, account_uuid: Account.get_master_account().uuid, is_admin: true, - originator: %System{} + originator: %Seeder{} } case User.get_by_email(data.email) do nil -> case User.insert(data) do {:ok, user} -> - {:ok, _} = AccountUser.link(data.account_uuid, user.uuid, %System{}) + {:ok, _} = AccountUser.link(data.account_uuid, user.uuid, %Seeder{}) writer.success(""" ID : #{user.id} diff --git a/apps/ewallet_db/priv/repo/seeds/02_key.exs b/apps/ewallet_db/priv/repo/seeds/02_key.exs index e9f9ad7ce..494171d29 100644 --- a/apps/ewallet_db/priv/repo/seeds/02_key.exs +++ b/apps/ewallet_db/priv/repo/seeds/02_key.exs @@ -1,6 +1,7 @@ defmodule EWalletDB.Repo.Seeds.KeySeed do alias EWallet.Web.Preloader alias EWalletDB.{Account, Key} + alias EWalletDB.Seeder def seed do [ @@ -12,7 +13,7 @@ defmodule EWalletDB.Repo.Seeds.KeySeed do def run(writer, args) do account = Account.get_by(name: "master_account") - case Key.insert(%{account_uuid: account.uuid}) do + case Key.insert(%{account_uuid: account.uuid, originator: %Seeder{}}) do {:ok, key} -> {:ok, key} = Preloader.preload_one(key, :account) diff --git a/apps/ewallet_db/priv/repo/seeds/02_membership.exs b/apps/ewallet_db/priv/repo/seeds/02_membership.exs index a401dbdcd..56d6eb774 100644 --- a/apps/ewallet_db/priv/repo/seeds/02_membership.exs +++ b/apps/ewallet_db/priv/repo/seeds/02_membership.exs @@ -1,6 +1,7 @@ defmodule EWalletDB.Repo.Seeds.MembershipSeed do alias EWallet.Web.Preloader alias EWalletDB.{Account, Membership, Role, User} + alias EWalletDB.Seeder def seed do [ @@ -18,7 +19,7 @@ defmodule EWalletDB.Repo.Seeds.MembershipSeed do case Membership.get_by_user_and_account(user, account) do nil -> - case Membership.assign(user, account, role) do + case Membership.assign(user, account, role, %Seeder{}) do {:ok, membership} -> {:ok, membership} = Preloader.preload_one(membership, [:user, :account, :role]) diff --git a/apps/ewallet_db/priv/repo/seeds_sample/00_account.exs b/apps/ewallet_db/priv/repo/seeds_sample/00_account.exs index 698c3f19c..7a0fb5ca6 100644 --- a/apps/ewallet_db/priv/repo/seeds_sample/00_account.exs +++ b/apps/ewallet_db/priv/repo/seeds_sample/00_account.exs @@ -1,12 +1,13 @@ defmodule EWalletDB.Repo.Seeds.AccountSampleSeed do alias EWallet.Web.Preloader alias EWalletDB.Account + alias EWalletDB.Seeder @seed_data [ - %{name: "brand1", description: "Brand 1", parent_name: "master_account"}, - %{name: "brand2", description: "Brand 2", parent_name: "master_account"}, - %{name: "branch1", description: "Branch 1", parent_name: "master_account"}, - %{name: "branch2", description: "Branch 2", parent_name: "master_account"} + %{name: "brand1", description: "Brand 1", parent_name: "master_account", originator: %Seeder{}}, + %{name: "brand2", description: "Brand 2", parent_name: "master_account", originator: %Seeder{}}, + %{name: "branch1", description: "Branch 1", parent_name: "master_account", originator: %Seeder{}}, + %{name: "branch2", description: "Branch 2", parent_name: "master_account", originator: %Seeder{}} ] def seed do diff --git a/apps/ewallet_db/priv/repo/seeds_sample/00_admin_panel_user.exs b/apps/ewallet_db/priv/repo/seeds_sample/00_admin_panel_user.exs index dbad16746..322280d7e 100644 --- a/apps/ewallet_db/priv/repo/seeds_sample/00_admin_panel_user.exs +++ b/apps/ewallet_db/priv/repo/seeds_sample/00_admin_panel_user.exs @@ -1,7 +1,7 @@ defmodule EWalletDB.Repo.Seeds.AdminPanelUserSampleSeed do import Utils.Helpers.Crypto, only: [generate_base64_key: 1] alias EWalletDB.User - alias ActivityLogger.System + alias EWalletDB.Seeder @seed_data [ %{ @@ -9,35 +9,35 @@ defmodule EWalletDB.Repo.Seeds.AdminPanelUserSampleSeed do password: generate_base64_key(16), metadata: %{}, is_admin: true, - originator: %System{} + originator: %Seeder{} }, %{ email: "admin_branch1@example.com", password: generate_base64_key(16), metadata: %{}, is_admin: true, - originator: %System{} + originator: %Seeder{} }, %{ email: "viewer_master@example.com", password: generate_base64_key(16), metadata: %{}, is_admin: true, - originator: %System{} + originator: %Seeder{} }, %{ email: "viewer_brand1@example.com", password: generate_base64_key(16), metadata: %{}, is_admin: true, - originator: %System{} + originator: %Seeder{} }, %{ email: "viewer_branch1@example.com", password: generate_base64_key(16), metadata: %{}, is_admin: true, - originator: %System{} + originator: %Seeder{} } ] diff --git a/apps/ewallet_db/priv/repo/seeds_sample/00_api_key.exs b/apps/ewallet_db/priv/repo/seeds_sample/00_api_key.exs index 0e263a378..03d0065b6 100644 --- a/apps/ewallet_db/priv/repo/seeds_sample/00_api_key.exs +++ b/apps/ewallet_db/priv/repo/seeds_sample/00_api_key.exs @@ -1,6 +1,7 @@ defmodule EWalletDB.Repo.Seeds.APIKeySampleSeed do alias EWallet.Web.Preloader alias EWalletDB.{Account, APIKey} + alias EWalletDB.Seeder def seed do [ @@ -11,8 +12,9 @@ defmodule EWalletDB.Repo.Seeds.APIKeySampleSeed do def run(writer, args) do account = Account.get_by(name: "master_account") + data = %{account_uuid: account.uuid, owner_app: "ewallet_api", originator: %Seeder{}} - case APIKey.insert(%{account_uuid: account.uuid, owner_app: "ewallet_api"}) do + case APIKey.insert(data) do {:ok, api_key} -> {:ok, api_key} = Preloader.preload_one(api_key, :account) diff --git a/apps/ewallet_db/priv/repo/seeds_sample/00_key.exs b/apps/ewallet_db/priv/repo/seeds_sample/00_key.exs index 0cf106064..5f0c3036a 100644 --- a/apps/ewallet_db/priv/repo/seeds_sample/00_key.exs +++ b/apps/ewallet_db/priv/repo/seeds_sample/00_key.exs @@ -1,6 +1,7 @@ defmodule EWalletDB.Repo.Seeds.KeySampleSeed do alias EWallet.Web.Preloader alias EWalletDB.{Account, Key} + alias EWalletDB.Seeder def seed do [ @@ -12,7 +13,7 @@ defmodule EWalletDB.Repo.Seeds.KeySampleSeed do def run(writer, args) do account = Account.get_by(name: "master_account") - case Key.insert(%{account_uuid: account.uuid}) do + case Key.insert(%{account_uuid: account.uuid, originator: %Seeder{}}) do {:ok, key} -> {:ok, key} = Preloader.preload_one(key, :account) diff --git a/apps/ewallet_db/priv/repo/seeds_sample/00_token.exs b/apps/ewallet_db/priv/repo/seeds_sample/00_token.exs index 95201bb1d..cf21b923b 100644 --- a/apps/ewallet_db/priv/repo/seeds_sample/00_token.exs +++ b/apps/ewallet_db/priv/repo/seeds_sample/00_token.exs @@ -3,6 +3,7 @@ defmodule EWalletDB.Repo.Seeds.TokenSampleSeed do alias EWallet.MintGate alias EWallet.Web.Preloader alias EWalletDB.{Account, Token} + alias EWalletDB.Seeder @seed_data [ %{ @@ -10,35 +11,40 @@ defmodule EWalletDB.Repo.Seeds.TokenSampleSeed do name: "OmiseGO", subunit_to_unit: 1_000_000_000_000_000_000, genesis_amount: 1_000_000_000_000_000_000_000_000, # 1,000,000 OMG - account_name: "master_account" + account_name: "master_account", + originator: %Seeder{} }, %{ symbol: "KNC", name: "Kyber", subunit_to_unit: 1_000_000_000_000_000_000, genesis_amount: 1_000_000_000_000_000_000_000_000, # 1,000,000 KNC - account_name: "master_account" + account_name: "master_account", + originator: %Seeder{} }, %{ symbol: "BTC", name: "Bitcoin", subunit_to_unit: 1_000_000_000_000_000_000, genesis_amount: 1_000_000_000_000_000_000_000_000, # 1,000,000 BTC - account_name: "master_account" + account_name: "master_account", + originator: %Seeder{} }, %{ symbol: "OEM", name: "One EM", subunit_to_unit: 1_000_000_000_000_000_000, genesis_amount: 1_000_000_000_000_000_000_000_000, # 1,000,000 OEM - account_name: "master_account" + account_name: "master_account", + originator: %Seeder{} }, %{ symbol: "ETH", name: "Ether", subunit_to_unit: 1_000_000_000_000_000_000, genesis_amount: 1_000_000_000_000_000_000_000_000, # 1,000,000 ETH - account_name: "master_account" + account_name: "master_account", + originator: %Seeder{} }, ] @@ -95,7 +101,8 @@ defmodule EWalletDB.Repo.Seeds.TokenSampleSeed do "token_id" => token.id, "amount" => data.genesis_amount, "description" => "Seeded #{data.genesis_amount} #{token.id}.", - "metadata" => %{} + "metadata" => %{}, + "originator" => %Seeder{} } case MintGate.insert(mint_data) do diff --git a/apps/ewallet_db/priv/repo/seeds_sample/00_user.exs b/apps/ewallet_db/priv/repo/seeds_sample/00_user.exs index fce6aaffc..c80dd19b9 100644 --- a/apps/ewallet_db/priv/repo/seeds_sample/00_user.exs +++ b/apps/ewallet_db/priv/repo/seeds_sample/00_user.exs @@ -2,7 +2,7 @@ defmodule EWalletDB.Repo.Seeds.UserSampleSeed do alias Ecto.UUID alias EWallet.TransactionGate alias EWalletDB.{Account, AccountUser, Token, User} - alias ActivityLogger.System + alias EWalletDB.Seeder @users_count 5 @username_prefix "user" @@ -30,7 +30,7 @@ defmodule EWalletDB.Repo.Seeds.UserSampleSeed do username: @username_prefix <> running_string, metadata: %{}, account_uuid: Account.get_master_account().uuid, - originator: %System{} + originator: %Seeder{} } case User.get_by_provider_user_id(data.provider_user_id) do @@ -38,7 +38,7 @@ defmodule EWalletDB.Repo.Seeds.UserSampleSeed do case User.insert(data) do {:ok, user} -> :ok = give_token(user, Token.all(), @minimum_token_amount) - {:ok, _} = AccountUser.link(data.account_uuid, user.uuid, %System{}) + {:ok, _} = AccountUser.link(data.account_uuid, user.uuid, %Seeder{}) writer.success(""" User ID : #{user.id} @@ -73,13 +73,14 @@ defmodule EWalletDB.Repo.Seeds.UserSampleSeed do defp give_token(user, token, minimum_amount) do master_account = Account.get_master_account() - TransactionGate.create(%{ + {:ok, _} = TransactionGate.create(%{ "from_address" => Account.get_primary_wallet(master_account).address, "to_address" => User.get_primary_wallet(user).address, "token_id" => token.id, "amount" => :rand.uniform(10) * minimum_amount * token.subunit_to_unit, "metadata" => %{}, - "idempotency_token" => UUID.generate() + "idempotency_token" => UUID.generate(), + "originator" => %Seeder{} }) end end diff --git a/apps/ewallet_db/priv/repo/seeds_sample/01_exchange_pair.exs b/apps/ewallet_db/priv/repo/seeds_sample/01_exchange_pair.exs index cc40f2dd6..72aa42311 100644 --- a/apps/ewallet_db/priv/repo/seeds_sample/01_exchange_pair.exs +++ b/apps/ewallet_db/priv/repo/seeds_sample/01_exchange_pair.exs @@ -1,6 +1,7 @@ defmodule EWalletDB.Repo.Seeds.ExchangePairSeed do alias EWallet.Web.Preloader alias EWalletDB.{ExchangePair, Token} + alias EWalletDB.Seeder @pairs [ %{from_token_symbol: "BTC", to_token_symbol: "OEM", rate: 1_000_000}, @@ -37,7 +38,8 @@ defmodule EWalletDB.Repo.Seeds.ExchangePairSeed do ExchangePair.insert(%{ from_token_uuid: from_token.uuid, to_token_uuid: to_token.uuid, - rate: args.rate + rate: args.rate, + originator: %Seeder{} }) {:ok, pair} = Preloader.preload_one(pair, [:from_token, :to_token]) diff --git a/apps/ewallet_db/priv/repo/seeds_sample/01_membership.exs b/apps/ewallet_db/priv/repo/seeds_sample/01_membership.exs index c9b666393..160e68d56 100644 --- a/apps/ewallet_db/priv/repo/seeds_sample/01_membership.exs +++ b/apps/ewallet_db/priv/repo/seeds_sample/01_membership.exs @@ -1,6 +1,7 @@ defmodule EWalletDB.Repo.Seeds.MembershipSampleSeed do alias EWallet.Web.Preloader alias EWalletDB.{Account, Membership, Role, User} + alias EWalletDB.Seeder @seed_data [ %{email: "admin_brand1@example.com", role_name: "admin", account_name: "brand1"}, @@ -30,7 +31,7 @@ defmodule EWalletDB.Repo.Seeds.MembershipSampleSeed do case Membership.get_by_user_and_account(user, account) do nil -> - case Membership.assign(user, account, role) do + case Membership.assign(user, account, role, %Seeder{}) do {:ok, membership} -> {:ok, membership} = Preloader.preload_one(membership, [:user, :account, :role]) diff --git a/apps/ewallet_db/priv/repo/seeds_sample/02_category.exs b/apps/ewallet_db/priv/repo/seeds_sample/02_category.exs index c1a273d37..d16a5ca64 100644 --- a/apps/ewallet_db/priv/repo/seeds_sample/02_category.exs +++ b/apps/ewallet_db/priv/repo/seeds_sample/02_category.exs @@ -1,10 +1,11 @@ defmodule EWalletDB.Repo.Seeds.CategorySampleSeed do alias EWallet.Web.Preloader alias EWalletDB.{Account, Category} + alias EWalletDB.Seeder @seed_data [ - %{name: "category1", description: "Sample Category 1", account_names: ["brand1", "branch1"]}, - %{name: "category2", description: "Sample Category 2", account_names: ["brand2", "branch2"]}, + %{name: "category1", description: "Sample Category 1", account_names: ["brand1", "branch1"], originator: %Seeder{}}, + %{name: "category2", description: "Sample Category 2", account_names: ["brand2", "branch2"], originator: %Seeder{}}, ] def seed do @@ -57,7 +58,7 @@ defmodule EWalletDB.Repo.Seeds.CategorySampleSeed do Enum.each(data.account_names, fn(name) -> [name: name] |> Account.get_by() - |> Account.add_category(category) + |> Account.add_category(category, %Seeder{}) end) {:ok, category} diff --git a/apps/ewallet_db/priv/repo/seeds_sample/30_transaction_request_and_consumption.exs b/apps/ewallet_db/priv/repo/seeds_sample/30_transaction_request_and_consumption.exs index 7eaed6e3d..33a370649 100644 --- a/apps/ewallet_db/priv/repo/seeds_sample/30_transaction_request_and_consumption.exs +++ b/apps/ewallet_db/priv/repo/seeds_sample/30_transaction_request_and_consumption.exs @@ -3,6 +3,7 @@ defmodule EWalletDB.Repo.Seeds.TransactionRequestSeed do alias EWallet.TransactionConsumptionConfirmerGate alias EWallet.Web.Preloader alias EWalletDB.{Account, Token, TransactionConsumption, TransactionRequest, User} + alias EWalletDB.Seeder @num_requests 20 @request_correlation_id_prefix "transaction_request_" @@ -94,13 +95,13 @@ defmodule EWalletDB.Repo.Seeds.TransactionRequestSeed do |> TransactionConsumption.insert() |> case do {:ok, consumption} -> - consumption = TransactionConsumption.approve(consumption) + consumption = TransactionConsumption.approve(consumption, %Seeder{}) {:ok, consumption} = Preloader.preload_one(consumption, [:token, :wallet, :transaction_request]) {:ok, consumption} = - TransactionConsumptionConfirmerGate.approve_and_confirm(request, consumption) + TransactionConsumptionConfirmerGate.approve_and_confirm(request, consumption, %Seeder{}) writer.success(""" Transaction Consumption ID : #{consumption.id} @@ -142,6 +143,7 @@ defmodule EWalletDB.Repo.Seeds.TransactionRequestSeed do max_consumptions_per_user: rand(attrs.max_consumptions_per_user), exchange_account_id: nil, exchange_wallet_address: nil, + originator: %Seeder{} } end @@ -158,6 +160,7 @@ defmodule EWalletDB.Repo.Seeds.TransactionRequestSeed do wallet_address: user_wallet.address, estimated_request_amount: request.amount, estimated_consumption_amount: request.amount, + originator: %Seeder{} } end diff --git a/apps/ewallet_db/priv/repo/seeds_settings/00_setting.exs b/apps/ewallet_db/priv/repo/seeds_settings/00_setting.exs index 1147fdf66..5ef58b47c 100644 --- a/apps/ewallet_db/priv/repo/seeds_settings/00_setting.exs +++ b/apps/ewallet_db/priv/repo/seeds_settings/00_setting.exs @@ -39,7 +39,10 @@ defmodule EWalletDB.Repo.Seeds.SettingSeed do defp run_with(writer, {key, data}) do case Config.get_setting(key) do nil -> - case Config.insert(data) do + data + |> Map.put(:originator, %EWalletDB.Seeder{}) + |> Config.insert() + |> case do {:ok, setting} -> writer.success(""" Key : #{setting.key} diff --git a/apps/ewallet_db/priv/repo/seeds_test/00_account.exs b/apps/ewallet_db/priv/repo/seeds_test/00_account.exs index fc5b920bc..fa0f31dae 100644 --- a/apps/ewallet_db/priv/repo/seeds_test/00_account.exs +++ b/apps/ewallet_db/priv/repo/seeds_test/00_account.exs @@ -1,11 +1,13 @@ # credo:disable-for-this-file defmodule EWalletDB.Repo.Seeds.AccountSeed do alias EWalletDB.Account + alias EWalletDB.Seeder @seed_data %{ name: "master_account", description: "Master Account", parent_id: nil, + originator: %Seeder{} } def seed do diff --git a/apps/ewallet_db/priv/repo/seeds_test/00_role.exs b/apps/ewallet_db/priv/repo/seeds_test/00_role.exs index b8b568d27..c7506e1dd 100644 --- a/apps/ewallet_db/priv/repo/seeds_test/00_role.exs +++ b/apps/ewallet_db/priv/repo/seeds_test/00_role.exs @@ -1,10 +1,11 @@ # credo:disable-for-this-file defmodule EWalletDB.Repo.Seeds.RoleSeed do alias EWalletDB.Role + alias EWalletDB.Seeder @seed_data [ - %{name: "admin", display_name: "Admin"}, - %{name: "viewer", display_name: "Viewer"}, + %{name: "admin", display_name: "Admin", originator: %Seeder{}}, + %{name: "viewer", display_name: "Viewer", originator: %Seeder{}}, ] def seed do diff --git a/apps/ewallet_db/priv/repo/seeds_test/01_user.exs b/apps/ewallet_db/priv/repo/seeds_test/01_user.exs index 784779807..9e956ab37 100644 --- a/apps/ewallet_db/priv/repo/seeds_test/01_user.exs +++ b/apps/ewallet_db/priv/repo/seeds_test/01_user.exs @@ -1,7 +1,7 @@ defmodule EWalletDB.Repo.Seeds.UserSeed do alias EWalletDB.{Account, AccountUser, User} # credo:disable-for-next-line Credo.Check.Readability.AliasOrder - alias ActivityLogger.System, as: OriginatorSystem + alias EWalletDB.Seeder @seed_data [ %{ @@ -10,7 +10,7 @@ defmodule EWalletDB.Repo.Seeds.UserSeed do metadata: %{}, account_name: "master_account", is_admin: true, - originator: %OriginatorSystem{} + originator: %Seeder{} }, %{ email: System.get_env("E2E_TEST_ADMIN_1_EMAIL") || "test_admin_1@example.com", @@ -18,7 +18,7 @@ defmodule EWalletDB.Repo.Seeds.UserSeed do metadata: %{}, account_name: "master_account", is_admin: true, - originator: %OriginatorSystem{} + originator: %Seeder{} }, %{ email: System.get_env("E2E_TEST_USER_EMAIL") || "test_user@example.com", @@ -26,7 +26,7 @@ defmodule EWalletDB.Repo.Seeds.UserSeed do metadata: %{}, account_name: "master_account", is_admin: false, - originator: %OriginatorSystem{} + originator: %Seeder{} }, ] @@ -49,7 +49,7 @@ defmodule EWalletDB.Repo.Seeds.UserSeed do case User.insert(data) do {:ok, user} -> account = Account.get_by(name: data.account_name) - {:ok, _} = AccountUser.link(account.uuid, user.uuid, %OriginatorSystem{}) + {:ok, _} = AccountUser.link(account.uuid, user.uuid, %Seeder{}) writer.success(""" ID : #{user.id} diff --git a/apps/ewallet_db/priv/repo/seeds_test/02_membership.exs b/apps/ewallet_db/priv/repo/seeds_test/02_membership.exs index e7a1977b6..e5fef7558 100644 --- a/apps/ewallet_db/priv/repo/seeds_test/02_membership.exs +++ b/apps/ewallet_db/priv/repo/seeds_test/02_membership.exs @@ -1,6 +1,6 @@ defmodule EWalletDB.Repo.Seeds.MembershipSeed do alias EWallet.Web.Preloader - alias EWalletDB.{Account, Membership, Role, User} + alias EWalletDB.{Account, Membership, Role, User, Seeder} @seed_data %{ admin_email: System.get_env("E2E_TEST_ADMIN_EMAIL") || "test_admin@example.com" @@ -22,7 +22,7 @@ defmodule EWalletDB.Repo.Seeds.MembershipSeed do case Membership.get_by_user_and_account(user, account) do nil -> - case Membership.assign(user, account, role) do + case Membership.assign(user, account, role, %Seeder{}) do {:ok, membership} -> {:ok, membership} = Preloader.preload_one(membership, [:user, :account, :role]) diff --git a/apps/ewallet_db/priv/repo/seeds_test/10_setting.exs b/apps/ewallet_db/priv/repo/seeds_test/10_setting.exs index 2f13cdb54..2a0a5e5d3 100644 --- a/apps/ewallet_db/priv/repo/seeds_test/10_setting.exs +++ b/apps/ewallet_db/priv/repo/seeds_test/10_setting.exs @@ -1,5 +1,6 @@ # credo:disable-for-this-file defmodule EWalletDB.Repo.Seeds.SettingsSeed do + alias EWalletDB.Seeder alias EWalletConfig.{Config, Setting} def seed do @@ -10,8 +11,9 @@ defmodule EWalletDB.Repo.Seeds.SettingsSeed do end def run(writer, _args) do - {:ok, [ok: %Setting{}]} = Config.update(%{ - enable_standalone: true + {:ok, [enable_standalone: {:ok, %Setting{}}]} = Config.update(%{ + enable_standalone: true, + originator: %Seeder{} }) writer.warn(""" From 6bc3a57a1279172fc44a7706320af44815112c58 Mon Sep 17 00:00:00 2001 From: Thibault Denizet Date: Mon, 3 Dec 2018 18:32:27 +0700 Subject: [PATCH 12/23] Remove unneeded originator from EWalletDB schemas --- .../ewallet_db/lib/ewallet_db/account_user.ex | 2 +- apps/ewallet_db/lib/ewallet_db/mint.ex | 5 ++--- apps/ewallet_db/lib/ewallet_db/transaction.ex | 20 +++++++------------ .../lib/ewallet_db/transaction_request.ex | 16 ++++++--------- apps/ewallet_db/lib/ewallet_db/user.ex | 17 ++++++---------- 5 files changed, 22 insertions(+), 38 deletions(-) diff --git a/apps/ewallet_db/lib/ewallet_db/account_user.ex b/apps/ewallet_db/lib/ewallet_db/account_user.ex index bb1b2a520..d66bff97a 100644 --- a/apps/ewallet_db/lib/ewallet_db/account_user.ex +++ b/apps/ewallet_db/lib/ewallet_db/account_user.ex @@ -39,7 +39,7 @@ defmodule EWalletDB.AccountUser do |> cast_and_validate_required_for_activity_log( attrs, [:account_uuid, :user_uuid], - [:account_uuid, :user_uuid, :originator] + [:account_uuid, :user_uuid] ) |> unique_constraint(:account_uuid, name: :account_user_account_uuid_user_uuid_index) |> assoc_constraint(:account) diff --git a/apps/ewallet_db/lib/ewallet_db/mint.ex b/apps/ewallet_db/lib/ewallet_db/mint.ex index 36813cdfa..8e9e36abb 100644 --- a/apps/ewallet_db/lib/ewallet_db/mint.ex +++ b/apps/ewallet_db/lib/ewallet_db/mint.ex @@ -9,7 +9,6 @@ defmodule EWalletDB.Mint do import EWalletDB.Helpers.Preloader alias Ecto.UUID alias EWalletDB.{Account, Mint, Repo, Token, Transaction} - alias Utils.Types.VirtualStruct @primary_key {:uuid, Ecto.UUID, autogenerate: true} @@ -19,7 +18,6 @@ defmodule EWalletDB.Mint do field(:description, :string) field(:amount, Utils.Types.Integer) field(:confirmed, :boolean, default: false) - field(:originator, VirtualStruct, virtual: true) belongs_to( :token, @@ -46,6 +44,7 @@ defmodule EWalletDB.Mint do ) timestamps() + activity_logging() end defp changeset(%Mint{} = mint, attrs) do @@ -59,7 +58,7 @@ defmodule EWalletDB.Mint do :token_uuid, :confirmed ], - [:amount, :token_uuid, :originator] + [:amount, :token_uuid] ) |> validate_number( :amount, diff --git a/apps/ewallet_db/lib/ewallet_db/transaction.ex b/apps/ewallet_db/lib/ewallet_db/transaction.ex index 2dca3689b..b69843601 100644 --- a/apps/ewallet_db/lib/ewallet_db/transaction.ex +++ b/apps/ewallet_db/lib/ewallet_db/transaction.ex @@ -10,7 +10,6 @@ defmodule EWalletDB.Transaction do import EWalletDB.Validator alias Ecto.{Multi, UUID} alias EWalletDB.{Account, ExchangePair, Repo, Token, Transaction, User, Wallet} - alias Utils.Types.VirtualStruct @pending "pending" @confirmed "confirmed" @@ -52,8 +51,6 @@ defmodule EWalletDB.Transaction do field(:metadata, :map, default: %{}) field(:encrypted_metadata, EWalletConfig.Encrypted.Map, default: %{}) - field(:originator, VirtualStruct, virtual: true) - belongs_to( :from_token, Token, @@ -143,6 +140,7 @@ defmodule EWalletDB.Transaction do ) timestamps() + activity_logging() end defp changeset(%Transaction{} = transaction, attrs) do @@ -173,8 +171,7 @@ defmodule EWalletDB.Transaction do :error_code, :error_description, :exchange_pair_uuid, - :calculated_at, - :originator + :calculated_at ], [ :idempotency_token, @@ -186,8 +183,7 @@ defmodule EWalletDB.Transaction do :to_amount, :to_token_uuid, :to, - :from, - :originator + :from ] ) |> validate_number(:from_amount, less_than: 100_000_000_000_000_000_000_000_000_000_000_000) @@ -218,8 +214,8 @@ defmodule EWalletDB.Transaction do transaction |> cast_and_validate_required_for_activity_log( attrs, - [:status, :local_ledger_uuid, :originator], - [:status, :local_ledger_uuid, :originator] + [:status, :local_ledger_uuid], + [:status, :local_ledger_uuid] ) |> validate_inclusion(:status, @statuses) end @@ -232,13 +228,11 @@ defmodule EWalletDB.Transaction do :status, :error_code, :error_description, - :error_data, - :originator + :error_data ], [ :status, - :error_code, - :originator + :error_code ] ) |> validate_inclusion(:status, @statuses) diff --git a/apps/ewallet_db/lib/ewallet_db/transaction_request.ex b/apps/ewallet_db/lib/ewallet_db/transaction_request.ex index 6a4d5c82a..e49d2a5a3 100644 --- a/apps/ewallet_db/lib/ewallet_db/transaction_request.ex +++ b/apps/ewallet_db/lib/ewallet_db/transaction_request.ex @@ -8,7 +8,6 @@ defmodule EWalletDB.TransactionRequest do import Ecto.{Changeset, Query} import EWalletDB.Helpers.Preloader alias Ecto.{Changeset, Query, UUID} - alias Utils.Types.VirtualStruct alias EWalletDB.{ Account, @@ -53,8 +52,6 @@ defmodule EWalletDB.TransactionRequest do field(:metadata, :map, default: %{}) field(:encrypted_metadata, EWalletConfig.Encrypted.Map, default: %{}) - field(:originator, VirtualStruct, virtual: true) - has_many( :consumptions, TransactionConsumption, @@ -111,6 +108,7 @@ defmodule EWalletDB.TransactionRequest do ) timestamps() + activity_logging() end defp changeset(%TransactionRequest{} = transaction_request, attrs) do @@ -134,15 +132,13 @@ defmodule EWalletDB.TransactionRequest do :encrypted_metadata, :allow_amount_override, :exchange_account_uuid, - :exchange_wallet_address, - :originator + :exchange_wallet_address ], [ :type, :status, :token_uuid, - :wallet_address, - :originator + :wallet_address ] ) |> validate_amount_if_disallow_override() @@ -171,7 +167,7 @@ defmodule EWalletDB.TransactionRequest do |> cast_and_validate_required_for_activity_log( attrs, [:status, :expired_at, :expiration_reason], - [:status, :expired_at, :expiration_reason, :originator] + [:status, :expired_at, :expiration_reason] ) |> validate_inclusion(:status, @statuses) end @@ -180,8 +176,8 @@ defmodule EWalletDB.TransactionRequest do transaction_request |> cast_and_validate_required_for_activity_log( attrs, - [:updated_at, :originator], - [:updated_at, :originator] + [:updated_at], + [:updated_at] ) end diff --git a/apps/ewallet_db/lib/ewallet_db/user.ex b/apps/ewallet_db/lib/ewallet_db/user.ex index 8af717e6c..4efe9eaac 100644 --- a/apps/ewallet_db/lib/ewallet_db/user.ex +++ b/apps/ewallet_db/lib/ewallet_db/user.ex @@ -107,8 +107,7 @@ defmodule EWalletDB.User do :password_confirmation, :metadata, :encrypted_metadata, - :invite_uuid, - :originator + :invite_uuid ] ) |> validate_confirmation(:password, message: "does not match password") @@ -132,8 +131,7 @@ defmodule EWalletDB.User do :provider_user_id, :metadata, :encrypted_metadata, - :invite_uuid, - :originator + :invite_uuid ] ) |> validate_immutable(:provider_user_id) @@ -152,8 +150,7 @@ defmodule EWalletDB.User do :calling_name, :metadata, :encrypted_metadata, - :invite_uuid, - :originator + :invite_uuid ] ) |> assoc_constraint(:invite) @@ -168,7 +165,6 @@ defmodule EWalletDB.User do user |> cast_and_validate_required_for_activity_log(attrs, []) |> cast_attachments(attrs, [:avatar]) - |> validate_required([:originator]) end defp password_changeset(user, attrs) do @@ -179,8 +175,7 @@ defmodule EWalletDB.User do attrs, [ :password, - :password_confirmation, - :originator + :password_confirmation ] ) |> validate_confirmation(:password, message: "does not match password") @@ -200,8 +195,8 @@ defmodule EWalletDB.User do user |> cast_and_validate_required_for_activity_log( attrs, - [:email, :originator], - [:email, :originator] + [:email], + [:email] ) |> validate_email(:email) |> unique_constraint(:email) From 88630ae01c79f9e027efc50999832c40fc6f68a6 Mon Sep 17 00:00:00 2001 From: Thibault Denizet Date: Thu, 6 Dec 2018 11:39:58 +0700 Subject: [PATCH 13/23] Add account test and handle encrypted_data properly --- .../lib/activity_logger/activity_log.ex | 14 +++- .../lib/activity_logger/activity_logging.ex | 6 +- .../admin_auth/account_controller_test.exs | 70 +++++++++++++++++++ apps/admin_api/test/support/conn_case.ex | 15 +++- apps/ewallet_db/lib/ewallet_db/account.ex | 3 +- 5 files changed, 102 insertions(+), 6 deletions(-) diff --git a/apps/activity_logger/lib/activity_logger/activity_log.ex b/apps/activity_logger/lib/activity_logger/activity_log.ex index b8ea660f8..6bed7b332 100644 --- a/apps/activity_logger/lib/activity_logger/activity_log.ex +++ b/apps/activity_logger/lib/activity_logger/activity_log.ex @@ -136,7 +136,9 @@ defmodule ActivityLogger.ActivityLog do true <- action == :delete || changes != %{} || :no_changes, encrypted_changes <- changes[:encrypted_changes], changes <- Map.delete(changes, :encrypted_changes), - changes <- format_changes(changes) do + encrypted_fields <- changes[:encrypted_fields], + changes <- Map.delete(changes, :encrypted_fields), + changes <- format_changes(changes, encrypted_fields) do %{ action: Atom.to_string(action), target_type: target_type, @@ -152,13 +154,21 @@ defmodule ActivityLogger.ActivityLog do end end - defp format_changes(changes) do + defp format_changes(changes, nil) do changes |> Enum.into(%{}, fn {field, value} -> format_change(field, value) end) end + defp format_changes(changes, encrypted_fields) do + changes + |> Enum.filter(fn {key, value} -> + !Enum.member?(encrypted_fields, key) + end) + |> format_changes(nil) + end + defp format_change(field, values) when is_list(values) do {field, Enum.map(values, fn value -> diff --git a/apps/activity_logger/lib/activity_logger/activity_logging.ex b/apps/activity_logger/lib/activity_logger/activity_logging.ex index 7b8c374a0..839a7a566 100644 --- a/apps/activity_logger/lib/activity_logger/activity_logging.ex +++ b/apps/activity_logger/lib/activity_logger/activity_logging.ex @@ -50,12 +50,14 @@ defmodule ActivityLogger.ActivityLogging do {changeset, encrypted_changes} = Enum.reduce(encrypted_fields, {changeset, %{}}, fn encrypted_field, {changeset, map} -> { - delete_change(changeset, encrypted_field), + changeset, Map.put(map, encrypted_field, get_change(changeset, encrypted_field)) } end) - put_change(changeset, :encrypted_changes, encrypted_changes) + changeset + |> put_change(:encrypted_fields, encrypted_fields) + |> put_change(:encrypted_changes, encrypted_changes) end defp put_encrypted_changes(changeset, _), do: changeset diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/account_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/account_controller_test.exs index 6d8e16c4b..e59830a1b 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/account_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/account_controller_test.exs @@ -253,6 +253,40 @@ defmodule AdminAPI.V1.AdminAuth.AccountControllerTest do assert response["data"]["encrypted_metadata"] == %{"something" => "secret"} end + test "generates an activity log" do + user = get_test_admin() + parent = User.get_account(user) + + request_data = %{ + parent_id: parent.id, + name: "A test account", + metadata: %{something: "interesting"}, + encrypted_metadata: %{something: "secret"} + } + + response = admin_user_request("/account.create", request_data) + + assert response["success"] == true + + account = Account.get(response["data"]["id"]) + log = get_last_activity_log(account) + + assert log.action == "insert" + assert log.inserted_at != nil + assert log.originator_type == "user" + assert log.originator_uuid == user.uuid + assert log.target_type == "account" + assert log.target_uuid == account.uuid + assert log.target_changes == %{ + "metadata" => %{"something" => "interesting"}, + "name" => "A test account", + "parent_uuid" => parent.uuid + } + assert log.target_encrypted_changes == %{ + "encrypted_metadata" => %{"something" => "secret"} + } + end + test "creates a new account with no parent_id" do parent = Account.get_master_account() @@ -304,6 +338,42 @@ defmodule AdminAPI.V1.AdminAuth.AccountControllerTest do assert response["data"]["description"] == "updated_description" end + test "generates an activity log" do + user = get_test_admin() + account = User.get_account(user) + + request_data = + params_for(:account, %{ + id: account.id, + name: "updated_name", + description: "updated_description", + encrypted_metadata: %{something: "secret"} + }) + + response = admin_user_request("/account.update", request_data) + + assert response["success"] == true + + account = Account.get(response["data"]["id"]) + log = get_last_activity_log(account) + + assert log.action == "update" + assert log.inserted_at != nil + assert log.originator_type == "user" + assert log.originator_uuid == user.uuid + assert log.target_type == "account" + assert log.target_uuid == account.uuid + assert log.target_changes == %{ + "name" => "updated_name", + "parent_uuid" => account.parent_uuid, + "description" => "updated_description" + } + assert log.target_encrypted_changes == %{ + "encrypted_metadata" => %{"something" => "secret"} + } + end + + test "updates the account's categories" do account = :account |> insert() |> Repo.preload(:categories) category = :category |> insert() diff --git a/apps/admin_api/test/support/conn_case.ex b/apps/admin_api/test/support/conn_case.ex index ad2a59e28..8472192de 100644 --- a/apps/admin_api/test/support/conn_case.ex +++ b/apps/admin_api/test/support/conn_case.ex @@ -22,7 +22,7 @@ defmodule AdminAPI.ConnCase do alias EWallet.Web.Date alias EWalletConfig.ConfigTestHelper alias EWalletDB.{Account, Key, Repo, User} - alias ActivityLogger.System + alias ActivityLogger.{ActivityLog, System} alias Utils.{Types.ExternalID, Helpers.Crypto} # Attributes required by Phoenix.ConnTest @@ -181,6 +181,19 @@ defmodule AdminAPI.ConnCase do |> Repo.one() end + def get_last_activity_log(schema) do + type = ActivityLog.get_type(schema.__struct__) + + ActivityLog + |> last(:inserted_at) + |> where(target_type: ^type, target_uuid: ^schema.uuid) + |> ActivityLogger.Repo.one() + end + + def assert_last_activity_log(action, expected_originator, expected_target) do + + end + def mint!(token, amount \\ 1_000_000, originator \\ %System{}) do {:ok, mint, _transaction} = MintGate.insert(%{ diff --git a/apps/ewallet_db/lib/ewallet_db/account.ex b/apps/ewallet_db/lib/ewallet_db/account.ex index 5ec2f9160..0555b6f8f 100644 --- a/apps/ewallet_db/lib/ewallet_db/account.ex +++ b/apps/ewallet_db/lib/ewallet_db/account.ex @@ -107,7 +107,8 @@ defmodule EWalletDB.Account do |> cast_and_validate_required_for_activity_log( attrs, [:name, :description, :parent_uuid, :metadata, :encrypted_metadata], - [:name] + [:name], + [:encrypted_metadata] ) |> validate_parent_uuid() |> validate_account_level(@child_level_limit) From 6465951b3ce6afc9d59d8eb4fdc84358a4d375a9 Mon Sep 17 00:00:00 2001 From: Thibault Denizet Date: Thu, 6 Dec 2018 13:53:36 +0700 Subject: [PATCH 14/23] Add proper encryption and prevent_save fields to activity logs --- .../lib/activity_logger/activity_log.ex | 16 +++++++- .../lib/activity_logger/activity_logging.ex | 15 +++----- .../test/support/test_document.ex | 6 +-- .../activity_logger/test/support/test_user.ex | 4 +- .../lib/ewallet_config/stored_setting.ex | 17 +++++---- .../test/ewallet_config/setting_test.exs | 6 ++- apps/ewallet_db/lib/ewallet_db/account.ex | 8 ++-- .../ewallet_db/lib/ewallet_db/account_user.ex | 4 +- apps/ewallet_db/lib/ewallet_db/api_key.ex | 12 +++--- apps/ewallet_db/lib/ewallet_db/auth_token.ex | 12 +++--- apps/ewallet_db/lib/ewallet_db/category.ex | 4 +- .../lib/ewallet_db/exchange_pair.ex | 8 ++-- .../lib/ewallet_db/forget_password_request.ex | 12 ++++-- apps/ewallet_db/lib/ewallet_db/invite.ex | 8 ++-- apps/ewallet_db/lib/ewallet_db/key.ex | 12 ++++-- apps/ewallet_db/lib/ewallet_db/membership.ex | 4 +- apps/ewallet_db/lib/ewallet_db/mint.ex | 8 ++-- apps/ewallet_db/lib/ewallet_db/role.ex | 4 +- apps/ewallet_db/lib/ewallet_db/soft_delete.ex | 2 +- apps/ewallet_db/lib/ewallet_db/token.ex | 16 ++++---- apps/ewallet_db/lib/ewallet_db/transaction.ex | 15 ++++---- .../lib/ewallet_db/transaction_consumption.ex | 31 +++++++-------- .../lib/ewallet_db/transaction_request.ex | 19 +++++----- .../lib/ewallet_db/update_email_request.ex | 4 +- apps/ewallet_db/lib/ewallet_db/user.ex | 38 +++++++++++++++---- apps/ewallet_db/lib/ewallet_db/wallet.ex | 17 +++++---- 26 files changed, 178 insertions(+), 124 deletions(-) diff --git a/apps/activity_logger/lib/activity_logger/activity_log.ex b/apps/activity_logger/lib/activity_logger/activity_log.ex index 6bed7b332..e45c101c6 100644 --- a/apps/activity_logger/lib/activity_logger/activity_log.ex +++ b/apps/activity_logger/lib/activity_logger/activity_log.ex @@ -77,6 +77,7 @@ defmodule ActivityLogger.ActivityLog do @spec all_for_target(String.t(), UUID.t()) :: [%ActivityLog{}] def all_for_target(type, uuid) when is_binary(type) do ActivityLog + |> order_by(desc: :inserted_at) |> where([a], a.target_type == ^type and a.target_uuid == ^uuid) |> Repo.all() end @@ -138,7 +139,11 @@ defmodule ActivityLogger.ActivityLog do changes <- Map.delete(changes, :encrypted_changes), encrypted_fields <- changes[:encrypted_fields], changes <- Map.delete(changes, :encrypted_fields), - changes <- format_changes(changes, encrypted_fields) do + prevent_saving <- changes[:prevent_saving], + changes <- Map.delete(changes, :prevent_saving), + changes <- format_changes(changes, encrypted_fields), + changes <- remove_forbidden(changes, prevent_saving), + encrypted_changes <- remove_forbidden(encrypted_changes, prevent_saving) do %{ action: Atom.to_string(action), target_type: target_type, @@ -154,6 +159,15 @@ defmodule ActivityLogger.ActivityLog do end end + defp remove_forbidden(changes, nil), do: changes + defp remove_forbidden(nil, _), do: nil + + defp remove_forbidden(changes, prevent_saving) do + changes + |> Enum.filter(fn {key, value} -> !Enum.member?(prevent_saving, key) end) + |> Enum.into(%{}) + end + defp format_changes(changes, nil) do changes |> Enum.into(%{}, fn {field, value} -> diff --git a/apps/activity_logger/lib/activity_logger/activity_logging.ex b/apps/activity_logger/lib/activity_logger/activity_logging.ex index 839a7a566..866045fd0 100644 --- a/apps/activity_logger/lib/activity_logger/activity_logging.ex +++ b/apps/activity_logger/lib/activity_logger/activity_logging.ex @@ -26,18 +26,13 @@ defmodule ActivityLogger.ActivityLogging do @doc """ Prepares a changeset for activity_log. """ - def cast_and_validate_required_for_activity_log( - record, - attrs, - cast \\ [], - required \\ [], - encrypted_fields \\ [] - ) do + def cast_and_validate_required_for_activity_log(record, attrs, opts \\ []) do record |> Map.delete(:originator) - |> cast(attrs, [:originator | cast]) - |> validate_required([:originator | required]) - |> put_encrypted_changes(encrypted_fields) + |> cast(attrs, [:originator | opts[:cast] || []]) + |> validate_required([:originator | opts[:required] || []]) + |> put_encrypted_changes(opts[:encrypted] || []) + |> put_change(:prevent_saving, opts[:prevent_saving] || []) end def insert_log(action, changeset, record) do diff --git a/apps/activity_logger/test/support/test_document.ex b/apps/activity_logger/test/support/test_document.ex index 3c8faa7a8..f15fa486b 100644 --- a/apps/activity_logger/test/support/test_document.ex +++ b/apps/activity_logger/test/support/test_document.ex @@ -27,9 +27,9 @@ defmodule ActivityLogger.TestDocument do changeset |> cast_and_validate_required_for_activity_log( attrs, - [:title, :body, :secret_data], - [:title], - [:secret_data] + cast: [:title, :body, :secret_data], + required: [:title], + encrypted: [:secret_data] ) end diff --git a/apps/activity_logger/test/support/test_user.ex b/apps/activity_logger/test/support/test_user.ex index 80621e7b6..fd3e8cfb0 100644 --- a/apps/activity_logger/test/support/test_user.ex +++ b/apps/activity_logger/test/support/test_user.ex @@ -23,8 +23,8 @@ defmodule ActivityLogger.TestUser do changeset |> cast_and_validate_required_for_activity_log( attrs, - [:username], - [:username] + cast: [:username], + required: [:username] ) end diff --git a/apps/ewallet_config/lib/ewallet_config/stored_setting.ex b/apps/ewallet_config/lib/ewallet_config/stored_setting.ex index 70555d38a..0645e74cf 100644 --- a/apps/ewallet_config/lib/ewallet_config/stored_setting.ex +++ b/apps/ewallet_config/lib/ewallet_config/stored_setting.ex @@ -42,7 +42,7 @@ defmodule EWalletConfig.StoredSetting do setting |> cast_and_validate_required_for_activity_log( attrs, - [ + cast: [ :key, :data, :encrypted_data, @@ -54,7 +54,7 @@ defmodule EWalletConfig.StoredSetting do :secret, :position ], - [ + required: [ :key, :type, :position @@ -70,11 +70,14 @@ defmodule EWalletConfig.StoredSetting do def update_changeset(%StoredSetting{} = setting, attrs) do setting - |> cast_and_validate_required_for_activity_log(attrs, [ - :data, - :encrypted_data, - :description - ]) + |> cast_and_validate_required_for_activity_log( + attrs, + cast: [ + :data, + :encrypted_data, + :description + ] + ) |> validate_required_exclusive([:data, :encrypted_data]) |> validate_type(setting) |> validate_with_options(setting) diff --git a/apps/ewallet_config/test/ewallet_config/setting_test.exs b/apps/ewallet_config/test/ewallet_config/setting_test.exs index d2aeace85..4cda38b45 100644 --- a/apps/ewallet_config/test/ewallet_config/setting_test.exs +++ b/apps/ewallet_config/test/ewallet_config/setting_test.exs @@ -243,7 +243,8 @@ defmodule EWalletConfig.SettingTest do type: "string", data: %{value: "abc"}, position: 0, - originator: %ActivityLogger.System{uuid: "00000000-0000-0000-0000-000000000000"} + originator: %ActivityLogger.System{uuid: "00000000-0000-0000-0000-000000000000"}, + prevent_saving: [] } assert changeset.errors == [key: {"can't be blank", [validation: :required]}] @@ -260,7 +261,8 @@ defmodule EWalletConfig.SettingTest do type: "fake", data: %{value: true}, position: 0, - originator: %ActivityLogger.System{uuid: "00000000-0000-0000-0000-000000000000"} + originator: %ActivityLogger.System{uuid: "00000000-0000-0000-0000-000000000000"}, + prevent_saving: [] } assert changeset.errors == [type: {"is invalid", [validation: :inclusion]}] diff --git a/apps/ewallet_db/lib/ewallet_db/account.ex b/apps/ewallet_db/lib/ewallet_db/account.ex index 0555b6f8f..84b4c9b1a 100644 --- a/apps/ewallet_db/lib/ewallet_db/account.ex +++ b/apps/ewallet_db/lib/ewallet_db/account.ex @@ -106,9 +106,9 @@ defmodule EWalletDB.Account do account |> cast_and_validate_required_for_activity_log( attrs, - [:name, :description, :parent_uuid, :metadata, :encrypted_metadata], - [:name], - [:encrypted_metadata] + cast: [:name, :description, :parent_uuid, :metadata, :encrypted_metadata], + required: [:name], + encrypted: [:encrypted_metadata] ) |> validate_parent_uuid() |> validate_account_level(@child_level_limit) @@ -138,7 +138,7 @@ defmodule EWalletDB.Account do Ecto.Changeset.t() | %Account{} | no_return() defp avatar_changeset(changeset, attrs) do changeset - |> cast_and_validate_required_for_activity_log(attrs, []) + |> cast_and_validate_required_for_activity_log(attrs) |> cast_attachments(attrs, [:avatar]) end diff --git a/apps/ewallet_db/lib/ewallet_db/account_user.ex b/apps/ewallet_db/lib/ewallet_db/account_user.ex index d66bff97a..898109642 100644 --- a/apps/ewallet_db/lib/ewallet_db/account_user.ex +++ b/apps/ewallet_db/lib/ewallet_db/account_user.ex @@ -38,8 +38,8 @@ defmodule EWalletDB.AccountUser do account |> cast_and_validate_required_for_activity_log( attrs, - [:account_uuid, :user_uuid], - [:account_uuid, :user_uuid] + cast: [:account_uuid, :user_uuid], + required: [:account_uuid, :user_uuid] ) |> unique_constraint(:account_uuid, name: :account_user_account_uuid_user_uuid_index) |> assoc_constraint(:account) diff --git a/apps/ewallet_db/lib/ewallet_db/api_key.ex b/apps/ewallet_db/lib/ewallet_db/api_key.ex index 2cfacc8ea..7d85baa52 100644 --- a/apps/ewallet_db/lib/ewallet_db/api_key.ex +++ b/apps/ewallet_db/lib/ewallet_db/api_key.ex @@ -47,8 +47,8 @@ defmodule EWalletDB.APIKey do key |> cast_and_validate_required_for_activity_log( attrs, - [:key, :owner_app, :account_uuid, :enabled, :exchange_address], - [:key, :owner_app, :account_uuid] + cast: [:key, :owner_app, :account_uuid, :enabled, :exchange_address], + required: [:key, :owner_app, :account_uuid] ) |> unique_constraint(:key) |> assoc_constraint(:account) @@ -59,8 +59,8 @@ defmodule EWalletDB.APIKey do key |> cast_and_validate_required_for_activity_log( attrs, - [:enabled], - [:enabled] + cast: [:enabled], + required: [:enabled] ) end @@ -68,8 +68,8 @@ defmodule EWalletDB.APIKey do key |> cast_and_validate_required_for_activity_log( attrs, - [:enabled, :exchange_address], - [:enabled] + cast: [:enabled, :exchange_address], + required: [:enabled] ) end diff --git a/apps/ewallet_db/lib/ewallet_db/auth_token.ex b/apps/ewallet_db/lib/ewallet_db/auth_token.ex index c44b7888d..30f2763ab 100644 --- a/apps/ewallet_db/lib/ewallet_db/auth_token.ex +++ b/apps/ewallet_db/lib/ewallet_db/auth_token.ex @@ -45,8 +45,8 @@ defmodule EWalletDB.AuthToken do token |> cast_and_validate_required_for_activity_log( attrs, - [:token, :owner_app, :user_uuid, :account_uuid, :expired], - [:token, :owner_app, :user_uuid] + cast: [:token, :owner_app, :user_uuid, :account_uuid, :expired], + required: [:token, :owner_app, :user_uuid] ) |> unique_constraint(:token) |> assoc_constraint(:user) @@ -56,8 +56,8 @@ defmodule EWalletDB.AuthToken do token |> cast_and_validate_required_for_activity_log( attrs, - [:expired], - [:expired] + cast: [:expired], + required: [:expired] ) end @@ -65,8 +65,8 @@ defmodule EWalletDB.AuthToken do token |> cast_and_validate_required_for_activity_log( attrs, - [:account_uuid], - [:account_uuid] + cast: [:account_uuid], + required: [:account_uuid] ) end diff --git a/apps/ewallet_db/lib/ewallet_db/category.ex b/apps/ewallet_db/lib/ewallet_db/category.ex index b1f086fdf..0cb961f22 100644 --- a/apps/ewallet_db/lib/ewallet_db/category.ex +++ b/apps/ewallet_db/lib/ewallet_db/category.ex @@ -37,8 +37,8 @@ defmodule EWalletDB.Category do category |> cast_and_validate_required_for_activity_log( attrs, - [:name, :description], - [:name] + cast: [:name, :description], + required: [:name] ) |> unique_constraint(:name) |> put_accounts(attrs, :account_ids) diff --git a/apps/ewallet_db/lib/ewallet_db/exchange_pair.ex b/apps/ewallet_db/lib/ewallet_db/exchange_pair.ex index b8d8829a7..2883c1405 100644 --- a/apps/ewallet_db/lib/ewallet_db/exchange_pair.ex +++ b/apps/ewallet_db/lib/ewallet_db/exchange_pair.ex @@ -61,8 +61,8 @@ defmodule EWalletDB.ExchangePair do exchange_pair |> cast_and_validate_required_for_activity_log( attrs, - [:from_token_uuid, :to_token_uuid, :rate, :deleted_at], - [:from_token_uuid, :to_token_uuid, :rate] + cast: [:from_token_uuid, :to_token_uuid, :rate, :deleted_at], + required: [:from_token_uuid, :to_token_uuid, :rate] ) |> validate_different_values(:from_token_uuid, :to_token_uuid) |> validate_immutable(:from_token_uuid) @@ -78,7 +78,7 @@ defmodule EWalletDB.ExchangePair do defp restore_changeset(exchange_pair, attrs) do exchange_pair - |> cast_and_validate_required_for_activity_log(attrs, [:deleted_at]) + |> cast_and_validate_required_for_activity_log(attrs, cast: [:deleted_at]) |> unique_constraint( :deleted_at, name: "exchange_pair_from_token_uuid_to_token_uuid_index" @@ -86,7 +86,7 @@ defmodule EWalletDB.ExchangePair do end defp touch_changeset(exchange_pair, attrs) do - cast_and_validate_required_for_activity_log(exchange_pair, attrs, [:updated_at]) + cast_and_validate_required_for_activity_log(exchange_pair, attrs, cast: [:updated_at]) end @doc """ diff --git a/apps/ewallet_db/lib/ewallet_db/forget_password_request.ex b/apps/ewallet_db/lib/ewallet_db/forget_password_request.ex index 788884182..9b371465b 100644 --- a/apps/ewallet_db/lib/ewallet_db/forget_password_request.ex +++ b/apps/ewallet_db/lib/ewallet_db/forget_password_request.ex @@ -31,10 +31,14 @@ defmodule EWalletDB.ForgetPasswordRequest do defp changeset(changeset, attrs) do changeset - |> cast_and_validate_required_for_activity_log(attrs, [:token, :user_uuid], [ - :token, - :user_uuid - ]) + |> cast_and_validate_required_for_activity_log( + attrs, + cast: [:token, :user_uuid], + required: [ + :token, + :user_uuid + ] + ) |> assoc_constraint(:user) end diff --git a/apps/ewallet_db/lib/ewallet_db/invite.ex b/apps/ewallet_db/lib/ewallet_db/invite.ex index da8bfdc99..ce52a5591 100644 --- a/apps/ewallet_db/lib/ewallet_db/invite.ex +++ b/apps/ewallet_db/lib/ewallet_db/invite.ex @@ -34,8 +34,8 @@ defmodule EWalletDB.Invite do changeset |> cast_and_validate_required_for_activity_log( attrs, - [:user_uuid, :token, :success_url], - [:user_uuid, :token] + cast: [:user_uuid, :token, :success_url], + required: [:user_uuid, :token] ) end @@ -43,8 +43,8 @@ defmodule EWalletDB.Invite do changeset |> cast_and_validate_required_for_activity_log( attrs, - [:verified_at], - [:verified_at] + cast: [:verified_at], + required: [:verified_at] ) end diff --git a/apps/ewallet_db/lib/ewallet_db/key.ex b/apps/ewallet_db/lib/ewallet_db/key.ex index c10b8e95e..59a4b7bf7 100644 --- a/apps/ewallet_db/lib/ewallet_db/key.ex +++ b/apps/ewallet_db/lib/ewallet_db/key.ex @@ -41,8 +41,9 @@ defmodule EWalletDB.Key do key |> cast_and_validate_required_for_activity_log( attrs, - [:access_key, :secret_key, :account_uuid, :enabled], - [:access_key, :secret_key, :account_uuid] + cast: [:access_key, :secret_key, :account_uuid, :enabled], + required: [:access_key, :secret_key, :account_uuid], + prevent_saving: [:secret_key] ) |> unique_constraint(:access_key, name: :key_access_key_index) |> put_change(:secret_key_hash, Crypto.hash_secret(attrs[:secret_key])) @@ -51,7 +52,12 @@ defmodule EWalletDB.Key do end defp enable_changeset(%Key{} = key, attrs) do - cast_and_validate_required_for_activity_log(key, attrs, [:enabled], [:enabled]) + cast_and_validate_required_for_activity_log( + key, + attrs, + cast: [:enabled], + required: [:enabled] + ) end @doc """ diff --git a/apps/ewallet_db/lib/ewallet_db/membership.ex b/apps/ewallet_db/lib/ewallet_db/membership.ex index 89162fe54..d26d40aa0 100644 --- a/apps/ewallet_db/lib/ewallet_db/membership.ex +++ b/apps/ewallet_db/lib/ewallet_db/membership.ex @@ -44,8 +44,8 @@ defmodule EWalletDB.Membership do membership |> cast_and_validate_required_for_activity_log( attrs, - [:user_uuid, :account_uuid, :role_uuid], - [:user_uuid, :account_uuid, :role_uuid] + cast: [:user_uuid, :account_uuid, :role_uuid], + required: [:user_uuid, :account_uuid, :role_uuid] ) |> unique_constraint(:user_uuid, name: :membership_user_id_account_id_index) |> assoc_constraint(:user) diff --git a/apps/ewallet_db/lib/ewallet_db/mint.ex b/apps/ewallet_db/lib/ewallet_db/mint.ex index 8e9e36abb..021fe0d47 100644 --- a/apps/ewallet_db/lib/ewallet_db/mint.ex +++ b/apps/ewallet_db/lib/ewallet_db/mint.ex @@ -51,14 +51,14 @@ defmodule EWalletDB.Mint do mint |> cast_and_validate_required_for_activity_log( attrs, - [ + cast: [ :description, :amount, :account_uuid, :token_uuid, :confirmed ], - [:amount, :token_uuid] + required: [:amount, :token_uuid] ) |> validate_number( :amount, @@ -77,8 +77,8 @@ defmodule EWalletDB.Mint do mint |> cast_and_validate_required_for_activity_log( attrs, - [:transaction_uuid], - [:transaction_uuid] + cast: [:transaction_uuid], + required: [:transaction_uuid] ) |> assoc_constraint(:transaction) end diff --git a/apps/ewallet_db/lib/ewallet_db/role.ex b/apps/ewallet_db/lib/ewallet_db/role.ex index 752a1c862..2df9f04e1 100644 --- a/apps/ewallet_db/lib/ewallet_db/role.ex +++ b/apps/ewallet_db/lib/ewallet_db/role.ex @@ -36,8 +36,8 @@ defmodule EWalletDB.Role do key |> cast_and_validate_required_for_activity_log( attrs, - [:priority, :name, :display_name], - [:name, :priority] + cast: [:priority, :name, :display_name], + required: [:name, :priority] ) |> validate_required([:name, :priority]) |> unique_constraint(:name) diff --git a/apps/ewallet_db/lib/ewallet_db/soft_delete.ex b/apps/ewallet_db/lib/ewallet_db/soft_delete.ex index 04841a23a..1644e13de 100644 --- a/apps/ewallet_db/lib/ewallet_db/soft_delete.ex +++ b/apps/ewallet_db/lib/ewallet_db/soft_delete.ex @@ -90,7 +90,7 @@ defmodule EWalletDB.SoftDelete do cast_and_validate_required_for_activity_log( record, attrs, - [:deleted_at] + cast: [:deleted_at] ) end diff --git a/apps/ewallet_db/lib/ewallet_db/token.ex b/apps/ewallet_db/lib/ewallet_db/token.ex index fa9f12f5a..9e3f4b454 100644 --- a/apps/ewallet_db/lib/ewallet_db/token.ex +++ b/apps/ewallet_db/lib/ewallet_db/token.ex @@ -63,7 +63,7 @@ defmodule EWalletDB.Token do token |> cast_and_validate_required_for_activity_log( attrs, - [ + cast: [ :symbol, :iso_code, :name, @@ -80,12 +80,13 @@ defmodule EWalletDB.Token do :metadata, :encrypted_metadata ], - [ + required: [ :symbol, :name, :subunit_to_unit, :account_uuid - ] + ], + encrypted: [:encrypted_metadata] ) |> validate_number( :subunit_to_unit, @@ -107,7 +108,7 @@ defmodule EWalletDB.Token do token |> cast_and_validate_required_for_activity_log( attrs, - [ + cast: [ :iso_code, :name, :description, @@ -118,9 +119,10 @@ defmodule EWalletDB.Token do :metadata, :encrypted_metadata ], - [ + required: [ :name - ] + ], + encrypted: [:encrypted_metadata] ) |> unique_constraint(:iso_code) |> unique_constraint(:name) @@ -130,7 +132,7 @@ defmodule EWalletDB.Token do defp enable_changeset(%Token{} = token, attrs) do token - |> cast_and_validate_required_for_activity_log(attrs, [:enabled], [:enabled]) + |> cast_and_validate_required_for_activity_log(attrs, cast: [:enabled], required: [:enabled]) end defp set_id(changeset, opts) do diff --git a/apps/ewallet_db/lib/ewallet_db/transaction.ex b/apps/ewallet_db/lib/ewallet_db/transaction.ex index b69843601..70ecf1c21 100644 --- a/apps/ewallet_db/lib/ewallet_db/transaction.ex +++ b/apps/ewallet_db/lib/ewallet_db/transaction.ex @@ -147,7 +147,7 @@ defmodule EWalletDB.Transaction do transaction |> cast_and_validate_required_for_activity_log( attrs, - [ + cast: [ :idempotency_token, :status, :type, @@ -173,7 +173,7 @@ defmodule EWalletDB.Transaction do :exchange_pair_uuid, :calculated_at ], - [ + required: [ :idempotency_token, :status, :type, @@ -184,7 +184,8 @@ defmodule EWalletDB.Transaction do :to_token_uuid, :to, :from - ] + ], + encrypted: [:encrypted_metadata] ) |> validate_number(:from_amount, less_than: 100_000_000_000_000_000_000_000_000_000_000_000) |> validate_number(:to_amount, less_than: 100_000_000_000_000_000_000_000_000_000_000_000) @@ -214,8 +215,8 @@ defmodule EWalletDB.Transaction do transaction |> cast_and_validate_required_for_activity_log( attrs, - [:status, :local_ledger_uuid], - [:status, :local_ledger_uuid] + cast: [:status, :local_ledger_uuid], + required: [:status, :local_ledger_uuid] ) |> validate_inclusion(:status, @statuses) end @@ -224,13 +225,13 @@ defmodule EWalletDB.Transaction do transaction |> cast_and_validate_required_for_activity_log( attrs, - [ + cast: [ :status, :error_code, :error_description, :error_data ], - [ + required: [ :status, :error_code ] diff --git a/apps/ewallet_db/lib/ewallet_db/transaction_consumption.ex b/apps/ewallet_db/lib/ewallet_db/transaction_consumption.ex index a6a0d81fb..b988f9e78 100644 --- a/apps/ewallet_db/lib/ewallet_db/transaction_consumption.ex +++ b/apps/ewallet_db/lib/ewallet_db/transaction_consumption.ex @@ -138,7 +138,7 @@ defmodule EWalletDB.TransactionConsumption do consumption |> cast_and_validate_required_for_activity_log( attrs, - [ + cast: [ :amount, :estimated_request_amount, :estimated_consumption_amount, @@ -158,13 +158,14 @@ defmodule EWalletDB.TransactionConsumption do :estimated_at, :estimated_rate ], - [ + required: [ :status, :idempotency_token, :transaction_request_uuid, :wallet_address, :token_uuid - ] + ], + encrypted: [:encrypted_metadata] ) |> validate_number( :amount, @@ -187,8 +188,8 @@ defmodule EWalletDB.TransactionConsumption do consumption |> cast_and_validate_required_for_activity_log( attrs, - [:status, :approved_at], - [:status, :approved_at] + cast: [:status, :approved_at], + required: [:status, :approved_at] ) end @@ -196,8 +197,8 @@ defmodule EWalletDB.TransactionConsumption do consumption |> cast_and_validate_required_for_activity_log( attrs, - [:status, :rejected_at], - [:status, :rejected_at] + cast: [:status, :rejected_at], + required: [:status, :rejected_at] ) end @@ -205,8 +206,8 @@ defmodule EWalletDB.TransactionConsumption do consumption |> cast_and_validate_required_for_activity_log( attrs, - [:status, :confirmed_at, :transaction_uuid], - [:status, :confirmed_at, :transaction_uuid] + cast: [:status, :confirmed_at, :transaction_uuid], + required: [:status, :confirmed_at, :transaction_uuid] ) |> assoc_constraint(:transaction) end @@ -215,8 +216,8 @@ defmodule EWalletDB.TransactionConsumption do consumption |> cast_and_validate_required_for_activity_log( attrs, - [:status, :failed_at, :transaction_uuid], - [:status, :failed_at, :transaction_uuid] + cast: [:status, :failed_at, :transaction_uuid], + required: [:status, :failed_at, :transaction_uuid] ) |> assoc_constraint(:transaction) end @@ -225,8 +226,8 @@ defmodule EWalletDB.TransactionConsumption do consumption |> cast_and_validate_required_for_activity_log( attrs, - [:status, :failed_at, :error_code, :error_description], - [:status, :failed_at, :error_code] + cast: [:status, :failed_at, :error_code, :error_description], + required: [:status, :failed_at, :error_code] ) end @@ -234,8 +235,8 @@ defmodule EWalletDB.TransactionConsumption do consumption |> cast_and_validate_required_for_activity_log( attrs, - [:status, :expired_at], - [:status, :expired_at] + cast: [:status, :expired_at], + required: [:status, :expired_at] ) end diff --git a/apps/ewallet_db/lib/ewallet_db/transaction_request.ex b/apps/ewallet_db/lib/ewallet_db/transaction_request.ex index e49d2a5a3..380b28ed0 100644 --- a/apps/ewallet_db/lib/ewallet_db/transaction_request.ex +++ b/apps/ewallet_db/lib/ewallet_db/transaction_request.ex @@ -115,7 +115,7 @@ defmodule EWalletDB.TransactionRequest do transaction_request |> cast_and_validate_required_for_activity_log( attrs, - [ + cast: [ :type, :amount, :correlation_id, @@ -134,12 +134,13 @@ defmodule EWalletDB.TransactionRequest do :exchange_account_uuid, :exchange_wallet_address ], - [ + required: [ :type, :status, :token_uuid, :wallet_address - ] + ], + encrypted: [:encrypted_metadata] ) |> validate_amount_if_disallow_override() |> validate_number(:amount, less_than: 100_000_000_000_000_000_000_000_000_000_000_000) @@ -157,8 +158,8 @@ defmodule EWalletDB.TransactionRequest do transaction_request |> cast_and_validate_required_for_activity_log( attrs, - [:consumptions_count], - [:consumptions_count] + cast: [:consumptions_count], + required: [:consumptions_count] ) end @@ -166,8 +167,8 @@ defmodule EWalletDB.TransactionRequest do transaction_request |> cast_and_validate_required_for_activity_log( attrs, - [:status, :expired_at, :expiration_reason], - [:status, :expired_at, :expiration_reason] + cast: [:status, :expired_at, :expiration_reason], + required: [:status, :expired_at, :expiration_reason] ) |> validate_inclusion(:status, @statuses) end @@ -176,8 +177,8 @@ defmodule EWalletDB.TransactionRequest do transaction_request |> cast_and_validate_required_for_activity_log( attrs, - [:updated_at], - [:updated_at] + cast: [:updated_at], + required: [:updated_at] ) end diff --git a/apps/ewallet_db/lib/ewallet_db/update_email_request.ex b/apps/ewallet_db/lib/ewallet_db/update_email_request.ex index f180199f7..26a0dd941 100644 --- a/apps/ewallet_db/lib/ewallet_db/update_email_request.ex +++ b/apps/ewallet_db/lib/ewallet_db/update_email_request.ex @@ -34,8 +34,8 @@ defmodule EWalletDB.UpdateEmailRequest do changeset |> cast_and_validate_required_for_activity_log( attrs, - [:email, :token, :user_uuid], - [:email, :token, :user_uuid] + cast: [:email, :token, :user_uuid], + required: [:email, :token, :user_uuid] ) |> validate_email(:email) |> unique_constraint(:token) diff --git a/apps/ewallet_db/lib/ewallet_db/user.ex b/apps/ewallet_db/lib/ewallet_db/user.ex index 4efe9eaac..aacef6cfa 100644 --- a/apps/ewallet_db/lib/ewallet_db/user.ex +++ b/apps/ewallet_db/lib/ewallet_db/user.ex @@ -96,7 +96,7 @@ defmodule EWalletDB.User do changeset |> cast_and_validate_required_for_activity_log( attrs, - [ + cast: [ :is_admin, :username, :full_name, @@ -108,6 +108,13 @@ defmodule EWalletDB.User do :metadata, :encrypted_metadata, :invite_uuid + ], + encrypted: [ + :encrypted_metadata + ], + prevent_saving: [ + :password, + :password_confirmation ] ) |> validate_confirmation(:password, message: "does not match password") @@ -124,7 +131,7 @@ defmodule EWalletDB.User do user |> cast_and_validate_required_for_activity_log( attrs, - [ + cast: [ :username, :full_name, :calling_name, @@ -132,6 +139,9 @@ defmodule EWalletDB.User do :metadata, :encrypted_metadata, :invite_uuid + ], + encrypted: [ + :encrypted_metadata ] ) |> validate_immutable(:provider_user_id) @@ -145,12 +155,15 @@ defmodule EWalletDB.User do user |> cast_and_validate_required_for_activity_log( attrs, - [ + cast: [ :full_name, :calling_name, :metadata, :encrypted_metadata, :invite_uuid + ], + encrypted: [ + :encrypted_metadata ] ) |> assoc_constraint(:invite) @@ -158,7 +171,7 @@ defmodule EWalletDB.User do end defp set_admin_changeset(user, attrs) do - cast_and_validate_required_for_activity_log(user, attrs, [:is_admin]) + cast_and_validate_required_for_activity_log(user, attrs, cast: [:is_admin]) end defp avatar_changeset(user, attrs) do @@ -173,7 +186,11 @@ defmodule EWalletDB.User do user |> cast_and_validate_required_for_activity_log( attrs, - [ + cast: [ + :password, + :password_confirmation + ], + prevent_saving: [ :password, :password_confirmation ] @@ -184,7 +201,12 @@ defmodule EWalletDB.User do end defp enable_changeset(%User{} = user, attrs) do - cast_and_validate_required_for_activity_log(user, attrs, [:enabled], [:enabled]) + cast_and_validate_required_for_activity_log( + user, + attrs, + cast: [:enabled], + required: [:enabled] + ) end defp get_attr(attrs, atom_field) do @@ -195,8 +217,8 @@ defmodule EWalletDB.User do user |> cast_and_validate_required_for_activity_log( attrs, - [:email], - [:email] + cast: [:email], + required: [:email] ) |> validate_email(:email) |> unique_constraint(:email) diff --git a/apps/ewallet_db/lib/ewallet_db/wallet.ex b/apps/ewallet_db/lib/ewallet_db/wallet.ex index 977fefa91..8632492c7 100644 --- a/apps/ewallet_db/lib/ewallet_db/wallet.ex +++ b/apps/ewallet_db/lib/ewallet_db/wallet.ex @@ -72,8 +72,9 @@ defmodule EWalletDB.Wallet do wallet |> cast_and_validate_required_for_activity_log( attrs, - @cast_attrs, - [:name, :identifier] + cast: @cast_attrs, + required: [:name, :identifier], + encrypted: [:encrypted_metadata] ) |> validate_format( :identifier, @@ -87,8 +88,9 @@ defmodule EWalletDB.Wallet do wallet |> cast_and_validate_required_for_activity_log( attrs, - @cast_attrs, - [:name, :identifier, :account_uuid] + cast: @cast_attrs, + required: [:name, :identifier, :account_uuid], + encrypted: [:encrypted_metadata] ) |> validate_format(:identifier, ~r/#{@secondary}_.*/) |> shared_changeset() @@ -98,8 +100,9 @@ defmodule EWalletDB.Wallet do wallet |> cast_and_validate_required_for_activity_log( attrs, - @cast_attrs, - [:name, :identifier, :account_uuid] + cast: @cast_attrs, + required: [:name, :identifier, :account_uuid], + encrypted: [:encrypted_metadata] ) |> validate_format(:identifier, ~r/#{@burn}|#{@burn}_.*/) |> shared_changeset() @@ -119,7 +122,7 @@ defmodule EWalletDB.Wallet do defp enable_changeset(%Wallet{} = wallet, attrs) do wallet - |> cast_and_validate_required_for_activity_log(attrs, [:enabled], [:enabled]) + |> cast_and_validate_required_for_activity_log(attrs, cast: [:enabled], required: [:enabled]) end @spec all_for(any()) :: Ecto.Query.t() | nil From 43e244d53c08e680f002ade0c728e7a26389f8dc Mon Sep 17 00:00:00 2001 From: Thibault Denizet Date: Thu, 6 Dec 2018 15:07:00 +0700 Subject: [PATCH 15/23] Add activity logs tests for accounts, users and invites --- .../lib/activity_logger/activity_logging.ex | 11 ++- .../admin_auth/account_controller_test.exs | 29 ++++---- .../admin_auth/invite_controller_test.exs | 74 +++++++++++++++++++ .../admin_auth/user_controller_test.exs | 37 ++++++++++ apps/admin_api/test/support/conn_case.ex | 10 ++- 5 files changed, 143 insertions(+), 18 deletions(-) diff --git a/apps/activity_logger/lib/activity_logger/activity_logging.ex b/apps/activity_logger/lib/activity_logger/activity_logging.ex index 866045fd0..c29d16ac2 100644 --- a/apps/activity_logger/lib/activity_logger/activity_logging.ex +++ b/apps/activity_logger/lib/activity_logger/activity_logging.ex @@ -44,10 +44,13 @@ defmodule ActivityLogger.ActivityLogging do defp put_encrypted_changes(changeset, encrypted_fields) when is_list(encrypted_fields) do {changeset, encrypted_changes} = Enum.reduce(encrypted_fields, {changeset, %{}}, fn encrypted_field, {changeset, map} -> - { - changeset, - Map.put(map, encrypted_field, get_change(changeset, encrypted_field)) - } + case get_change(changeset, encrypted_field, :not_found) do + :not_found -> + {changeset, map} + + change -> + {changeset, Map.put(map, encrypted_field, change)} + end end) changeset diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/account_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/account_controller_test.exs index e59830a1b..c58ed430d 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/account_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/account_controller_test.exs @@ -277,14 +277,16 @@ defmodule AdminAPI.V1.AdminAuth.AccountControllerTest do assert log.originator_uuid == user.uuid assert log.target_type == "account" assert log.target_uuid == account.uuid + assert log.target_changes == %{ - "metadata" => %{"something" => "interesting"}, - "name" => "A test account", - "parent_uuid" => parent.uuid - } + "metadata" => %{"something" => "interesting"}, + "name" => "A test account", + "parent_uuid" => parent.uuid + } + assert log.target_encrypted_changes == %{ - "encrypted_metadata" => %{"something" => "secret"} - } + "encrypted_metadata" => %{"something" => "secret"} + } end test "creates a new account with no parent_id" do @@ -363,17 +365,18 @@ defmodule AdminAPI.V1.AdminAuth.AccountControllerTest do assert log.originator_uuid == user.uuid assert log.target_type == "account" assert log.target_uuid == account.uuid + assert log.target_changes == %{ - "name" => "updated_name", - "parent_uuid" => account.parent_uuid, - "description" => "updated_description" - } + "name" => "updated_name", + "parent_uuid" => account.parent_uuid, + "description" => "updated_description" + } + assert log.target_encrypted_changes == %{ - "encrypted_metadata" => %{"something" => "secret"} - } + "encrypted_metadata" => %{"something" => "secret"} + } end - test "updates the account's categories" do account = :account |> insert() |> Repo.preload(:categories) category = :category |> insert() diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/invite_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/invite_controller_test.exs index e4b16d5cb..20d739e23 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/invite_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/invite_controller_test.exs @@ -47,6 +47,80 @@ defmodule AdminAPI.V1.AdminAuth.InviteControllerTest do assert invite.user.id |> User.get() |> User.admin?() end + test "generates activity logs" do + {:ok, user} = :admin |> params_for(is_admin: false) |> User.insert() + {:ok, invite} = Invite.generate(user, %System{}, preload: :user) + + response = request(invite.user.email, invite.token, "some_password", "some_password") + + assert response["success"] == true + + user = User.get(response["data"]["id"]) + logs = get_all_activity_logs(user) + + # Set is admin + log = Enum.at(logs, 0) + assert log.action == "update" + assert log.inserted_at != nil + assert log.originator_type == "system" + assert log.originator_uuid == "00000000-0000-0000-0000-000000000000" + assert log.target_type == "user" + assert log.target_uuid == user.uuid + assert log.target_changes == %{"is_admin" => true} + assert log.target_encrypted_changes == %{} + + # update invite_uuid to nil + log = Enum.at(logs, 1) + assert log.action == "update" + assert log.inserted_at != nil + assert log.originator_type == "user" + assert log.originator_uuid == user.uuid + assert log.target_type == "user" + assert log.target_uuid == user.uuid + assert log.target_changes == %{"invite_uuid" => nil} + assert log.target_encrypted_changes == %{} + + # Update user password + log = Enum.at(logs, 2) + assert log.action == "update" + assert log.inserted_at != nil + assert log.originator_type == "user" + assert log.originator_uuid == user.uuid + assert log.target_type == "user" + assert log.target_uuid == user.uuid + assert log.target_changes == %{"password_hash" => user.password_hash} + assert log.target_encrypted_changes == %{} + + # Invite update + log = Enum.at(logs, 3) + assert log.action == "update" + assert log.inserted_at != nil + assert log.originator_type == "invite" + assert log.originator_uuid == invite.uuid + assert log.target_type == "user" + assert log.target_uuid == user.uuid + assert log.target_changes == %{"invite_uuid" => invite.uuid} + assert log.target_encrypted_changes == %{} + + # Set password + log = Enum.at(logs, 4) + assert log.action == "insert" + assert log.inserted_at != nil + assert log.originator_type == "system" + assert log.originator_uuid == "00000000-0000-0000-0000-000000000000" + assert log.target_type == "user" + assert log.target_uuid == user.uuid + assert log.target_changes["email"] == user.email + assert log.target_changes["metadata"] == user.metadata + assert log.target_changes["password_hash"] != user.password_hash + assert log.target_encrypted_changes == %{} + + Enum.each(logs, fn log -> + assert log.target_changes["password"] == nil + assert log.target_encrypted_changes["password"] == nil + end) + end + test "returns :invite_not_found error if the email has not been invited" do {:ok, user} = :admin |> params_for() |> User.insert() {:ok, invite} = Invite.generate(user, %System{}) diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/user_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/user_controller_test.exs index ba97d7c27..c3fd7e803 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/user_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/user_controller_test.exs @@ -330,6 +330,43 @@ defmodule AdminAPI.V1.AdminAuth.UserControllerTest do assert metadata["last_name"] == request_data.metadata["last_name"] end + test "generates an activity log" do + admin = get_test_admin() + + request_data = + params_for( + :user, + metadata: %{something: "interesting"}, + encrypted_metadata: %{something: "secret"} + ) + + response = admin_user_request("/user.create", request_data) + + assert response["success"] == true + + user = User.get(response["data"]["id"]) + log = get_last_activity_log(user) + + assert log.action == "insert" + assert log.inserted_at != nil + assert log.originator_type == "user" + assert log.originator_uuid == admin.uuid + assert log.target_type == "user" + assert log.target_uuid == user.uuid + + assert log.target_changes == %{ + "metadata" => %{"something" => "interesting"}, + "calling_name" => user.calling_name, + "full_name" => user.full_name, + "provider_user_id" => user.provider_user_id, + "username" => user.username + } + + assert log.target_encrypted_changes == %{ + "encrypted_metadata" => %{"something" => "secret"} + } + end + test "returns an error if provider_user_id is not provided" do request_data = params_for(:user, provider_user_id: "") response = admin_user_request("/user.create", request_data) diff --git a/apps/admin_api/test/support/conn_case.ex b/apps/admin_api/test/support/conn_case.ex index 8472192de..abcb89468 100644 --- a/apps/admin_api/test/support/conn_case.ex +++ b/apps/admin_api/test/support/conn_case.ex @@ -190,8 +190,16 @@ defmodule AdminAPI.ConnCase do |> ActivityLogger.Repo.one() end - def assert_last_activity_log(action, expected_originator, expected_target) do + def get_all_activity_logs(schema) do + type = ActivityLog.get_type(schema.__struct__) + + ActivityLog + |> order_by(desc: :inserted_at) + |> where(target_type: ^type) + |> ActivityLogger.Repo.all() + end + def assert_last_activity_log(action, expected_originator, expected_target) do end def mint!(token, amount \\ 1_000_000, originator \\ %System{}) do From 5e6e77d1ee07c44bc14e0c94249f6b80a6286e34 Mon Sep 17 00:00:00 2001 From: mederic Date: Thu, 13 Dec 2018 14:23:10 +0700 Subject: [PATCH 16/23] Merge master --- apps/admin_api/config/config.exs | 1 + .../global/controllers/status_controller.ex | 14 +- .../admin_api/v1/channels/account_channel.ex | 16 +- .../transaction_consumption_channel.ex | 17 +- .../channels/transaction_request_channel.ex | 16 +- .../lib/admin_api/v1/channels/user_channel.ex | 15 +- .../admin_api/v1/channels/wallet_channel.ex | 16 +- .../v1/controllers/key_controller.ex | 2 +- .../controllers/reset_password_controller.ex | 96 ++-- .../lib/admin_api/v1/error_handler.ex | 8 - .../v1/views/account_membership_view.ex | 3 +- .../admin_api/v1/views/reset_password_view.ex | 3 +- apps/admin_api/mix.exs | 2 +- .../controllers/status_controller_test.exs | 8 +- .../v1/channels/account_channel_test.exs | 66 ++- .../v1/channels/address_channel_test.exs | 32 -- .../transaction_consumption_channel_test.exs | 89 +++- .../transaction_request_channel_test.exs | 68 ++- .../v1/channels/user_channel_test.exs | 57 +- .../v1/channels/wallet_channel_test.exs | 121 +++++ .../configuration_controller_test.exs | 30 +- .../reset_password_controller_test.exs | 18 +- .../configuration_controller_test.exs | 160 ++++++ apps/admin_api/test/support/channel_case.ex | 99 +++- apps/admin_api/test/support/conn_case.ex | 56 +- apps/admin_panel/mix.exs | 2 +- apps/ewallet/config/dev.exs | 5 + apps/ewallet/config/prod.exs | 5 + .../lib/ewallet/gates/reset_password_gate.ex | 84 +++ .../lib/ewallet/policies/account_policy.ex | 4 +- .../transaction_consumption_policy.ex | 12 + .../policies/transaction_request_policy.ex | 12 + .../lib/ewallet/policies/user_policy.ex | 14 +- .../lib/ewallet/policies/wallet_policy.ex | 14 + .../forget_password_request_scheduler.ex | 15 + .../web/emails/forget_password_email.ex | 2 +- apps/ewallet/lib/ewallet/web/originator.ex | 2 +- .../lib/ewallet/web/v1/error_handler.ex | 9 + apps/ewallet/mix.exs | 2 +- .../emails/forget_password_email_test.exs | 2 +- .../gates/reset_password_gate_test.exs | 126 +++++ ...forget_password_request_scheduler_test.exs | 31 ++ apps/ewallet/test/support/db_case.ex | 28 +- .../ewallet/test/support/local_ledger_case.ex | 3 + apps/ewallet_api/config/config.exs | 1 + .../global/controllers/status_controller.ex | 12 +- .../lib/ewallet_api/v1/auth/client_auth.ex | 2 +- .../transaction_consumption_channel.ex | 25 +- .../channels/transaction_request_channel.ex | 24 +- .../ewallet_api/v1/channels/user_channel.ex | 20 +- .../ewallet_api/v1/channels/wallet_channel.ex | 24 +- .../controllers/reset_password_controller.ex | 92 ++++ .../v1/controllers/self_controller.ex | 4 +- .../transaction_consumption_controller.ex | 6 +- .../v1/controllers/transaction_controller.ex | 4 +- .../transaction_request_controller.ex | 2 +- .../ewallet_api/v1/plugs/client_auth_plug.ex | 4 +- apps/ewallet_api/lib/ewallet_api/v1/router.ex | 3 + .../v1/views/reset_password_view.ex | 8 + .../lib/ewallet_api/v1/views/signup_view.ex | 3 +- apps/ewallet_api/mix.exs | 2 +- apps/ewallet_api/priv/spec.json | 504 ++++++++++++++++++ apps/ewallet_api/priv/spec.yaml | 64 +++ apps/ewallet_api/priv/swagger/swagger.yaml | 4 + apps/ewallet_api/priv/swagger/user/paths.yaml | 28 + .../priv/swagger/user/request_bodies.yaml | 48 ++ .../controllers/status_controller_test.exs | 6 +- .../ewallet_api/v1/auth/client_auth_test.exs | 18 +- .../v1/channels/address_channel_test.exs | 45 -- .../transaction_consumption_channel_test.exs | 46 +- .../transaction_request_channel_test.exs | 42 +- .../v1/channels/user_channel_test.exs | 70 +-- .../v1/channels/wallet_channel_test.exs | 35 ++ .../reset_password_controller_test.exs | 274 ++++++++++ .../v1/plugs/client_auth_plug_test.exs | 4 +- .../v1/views/reset_password_view_test.exs | 15 + apps/ewallet_api/test/support/channel_case.ex | 46 +- apps/ewallet_api/test/support/conn_case.ex | 31 +- apps/ewallet_api/test/test_helper.exs | 1 - apps/ewallet_config/config/config.exs | 46 +- .../lib/ewallet_config/config.ex | 30 +- .../lib/ewallet_config/setting.ex | 21 +- .../lib/ewallet_config/setting_validator.ex | 3 +- .../lib/ewallet_config/stored_setting.ex | 3 +- apps/ewallet_config/mix.exs | 2 +- .../test/ewallet_config/config_test.exs | 3 +- .../file_storage_settings_loader_test.exs | 3 + .../test/ewallet_config/setting_test.exs | 7 +- .../test/support/config_test_helper.ex | 5 +- .../test/support/schema_case.ex | 4 + .../lib/ewallet_db/forget_password_request.ex | 117 ++-- apps/ewallet_db/lib/ewallet_db/key.ex | 26 +- apps/ewallet_db/mix.exs | 2 +- ..._expires_at_to_forget_password_request.exs | 50 ++ .../priv/repo/seeds_settings/00_setting.exs | 39 +- .../forget_password_request_test.exs | 73 ++- apps/ewallet_db/test/ewallet_db/key_test.exs | 10 +- apps/ewallet_db/test/support/factory.ex | 3 +- apps/load_tester/mix.exs | 2 +- apps/local_ledger/mix.exs | 2 +- .../{encrypted => types}/map.ex | 0 apps/local_ledger_db/mix.exs | 2 +- .../url_dispatcher/lib/url_dispatcher/plug.ex | 2 +- apps/url_dispatcher/mix.exs | 2 +- .../test/url_dispatcher/plug_test.exs | 3 +- config/config.exs | 2 +- 106 files changed, 2672 insertions(+), 692 deletions(-) delete mode 100644 apps/admin_api/test/admin_api/v1/channels/address_channel_test.exs create mode 100644 apps/admin_api/test/admin_api/v1/channels/wallet_channel_test.exs create mode 100644 apps/admin_api/test/admin_api/v1/controllers/provider_auth/configuration_controller_test.exs create mode 100644 apps/ewallet/lib/ewallet/gates/reset_password_gate.ex create mode 100644 apps/ewallet/lib/ewallet/schedulers/forget_password_request_scheduler.ex create mode 100644 apps/ewallet/test/ewallet/gates/reset_password_gate_test.exs create mode 100644 apps/ewallet/test/ewallet/schedulers/forget_password_request_scheduler_test.exs create mode 100644 apps/ewallet_api/lib/ewallet_api/v1/controllers/reset_password_controller.ex create mode 100644 apps/ewallet_api/lib/ewallet_api/v1/views/reset_password_view.ex delete mode 100644 apps/ewallet_api/test/ewallet_api/v1/channels/address_channel_test.exs create mode 100644 apps/ewallet_api/test/ewallet_api/v1/channels/wallet_channel_test.exs create mode 100644 apps/ewallet_api/test/ewallet_api/v1/controllers/reset_password_controller_test.exs create mode 100644 apps/ewallet_api/test/ewallet_api/v1/views/reset_password_view_test.exs create mode 100644 apps/ewallet_db/priv/repo/migrations/20181128093519_add_used_at_and_expires_at_to_forget_password_request.exs rename apps/local_ledger_db/lib/local_ledger_db/{encrypted => types}/map.ex (100%) diff --git a/apps/admin_api/config/config.exs b/apps/admin_api/config/config.exs index dce7919f3..d7de30da1 100644 --- a/apps/admin_api/config/config.exs +++ b/apps/admin_api/config/config.exs @@ -47,6 +47,7 @@ config :admin_api, :generators, context_app: false # Maps an accept header to the respective router version. config :admin_api, :api_versions, %{ "application/vnd.omisego.v1+json" => %{ + name: "v1", router: AdminAPI.V1.Router, endpoint: AdminAPI.V1.Endpoint, websocket_serializer: EWallet.Web.V1.WebsocketResponseSerializer diff --git a/apps/admin_api/lib/admin_api/global/controllers/status_controller.ex b/apps/admin_api/lib/admin_api/global/controllers/status_controller.ex index b78f0e608..e7ee16e20 100644 --- a/apps/admin_api/lib/admin_api/global/controllers/status_controller.ex +++ b/apps/admin_api/lib/admin_api/global/controllers/status_controller.ex @@ -2,6 +2,18 @@ defmodule AdminAPI.StatusController do use AdminAPI, :controller def status(conn, _attrs) do - json(conn, %{success: true}) + json(conn, %{ + success: true, + api_versions: api_versions(), + ewallet_version: Application.get_env(:ewallet, :version) + }) + end + + defp api_versions do + api_versions = Application.get_env(:admin_api, :api_versions) + + Enum.map(api_versions, fn {key, value} -> + %{name: value[:name], media_type: key} + end) end end diff --git a/apps/admin_api/lib/admin_api/v1/channels/account_channel.ex b/apps/admin_api/lib/admin_api/v1/channels/account_channel.ex index ccbbafa5e..5c2e5f498 100644 --- a/apps/admin_api/lib/admin_api/v1/channels/account_channel.ex +++ b/apps/admin_api/lib/admin_api/v1/channels/account_channel.ex @@ -4,20 +4,14 @@ defmodule AdminAPI.V1.AccountChannel do Represents the account channel. """ use Phoenix.Channel, async: false - alias EWalletDB.Account + alias EWallet.AccountPolicy def join("account:" <> account_id, _params, %{assigns: %{auth: auth}} = socket) do - join_as(account_id, auth, socket) + case Bodyguard.permit(AccountPolicy, :join, auth, account_id) do + :ok -> {:ok, socket} + _ -> {:error, :forbidden_channel} + end end def join(_, _, _), do: {:error, :invalid_parameter} - - defp join_as(account_id, %{authenticated: true}, socket) do - account_id |> Account.get() |> respond(socket) - end - - defp join_as(_, _, _), do: {:error, :forbidden_channel} - - defp respond(nil, _socket), do: {:error, :channel_not_found} - defp respond(_account, socket), do: {:ok, socket} end diff --git a/apps/admin_api/lib/admin_api/v1/channels/transaction_consumption_channel.ex b/apps/admin_api/lib/admin_api/v1/channels/transaction_consumption_channel.ex index ccab60e35..648c6df43 100644 --- a/apps/admin_api/lib/admin_api/v1/channels/transaction_consumption_channel.ex +++ b/apps/admin_api/lib/admin_api/v1/channels/transaction_consumption_channel.ex @@ -5,6 +5,7 @@ defmodule AdminAPI.V1.TransactionConsumptionChannel do """ use Phoenix.Channel, async: false alias EWalletDB.TransactionConsumption + alias EWallet.TransactionConsumptionPolicy def join( "transaction_consumption:" <> consumption_id, @@ -13,16 +14,14 @@ defmodule AdminAPI.V1.TransactionConsumptionChannel do assigns: %{auth: auth} } = socket ) do - consumption_id - |> TransactionConsumption.get() - |> join_as(auth, socket) + with %TransactionConsumption{} = consumption <- + TransactionConsumption.get(consumption_id, preload: [:account, :wallet]), + :ok <- Bodyguard.permit(TransactionConsumptionPolicy, :join, auth, consumption) do + {:ok, socket} + else + _ -> {:error, :forbidden_channel} + end end def join(_, _, _), do: {:error, :invalid_parameter} - - defp join_as(nil, _auth, _socket), do: {:error, :channel_not_found} - - defp join_as(_consumption, %{authenticated: true}, socket) do - {:ok, socket} - end end diff --git a/apps/admin_api/lib/admin_api/v1/channels/transaction_request_channel.ex b/apps/admin_api/lib/admin_api/v1/channels/transaction_request_channel.ex index 77af3ac01..d4f508123 100644 --- a/apps/admin_api/lib/admin_api/v1/channels/transaction_request_channel.ex +++ b/apps/admin_api/lib/admin_api/v1/channels/transaction_request_channel.ex @@ -5,18 +5,16 @@ defmodule AdminAPI.V1.TransactionRequestChannel do """ use Phoenix.Channel, async: false alias EWalletDB.TransactionRequest + alias EWallet.TransactionRequestPolicy def join("transaction_request:" <> request_id, _params, %{assigns: %{auth: auth}} = socket) do - request_id - |> TransactionRequest.get() - |> join_as(auth, socket) + with %TransactionRequest{} = request <- TransactionRequest.get(request_id, preload: :wallet), + :ok <- Bodyguard.permit(TransactionRequestPolicy, :join, auth, request) do + {:ok, socket} + else + _ -> {:error, :forbidden_channel} + end end def join(_, _, _), do: {:error, :invalid_parameter} - - defp join_as(nil, _auth, _socket), do: {:error, :channel_not_found} - - defp join_as(_request, %{authenticated: true}, socket) do - {:ok, socket} - end end diff --git a/apps/admin_api/lib/admin_api/v1/channels/user_channel.ex b/apps/admin_api/lib/admin_api/v1/channels/user_channel.ex index bf4b635f7..febb5e0ef 100644 --- a/apps/admin_api/lib/admin_api/v1/channels/user_channel.ex +++ b/apps/admin_api/lib/admin_api/v1/channels/user_channel.ex @@ -5,6 +5,7 @@ defmodule AdminAPI.V1.UserChannel do """ use Phoenix.Channel, async: false alias EWalletDB.User + alias EWallet.UserPolicy def join( "user:" <> user_id, @@ -13,15 +14,13 @@ defmodule AdminAPI.V1.UserChannel do assigns: %{auth: auth} } = socket ) do - user = User.get(user_id) || User.get_by_provider_user_id(user_id) - join_as(user, auth, socket) + with %User{} = user <- User.get(user_id) || User.get_by_provider_user_id(user_id), + :ok <- Bodyguard.permit(UserPolicy, :join, auth, user) do + {:ok, socket} + else + _ -> {:error, :forbidden_channel} + end end def join(_, _, _), do: {:error, :invalid_parameter} - - defp join_as(nil, _auth, _socket), do: {:error, :channel_not_found} - - defp join_as(_user, %{authenticated: true}, socket) do - {:ok, socket} - end end diff --git a/apps/admin_api/lib/admin_api/v1/channels/wallet_channel.ex b/apps/admin_api/lib/admin_api/v1/channels/wallet_channel.ex index ba7ba8538..bfc3cc1d1 100644 --- a/apps/admin_api/lib/admin_api/v1/channels/wallet_channel.ex +++ b/apps/admin_api/lib/admin_api/v1/channels/wallet_channel.ex @@ -5,18 +5,16 @@ defmodule AdminAPI.V1.WalletChannel do """ use Phoenix.Channel, async: false alias EWalletDB.Wallet + alias EWallet.WalletPolicy def join("address:" <> address, _params, %{assigns: %{auth: auth}} = socket) do - address - |> Wallet.get() - |> join_as(auth, socket) + with %Wallet{} = wallet <- Wallet.get(address), + :ok <- Bodyguard.permit(WalletPolicy, :join, auth, wallet) do + {:ok, socket} + else + _ -> {:error, :forbidden_channel} + end end def join(_, _, _), do: {:error, :invalid_parameter} - - defp join_as(nil, _auth, _socket), do: {:error, :channel_not_found} - - defp join_as(_wallet, %{authenticated: true}, socket) do - {:ok, socket} - end end diff --git a/apps/admin_api/lib/admin_api/v1/controllers/key_controller.ex b/apps/admin_api/lib/admin_api/v1/controllers/key_controller.ex index 9490401ef..87a3556d4 100644 --- a/apps/admin_api/lib/admin_api/v1/controllers/key_controller.ex +++ b/apps/admin_api/lib/admin_api/v1/controllers/key_controller.ex @@ -105,7 +105,7 @@ defmodule AdminAPI.V1.KeyController do @spec delete(Plug.Conn.t(), map()) :: Plug.Conn.t() def delete(conn, %{"access_key" => access_key}) do with :ok <- permit(:delete, conn.assigns, nil) do - key = Key.get(:access_key, access_key) + key = Key.get_by(access_key: access_key) do_delete(conn, key) else {:error, code} -> diff --git a/apps/admin_api/lib/admin_api/v1/controllers/reset_password_controller.ex b/apps/admin_api/lib/admin_api/v1/controllers/reset_password_controller.ex index b11508334..0e6c2dcba 100644 --- a/apps/admin_api/lib/admin_api/v1/controllers/reset_password_controller.ex +++ b/apps/admin_api/lib/admin_api/v1/controllers/reset_password_controller.ex @@ -1,32 +1,36 @@ defmodule AdminAPI.V1.ResetPasswordController do use AdminAPI, :controller import AdminAPI.V1.ErrorHandler - alias EWallet.ForgetPasswordEmail alias Bamboo.Email - alias EWallet.Mailer + alias EWallet.{ForgetPasswordEmail, Mailer, ResetPasswordGate} alias EWallet.Web.UrlValidator - alias EWalletDB.{ForgetPasswordRequest, User} + @doc """ + Starts the reset password request flow for an admin. + """ @spec reset(Plug.Conn.t(), map()) :: Plug.Conn.t() def reset(conn, %{"email" => email, "redirect_url" => redirect_url}) when not is_nil(email) and not is_nil(redirect_url) do with {:ok, redirect_url} <- validate_redirect_url(redirect_url), - %User{} = user <- User.get_by_email(email) || :user_email_not_found, - {_, _} <- ForgetPasswordRequest.disable_all_for(user), - %ForgetPasswordRequest{} = request <- ForgetPasswordRequest.generate(user), - %Email{} = email_object <- ForgetPasswordEmail.create(request, redirect_url), - %Email{} <- Mailer.deliver_now(email_object) do + {:ok, request} <- ResetPasswordGate.request(email), + %Email{} <- send_request_email(request, redirect_url) do render(conn, :empty, %{success: true}) else + # Prevents attackers from gaining knowledge about a user's email. + {:error, :user_email_not_found} -> + render(conn, :empty, %{success: true}) + {:error, code, meta} -> handle_error(conn, code, meta) - error_code -> - handle_error(conn, error_code) + {:error, code} -> + handle_error(conn, code) end end - def reset(conn, _), do: handle_error(conn, :invalid_parameter) + def reset(conn, _) do + handle_error(conn, :invalid_parameter, "`email` and `redirect_url` are required") + end defp validate_redirect_url(url) do if UrlValidator.allowed_redirect_url?(url) do @@ -36,53 +40,43 @@ defmodule AdminAPI.V1.ResetPasswordController do end end + defp send_request_email(request, redirect_url) do + request + |> ForgetPasswordEmail.create(redirect_url) + |> Mailer.deliver_now() + end + + @doc """ + Completes the reset password request flow for an admin. + """ @spec update(Plug.Conn.t(), map()) :: Plug.Conn.t() - def update( - conn, - %{ - "email" => email, - "token" => token, - "password" => _, - "password_confirmation" => _ - } = attrs - ) do - with %User{} = user <- get_user(email), - %ForgetPasswordRequest{} = request <- get_request(user, token), - attrs <- Map.put(attrs, "originator", request), - {:ok, %User{} = user} <- update_password(request, attrs) do - _ = ForgetPasswordRequest.disable_all_for(user) - render(conn, :empty, %{success: true}) - else - error when is_atom(error) -> - handle_error(conn, error) + def update(conn, %{ + "email" => email, + "token" => token, + "password" => password, + "password_confirmation" => password_confirmation + }) do + case ResetPasswordGate.update(email, token, password, password_confirmation) do + {:ok, _user} -> + render(conn, :empty, %{success: true}) + + # Prevents attackers from gaining knowledge about a user's email. + {:error, :user_email_not_found} -> + handle_error(conn, :invalid_reset_token) + + {:error, code} when is_atom(code) -> + handle_error(conn, code) {:error, changeset} -> handle_error(conn, :invalid_parameter, changeset) end end - def update(conn, _), do: handle_error(conn, :invalid_parameter) - - defp get_user(email) do - User.get_by_email(email) || :user_email_not_found - end - - defp get_request(user, token) do - ForgetPasswordRequest.get(user, token) || :invalid_reset_token - end - - defp update_password(request, %{ - "password" => password, - "password_confirmation" => password_confirmation - }) do - User.update_password( - request.user, - %{ - password: password, - password_confirmation: password_confirmation, - originator: request - }, - ignore_current: true + def update(conn, _) do + handle_error( + conn, + :invalid_parameter, + "`email`, `token`, `password` and `password_confirmation` are required" ) end end diff --git a/apps/admin_api/lib/admin_api/v1/error_handler.ex b/apps/admin_api/lib/admin_api/v1/error_handler.ex index 77a950a00..357232a52 100644 --- a/apps/admin_api/lib/admin_api/v1/error_handler.ex +++ b/apps/admin_api/lib/admin_api/v1/error_handler.ex @@ -20,10 +20,6 @@ defmodule AdminAPI.V1.ErrorHandler do code: "access_key:unauthorized", description: "The current access key is not allowed to perform the requested operation." }, - invalid_reset_token: %{ - code: "forget_password:token_not_found", - description: "There are no password reset requests corresponding to the provided token." - }, invalid_email_update_token: %{ code: "email_update:token_not_found", description: "There are no email update requests corresponding to the provided token." @@ -32,10 +28,6 @@ defmodule AdminAPI.V1.ErrorHandler do code: "auth_token:not_found", description: "There is no auth token corresponding to the provided token." }, - user_email_not_found: %{ - code: "user:email_not_found", - description: "There is no user corresponding to the provided email." - }, account_id_not_found: %{ code: "account:id_not_found", description: "There is no account corresponding to the provided id." diff --git a/apps/admin_api/lib/admin_api/v1/views/account_membership_view.ex b/apps/admin_api/lib/admin_api/v1/views/account_membership_view.ex index 0bafdb1f1..7db173a80 100644 --- a/apps/admin_api/lib/admin_api/v1/views/account_membership_view.ex +++ b/apps/admin_api/lib/admin_api/v1/views/account_membership_view.ex @@ -10,7 +10,6 @@ defmodule AdminAPI.V1.AccountMembershipView do end def render("empty.json", %{success: success}) do - %{} - |> ResponseSerializer.serialize(success: success) + ResponseSerializer.serialize(%{}, success: success) end end diff --git a/apps/admin_api/lib/admin_api/v1/views/reset_password_view.ex b/apps/admin_api/lib/admin_api/v1/views/reset_password_view.ex index 49e3b5326..b865d1116 100644 --- a/apps/admin_api/lib/admin_api/v1/views/reset_password_view.ex +++ b/apps/admin_api/lib/admin_api/v1/views/reset_password_view.ex @@ -3,7 +3,6 @@ defmodule AdminAPI.V1.ResetPasswordView do alias EWallet.Web.V1.ResponseSerializer def render("empty.json", %{success: success}) do - %{} - |> ResponseSerializer.serialize(success: success) + ResponseSerializer.serialize(%{}, success: success) end end diff --git a/apps/admin_api/mix.exs b/apps/admin_api/mix.exs index bde22fa4d..8a7b3fe14 100644 --- a/apps/admin_api/mix.exs +++ b/apps/admin_api/mix.exs @@ -4,7 +4,7 @@ defmodule AdminAPI.Mixfile do def project do [ app: :admin_api, - version: "0.1.0-beta", + version: Application.get_env(:ewallet, :version), build_path: "../../_build", config_path: "../../config/config.exs", deps_path: "../../deps", diff --git a/apps/admin_api/test/admin_api/global/controllers/status_controller_test.exs b/apps/admin_api/test/admin_api/global/controllers/status_controller_test.exs index 3f838e839..ac0c1f3d1 100644 --- a/apps/admin_api/test/admin_api/global/controllers/status_controller_test.exs +++ b/apps/admin_api/test/admin_api/global/controllers/status_controller_test.exs @@ -8,7 +8,13 @@ defmodule AdminAPI.StatusControllerTest do |> get(@base_dir <> "/") |> json_response(:ok) - assert response == %{"success" => true} + assert response == %{ + "success" => true, + "ewallet_version" => "1.1.0", + "api_versions" => [ + %{"name" => "v1", "media_type" => "application/vnd.omisego.v1+json"} + ] + } end end end diff --git a/apps/admin_api/test/admin_api/v1/channels/account_channel_test.exs b/apps/admin_api/test/admin_api/v1/channels/account_channel_test.exs index e93603551..e553a580e 100644 --- a/apps/admin_api/test/admin_api/v1/channels/account_channel_test.exs +++ b/apps/admin_api/test/admin_api/v1/channels/account_channel_test.exs @@ -2,28 +2,62 @@ defmodule AdminAPI.V1.AccountChannelTest do use AdminAPI.ChannelCase, async: false alias AdminAPI.V1.AccountChannel + alias EWalletDB.Account + alias Ecto.UUID - describe "join/3 as provider" do - test "joins the channel with authenticated account" do - account = insert(:account) + defp topic(id), do: "account:#{id}" - {res, _, socket} = - "test" - |> socket(%{auth: %{authenticated: true, account: account}}) - |> subscribe_and_join(AccountChannel, "account:#{account.id}") + describe "join/3" do + test "can join the channel of the current account" do + master = Account.get_master_account() + topic = topic(master.id) - assert res == :ok - assert socket.topic == "account:#{account.id}" + test_with_auths(fn auth -> + auth + |> subscribe_and_join(AccountChannel, topic) + |> assert_success(topic) + end) end - test "can't join a channel for an inexisting account" do - {res, code} = - "test" - |> socket(%{auth: %{authenticated: true}}) - |> subscribe_and_join(AccountChannel, "account:123") + test "can join the channel of an account that is a child of the current account" do + master = Account.get_master_account() + account = insert(:account, %{parent: master}) + topic = topic(account.id) - assert res == :error - assert code == :channel_not_found + test_with_auths(fn auth -> + auth + |> subscribe_and_join(AccountChannel, topic) + |> assert_success(topic) + end) + end + + test "can't join the channel of a parrent account" do + master_account = Account.get_master_account() + account = insert(:account, %{parent: master_account}) + role = insert(:role, %{name: "some_role"}) + admin = insert(:admin) + insert(:membership, %{user: admin, account: account, role: role}) + insert(:key, %{account: account, access_key: "a_sub_key", secret_key: "123"}) + + topic = topic(master_account.id) + + test_with_auths( + fn auth -> + auth + |> subscribe_and_join(AccountChannel, topic) + |> assert_failure(:forbidden_channel) + end, + admin.id, + "a_sub_key" + ) + end + + test "can't join the channel of an inexisting account" do + test_with_auths(fn auth -> + auth + |> subscribe_and_join(AccountChannel, topic(UUID.generate())) + |> assert_failure(:forbidden_channel) + end) end end end diff --git a/apps/admin_api/test/admin_api/v1/channels/address_channel_test.exs b/apps/admin_api/test/admin_api/v1/channels/address_channel_test.exs deleted file mode 100644 index 86f8f225d..000000000 --- a/apps/admin_api/test/admin_api/v1/channels/address_channel_test.exs +++ /dev/null @@ -1,32 +0,0 @@ -# credo:disable-for-this-file -defmodule AdminAPI.V1.WalletChannelTest do - use AdminAPI.ChannelCase, async: false - alias AdminAPI.V1.WalletChannel - - describe "join/3 as provider" do - test "joins the channel with authenticated account and valid address" do - wallet = insert(:wallet) - account = insert(:account) - - {res, _, socket} = - "test" - |> socket(%{auth: %{authenticated: true, account: account}}) - |> subscribe_and_join(WalletChannel, "address:#{wallet.address}") - - assert res == :ok - assert socket.topic == "address:#{wallet.address}" - end - - test "can't join a channel for an inexisting address" do - account = insert(:account) - - {res, code} = - "test" - |> socket(%{auth: %{authenticated: true, account: account}}) - |> subscribe_and_join(WalletChannel, "address:none000000000000") - - assert res == :error - assert code == :channel_not_found - end - end -end diff --git a/apps/admin_api/test/admin_api/v1/channels/transaction_consumption_channel_test.exs b/apps/admin_api/test/admin_api/v1/channels/transaction_consumption_channel_test.exs index 9cddb8b8f..f6f840313 100644 --- a/apps/admin_api/test/admin_api/v1/channels/transaction_consumption_channel_test.exs +++ b/apps/admin_api/test/admin_api/v1/channels/transaction_consumption_channel_test.exs @@ -2,34 +2,77 @@ defmodule AdminAPI.V1.TransactionConsumptionChannelTest do use AdminAPI.ChannelCase, async: false alias AdminAPI.V1.TransactionConsumptionChannel + alias EWalletDB.Account + alias Ecto.UUID - describe "join/3 as provider" do - test "joins the channel with authenticated account and valid consumption" do - account = insert(:account) - consumption = insert(:transaction_consumption) - - {res, _, socket} = - "test" - |> socket(%{auth: %{authenticated: true, account: account}}) - |> subscribe_and_join( - TransactionConsumptionChannel, - "transaction_consumption:#{consumption.id}" - ) - - assert res == :ok - assert socket.topic == "transaction_consumption:#{consumption.id}" + defp topic(id), do: "transaction_consumption:#{id}" + + describe "join/3" do + test "can join the channel of a valid user's consumption" do + consumption = insert(:transaction_consumption, %{account: nil}) + topic = topic(consumption.id) + + test_with_auths(fn auth -> + auth + |> subscribe_and_join(TransactionConsumptionChannel, topic) + |> assert_success(topic) + end) + end + + test "can join the channel of a valid account's consumption" do + master = Account.get_master_account() + consumption = insert(:transaction_consumption, %{account: master}) + topic = topic(consumption.id) + + test_with_auths(fn auth -> + auth + |> subscribe_and_join(TransactionConsumptionChannel, topic) + |> assert_success(topic) + end) end - test "can't join a channel for an inexisting consumption" do - account = insert(:account) + test "can join the channel of an account's consumption that is a child of the current account" do + master = Account.get_master_account() + account = insert(:account, %{parent: master}) + consumption = insert(:transaction_consumption, %{account: account}) + topic = topic(consumption.id) + + test_with_auths(fn auth -> + auth + |> subscribe_and_join(TransactionConsumptionChannel, topic) + |> assert_success(topic) + end) + end + + test "can't join the channel of an account's consumption that is a parrent account" do + master_account = Account.get_master_account() + account = insert(:account, %{parent: master_account}) + role = insert(:role, %{name: "some_role"}) + admin = insert(:admin) + insert(:membership, %{user: admin, account: account, role: role}) + insert(:key, %{account: account, access_key: "a_sub_key", secret_key: "123"}) + consumption = insert(:transaction_consumption, %{account: master_account}) + topic = topic(consumption.id) + + test_with_auths( + fn auth -> + auth + |> subscribe_and_join(TransactionConsumptionChannel, topic) + |> assert_failure(:forbidden_channel) + end, + admin.id, + "a_sub_key" + ) + end - {res, code} = - "test" - |> socket(%{auth: %{authenticated: true, account: account}}) - |> subscribe_and_join(TransactionConsumptionChannel, "transaction_consumption:123") + test "can't join the channel of an inexisting consumption" do + topic = topic(UUID.generate()) - assert res == :error - assert code == :channel_not_found + test_with_auths(fn auth -> + auth + |> subscribe_and_join(TransactionConsumptionChannel, topic) + |> assert_failure(:forbidden_channel) + end) end end end diff --git a/apps/admin_api/test/admin_api/v1/channels/transaction_request_channel_test.exs b/apps/admin_api/test/admin_api/v1/channels/transaction_request_channel_test.exs index 5c12ecc2c..0fdd60af1 100644 --- a/apps/admin_api/test/admin_api/v1/channels/transaction_request_channel_test.exs +++ b/apps/admin_api/test/admin_api/v1/channels/transaction_request_channel_test.exs @@ -2,31 +2,65 @@ defmodule AdminAPI.V1.TransactionRequestChannelTest do use AdminAPI.ChannelCase, async: false alias AdminAPI.V1.TransactionRequestChannel + alias EWalletDB.Account + alias Ecto.UUID - describe "join/3 as provider" do - test "joins the channel with authenticated account and valid request" do - account = insert(:account) + defp topic(id), do: "transaction_request:#{id}" + + describe "join/3" do + test "can join the channel of a valid request" do request = insert(:transaction_request) + topic = topic(request.id) + + test_with_auths(fn auth -> + auth + |> subscribe_and_join(TransactionRequestChannel, topic) + |> assert_success(topic) + end) + end - {res, _, socket} = - "test" - |> socket(%{auth: %{authenticated: true, account: account}}) - |> subscribe_and_join(TransactionRequestChannel, "transaction_request:#{request.id}") + test "can join the channel of an account's request that is a child of the current account" do + master = Account.get_master_account() + account = insert(:account, %{parent: master}) + request = insert(:transaction_request, %{account: account}) + topic = topic(request.id) - assert res == :ok - assert socket.topic == "transaction_request:#{request.id}" + test_with_auths(fn auth -> + auth + |> subscribe_and_join(TransactionRequestChannel, topic) + |> assert_success(topic) + end) end - test "can't join a channel for an inexisting request" do - account = insert(:account) + test "can join the channel of an account's request that is a parrent account" do + master_account = Account.get_master_account() + account = insert(:account, %{parent: master_account}) + role = insert(:role, %{name: "some_role"}) + admin = insert(:admin) + insert(:membership, %{user: admin, account: account, role: role}) + insert(:key, %{account: account, access_key: "a_sub_key", secret_key: "123"}) + request = insert(:transaction_request, %{account: master_account}) + topic = topic(request.id) + + test_with_auths( + fn auth -> + auth + |> subscribe_and_join(TransactionRequestChannel, topic) + |> assert_success(topic) + end, + admin.id, + "a_sub_key" + ) + end - {res, code} = - "test" - |> socket(%{auth: %{authenticated: true, account: account}}) - |> subscribe_and_join(TransactionRequestChannel, "transaction_request:123") + test "can't join the channel of an inexisting request" do + topic = topic(UUID.generate()) - assert res == :error - assert code == :channel_not_found + test_with_auths(fn auth -> + auth + |> subscribe_and_join(TransactionRequestChannel, topic) + |> assert_failure(:forbidden_channel) + end) end end end diff --git a/apps/admin_api/test/admin_api/v1/channels/user_channel_test.exs b/apps/admin_api/test/admin_api/v1/channels/user_channel_test.exs index 166bd42e2..902406633 100644 --- a/apps/admin_api/test/admin_api/v1/channels/user_channel_test.exs +++ b/apps/admin_api/test/admin_api/v1/channels/user_channel_test.exs @@ -3,44 +3,45 @@ defmodule AdminAPI.V1.UserChannelTest do use AdminAPI.ChannelCase, async: false alias AdminAPI.V1.UserChannel alias EWalletDB.User + alias Ecto.UUID - describe "join/3 as provider" do - test "joins the channel with authenticated account and valid user ID" do - account = insert(:account) + defp topic(id), do: "user:#{id}" + + describe "join/3" do + test "can join the channel of a valid user ID" do {:ok, user} = :user |> params_for() |> User.insert() - {res, _, socket} = - "test" - |> socket(%{auth: %{authenticated: true, account: account}}) - |> subscribe_and_join(UserChannel, "user:#{user.id}") + topic = topic(user.id) - assert res == :ok - assert socket.topic == "user:#{user.id}" + test_with_auths(fn auth -> + auth + |> subscribe_and_join(UserChannel, topic) + |> assert_success(topic) + end) end - test "joins the channel with authenticated account and valid provider user ID" do - account = insert(:account) + test "can join the channel of a valid provider user ID" do {:ok, user} = :user |> params_for() |> User.insert() - - {res, _, socket} = - "test" - |> socket(%{auth: %{authenticated: true, account: account}}) - |> subscribe_and_join(UserChannel, "user:#{user.provider_user_id}") - - assert res == :ok - assert socket.topic == "user:#{user.provider_user_id}" + topic = topic(user.provider_user_id) + + test_with_auths(fn auth -> + auth + |> subscribe_and_join( + UserChannel, + topic + ) + |> assert_success(topic) + end) end - test "can't join a channel for an inexisting user" do - account = insert(:account) - - {res, code} = - "test" - |> socket(%{auth: %{authenticated: true, account: account}}) - |> subscribe_and_join(UserChannel, "user:123") + test "can't join the channel of an inexisting user" do + topic = topic(UUID.generate()) - assert res == :error - assert code == :channel_not_found + test_with_auths(fn auth -> + auth + |> subscribe_and_join(UserChannel, topic) + |> assert_failure(:forbidden_channel) + end) end end end diff --git a/apps/admin_api/test/admin_api/v1/channels/wallet_channel_test.exs b/apps/admin_api/test/admin_api/v1/channels/wallet_channel_test.exs new file mode 100644 index 000000000..a34858ac1 --- /dev/null +++ b/apps/admin_api/test/admin_api/v1/channels/wallet_channel_test.exs @@ -0,0 +1,121 @@ +# credo:disable-for-this-file +defmodule AdminAPI.V1.WalletChannelTest do + use AdminAPI.ChannelCase, async: false + alias AdminAPI.V1.WalletChannel + alias EWalletDB.{Account, AccountUser} + alias ActivityLogger.System + + defp topic(address), do: "address:#{address}" + + describe "join/3" do + test "can join the channel of a current account's user's wallet" do + user = insert(:user) + account = Account.get_master_account() + {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) + + wallet = insert(:wallet, %{user: user}) + topic = topic(wallet.address) + + test_with_auths(fn auth -> + auth + |> subscribe_and_join(WalletChannel, topic) + |> assert_success(topic) + end) + end + + test "can join the channel of a child account's user's wallet" do + user = insert(:user) + master = Account.get_master_account() + account = insert(:account, %{parent: master}) + {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) + + wallet = insert(:wallet, %{user: user}) + topic = topic(wallet.address) + + test_with_auths(fn auth -> + auth + |> subscribe_and_join(WalletChannel, topic) + |> assert_success(topic) + end) + end + + test "can't join the channel of a parent account's user's wallet" do + user = insert(:user) + master_account = Account.get_master_account() + {:ok, _} = AccountUser.link(master_account.uuid, user.uuid, %System{}) + wallet = insert(:wallet, %{user: user}) + + account = insert(:account, %{parent: master_account}) + role = insert(:role, %{name: "some_role"}) + admin = insert(:admin) + insert(:membership, %{user: admin, account: account, role: role}) + insert(:key, %{account: account, access_key: "a_sub_key", secret_key: "123"}) + topic = topic(wallet.address) + + test_with_auths( + fn auth -> + auth + |> subscribe_and_join(WalletChannel, topic) + |> assert_failure(:forbidden_channel) + end, + admin.id, + "a_sub_key" + ) + end + + test "can join the channel of the current account's wallet" do + wallet = Account.get_master_account() |> Account.get_primary_wallet() + topic = topic(wallet.address) + + test_with_auths(fn auth -> + auth + |> subscribe_and_join(WalletChannel, topic) + |> assert_success(topic) + end) + end + + test "can join the channel of a child's account's wallet" do + {:ok, account} = :account |> params_for() |> Account.insert() + wallet = Account.get_primary_wallet(account) + topic = topic(wallet.address) + + test_with_auths(fn auth -> + auth + |> subscribe_and_join(WalletChannel, topic) + |> assert_success(topic) + end) + end + + test "can't join the channel of a parent's account's wallet" do + master_account = Account.get_master_account() + wallet = Account.get_primary_wallet(master_account) + + account = insert(:account, %{parent: master_account}) + role = insert(:role, %{name: "some_role"}) + admin = insert(:admin) + insert(:membership, %{user: admin, account: account, role: role}) + insert(:key, %{account: account, access_key: "a_sub_key", secret_key: "123"}) + topic = topic(wallet.address) + + test_with_auths( + fn auth -> + auth + |> subscribe_and_join(WalletChannel, topic) + |> assert_failure(:forbidden_channel) + end, + admin.id, + "a_sub_key" + ) + end + + test "can't join the channel of an inexisting wallet" do + topic = topic("none000000000000") + + test_with_auths(fn auth -> + auth + |> subscribe_and_join(WalletChannel, topic) + |> assert_failure(:forbidden_channel) + end) + end + end +end diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/configuration_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/configuration_controller_test.exs index da5619376..0178daba0 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/configuration_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/configuration_controller_test.exs @@ -1,5 +1,6 @@ defmodule AdminAPI.V1.AdminAuth.ConfigurationControllerTest do use AdminAPI.ConnCase, async: true + alias EWalletConfig.Config describe "/configuration.get" do test "returns a list of settings and pagination data" do @@ -26,18 +27,20 @@ defmodule AdminAPI.V1.AdminAuth.ConfigurationControllerTest do sort_dir: "asc" }) + default_settings = Application.get_env(:ewallet_config, :default_settings) + assert response["success"] == true - assert length(response["data"]["data"]) == 19 - assert response["data"]["pagination"]["count"] == 19 + assert length(response["data"]["data"]) == Enum.count(default_settings) + assert response["data"]["pagination"]["count"] == Enum.count(default_settings) first_setting = Enum.at(response["data"]["data"], 0) last_setting = Enum.at(response["data"]["data"], -1) assert first_setting["key"] == "base_url" - assert first_setting["position"] == 1 + assert first_setting["position"] == default_settings["base_url"].position assert last_setting["key"] == "aws_secret_access_key" - assert last_setting["position"] == 19 + assert last_setting["position"] == default_settings["aws_secret_access_key"].position end end @@ -54,6 +57,25 @@ defmodule AdminAPI.V1.AdminAuth.ConfigurationControllerTest do assert response["data"]["data"]["base_url"]["value"] == "new_base_url.example" end + test "updates a list of settings", meta do + response = + admin_user_request("/configuration.update", %{ + "aws_access_key_id" => "asd", + "aws_bucket" => "asd", + "aws_region" => "asdz", + "aws_secret_access_key" => "asdasdasdasdasd", + "config_pid" => meta[:config_pid] + }) + + assert response["success"] == true + data = response["data"]["data"] + + assert data["aws_access_key_id"]["value"] == "asd" + assert data["aws_bucket"]["value"] == "asd" + assert data["aws_region"]["value"] == "asdz" + assert data["aws_secret_access_key"]["value"] == "asdasdasdasdasd" + end + test "updates a list of settings with failures", meta do response = admin_user_request("/configuration.update", %{ diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/reset_password_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/reset_password_controller_test.exs index 1e6770100..36405721f 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/reset_password_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/reset_password_controller_test.exs @@ -55,15 +55,17 @@ defmodule AdminAPI.V1.AdminAuth.ResetPasswordControllerTest do assert response["data"]["code"] == "client:invalid_parameter" end - test "returns an error if no user is found with the associated email" do + test "returns a success without a new request, when the given email is not found" do + num_requests = Repo.aggregate(ForgetPasswordRequest, :count, :token) + response = unauthenticated_request("/admin.reset_password", %{ "email" => "example@mail.com", "redirect_url" => @redirect_url }) - assert response["success"] == false - assert response["data"]["code"] == "user:email_not_found" + assert response["success"] == true + assert Repo.aggregate(ForgetPasswordRequest, :count, :token) == num_requests end test "returns an error if the email is not supplied" do @@ -124,9 +126,9 @@ defmodule AdminAPI.V1.AdminAuth.ResetPasswordControllerTest do assert ForgetPasswordRequest.all_active() |> length() == 0 end - test "returns an email_not_found error when the user is not found" do + test "returns an token_not_found error when the user is not found" do {:ok, user} = :admin |> params_for() |> User.insert() - request = ForgetPasswordRequest.generate(user) + {:ok, request} = ForgetPasswordRequest.generate(user) response = unauthenticated_request("/admin.update_password", %{ @@ -137,7 +139,7 @@ defmodule AdminAPI.V1.AdminAuth.ResetPasswordControllerTest do }) assert response["success"] == false - assert response["data"]["code"] == "user:email_not_found" + assert response["data"]["code"] == "forget_password:token_not_found" assert ForgetPasswordRequest |> Repo.all() |> length() == 1 end @@ -162,7 +164,7 @@ defmodule AdminAPI.V1.AdminAuth.ResetPasswordControllerTest do test "returns a client:invalid_parameter error when the password is too short" do {:ok, user} = :admin |> params_for() |> User.insert() - request = ForgetPasswordRequest.generate(user) + {:ok, request} = ForgetPasswordRequest.generate(user) assert user.password_hash != Crypto.hash_password("password") @@ -185,7 +187,7 @@ defmodule AdminAPI.V1.AdminAuth.ResetPasswordControllerTest do test "returns an invalid parameter error when the email is not sent" do {:ok, user} = :admin |> params_for() |> User.insert() - request = ForgetPasswordRequest.generate(user) + {:ok, request} = ForgetPasswordRequest.generate(user) assert user.password_hash != Crypto.hash_password("password") diff --git a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/configuration_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/configuration_controller_test.exs new file mode 100644 index 000000000..d0c283d44 --- /dev/null +++ b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/configuration_controller_test.exs @@ -0,0 +1,160 @@ +defmodule AdminAPI.V1.ProviderAuth.ConfigurationControllerTest do + use AdminAPI.ConnCase, async: true + alias EWalletConfig.Config + + describe "/configuration.get" do + test "returns a list of settings and pagination data" do + response = provider_request("/configuration.get", %{}) + + # Asserts return data + assert response["success"] + assert response["data"]["object"] == "list" + assert is_list(response["data"]["data"]) + + # Asserts pagination data + pagination = response["data"]["pagination"] + assert is_integer(pagination["per_page"]) + assert is_integer(pagination["current_page"]) + assert is_boolean(pagination["is_last_page"]) + assert is_boolean(pagination["is_first_page"]) + end + + test "returns a list of settings" do + response = + provider_request("/configuration.get", %{ + per_page: 100, + sort_by: "position", + sort_dir: "asc" + }) + + default_settings = Application.get_env(:ewallet_config, :default_settings) + + assert response["success"] == true + assert length(response["data"]["data"]) == Enum.count(default_settings) + assert response["data"]["pagination"]["count"] == Enum.count(default_settings) + + first_setting = Enum.at(response["data"]["data"], 0) + last_setting = Enum.at(response["data"]["data"], -1) + + assert first_setting["key"] == "base_url" + assert first_setting["position"] == default_settings["base_url"].position + + assert last_setting["key"] == "aws_secret_access_key" + assert last_setting["position"] == default_settings["aws_secret_access_key"].position + end + end + + describe "/configuration.update" do + test "updates one setting", meta do + response = + provider_request("/configuration.update", %{ + base_url: "new_base_url.example", + config_pid: meta[:config_pid] + }) + + assert response["success"] == true + assert response["data"]["data"]["base_url"] != nil + assert response["data"]["data"]["base_url"]["value"] == "new_base_url.example" + end + + test "updates a list of settings", meta do + response = + provider_request("/configuration.update", %{ + "aws_access_key_id" => "asd", + "aws_bucket" => "asd", + "aws_region" => "asdz", + "aws_secret_access_key" => "asdasdasdasdasd", + "config_pid" => meta[:config_pid] + }) + + assert response["success"] == true + data = response["data"]["data"] + + assert data["aws_access_key_id"]["value"] == "asd" + assert data["aws_bucket"]["value"] == "asd" + assert data["aws_region"]["value"] == "asdz" + assert data["aws_secret_access_key"]["value"] == "asdasdasdasdasd" + end + + test "updates a list of settings with failures", meta do + response = + provider_request("/configuration.update", %{ + base_url: "new_base_url.example", + redirect_url_prefixes: ["new_base_url.example", "something.else"], + fake_setting: "my_value", + max_per_page: true, + email_adapter: "fake", + config_pid: meta[:config_pid] + }) + + assert response["success"] == true + data = response["data"]["data"] + + assert data["base_url"] != nil + assert data["base_url"]["value"] == "new_base_url.example" + + assert data["redirect_url_prefixes"] != nil + assert data["redirect_url_prefixes"]["value"] == ["new_base_url.example", "something.else"] + + assert data["email_adapter"] == %{ + "code" => "client:invalid_parameter", + "description" => + "Invalid parameter provided. `value` must be one of 'smtp', 'local', 'test'.", + "messages" => %{"value" => ["value_not_allowed"]}, + "object" => "error" + } + + assert data["fake_setting"] == %{ + "code" => "setting:not_found", + "object" => "error", + "description" => "The setting could not be inserted.", + "messages" => nil + } + + assert data["max_per_page"] == %{ + "code" => "client:invalid_parameter", + "description" => "Invalid parameter provided. `value` must be of type 'integer'.", + "messages" => %{"value" => ["invalid_type_for_value"]}, + "object" => "error" + } + end + + test "reloads app env", meta do + response = + provider_request("/configuration.update", %{ + base_url: "new_base_url.example", + config_pid: meta[:config_pid] + }) + + assert response["success"] == true + + assert Application.get_env(:admin_api, :base_url, "new_base_url.example") + end + + test "generates an activity log", meta do + timestamp = DateTime.utc_now() + + response = + provider_request("/configuration.update", %{ + base_url: "new_base_url.example", + config_pid: meta[:config_pid] + }) + + assert response["success"] == true + setting = Config.get_setting(:base_url) + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_key(), + target: setting, + changes: %{"data" => %{"value" => "new_base_url.example"}, "position" => setting.position}, + encrypted_changes: %{} + ) + end + end +end diff --git a/apps/admin_api/test/support/channel_case.ex b/apps/admin_api/test/support/channel_case.ex index 38ad3844b..4e9c4e26c 100644 --- a/apps/admin_api/test/support/channel_case.ex +++ b/apps/admin_api/test/support/channel_case.ex @@ -14,8 +14,23 @@ defmodule AdminAPI.ChannelCase do """ use ExUnit.CaseTemplate, async: false + use Phoenix.ChannelTest + import EWalletDB.Factory alias Ecto.Adapters.SQL.Sandbox alias EWalletConfig.ConfigTestHelper + alias EWalletDB.{Key, Account, User} + alias Utils.{Types.ExternalID, Helpers.Crypto} + + # Attributes for provider calls + @access_key "test_access_key" + @secret_key "test_secret_key" + + @endpoint AdminAPI.V1.Endpoint + + # Attributes for user calls + @admin_id ExternalID.generate("usr_") + @password "test_password" + @user_email "email@example.com" using do quote do @@ -23,13 +38,25 @@ defmodule AdminAPI.ChannelCase do use Phoenix.ChannelTest alias Ecto.Adapters.SQL.Sandbox import EWalletDB.Factory + import AdminAPI.ChannelCase # The default endpoint for testing @endpoint AdminAPI.V1.Endpoint + + @access_key unquote(@access_key) + @secret_key unquote(@secret_key) + + @admin_id unquote(@admin_id) + @password unquote(@password) + @user_email unquote(@user_email) end end setup tags do + # Restarts `EWalletConfig.Config` so it does not hang on to a DB connection for too long. + Supervisor.terminate_child(EWalletConfig.Supervisor, EWalletConfig.Config) + Supervisor.restart_child(EWalletConfig.Supervisor, EWalletConfig.Config) + :ok = Sandbox.checkout(EWalletDB.Repo) :ok = Sandbox.checkout(LocalLedgerDB.Repo) :ok = Sandbox.checkout(EWalletConfig.Repo) @@ -42,18 +69,64 @@ defmodule AdminAPI.ChannelCase do Sandbox.mode(ActivityLogger.Repo, {:shared, self()}) end - pid = - ConfigTestHelper.restart_config_genserver( - self(), - EWalletConfig.Repo, - [:ewallet_db, :ewallet, :admin_api], - %{ - "base_url" => "http://localhost:4000", - "email_adapter" => "test", - "sender_email" => "admin@example.com" - } - ) - - %{config_pid: pid} + config_pid = start_supervised!(EWalletConfig.Config) + + ConfigTestHelper.restart_config_genserver( + self(), + config_pid, + EWalletConfig.Repo, + [:ewallet_db, :ewallet, :admin_api], + %{ + "base_url" => "http://localhost:4000", + "email_adapter" => "test", + "sender_email" => "admin@example.com" + } + ) + + {:ok, account} = :account |> params_for(parent: nil) |> Account.insert() + + admin = + insert(:admin, %{ + id: @admin_id, + email: @user_email, + password_hash: Crypto.hash_password(@password) + }) + + role = insert(:role, %{name: "admin"}) + _membership = insert(:membership, %{user: admin, role: role, account: account}) + + :key + |> params_for(%{ + account: account, + access_key: @access_key, + secret_key: @secret_key + }) + |> Key.insert() + + %{config_pid: config_pid} + end + + def admin_auth_socket(admin_id \\ @admin_id) do + socket("test", %{auth: %{authenticated: true, admin_user: User.get(admin_id)}}) + end + + def key_auth_socket(access_key \\ @access_key) do + socket("test", %{ + auth: %{authenticated: true, key: Key.get_by(%{access_key: access_key}, preload: :account)} + }) + end + + def test_with_auths(func, admin_id \\ @admin_id, access_key \\ @access_key) do + Enum.each([admin_auth_socket(admin_id), key_auth_socket(access_key)], func) + end + + def assert_success({res, _, socket}, topic) do + assert res == :ok + assert socket.topic == topic + end + + def assert_failure({res, code}, error) do + assert res == :error + assert code == error end end diff --git a/apps/admin_api/test/support/conn_case.ex b/apps/admin_api/test/support/conn_case.ex index abcb89468..03dde08ed 100644 --- a/apps/admin_api/test/support/conn_case.ex +++ b/apps/admin_api/test/support/conn_case.ex @@ -22,8 +22,8 @@ defmodule AdminAPI.ConnCase do alias EWallet.Web.Date alias EWalletConfig.ConfigTestHelper alias EWalletDB.{Account, Key, Repo, User} - alias ActivityLogger.{ActivityLog, System} alias Utils.{Types.ExternalID, Helpers.Crypto} + alias ActivityLogger.System # Attributes required by Phoenix.ConnTest @endpoint AdminAPI.Endpoint @@ -60,6 +60,8 @@ defmodule AdminAPI.ConnCase do import AdminAPI.Router.Helpers import EWalletDB.Factory + import ActivityLogger.ActivityLoggerTestHelper + # Reiterate all module attributes from `AdminAPI.ConnCase` @endpoint unquote(@endpoint) @@ -84,6 +86,10 @@ defmodule AdminAPI.ConnCase do end setup tags do + # Restarts `EWalletConfig.Config` so it does not hang on to a DB connection for too long. + Supervisor.terminate_child(EWalletConfig.Supervisor, EWalletConfig.Config) + Supervisor.restart_child(EWalletConfig.Supervisor, EWalletConfig.Config) + :ok = Sandbox.checkout(EWalletDB.Repo) :ok = Sandbox.checkout(LocalLedgerDB.Repo) :ok = Sandbox.checkout(EWalletConfig.Repo) @@ -96,17 +102,19 @@ defmodule AdminAPI.ConnCase do Sandbox.mode(ActivityLogger.Repo, {:shared, self()}) end - pid = - ConfigTestHelper.restart_config_genserver( - self(), - EWalletConfig.Repo, - [:ewallet_db, :ewallet, :admin_api], - %{ - "base_url" => "http://localhost:4000", - "email_adapter" => "test", - "sender_email" => "admin@example.com" - } - ) + config_pid = start_supervised!(EWalletConfig.Config) + + ConfigTestHelper.restart_config_genserver( + self(), + config_pid, + EWalletConfig.Repo, + [:ewallet_db, :ewallet, :admin_api], + %{ + "base_url" => "http://localhost:4000", + "email_adapter" => "test", + "sender_email" => "admin@example.com" + } + ) # Insert account via `Account.insert/1` instead of the test factory to initialize wallets, etc. {:ok, account} = :account |> params_for(parent: nil) |> Account.insert() @@ -151,7 +159,7 @@ defmodule AdminAPI.ConnCase do # by returning {:ok, context_map}. But it would make the code # much less readable, i.e. `test "my test name", context do`, # and access using `context[:attribute]`. - %{config_pid: pid} + %{config_pid: config_pid} end def stringify_keys(%NaiveDateTime{} = value) do @@ -171,6 +179,7 @@ defmodule AdminAPI.ConnCase do """ def get_test_admin, do: User.get(@admin_id) def get_test_user, do: User.get_by_provider_user_id(@provider_user_id) + def get_test_key, do: Key.get_by(%{access_key: @access_key}) @doc """ Returns the last inserted record of the given schema. @@ -181,27 +190,6 @@ defmodule AdminAPI.ConnCase do |> Repo.one() end - def get_last_activity_log(schema) do - type = ActivityLog.get_type(schema.__struct__) - - ActivityLog - |> last(:inserted_at) - |> where(target_type: ^type, target_uuid: ^schema.uuid) - |> ActivityLogger.Repo.one() - end - - def get_all_activity_logs(schema) do - type = ActivityLog.get_type(schema.__struct__) - - ActivityLog - |> order_by(desc: :inserted_at) - |> where(target_type: ^type) - |> ActivityLogger.Repo.all() - end - - def assert_last_activity_log(action, expected_originator, expected_target) do - end - def mint!(token, amount \\ 1_000_000, originator \\ %System{}) do {:ok, mint, _transaction} = MintGate.insert(%{ diff --git a/apps/admin_panel/mix.exs b/apps/admin_panel/mix.exs index 64a03df71..55d19d786 100644 --- a/apps/admin_panel/mix.exs +++ b/apps/admin_panel/mix.exs @@ -4,7 +4,7 @@ defmodule AdminPanel.Mixfile do def project do [ app: :admin_panel, - version: "0.1.0-beta", + version: Application.get_env(:ewallet, :version), build_path: "../../_build", config_path: "../../config/config.exs", deps_path: "../../deps", diff --git a/apps/ewallet/config/dev.exs b/apps/ewallet/config/dev.exs index c934b8f22..9db2348ed 100644 --- a/apps/ewallet/config/dev.exs +++ b/apps/ewallet/config/dev.exs @@ -17,6 +17,11 @@ unless IEx.started?() do schedule: "* * * * *", task: {EWallet.TransactionConsumptionScheduler, :expire_all, []}, run_strategy: {Quantum.RunStrategy.Random, :cluster} + ], + expire_forget_password_requests: [ + schedule: "* * * * *", + task: {EWallet.ForgetPasswordRequestScheduler, :expire_all, []}, + run_strategy: {Quantum.RunStrategy.Random, :cluster} ] ] end diff --git a/apps/ewallet/config/prod.exs b/apps/ewallet/config/prod.exs index 0fbbcd896..51d3dad17 100644 --- a/apps/ewallet/config/prod.exs +++ b/apps/ewallet/config/prod.exs @@ -14,5 +14,10 @@ config :ewallet, EWallet.Scheduler, schedule: "* * * * *", task: {EWallet.TransactionConsumptionScheduler, :expire_all, []}, run_strategy: {Quantum.RunStrategy.Random, :cluster} + ], + expire_forget_password_requests: [ + schedule: "* * * * *", + task: {EWallet.ForgetPasswordRequestScheduler, :expire_all, []}, + run_strategy: {Quantum.RunStrategy.Random, :cluster} ] ] diff --git a/apps/ewallet/lib/ewallet/gates/reset_password_gate.ex b/apps/ewallet/lib/ewallet/gates/reset_password_gate.ex new file mode 100644 index 000000000..8969c55fd --- /dev/null +++ b/apps/ewallet/lib/ewallet/gates/reset_password_gate.ex @@ -0,0 +1,84 @@ +defmodule EWallet.ResetPasswordGate do + @moduledoc """ + Handles a user's password reset. + """ + alias EWalletDB.{ForgetPasswordRequest, User} + + @doc """ + Creates a reset password reset. + """ + @spec request(map()) :: + {:ok, %ForgetPasswordRequest{}} + | {:error, :user_email_not_found} + | {:error, Ecto.Changeset.t()} + def request(email) do + with {:ok, user} <- get_user_by_email(email), + {:ok, request} <- ForgetPasswordRequest.generate(user) do + {:ok, request} + else + error -> error + end + end + + @doc """ + Verifies a reset password request and updates the password. + """ + @spec update(String.t(), String.t(), String.t(), String.t()) :: + {:ok, %User{}} + | {:error, :user_email_not_found} + | {:error, :invalid_reset_token} + | {:error, Ecto.Changeset.t()} + def update(email, token, password, password_confirmation) do + with {:ok, user} <- get_user_by_email(email), + {:ok, request} <- get_request(user, token), + {:ok, user} <- update_password(request, password, password_confirmation), + {:ok, _} <- ForgetPasswordRequest.expire_as_used(request) do + {:ok, user} + else + error -> error + end + end + + # Private functions + + defp get_user_by_email(nil), do: {:error, :user_email_not_found} + + defp get_user_by_email(email) do + case User.get_by_email(email) do + nil -> {:error, :user_email_not_found} + user -> {:ok, user} + end + end + + defp get_request(user, token) when is_nil(user) when is_nil(token) do + {:error, :invalid_reset_token} + end + + defp get_request(user, token) do + case ForgetPasswordRequest.get(user, token) do + nil -> + {:error, :invalid_reset_token} + + request -> + case NaiveDateTime.compare(NaiveDateTime.utc_now(), request.expires_at) do + :gt -> + {:error, :invalid_reset_token} + + _ -> + {:ok, request} + end + end + end + + defp update_password(request, password, password_confirmation) do + User.update_password( + request.user, + %{ + password: password, + password_confirmation: password_confirmation, + originator: request + }, + ignore_current: true + ) + end +end diff --git a/apps/ewallet/lib/ewallet/policies/account_policy.ex b/apps/ewallet/lib/ewallet/policies/account_policy.ex index 4e72fd02c..b44cc54ba 100644 --- a/apps/ewallet/lib/ewallet/policies/account_policy.ex +++ b/apps/ewallet/lib/ewallet/policies/account_policy.ex @@ -25,7 +25,9 @@ defmodule EWallet.AccountPolicy do PolicyHelper.viewer_authorize(user, account_id) end - # create/update/delete, or anything else. + def authorize(:join, param, account_id), do: authorize(:get, param, account_id) + + # create/update/delete/join, or anything else. def authorize(_, %{admin_user: user}, account_id) do PolicyHelper.admin_authorize(user, account_id) end diff --git a/apps/ewallet/lib/ewallet/policies/transaction_consumption_policy.ex b/apps/ewallet/lib/ewallet/policies/transaction_consumption_policy.ex index 8e6886c6a..4ac7859a6 100644 --- a/apps/ewallet/lib/ewallet/policies/transaction_consumption_policy.ex +++ b/apps/ewallet/lib/ewallet/policies/transaction_consumption_policy.ex @@ -38,6 +38,18 @@ defmodule EWallet.TransactionConsumptionPolicy do PolicyHelper.viewer_authorize(user, consumption.account.id) end + def authorize(:join, %{admin_user: _} = params, consumption) do + authorize(:get, params, consumption) + end + + def authorize(:join, %{key: _} = params, consumption) do + authorize(:get, params, consumption) + end + + def authorize(:join, %{end_user: _} = params, consumption) do + WalletPolicy.authorize(:join, params, consumption.wallet) + end + def authorize(:consume, params, %TransactionConsumption{} = consumption) do WalletPolicy.authorize(:admin, params, consumption.wallet) end diff --git a/apps/ewallet/lib/ewallet/policies/transaction_request_policy.ex b/apps/ewallet/lib/ewallet/policies/transaction_request_policy.ex index 63cbcb764..fadc90597 100644 --- a/apps/ewallet/lib/ewallet/policies/transaction_request_policy.ex +++ b/apps/ewallet/lib/ewallet/policies/transaction_request_policy.ex @@ -16,6 +16,18 @@ defmodule EWallet.TransactionRequestPolicy do true end + def authorize(:join, %{admin_user: _} = params, request) do + authorize(:get, params, request) + end + + def authorize(:join, %{key: _} = params, request) do + authorize(:get, params, request) + end + + def authorize(:join, %{end_user: _} = params, request) do + WalletPolicy.authorize(:join, params, request.wallet) + end + # Check with the passed attributes if the current accessor can # create a request for the account def authorize(:create, params, %Wallet{} = wallet) do diff --git a/apps/ewallet/lib/ewallet/policies/user_policy.ex b/apps/ewallet/lib/ewallet/policies/user_policy.ex index 99f002afb..185ad1f11 100644 --- a/apps/ewallet/lib/ewallet/policies/user_policy.ex +++ b/apps/ewallet/lib/ewallet/policies/user_policy.ex @@ -12,7 +12,19 @@ defmodule EWallet.UserPolicy do def authorize(:all, _params, nil), do: true - # Anyone can get any user + def authorize(:join, %{admin_user: _} = params, user) do + authorize(:get, params, user) + end + + def authorize(:join, %{key: _} = params, user) do + authorize(:get, params, user) + end + + def authorize(:join, %{end_user: end_user}, user) do + end_user.uuid == user.uuid + end + + # Anyone admin or key get any user def authorize(:get, _admin_user_or_key, _admin_user), do: true # Anyone can create a new user diff --git a/apps/ewallet/lib/ewallet/policies/wallet_policy.ex b/apps/ewallet/lib/ewallet/policies/wallet_policy.ex index 9f44a41af..87ae4e166 100644 --- a/apps/ewallet/lib/ewallet/policies/wallet_policy.ex +++ b/apps/ewallet/lib/ewallet/policies/wallet_policy.ex @@ -16,6 +16,20 @@ defmodule EWallet.WalletPolicy do def authorize(:all, _params, nil), do: true + def authorize(:join, %{admin_user: _} = params, wallet) do + authorize(:get, params, wallet) + end + + def authorize(:join, %{key: _} = params, wallet) do + authorize(:get, params, wallet) + end + + def authorize(:join, %{end_user: end_user}, %Wallet{} = wallet) do + end_user + |> User.addresses() + |> Enum.member?(wallet.address) + end + # Anyone can create a wallet for a user def authorize(:create, %{key: _key}, %{"user_id" => user_id}) when not is_nil(user_id) do true diff --git a/apps/ewallet/lib/ewallet/schedulers/forget_password_request_scheduler.ex b/apps/ewallet/lib/ewallet/schedulers/forget_password_request_scheduler.ex new file mode 100644 index 000000000..52908d534 --- /dev/null +++ b/apps/ewallet/lib/ewallet/schedulers/forget_password_request_scheduler.ex @@ -0,0 +1,15 @@ +defmodule EWallet.ForgetPasswordRequestScheduler do + @moduledoc """ + Scheduler containing logic for CRON tasks related to + forget password requests. + """ + alias EWalletDB.ForgetPasswordRequest + + @doc """ + Expires all forget password requests which are + past their expiration dates. + """ + def expire_all do + {:ok, _count} = ForgetPasswordRequest.expire_all() + end +end diff --git a/apps/ewallet/lib/ewallet/web/emails/forget_password_email.ex b/apps/ewallet/lib/ewallet/web/emails/forget_password_email.ex index dd9bd7a04..f54b95266 100644 --- a/apps/ewallet/lib/ewallet/web/emails/forget_password_email.ex +++ b/apps/ewallet/lib/ewallet/web/emails/forget_password_email.ex @@ -5,7 +5,7 @@ defmodule EWallet.ForgetPasswordEmail do import Bamboo.Email def create(request, redirect_url) do - sender = Application.get_env(:admin_api, :sender_email) + sender = Application.get_env(:ewallet, :sender_email) link = redirect_url diff --git a/apps/ewallet/lib/ewallet/web/originator.ex b/apps/ewallet/lib/ewallet/web/originator.ex index 4e85713eb..ceedaecd4 100644 --- a/apps/ewallet/lib/ewallet/web/originator.ex +++ b/apps/ewallet/lib/ewallet/web/originator.ex @@ -16,7 +16,7 @@ defmodule EWallet.Web.Originator do end @spec extract(map()) :: %User{} - def extract(%{user: user}) do + def extract(%{end_user: user}) do user end diff --git a/apps/ewallet/lib/ewallet/web/v1/error_handler.ex b/apps/ewallet/lib/ewallet/web/v1/error_handler.ex index b2522ad74..ea9194cf1 100644 --- a/apps/ewallet/lib/ewallet/web/v1/error_handler.ex +++ b/apps/ewallet/lib/ewallet/web/v1/error_handler.ex @@ -46,6 +46,15 @@ defmodule EWallet.Web.V1.ErrorHandler do code: "user:email_token_not_found", description: "There is no pending email verification for the provided email and token." }, + user_email_not_found: %{ + code: "user:email_not_found", + description: "There is no user corresponding to the provided email." + }, + invalid_reset_token: %{ + code: "forget_password:token_not_found", + description: + "There are no password reset requests corresponding to the provided email and token." + }, invite_not_found: %{ code: "user:invite_not_found", description: "There is no invite corresponding to the provided email and token." diff --git a/apps/ewallet/mix.exs b/apps/ewallet/mix.exs index e1a432e62..a1cf397cd 100644 --- a/apps/ewallet/mix.exs +++ b/apps/ewallet/mix.exs @@ -4,7 +4,7 @@ defmodule EWallet.Mixfile do def project do [ app: :ewallet, - version: "0.1.0-beta", + version: Application.get_env(:ewallet, :version), build_path: "../../_build", config_path: "../../config/config.exs", deps_path: "../../deps", diff --git a/apps/ewallet/test/ewallet/emails/forget_password_email_test.exs b/apps/ewallet/test/ewallet/emails/forget_password_email_test.exs index 1b40c779b..f82f9d775 100644 --- a/apps/ewallet/test/ewallet/emails/forget_password_email_test.exs +++ b/apps/ewallet/test/ewallet/emails/forget_password_email_test.exs @@ -17,7 +17,7 @@ defmodule EWallet.ForgetPasswordEmailTest do email = create_email("forgetpassword@example.com", "the_token") # `from` should be the one set in the config - assert email.from == Application.get_env(:admin_api, :sender_email) + assert email.from == Application.get_env(:ewallet, :sender_email) # `to` should be the user's email assert email.to == "forgetpassword@example.com" diff --git a/apps/ewallet/test/ewallet/gates/reset_password_gate_test.exs b/apps/ewallet/test/ewallet/gates/reset_password_gate_test.exs new file mode 100644 index 000000000..aeeba1998 --- /dev/null +++ b/apps/ewallet/test/ewallet/gates/reset_password_gate_test.exs @@ -0,0 +1,126 @@ +defmodule EWallet.ResetPasswordGateTest do + use EWallet.DBCase, async: true + alias EWallet.ResetPasswordGate + alias EWalletDB.{ForgetPasswordRequest, User} + + describe "request/1" do + test "returns {:ok, request} with the user's password unchanged" do + admin = insert(:admin) + + {res, request} = ResetPasswordGate.request(admin.email) + + assert res == :ok + assert %ForgetPasswordRequest{} = request + assert User.get(admin.id).password_hash == admin.password_hash + end + + test "allows multiple active requests" do + admin = insert(:admin) + + request_1 = insert(:forget_password_request, user_uuid: admin.uuid) + request_2 = insert(:forget_password_request, user_uuid: admin.uuid) + + assert ForgetPasswordRequest.get(admin, request_1.token).enabled + assert ForgetPasswordRequest.get(admin, request_2.token).enabled + + {:ok, request_3} = ResetPasswordGate.request(admin.email) + + assert ForgetPasswordRequest.get(admin, request_1.token) + assert ForgetPasswordRequest.get(admin, request_2.token) + assert ForgetPasswordRequest.get(admin, request_3.token) + end + + test "returns :user_email_not_found if the email could not be found" do + {res, error} = ResetPasswordGate.request("some.unknown@example.com") + + assert res == :error + assert error == :user_email_not_found + end + end + + describe "update/4" do + test "returns {:ok, user} if the password update is successful" do + admin = insert(:admin) + request = insert(:forget_password_request, user_uuid: admin.uuid) + password = "new.password" + + {res, user} = ResetPasswordGate.update(admin.email, request.token, password, password) + + assert res == :ok + assert user.uuid == request.user_uuid + end + + test "updates the password if the verification is successful" do + admin = insert(:admin) + request = insert(:forget_password_request, user_uuid: admin.uuid) + password = "new.password" + + assert User.get(admin.id).password_hash == admin.password_hash + + {res, updated} = ResetPasswordGate.update(admin.email, request.token, password, password) + + assert res == :ok + assert updated.uuid == request.user_uuid + assert updated.password_hash != admin.password_hash + end + + test "disables the token after use" do + admin = insert(:admin) + request = insert(:forget_password_request, user_uuid: admin.uuid) + password = "new.password" + + assert ForgetPasswordRequest.get(admin, request.token) != nil + + {:ok, _} = ResetPasswordGate.update(admin.email, request.token, password, password) + + assert ForgetPasswordRequest.get(admin, request.token) == nil + end + + test "allows remaining requests to be used" do + admin = insert(:admin) + request_1 = insert(:forget_password_request, user_uuid: admin.uuid) + request_2 = insert(:forget_password_request, user_uuid: admin.uuid) + request_3 = insert(:forget_password_request, user_uuid: admin.uuid) + + {:ok, _} = ResetPasswordGate.update(admin.email, request_1.token, "newpass1", "newpass1") + {:ok, _} = ResetPasswordGate.update(admin.email, request_2.token, "newpass2", "newpass2") + {:ok, _} = ResetPasswordGate.update(admin.email, request_3.token, "newpass3", "newpass3") + end + + test "returns :invalid_reset_token error if the request is already expired" do + admin_1 = insert(:admin) + admin_2 = insert(:admin) + expires_at = NaiveDateTime.utc_now() |> NaiveDateTime.add(-10) + request = insert(:forget_password_request, user_uuid: admin_1.uuid, expires_at: expires_at) + password = "new.password" + + {res, code} = ResetPasswordGate.update(admin_2.email, request.token, password, password) + + assert res == :error + assert code == :invalid_reset_token + end + + test "returns :invalid_reset_token error if the email does not match the token" do + admin_1 = insert(:admin) + admin_2 = insert(:admin) + request = insert(:forget_password_request, user_uuid: admin_1.uuid) + password = "new.password" + + {res, code} = ResetPasswordGate.update(admin_2.email, request.token, password, password) + + assert res == :error + assert code == :invalid_reset_token + end + + test "returns :invalid_reset_token error if the token could not be found" do + admin = insert(:admin) + _request = insert(:forget_password_request, user_uuid: admin.uuid) + password = "new.password" + + {res, code} = ResetPasswordGate.update(admin.email, "invalid_token", password, password) + + assert res == :error + assert code == :invalid_reset_token + end + end +end diff --git a/apps/ewallet/test/ewallet/schedulers/forget_password_request_scheduler_test.exs b/apps/ewallet/test/ewallet/schedulers/forget_password_request_scheduler_test.exs new file mode 100644 index 000000000..dad534224 --- /dev/null +++ b/apps/ewallet/test/ewallet/schedulers/forget_password_request_scheduler_test.exs @@ -0,0 +1,31 @@ +defmodule EWallet.ForgetPasswordRequestSchedulerTest do + use EWallet.DBCase, async: true + alias EWallet.ForgetPasswordRequestScheduler + alias EWalletDB.{ForgetPasswordRequest, Repo} + + describe "expire_all/0" do + test "expires all requests past their expiration date" do + now = NaiveDateTime.utc_now() + + # f1 and f2 have expiration dates in the past + f1 = insert(:forget_password_request, expires_at: NaiveDateTime.add(now, -60, :seconds)) + f2 = insert(:forget_password_request, expires_at: NaiveDateTime.add(now, -600, :seconds)) + f3 = insert(:forget_password_request, expires_at: NaiveDateTime.add(now, 600, :seconds)) + f4 = insert(:forget_password_request, expires_at: NaiveDateTime.add(now, 160, :seconds)) + + # They are still valid since we haven't made them expired yet + assert Repo.get(ForgetPasswordRequest, f1.uuid).enabled == true + assert Repo.get(ForgetPasswordRequest, f2.uuid).enabled == true + assert Repo.get(ForgetPasswordRequest, f3.uuid).enabled == true + assert Repo.get(ForgetPasswordRequest, f4.uuid).enabled == true + + ForgetPasswordRequestScheduler.expire_all() + + # Now t1 and t2 are expired + assert Repo.get(ForgetPasswordRequest, f1.uuid).enabled == false + assert Repo.get(ForgetPasswordRequest, f2.uuid).enabled == false + assert Repo.get(ForgetPasswordRequest, f3.uuid).enabled == true + assert Repo.get(ForgetPasswordRequest, f4.uuid).enabled == true + end + end +end diff --git a/apps/ewallet/test/support/db_case.ex b/apps/ewallet/test/support/db_case.ex index ff01915c7..5e33d2a21 100644 --- a/apps/ewallet/test/support/db_case.ex +++ b/apps/ewallet/test/support/db_case.ex @@ -28,19 +28,21 @@ defmodule EWallet.DBCase do Sandbox.mode(ActivityLogger.Repo, {:shared, self()}) end - pid = - ConfigTestHelper.restart_config_genserver( - self(), - EWalletConfig.Repo, - [:ewallet_db, :ewallet], - %{ - "enable_standalone" => false, - "base_url" => "http://localhost:4000", - "email_adapter" => "test" - } - ) - - %{config_pid: pid} + config_pid = start_supervised!(EWalletConfig.Config) + + ConfigTestHelper.restart_config_genserver( + self(), + config_pid, + EWalletConfig.Repo, + [:ewallet_db, :ewallet], + %{ + "enable_standalone" => false, + "base_url" => "http://localhost:4000", + "email_adapter" => "test" + } + ) + + %{config_pid: config_pid} end end end diff --git a/apps/ewallet/test/support/local_ledger_case.ex b/apps/ewallet/test/support/local_ledger_case.ex index 408f98cc3..a40fd94e7 100644 --- a/apps/ewallet/test/support/local_ledger_case.ex +++ b/apps/ewallet/test/support/local_ledger_case.ex @@ -30,8 +30,11 @@ defmodule EWallet.LocalLedgerCase do Sandbox.mode(ActivityLogger.Repo, {:shared, self()}) end + config_pid = start_supervised!(EWalletConfig.Config) + ConfigTestHelper.restart_config_genserver( self(), + config_pid, EWalletConfig.Repo, [:ewallet_db, :ewallet], %{ diff --git a/apps/ewallet_api/config/config.exs b/apps/ewallet_api/config/config.exs index 3f42ad299..4b381caf2 100644 --- a/apps/ewallet_api/config/config.exs +++ b/apps/ewallet_api/config/config.exs @@ -48,6 +48,7 @@ config :ewallet_api, :generators, context_app: false # Maps an accept header to the respective router version. config :ewallet_api, :api_versions, %{ "application/vnd.omisego.v1+json" => %{ + name: "v1", router: EWalletAPI.V1.Router, endpoint: EWalletAPI.V1.Endpoint, websocket_serializer: EWallet.Web.V1.WebsocketResponseSerializer diff --git a/apps/ewallet_api/lib/ewallet_api/global/controllers/status_controller.ex b/apps/ewallet_api/lib/ewallet_api/global/controllers/status_controller.ex index a1e22341e..4b7bcf1b7 100644 --- a/apps/ewallet_api/lib/ewallet_api/global/controllers/status_controller.ex +++ b/apps/ewallet_api/lib/ewallet_api/global/controllers/status_controller.ex @@ -9,7 +9,9 @@ defmodule EWalletAPI.StatusController do services: %{ ewallet: true, local_ledger: local_ledger() - } + }, + api_versions: api_versions(), + ewallet_version: Application.get_env(:ewallet, :version) }) end @@ -20,4 +22,12 @@ defmodule EWalletAPI.StatusController do defp node_count do length(Node.list() ++ [Node.self()]) end + + defp api_versions do + api_versions = Application.get_env(:ewallet_api, :api_versions) + + Enum.map(api_versions, fn {key, value} -> + %{name: value[:name], media_type: key} + end) + end end diff --git a/apps/ewallet_api/lib/ewallet_api/v1/auth/client_auth.ex b/apps/ewallet_api/lib/ewallet_api/v1/auth/client_auth.ex index 05d79cc0c..aee98492e 100644 --- a/apps/ewallet_api/lib/ewallet_api/v1/auth/client_auth.ex +++ b/apps/ewallet_api/lib/ewallet_api/v1/auth/client_auth.ex @@ -64,7 +64,7 @@ defmodule EWalletAPI.V1.ClientAuth do %User{} = user -> auth |> Map.put(:authenticated, true) - |> Map.put(:user, user) + |> Map.put(:end_user, user) false -> auth diff --git a/apps/ewallet_api/lib/ewallet_api/v1/channels/transaction_consumption_channel.ex b/apps/ewallet_api/lib/ewallet_api/v1/channels/transaction_consumption_channel.ex index 8e2b860e3..fb87e940e 100644 --- a/apps/ewallet_api/lib/ewallet_api/v1/channels/transaction_consumption_channel.ex +++ b/apps/ewallet_api/lib/ewallet_api/v1/channels/transaction_consumption_channel.ex @@ -4,7 +4,8 @@ defmodule EWalletAPI.V1.TransactionConsumptionChannel do Represents the transaction consumption channel. """ use Phoenix.Channel, async: false - alias EWalletDB.{TransactionConsumption, User} + alias EWalletDB.TransactionConsumption + alias EWallet.TransactionConsumptionPolicy def join( "transaction_consumption:" <> consumption_id, @@ -13,22 +14,14 @@ defmodule EWalletAPI.V1.TransactionConsumptionChannel do assigns: %{auth: auth} } = socket ) do - consumption_id - |> TransactionConsumption.get() - |> join_as(auth, socket) + with %TransactionConsumption{} = consumption <- + TransactionConsumption.get(consumption_id, preload: [:account, :wallet]), + :ok <- Bodyguard.permit(TransactionConsumptionPolicy, :join, auth, consumption) do + {:ok, socket} + else + _ -> {:error, :forbidden_channel} + end end def join(_, _, _), do: {:error, :invalid_parameter} - - defp join_as(nil, _auth, _socket), do: {:error, :channel_not_found} - - defp join_as(consumption, %{authenticated: true, user: user}, socket) do - user - |> User.addresses() - |> Enum.member?(consumption.wallet_address) - |> case do - true -> {:ok, socket} - false -> {:error, :forbidden_channel} - end - end end diff --git a/apps/ewallet_api/lib/ewallet_api/v1/channels/transaction_request_channel.ex b/apps/ewallet_api/lib/ewallet_api/v1/channels/transaction_request_channel.ex index 958f3e0be..c17c6d35b 100644 --- a/apps/ewallet_api/lib/ewallet_api/v1/channels/transaction_request_channel.ex +++ b/apps/ewallet_api/lib/ewallet_api/v1/channels/transaction_request_channel.ex @@ -4,25 +4,17 @@ defmodule EWalletAPI.V1.TransactionRequestChannel do Represents the transaction request channel. """ use Phoenix.Channel, async: false - alias EWalletDB.{TransactionRequest, User} + alias EWalletDB.TransactionRequest + alias EWallet.TransactionRequestPolicy def join("transaction_request:" <> request_id, _params, %{assigns: %{auth: auth}} = socket) do - request_id - |> TransactionRequest.get() - |> join_as(auth, socket) + with %TransactionRequest{} = request <- TransactionRequest.get(request_id, preload: :wallet), + :ok <- Bodyguard.permit(TransactionRequestPolicy, :join, auth, request) do + {:ok, socket} + else + _ -> {:error, :forbidden_channel} + end end def join(_, _, _), do: {:error, :invalid_parameter} - - defp join_as(nil, _auth, _socket), do: {:error, :channel_not_found} - - defp join_as(request, %{authenticated: true, user: user}, socket) do - user - |> User.addresses() - |> Enum.member?(request.wallet_address) - |> case do - true -> {:ok, socket} - false -> {:error, :forbidden_channel} - end - end end diff --git a/apps/ewallet_api/lib/ewallet_api/v1/channels/user_channel.ex b/apps/ewallet_api/lib/ewallet_api/v1/channels/user_channel.ex index 169cf48e6..be1303d06 100644 --- a/apps/ewallet_api/lib/ewallet_api/v1/channels/user_channel.ex +++ b/apps/ewallet_api/lib/ewallet_api/v1/channels/user_channel.ex @@ -5,6 +5,7 @@ defmodule EWalletAPI.V1.UserChannel do """ use Phoenix.Channel, async: false alias EWalletDB.User + alias EWallet.UserPolicy def join( "user:" <> user_id, @@ -13,20 +14,13 @@ defmodule EWalletAPI.V1.UserChannel do assigns: %{auth: auth} } = socket ) do - user = User.get(user_id) || User.get_by_provider_user_id(user_id) - join_as(user, auth, socket) + with %User{} = user <- User.get(user_id) || User.get_by_provider_user_id(user_id), + :ok <- Bodyguard.permit(UserPolicy, :join, auth, user) do + {:ok, socket} + else + _ -> {:error, :forbidden_channel} + end end def join(_, _, _), do: {:error, :invalid_parameter} - - defp join_as(nil, _auth, _socket), do: {:error, :channel_not_found} - - defp join_as(user, %{authenticated: true, user: auth_user}, socket) do - same_user? = auth_user.id == user.id || auth_user.provider_user_id == user.provider_user_id - - case same_user? do - true -> {:ok, socket} - false -> {:error, :forbidden_channel} - end - end end diff --git a/apps/ewallet_api/lib/ewallet_api/v1/channels/wallet_channel.ex b/apps/ewallet_api/lib/ewallet_api/v1/channels/wallet_channel.ex index b7a2994b3..b6fc6e018 100644 --- a/apps/ewallet_api/lib/ewallet_api/v1/channels/wallet_channel.ex +++ b/apps/ewallet_api/lib/ewallet_api/v1/channels/wallet_channel.ex @@ -4,25 +4,17 @@ defmodule EWalletAPI.V1.WalletChannel do Represents the address channel. """ use Phoenix.Channel, async: false - alias EWalletDB.{User, Wallet} + alias EWalletDB.{Wallet} + alias EWallet.WalletPolicy def join("address:" <> address, _params, %{assigns: %{auth: auth}} = socket) do - address - |> Wallet.get() - |> join_as(auth, socket) + with %Wallet{} = wallet <- Wallet.get(address), + :ok <- Bodyguard.permit(WalletPolicy, :join, auth, wallet) do + {:ok, socket} + else + _ -> {:error, :forbidden_channel} + end end def join(_, _, _), do: {:error, :invalid_parameter} - - defp join_as(nil, _auth, _socket), do: {:error, :channel_not_found} - - defp join_as(wallet, %{authenticated: true, user: user}, socket) do - user - |> User.addresses() - |> Enum.member?(wallet.address) - |> case do - true -> {:ok, socket} - false -> {:error, :forbidden_channel} - end - end end diff --git a/apps/ewallet_api/lib/ewallet_api/v1/controllers/reset_password_controller.ex b/apps/ewallet_api/lib/ewallet_api/v1/controllers/reset_password_controller.ex new file mode 100644 index 000000000..f61fa6ee5 --- /dev/null +++ b/apps/ewallet_api/lib/ewallet_api/v1/controllers/reset_password_controller.ex @@ -0,0 +1,92 @@ +defmodule EWalletAPI.V1.ResetPasswordController do + use EWalletAPI, :controller + import EWalletAPI.V1.ErrorHandler + alias EWallet.{ForgetPasswordEmail, Mailer, ResetPasswordGate} + alias EWallet.Web.UrlValidator + + @doc """ + Starts the reset password request flow for a user. + + This function is used when the eWallet is setup as a standalone solution, + allowing users to reset their password without going through the integration + with the provider's server. + """ + @spec reset(Plug.Conn.t(), map()) :: Plug.Conn.t() + def reset(conn, %{"email" => email, "redirect_url" => redirect_url}) + when not is_nil(email) and not is_nil(redirect_url) do + with {:ok, redirect_url} <- validate_redirect_url(redirect_url), + {:ok, request} <- ResetPasswordGate.request(email), + {:ok, _email} <- send_request_email(request, redirect_url) do + render(conn, :empty, %{success: true}) + else + # Prevents attackers from gaining knowledge about a user's email. + {:error, :user_email_not_found} -> + render(conn, :empty, %{success: true}) + + {:error, code, meta} -> + handle_error(conn, code, meta) + + {:error, code} -> + handle_error(conn, code) + end + end + + def reset(conn, _) do + handle_error(conn, :invalid_parameter, "`email` and `redirect_url` are required") + end + + defp validate_redirect_url(url) do + if UrlValidator.allowed_redirect_url?(url) do + {:ok, url} + else + {:error, :prohibited_url, param_name: "redirect_url", url: url} + end + end + + defp send_request_email(request, redirect_url) do + email = + request + |> ForgetPasswordEmail.create(redirect_url) + |> Mailer.deliver_now() + + {:ok, email} + end + + @doc """ + Completes the reset password request flow for a user. + + This function is used when the eWallet is setup as a standalone solution, + allowing users to reset their password without going through the integration + with the provider's server. + """ + @spec update(Plug.Conn.t(), map()) :: Plug.Conn.t() + def update(conn, %{ + "email" => email, + "token" => token, + "password" => password, + "password_confirmation" => password_confirmation + }) do + case ResetPasswordGate.update(email, token, password, password_confirmation) do + {:ok, _user} -> + render(conn, :empty, %{success: true}) + + # Prevents attackers from gaining knowledge about a user's email. + {:error, :user_email_not_found} -> + handle_error(conn, :invalid_reset_token) + + {:error, code} when is_atom(code) -> + handle_error(conn, code) + + {:error, changeset} -> + handle_error(conn, :invalid_parameter, changeset) + end + end + + def update(conn, _) do + handle_error( + conn, + :invalid_parameter, + "`email`, `token`, `password` and `password_confirmation` are required" + ) + end +end diff --git a/apps/ewallet_api/lib/ewallet_api/v1/controllers/self_controller.ex b/apps/ewallet_api/lib/ewallet_api/v1/controllers/self_controller.ex index 9cc2f9ec8..ea01b649f 100644 --- a/apps/ewallet_api/lib/ewallet_api/v1/controllers/self_controller.ex +++ b/apps/ewallet_api/lib/ewallet_api/v1/controllers/self_controller.ex @@ -6,7 +6,7 @@ defmodule EWalletAPI.V1.SelfController do alias EWalletDB.Token def get(conn, _attrs) do - render(conn, :user, %{user: conn.assigns.user}) + render(conn, :user, %{user: conn.assigns.end_user}) end def get_settings(conn, _attrs) do @@ -15,7 +15,7 @@ defmodule EWalletAPI.V1.SelfController do end def get_wallets(conn, attrs) do - with {:ok, wallet} <- BalanceFetcher.all(%{"user_id" => conn.assigns.user.id}) do + with {:ok, wallet} <- BalanceFetcher.all(%{"user_id" => conn.assigns.end_user.id}) do {:ok, wallets} = Orchestrator.all([wallet], WalletOverlay, attrs) render(conn, :wallets, %{wallets: wallets}) else diff --git a/apps/ewallet_api/lib/ewallet_api/v1/controllers/transaction_consumption_controller.ex b/apps/ewallet_api/lib/ewallet_api/v1/controllers/transaction_consumption_controller.ex index e282489a0..641400022 100644 --- a/apps/ewallet_api/lib/ewallet_api/v1/controllers/transaction_consumption_controller.ex +++ b/apps/ewallet_api/lib/ewallet_api/v1/controllers/transaction_consumption_controller.ex @@ -20,7 +20,7 @@ defmodule EWalletAPI.V1.TransactionConsumptionController do |> Map.put("originator", Originator.extract(conn.assigns)) with {:ok, consumption} <- - TransactionConsumptionConsumerGate.consume(conn.assigns.user, attrs) do + TransactionConsumptionConsumerGate.consume(conn.assigns.end_user, attrs) do consumption |> Orchestrator.one(TransactionConsumptionOverlay, attrs) |> respond(conn, true) @@ -34,8 +34,8 @@ defmodule EWalletAPI.V1.TransactionConsumptionController do handle_error(conn, :invalid_parameter) end - def approve_for_user(conn, attrs), do: confirm(conn, conn.assigns.user, attrs, true) - def reject_for_user(conn, attrs), do: confirm(conn, conn.assigns.user, attrs, false) + def approve_for_user(conn, attrs), do: confirm(conn, conn.assigns.end_user, attrs, true) + def reject_for_user(conn, attrs), do: confirm(conn, conn.assigns.end_user, attrs, false) defp confirm(conn, user, %{"id" => id} = attrs, approved) do id diff --git a/apps/ewallet_api/lib/ewallet_api/v1/controllers/transaction_controller.ex b/apps/ewallet_api/lib/ewallet_api/v1/controllers/transaction_controller.ex index ee8bbd084..9efb9cbbc 100644 --- a/apps/ewallet_api/lib/ewallet_api/v1/controllers/transaction_controller.ex +++ b/apps/ewallet_api/lib/ewallet_api/v1/controllers/transaction_controller.ex @@ -63,7 +63,7 @@ defmodule EWalletAPI.V1.TransactionController do The 'from' and 'to' fields cannot be searched for at the same time in the 'search_terms' param. """ - def get_transactions(%{assigns: %{user: user}} = conn, attrs) do + def get_transactions(%{assigns: %{end_user: user}} = conn, attrs) do with {:ok, wallet} <- WalletFetcher.get(user, attrs["address"]) do attrs = user @@ -83,7 +83,7 @@ defmodule EWalletAPI.V1.TransactionController do attrs |> Enum.filter(fn {k, _v} -> Enum.member?(@allowed_fields, k) end) |> Enum.into(%{}) - |> Map.put("from_user_id", conn.assigns.user.id) + |> Map.put("from_user_id", conn.assigns.end_user.id) |> Map.put("originator", Originator.extract(conn.assigns)) |> TransactionGate.create() diff --git a/apps/ewallet_api/lib/ewallet_api/v1/controllers/transaction_request_controller.ex b/apps/ewallet_api/lib/ewallet_api/v1/controllers/transaction_request_controller.ex index 473d63707..895675e41 100644 --- a/apps/ewallet_api/lib/ewallet_api/v1/controllers/transaction_request_controller.ex +++ b/apps/ewallet_api/lib/ewallet_api/v1/controllers/transaction_request_controller.ex @@ -13,7 +13,7 @@ defmodule EWalletAPI.V1.TransactionRequestController do def create_for_user(conn, attrs) do attrs = Map.put(attrs, "originator", Originator.extract(conn.assigns)) - conn.assigns.user + conn.assigns.end_user |> TransactionRequestGate.create(attrs) |> respond(conn) end diff --git a/apps/ewallet_api/lib/ewallet_api/v1/plugs/client_auth_plug.ex b/apps/ewallet_api/lib/ewallet_api/v1/plugs/client_auth_plug.ex index 53efa1888..3111b4225 100644 --- a/apps/ewallet_api/lib/ewallet_api/v1/plugs/client_auth_plug.ex +++ b/apps/ewallet_api/lib/ewallet_api/v1/plugs/client_auth_plug.ex @@ -30,7 +30,7 @@ defmodule EWalletAPI.V1.ClientAuthPlug do conn |> assign(:authenticated, true) |> assign(:auth_scheme, :client) - |> assign(:user, auth[:user]) + |> assign(:end_user, auth[:end_user]) |> put_private(:auth_api_key, auth[:auth_api_key]) |> put_private(:auth_auth_token, auth[:auth_auth_token]) end @@ -46,6 +46,6 @@ defmodule EWalletAPI.V1.ClientAuthPlug do conn |> assign(:authenticated, false) - |> assign(:user, nil) + |> assign(:end_user, nil) end end diff --git a/apps/ewallet_api/lib/ewallet_api/v1/router.ex b/apps/ewallet_api/lib/ewallet_api/v1/router.ex index 4dda777d8..c4df4a254 100644 --- a/apps/ewallet_api/lib/ewallet_api/v1/router.ex +++ b/apps/ewallet_api/lib/ewallet_api/v1/router.ex @@ -50,6 +50,9 @@ defmodule EWalletAPI.V1.Router do post("/user.signup", SignupController, :signup) post("/user.verify_email", SignupController, :verify_email) post("/user.login", AuthController, :login) + + post("/user.reset_password", ResetPasswordController, :reset) + post("/user.update_password", ResetPasswordController, :update) end # Public endpoints diff --git a/apps/ewallet_api/lib/ewallet_api/v1/views/reset_password_view.ex b/apps/ewallet_api/lib/ewallet_api/v1/views/reset_password_view.ex new file mode 100644 index 000000000..c5111e1dd --- /dev/null +++ b/apps/ewallet_api/lib/ewallet_api/v1/views/reset_password_view.ex @@ -0,0 +1,8 @@ +defmodule EWalletAPI.V1.ResetPasswordView do + use EWalletAPI, :view + alias EWallet.Web.V1.ResponseSerializer + + def render("empty.json", %{success: success}) do + ResponseSerializer.serialize(%{}, success: success) + end +end diff --git a/apps/ewallet_api/lib/ewallet_api/v1/views/signup_view.ex b/apps/ewallet_api/lib/ewallet_api/v1/views/signup_view.ex index bd7326eff..cb77b46b0 100644 --- a/apps/ewallet_api/lib/ewallet_api/v1/views/signup_view.ex +++ b/apps/ewallet_api/lib/ewallet_api/v1/views/signup_view.ex @@ -3,8 +3,7 @@ defmodule EWalletAPI.V1.SignupView do alias EWallet.Web.V1.{ResponseSerializer, UserSerializer} def render("empty.json", %{success: success}) do - %{} - |> ResponseSerializer.serialize(success: success) + ResponseSerializer.serialize(%{}, success: success) end def render("user.json", %{user: user}) do diff --git a/apps/ewallet_api/mix.exs b/apps/ewallet_api/mix.exs index ee3395a69..c4559b5d2 100644 --- a/apps/ewallet_api/mix.exs +++ b/apps/ewallet_api/mix.exs @@ -4,7 +4,7 @@ defmodule EWalletAPI.Mixfile do def project do [ app: :ewallet_api, - version: "0.1.0-beta", + version: Application.get_env(:ewallet, :version), build_path: "../../_build", config_path: "../../config/config.exs", deps_path: "../../deps", diff --git a/apps/ewallet_api/priv/spec.json b/apps/ewallet_api/priv/spec.json index 762828ecf..e7711ed1f 100644 --- a/apps/ewallet_api/priv/spec.json +++ b/apps/ewallet_api/priv/spec.json @@ -967,6 +967,510 @@ } } }, + "/user.reset_password": { + "post": { + "tags": [ + "User" + ], + "summary": "Request a password reset", + "operationId": "user_reset_password", + "requestBody": { + "description": "The parameters to use for requesting a password reset", + "required": true, + "content": { + "application/vnd.omisego.v1+json": { + "schema": { + "properties": { + "email": { + "type": "string", + "format": "email" + }, + "redirect_url": { + "type": "string" + } + }, + "required": [ + "email", + "redirect_url" + ], + "example": { + "email": "johndoe@example.com", + "redirect_url": "https://example.com/reset_password?email={email}&token={token}" + } + } + } + } + }, + "responses": { + "200": { + "description": "Returns a single user", + "content": { + "application/vnd.omisego.v1+json": { + "schema": { + "description": "The response schema for a user", + "allOf": [ + { + "description": "The response schema for a successful operation", + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "success": { + "type": "boolean" + }, + "data": { + "type": "object" + } + }, + "required": [ + "version", + "success", + "data" + ], + "example": { + "version": "1", + "success": true, + "data": {} + } + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "description": "The object schema for a user", + "properties": { + "object": { + "type": "string" + }, + "id": { + "type": "string" + }, + "username": { + "type": "string" + }, + "full_name": { + "type": "string" + }, + "calling_name": { + "type": "string" + }, + "provider_user_id": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + }, + "enabled": { + "type": "boolean" + }, + "metadata": { + "type": "object" + }, + "encrypted_metadata": { + "type": "object" + }, + "avatar": { + "type": "object" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "object", + "id", + "created_at", + "updated_at" + ] + } + }, + "example": { + "data": { + "object": "user", + "id": "usr_01ce83zf80j542z4q4zqd8qvfx", + "provider_user_id": "wijf-fbancomw-dqwjudb", + "username": "johndoe", + "full_name": "John Doe", + "calling_name": "John", + "email": "johndoe@omise.co", + "enabled": true, + "metadata": { + "first_name": "John", + "last_name": "Doe" + }, + "encrypted_metadata": { + "something": "secret" + }, + "avatar": { + "original": "file_url" + }, + "created_at": "2018-01-01T00:00:00Z", + "updated_at": "2018-01-01T10:00:00Z" + } + } + } + ] + } + } + } + }, + "500": { + "description": "Returns an internal server error", + "content": { + "application/vnd.omisego.v1+json": { + "schema": { + "description": "The response schema for an error", + "allOf": [ + { + "description": "The response schema for a successful operation", + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "success": { + "type": "boolean" + }, + "data": { + "type": "object" + } + }, + "required": [ + "version", + "success", + "data" + ], + "example": { + "version": "1", + "success": true, + "data": {} + } + }, + { + "type": "object", + "properties": { + "data": { + "description": "The object schema for an error", + "type": "object", + "properties": { + "object": { + "type": "string" + }, + "code": { + "type": "string" + }, + "description": { + "type": "string" + }, + "messages": { + "type": "object" + } + }, + "required": [ + "object", + "code", + "description", + "messages" + ], + "example": { + "object": "error", + "code": "server:internal_server_error", + "description": "Something went wrong on the server", + "messages": { + "error_key": "error_reason" + } + } + } + }, + "required": [ + "data" + ], + "example": { + "success": false, + "data": { + "object": "error", + "code": "server:internal_server_error", + "description": "Something went wrong on the server", + "messages": { + "error_key": "error_reason" + } + } + } + } + ] + } + } + } + } + } + } + }, + "/user.update_password": { + "post": { + "tags": [ + "User" + ], + "summary": "Perform a password reset", + "operationId": "user_update_password", + "requestBody": { + "description": "The parameters to use for resetting the user's password", + "required": true, + "content": { + "application/vnd.omisego.v1+json": { + "schema": { + "properties": { + "email": { + "type": "string", + "format": "email" + }, + "token": { + "type": "string" + }, + "password": { + "type": "string", + "format": "password" + }, + "password_confirmation": { + "type": "string", + "format": "password" + } + }, + "required": [ + "email", + "token", + "password", + "password_confirmation" + ], + "example": { + "email": "johndoe@example.com", + "token": "some_token_string", + "password": "new_password", + "password_confirmation": "new_password" + } + } + } + } + }, + "responses": { + "200": { + "description": "Returns a single user", + "content": { + "application/vnd.omisego.v1+json": { + "schema": { + "description": "The response schema for a user", + "allOf": [ + { + "description": "The response schema for a successful operation", + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "success": { + "type": "boolean" + }, + "data": { + "type": "object" + } + }, + "required": [ + "version", + "success", + "data" + ], + "example": { + "version": "1", + "success": true, + "data": {} + } + }, + { + "type": "object", + "properties": { + "data": { + "type": "object", + "description": "The object schema for a user", + "properties": { + "object": { + "type": "string" + }, + "id": { + "type": "string" + }, + "username": { + "type": "string" + }, + "full_name": { + "type": "string" + }, + "calling_name": { + "type": "string" + }, + "provider_user_id": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email" + }, + "enabled": { + "type": "boolean" + }, + "metadata": { + "type": "object" + }, + "encrypted_metadata": { + "type": "object" + }, + "avatar": { + "type": "object" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + }, + "required": [ + "object", + "id", + "created_at", + "updated_at" + ] + } + }, + "example": { + "data": { + "object": "user", + "id": "usr_01ce83zf80j542z4q4zqd8qvfx", + "provider_user_id": "wijf-fbancomw-dqwjudb", + "username": "johndoe", + "full_name": "John Doe", + "calling_name": "John", + "email": "johndoe@omise.co", + "enabled": true, + "metadata": { + "first_name": "John", + "last_name": "Doe" + }, + "encrypted_metadata": { + "something": "secret" + }, + "avatar": { + "original": "file_url" + }, + "created_at": "2018-01-01T00:00:00Z", + "updated_at": "2018-01-01T10:00:00Z" + } + } + } + ] + } + } + } + }, + "500": { + "description": "Returns an internal server error", + "content": { + "application/vnd.omisego.v1+json": { + "schema": { + "description": "The response schema for an error", + "allOf": [ + { + "description": "The response schema for a successful operation", + "type": "object", + "properties": { + "version": { + "type": "string" + }, + "success": { + "type": "boolean" + }, + "data": { + "type": "object" + } + }, + "required": [ + "version", + "success", + "data" + ], + "example": { + "version": "1", + "success": true, + "data": {} + } + }, + { + "type": "object", + "properties": { + "data": { + "description": "The object schema for an error", + "type": "object", + "properties": { + "object": { + "type": "string" + }, + "code": { + "type": "string" + }, + "description": { + "type": "string" + }, + "messages": { + "type": "object" + } + }, + "required": [ + "object", + "code", + "description", + "messages" + ], + "example": { + "object": "error", + "code": "server:internal_server_error", + "description": "Something went wrong on the server", + "messages": { + "error_key": "error_reason" + } + } + } + }, + "required": [ + "data" + ], + "example": { + "success": false, + "data": { + "object": "error", + "code": "server:internal_server_error", + "description": "Something went wrong on the server", + "messages": { + "error_key": "error_reason" + } + } + } + } + ] + } + } + } + } + } + } + }, "/me.get": { "post": { "tags": [ diff --git a/apps/ewallet_api/priv/spec.yaml b/apps/ewallet_api/priv/spec.yaml index 35ea13b2b..6ac8e561b 100644 --- a/apps/ewallet_api/priv/spec.yaml +++ b/apps/ewallet_api/priv/spec.yaml @@ -336,6 +336,70 @@ paths: responses: '200': *ref_4 '500': *ref_1 + /user.reset_password: + post: + tags: + - User + summary: Request a password reset + operationId: user_reset_password + requestBody: + description: The parameters to use for requesting a password reset + required: true + content: + application/vnd.omisego.v1+json: + schema: + properties: + email: + type: string + format: email + redirect_url: + type: string + required: + - email + - redirect_url + example: + email: johndoe@example.com + redirect_url: 'https://example.com/reset_password?email={email}&token={token}' + responses: + '200': *ref_4 + '500': *ref_1 + /user.update_password: + post: + tags: + - User + summary: Perform a password reset + operationId: user_update_password + requestBody: + description: The parameters to use for resetting the user's password + required: true + content: + application/vnd.omisego.v1+json: + schema: + properties: + email: + type: string + format: email + token: + type: string + password: + type: string + format: password + password_confirmation: + type: string + format: password + required: + - email + - token + - password + - password_confirmation + example: + email: johndoe@example.com + token: some_token_string + password: new_password + password_confirmation: new_password + responses: + '200': *ref_4 + '500': *ref_1 /me.get: post: tags: diff --git a/apps/ewallet_api/priv/swagger/swagger.yaml b/apps/ewallet_api/priv/swagger/swagger.yaml index 07f17987f..6df719bfc 100644 --- a/apps/ewallet_api/priv/swagger/swagger.yaml +++ b/apps/ewallet_api/priv/swagger/swagger.yaml @@ -62,6 +62,10 @@ paths: $ref: 'user/paths.yaml#/user.signup' /user.verify_email: $ref: 'user/paths.yaml#/user.verify_email' + /user.reset_password: + $ref: 'user/paths.yaml#/user.reset_password' + /user.update_password: + $ref: 'user/paths.yaml#/user.update_password' /me.get: $ref: 'user/paths.yaml#/me.get' diff --git a/apps/ewallet_api/priv/swagger/user/paths.yaml b/apps/ewallet_api/priv/swagger/user/paths.yaml index 3d5854bf1..44aef7669 100644 --- a/apps/ewallet_api/priv/swagger/user/paths.yaml +++ b/apps/ewallet_api/priv/swagger/user/paths.yaml @@ -26,6 +26,34 @@ user.verify_email: '500': $ref: '../../../../ewallet/priv/swagger/shared/responses.yaml#/InternalServerError' +user.reset_password: + post: + tags: + - User + summary: Request a password reset + operationId: user_reset_password + requestBody: + $ref: 'request_bodies.yaml#/UserResetPasswordBody' + responses: + '200': + $ref: 'responses.yaml#/UserResponse' + '500': + $ref: '../../../../ewallet/priv/swagger/shared/responses.yaml#/InternalServerError' + +user.update_password: + post: + tags: + - User + summary: Perform a password reset + operationId: user_update_password + requestBody: + $ref: 'request_bodies.yaml#/UserUpdatePasswordBody' + responses: + '200': + $ref: 'responses.yaml#/UserResponse' + '500': + $ref: '../../../../ewallet/priv/swagger/shared/responses.yaml#/InternalServerError' + me.get: post: tags: diff --git a/apps/ewallet_api/priv/swagger/user/request_bodies.yaml b/apps/ewallet_api/priv/swagger/user/request_bodies.yaml index b0133ece4..712ad424f 100644 --- a/apps/ewallet_api/priv/swagger/user/request_bodies.yaml +++ b/apps/ewallet_api/priv/swagger/user/request_bodies.yaml @@ -47,3 +47,51 @@ UserVerifyEmailBody: example: email: johndoe@example.com token: some_token_string + +UserResetPasswordBody: + description: The parameters to use for requesting a password reset + required: true + content: + application/vnd.omisego.v1+json: + schema: + properties: + email: + type: string + format: email + redirect_url: + type: string + required: + - email + - redirect_url + example: + email: johndoe@example.com + redirect_url: 'https://example.com/reset_password?email={email}&token={token}' + +UserUpdatePasswordBody: + description: The parameters to use for resetting the user's password + required: true + content: + application/vnd.omisego.v1+json: + schema: + properties: + email: + type: string + format: email + token: + type: string + password: + type: string + format: password + password_confirmation: + type: string + format: password + required: + - email + - token + - password + - password_confirmation + example: + email: johndoe@example.com + token: some_token_string + password: new_password + password_confirmation: new_password diff --git a/apps/ewallet_api/test/ewallet_api/global/controllers/status_controller_test.exs b/apps/ewallet_api/test/ewallet_api/global/controllers/status_controller_test.exs index 5a83fc7fb..2d66e39ef 100644 --- a/apps/ewallet_api/test/ewallet_api/global/controllers/status_controller_test.exs +++ b/apps/ewallet_api/test/ewallet_api/global/controllers/status_controller_test.exs @@ -14,7 +14,11 @@ defmodule EWalletAPI.StatusControllerTest do "services" => %{ "ewallet" => true, "local_ledger" => true - } + }, + "ewallet_version" => "1.1.0", + "api_versions" => [ + %{"name" => "v1", "media_type" => "application/vnd.omisego.v1+json"} + ] } end end diff --git a/apps/ewallet_api/test/ewallet_api/v1/auth/client_auth_test.exs b/apps/ewallet_api/test/ewallet_api/v1/auth/client_auth_test.exs index cc1a187d6..18094a254 100644 --- a/apps/ewallet_api/test/ewallet_api/v1/auth/client_auth_test.exs +++ b/apps/ewallet_api/test/ewallet_api/v1/auth/client_auth_test.exs @@ -31,7 +31,7 @@ defmodule EWalletAPI.V1.ClientAuthTest do assert auth.authenticated assert auth[:authenticated] == true assert auth[:account] != nil - assert auth[:user] != nil + assert auth[:end_user] != nil end test "halts with :invalid_api_key if api_key is missing", meta do @@ -40,7 +40,7 @@ defmodule EWalletAPI.V1.ClientAuthTest do assert auth.authenticated == false assert auth[:auth_error] == :invalid_api_key assert auth[:account] == nil - assert auth[:user] == nil + assert auth[:end_user] == nil end test "halts with :invalid_api_key if api_key is incorrect", meta do @@ -49,7 +49,7 @@ defmodule EWalletAPI.V1.ClientAuthTest do assert auth.authenticated == false assert auth[:auth_error] == :invalid_api_key assert auth[:account] == nil - assert auth[:user] == nil + assert auth[:end_user] == nil end test "halts with :auth_token_not_found if auth_token is missing", meta do @@ -58,7 +58,7 @@ defmodule EWalletAPI.V1.ClientAuthTest do assert auth.authenticated == false assert auth[:auth_error] == :auth_token_not_found assert auth[:account].uuid == meta.api_key.account.uuid - assert auth[:user] == nil + assert auth[:end_user] == nil end test "halts with :auth_token_not_found if auth_token is incorrect", meta do @@ -67,7 +67,7 @@ defmodule EWalletAPI.V1.ClientAuthTest do assert auth.authenticated == false assert auth[:auth_error] == :auth_token_not_found assert auth[:account].uuid == meta.api_key.account.uuid - assert auth[:user] == nil + assert auth[:end_user] == nil end test "halts with :auth_token_expired if auth_token exists but expired", meta do @@ -77,7 +77,7 @@ defmodule EWalletAPI.V1.ClientAuthTest do assert auth.authenticated == false assert auth[:auth_error] == :auth_token_expired assert auth[:account].uuid == meta.api_key.account.uuid - assert auth[:user] == nil + assert auth[:end_user] == nil end test "halts with :invalid_auth_scheme if auth header is not provided" do @@ -89,7 +89,7 @@ defmodule EWalletAPI.V1.ClientAuthTest do assert auth.authenticated == false assert auth[:auth_error] == :invalid_auth_scheme assert auth[:account] == nil - assert auth[:user] == nil + assert auth[:end_user] == nil end test "halts with :invalid_auth_scheme if auth header is not a valid auth scheme" do @@ -103,7 +103,7 @@ defmodule EWalletAPI.V1.ClientAuthTest do assert auth.authenticated == false assert auth[:auth_error] == :invalid_auth_scheme assert auth[:account] == nil - assert auth[:user] == nil + assert auth[:end_user] == nil end test "halts with :invalid_auth_scheme if auth header is invalid" do @@ -117,7 +117,7 @@ defmodule EWalletAPI.V1.ClientAuthTest do assert auth.authenticated == false assert auth[:auth_error] == :invalid_auth_scheme assert auth[:account] == nil - assert auth[:user] == nil + assert auth[:end_user] == nil end end end diff --git a/apps/ewallet_api/test/ewallet_api/v1/channels/address_channel_test.exs b/apps/ewallet_api/test/ewallet_api/v1/channels/address_channel_test.exs deleted file mode 100644 index 28a1b9c4d..000000000 --- a/apps/ewallet_api/test/ewallet_api/v1/channels/address_channel_test.exs +++ /dev/null @@ -1,45 +0,0 @@ -# credo:disable-for-this-file -defmodule EWalletAPI.V1.WalletChannelTest do - use EWalletAPI.ChannelCase, async: false - alias EWalletAPI.V1.WalletChannel - - describe "join/3 as client" do - test "joins the channel with authenticated user and owned address" do - user = insert(:user) - wallet = insert(:wallet, user: user) - - {res, _, socket} = - "test" - |> socket(%{auth: %{authenticated: true, user: user}}) - |> subscribe_and_join(WalletChannel, "address:#{wallet.address}") - - assert res == :ok - assert socket.topic == "address:#{wallet.address}" - end - - test "can't join channel with existing not owned address" do - user = insert(:user) - wallet = insert(:wallet) - - {res, code} = - "test" - |> socket(%{auth: %{authenticated: true, user: user}}) - |> subscribe_and_join(WalletChannel, "address:#{wallet.address}") - - assert res == :error - assert code == :forbidden_channel - end - - test "can't join channel with inexisting address" do - user = insert(:user) - - {res, code} = - "test" - |> socket(%{auth: %{authenticated: true, user: user}}) - |> subscribe_and_join(WalletChannel, "address:none000000000000") - - assert res == :error - assert code == :channel_not_found - end - end -end diff --git a/apps/ewallet_api/test/ewallet_api/v1/channels/transaction_consumption_channel_test.exs b/apps/ewallet_api/test/ewallet_api/v1/channels/transaction_consumption_channel_test.exs index 20b7ea651..b6d96b428 100644 --- a/apps/ewallet_api/test/ewallet_api/v1/channels/transaction_consumption_channel_test.exs +++ b/apps/ewallet_api/test/ewallet_api/v1/channels/transaction_consumption_channel_test.exs @@ -4,50 +4,34 @@ defmodule EWalletAPI.V1.TransactionConsumptionChannelTest do alias EWalletAPI.V1.TransactionConsumptionChannel alias EWalletDB.User + defp topic(id), do: "transaction_consumption:#{id}" + describe "join/3 as client" do test "joins the channel with authenticated user and owned consumption" do - {:ok, user} = :user |> params_for() |> User.insert() + user = get_test_user() wallet = User.get_primary_wallet(user) consumption = insert(:transaction_consumption, wallet_address: wallet.address) - {res, _, socket} = - "test" - |> socket(%{auth: %{authenticated: true, user: user}}) - |> subscribe_and_join( - TransactionConsumptionChannel, - "transaction_consumption:#{consumption.id}" - ) - - assert res == :ok - assert socket.topic == "transaction_consumption:#{consumption.id}" + consumption.id + |> topic() + |> test_with_topic(TransactionConsumptionChannel) + |> assert_success(topic(consumption.id)) end test "can't join channel with existing not owned address" do - user = insert(:user) consumption = insert(:transaction_consumption) - {res, code} = - "test" - |> socket(%{auth: %{authenticated: true, user: user}}) - |> subscribe_and_join( - TransactionConsumptionChannel, - "transaction_consumption:#{consumption.id}" - ) - - assert res == :error - assert code == :forbidden_channel + consumption.id + |> topic() + |> test_with_topic(TransactionConsumptionChannel) + |> assert_failure(:forbidden_channel) end test "can't join channel with inexisting consumption" do - user = insert(:user) - - {res, code} = - "test" - |> socket(%{auth: %{authenticated: true, user: user}}) - |> subscribe_and_join(TransactionConsumptionChannel, "transaction_consumption:123") - - assert res == :error - assert code == :channel_not_found + "123" + |> topic() + |> test_with_topic(TransactionConsumptionChannel) + |> assert_failure(:forbidden_channel) end end end diff --git a/apps/ewallet_api/test/ewallet_api/v1/channels/transaction_request_channel_test.exs b/apps/ewallet_api/test/ewallet_api/v1/channels/transaction_request_channel_test.exs index 3ac9e8102..d7d812b94 100644 --- a/apps/ewallet_api/test/ewallet_api/v1/channels/transaction_request_channel_test.exs +++ b/apps/ewallet_api/test/ewallet_api/v1/channels/transaction_request_channel_test.exs @@ -4,44 +4,34 @@ defmodule EWalletAPI.V1.TransactionRequestChannelTest do alias EWalletAPI.V1.TransactionRequestChannel alias EWalletDB.User + defp topic(id), do: "transaction_request:#{id}" + describe "join/3 as client" do - test "joins the channel with authenticated user and owned request" do - {:ok, user} = :user |> params_for() |> User.insert() + test "can join the channel with authenticated user and owned request" do + user = get_test_user() wallet = User.get_primary_wallet(user) request = insert(:transaction_request, wallet: wallet) - {res, _, socket} = - "test" - |> socket(%{auth: %{authenticated: true, user: user}}) - |> subscribe_and_join(TransactionRequestChannel, "transaction_request:#{request.id}") - - assert res == :ok - assert socket.topic == "transaction_request:#{request.id}" + request.id + |> topic() + |> test_with_topic(TransactionRequestChannel) + |> assert_success(topic(request.id)) end test "can't join channel with existing not owned address" do - user = insert(:user) request = insert(:transaction_request) - {res, code} = - "test" - |> socket(%{auth: %{authenticated: true, user: user}}) - |> subscribe_and_join(TransactionRequestChannel, "transaction_request:#{request.id}") - - assert res == :error - assert code == :forbidden_channel + request.id + |> topic() + |> test_with_topic(TransactionRequestChannel) + |> assert_failure(:forbidden_channel) end test "can't join channel with inexisting request" do - user = insert(:user) - - {res, code} = - "test" - |> socket(%{auth: %{authenticated: true, user: user}}) - |> subscribe_and_join(TransactionRequestChannel, "transaction_request:123") - - assert res == :error - assert code == :channel_not_found + "123" + |> topic() + |> test_with_topic(TransactionRequestChannel) + |> assert_failure(:forbidden_channel) end end end diff --git a/apps/ewallet_api/test/ewallet_api/v1/channels/user_channel_test.exs b/apps/ewallet_api/test/ewallet_api/v1/channels/user_channel_test.exs index 8d47f2d1b..8953912f2 100644 --- a/apps/ewallet_api/test/ewallet_api/v1/channels/user_channel_test.exs +++ b/apps/ewallet_api/test/ewallet_api/v1/channels/user_channel_test.exs @@ -2,69 +2,51 @@ defmodule EWalletAPI.V1.UserChannelTest do use EWalletAPI.ChannelCase, async: false alias EWalletAPI.V1.UserChannel - alias EWalletDB.User + + defp topic(id), do: "user:#{id}" describe "join/3 as client" do test "joins the channel with authenticated user and same user (using id)" do - {:ok, user} = :user |> params_for() |> User.insert() - - {res, _, socket} = - "test" - |> socket(%{auth: %{authenticated: true, user: user}}) - |> subscribe_and_join(UserChannel, "user:#{user.id}") + user = get_test_user() - assert res == :ok - assert socket.topic == "user:#{user.id}" + user.id + |> topic() + |> test_with_topic(UserChannel) + |> assert_success(topic(user.id)) end test "joins the channel with authenticated user and same user (using provider_user_id)" do - {:ok, user} = :user |> params_for() |> User.insert() - - {res, _, socket} = - "test" - |> socket(%{auth: %{authenticated: true, user: user}}) - |> subscribe_and_join(UserChannel, "user:#{user.provider_user_id}") + user = get_test_user() - assert res == :ok - assert socket.topic == "user:#{user.provider_user_id}" + user.provider_user_id + |> topic() + |> test_with_topic(UserChannel) + |> assert_success(topic(user.provider_user_id)) end test "can't join channel with existing different user (using id)" do - user1 = insert(:user) - user2 = insert(:user) - - {res, code} = - "test" - |> socket(%{auth: %{authenticated: true, user: user1}}) - |> subscribe_and_join(UserChannel, "user:#{user2.id}") + user = insert(:user) - assert res == :error - assert code == :forbidden_channel + user.id + |> topic() + |> test_with_topic(UserChannel) + |> assert_failure(:forbidden_channel) end test "can't join channel with existing different user (using provider_user_id)" do - user1 = insert(:user) - user2 = insert(:user) - - {res, code} = - "test" - |> socket(%{auth: %{authenticated: true, user: user1}}) - |> subscribe_and_join(UserChannel, "user:#{user2.provider_user_id}") + user = insert(:user) - assert res == :error - assert code == :forbidden_channel + user.provider_user_id + |> topic() + |> test_with_topic(UserChannel) + |> assert_failure(:forbidden_channel) end test "can't join channel with inexisting user" do - user = insert(:user) - - {res, code} = - "test" - |> socket(%{auth: %{authenticated: true, user: user}}) - |> subscribe_and_join(UserChannel, "user:123") - - assert res == :error - assert code == :channel_not_found + "usr_123" + |> topic() + |> test_with_topic(UserChannel) + |> assert_failure(:forbidden_channel) end end end diff --git a/apps/ewallet_api/test/ewallet_api/v1/channels/wallet_channel_test.exs b/apps/ewallet_api/test/ewallet_api/v1/channels/wallet_channel_test.exs new file mode 100644 index 000000000..7130a23a7 --- /dev/null +++ b/apps/ewallet_api/test/ewallet_api/v1/channels/wallet_channel_test.exs @@ -0,0 +1,35 @@ +# credo:disable-for-this-file +defmodule EWalletAPI.V1.WalletChannelTest do + use EWalletAPI.ChannelCase, async: false + alias EWalletAPI.V1.WalletChannel + alias EWalletDB.User + + defp topic(address), do: "address:#{address}" + + describe "join/3 as client" do + test "Can join the channel with authenticated user and owned address" do + wallet = User.get_primary_wallet(get_test_user()) + + wallet.address + |> topic() + |> test_with_topic(WalletChannel) + |> assert_success(topic(wallet.address)) + end + + test "can't join channel with existing not owned address" do + wallet = insert(:wallet) + + wallet.address + |> topic() + |> test_with_topic(WalletChannel) + |> assert_failure(:forbidden_channel) + end + + test "can't join channel with inexisting address" do + "none000000000000" + |> topic() + |> test_with_topic(WalletChannel) + |> assert_failure(:forbidden_channel) + end + end +end diff --git a/apps/ewallet_api/test/ewallet_api/v1/controllers/reset_password_controller_test.exs b/apps/ewallet_api/test/ewallet_api/v1/controllers/reset_password_controller_test.exs new file mode 100644 index 000000000..e0aa00488 --- /dev/null +++ b/apps/ewallet_api/test/ewallet_api/v1/controllers/reset_password_controller_test.exs @@ -0,0 +1,274 @@ +defmodule EWalletAPI.V1.ResetPasswordControllerTest do + use EWalletAPI.ConnCase, async: true + use Bamboo.Test + alias EWallet.ForgetPasswordEmail + alias Utils.Helpers.Crypto + alias EWalletDB.{ForgetPasswordRequest, Repo, User} + + @redirect_url "http://localhost:4000/reset_password?email={email}&token={token}" + + describe "ResetPasswordController.reset/2" do + test "returns success if the request was generated successfully" do + {:ok, user} = :admin |> params_for() |> User.insert() + + response = + public_request("/user.reset_password", %{ + "email" => user.email, + "redirect_url" => @redirect_url + }) + + request = + ForgetPasswordRequest + |> Repo.get_by(user_uuid: user.uuid) + |> Repo.preload(:user) + + assert response["success"] + assert_delivered_email(ForgetPasswordEmail.create(request, @redirect_url)) + assert request != nil + assert request.token != nil + end + + test "returns client:invalid_parameter error if the redirect_url is not allowed" do + redirect_url = "http://unknown-url.com/reset_password?email={email}&token={token}" + + response = + public_request("/user.reset_password", %{ + "email" => "example@mail.com", + "redirect_url" => redirect_url + }) + + assert response["success"] == false + assert response["data"]["code"] == "client:invalid_parameter" + + assert response["data"]["description"] == + "The given `redirect_url` is not allowed. Got: '#{redirect_url}'." + end + + test "returns an error when sending email = nil" do + response = + public_request("/user.reset_password", %{ + "email" => nil, + "redirect_url" => @redirect_url + }) + + assert response["success"] == false + assert response["data"]["code"] == "client:invalid_parameter" + end + + test "returns a success without a new request, when the given email is not found" do + num_requests = Repo.aggregate(ForgetPasswordRequest, :count, :token) + + response = + public_request("/user.reset_password", %{ + "email" => "example@mail.com", + "redirect_url" => @redirect_url + }) + + assert response["success"] == true + assert Repo.aggregate(ForgetPasswordRequest, :count, :token) == num_requests + end + + test "returns an error if the email is not supplied" do + {:ok, user} = :admin |> params_for() |> User.insert() + + response = + public_request("/user.reset_password", %{ + "redirect_url" => @redirect_url + }) + + request = + ForgetPasswordRequest + |> Repo.get_by(user_uuid: user.uuid) + |> Repo.preload(:user) + + assert response["success"] == false + assert response["data"]["code"] == "client:invalid_parameter" + assert request == nil + end + + test "returns an error if the redirect_url is not supplied" do + {:ok, user} = :admin |> params_for() |> User.insert() + + response = + public_request("/user.reset_password", %{ + "email" => user.email + }) + + request = + ForgetPasswordRequest + |> Repo.get_by(user_uuid: user.uuid) + |> Repo.preload(:user) + + assert response["success"] == false + assert response["data"]["code"] == "client:invalid_parameter" + assert request == nil + end + + test "generates an activity log" do + {:ok, user} = :admin |> params_for() |> User.insert() + + timestamp = DateTime.utc_now() + + response = + public_request("/user.reset_password", %{ + "email" => user.email, + "redirect_url" => @redirect_url + }) + + request = + ForgetPasswordRequest + |> Repo.get_by(user_uuid: user.uuid) + |> Repo.preload(:user) + + assert response["success"] == true + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: :system, + target: request, + changes: %{ + "expires_at" => NaiveDateTime.to_iso8601(request.expires_at), + "token" => request.token, + "user_uuid" => user.uuid + }, + encrypted_changes: %{} + ) + end + end + + describe "ResetPasswordController.update/2" do + test "returns success and updates the password if the password has been reset succesfully" do + {:ok, user} = :admin |> params_for() |> User.insert() + {:ok, request} = ForgetPasswordRequest.generate(user) + + assert user.password_hash != Crypto.hash_password("password") + + response = + public_request("/user.update_password", %{ + email: user.email, + token: request.token, + password: "password", + password_confirmation: "password" + }) + + assert response["success"] + user = User.get(user.id) + assert Crypto.verify_password("password", user.password_hash) + assert ForgetPasswordRequest.all_active() |> length() == 0 + end + + test "returns a token_not_found error when the user is not found" do + {:ok, user} = :admin |> params_for() |> User.insert() + {:ok, request} = ForgetPasswordRequest.generate(user) + + response = + public_request("/user.update_password", %{ + email: "example@mail.com", + token: request.token, + password: "password", + password_confirmation: "password" + }) + + assert response["success"] == false + assert response["data"]["code"] == "forget_password:token_not_found" + assert ForgetPasswordRequest |> Repo.all() |> length() == 1 + end + + test "returns a token_not_found error when the request is not found" do + {:ok, user} = :admin |> params_for() |> User.insert() + _request = ForgetPasswordRequest.generate(user) + + assert user.password_hash != Crypto.hash_password("password") + + response = + public_request("/user.update_password", %{ + email: user.email, + token: "123", + password: "password", + password_confirmation: "password" + }) + + assert response["success"] == false + assert response["data"]["code"] == "forget_password:token_not_found" + assert ForgetPasswordRequest |> Repo.all() |> length() == 1 + end + + test "returns a client:invalid_parameter error when the password is too short" do + {:ok, user} = :admin |> params_for() |> User.insert() + {:ok, request} = ForgetPasswordRequest.generate(user) + + assert user.password_hash != Crypto.hash_password("password") + + response = + public_request("/user.update_password", %{ + email: user.email, + token: request.token, + password: "short", + password_confirmation: "short" + }) + + assert response["success"] == false + assert response["data"]["code"] == "client:invalid_parameter" + + assert response["data"]["description"] == + "Invalid parameter provided. `password` must be 8 characters or more." + + assert ForgetPasswordRequest |> Repo.all() |> length() == 1 + end + + test "returns an invalid parameter error when the email is not sent" do + {:ok, user} = :admin |> params_for() |> User.insert() + {:ok, request} = ForgetPasswordRequest.generate(user) + + assert user.password_hash != Crypto.hash_password("password") + + response = + public_request("/user.update_password", %{ + token: request.token, + password: "password", + password_confirmation: "password" + }) + + assert response["success"] == false + assert response["data"]["code"] == "client:invalid_parameter" + assert ForgetPasswordRequest |> Repo.all() |> length() == 1 + end + + test "generates an activity log" do + {:ok, user} = :admin |> params_for() |> User.insert() + {:ok, request} = ForgetPasswordRequest.generate(user) + + timestamp = DateTime.utc_now() + + response = + public_request("/user.update_password", %{ + email: user.email, + token: request.token, + password: "password", + password_confirmation: "password" + }) + + assert response["success"] == true + + user = User.get(user.id) + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: request, + target: user, + changes: %{"password_hash" => user.password_hash}, + encrypted_changes: %{} + ) + end + end +end diff --git a/apps/ewallet_api/test/ewallet_api/v1/plugs/client_auth_plug_test.exs b/apps/ewallet_api/test/ewallet_api/v1/plugs/client_auth_plug_test.exs index 145bb477d..389aae73d 100644 --- a/apps/ewallet_api/test/ewallet_api/v1/plugs/client_auth_plug_test.exs +++ b/apps/ewallet_api/test/ewallet_api/v1/plugs/client_auth_plug_test.exs @@ -11,7 +11,7 @@ defmodule EWalletAPI.V1.ClientAuthPlugTest do refute conn.halted assert conn.assigns[:authenticated] == true assert conn.assigns[:auth_scheme] == :client - assert conn.assigns.user.username == @username + assert conn.assigns.end_user.username == @username end test "halts with :invalid_api_key if api_key is missing" do @@ -77,7 +77,7 @@ defmodule EWalletAPI.V1.ClientAuthPlugTest do refute conn.halted assert AuthToken.authenticate(@auth_token, :ewallet_api) == :token_expired refute conn.assigns[:authenticated] - refute conn.assigns[:user] + refute conn.assigns[:end_user] end end diff --git a/apps/ewallet_api/test/ewallet_api/v1/views/reset_password_view_test.exs b/apps/ewallet_api/test/ewallet_api/v1/views/reset_password_view_test.exs new file mode 100644 index 000000000..3f3d98323 --- /dev/null +++ b/apps/ewallet_api/test/ewallet_api/v1/views/reset_password_view_test.exs @@ -0,0 +1,15 @@ +defmodule EWalletAPI.V1.ResetPasswordViewTest do + use EWalletAPI.ViewCase, :v1 + alias EWalletAPI.V1.ResetPasswordView + + describe "render/2" do + test "renders empty.json correctly" do + assert ResetPasswordView.render("empty.json", %{success: true}) == + %{ + version: @expected_version, + success: true, + data: %{} + } + end + end +end diff --git a/apps/ewallet_api/test/support/channel_case.ex b/apps/ewallet_api/test/support/channel_case.ex index a85921071..d601393a6 100644 --- a/apps/ewallet_api/test/support/channel_case.ex +++ b/apps/ewallet_api/test/support/channel_case.ex @@ -13,9 +13,16 @@ defmodule EWalletAPI.ChannelCase do of the test unless the test case is marked as async. """ - use ExUnit.CaseTemplate, async: false + use ExUnit.CaseTemplate + use Phoenix.ChannelTest alias Ecto.Adapters.SQL.Sandbox + import EWalletDB.Factory alias EWalletConfig.ConfigTestHelper + alias EWalletDB.User + + @endpoint EWalletAPI.V1.Endpoint + + @provider_user_id "test_provider_user_id" using do quote do @@ -23,13 +30,20 @@ defmodule EWalletAPI.ChannelCase do use Phoenix.ChannelTest alias Ecto.Adapters.SQL.Sandbox import EWalletDB.Factory + import EWalletAPI.ChannelCase # The default endpoint for testing @endpoint EWalletAPI.V1.Endpoint + + @provider_user_id unquote(@provider_user_id) end end setup tags do + # Restarts `EWalletConfig.Config` so it does not hang on to a DB connection for too long. + Supervisor.terminate_child(EWalletConfig.Supervisor, EWalletConfig.Config) + Supervisor.restart_child(EWalletConfig.Supervisor, EWalletConfig.Config) + :ok = Sandbox.checkout(EWalletConfig.Repo) :ok = Sandbox.checkout(EWalletDB.Repo) :ok = Sandbox.checkout(LocalLedgerDB.Repo) @@ -42,8 +56,11 @@ defmodule EWalletAPI.ChannelCase do Sandbox.mode(ActivityLogger.Repo, {:shared, self()}) end + config_pid = start_supervised!(EWalletConfig.Config) + ConfigTestHelper.restart_config_genserver( self(), + config_pid, EWalletConfig.Repo, [:ewallet_db, :ewallet, :ewallet_api], %{ @@ -53,6 +70,33 @@ defmodule EWalletAPI.ChannelCase do } ) + {:ok, _user} = + :user + |> params_for(%{provider_user_id: @provider_user_id}) + |> User.insert() + :ok end + + def get_test_user, do: User.get_by_provider_user_id(@provider_user_id) + + def test_socket(provider_user_id \\ @provider_user_id) do + socket("test", %{ + auth: %{authenticated: true, end_user: User.get_by_provider_user_id(provider_user_id)} + }) + end + + def test_with_topic(topic, channel, provider_user_id \\ @provider_user_id) do + subscribe_and_join(test_socket(provider_user_id), channel, topic) + end + + def assert_success({res, _, socket}, topic) do + assert res == :ok + assert socket.topic == topic + end + + def assert_failure({res, code}, error) do + assert res == :error + assert code == error + end end diff --git a/apps/ewallet_api/test/support/conn_case.ex b/apps/ewallet_api/test/support/conn_case.ex index 020e916fc..c4befdbfe 100644 --- a/apps/ewallet_api/test/support/conn_case.ex +++ b/apps/ewallet_api/test/support/conn_case.ex @@ -47,6 +47,7 @@ defmodule EWalletAPI.ConnCase do import EWalletAPI.ConnCase import EWalletAPI.Router.Helpers import EWalletDB.Factory + import ActivityLogger.ActivityLoggerTestHelper # Reiterate all module attributes from @endpoint EWalletAPI.Endpoint @@ -61,6 +62,10 @@ defmodule EWalletAPI.ConnCase do end setup tags do + # Restarts `EWalletConfig.Config` so it does not hang on to a DB connection for too long. + Supervisor.terminate_child(EWalletConfig.Supervisor, EWalletConfig.Config) + Supervisor.restart_child(EWalletConfig.Supervisor, EWalletConfig.Config) + :ok = Sandbox.checkout(EWalletDB.Repo) :ok = Sandbox.checkout(LocalLedgerDB.Repo) :ok = Sandbox.checkout(EWalletConfig.Repo) @@ -73,17 +78,19 @@ defmodule EWalletAPI.ConnCase do Sandbox.mode(ActivityLogger.Repo, {:shared, self()}) end - pid = - ConfigTestHelper.restart_config_genserver( - self(), - EWalletConfig.Repo, - [:ewallet_db, :ewallet, :ewallet_api], - %{ - "enable_standalone" => true, - "base_url" => "http://localhost:4000", - "email_adapter" => "test" - } - ) + config_pid = start_supervised!(EWalletConfig.Config) + + ConfigTestHelper.restart_config_genserver( + self(), + config_pid, + EWalletConfig.Repo, + [:ewallet_db, :ewallet, :ewallet_api], + %{ + "enable_standalone" => true, + "base_url" => "http://localhost:4000", + "email_adapter" => "test" + } + ) # Insert account via `Account.insert/1` instead of the test factory to initialize wallets, etc. {:ok, account} = :account |> params_for(parent: nil) |> Account.insert() @@ -108,7 +115,7 @@ defmodule EWalletAPI.ConnCase do # by returning {:ok, context_map}. But it would make the code # much less readable, i.e. `test "my test name", context do`, # and access using `context[:attribute]`. - %{config_pid: pid} + %{config_pid: config_pid} end def stringify_keys(%NaiveDateTime{} = value) do diff --git a/apps/ewallet_api/test/test_helper.exs b/apps/ewallet_api/test/test_helper.exs index 804523856..658e7a3b3 100644 --- a/apps/ewallet_api/test/test_helper.exs +++ b/apps/ewallet_api/test/test_helper.exs @@ -3,4 +3,3 @@ ExUnit.start() Ecto.Adapters.SQL.Sandbox.mode(EWalletConfig.Repo, :manual) Ecto.Adapters.SQL.Sandbox.mode(LocalLedgerDB.Repo, :manual) Ecto.Adapters.SQL.Sandbox.mode(EWalletDB.Repo, :manual) -# Ecto.Adapters.SQL.Sandbox.mode(LocalLedgerDB.Repo, :manual) diff --git a/apps/ewallet_config/config/config.exs b/apps/ewallet_config/config/config.exs index c567e97b1..9d5b4d610 100644 --- a/apps/ewallet_config/config/config.exs +++ b/apps/ewallet_config/config/config.exs @@ -12,18 +12,18 @@ config :ewallet_config, }, default_settings: %{ # Global Settings - "base_url" => %{key: "base_url", value: "", type: "string", position: 1}, + "base_url" => %{key: "base_url", value: "", type: "string", position: 100}, "redirect_url_prefixes" => %{ key: "redirect_url_prefixes", value: [], type: "array", - position: 2 + position: 101 }, "enable_standalone" => %{ key: "enable_standalone", value: false, type: "boolean", - position: 3, + position: 102, description: "Enables the /user.signup endpoint in the client API, allowing users to sign up directly." }, @@ -31,29 +31,37 @@ config :ewallet_config, key: "max_per_page", value: 100, type: "integer", - position: 4, + position: 103, description: "The maximum number of records that can be returned for a list." }, "min_password_length" => %{ key: "min_password_length", value: 8, type: "integer", - position: 5, + position: 104, description: "The minimum length for passwords." }, + "forget_password_request_lifetime" => %{ + key: "forget_password_request_lifetime", + value: 10, + type: "integer", + position: 105, + description: "The duration (in minutes) that a forget password request will be valid for." + }, + # Email Settings "sender_email" => %{ key: "sender_email", value: "admin@localhost", type: "string", - position: 6, + position: 200, description: "The address from which system emails will be sent." }, "email_adapter" => %{ key: "email_adapter", value: "local", type: "string", - position: 7, + position: 201, options: ["smtp", "local", "test"], description: "When set to local, a local email adapter will be used. Perfect for testing and development." @@ -62,7 +70,7 @@ config :ewallet_config, key: "smtp_host", value: nil, type: "string", - position: 8, + position: 202, description: "The SMTP host to use to send emails.", parent: "email_adapter", parent_value: "smtp" @@ -71,7 +79,7 @@ config :ewallet_config, key: "smtp_port", value: nil, type: "string", - position: 9, + position: 203, description: "The SMTP port to use to send emails.", parent: "email_adapter", parent_value: "smtp" @@ -80,7 +88,7 @@ config :ewallet_config, key: "smtp_username", value: nil, type: "string", - position: 10, + position: 204, description: "The SMTP username to use to send emails.", parent: "email_adapter", parent_value: "smtp" @@ -89,7 +97,7 @@ config :ewallet_config, key: "smtp_password", value: nil, type: "string", - position: 11, + position: 205, description: "The SMTP password to use to send emails.", parent: "email_adapter", parent_value: "smtp" @@ -100,7 +108,7 @@ config :ewallet_config, key: "balance_caching_strategy", value: "since_beginning", type: "string", - position: 12, + position: 300, options: ["since_beginning", "since_last_cached"], description: "The strategy to use for balance caching. It will either re-calculate from the beginning or from the last caching point." @@ -111,7 +119,7 @@ config :ewallet_config, key: "file_storage_adapter", value: "local", type: "string", - position: 13, + position: 400, options: ["local", "gcs", "aws"], description: "The type of storage to use for images and files." }, @@ -121,7 +129,7 @@ config :ewallet_config, key: "gcs_bucket", value: nil, type: "string", - position: 14, + position: 500, parent: "file_storage_adapter", parent_value: "gcs", description: "The name of the GCS bucket." @@ -131,7 +139,7 @@ config :ewallet_config, value: nil, secret: true, type: "string", - position: 15, + position: 501, parent: "file_storage_adapter", parent_value: "gcs", description: "The credentials of the Google Cloud account." @@ -142,7 +150,7 @@ config :ewallet_config, key: "aws_bucket", value: nil, type: "string", - position: 16, + position: 600, parent: "file_storage_adapter", parent_value: "aws", description: "The name of the AWS bucket." @@ -151,7 +159,7 @@ config :ewallet_config, key: "aws_region", value: nil, type: "string", - position: 17, + position: 601, parent: "file_storage_adapter", parent_value: "aws", description: "The AWS region where your bucket lives." @@ -160,7 +168,7 @@ config :ewallet_config, key: "aws_access_key_id", value: nil, type: "string", - position: 18, + position: 602, parent: "file_storage_adapter", parent_value: "aws", description: "An AWS access key having access to the specified bucket." @@ -170,7 +178,7 @@ config :ewallet_config, value: nil, secret: true, type: "string", - position: 19, + position: 603, parent: "file_storage_adapter", parent_value: "aws", description: "An AWS secret having access to the specified bucket." diff --git a/apps/ewallet_config/lib/ewallet_config/config.ex b/apps/ewallet_config/lib/ewallet_config/config.ex index dd8366247..5bf2818c6 100644 --- a/apps/ewallet_config/lib/ewallet_config/config.ex +++ b/apps/ewallet_config/lib/ewallet_config/config.ex @@ -15,32 +15,36 @@ defmodule EWalletConfig.Config do SettingLoader } - @spec start_link(map()) :: {:ok, pid()} | {:error, Atom.t()} - def start_link(named: true) do - GenServer.start_link(__MODULE__, [], name: __MODULE__) + @spec init(Map.t()) :: {:ok, []} + def init(_args) do + {:ok, []} end - @spec start_link() :: {:ok, pid()} | {:error, Atom.t()} - def start_link do - GenServer.start_link(__MODULE__, []) + @spec start_link() :: GenServer.on_start() + def start_link, do: start_link([]) + + @spec start_link(keyword()) :: GenServer.on_start() + def start_link(args) do + {opts, args} = + case Keyword.pop(args, :named) do + {true, popped} -> {[name: __MODULE__], popped} + {_, popped} -> {[], popped} + end + + GenServer.start_link(__MODULE__, args, opts) end - @spec start_link(pid()) :: :ok | {:error, Atom.t()} + @spec stop(pid()) :: :ok def stop(pid \\ __MODULE__) do GenServer.stop(pid) end - @spec start_link(map()) :: {:ok, []} - def init(_args) do - {:ok, []} - end - @spec handle_call(:get_registered_apps, Atom.t(), [Atom.t()]) :: [{Atom.t(), [Atom.t()]}] def handle_call(:get_registered_apps, _from, registered_apps) do {:reply, registered_apps, registered_apps} end - @spec handle_call(:register_and_load, Atom.t(), [Atom.t()]) :: [Atom.t()] + @spec handle_call(:register_and_load, atom(), [atom()]) :: [atom()] def handle_call({:register_and_load, app, settings}, _from, registered_apps) do SettingLoader.load_settings(app, settings) {:reply, :ok, [{app, settings} | registered_apps]} diff --git a/apps/ewallet_config/lib/ewallet_config/setting.ex b/apps/ewallet_config/lib/ewallet_config/setting.ex index 92e84f061..556124041 100644 --- a/apps/ewallet_config/lib/ewallet_config/setting.ex +++ b/apps/ewallet_config/lib/ewallet_config/setting.ex @@ -177,7 +177,7 @@ defmodule EWalletConfig.Setting do {:error, :setting_not_found} setting -> - attrs = cast_attrs(attrs) + attrs = cast_attrs(setting, attrs) setting |> StoredSetting.update_changeset(attrs) @@ -264,6 +264,13 @@ defmodule EWalletConfig.Setting do |> add_position() end + defp cast_attrs(setting, attrs) do + attrs + |> cast_value(setting) + |> cast_options() + |> add_position() + end + defp return_from_change({:ok, stored_setting}) do {:ok, build(stored_setting)} end @@ -290,6 +297,18 @@ defmodule EWalletConfig.Setting do Map.get(options, :array) || Map.get(options, "array") end + defp cast_value(%{value: value} = attrs, %{secret: true}) do + Map.put(attrs, :encrypted_data, %{value: value}) + end + + defp cast_value(%{value: value} = attrs, _) do + Map.put(attrs, :data, %{value: value}) + end + + defp cast_value(attrs, _) do + Map.put(attrs, :data, %{value: nil}) + end + defp cast_value(%{secret: true, value: value} = attrs) do Map.put(attrs, :encrypted_data, %{value: value}) end diff --git a/apps/ewallet_config/lib/ewallet_config/setting_validator.ex b/apps/ewallet_config/lib/ewallet_config/setting_validator.ex index e6337195f..bb3cb25bf 100644 --- a/apps/ewallet_config/lib/ewallet_config/setting_validator.ex +++ b/apps/ewallet_config/lib/ewallet_config/setting_validator.ex @@ -62,8 +62,7 @@ defmodule EWalletConfig.SettingValidator do |> do_validate_type(%{type: type}, changeset) end - @spec validate_type(Changeset.t(), Setting.t()) :: Changeset.t() - def validate_type(changeset, %{secret: false} = setting) do + def validate_type(changeset, setting) do changeset |> get_value(setting) |> do_validate_type(setting, changeset) diff --git a/apps/ewallet_config/lib/ewallet_config/stored_setting.ex b/apps/ewallet_config/lib/ewallet_config/stored_setting.ex index 0645e74cf..61a5afe08 100644 --- a/apps/ewallet_config/lib/ewallet_config/stored_setting.ex +++ b/apps/ewallet_config/lib/ewallet_config/stored_setting.ex @@ -75,7 +75,8 @@ defmodule EWalletConfig.StoredSetting do cast: [ :data, :encrypted_data, - :description + :description, + :position ] ) |> validate_required_exclusive([:data, :encrypted_data]) diff --git a/apps/ewallet_config/mix.exs b/apps/ewallet_config/mix.exs index 3edb7a2c7..3f820d2f9 100644 --- a/apps/ewallet_config/mix.exs +++ b/apps/ewallet_config/mix.exs @@ -4,7 +4,7 @@ defmodule EWalletConfig.MixProject do def project do [ app: :ewallet_config, - version: "0.1.0", + version: Application.get_env(:ewallet, :version), build_path: "../../_build", config_path: "../../config/config.exs", deps_path: "../../deps", diff --git a/apps/ewallet_config/test/ewallet_config/config_test.exs b/apps/ewallet_config/test/ewallet_config/config_test.exs index 6187091b3..61a80c78e 100644 --- a/apps/ewallet_config/test/ewallet_config/config_test.exs +++ b/apps/ewallet_config/test/ewallet_config/config_test.exs @@ -172,7 +172,8 @@ defmodule EWalletConfig.ConfigTest do Sandbox.allow(Repo, self(), pid) :ok = Config.insert_all_defaults(%System{}, %{}, pid) - assert length(Config.settings()) == 19 + default_settings = Application.get_env(:ewallet_config, :default_settings) + assert length(Config.settings()) == Enum.count(default_settings) end end diff --git a/apps/ewallet_config/test/ewallet_config/loaders/file_storage_settings_loader_test.exs b/apps/ewallet_config/test/ewallet_config/loaders/file_storage_settings_loader_test.exs index c0f71326a..5d0cb0605 100644 --- a/apps/ewallet_config/test/ewallet_config/loaders/file_storage_settings_loader_test.exs +++ b/apps/ewallet_config/test/ewallet_config/loaders/file_storage_settings_loader_test.exs @@ -13,8 +13,11 @@ defmodule EWalletConfig.FileStorageSettingsLoaderTest do :gcs_credentials ]) + config_pid = start_supervised!(EWalletConfig.Config) + ConfigTestHelper.restart_config_genserver( self(), + config_pid, EWalletConfig.Repo, [:my_app], opts diff --git a/apps/ewallet_config/test/ewallet_config/setting_test.exs b/apps/ewallet_config/test/ewallet_config/setting_test.exs index 4cda38b45..b5f2bd43d 100644 --- a/apps/ewallet_config/test/ewallet_config/setting_test.exs +++ b/apps/ewallet_config/test/ewallet_config/setting_test.exs @@ -514,9 +514,10 @@ defmodule EWalletConfig.SettingTest do describe "insert_all_defaults/1" do test "insert all defaults without overrides" do assert Setting.insert_all_defaults(%System{}) == :ok + default_settings = Application.get_env(:ewallet_config, :default_settings) settings = Setting.all() - assert length(settings) == 19 + assert length(settings) == Enum.count(default_settings) first_setting = Enum.at(settings, 0) assert first_setting.key == "base_url" @@ -527,7 +528,9 @@ defmodule EWalletConfig.SettingTest do "base_url" => "fake_url" }) == :ok - assert length(Setting.all()) == 19 + default_settings = Application.get_env(:ewallet_config, :default_settings) + + assert length(Setting.all()) == Enum.count(default_settings) assert Setting.get_value("base_url") == "fake_url" end end diff --git a/apps/ewallet_config/test/support/config_test_helper.ex b/apps/ewallet_config/test/support/config_test_helper.ex index b883a897e..08dfcd2ac 100644 --- a/apps/ewallet_config/test/support/config_test_helper.ex +++ b/apps/ewallet_config/test/support/config_test_helper.ex @@ -6,8 +6,7 @@ defmodule EWalletConfig.ConfigTestHelper do alias Ecto.Adapters.SQL.Sandbox alias ActivityLogger.System - def restart_config_genserver(parent, repo, apps, attrs) do - {:ok, pid} = Config.start_link() + def restart_config_genserver(parent, pid, repo, apps, attrs) do Sandbox.allow(repo, parent, pid) Sandbox.allow(ActivityLogger.Repo, parent, pid) @@ -17,8 +16,6 @@ defmodule EWalletConfig.ConfigTestHelper do settings = Application.get_env(app, :settings) Config.register_and_load(app, settings, pid) end) - - pid end def spawn(nodes \\ []) do diff --git a/apps/ewallet_config/test/support/schema_case.ex b/apps/ewallet_config/test/support/schema_case.ex index 5f06d89b3..c5b342896 100644 --- a/apps/ewallet_config/test/support/schema_case.ex +++ b/apps/ewallet_config/test/support/schema_case.ex @@ -10,6 +10,10 @@ defmodule EWalletConfig.SchemaCase do alias EWalletConfig.Repo setup do + # Restarts `EWalletConfig.Config` so it does not hang on to a DB connection for too long. + Supervisor.terminate_child(EWalletConfig.Supervisor, EWalletConfig.Config) + Supervisor.restart_child(EWalletConfig.Supervisor, EWalletConfig.Config) + Sandbox.checkout(Repo) Sandbox.checkout(ActivityLogger.Repo) end diff --git a/apps/ewallet_db/lib/ewallet_db/forget_password_request.ex b/apps/ewallet_db/lib/ewallet_db/forget_password_request.ex index 9b371465b..6ce68453c 100644 --- a/apps/ewallet_db/lib/ewallet_db/forget_password_request.ex +++ b/apps/ewallet_db/lib/ewallet_db/forget_password_request.ex @@ -6,12 +6,13 @@ defmodule EWalletDB.ForgetPasswordRequest do use ActivityLogger.ActivityLogging import Ecto.{Changeset, Query} alias Ecto.UUID - alias EWalletDB.{ForgetPasswordRequest, Repo, User} + alias EWalletDB.{Repo, User} alias Utils.Helpers.Crypto alias ActivityLogger.System @primary_key {:uuid, UUID, autogenerate: true} @token_length 32 + @default_lifetime_minutes 10 schema "forget_password_request" do field(:token, :string) @@ -25,6 +26,9 @@ defmodule EWalletDB.ForgetPasswordRequest do type: UUID ) + field(:used_at, :naive_datetime) + field(:expires_at, :naive_datetime) + timestamps() activity_logging() end @@ -33,44 +37,55 @@ defmodule EWalletDB.ForgetPasswordRequest do changeset |> cast_and_validate_required_for_activity_log( attrs, - cast: [:token, :user_uuid], + cast: [:token, :user_uuid, :expires_at], required: [ :token, - :user_uuid + :user_uuid, + :expires_at ] ) |> assoc_constraint(:user) end + defp expire_changeset(changeset, attrs) do + changeset + |> cast(attrs, [:enabled]) + |> validate_required([:enabled]) + end + + defp expire_as_used_changeset(changeset, attrs) do + changeset + |> cast(attrs, [:enabled, :used_at]) + |> validate_required([:enabled, :used_at]) + end + @doc """ Retrieves all active requests. """ - @spec all_active() :: [%ForgetPasswordRequest{}] + @spec all_active() :: [%__MODULE__{}] def all_active do - ForgetPasswordRequest - |> where([c], c.enabled == true) - |> order_by([c], desc: c.inserted_at) + __MODULE__ + |> where([f], f.enabled == true) + |> order_by([f], desc: f.inserted_at) |> Repo.all() end @doc """ Retrieves a specific invite by its token. """ - @spec get(%User{} | nil, String.t() | nil) :: %ForgetPasswordRequest{} | nil + @spec get(%User{} | nil, String.t() | nil) :: %__MODULE__{} | nil def get(nil, _), do: nil def get(_, nil), do: nil def get(user, token) do - request = - ForgetPasswordRequest - |> where([c], c.user_uuid == ^user.uuid) - |> where([c], c.enabled == true) - |> order_by([c], desc: c.inserted_at) - |> limit(1) - |> Repo.one() + requests = + __MODULE__ + |> where([f], f.user_uuid == ^user.uuid) + |> where([f], f.enabled == true) + |> Repo.all() |> Repo.preload(:user) - check_token(request, token) + Enum.find(requests, fn r -> check_token(r, token) end) end defp check_token(nil, _token), do: nil @@ -80,34 +95,72 @@ defmodule EWalletDB.ForgetPasswordRequest do end @doc """ - Deletes all the current requests for a user. + Expires the given request. + """ + @spec expire(%__MODULE__{}) :: {:ok, %__MODULE__{}} | {:error, Ecto.Changeset.t()} + def expire(request) do + request + |> expire_changeset(%{enabled: false}) + |> Repo.update() + end + + @doc """ + Expires the given request and set the `used_at` field. + """ + @spec expire_as_used(%__MODULE__{}) :: {:ok, %__MODULE__{}} | {:error, Ecto.Changeset.t()} + def expire_as_used(request) do + request + |> expire_as_used_changeset(%{enabled: false, used_at: NaiveDateTime.utc_now()}) + |> Repo.update() + end + + @doc """ + Expires all requests that their expiry dates have passed. """ - @spec disable_all_for(%User{}) :: {integer(), nil} - def disable_all_for(user) do - ForgetPasswordRequest - |> where([f], f.user_uuid == ^user.uuid) - |> Repo.update_all(set: [enabled: false]) + @spec expire_all() :: {:ok, integer()} + def expire_all do + now = NaiveDateTime.utc_now() + + # There's a DB index for [:enabled, :expires_at], so filtering for + # `:enabled` then `:expires_at` should be efficient. + {num_updated, _} = + __MODULE__ + |> where([f], f.enabled == true) + |> where([f], not is_nil(f.expires_at)) + |> where([f], f.expires_at <= ^now) + |> Repo.update_all([set: [enabled: false]], returning: true) + + {:ok, num_updated} end @doc """ Generates a forget password request for the given user. """ - @spec generate(%User{}) :: %ForgetPasswordRequest{} | {:error, Ecto.Changeset.t()} + @spec generate(%User{}) :: %__MODULE__{} | {:error, Ecto.Changeset.t()} def generate(user) do token = Crypto.generate_base64_key(@token_length) - {:ok, _} = - insert(%{ - token: token, - user_uuid: user.uuid, - originator: %System{} - }) - - ForgetPasswordRequest.get(user, token) + lifetime_minutes = + Application.get_env(:ewallet, :forget_password_request_lifetime, @default_lifetime_minutes) + + expires_at = NaiveDateTime.utc_now() |> NaiveDateTime.add(60 * lifetime_minutes) + + with {:ok, _} <- + insert(%{ + token: token, + user_uuid: user.uuid, + expires_at: expires_at, + originator: %System{} + }), + %__MODULE__{} = request <- __MODULE__.get(user, token) do + {:ok, request} + else + error -> error + end end defp insert(attrs) do - %ForgetPasswordRequest{} + %__MODULE__{} |> changeset(attrs) |> Repo.insert_record_with_activity_log() end diff --git a/apps/ewallet_db/lib/ewallet_db/key.ex b/apps/ewallet_db/lib/ewallet_db/key.ex index 59a4b7bf7..08c4c2112 100644 --- a/apps/ewallet_db/lib/ewallet_db/key.ex +++ b/apps/ewallet_db/lib/ewallet_db/key.ex @@ -7,6 +7,7 @@ defmodule EWalletDB.Key do use Utils.Types.ExternalID use ActivityLogger.ActivityLogging import Ecto.{Changeset, Query} + import EWalletDB.Helpers.Preloader alias Ecto.UUID alias Utils.Helpers.Crypto alias EWalletDB.{Account, Key, Repo} @@ -75,27 +76,26 @@ defmodule EWalletDB.Key do end @doc """ - Get key by id, exclude soft-deleted. + Retrieves a key with the given ID. """ - @spec get(ExternalID.t()) :: %Key{} | nil - def get(id) + @spec get(String.t(), keyword()) :: %__MODULE__{} | nil + def get(id, opts \\ []) - def get(id) when is_external_id(id) do - Key - |> exclude_deleted() - |> Repo.get_by(id: id) + def get(id, opts) when is_external_id(id) do + get_by([id: id], opts) end - def get(_), do: nil + def get(_id, _opts), do: nil @doc """ - Get key by its `:access_key`, exclude soft-deleted. + Retrieves a key using one or more fields. """ - @spec get(:access_key, String.t()) :: %Key{} | nil - def get(:access_key, access_key) do - Key + @spec get_by(map() | keyword(), keyword()) :: %__MODULE__{} | nil + def get_by(fields, opts \\ []) do + __MODULE__ |> exclude_deleted() - |> Repo.get_by(access_key: access_key) + |> Repo.get_by(fields) + |> preload_option(opts) end @doc """ diff --git a/apps/ewallet_db/mix.exs b/apps/ewallet_db/mix.exs index 1faa971fa..221e06288 100644 --- a/apps/ewallet_db/mix.exs +++ b/apps/ewallet_db/mix.exs @@ -4,7 +4,7 @@ defmodule EWalletDB.Mixfile do def project do [ app: :ewallet_db, - version: "0.1.0-beta", + version: Application.get_env(:ewallet, :version), build_path: "../../_build", config_path: "../../config/config.exs", deps_path: "../../deps", diff --git a/apps/ewallet_db/priv/repo/migrations/20181128093519_add_used_at_and_expires_at_to_forget_password_request.exs b/apps/ewallet_db/priv/repo/migrations/20181128093519_add_used_at_and_expires_at_to_forget_password_request.exs new file mode 100644 index 000000000..2063cf14a --- /dev/null +++ b/apps/ewallet_db/priv/repo/migrations/20181128093519_add_used_at_and_expires_at_to_forget_password_request.exs @@ -0,0 +1,50 @@ +defmodule EWalletDB.Repo.Migrations.AddUsedAtAndExpiresAtToForgetPasswordRequest do + use Ecto.Migration + import Ecto.Query + alias EWalletDB.Repo + + def up do + alter table(:forget_password_request) do + add :used_at, :naive_datetime + add :expires_at, :naive_datetime + end + + create index(:forget_password_request, [:enabled, :expires_at]) + flush() + + add_expires_at_to_existing_requests() + end + + def down do + expires_past_requests() + + alter table(:forget_password_request) do + remove :used_at + remove :expires_at + end + end + + # Private functions + + defp add_expires_at_to_existing_requests do + query = from(f in "forget_password_request", + update: [ + set: [ + expires_at: fragment("? + '10 minute'::INTERVAL", f.inserted_at) + ] + ] + ) + + Repo.update_all(query, []) + end + + defp expires_past_requests do + query = from(f in "forget_password_request", + where: f.enabled == true, + where: f.expires_at <= ^NaiveDateTime.utc_now(), + update: [set: [enabled: false]] + ) + + Repo.update_all(query, []) + end +end diff --git a/apps/ewallet_db/priv/repo/seeds_settings/00_setting.exs b/apps/ewallet_db/priv/repo/seeds_settings/00_setting.exs index 5ef58b47c..8b77370d0 100644 --- a/apps/ewallet_db/priv/repo/seeds_settings/00_setting.exs +++ b/apps/ewallet_db/priv/repo/seeds_settings/00_setting.exs @@ -1,6 +1,6 @@ # credo:disable-for-this-file defmodule EWalletDB.Repo.Seeds.SettingSeed do - alias EWalletConfig.Config + alias EWalletConfig.{Config, Setting} @argsline_desc """ The base URL is needed for various operators in the eWallet. It will be saved in the @@ -45,8 +45,9 @@ defmodule EWalletDB.Repo.Seeds.SettingSeed do |> case do {:ok, setting} -> writer.success(""" - Key : #{setting.key} - Value : #{setting.value} + Key : #{setting.key} + Value : #{setting.value} + Position : #{setting.position} """) {:error, changeset} -> writer.error(" The setting could not be inserted:") @@ -56,10 +57,34 @@ defmodule EWalletDB.Repo.Seeds.SettingSeed do writer.error(" Unknown error.") end setting -> - writer.warn(""" - Key : #{setting.key} - Value : #{setting.value} - """) + case sync_position(key, setting, data) do + {:ok, old_position, new_position} -> + writer.warn(""" + Key : #{setting.key} + Value : #{setting.value} + Position : #{old_position} -> #{new_position} + """) + + {:error, changeset} -> + writer.error(" The setting's position could not be synchronized:") + writer.print_errors(changeset) + + nil -> + writer.warn(""" + Key : #{setting.key} + Value : #{setting.value} + Position : #{setting.position} + """) + end end end + + defp sync_position(key, %{position: old_pos}, %{position: new_pos}) when is_integer(old_pos) and is_integer(new_pos) and old_pos != new_pos do + case Setting.update(key, %{position: new_pos}) do + {:ok, _} -> {:ok, old_pos, new_pos} + {:error, changeset} -> {:error, changeset} + end + end + + defp sync_position(_, _, _), do: nil end diff --git a/apps/ewallet_db/test/ewallet_db/forget_password_request_test.exs b/apps/ewallet_db/test/ewallet_db/forget_password_request_test.exs index 2c340f1f7..520a71493 100644 --- a/apps/ewallet_db/test/ewallet_db/forget_password_request_test.exs +++ b/apps/ewallet_db/test/ewallet_db/forget_password_request_test.exs @@ -1,13 +1,6 @@ defmodule EWalletDB.ForgetPasswordRequestTest do use EWalletDB.SchemaCase - import Ecto.Query - alias EWalletDB.{Repo, ForgetPasswordRequest} - - defp get_request_by_uuid(uuid) do - ForgetPasswordRequest - |> where([a], a.uuid == ^uuid) - |> Repo.one() - end + alias EWalletDB.{ForgetPasswordRequest, Repo} describe "all_active/0" do test "returns only enabled requests" do @@ -32,10 +25,15 @@ defmodule EWalletDB.ForgetPasswordRequestTest do describe "get/2" do test "returns the request with the given email and token" do user = insert(:admin) - request = insert(:forget_password_request, user: user) - result = ForgetPasswordRequest.get(user, request.token) + request_1 = insert(:forget_password_request, user: user) + request_2 = insert(:forget_password_request, user: user) + request_3 = insert(:forget_password_request, user: user) + + result = ForgetPasswordRequest.get(user, request_2.token) - assert result.uuid == request.uuid + refute result.uuid == request_1.uuid + assert result.uuid == request_2.uuid + refute result.uuid == request_3.uuid end test "returns nil if the request is disabled" do @@ -81,33 +79,56 @@ defmodule EWalletDB.ForgetPasswordRequestTest do end end - describe "disable_all_for/1" do - test "disables all requests for the given user" do - user_1 = insert(:admin) - user_2 = insert(:admin) + describe "expire/1" do + test "expires the given request" do + user = insert(:admin) + request_1 = insert(:forget_password_request, user: user) + request_2 = insert(:forget_password_request, user: user) + request_3 = insert(:forget_password_request, user: user) + + {res, _} = ForgetPasswordRequest.expire(request_2) - request_1 = insert(:forget_password_request, user: user_1, enabled: true) - request_2 = insert(:forget_password_request, user: user_1, enabled: false) - request_3 = insert(:forget_password_request, user: user_2, enabled: true) - request_4 = insert(:forget_password_request, user: user_2, enabled: false) + assert res == :ok + assert ForgetPasswordRequest.get(user, request_1.token) + refute ForgetPasswordRequest.get(user, request_2.token) + assert ForgetPasswordRequest.get(user, request_3.token) + end - _ = ForgetPasswordRequest.disable_all_for(user_1) + test "does not re-enable the request if expiring an expired request" do + user = insert(:admin) + request = insert(:forget_password_request, user: user, enabled: false) - assert get_request_by_uuid(request_1.uuid).enabled == false - assert get_request_by_uuid(request_2.uuid).enabled == false - assert get_request_by_uuid(request_3.uuid).enabled == true - assert get_request_by_uuid(request_4.uuid).enabled == false + {res, _} = ForgetPasswordRequest.expire(request) + + assert res == :ok + refute ForgetPasswordRequest.get(user, request.token) + end + end + + describe "expire_as_used/1" do + test "expires and sets used_at on the given request" do + user = insert(:admin) + request = insert(:forget_password_request, user: user) + + {res, _} = ForgetPasswordRequest.expire_as_used(request) + + assert res == :ok + assert ForgetPasswordRequest |> Repo.get(request.uuid) |> Map.get(:used_at) != nil end end describe "generate/2" do test "returns an ForgetPasswordRequest" do user = insert(:admin) - request = ForgetPasswordRequest.generate(user) + {res, request} = ForgetPasswordRequest.generate(user) + + assert res == :ok assert %ForgetPasswordRequest{} = request + assert request.enabled == true assert request.user_uuid == user.uuid - assert String.length(request.token) == 43 + assert request.expires_at != nil + assert request.token |> String.length() == 43 end end end diff --git a/apps/ewallet_db/test/ewallet_db/key_test.exs b/apps/ewallet_db/test/ewallet_db/key_test.exs index 316d6272e..6ec20d86f 100644 --- a/apps/ewallet_db/test/ewallet_db/key_test.exs +++ b/apps/ewallet_db/test/ewallet_db/key_test.exs @@ -26,7 +26,7 @@ defmodule EWalletDB.KeyTest do end end - describe "get/1" do + describe "get/2" do test "accepts a uuid" do key = insert(:key) result = Key.get(key.id) @@ -47,20 +47,20 @@ defmodule EWalletDB.KeyTest do end end - describe "get/2" do + describe "get_by/2" do test "returns a key if provided an access_key" do key = insert(:key) - result = Key.get(:access_key, key.access_key) + result = Key.get_by(access_key: key.access_key) assert result.uuid == key.uuid end test "does not return a soft-deleted key" do {:ok, key} = :key |> insert() |> Key.delete(%System{}) - assert Key.get(:access_key, key.access_key) == nil + assert Key.get_by(access_key: key.access_key) == nil end test "returns nil if the key with the given access_key is not found" do - assert Key.get(:access_key, "not_access_key") == nil + assert Key.get(access_key: "not_access_key") == nil end end diff --git a/apps/ewallet_db/test/support/factory.ex b/apps/ewallet_db/test/support/factory.ex index 29de9d1c3..939fdb159 100644 --- a/apps/ewallet_db/test/support/factory.ex +++ b/apps/ewallet_db/test/support/factory.ex @@ -270,7 +270,8 @@ defmodule EWalletDB.Factory do %ForgetPasswordRequest{ token: sequence("123"), enabled: true, - originator: %System{} + originator: %System{}, + expires_at: NaiveDateTime.utc_now() |> NaiveDateTime.add(60 * 10) } end diff --git a/apps/load_tester/mix.exs b/apps/load_tester/mix.exs index 249422742..405b22cc3 100644 --- a/apps/load_tester/mix.exs +++ b/apps/load_tester/mix.exs @@ -4,7 +4,7 @@ defmodule LoadTester.MixProject do def project do [ app: :load_tester, - version: "0.1.0", + version: Application.get_env(:ewallet, :version), build_path: "../../_build", config_path: "../../config/config.exs", deps_path: "../../deps", diff --git a/apps/local_ledger/mix.exs b/apps/local_ledger/mix.exs index c069f01ab..0afe97311 100644 --- a/apps/local_ledger/mix.exs +++ b/apps/local_ledger/mix.exs @@ -4,7 +4,7 @@ defmodule LocalLedger.Mixfile do def project do [ app: :local_ledger, - version: "0.1.0-beta", + version: Application.get_env(:ewallet, :version), build_path: "../../_build", config_path: "../../config/config.exs", deps_path: "../../deps", diff --git a/apps/local_ledger_db/lib/local_ledger_db/encrypted/map.ex b/apps/local_ledger_db/lib/local_ledger_db/types/map.ex similarity index 100% rename from apps/local_ledger_db/lib/local_ledger_db/encrypted/map.ex rename to apps/local_ledger_db/lib/local_ledger_db/types/map.ex diff --git a/apps/local_ledger_db/mix.exs b/apps/local_ledger_db/mix.exs index c7a796c87..30e58d7e6 100644 --- a/apps/local_ledger_db/mix.exs +++ b/apps/local_ledger_db/mix.exs @@ -4,7 +4,7 @@ defmodule LocalLedgerDB.Mixfile do def project do [ app: :local_ledger_db, - version: "0.1.0-beta", + version: Application.get_env(:ewallet, :version), build_path: "../../_build", config_path: "../../config/config.exs", deps_path: "../../deps", diff --git a/apps/url_dispatcher/lib/url_dispatcher/plug.ex b/apps/url_dispatcher/lib/url_dispatcher/plug.ex index e3f728b80..da235d6df 100644 --- a/apps/url_dispatcher/lib/url_dispatcher/plug.ex +++ b/apps/url_dispatcher/lib/url_dispatcher/plug.ex @@ -12,7 +12,7 @@ defmodule UrlDispatcher.Plug do defp handle_request("/", conn) do conn |> put_status(200) - |> json(%{status: true}) + |> json(%{status: true, ewallet_version: Application.get_env(:ewallet, :version)}) end defp handle_request("/admin" <> _, conn), do: AdminPanel.Endpoint.call(conn, []) diff --git a/apps/url_dispatcher/mix.exs b/apps/url_dispatcher/mix.exs index f57f07748..633403708 100644 --- a/apps/url_dispatcher/mix.exs +++ b/apps/url_dispatcher/mix.exs @@ -4,7 +4,7 @@ defmodule UrlDispatcher.Mixfile do def project do [ app: :url_dispatcher, - version: "0.1.0-beta", + version: Application.get_env(:ewallet, :version), build_path: "../../_build", config_path: "../../config/config.exs", deps_path: "../../deps", diff --git a/apps/url_dispatcher/test/url_dispatcher/plug_test.exs b/apps/url_dispatcher/test/url_dispatcher/plug_test.exs index 8e5523a9f..7d8522bda 100644 --- a/apps/url_dispatcher/test/url_dispatcher/plug_test.exs +++ b/apps/url_dispatcher/test/url_dispatcher/plug_test.exs @@ -15,7 +15,8 @@ defmodule UrlDispatcher.PlugTest do refute conn.halted assert conn.status == 200 - assert conn.resp_body == ~s({"status":true}) + + assert conn.resp_body == ~s({"status":true,"ewallet_version":"1.1.0"}) end test "returns a 200 response when requesting /api" do diff --git a/config/config.exs b/config/config.exs index 4d0280b8c..6a9be76f7 100644 --- a/config/config.exs +++ b/config/config.exs @@ -9,7 +9,7 @@ use Mix.Config # back to each application for organization purposes. import_config "../apps/*/config/config.exs" -config :ewallet, root: File.cwd!() +config :ewallet, root: File.cwd!(), version: "1.1.0" # Configures Elixir's Logger config :logger, :console, From 534348c0c34ee7d07cc9d1a9a53a56c42dc8b3a6 Mon Sep 17 00:00:00 2001 From: mederic Date: Thu, 13 Dec 2018 14:29:54 +0700 Subject: [PATCH 17/23] Add tests for log generation in controllers --- .../support/activity_logger_test_helper.ex | 91 ++++++ .../v1/controllers/admin_auth_controller.ex | 3 +- .../v1/controllers/role_controller.ex | 1 + .../v1/controllers/user_auth_controller.ex | 3 +- .../admin_auth/account_controller_test.exs | 127 +++++--- .../account_membership_controller_test.exs | 64 +++- .../admin_auth/admin_auth_controller_test.exs | 77 +++++ .../admin_auth/admin_user_controller_test.exs | 34 +- .../admin_auth/api_key_controller_test.exs | 104 ++++++ .../admin_auth/category_controller_test.exs | 76 ++++- .../configuration_controller_test.exs | 26 ++ .../exchange_pair_controller_test.exs | 83 +++++ .../admin_auth/invite_controller_test.exs | 106 +++---- .../admin_auth/key_controller_test.exs | 107 ++++++- .../admin_auth/mint_controller_test.exs | 113 ++++++- .../reset_password_controller_test.exs | 69 +++- .../admin_auth/role_controller_test.exs | 83 ++++- .../admin_auth/self_controller_test.exs | 153 +++++++++ .../admin_auth/token_controller_test.exs | 239 +++++++++++++- ...ransaction_consumption_controller_test.exs | 171 ++++++++++ .../transaction_controller_test.exs | 164 ++++++++++ .../transaction_request_controller_test.exs | 56 ++++ .../update_email_controller_test.exs | 63 ++++ .../admin_auth/user_auth_controller_test.exs | 58 ++++ .../admin_auth/user_controller_test.exs | 156 +++++++-- .../admin_auth/wallet_controller_test.exs | 72 +++++ .../provider_auth/account_controller_test.exs | 127 ++++++++ .../account_membership_controller_test.exs | 64 +++- .../admin_user_controller_test.exs | 35 +- .../provider_auth/api_key_controller_test.exs | 104 ++++++ .../category_controller_test.exs | 76 ++++- .../exchange_pair_controller_test.exs | 83 +++++ .../provider_auth/key_controller_test.exs | 107 ++++++- .../provider_auth/mint_controller_test.exs | 113 ++++++- .../provider_auth/role_controller_test.exs | 83 ++++- .../provider_auth/token_controller_test.exs | 299 +++++++++++++++++- ...ransaction_consumption_controller_test.exs | 171 ++++++++++ .../transaction_controller_test.exs | 164 ++++++++++ .../transaction_request_controller_test.exs | 56 ++++ .../user_auth_controller_test.exs | 58 ++++ .../provider_auth/user_controller_test.exs | 149 ++++++++- .../provider_auth/wallet_controller_test.exs | 35 ++ apps/ewallet/priv/repo/report_minimum.exs | 4 +- .../v1/controllers/auth_controller.ex | 5 +- .../v1/controllers/auth_controller_test.exs | 53 +++- .../v1/controllers/signup_controller_test.exs | 126 +++++++- ...ransaction_consumption_controller_test.exs | 164 +++++++++- .../transaction_controller_test.exs | 77 +++++ .../transaction_request_controller_test.exs | 46 +++ apps/ewallet_db/lib/ewallet_db/account.ex | 2 +- apps/ewallet_db/lib/ewallet_db/auth_token.ex | 6 +- apps/ewallet_db/lib/ewallet_db/token.ex | 2 +- apps/ewallet_db/lib/ewallet_db/transaction.ex | 6 +- .../lib/ewallet_db/transaction_request.ex | 2 +- apps/ewallet_db/lib/ewallet_db/types/map.ex | 5 + apps/ewallet_db/lib/ewallet_db/user.ex | 2 +- apps/ewallet_db/lib/ewallet_db/wallet.ex | 2 +- .../priv/repo/seeds/02_auth_token.exs | 4 +- .../priv/repo/seeds_sample/01_auth_token.exs | 4 +- .../test/ewallet_db/auth_token_test.exs | 32 +- 60 files changed, 4389 insertions(+), 176 deletions(-) create mode 100644 apps/activity_logger/test/support/activity_logger_test_helper.ex create mode 100644 apps/ewallet_db/lib/ewallet_db/types/map.ex diff --git a/apps/activity_logger/test/support/activity_logger_test_helper.ex b/apps/activity_logger/test/support/activity_logger_test_helper.ex new file mode 100644 index 000000000..c80379bca --- /dev/null +++ b/apps/activity_logger/test/support/activity_logger_test_helper.ex @@ -0,0 +1,91 @@ +defmodule ActivityLogger.ActivityLoggerTestHelper do + @moduledoc """ + Contains helper methods to make testing activity logging easier + """ + use ExUnit.CaseTemplate + import Ecto.Query + alias ActivityLogger.{ActivityLog, System, Repo} + alias EWalletConfig.{Setting, StoredSetting} + + def assert_activity_log( + log, + action: action, + originator: originator, + target: %Setting{} = target, + changes: changes, + encrypted_changes: encrypted_changes + ) do + assert_activity_log( + log, + action: action, + originator: originator, + target: %StoredSetting{uuid: target.uuid}, + changes: changes, + encrypted_changes: encrypted_changes + ) + end + + def assert_activity_log( + log, + action: action, + originator: :system, + target: target, + changes: changes, + encrypted_changes: encrypted_changes + ) do + assert_activity_log( + log, + action: action, + originator: %System{}, + target: target, + changes: changes, + encrypted_changes: encrypted_changes + ) + end + + def assert_activity_log( + log, + action: action, + originator: originator, + target: target, + changes: changes, + encrypted_changes: encrypted_changes + ) do + assert log.action == action + assert log.inserted_at != nil + assert log.originator_type == ActivityLog.get_type(originator.__struct__) + assert log.originator_uuid == originator.uuid + assert log.target_type == ActivityLog.get_type(target.__struct__) + assert log.target_uuid == target.uuid + assert log.target_changes == changes + assert log.target_encrypted_changes == encrypted_changes + end + + def assert_simple_activity_log( + log, + action: action, + originator_type: o_type, + target_type: t_type + ) do + assert log.action == action + assert log.inserted_at != nil + assert log.originator_type == o_type + assert log.target_type == t_type + end + + def get_all_activity_logs(schema) do + type = ActivityLog.get_type(schema.__struct__) + + ActivityLog + |> order_by(asc: :inserted_at) + |> where(target_type: ^type) + |> Repo.all() + end + + def get_all_activity_logs_since(since) do + ActivityLog + |> order_by(asc: :inserted_at) + |> where([a], a.inserted_at > ^since) + |> Repo.all() + end +end diff --git a/apps/admin_api/lib/admin_api/v1/controllers/admin_auth_controller.ex b/apps/admin_api/lib/admin_api/v1/controllers/admin_auth_controller.ex index df184111a..5de178f1f 100644 --- a/apps/admin_api/lib/admin_api/v1/controllers/admin_auth_controller.ex +++ b/apps/admin_api/lib/admin_api/v1/controllers/admin_auth_controller.ex @@ -16,7 +16,8 @@ defmodule AdminAPI.V1.AdminAuthController do conn <- AdminUserAuthenticator.authenticate(conn, attrs["email"], attrs["password"]), true <- conn.assigns.authenticated || {:error, :invalid_login_credentials}, true <- User.get_status(conn.assigns.admin_user) == :active || {:error, :invite_pending}, - {:ok, auth_token} = AuthToken.generate(conn.assigns.admin_user, :admin_api), + originator <- Originator.extract(conn.assigns), + {:ok, auth_token} = AuthToken.generate(conn.assigns.admin_user, :admin_api, originator), {:ok, auth_token} <- Orchestrator.one(auth_token, AuthTokenOverlay, attrs) do render_token(conn, auth_token) else diff --git a/apps/admin_api/lib/admin_api/v1/controllers/role_controller.ex b/apps/admin_api/lib/admin_api/v1/controllers/role_controller.ex index b45a84272..31778edf6 100644 --- a/apps/admin_api/lib/admin_api/v1/controllers/role_controller.ex +++ b/apps/admin_api/lib/admin_api/v1/controllers/role_controller.ex @@ -66,6 +66,7 @@ defmodule AdminAPI.V1.RoleController do def update(conn, %{"id" => id} = attrs) do with :ok <- permit(:update, conn.assigns, id), %Role{} = original <- Role.get(id) || {:error, :role_id_not_found}, + attrs <- Originator.set_in_attrs(attrs, conn.assigns), {:ok, updated} <- Role.update(original, attrs), {:ok, updated} <- Orchestrator.one(updated, RoleOverlay, attrs) do render(conn, :role, %{role: updated}) diff --git a/apps/admin_api/lib/admin_api/v1/controllers/user_auth_controller.ex b/apps/admin_api/lib/admin_api/v1/controllers/user_auth_controller.ex index 11b8b736d..a3d845840 100644 --- a/apps/admin_api/lib/admin_api/v1/controllers/user_auth_controller.ex +++ b/apps/admin_api/lib/admin_api/v1/controllers/user_auth_controller.ex @@ -11,7 +11,8 @@ defmodule AdminAPI.V1.UserAuthController do def login(conn, attrs) do with {:ok, %User{} = user} <- UserFetcher.fetch(attrs), true <- User.enabled?(user) || {:error, :user_disabled}, - {:ok, token} = AuthToken.generate(user, :ewallet_api), + originator <- Originator.extract(conn.assigns), + {:ok, token} = AuthToken.generate(user, :ewallet_api, originator), {:ok, token} = Orchestrator.one(token, AuthTokenOverlay, attrs) do render(conn, :auth_token, %{auth_token: token}) else diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/account_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/account_controller_test.exs index c58ed430d..ebc5c633b 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/account_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/account_controller_test.exs @@ -256,6 +256,7 @@ defmodule AdminAPI.V1.AdminAuth.AccountControllerTest do test "generates an activity log" do user = get_test_admin() parent = User.get_account(user) + timestamp = DateTime.utc_now() request_data = %{ parent_id: parent.id, @@ -269,24 +270,41 @@ defmodule AdminAPI.V1.AdminAuth.AccountControllerTest do assert response["success"] == true account = Account.get(response["data"]["id"]) - log = get_last_activity_log(account) - - assert log.action == "insert" - assert log.inserted_at != nil - assert log.originator_type == "user" - assert log.originator_uuid == user.uuid - assert log.target_type == "account" - assert log.target_uuid == account.uuid - - assert log.target_changes == %{ - "metadata" => %{"something" => "interesting"}, - "name" => "A test account", - "parent_uuid" => parent.uuid - } - - assert log.target_encrypted_changes == %{ - "encrypted_metadata" => %{"something" => "secret"} - } + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 3 + + logs + |> Enum.at(0) + |> assert_simple_activity_log( + action: "insert", + originator_type: "account", + target_type: "wallet" + ) + + logs + |> Enum.at(1) + |> assert_simple_activity_log( + action: "insert", + originator_type: "account", + target_type: "wallet" + ) + + logs + |> Enum.at(2) + |> assert_activity_log( + action: "insert", + originator: user, + target: account, + changes: %{ + "metadata" => %{"something" => "interesting"}, + "name" => "A test account", + "parent_uuid" => parent.uuid + }, + encrypted_changes: %{ + "encrypted_metadata" => %{"something" => "secret"} + } + ) end test "creates a new account with no parent_id" do @@ -343,6 +361,7 @@ defmodule AdminAPI.V1.AdminAuth.AccountControllerTest do test "generates an activity log" do user = get_test_admin() account = User.get_account(user) + timestamp = DateTime.utc_now() request_data = params_for(:account, %{ @@ -357,24 +376,24 @@ defmodule AdminAPI.V1.AdminAuth.AccountControllerTest do assert response["success"] == true account = Account.get(response["data"]["id"]) - log = get_last_activity_log(account) - - assert log.action == "update" - assert log.inserted_at != nil - assert log.originator_type == "user" - assert log.originator_uuid == user.uuid - assert log.target_type == "account" - assert log.target_uuid == account.uuid - - assert log.target_changes == %{ - "name" => "updated_name", - "parent_uuid" => account.parent_uuid, - "description" => "updated_description" - } - - assert log.target_encrypted_changes == %{ - "encrypted_metadata" => %{"something" => "secret"} - } + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: user, + target: account, + changes: %{ + "name" => "updated_name", + "parent_uuid" => account.parent_uuid, + "description" => "updated_description" + }, + encrypted_changes: %{ + "encrypted_metadata" => %{"something" => "secret"} + } + ) end test "updates the account's categories" do @@ -571,5 +590,41 @@ defmodule AdminAPI.V1.AdminAuth.AccountControllerTest do assert response["data"]["description"] == "You are not allowed to perform the requested operation." end + + test "generates an activity log" do + user = get_test_admin() + account = insert(:account) + timestamp = DateTime.utc_now() + + response = + admin_user_request("/account.upload_avatar", %{ + "id" => account.id, + "avatar" => %Plug.Upload{ + path: "test/support/assets/test.jpg", + filename: "test.jpg" + } + }) + + assert response["success"] == true + + account = Account.get(account.id) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: user, + target: account, + changes: %{ + "avatar" => %{ + "file_name" => "test.jpg", + "updated_at" => Ecto.DateTime.to_iso8601(account.avatar.updated_at) + } + }, + encrypted_changes: %{} + ) + end end end diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/account_membership_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/account_membership_controller_test.exs index 3486d9415..d883ace95 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/account_membership_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/account_membership_controller_test.exs @@ -2,7 +2,7 @@ defmodule AdminAPI.V1.AdminAuth.AccountMembershipControllerTest do use AdminAPI.ConnCase, async: true alias Ecto.UUID alias EWallet.Web.Date - alias EWalletDB.{Account, User} + alias EWalletDB.{Account, User, Membership} @redirect_url "http://localhost:4000/invite?email={email}&token={token}" @@ -532,6 +532,40 @@ defmodule AdminAPI.V1.AdminAuth.AccountMembershipControllerTest do assert response["data"]["description"] == "There is no role corresponding to the provided name." end + + test "generates an activity log" do + {:ok, user} = :user |> params_for() |> User.insert() + account = insert(:account) + role = insert(:role) + timestamp = DateTime.utc_now() + + response = + admin_user_request("/account.assign_user", %{ + user_id: user.id, + account_id: account.id, + role_name: role.name, + redirect_url: @redirect_url + }) + + assert response["success"] == true + membership = get_last_inserted(Membership) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: get_test_admin(), + target: membership, + changes: %{ + "account_uuid" => account.uuid, + "role_uuid" => role.uuid, + "user_uuid" => user.uuid + }, + encrypted_changes: %{} + ) + end end describe "/account.unassign_user" do @@ -599,5 +633,33 @@ defmodule AdminAPI.V1.AdminAuth.AccountMembershipControllerTest do assert response["data"]["description"] == "You are not allowed to perform the requested operation." end + + test "generates an activity log" do + account = insert(:account) + {:ok, user} = :user |> params_for() |> User.insert() + membership = insert(:membership, %{account: account, user: user}) + + timestamp = DateTime.utc_now() + + response = + admin_user_request("/account.unassign_user", %{ + user_id: user.id, + account_id: account.id + }) + + assert response["success"] == true + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "delete", + originator: get_test_admin(), + target: membership, + changes: %{}, + encrypted_changes: %{} + ) + end end end diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/admin_auth_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/admin_auth_controller_test.exs index 6a81ed0dc..42970540f 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/admin_auth_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/admin_auth_controller_test.exs @@ -177,6 +177,33 @@ defmodule AdminAPI.V1.AdminAuth.AdminAuthControllerTest do refute response["success"] assert response["data"]["code"] == "client:invalid_parameter" end + + test "generates an activity log" do + timestamp = DateTime.utc_now() + + response = + unauthenticated_request("/admin.login", %{email: @user_email, password: @password}) + + assert response["success"] == true + auth_token = AuthToken |> get_last_inserted() |> Repo.preload([:user, :account]) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: get_test_admin(), + target: auth_token, + changes: %{ + "account_uuid" => auth_token.account.uuid, + "owner_app" => "admin_api", + "token" => auth_token.token, + "user_uuid" => auth_token.user.uuid + }, + encrypted_changes: %{} + ) + end end describe "/auth_token.switch_account" do @@ -243,6 +270,33 @@ defmodule AdminAPI.V1.AdminAuth.AdminAuthControllerTest do refute response["success"] assert response["data"]["code"] == "auth_token:not_found" end + + test "generates an activity log" do + account = insert(:account, parent: Account.get_master_account()) + timestamp = DateTime.utc_now() + + response = + admin_user_request("/auth_token.switch_account", %{ + "account_id" => account.id + }) + + assert response["success"] == true + auth_token = AuthToken |> get_last_inserted() |> Repo.preload([:user, :account]) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_admin(), + target: auth_token, + changes: %{ + "account_uuid" => account.uuid + }, + encrypted_changes: %{} + ) + end end describe "/me.logout" do @@ -266,5 +320,28 @@ defmodule AdminAPI.V1.AdminAuth.AdminAuthControllerTest do refute response2["success"] assert response2["data"]["code"] == "user:auth_token_expired" end + + test "generates an activity log" do + timestamp = DateTime.utc_now() + + response = admin_user_request("/me.logout") + + assert response["success"] == true + auth_token = AuthToken |> get_last_inserted() |> Repo.preload([:user, :account]) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_admin(), + target: auth_token, + changes: %{ + "expired" => true + }, + encrypted_changes: %{} + ) + end end end diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/admin_user_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/admin_user_controller_test.exs index 7f68bf80f..055cd29b7 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/admin_user_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/admin_user_controller_test.exs @@ -178,7 +178,7 @@ defmodule AdminAPI.V1.AdminAuth.AdminUserControllerTest do admin = insert(:admin, %{email: "admin@omise.co"}) _membership = insert(:membership, %{user: admin, account: account, role: role}) - {:ok, token} = AuthToken.generate(admin, @owner_app) + {:ok, token} = AuthToken.generate(admin, @owner_app, %System{}) token_string = token.token # Ensure tokens is usable. assert AuthToken.authenticate(token_string, @owner_app) @@ -255,5 +255,37 @@ defmodule AdminAPI.V1.AdminAuth.AdminUserControllerTest do assert response["data"]["description"] == "You are not allowed to perform the requested operation." end + + test "generates an activity log" do + account = Account.get_master_account() + role = insert(:role, %{name: "some_role"}) + admin = insert(:admin, %{email: "admin@omise.co"}) + _membership = insert(:membership, %{user: admin, account: account, role: role}) + + timestamp = DateTime.utc_now() + + response = + admin_user_request("/admin.enable_or_disable", %{ + id: admin.id, + enabled: false + }) + + assert response["success"] == true + admin = User.get(admin.id) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_admin(), + target: admin, + changes: %{ + "enabled" => false + }, + encrypted_changes: %{} + ) + end end end diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/api_key_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/api_key_controller_test.exs index a79dfc98b..9bc9dd502 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/api_key_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/api_key_controller_test.exs @@ -120,6 +120,32 @@ defmodule AdminAPI.V1.AdminAuth.APIKeyControllerTest do } } end + + test "generates an activity log" do + timestamp = DateTime.utc_now() + response = admin_user_request("/api_key.create", %{}) + + assert response["success"] == true + api_key = get_last_inserted(APIKey) + account = Account.get_master_account() + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: get_test_admin(), + target: api_key, + changes: %{ + "account_uuid" => account.uuid, + "key" => response["data"]["key"], + "owner_app" => "ewallet_api" + }, + encrypted_changes: %{} + ) + end end describe "/api_key.update" do @@ -185,6 +211,34 @@ defmodule AdminAPI.V1.AdminAuth.APIKeyControllerTest do } } end + + test "generates an activity log" do + api_key = :api_key |> insert() |> Repo.preload(:account) + timestamp = DateTime.utc_now() + + response = + admin_user_request("/api_key.update", %{ + id: api_key.id, + expired: true + }) + + assert response["success"] == true + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_admin(), + target: api_key, + changes: %{ + "enabled" => false + }, + encrypted_changes: %{} + ) + end end describe "/api_key.enable_or_disable" do @@ -238,6 +292,34 @@ defmodule AdminAPI.V1.AdminAuth.APIKeyControllerTest do assert response["data"]["id"] == api_key.id assert response["data"]["enabled"] == true end + + test "generates an activity log" do + api_key = :api_key |> insert() |> Repo.preload(:account) + timestamp = DateTime.utc_now() + + response = + admin_user_request("/api_key.enable_or_disable", %{ + id: api_key.id, + enabled: false + }) + + assert response["success"] == true + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_admin(), + target: api_key, + changes: %{ + "enabled" => false + }, + encrypted_changes: %{} + ) + end end describe "/api_key.delete" do @@ -287,5 +369,27 @@ defmodule AdminAPI.V1.AdminAuth.APIKeyControllerTest do } } end + + test "generates an activity log" do + api_key = insert(:api_key) + timestamp = DateTime.utc_now() + response = admin_user_request("/api_key.delete", %{id: api_key.id}) + + assert response["success"] == true + + api_key = Repo.get_by(APIKey, %{id: api_key.id}) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_admin(), + target: api_key, + changes: %{"deleted_at" => NaiveDateTime.to_iso8601(api_key.deleted_at)}, + encrypted_changes: %{} + ) + end end end diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/category_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/category_controller_test.exs index 60ee4cc86..407f97347 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/category_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/category_controller_test.exs @@ -1,6 +1,6 @@ defmodule AdminAPI.V1.AdminAuth.CategoryControllerTest do use AdminAPI.ConnCase, async: true - alias EWalletDB.Category + alias EWalletDB.{Category, Repo} alias EWalletDB.Helpers.Preloader alias ActivityLogger.System @@ -102,6 +102,28 @@ defmodule AdminAPI.V1.AdminAuth.CategoryControllerTest do assert response["data"]["object"] == "error" assert response["data"]["code"] == "client:invalid_parameter" end + + test "generates an activity log" do + timestamp = DateTime.utc_now() + request_data = %{name: "A test category"} + response = admin_user_request("/category.create", request_data) + + assert response["success"] == true + + category = Category.get(response["data"]["id"]) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: get_test_admin(), + target: category, + changes: %{"name" => "A test category"}, + encrypted_changes: %{} + ) + end end describe "/category.update" do @@ -164,6 +186,35 @@ defmodule AdminAPI.V1.AdminAuth.CategoryControllerTest do assert response["data"]["description"] == "There is no category corresponding to the provided id." end + + test "generates an activity log" do + category = insert(:category) + timestamp = DateTime.utc_now() + + request_data = + params_for(:category, %{ + id: category.id, + name: "updated_name", + description: "updated_description" + }) + + response = admin_user_request("/category.update", request_data) + + assert response["success"] == true + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_admin(), + target: category, + changes: %{"name" => "updated_name", "description" => "updated_description"}, + encrypted_changes: %{} + ) + end end describe "/category.delete" do @@ -238,5 +289,28 @@ defmodule AdminAPI.V1.AdminAuth.CategoryControllerTest do } } end + + test "generates an activity log" do + category = insert(:category) + timestamp = DateTime.utc_now() + + response = admin_user_request("/category.delete", %{id: category.id}) + + assert response["success"] == true + + category = Repo.get_by(Category, %{id: category.id}) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_admin(), + target: category, + changes: %{"deleted_at" => NaiveDateTime.to_iso8601(category.deleted_at)}, + encrypted_changes: %{} + ) + end end end diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/configuration_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/configuration_controller_test.exs index 0178daba0..dd6b3099a 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/configuration_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/configuration_controller_test.exs @@ -130,5 +130,31 @@ defmodule AdminAPI.V1.AdminAuth.ConfigurationControllerTest do assert Application.get_env(:admin_api, :base_url, "new_base_url.example") end + + test "generates an activity log", meta do + timestamp = DateTime.utc_now() + + response = + admin_user_request("/configuration.update", %{ + base_url: "new_base_url.example", + config_pid: meta[:config_pid] + }) + + assert response["success"] == true + setting = Config.get_setting(:base_url) + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_admin(), + target: setting, + changes: %{"data" => %{"value" => "new_base_url.example"}, "position" => setting.position}, + encrypted_changes: %{} + ) + end end end diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/exchange_pair_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/exchange_pair_controller_test.exs index c6da35fe3..1cdcc0a6d 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/exchange_pair_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/exchange_pair_controller_test.exs @@ -207,6 +207,36 @@ defmodule AdminAPI.V1.AdminAuth.ExchangePairControllerTest do assert response["data"]["description"] == "Invalid parameter provided. `to_token_id` can't have the same value as `from_token_id`." end + + test "generates an activity log" do + timestamp = DateTime.utc_now() + request_data = insert_params() + response = admin_user_request("/exchange_pair.create", request_data) + + assert response["success"] == true + + exchange_pair = + ExchangePair + |> get_last_inserted() + |> Repo.preload([:from_token, :to_token]) + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: get_test_admin(), + target: exchange_pair, + changes: %{ + "from_token_uuid" => exchange_pair.from_token.uuid, + "rate" => exchange_pair.rate, + "to_token_uuid" => exchange_pair.to_token.uuid + }, + encrypted_changes: %{} + ) + end end describe "/exchange_pair.update" do @@ -341,6 +371,35 @@ defmodule AdminAPI.V1.AdminAuth.ExchangePairControllerTest do assert response["data"]["description"] == "Invalid parameter provided. `rate` must be greater than 0." end + + test "generates an activity log" do + exchange_pair = :exchange_pair |> insert() |> Repo.preload([:from_token, :to_token]) + timestamp = DateTime.utc_now() + + request_data = %{ + id: exchange_pair.id, + rate: 999.99 + } + + response = admin_user_request("/exchange_pair.update", request_data) + + assert response["success"] == true + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_admin(), + target: exchange_pair, + changes: %{ + "rate" => request_data.rate + }, + encrypted_changes: %{} + ) + end end describe "/exchange_pair.delete" do @@ -439,5 +498,29 @@ defmodule AdminAPI.V1.AdminAuth.ExchangePairControllerTest do } } end + + test "generates an activity log" do + timestamp = DateTime.utc_now() + exchange_pair = insert(:exchange_pair) + + response = admin_user_request("/exchange_pair.delete", %{id: exchange_pair.id}) + + assert response["success"] == true + exchange_pair = Repo.get_by(ExchangePair, %{id: exchange_pair.id}) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_admin(), + target: exchange_pair, + changes: %{ + "deleted_at" => NaiveDateTime.to_iso8601(exchange_pair.deleted_at) + }, + encrypted_changes: %{} + ) + end end end diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/invite_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/invite_controller_test.exs index 20d739e23..d5b0a1340 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/invite_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/invite_controller_test.exs @@ -1,7 +1,7 @@ defmodule AdminAPI.V1.AdminAuth.InviteControllerTest do use AdminAPI.ConnCase, async: true alias EWallet.Web.Date - alias EWalletDB.{Invite, User} + alias EWalletDB.{Invite, User, Repo} alias ActivityLogger.System defp request(email, token, password, password_confirmation) do @@ -50,70 +50,60 @@ defmodule AdminAPI.V1.AdminAuth.InviteControllerTest do test "generates activity logs" do {:ok, user} = :admin |> params_for(is_admin: false) |> User.insert() {:ok, invite} = Invite.generate(user, %System{}, preload: :user) + timestamp = DateTime.utc_now() response = request(invite.user.email, invite.token, "some_password", "some_password") - assert response["success"] == true - user = User.get(response["data"]["id"]) - logs = get_all_activity_logs(user) + invite = Repo.get_by(Invite, uuid: invite.uuid) + user = User.get(user.id) - # Set is admin - log = Enum.at(logs, 0) - assert log.action == "update" - assert log.inserted_at != nil - assert log.originator_type == "system" - assert log.originator_uuid == "00000000-0000-0000-0000-000000000000" - assert log.target_type == "user" - assert log.target_uuid == user.uuid - assert log.target_changes == %{"is_admin" => true} - assert log.target_encrypted_changes == %{} - - # update invite_uuid to nil - log = Enum.at(logs, 1) - assert log.action == "update" - assert log.inserted_at != nil - assert log.originator_type == "user" - assert log.originator_uuid == user.uuid - assert log.target_type == "user" - assert log.target_uuid == user.uuid - assert log.target_changes == %{"invite_uuid" => nil} - assert log.target_encrypted_changes == %{} + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 4 # Update user password - log = Enum.at(logs, 2) - assert log.action == "update" - assert log.inserted_at != nil - assert log.originator_type == "user" - assert log.originator_uuid == user.uuid - assert log.target_type == "user" - assert log.target_uuid == user.uuid - assert log.target_changes == %{"password_hash" => user.password_hash} - assert log.target_encrypted_changes == %{} - - # Invite update - log = Enum.at(logs, 3) - assert log.action == "update" - assert log.inserted_at != nil - assert log.originator_type == "invite" - assert log.originator_uuid == invite.uuid - assert log.target_type == "user" - assert log.target_uuid == user.uuid - assert log.target_changes == %{"invite_uuid" => invite.uuid} - assert log.target_encrypted_changes == %{} - - # Set password - log = Enum.at(logs, 4) - assert log.action == "insert" - assert log.inserted_at != nil - assert log.originator_type == "system" - assert log.originator_uuid == "00000000-0000-0000-0000-000000000000" - assert log.target_type == "user" - assert log.target_uuid == user.uuid - assert log.target_changes["email"] == user.email - assert log.target_changes["metadata"] == user.metadata - assert log.target_changes["password_hash"] != user.password_hash - assert log.target_encrypted_changes == %{} + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: user, + target: user, + changes: %{"password_hash" => user.password_hash}, + encrypted_changes: %{} + ) + + # Update invite_uuid to nil + logs + |> Enum.at(1) + |> assert_activity_log( + action: "update", + originator: user, + target: user, + changes: %{"invite_uuid" => nil}, + encrypted_changes: %{} + ) + + # Update verified_at for the invitation + logs + |> Enum.at(2) + |> assert_activity_log( + action: "update", + originator: user, + target: invite, + changes: %{"verified_at" => NaiveDateTime.to_iso8601(invite.verified_at)}, + encrypted_changes: %{} + ) + + # Set is admin + logs + |> Enum.at(3) + |> assert_activity_log( + action: "update", + originator: :system, + target: user, + changes: %{"is_admin" => true}, + encrypted_changes: %{} + ) Enum.each(logs, fn log -> assert log.target_changes["password"] == nil diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/key_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/key_controller_test.exs index 327777c81..7ddcc1f58 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/key_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/key_controller_test.exs @@ -5,7 +5,7 @@ defmodule AdminAPI.V1.AdminAuth.KeyControllerTest do describe "/access_key.all" do test "responds with a list of keys without secret keys" do - key_1 = Key |> Repo.get_by(access_key: @access_key) |> Repo.preload([:account]) + key_1 = Key.get_by(%{access_key: @access_key}, preload: :account) key_2 = insert(:key, %{secret_key: "the_secret_key"}) response = admin_user_request("/access_key.all") @@ -66,6 +66,32 @@ defmodule AdminAPI.V1.AdminAuth.KeyControllerTest do # so we can only check that it is a string with some length. assert String.length(response["data"]["secret_key"]) > 0 end + + test "generates an activity log" do + timestamp = DateTime.utc_now() + response = admin_user_request("/access_key.create") + + assert response["success"] == true + key = get_last_inserted(Key) + account = Account.get_master_account() + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: get_test_admin(), + target: key, + changes: %{ + "account_uuid" => account.uuid, + "access_key" => key.access_key, + "secret_key_hash" => key.secret_key_hash + }, + encrypted_changes: %{} + ) + end end describe "/access_key.update" do @@ -122,6 +148,34 @@ defmodule AdminAPI.V1.AdminAuth.KeyControllerTest do updated = Key.get(key.id) assert updated.secret_key_hash == key.secret_key_hash end + + test "generates an activity log" do + key = insert(:key) + timestamp = DateTime.utc_now() + + response = + admin_user_request("/access_key.update", %{ + id: key.id, + expired: true + }) + + assert response["success"] == true + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_admin(), + target: key, + changes: %{ + "enabled" => false + }, + encrypted_changes: %{} + ) + end end describe "/access_key.enable_or_disable" do @@ -175,6 +229,34 @@ defmodule AdminAPI.V1.AdminAuth.KeyControllerTest do assert response["data"]["id"] == key.id assert response["data"]["enabled"] == true end + + test "generates an activity log" do + key = insert(:key) + timestamp = DateTime.utc_now() + + response = + admin_user_request("/access_key.enable_or_disable", %{ + id: key.id, + enabled: false + }) + + assert response["success"] == true + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_admin(), + target: key, + changes: %{ + "enabled" => false + }, + encrypted_changes: %{} + ) + end end describe "/access_key.delete" do @@ -227,5 +309,28 @@ defmodule AdminAPI.V1.AdminAuth.KeyControllerTest do } } end + + test "generates an activity log" do + key = insert(:key) + + timestamp = DateTime.utc_now() + response = admin_user_request("/access_key.delete", %{id: key.id}) + + assert response["success"] == true + + key = Repo.get_by(Key, %{id: key.id}) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_admin(), + target: key, + changes: %{"deleted_at" => NaiveDateTime.to_iso8601(key.deleted_at)}, + encrypted_changes: %{} + ) + end end end diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/mint_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/mint_controller_test.exs index 173397569..7a1d83474 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/mint_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/mint_controller_test.exs @@ -3,7 +3,7 @@ defmodule AdminAPI.V1.AdminAuth.MintControllerTest do alias EWallet.MintGate alias EWallet.Web.Date alias EWallet.Web.V1.{AccountSerializer, TokenSerializer, TransactionSerializer} - alias EWalletDB.{Mint, Repo} + alias EWalletDB.{Mint, Account, Transaction, Wallet, Repo} alias ActivityLogger.System describe "/token.get_mints" do @@ -249,5 +249,116 @@ defmodule AdminAPI.V1.AdminAuth.MintControllerTest do assert response["data"]["messages"] == %{"amount" => ["number"]} end + + test "generates an activity log" do + token = insert(:token) + timestamp = DateTime.utc_now() + + response = + admin_user_request("/token.mint", %{ + id: token.id, + amount: 1_000_000 * token.subunit_to_unit + }) + + assert response["success"] == true + + mint = Mint.get(response["data"]["id"]) + account = Account.get_master_account() + wallet = Account.get_primary_wallet(account) + genesis = Wallet.get("gnis000000000000") + transaction = get_last_inserted(Transaction) + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 6 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: get_test_admin(), + target: mint, + changes: %{ + "account_uuid" => account.uuid, + "amount" => 100_000_000, + "token_uuid" => token.uuid + }, + encrypted_changes: %{} + ) + + logs + |> Enum.at(1) + |> assert_activity_log( + action: "insert", + originator: :system, + target: genesis, + changes: %{ + "address" => "gnis000000000000", + "identifier" => "genesis", + "name" => "genesis" + }, + encrypted_changes: %{} + ) + + logs + |> Enum.at(2) + |> assert_activity_log( + action: "insert", + originator: mint, + target: transaction, + changes: %{ + "from" => "gnis000000000000", + "from_amount" => 100_000_000, + "from_token_uuid" => token.uuid, + "idempotency_token" => transaction.idempotency_token, + "to" => wallet.address, + "to_account_uuid" => account.uuid, + "to_amount" => 100_000_000, + "to_token_uuid" => token.uuid + }, + encrypted_changes: %{ + "payload" => %{ + "amount" => 100_000_000, + "description" => nil, + "idempotency_token" => transaction.idempotency_token, + "token_id" => token.id + } + } + ) + + logs + |> Enum.at(3) + |> assert_activity_log( + action: "update", + originator: transaction, + target: mint, + changes: %{ + "transaction_uuid" => transaction.uuid + }, + encrypted_changes: %{} + ) + + logs + |> Enum.at(4) + |> assert_activity_log( + action: "update", + originator: :system, + target: transaction, + changes: %{ + "local_ledger_uuid" => transaction.local_ledger_uuid, + "status" => "confirmed" + }, + encrypted_changes: %{} + ) + + logs + |> Enum.at(5) + |> assert_activity_log( + action: "update", + originator: transaction, + target: mint, + changes: %{"confirmed" => true}, + encrypted_changes: %{} + ) + end end end diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/reset_password_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/reset_password_controller_test.exs index 36405721f..f180886af 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/reset_password_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/reset_password_controller_test.exs @@ -103,12 +103,48 @@ defmodule AdminAPI.V1.AdminAuth.ResetPasswordControllerTest do assert response["data"]["code"] == "client:invalid_parameter" assert request == nil end + + test "generates an activity log" do + {:ok, user} = :admin |> params_for() |> User.insert() + + timestamp = DateTime.utc_now() + + response = + unauthenticated_request("/admin.reset_password", %{ + "email" => user.email, + "redirect_url" => @redirect_url + }) + + assert response["success"] == true + + request = + ForgetPasswordRequest + |> Repo.get_by(user_uuid: user.uuid) + |> Repo.preload(:user) + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: :system, + target: request, + changes: %{ + "token" => request.token, + "user_uuid" => user.uuid, + "expires_at" => NaiveDateTime.to_iso8601(request.expires_at) + }, + encrypted_changes: %{} + ) + end end describe "ResetPasswordController.update/2" do test "returns success and updates the password if the password has been reset succesfully" do {:ok, user} = :admin |> params_for() |> User.insert() - request = ForgetPasswordRequest.generate(user) + {:ok, request} = ForgetPasswordRequest.generate(user) assert user.password_hash != Crypto.hash_password("password") @@ -202,5 +238,36 @@ defmodule AdminAPI.V1.AdminAuth.ResetPasswordControllerTest do assert response["data"]["code"] == "client:invalid_parameter" assert ForgetPasswordRequest |> Repo.all() |> length() == 1 end + + test "generates an activity log" do + {:ok, user} = :admin |> params_for() |> User.insert() + {:ok, request} = ForgetPasswordRequest.generate(user) + timestamp = DateTime.utc_now() + + response = + unauthenticated_request("/admin.update_password", %{ + email: user.email, + token: request.token, + password: "password", + password_confirmation: "password" + }) + + assert response["success"] == true + + user = User.get(user.id) + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: request, + target: user, + changes: %{"password_hash" => user.password_hash}, + encrypted_changes: %{} + ) + end end end diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/role_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/role_controller_test.exs index 382559f9c..1ae7c1c7f 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/role_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/role_controller_test.exs @@ -1,6 +1,6 @@ defmodule AdminAPI.V1.AdminAuth.RoleControllerTest do use AdminAPI.ConnCase, async: true - alias EWalletDB.{Membership, Role} + alias EWalletDB.{Membership, Role, Repo} alias ActivityLogger.System describe "/role.all" do @@ -101,6 +101,28 @@ defmodule AdminAPI.V1.AdminAuth.RoleControllerTest do assert response["data"]["object"] == "error" assert response["data"]["code"] == "client:invalid_parameter" end + + test "generates an activity log" do + timestamp = DateTime.utc_now() + request_data = %{name: "test_role"} + response = admin_user_request("/role.create", request_data) + + assert response["success"] == true + + role = Role.get(response["data"]["id"]) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: get_test_admin(), + target: role, + changes: %{"name" => "test_role", "priority" => role.priority}, + encrypted_changes: %{} + ) + end end describe "/role.update" do @@ -144,6 +166,40 @@ defmodule AdminAPI.V1.AdminAuth.RoleControllerTest do assert response["data"]["description"] == "There is no role corresponding to the provided id." end + + test "generates an activity log" do + timestamp = DateTime.utc_now() + role = insert(:role) + + request_data = + params_for(:role, %{ + id: role.id, + name: "updated_name", + display_name: "updated_display_name" + }) + + response = admin_user_request("/role.update", request_data) + + assert response["success"] == true + + role = Role.get(response["data"]["id"]) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_admin(), + target: role, + changes: %{ + "priority" => role.priority, + "name" => "updated_name", + "display_name" => "updated_display_name" + }, + encrypted_changes: %{} + ) + end end describe "/role.delete" do @@ -216,5 +272,30 @@ defmodule AdminAPI.V1.AdminAuth.RoleControllerTest do } } end + + test "generates an activity log" do + role = insert(:role) + + timestamp = DateTime.utc_now() + response = admin_user_request("/role.delete", %{id: role.id}) + + assert response["success"] == true + + role = Repo.get_by(Role, %{id: role.id}) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_admin(), + target: role, + changes: %{ + "deleted_at" => NaiveDateTime.to_iso8601(role.deleted_at) + }, + encrypted_changes: %{} + ) + end end end diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/self_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/self_controller_test.exs index b48d5e3cb..469e2e259 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/self_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/self_controller_test.exs @@ -62,6 +62,36 @@ defmodule AdminAPI.V1.AdminAuth.SelfControllerTest do assert response["data"]["description"] == "Invalid parameter provided. `metadata` is invalid." end + + test "generates an activity log" do + timestamp = DateTime.utc_now() + + response = + admin_user_request("/me.update", %{ + metadata: %{"key" => "value_1337"}, + encrypted_metadata: %{"key" => "value_1337"} + }) + + assert response["success"] == true + + admin = get_test_admin() + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: admin, + target: admin, + changes: %{ + "metadata" => %{"key" => "value_1337"} + }, + encrypted_changes: %{ + "encrypted_metadata" => %{"key" => "value_1337"} + } + ) + end end describe "/me.update_password" do @@ -124,6 +154,35 @@ defmodule AdminAPI.V1.AdminAuth.SelfControllerTest do assert response["data"]["description"] == "Invalid parameter provided. `password` must be 8 characters or more." end + + test "generates an activity log" do + timestamp = DateTime.utc_now() + + response = + admin_user_request("/me.update_password", %{ + old_password: @password, + password: "the_new_password", + password_confirmation: "the_new_password" + }) + + assert response["success"] == true + + admin = get_test_admin() + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: admin, + target: admin, + changes: %{ + "password_hash" => admin.password_hash + }, + encrypted_changes: %{} + ) + end end describe "/me.update_email" do @@ -189,6 +248,37 @@ defmodule AdminAPI.V1.AdminAuth.SelfControllerTest do assert response["success"] == false assert response["data"]["code"] == "client:invalid_parameter" end + + test "generates an activity log" do + timestamp = DateTime.utc_now() + + response = + admin_user_request("/me.update_email", %{ + "email" => "test.sends.verification.email@example.com", + "redirect_url" => @update_email_url + }) + + assert response["success"] == true + + admin = get_test_admin() + request = Repo.get_by(UpdateEmailRequest, user_uuid: admin.uuid) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: admin, + target: request, + changes: %{ + "email" => "test.sends.verification.email@example.com", + "token" => request.token, + "user_uuid" => admin.uuid + }, + encrypted_changes: %{} + ) + end end describe "/admin.verify_email_update" do @@ -299,6 +389,36 @@ defmodule AdminAPI.V1.AdminAuth.SelfControllerTest do assert response["data"]["code"] == "client:invalid_parameter" assert response["data"]["description"] == "Invalid parameter provided." end + + test "generates an activity log" do + admin = get_test_admin() + new_email = "test_email_update@example.com" + request = UpdateEmailRequest.generate(admin, new_email) + + timestamp = DateTime.utc_now() + + response = + unauthenticated_request("/admin.verify_email_update", %{ + email: new_email, + token: request.token + }) + + assert response["success"] == true + + request = Repo.get_by(UpdateEmailRequest, user_uuid: admin.uuid) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: request, + target: admin, + changes: %{"email" => "test_email_update@example.com"}, + encrypted_changes: %{} + ) + end end describe "/me.upload_avatar" do @@ -436,6 +556,39 @@ defmodule AdminAPI.V1.AdminAuth.SelfControllerTest do admin = User.get(admin.id) assert admin.avatar == nil end + + test "generates an activity log" do + timestamp = DateTime.utc_now() + + response = + admin_user_request("/me.upload_avatar", %{ + "avatar" => %Plug.Upload{ + path: "test/support/assets/test.jpg", + filename: "test.jpg" + } + }) + + assert response["success"] == true + + admin = get_test_admin() + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: admin, + target: admin, + changes: %{ + "avatar" => %{ + "file_name" => "test.jpg", + "updated_at" => Ecto.DateTime.to_iso8601(admin.avatar.updated_at) + } + }, + encrypted_changes: %{} + ) + end end describe "/me.get_account" do diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/token_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/token_controller_test.exs index 008ab1bcd..6f1e4f4d7 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/token_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/token_controller_test.exs @@ -1,7 +1,7 @@ defmodule AdminAPI.V1.AdminAuth.TokenControllerTest do use AdminAPI.ConnCase, async: true alias EWallet.Web.V1.TokenSerializer - alias EWalletDB.{Mint, Repo, Token} + alias EWalletDB.{Mint, Repo, Token, Account, Wallet, Transaction} alias ActivityLogger.System describe "/token.all" do @@ -264,6 +264,178 @@ defmodule AdminAPI.V1.AdminAuth.TokenControllerTest do inserted = Token |> Repo.all() |> Enum.at(0) assert inserted == nil end + + test "generates an activity log" do + timestamp = DateTime.utc_now() + + response = + admin_user_request("/token.create", %{ + symbol: "BTC", + name: "Bitcoin", + description: "desc", + subunit_to_unit: 100, + metadata: %{something: "interesting"}, + encrypted_metadata: %{something: "secret"} + }) + + assert response["success"] == true + + token = Token.get(response["data"]["id"]) + account = Account.get_master_account() + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: get_test_admin(), + target: token, + changes: %{ + "name" => "Bitcoin", + "account_uuid" => account.uuid, + "description" => "desc", + "id" => token.id, + "metadata" => %{"something" => "interesting"}, + "subunit_to_unit" => 100, + "symbol" => "BTC" + }, + encrypted_changes: %{"encrypted_metadata" => %{"something" => "secret"}} + ) + end + + test "generates an activity log when minting" do + timestamp = DateTime.utc_now() + + response = + admin_user_request("/token.create", %{ + symbol: "BTC", + name: "Bitcoin", + description: "desc", + subunit_to_unit: 100, + amount: 100, + metadata: %{something: "interesting"}, + encrypted_metadata: %{something: "secret"} + }) + + assert response["success"] == true + + token = Token.get(response["data"]["id"]) + account = Account.get_master_account() + wallet = Account.get_primary_wallet(account) + genesis = Wallet.get("gnis000000000000") + mint = Mint |> Repo.all() |> Enum.at(0) + transaction = get_last_inserted(Transaction) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 7 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: get_test_admin(), + target: token, + changes: %{ + "name" => "Bitcoin", + "account_uuid" => account.uuid, + "description" => "desc", + "id" => token.id, + "metadata" => %{"something" => "interesting"}, + "subunit_to_unit" => 100, + "symbol" => "BTC" + }, + encrypted_changes: %{"encrypted_metadata" => %{"something" => "secret"}} + ) + + logs + |> Enum.at(1) + |> assert_activity_log( + action: "insert", + originator: get_test_admin(), + target: mint, + changes: %{ + "account_uuid" => account.uuid, + "amount" => 100, + "token_uuid" => token.uuid + }, + encrypted_changes: %{} + ) + + logs + |> Enum.at(2) + |> assert_activity_log( + action: "insert", + originator: :system, + target: genesis, + changes: %{ + "address" => "gnis000000000000", + "identifier" => "genesis", + "name" => "genesis" + }, + encrypted_changes: %{} + ) + + logs + |> Enum.at(3) + |> assert_activity_log( + action: "insert", + originator: mint, + target: transaction, + changes: %{ + "from" => "gnis000000000000", + "from_amount" => 100, + "from_token_uuid" => token.uuid, + "idempotency_token" => transaction.idempotency_token, + "to" => wallet.address, + "to_account_uuid" => account.uuid, + "to_amount" => 100, + "to_token_uuid" => token.uuid + }, + encrypted_changes: %{ + "payload" => %{ + "amount" => 100, + "description" => nil, + "idempotency_token" => transaction.idempotency_token, + "token_id" => token.id + } + } + ) + + logs + |> Enum.at(4) + |> assert_activity_log( + action: "update", + originator: transaction, + target: mint, + changes: %{ + "transaction_uuid" => transaction.uuid + }, + encrypted_changes: %{} + ) + + logs + |> Enum.at(5) + |> assert_activity_log( + action: "update", + originator: :system, + target: transaction, + changes: %{ + "local_ledger_uuid" => transaction.local_ledger_uuid, + "status" => "confirmed" + }, + encrypted_changes: %{} + ) + + logs + |> Enum.at(6) + |> assert_activity_log( + action: "update", + originator: transaction, + target: mint, + changes: %{"confirmed" => true}, + encrypted_changes: %{} + ) + end end describe "/token.update" do @@ -327,6 +499,41 @@ defmodule AdminAPI.V1.AdminAuth.TokenControllerTest do "messages" => nil } end + + test "generates an activity log" do + token = insert(:token) + + timestamp = DateTime.utc_now() + + response = + admin_user_request("/token.update", %{ + id: token.id, + name: "updated name", + description: "updated description", + metadata: %{something: "interesting"}, + encrypted_metadata: %{something: "secret"} + }) + + assert response["success"] == true + + token = Token.get(token.id) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_admin(), + target: token, + changes: %{ + "metadata" => %{"something" => "interesting"}, + "description" => "updated description", + "name" => "updated name" + }, + encrypted_changes: %{"encrypted_metadata" => %{"something" => "secret"}} + ) + end end describe "/token.enable_or_disable" do @@ -386,5 +593,35 @@ defmodule AdminAPI.V1.AdminAuth.TokenControllerTest do "messages" => nil } end + + test "generates an activity log" do + token = insert(:token) + + timestamp = DateTime.utc_now() + + response = + admin_user_request("/token.enable_or_disable", %{ + id: token.id, + enabled: false + }) + + assert response["success"] == true + + token = Token.get(token.id) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_admin(), + target: token, + changes: %{ + "enabled" => false + }, + encrypted_changes: %{} + ) + end end end diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/transaction_consumption_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/transaction_consumption_controller_test.exs index 2e5121a7f..85a6a1481 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/transaction_consumption_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/transaction_consumption_controller_test.exs @@ -8,6 +8,7 @@ defmodule AdminAPI.V1.AdminAuth.TransactionConsumptionControllerTest do Token, Transaction, TransactionConsumption, + TransactionRequest, User, Wallet } @@ -2191,5 +2192,175 @@ defmodule AdminAPI.V1.AdminAuth.TransactionConsumptionControllerTest do Endpoint.unsubscribe("transaction_request:#{transaction_request.id}") Endpoint.unsubscribe("transaction_consumption:#{consumption_id}") end + + test "generates an activity log", meta do + transaction_request = + insert( + :transaction_request, + type: "receive", + token_uuid: meta.token.uuid, + user_uuid: meta.alice.uuid, + wallet: meta.alice_wallet, + amount: 100_000 * meta.token.subunit_to_unit + ) + + set_initial_balance(%{ + address: meta.bob_wallet.address, + token: meta.token, + amount: 150_000 + }) + + timestamp = DateTime.utc_now() + + response = + admin_user_request("/transaction_request.consume", %{ + idempotency_token: "123", + formatted_transaction_request_id: transaction_request.id, + correlation_id: nil, + amount: nil, + address: nil, + metadata: nil, + token_id: nil, + account_id: meta.account.id + }) + + assert response["success"] == true + + transaction_request = TransactionRequest.get(transaction_request.id) + transaction_consumption = TransactionConsumption.get(response["data"]["id"]) + transaction = get_last_inserted(Transaction) + alice_account_user = get_last_inserted(AccountUser) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 8 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: get_test_admin(), + target: transaction_consumption, + changes: %{ + "account_uuid" => meta.account.uuid, + "estimated_at" => NaiveDateTime.to_iso8601(transaction_consumption.estimated_at), + "estimated_consumption_amount" => 10_000_000, + "estimated_rate" => 1.0, + "estimated_request_amount" => 10_000_000, + "idempotency_token" => "123", + "token_uuid" => meta.token.uuid, + "transaction_request_uuid" => transaction_request.uuid, + "wallet_address" => meta.account_wallet.address + }, + encrypted_changes: %{} + ) + + logs + |> Enum.at(1) + |> assert_activity_log( + action: "update", + originator: :system, + target: transaction_consumption, + changes: %{ + "approved_at" => NaiveDateTime.to_iso8601(transaction_consumption.approved_at), + "status" => "approved" + }, + encrypted_changes: %{} + ) + + logs + |> Enum.at(2) + |> assert_activity_log( + action: "insert", + originator: transaction_consumption, + target: transaction, + changes: %{ + "calculated_at" => NaiveDateTime.to_iso8601(transaction.calculated_at), + "from" => meta.account_wallet.address, + "from_account_uuid" => meta.account.uuid, + "from_amount" => 10_000_000, + "from_token_uuid" => meta.token.uuid, + "idempotency_token" => "123", + "rate" => 1.0, + "to" => meta.alice_wallet.address, + "to_amount" => 10_000_000, + "to_token_uuid" => meta.token.uuid, + "to_user_uuid" => meta.alice.uuid + }, + encrypted_changes: %{ + "payload" => %{ + "encrypted_metadata" => %{}, + "exchange_account_id" => nil, + "exchange_wallet_address" => nil, + "from_address" => meta.account_wallet.address, + "from_amount" => 10_000_000, + "from_token_id" => meta.token.id, + "idempotency_token" => "123", + "metadata" => %{}, + "to_address" => meta.alice_wallet.address, + "to_amount" => 10_000_000, + "to_token_id" => meta.token.id + } + } + ) + + logs + |> Enum.at(3) + |> assert_activity_log( + action: "insert", + originator: transaction, + target: alice_account_user, + changes: %{ + "account_uuid" => alice_account_user.account_uuid, + "user_uuid" => meta.alice.uuid + }, + encrypted_changes: %{} + ) + + logs + |> Enum.at(4) + |> assert_activity_log( + action: "update", + originator: :system, + target: transaction, + changes: %{ + "local_ledger_uuid" => transaction.local_ledger_uuid, + "status" => "confirmed" + }, + encrypted_changes: %{} + ) + + logs + |> Enum.at(5) + |> assert_activity_log( + action: "update", + originator: transaction, + target: transaction_consumption, + changes: %{ + "confirmed_at" => NaiveDateTime.to_iso8601(transaction_consumption.confirmed_at), + "status" => "confirmed", + "transaction_uuid" => transaction.uuid + }, + encrypted_changes: %{} + ) + + logs + |> Enum.at(6) + |> assert_activity_log( + action: "update", + originator: :system, + target: transaction_request, + changes: %{"consumptions_count" => 1}, + encrypted_changes: %{} + ) + + logs + |> Enum.at(7) + |> assert_activity_log( + action: "update", + originator: :system, + target: transaction_request, + changes: %{"updated_at" => NaiveDateTime.to_iso8601(transaction_request.updated_at)}, + encrypted_changes: %{} + ) + end end end diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/transaction_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/transaction_controller_test.exs index 978005399..79870ae05 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/transaction_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/transaction_controller_test.exs @@ -819,6 +819,78 @@ defmodule AdminAPI.V1.AdminAuth.TransactionControllerTest do assert response["data"]["object"] == "error" assert response["data"]["code"] == "token:disabled" end + + test "generates an activity log" do + token = insert(:token) + mint!(token) + + wallet_1 = insert(:wallet) + wallet_2 = insert(:wallet) + + set_initial_balance(%{ + address: wallet_1.address, + token: token, + amount: 2_000_000 + }) + + timestamp = DateTime.utc_now() + + response = + admin_user_request("/transaction.create", %{ + "idempotency_token" => "123", + "from_address" => wallet_1.address, + "to_address" => wallet_2.address, + "token_id" => token.id, + "amount" => 1_000_000 + }) + + assert response["success"] == true + + transaction = Transaction.get(response["data"]["id"]) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 2 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: get_test_admin(), + target: transaction, + changes: %{ + "from" => wallet_1.address, + "from_amount" => 1_000_000, + "from_token_uuid" => token.uuid, + "from_user_uuid" => wallet_1.user.uuid, + "idempotency_token" => "123", + "to" => wallet_2.address, + "to_amount" => 1_000_000, + "to_token_uuid" => token.uuid, + "to_user_uuid" => wallet_2.user.uuid + }, + encrypted_changes: %{ + "payload" => %{ + "amount" => 1_000_000, + "from_address" => wallet_1.address, + "idempotency_token" => "123", + "to_address" => wallet_2.address, + "token_id" => token.id + } + } + ) + + logs + |> Enum.at(1) + |> assert_activity_log( + action: "update", + originator: :system, + target: transaction, + changes: %{ + "local_ledger_uuid" => transaction.local_ledger_uuid, + "status" => "confirmed" + }, + encrypted_changes: %{} + ) + end end describe "/transaction.create for cross-token transactions" do @@ -1273,5 +1345,97 @@ defmodule AdminAPI.V1.AdminAuth.TransactionControllerTest do "object" => "error" } end + + test "generates an activity log" do + account = Account.get_master_account() + account_wallet = Account.get_primary_wallet(account) + {:ok, user_1} = :user |> params_for() |> User.insert() + {:ok, user_2} = :user |> params_for() |> User.insert() + wallet_1 = User.get_primary_wallet(user_1) + wallet_2 = User.get_primary_wallet(user_2) + + token_1 = insert(:token) + token_2 = insert(:token) + + mint!(token_1) + mint!(token_2) + + pair = insert(:exchange_pair, from_token: token_1, to_token: token_2, rate: 2) + + set_initial_balance(%{ + address: wallet_1.address, + token: token_1, + amount: 2_000_000 + }) + + timestamp = DateTime.utc_now() + + response = + admin_user_request("/transaction.create", %{ + "idempotency_token" => "12344", + "exchange_account_id" => account.id, + "from_amount" => 1_000, + "from_token_id" => token_1.id, + "from_address" => wallet_1.address, + "to_amount" => 1_000 * pair.rate, + "to_token_id" => token_2.id, + "to_address" => wallet_2.address + }) + + assert response["success"] == true + + transaction = Transaction.get(response["data"]["id"]) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 2 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: get_test_admin(), + target: transaction, + changes: %{ + "from" => wallet_1.address, + "from_token_uuid" => token_1.uuid, + "from_user_uuid" => user_1.uuid, + "to" => wallet_2.address, + "to_token_uuid" => token_2.uuid, + "to_user_uuid" => user_2.uuid, + "from_amount" => 1000, + "idempotency_token" => "12344", + "to_amount" => 2000, + "calculated_at" => NaiveDateTime.to_iso8601(transaction.calculated_at), + "exchange_account_uuid" => account.uuid, + "exchange_pair_uuid" => pair.uuid, + "exchange_wallet_address" => account_wallet.address, + "rate" => 2.0 + }, + encrypted_changes: %{ + "payload" => %{ + "from_address" => wallet_1.address, + "to_address" => wallet_2.address, + "idempotency_token" => "12344", + "exchange_account_id" => account.id, + "from_amount" => 1000, + "from_token_id" => token_1.id, + "to_amount" => 2000, + "to_token_id" => token_2.id + } + } + ) + + logs + |> Enum.at(1) + |> assert_activity_log( + action: "update", + originator: :system, + target: transaction, + changes: %{ + "local_ledger_uuid" => transaction.local_ledger_uuid, + "status" => "confirmed" + }, + encrypted_changes: %{} + ) + end end end diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/transaction_request_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/transaction_request_controller_test.exs index 4bdce497c..be0bbe550 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/transaction_request_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/transaction_request_controller_test.exs @@ -513,6 +513,62 @@ defmodule AdminAPI.V1.AdminAuth.TransactionRequestControllerTest do assert response["success"] == false assert response["data"]["code"] == "wallet:disabled" end + + test "generates an activity log" do + account = Account.get_master_account() + user = get_test_user() + token = insert(:token) + account_wallet = Account.get_primary_wallet(account) + wallet = User.get_primary_wallet(user) + {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) + + timestamp = DateTime.utc_now() + + response = + admin_user_request("/transaction_request.create", %{ + type: "send", + token_id: token.id, + correlation_id: "123", + amount: 1_000, + address: wallet.address, + exchange_wallet_address: account_wallet.address + }) + + assert response["success"] == true + + transaction_request = TransactionRequest.get(response["data"]["id"]) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 2 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: get_test_admin(), + target: transaction_request, + changes: %{ + "amount" => 1000, + "correlation_id" => "123", + "exchange_account_uuid" => account.uuid, + "exchange_wallet_address" => account_wallet.address, + "token_uuid" => token.uuid, + "type" => "send", + "user_uuid" => user.uuid, + "wallet_address" => wallet.address + }, + encrypted_changes: %{} + ) + + logs + |> Enum.at(1) + |> assert_activity_log( + action: "update", + originator: :system, + target: transaction_request, + changes: %{"consumptions_count" => 0}, + encrypted_changes: %{} + ) + end end describe "/transaction_request.get" do diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/update_email_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/update_email_controller_test.exs index c16dc8435..8325f5b67 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/update_email_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/update_email_controller_test.exs @@ -98,6 +98,38 @@ defmodule AdminAPI.V1.AdminAuth.UpdateEmailControllerTest do assert response["success"] == false assert response["data"]["code"] == "user:email_already_exists" end + + test "generates activity logs" do + admin = get_test_admin() + timestamp = DateTime.utc_now() + + response = + admin_user_request("/me.update_email", %{ + "email" => "test_email_update@example.com", + "redirect_url" => @redirect_url + }) + + assert response["success"] == true + + request = Repo.get_by(UpdateEmailRequest, user_uuid: admin.uuid) + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: admin, + target: request, + changes: %{ + "email" => "test_email_update@example.com", + "token" => request.token, + "user_uuid" => admin.uuid + }, + encrypted_changes: %{} + ) + end end describe "/me.update_email_verification" do @@ -208,5 +240,36 @@ defmodule AdminAPI.V1.AdminAuth.UpdateEmailControllerTest do assert response["data"]["code"] == "client:invalid_parameter" assert response["data"]["description"] == "Invalid parameter provided." end + + test "generates activity logs" do + admin = get_test_admin() + new_email = "test_email_update@example.com" + request = UpdateEmailRequest.generate(admin, new_email) + + timestamp = DateTime.utc_now() + + response = + unauthenticated_request("/admin.verify_email_update", %{ + email: new_email, + token: request.token + }) + + assert response["success"] == true + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: request, + target: admin, + changes: %{ + "email" => "test_email_update@example.com" + }, + encrypted_changes: %{} + ) + end end end diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/user_auth_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/user_auth_controller_test.exs index 34e745c63..2c3df41aa 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/user_auth_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/user_auth_controller_test.exs @@ -97,6 +97,33 @@ defmodule AdminAPI.V1.AdminAuth.UserAuthControllerTest do assert response == expected end + + test "generates activity logs" do + {:ok, user} = :user |> params_for() |> User.insert() + timestamp = DateTime.utc_now() + + response = admin_user_request("/user.login", %{id: user.id}) + + assert response["success"] == true + + auth_token = get_last_inserted(AuthToken) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: get_test_admin(), + target: auth_token, + changes: %{ + "owner_app" => "ewallet_api", + "token" => auth_token.token, + "user_uuid" => user.uuid + }, + encrypted_changes: %{} + ) + end end describe "/user.logout" do @@ -114,5 +141,36 @@ defmodule AdminAPI.V1.AdminAuth.UserAuthControllerTest do assert response["success"] == true assert response["data"] == %{} end + + test "generates activity logs" do + _user = insert(:user, %{provider_user_id: "1234"}) + admin_user_request("/user.login", %{provider_user_id: "1234"}) + auth_token = get_last_inserted(AuthToken) + + timestamp = DateTime.utc_now() + + response = + admin_user_request("/user.logout", %{ + "auth_token" => auth_token.token + }) + + assert response["success"] == true + + auth_token = get_last_inserted(AuthToken) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_admin(), + target: auth_token, + changes: %{ + "expired" => true + }, + encrypted_changes: %{} + ) + end end end diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/user_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/user_controller_test.exs index c3fd7e803..3b15c615d 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/user_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/user_controller_test.exs @@ -331,8 +331,6 @@ defmodule AdminAPI.V1.AdminAuth.UserControllerTest do end test "generates an activity log" do - admin = get_test_admin() - request_data = params_for( :user, @@ -340,31 +338,63 @@ defmodule AdminAPI.V1.AdminAuth.UserControllerTest do encrypted_metadata: %{something: "secret"} ) + timestamp = DateTime.utc_now() + response = admin_user_request("/user.create", request_data) assert response["success"] == true user = User.get(response["data"]["id"]) - log = get_last_activity_log(user) - - assert log.action == "insert" - assert log.inserted_at != nil - assert log.originator_type == "user" - assert log.originator_uuid == admin.uuid - assert log.target_type == "user" - assert log.target_uuid == user.uuid - - assert log.target_changes == %{ - "metadata" => %{"something" => "interesting"}, - "calling_name" => user.calling_name, - "full_name" => user.full_name, - "provider_user_id" => user.provider_user_id, - "username" => user.username - } - - assert log.target_encrypted_changes == %{ - "encrypted_metadata" => %{"something" => "secret"} - } + wallet = User.get_primary_wallet(user) + account_user = get_last_inserted(AccountUser) + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 3 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: user, + target: wallet, + changes: %{ + "identifier" => "primary", + "name" => "primary", + "user_uuid" => user.uuid + }, + encrypted_changes: %{} + ) + + logs + |> Enum.at(1) + |> assert_activity_log( + action: "insert", + originator: get_test_admin(), + target: user, + changes: %{ + "metadata" => %{"something" => "interesting"}, + "calling_name" => user.calling_name, + "full_name" => user.full_name, + "provider_user_id" => user.provider_user_id, + "username" => user.username + }, + encrypted_changes: %{ + "encrypted_metadata" => %{"something" => "secret"} + } + ) + + logs + |> Enum.at(2) + |> assert_activity_log( + action: "insert", + originator: get_test_admin(), + target: account_user, + changes: %{ + "account_uuid" => account_user.account_uuid, + "user_uuid" => user.uuid + }, + encrypted_changes: %{} + ) end test "returns an error if provider_user_id is not provided" do @@ -560,6 +590,53 @@ defmodule AdminAPI.V1.AdminAuth.UserControllerTest do assert response["data"]["code"] == "client:invalid_parameter" assert response["data"]["description"] == "Invalid parameter provided." end + + test "generates an activity log" do + {:ok, user} = :user |> params_for() |> User.insert() + + # Prepare the update data while keeping only provider_user_id the same + request_data = + params_for(:user, %{ + provider_user_id: user.provider_user_id, + username: "updated_username", + metadata: %{ + first_name: "updated_first_name", + last_name: "updated_last_name" + } + }) + + account = Account.get_master_account() + {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) + + timestamp = DateTime.utc_now() + + response = admin_user_request("/user.update", request_data) + + assert response["success"] == true + + user = User.get(response["data"]["id"]) + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_admin(), + target: user, + changes: %{ + "metadata" => %{ + "first_name" => "updated_first_name", + "last_name" => "updated_last_name" + }, + "username" => "updated_username", + "calling_name" => user.calling_name, + "full_name" => user.full_name + }, + encrypted_changes: %{} + ) + end end describe "/user.enable_or_disable" do @@ -568,7 +645,7 @@ defmodule AdminAPI.V1.AdminAuth.UserControllerTest do account = Account.get_master_account() {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) - {:ok, token} = AuthToken.generate(user, @owner_app) + {:ok, token} = AuthToken.generate(user, @owner_app, %System{}) token_string = token.token # Ensure tokens is usable. assert AuthToken.authenticate(token_string, @owner_app) @@ -589,7 +666,7 @@ defmodule AdminAPI.V1.AdminAuth.UserControllerTest do account = Account.get_master_account() {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) - {:ok, token} = AuthToken.generate(user, @owner_app) + {:ok, token} = AuthToken.generate(user, @owner_app, %System{}) token_string = token.token # Ensure tokens is usable. assert AuthToken.authenticate(token_string, @owner_app) @@ -654,5 +731,36 @@ defmodule AdminAPI.V1.AdminAuth.UserControllerTest do assert response["data"]["description"] == "Invalid parameter provided. `id` or `provider_user_id` is required." end + + test "generates an activity log" do + user = insert(:user, %{enabled: true}) + account = Account.get_master_account() + {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) + + timestamp = DateTime.utc_now() + + response = + admin_user_request("/user.enable_or_disable", %{ + id: user.id, + enabled: false + }) + + assert response["success"] == true + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_admin(), + target: user, + changes: %{ + "enabled" => false + }, + encrypted_changes: %{} + ) + end end end diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/wallet_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/wallet_controller_test.exs index 73dc5761e..8de0611c1 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/wallet_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/wallet_controller_test.exs @@ -561,6 +561,41 @@ defmodule AdminAPI.V1.AdminAuth.WalletControllerTest do length = Wallet |> Repo.all() |> length() assert length == 3 end + + test "generates an activity log" do + account = insert(:account) + assert Wallet |> Repo.all() |> length() == 3 + + timestamp = DateTime.utc_now() + + response = + admin_user_request("/wallet.create", %{ + name: "MyWallet", + identifier: "secondary", + account_id: account.id + }) + + assert response["success"] == true + + wallet = Wallet.get(response["data"]["address"]) + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: get_test_admin(), + target: wallet, + changes: %{ + "name" => "MyWallet", + "identifier" => wallet.identifier, + "account_uuid" => account.uuid + }, + encrypted_changes: %{} + ) + end end describe "/wallet.enable_or_disable" do @@ -621,5 +656,42 @@ defmodule AdminAPI.V1.AdminAuth.WalletControllerTest do assert response["success"] == false assert response["data"]["code"] == "wallet:primary_cannot_be_disabled" end + + test "generates an activity log" do + account = Account.get_master_account() + + {:ok, wallet} = + Wallet.insert_secondary_or_burn(%{ + "account_uuid" => account.uuid, + "name" => "MySecondary", + "identifier" => "secondary", + "originator" => %System{} + }) + + timestamp = DateTime.utc_now() + + response = + admin_user_request("/wallet.enable_or_disable", %{ + address: wallet.address, + enabled: false + }) + + assert response["success"] == true + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_admin(), + target: wallet, + changes: %{ + "enabled" => false + }, + encrypted_changes: %{} + ) + end end end diff --git a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/account_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/account_controller_test.exs index 2403d55bc..f67ff38c5 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/account_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/account_controller_test.exs @@ -127,6 +127,60 @@ defmodule AdminAPI.V1.ProviderAuth.AccountControllerTest do assert response["data"]["object"] == "error" assert response["data"]["code"] == "client:invalid_parameter" end + + test "generates an activity log" do + user = get_test_admin() + parent = User.get_account(user) + timestamp = DateTime.utc_now() + + request_data = %{ + parent_id: parent.id, + name: "A test account", + metadata: %{something: "interesting"}, + encrypted_metadata: %{something: "secret"} + } + + response = provider_request("/account.create", request_data) + + assert response["success"] == true + + account = Account.get(response["data"]["id"]) + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 3 + + logs + |> Enum.at(0) + |> assert_simple_activity_log( + action: "insert", + originator_type: "account", + target_type: "wallet" + ) + + logs + |> Enum.at(1) + |> assert_simple_activity_log( + action: "insert", + originator_type: "account", + target_type: "wallet" + ) + + logs + |> Enum.at(2) + |> assert_activity_log( + action: "insert", + originator: get_test_key(), + target: account, + changes: %{ + "metadata" => %{"something" => "interesting"}, + "name" => "A test account", + "parent_uuid" => parent.uuid + }, + encrypted_changes: %{ + "encrypted_metadata" => %{"something" => "secret"} + } + ) + end end describe "/account.update" do @@ -186,6 +240,44 @@ defmodule AdminAPI.V1.ProviderAuth.AccountControllerTest do assert response["data"]["object"] == "error" assert response["data"]["code"] == "unauthorized" end + + test "generates an activity log" do + user = get_test_admin() + account = User.get_account(user) + timestamp = DateTime.utc_now() + + request_data = + params_for(:account, %{ + id: account.id, + name: "updated_name", + description: "updated_description", + encrypted_metadata: %{something: "secret"} + }) + + response = provider_request("/account.update", request_data) + + assert response["success"] == true + + account = Account.get(response["data"]["id"]) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_key(), + target: account, + changes: %{ + "name" => "updated_name", + "parent_uuid" => account.parent_uuid, + "description" => "updated_description" + }, + encrypted_changes: %{ + "encrypted_metadata" => %{"something" => "secret"} + } + ) + end end describe "/account.upload_avatar" do @@ -309,5 +401,40 @@ defmodule AdminAPI.V1.ProviderAuth.AccountControllerTest do assert response["data"]["object"] == "error" assert response["data"]["code"] == "unauthorized" end + + test "generates an activity log" do + account = insert(:account) + timestamp = DateTime.utc_now() + + response = + provider_request("/account.upload_avatar", %{ + "id" => account.id, + "avatar" => %Plug.Upload{ + path: "test/support/assets/test.jpg", + filename: "test.jpg" + } + }) + + assert response["success"] == true + + account = Account.get(account.id) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_key(), + target: account, + changes: %{ + "avatar" => %{ + "file_name" => "test.jpg", + "updated_at" => Ecto.DateTime.to_iso8601(account.avatar.updated_at) + } + }, + encrypted_changes: %{} + ) + end end end diff --git a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/account_membership_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/account_membership_controller_test.exs index 9a07cf08b..b61f6eeb9 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/account_membership_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/account_membership_controller_test.exs @@ -2,7 +2,7 @@ defmodule AdminAPI.V1.ProviderAuth.AccountMembershipControllerTest do use AdminAPI.ConnCase, async: true alias Ecto.UUID alias EWallet.Web.Date - alias EWalletDB.{Account, User} + alias EWalletDB.{Account, User, Membership} @redirect_url "http://localhost:4000/invite?email={email}&token={token}" @@ -532,6 +532,40 @@ defmodule AdminAPI.V1.ProviderAuth.AccountMembershipControllerTest do assert response["data"]["description"] == "There is no role corresponding to the provided name." end + + test "generates an activity log" do + {:ok, user} = :user |> params_for() |> User.insert() + account = insert(:account) + role = insert(:role) + timestamp = DateTime.utc_now() + + response = + provider_request("/account.assign_user", %{ + user_id: user.id, + account_id: account.id, + role_name: role.name, + redirect_url: @redirect_url + }) + + assert response["success"] == true + membership = get_last_inserted(Membership) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: get_test_key(), + target: membership, + changes: %{ + "account_uuid" => account.uuid, + "role_uuid" => role.uuid, + "user_uuid" => user.uuid + }, + encrypted_changes: %{} + ) + end end describe "/account.unassign_user" do @@ -599,5 +633,33 @@ defmodule AdminAPI.V1.ProviderAuth.AccountMembershipControllerTest do assert response["data"]["description"] == "You are not allowed to perform the requested operation." end + + test "generates an activity log" do + account = insert(:account) + {:ok, user} = :user |> params_for() |> User.insert() + membership = insert(:membership, %{account: account, user: user}) + + timestamp = DateTime.utc_now() + + response = + provider_request("/account.unassign_user", %{ + user_id: user.id, + account_id: account.id + }) + + assert response["success"] == true + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "delete", + originator: get_test_key(), + target: membership, + changes: %{}, + encrypted_changes: %{} + ) + end end end diff --git a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/admin_user_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/admin_user_controller_test.exs index a35d88492..863ddaf14 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/admin_user_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/admin_user_controller_test.exs @@ -2,6 +2,7 @@ defmodule AdminAPI.V1.ProviderAuth.AdminControllerTest do use AdminAPI.ConnCase, async: true alias Ecto.UUID alias EWalletDB.{User, Account, AuthToken} + alias ActivityLogger.System @owner_app :some_app @@ -175,7 +176,7 @@ defmodule AdminAPI.V1.ProviderAuth.AdminControllerTest do admin = insert(:admin, %{email: "admin@omise.co"}) _membership = insert(:membership, %{user: admin, account: account, role: role}) - {:ok, token} = AuthToken.generate(admin, @owner_app) + {:ok, token} = AuthToken.generate(admin, @owner_app, %System{}) token_string = token.token # Ensure tokens is usable. assert AuthToken.authenticate(token_string, @owner_app) @@ -238,5 +239,37 @@ defmodule AdminAPI.V1.ProviderAuth.AdminControllerTest do assert response["data"]["code"] == "client:invalid_parameter" assert response["data"]["description"] == "Invalid parameter provided. `id` is required." end + + test "generates an activity log" do + account = Account.get_master_account() + role = insert(:role, %{name: "some_role"}) + admin = insert(:admin, %{email: "admin@omise.co"}) + _membership = insert(:membership, %{user: admin, account: account, role: role}) + + timestamp = DateTime.utc_now() + + response = + provider_request("/admin.enable_or_disable", %{ + id: admin.id, + enabled: false + }) + + assert response["success"] == true + admin = User.get(admin.id) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_key(), + target: admin, + changes: %{ + "enabled" => false + }, + encrypted_changes: %{} + ) + end end end diff --git a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/api_key_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/api_key_controller_test.exs index d308a31bb..b6f6b82d6 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/api_key_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/api_key_controller_test.exs @@ -120,6 +120,32 @@ defmodule AdminAPI.V1.ProviderAuth.APIKeyControllerTest do } } end + + test "generates an activity log" do + timestamp = DateTime.utc_now() + response = provider_request("/api_key.create", %{}) + + assert response["success"] == true + api_key = get_last_inserted(APIKey) + account = Account.get_master_account() + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: get_test_key(), + target: api_key, + changes: %{ + "account_uuid" => account.uuid, + "key" => response["data"]["key"], + "owner_app" => "ewallet_api" + }, + encrypted_changes: %{} + ) + end end describe "/api_key.update" do @@ -185,6 +211,34 @@ defmodule AdminAPI.V1.ProviderAuth.APIKeyControllerTest do } } end + + test "generates an activity log" do + api_key = :api_key |> insert() |> Repo.preload(:account) + timestamp = DateTime.utc_now() + + response = + provider_request("/api_key.update", %{ + id: api_key.id, + expired: true + }) + + assert response["success"] == true + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_key(), + target: api_key, + changes: %{ + "enabled" => false + }, + encrypted_changes: %{} + ) + end end describe "/api_key.enable_or_disable" do @@ -238,6 +292,34 @@ defmodule AdminAPI.V1.ProviderAuth.APIKeyControllerTest do assert response["data"]["id"] == api_key.id assert response["data"]["enabled"] == true end + + test "generates an activity log" do + api_key = :api_key |> insert() |> Repo.preload(:account) + timestamp = DateTime.utc_now() + + response = + provider_request("/api_key.enable_or_disable", %{ + id: api_key.id, + enabled: false + }) + + assert response["success"] == true + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_key(), + target: api_key, + changes: %{ + "enabled" => false + }, + encrypted_changes: %{} + ) + end end describe "/api_key.delete" do @@ -287,5 +369,27 @@ defmodule AdminAPI.V1.ProviderAuth.APIKeyControllerTest do } } end + + test "generates an activity log" do + api_key = insert(:api_key) + timestamp = DateTime.utc_now() + response = provider_request("/api_key.delete", %{id: api_key.id}) + + assert response["success"] == true + + api_key = Repo.get_by(APIKey, %{id: api_key.id}) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_key(), + target: api_key, + changes: %{"deleted_at" => NaiveDateTime.to_iso8601(api_key.deleted_at)}, + encrypted_changes: %{} + ) + end end end diff --git a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/category_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/category_controller_test.exs index 978ce65d0..2010962c1 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/category_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/category_controller_test.exs @@ -1,6 +1,6 @@ defmodule AdminAPI.V1.ProviderAuth.CategoryControllerTest do use AdminAPI.ConnCase, async: true - alias EWalletDB.Category + alias EWalletDB.{Category, Repo} alias EWalletDB.Helpers.Preloader alias ActivityLogger.System @@ -102,6 +102,28 @@ defmodule AdminAPI.V1.ProviderAuth.CategoryControllerTest do assert response["data"]["object"] == "error" assert response["data"]["code"] == "client:invalid_parameter" end + + test "generates an activity log" do + timestamp = DateTime.utc_now() + request_data = %{name: "A test category"} + response = provider_request("/category.create", request_data) + + assert response["success"] == true + + category = Category.get(response["data"]["id"]) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: get_test_key(), + target: category, + changes: %{"name" => "A test category"}, + encrypted_changes: %{} + ) + end end describe "/category.update" do @@ -164,6 +186,35 @@ defmodule AdminAPI.V1.ProviderAuth.CategoryControllerTest do assert response["data"]["description"] == "There is no category corresponding to the provided id." end + + test "generates an activity log" do + category = insert(:category) + timestamp = DateTime.utc_now() + + request_data = + params_for(:category, %{ + id: category.id, + name: "updated_name", + description: "updated_description" + }) + + response = provider_request("/category.update", request_data) + + assert response["success"] == true + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_key(), + target: category, + changes: %{"name" => "updated_name", "description" => "updated_description"}, + encrypted_changes: %{} + ) + end end describe "/category.delete" do @@ -238,5 +289,28 @@ defmodule AdminAPI.V1.ProviderAuth.CategoryControllerTest do } } end + + test "generates an activity log" do + category = insert(:category) + timestamp = DateTime.utc_now() + + response = provider_request("/category.delete", %{id: category.id}) + + assert response["success"] == true + + category = Repo.get_by(Category, %{id: category.id}) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_key(), + target: category, + changes: %{"deleted_at" => NaiveDateTime.to_iso8601(category.deleted_at)}, + encrypted_changes: %{} + ) + end end end diff --git a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/exchange_pair_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/exchange_pair_controller_test.exs index 094a25f87..469e35e21 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/exchange_pair_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/exchange_pair_controller_test.exs @@ -191,6 +191,36 @@ defmodule AdminAPI.V1.ProviderAuth.ExchangePairControllerTest do assert response["data"]["description"] == "Invalid parameter provided. `to_token_id` can't be blank." end + + test "generates an activity log" do + timestamp = DateTime.utc_now() + request_data = insert_params() + response = provider_request("/exchange_pair.create", request_data) + + assert response["success"] == true + + exchange_pair = + ExchangePair + |> get_last_inserted() + |> Repo.preload([:from_token, :to_token]) + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: get_test_key(), + target: exchange_pair, + changes: %{ + "from_token_uuid" => exchange_pair.from_token.uuid, + "rate" => exchange_pair.rate, + "to_token_uuid" => exchange_pair.to_token.uuid + }, + encrypted_changes: %{} + ) + end end describe "/exchange_pair.update" do @@ -325,6 +355,35 @@ defmodule AdminAPI.V1.ProviderAuth.ExchangePairControllerTest do assert response["data"]["description"] == "Invalid parameter provided. `rate` must be greater than 0." end + + test "generates an activity log" do + exchange_pair = :exchange_pair |> insert() |> Repo.preload([:from_token, :to_token]) + timestamp = DateTime.utc_now() + + request_data = %{ + id: exchange_pair.id, + rate: 999.99 + } + + response = provider_request("/exchange_pair.update", request_data) + + assert response["success"] == true + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_key(), + target: exchange_pair, + changes: %{ + "rate" => request_data.rate + }, + encrypted_changes: %{} + ) + end end describe "/exchange_pair.delete" do @@ -423,5 +482,29 @@ defmodule AdminAPI.V1.ProviderAuth.ExchangePairControllerTest do } } end + + test "generates an activity log" do + timestamp = DateTime.utc_now() + exchange_pair = insert(:exchange_pair) + + response = provider_request("/exchange_pair.delete", %{id: exchange_pair.id}) + + assert response["success"] == true + exchange_pair = Repo.get_by(ExchangePair, %{id: exchange_pair.id}) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_key(), + target: exchange_pair, + changes: %{ + "deleted_at" => NaiveDateTime.to_iso8601(exchange_pair.deleted_at) + }, + encrypted_changes: %{} + ) + end end end diff --git a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/key_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/key_controller_test.exs index aa254290b..f72f9d0c9 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/key_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/key_controller_test.exs @@ -5,7 +5,7 @@ defmodule AdminAPI.V1.ProviderAuth.KeyControllerTest do describe "/access_key.all" do test "responds with a list of keys without secret keys" do - key_1 = Key |> Repo.get_by(access_key: @access_key) |> Repo.preload([:account]) + key_1 = Key.get_by(%{access_key: @access_key}, preload: :account) key_2 = insert(:key, %{secret_key: "the_secret_key"}) response = provider_request("/access_key.all") @@ -66,6 +66,32 @@ defmodule AdminAPI.V1.ProviderAuth.KeyControllerTest do # so we can only check that it is a string with some length. assert String.length(response["data"]["secret_key"]) > 0 end + + test "generates an activity log" do + timestamp = DateTime.utc_now() + response = provider_request("/access_key.create") + + assert response["success"] == true + key = get_last_inserted(Key) + account = Account.get_master_account() + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: get_test_key(), + target: key, + changes: %{ + "account_uuid" => account.uuid, + "access_key" => key.access_key, + "secret_key_hash" => key.secret_key_hash + }, + encrypted_changes: %{} + ) + end end describe "/access_key.update" do @@ -122,6 +148,34 @@ defmodule AdminAPI.V1.ProviderAuth.KeyControllerTest do updated = Key.get(key.id) assert updated.secret_key_hash == key.secret_key_hash end + + test "generates an activity log" do + key = insert(:key) + timestamp = DateTime.utc_now() + + response = + provider_request("/access_key.update", %{ + id: key.id, + expired: true + }) + + assert response["success"] == true + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_key(), + target: key, + changes: %{ + "enabled" => false + }, + encrypted_changes: %{} + ) + end end describe "/access_key.enable_or_disable" do @@ -175,6 +229,34 @@ defmodule AdminAPI.V1.ProviderAuth.KeyControllerTest do assert response["data"]["id"] == key.id assert response["data"]["enabled"] == true end + + test "generates an activity log" do + key = insert(:key) + timestamp = DateTime.utc_now() + + response = + provider_request("/access_key.enable_or_disable", %{ + id: key.id, + enabled: false + }) + + assert response["success"] == true + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_key(), + target: key, + changes: %{ + "enabled" => false + }, + encrypted_changes: %{} + ) + end end describe "/access_key.delete" do @@ -227,5 +309,28 @@ defmodule AdminAPI.V1.ProviderAuth.KeyControllerTest do } } end + + test "generates an activity log" do + key = insert(:key) + + timestamp = DateTime.utc_now() + response = provider_request("/access_key.delete", %{id: key.id}) + + assert response["success"] == true + + key = Repo.get_by(Key, %{id: key.id}) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_key(), + target: key, + changes: %{"deleted_at" => NaiveDateTime.to_iso8601(key.deleted_at)}, + encrypted_changes: %{} + ) + end end end diff --git a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/mint_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/mint_controller_test.exs index 7a2d5ac2a..ae78d3ade 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/mint_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/mint_controller_test.exs @@ -3,7 +3,7 @@ defmodule AdminAPI.V1.ProviderAuth.MintControllerTest do alias EWallet.MintGate alias EWallet.Web.Date alias EWallet.Web.V1.{AccountSerializer, TokenSerializer, TransactionSerializer} - alias EWalletDB.{Mint, Repo} + alias EWalletDB.{Mint, Repo, Account, Wallet, Transaction} alias ActivityLogger.System describe "/token.get_mints" do @@ -181,5 +181,116 @@ defmodule AdminAPI.V1.ProviderAuth.MintControllerTest do assert response["data"]["messages"] == %{"amount" => ["number"]} end + + test "generates an activity log" do + token = insert(:token) + timestamp = DateTime.utc_now() + + response = + provider_request("/token.mint", %{ + id: token.id, + amount: 1_000_000 * token.subunit_to_unit + }) + + assert response["success"] == true + + mint = Mint.get(response["data"]["id"]) + account = Account.get_master_account() + wallet = Account.get_primary_wallet(account) + genesis = Wallet.get("gnis000000000000") + transaction = get_last_inserted(Transaction) + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 6 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: get_test_key(), + target: mint, + changes: %{ + "account_uuid" => account.uuid, + "amount" => 100_000_000, + "token_uuid" => token.uuid + }, + encrypted_changes: %{} + ) + + logs + |> Enum.at(1) + |> assert_activity_log( + action: "insert", + originator: :system, + target: genesis, + changes: %{ + "address" => "gnis000000000000", + "identifier" => "genesis", + "name" => "genesis" + }, + encrypted_changes: %{} + ) + + logs + |> Enum.at(2) + |> assert_activity_log( + action: "insert", + originator: mint, + target: transaction, + changes: %{ + "from" => "gnis000000000000", + "from_amount" => 100_000_000, + "from_token_uuid" => token.uuid, + "idempotency_token" => transaction.idempotency_token, + "to" => wallet.address, + "to_account_uuid" => account.uuid, + "to_amount" => 100_000_000, + "to_token_uuid" => token.uuid + }, + encrypted_changes: %{ + "payload" => %{ + "amount" => 100_000_000, + "description" => nil, + "idempotency_token" => transaction.idempotency_token, + "token_id" => token.id + } + } + ) + + logs + |> Enum.at(3) + |> assert_activity_log( + action: "update", + originator: transaction, + target: mint, + changes: %{ + "transaction_uuid" => transaction.uuid + }, + encrypted_changes: %{} + ) + + logs + |> Enum.at(4) + |> assert_activity_log( + action: "update", + originator: :system, + target: transaction, + changes: %{ + "local_ledger_uuid" => transaction.local_ledger_uuid, + "status" => "confirmed" + }, + encrypted_changes: %{} + ) + + logs + |> Enum.at(5) + |> assert_activity_log( + action: "update", + originator: transaction, + target: mint, + changes: %{"confirmed" => true}, + encrypted_changes: %{} + ) + end end end diff --git a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/role_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/role_controller_test.exs index a1e63e6aa..6ecd169e4 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/role_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/role_controller_test.exs @@ -1,6 +1,6 @@ defmodule AdminAPI.V1.ProviderAuth.RoleControllerTest do use AdminAPI.ConnCase, async: true - alias EWalletDB.{Membership, Role} + alias EWalletDB.{Membership, Role, Repo} alias ActivityLogger.System describe "/role.all" do @@ -101,6 +101,28 @@ defmodule AdminAPI.V1.ProviderAuth.RoleControllerTest do assert response["data"]["object"] == "error" assert response["data"]["code"] == "client:invalid_parameter" end + + test "generates an activity log" do + timestamp = DateTime.utc_now() + request_data = %{name: "test_role"} + response = provider_request("/role.create", request_data) + + assert response["success"] == true + + role = Role.get(response["data"]["id"]) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: get_test_key(), + target: role, + changes: %{"name" => "test_role", "priority" => role.priority}, + encrypted_changes: %{} + ) + end end describe "/role.update" do @@ -144,6 +166,40 @@ defmodule AdminAPI.V1.ProviderAuth.RoleControllerTest do assert response["data"]["description"] == "There is no role corresponding to the provided id." end + + test "generates an activity log" do + timestamp = DateTime.utc_now() + role = insert(:role) + + request_data = + params_for(:role, %{ + id: role.id, + name: "updated_name", + display_name: "updated_display_name" + }) + + response = provider_request("/role.update", request_data) + + assert response["success"] == true + + role = Role.get(response["data"]["id"]) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_key(), + target: role, + changes: %{ + "priority" => role.priority, + "name" => "updated_name", + "display_name" => "updated_display_name" + }, + encrypted_changes: %{} + ) + end end describe "/role.delete" do @@ -216,5 +272,30 @@ defmodule AdminAPI.V1.ProviderAuth.RoleControllerTest do } } end + + test "generates an activity log" do + role = insert(:role) + + timestamp = DateTime.utc_now() + response = provider_request("/role.delete", %{id: role.id}) + + assert response["success"] == true + + role = Repo.get_by(Role, %{id: role.id}) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_key(), + target: role, + changes: %{ + "deleted_at" => NaiveDateTime.to_iso8601(role.deleted_at) + }, + encrypted_changes: %{} + ) + end end end diff --git a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/token_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/token_controller_test.exs index b839dd876..6adb7fa3d 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/token_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/token_controller_test.exs @@ -1,7 +1,8 @@ defmodule AdminAPI.V1.ProviderAuth.TokenControllerTest do use AdminAPI.ConnCase, async: true alias EWallet.Web.V1.TokenSerializer - alias EWalletDB.{Mint, Repo, Token} + alias EWalletDB.{Mint, Repo, Token, Account, Wallet, Transaction} + alias ActivityLogger.System describe "/token.all" do test "returns a list of tokens and pagination data" do @@ -241,6 +242,178 @@ defmodule AdminAPI.V1.ProviderAuth.TokenControllerTest do inserted = Token |> Repo.all() |> Enum.at(0) assert inserted == nil end + + test "generates an activity log" do + timestamp = DateTime.utc_now() + + response = + provider_request("/token.create", %{ + symbol: "BTC", + name: "Bitcoin", + description: "desc", + subunit_to_unit: 100, + metadata: %{something: "interesting"}, + encrypted_metadata: %{something: "secret"} + }) + + assert response["success"] == true + + token = Token.get(response["data"]["id"]) + account = Account.get_master_account() + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: get_test_key(), + target: token, + changes: %{ + "name" => "Bitcoin", + "account_uuid" => account.uuid, + "description" => "desc", + "id" => token.id, + "metadata" => %{"something" => "interesting"}, + "subunit_to_unit" => 100, + "symbol" => "BTC" + }, + encrypted_changes: %{"encrypted_metadata" => %{"something" => "secret"}} + ) + end + + test "generates an activity log when minting" do + timestamp = DateTime.utc_now() + + response = + provider_request("/token.create", %{ + symbol: "BTC", + name: "Bitcoin", + description: "desc", + subunit_to_unit: 100, + amount: 100, + metadata: %{something: "interesting"}, + encrypted_metadata: %{something: "secret"} + }) + + assert response["success"] == true + + token = Token.get(response["data"]["id"]) + account = Account.get_master_account() + wallet = Account.get_primary_wallet(account) + genesis = Wallet.get("gnis000000000000") + mint = Mint |> Repo.all() |> Enum.at(0) + transaction = get_last_inserted(Transaction) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 7 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: get_test_key(), + target: token, + changes: %{ + "name" => "Bitcoin", + "account_uuid" => account.uuid, + "description" => "desc", + "id" => token.id, + "metadata" => %{"something" => "interesting"}, + "subunit_to_unit" => 100, + "symbol" => "BTC" + }, + encrypted_changes: %{"encrypted_metadata" => %{"something" => "secret"}} + ) + + logs + |> Enum.at(1) + |> assert_activity_log( + action: "insert", + originator: get_test_key(), + target: mint, + changes: %{ + "account_uuid" => account.uuid, + "amount" => 100, + "token_uuid" => token.uuid + }, + encrypted_changes: %{} + ) + + logs + |> Enum.at(2) + |> assert_activity_log( + action: "insert", + originator: :system, + target: genesis, + changes: %{ + "address" => "gnis000000000000", + "identifier" => "genesis", + "name" => "genesis" + }, + encrypted_changes: %{} + ) + + logs + |> Enum.at(3) + |> assert_activity_log( + action: "insert", + originator: mint, + target: transaction, + changes: %{ + "from" => "gnis000000000000", + "from_amount" => 100, + "from_token_uuid" => token.uuid, + "idempotency_token" => transaction.idempotency_token, + "to" => wallet.address, + "to_account_uuid" => account.uuid, + "to_amount" => 100, + "to_token_uuid" => token.uuid + }, + encrypted_changes: %{ + "payload" => %{ + "amount" => 100, + "description" => nil, + "idempotency_token" => transaction.idempotency_token, + "token_id" => token.id + } + } + ) + + logs + |> Enum.at(4) + |> assert_activity_log( + action: "update", + originator: transaction, + target: mint, + changes: %{ + "transaction_uuid" => transaction.uuid + }, + encrypted_changes: %{} + ) + + logs + |> Enum.at(5) + |> assert_activity_log( + action: "update", + originator: :system, + target: transaction, + changes: %{ + "local_ledger_uuid" => transaction.local_ledger_uuid, + "status" => "confirmed" + }, + encrypted_changes: %{} + ) + + logs + |> Enum.at(6) + |> assert_activity_log( + action: "update", + originator: transaction, + target: mint, + changes: %{"confirmed" => true}, + encrypted_changes: %{} + ) + end end describe "/token.update" do @@ -288,5 +461,129 @@ defmodule AdminAPI.V1.ProviderAuth.TokenControllerTest do "messages" => nil } end + + test "generates an activity log" do + token = insert(:token) + + timestamp = DateTime.utc_now() + + response = + provider_request("/token.update", %{ + id: token.id, + name: "updated name", + description: "updated description", + metadata: %{something: "interesting"}, + encrypted_metadata: %{something: "secret"} + }) + + assert response["success"] == true + + token = Token.get(token.id) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_key(), + target: token, + changes: %{ + "metadata" => %{"something" => "interesting"}, + "description" => "updated description", + "name" => "updated name" + }, + encrypted_changes: %{"encrypted_metadata" => %{"something" => "secret"}} + ) + end + end + + describe "/token.enable_or_disable" do + test "disables an existing token" do + token = insert(:token) + + response = + provider_request("/token.enable_or_disable", %{ + id: token.id, + enabled: false + }) + + assert response["success"] + assert response["data"]["object"] == "token" + assert response["data"]["enabled"] == false + end + + test "fails to disable an existing token with enabled = nil" do + token = insert(:token) + + response = + provider_request("/token.enable_or_disable", %{ + id: token.id, + enabled: nil + }) + + assert response["success"] == false + assert response["data"]["code"] == "client:invalid_parameter" + + assert response["data"]["description"] == + "Invalid parameter provided. `enabled` can't be blank." + end + + test "Raises invalid_parameter error if id is missing" do + response = + provider_request("/token.enable_or_disable", %{enabled: false, originator: %System{}}) + + refute response["success"] + + assert response["data"] == %{ + "object" => "error", + "code" => "client:invalid_parameter", + "description" => "Invalid parameter provided. `id` is required.", + "messages" => nil + } + end + + test "Raises token_not_found error if the token can't be found" do + response = provider_request("/token.enable_or_disable", %{id: "fake", enabled: false}) + + refute response["success"] + + assert response["data"] == %{ + "object" => "error", + "code" => "token:id_not_found", + "description" => "There is no token corresponding to the provided id.", + "messages" => nil + } + end + + test "generates an activity log" do + token = insert(:token) + + timestamp = DateTime.utc_now() + + response = + provider_request("/token.enable_or_disable", %{ + id: token.id, + enabled: false + }) + + assert response["success"] == true + + token = Token.get(token.id) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_key(), + target: token, + changes: %{ + "enabled" => false + }, + encrypted_changes: %{} + ) + end end end diff --git a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/transaction_consumption_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/transaction_consumption_controller_test.exs index 780f2849d..767c75ffd 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/transaction_consumption_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/transaction_consumption_controller_test.exs @@ -7,6 +7,7 @@ defmodule AdminAPI.V1.ProviderAuth.TransactionConsumptionControllerTest do Repo, Transaction, TransactionConsumption, + TransactionRequest, User } @@ -2035,5 +2036,175 @@ defmodule AdminAPI.V1.ProviderAuth.TransactionConsumptionControllerTest do Endpoint.unsubscribe("transaction_request:#{transaction_request.id}") Endpoint.unsubscribe("transaction_consumption:#{consumption_id}") end + + test "generates an activity log", meta do + transaction_request = + insert( + :transaction_request, + type: "receive", + token_uuid: meta.token.uuid, + user_uuid: meta.alice.uuid, + wallet: meta.alice_wallet, + amount: 100_000 * meta.token.subunit_to_unit + ) + + set_initial_balance(%{ + address: meta.bob_wallet.address, + token: meta.token, + amount: 150_000 + }) + + timestamp = DateTime.utc_now() + + response = + provider_request("/transaction_request.consume", %{ + idempotency_token: "123", + formatted_transaction_request_id: transaction_request.id, + correlation_id: nil, + amount: nil, + address: nil, + metadata: nil, + token_id: nil, + account_id: meta.account.id + }) + + assert response["success"] == true + + transaction_request = TransactionRequest.get(transaction_request.id) + transaction_consumption = TransactionConsumption.get(response["data"]["id"]) + transaction = get_last_inserted(Transaction) + alice_account_user = get_last_inserted(AccountUser) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 8 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: get_test_key(), + target: transaction_consumption, + changes: %{ + "account_uuid" => meta.account.uuid, + "estimated_at" => NaiveDateTime.to_iso8601(transaction_consumption.estimated_at), + "estimated_consumption_amount" => 10_000_000, + "estimated_rate" => 1.0, + "estimated_request_amount" => 10_000_000, + "idempotency_token" => "123", + "token_uuid" => meta.token.uuid, + "transaction_request_uuid" => transaction_request.uuid, + "wallet_address" => meta.account_wallet.address + }, + encrypted_changes: %{} + ) + + logs + |> Enum.at(1) + |> assert_activity_log( + action: "update", + originator: :system, + target: transaction_consumption, + changes: %{ + "approved_at" => NaiveDateTime.to_iso8601(transaction_consumption.approved_at), + "status" => "approved" + }, + encrypted_changes: %{} + ) + + logs + |> Enum.at(2) + |> assert_activity_log( + action: "insert", + originator: transaction_consumption, + target: transaction, + changes: %{ + "calculated_at" => NaiveDateTime.to_iso8601(transaction.calculated_at), + "from" => meta.account_wallet.address, + "from_account_uuid" => meta.account.uuid, + "from_amount" => 10_000_000, + "from_token_uuid" => meta.token.uuid, + "idempotency_token" => "123", + "rate" => 1.0, + "to" => meta.alice_wallet.address, + "to_amount" => 10_000_000, + "to_token_uuid" => meta.token.uuid, + "to_user_uuid" => meta.alice.uuid + }, + encrypted_changes: %{ + "payload" => %{ + "encrypted_metadata" => %{}, + "exchange_account_id" => nil, + "exchange_wallet_address" => nil, + "from_address" => meta.account_wallet.address, + "from_amount" => 10_000_000, + "from_token_id" => meta.token.id, + "idempotency_token" => "123", + "metadata" => %{}, + "to_address" => meta.alice_wallet.address, + "to_amount" => 10_000_000, + "to_token_id" => meta.token.id + } + } + ) + + logs + |> Enum.at(3) + |> assert_activity_log( + action: "insert", + originator: transaction, + target: alice_account_user, + changes: %{ + "account_uuid" => alice_account_user.account_uuid, + "user_uuid" => meta.alice.uuid + }, + encrypted_changes: %{} + ) + + logs + |> Enum.at(4) + |> assert_activity_log( + action: "update", + originator: :system, + target: transaction, + changes: %{ + "local_ledger_uuid" => transaction.local_ledger_uuid, + "status" => "confirmed" + }, + encrypted_changes: %{} + ) + + logs + |> Enum.at(5) + |> assert_activity_log( + action: "update", + originator: transaction, + target: transaction_consumption, + changes: %{ + "confirmed_at" => NaiveDateTime.to_iso8601(transaction_consumption.confirmed_at), + "status" => "confirmed", + "transaction_uuid" => transaction.uuid + }, + encrypted_changes: %{} + ) + + logs + |> Enum.at(6) + |> assert_activity_log( + action: "update", + originator: :system, + target: transaction_request, + changes: %{"consumptions_count" => 1}, + encrypted_changes: %{} + ) + + logs + |> Enum.at(7) + |> assert_activity_log( + action: "update", + originator: :system, + target: transaction_request, + changes: %{"updated_at" => NaiveDateTime.to_iso8601(transaction_request.updated_at)}, + encrypted_changes: %{} + ) + end end end diff --git a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/transaction_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/transaction_controller_test.exs index 33cddc1ad..29b9e3050 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/transaction_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/transaction_controller_test.exs @@ -785,6 +785,78 @@ defmodule AdminAPI.V1.ProviderAuth.TransactionControllerTest do "object" => "error" } end + + test "generates an activity log" do + token = insert(:token) + mint!(token) + + wallet_1 = insert(:wallet) + wallet_2 = insert(:wallet) + + set_initial_balance(%{ + address: wallet_1.address, + token: token, + amount: 2_000_000 + }) + + timestamp = DateTime.utc_now() + + response = + provider_request("/transaction.create", %{ + "idempotency_token" => "123", + "from_address" => wallet_1.address, + "to_address" => wallet_2.address, + "token_id" => token.id, + "amount" => 1_000_000 + }) + + assert response["success"] == true + + transaction = Transaction.get(response["data"]["id"]) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 2 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: get_test_key(), + target: transaction, + changes: %{ + "from" => wallet_1.address, + "from_amount" => 1_000_000, + "from_token_uuid" => token.uuid, + "from_user_uuid" => wallet_1.user.uuid, + "idempotency_token" => "123", + "to" => wallet_2.address, + "to_amount" => 1_000_000, + "to_token_uuid" => token.uuid, + "to_user_uuid" => wallet_2.user.uuid + }, + encrypted_changes: %{ + "payload" => %{ + "amount" => 1_000_000, + "from_address" => wallet_1.address, + "idempotency_token" => "123", + "to_address" => wallet_2.address, + "token_id" => token.id + } + } + ) + + logs + |> Enum.at(1) + |> assert_activity_log( + action: "update", + originator: :system, + target: transaction, + changes: %{ + "local_ledger_uuid" => transaction.local_ledger_uuid, + "status" => "confirmed" + }, + encrypted_changes: %{} + ) + end end describe "/transaction.create for cross-token transactions" do @@ -1239,5 +1311,97 @@ defmodule AdminAPI.V1.ProviderAuth.TransactionControllerTest do "object" => "error" } end + + test "generates an activity log" do + account = Account.get_master_account() + account_wallet = Account.get_primary_wallet(account) + {:ok, user_1} = :user |> params_for() |> User.insert() + {:ok, user_2} = :user |> params_for() |> User.insert() + wallet_1 = User.get_primary_wallet(user_1) + wallet_2 = User.get_primary_wallet(user_2) + + token_1 = insert(:token) + token_2 = insert(:token) + + mint!(token_1) + mint!(token_2) + + pair = insert(:exchange_pair, from_token: token_1, to_token: token_2, rate: 2) + + set_initial_balance(%{ + address: wallet_1.address, + token: token_1, + amount: 2_000_000 + }) + + timestamp = DateTime.utc_now() + + response = + provider_request("/transaction.create", %{ + "idempotency_token" => "12344", + "exchange_account_id" => account.id, + "from_amount" => 1_000, + "from_token_id" => token_1.id, + "from_address" => wallet_1.address, + "to_amount" => 1_000 * pair.rate, + "to_token_id" => token_2.id, + "to_address" => wallet_2.address + }) + + assert response["success"] == true + + transaction = Transaction.get(response["data"]["id"]) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 2 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: get_test_key(), + target: transaction, + changes: %{ + "from" => wallet_1.address, + "from_token_uuid" => token_1.uuid, + "from_user_uuid" => user_1.uuid, + "to" => wallet_2.address, + "to_token_uuid" => token_2.uuid, + "to_user_uuid" => user_2.uuid, + "from_amount" => 1000, + "idempotency_token" => "12344", + "to_amount" => 2000, + "calculated_at" => NaiveDateTime.to_iso8601(transaction.calculated_at), + "exchange_account_uuid" => account.uuid, + "exchange_pair_uuid" => pair.uuid, + "exchange_wallet_address" => account_wallet.address, + "rate" => 2.0 + }, + encrypted_changes: %{ + "payload" => %{ + "from_address" => wallet_1.address, + "to_address" => wallet_2.address, + "idempotency_token" => "12344", + "exchange_account_id" => account.id, + "from_amount" => 1000, + "from_token_id" => token_1.id, + "to_amount" => 2000, + "to_token_id" => token_2.id + } + } + ) + + logs + |> Enum.at(1) + |> assert_activity_log( + action: "update", + originator: :system, + target: transaction, + changes: %{ + "local_ledger_uuid" => transaction.local_ledger_uuid, + "status" => "confirmed" + }, + encrypted_changes: %{} + ) + end end end diff --git a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/transaction_request_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/transaction_request_controller_test.exs index bda1e8b50..8c8778291 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/transaction_request_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/transaction_request_controller_test.exs @@ -340,6 +340,62 @@ defmodule AdminAPI.V1.ProviderAuth.TransactionRequestControllerTest do } } end + + test "generates an activity log" do + account = Account.get_master_account() + user = get_test_user() + token = insert(:token) + account_wallet = Account.get_primary_wallet(account) + wallet = User.get_primary_wallet(user) + {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) + + timestamp = DateTime.utc_now() + + response = + provider_request("/transaction_request.create", %{ + type: "send", + token_id: token.id, + correlation_id: "123", + amount: 1_000, + address: wallet.address, + exchange_wallet_address: account_wallet.address + }) + + assert response["success"] == true + + transaction_request = TransactionRequest.get(response["data"]["id"]) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 2 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: get_test_key(), + target: transaction_request, + changes: %{ + "amount" => 1000, + "correlation_id" => "123", + "exchange_account_uuid" => account.uuid, + "exchange_wallet_address" => account_wallet.address, + "token_uuid" => token.uuid, + "type" => "send", + "user_uuid" => user.uuid, + "wallet_address" => wallet.address + }, + encrypted_changes: %{} + ) + + logs + |> Enum.at(1) + |> assert_activity_log( + action: "update", + originator: :system, + target: transaction_request, + changes: %{"consumptions_count" => 0}, + encrypted_changes: %{} + ) + end end describe "/transaction_request.get" do diff --git a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/user_auth_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/user_auth_controller_test.exs index cb19b127f..7cdda21c1 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/user_auth_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/user_auth_controller_test.exs @@ -97,6 +97,33 @@ defmodule AdminAPI.V1.ProviderAuth.UserAuthControllerTest do assert response == expected end + + test "generates activity logs" do + {:ok, user} = :user |> params_for() |> User.insert() + timestamp = DateTime.utc_now() + + response = provider_request("/user.login", %{id: user.id}) + + assert response["success"] == true + + auth_token = get_last_inserted(AuthToken) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: get_test_key(), + target: auth_token, + changes: %{ + "owner_app" => "ewallet_api", + "token" => auth_token.token, + "user_uuid" => user.uuid + }, + encrypted_changes: %{} + ) + end end describe "/user.logout" do @@ -114,5 +141,36 @@ defmodule AdminAPI.V1.ProviderAuth.UserAuthControllerTest do assert response["success"] == true assert response["data"] == %{} end + + test "generates activity logs" do + _user = insert(:user, %{provider_user_id: "1234"}) + admin_user_request("/user.login", %{provider_user_id: "1234"}) + auth_token = get_last_inserted(AuthToken) + + timestamp = DateTime.utc_now() + + response = + provider_request("/user.logout", %{ + "auth_token" => auth_token.token + }) + + assert response["success"] == true + + auth_token = get_last_inserted(AuthToken) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_key(), + target: auth_token, + changes: %{ + "expired" => true + }, + encrypted_changes: %{} + ) + end end end diff --git a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/user_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/user_controller_test.exs index 7851bf8e3..f2c523794 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/user_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/user_controller_test.exs @@ -242,6 +242,73 @@ defmodule AdminAPI.V1.ProviderAuth.UserControllerTest do assert response["data"]["messages"] == %{"username" => ["required"]} end + + test "generates an activity log" do + request_data = + params_for( + :user, + metadata: %{something: "interesting"}, + encrypted_metadata: %{something: "secret"} + ) + + timestamp = DateTime.utc_now() + + response = provider_request("/user.create", request_data) + + assert response["success"] == true + + user = User.get(response["data"]["id"]) + wallet = User.get_primary_wallet(user) + account_user = get_last_inserted(AccountUser) + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 3 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: user, + target: wallet, + changes: %{ + "identifier" => "primary", + "name" => "primary", + "user_uuid" => user.uuid + }, + encrypted_changes: %{} + ) + + logs + |> Enum.at(1) + |> assert_activity_log( + action: "insert", + originator: get_test_key(), + target: user, + changes: %{ + "metadata" => %{"something" => "interesting"}, + "calling_name" => user.calling_name, + "full_name" => user.full_name, + "provider_user_id" => user.provider_user_id, + "username" => user.username + }, + encrypted_changes: %{ + "encrypted_metadata" => %{"something" => "secret"} + } + ) + + logs + |> Enum.at(2) + |> assert_activity_log( + action: "insert", + originator: get_test_key(), + target: account_user, + changes: %{ + "account_uuid" => account_user.account_uuid, + "user_uuid" => user.uuid + }, + encrypted_changes: %{} + ) + end end describe "/user.update" do @@ -404,6 +471,53 @@ defmodule AdminAPI.V1.ProviderAuth.UserControllerTest do assert response["data"]["code"] == "client:invalid_parameter" assert response["data"]["description"] == "Invalid parameter provided." end + + test "generates an activity log" do + {:ok, user} = :user |> params_for() |> User.insert() + + # Prepare the update data while keeping only provider_user_id the same + request_data = + params_for(:user, %{ + provider_user_id: user.provider_user_id, + username: "updated_username", + metadata: %{ + first_name: "updated_first_name", + last_name: "updated_last_name" + } + }) + + account = Account.get_master_account() + {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) + + timestamp = DateTime.utc_now() + + response = provider_request("/user.update", request_data) + + assert response["success"] == true + + user = User.get(response["data"]["id"]) + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_key(), + target: user, + changes: %{ + "metadata" => %{ + "first_name" => "updated_first_name", + "last_name" => "updated_last_name" + }, + "username" => "updated_username", + "calling_name" => user.calling_name, + "full_name" => user.full_name + }, + encrypted_changes: %{} + ) + end end describe "/user.enable_or_disable" do @@ -412,7 +526,7 @@ defmodule AdminAPI.V1.ProviderAuth.UserControllerTest do account = Account.get_master_account() {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) - {:ok, token} = AuthToken.generate(user, @owner_app) + {:ok, token} = AuthToken.generate(user, @owner_app, %System{}) token_string = token.token # Ensure tokens is usable. assert AuthToken.authenticate(token_string, @owner_app) @@ -433,7 +547,7 @@ defmodule AdminAPI.V1.ProviderAuth.UserControllerTest do account = Account.get_master_account() {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) - {:ok, token} = AuthToken.generate(user, @owner_app) + {:ok, token} = AuthToken.generate(user, @owner_app, %System{}) token_string = token.token # Ensure tokens is usable. assert AuthToken.authenticate(token_string, @owner_app) @@ -499,5 +613,36 @@ defmodule AdminAPI.V1.ProviderAuth.UserControllerTest do assert response["data"]["description"] == "Invalid parameter provided. `id` or `provider_user_id` is required." end + + test "generates an activity log" do + user = insert(:user, %{enabled: true}) + account = Account.get_master_account() + {:ok, _} = AccountUser.link(account.uuid, user.uuid, %System{}) + + timestamp = DateTime.utc_now() + + response = + provider_request("/user.enable_or_disable", %{ + id: user.id, + enabled: false + }) + + assert response["success"] == true + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: get_test_key(), + target: user, + changes: %{ + "enabled" => false + }, + encrypted_changes: %{} + ) + end end end diff --git a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/wallet_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/wallet_controller_test.exs index 3dee82811..573dcad36 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/wallet_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/wallet_controller_test.exs @@ -500,5 +500,40 @@ defmodule AdminAPI.V1.ProviderAuth.WalletControllerTest do length = Wallet |> Repo.all() |> length() assert length == 3 end + + test "generates an activity log" do + account = insert(:account) + assert Wallet |> Repo.all() |> length() == 3 + + timestamp = DateTime.utc_now() + + response = + provider_request("/wallet.create", %{ + name: "MyWallet", + identifier: "secondary", + account_id: account.id + }) + + assert response["success"] == true + + wallet = Wallet.get(response["data"]["address"]) + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: get_test_key(), + target: wallet, + changes: %{ + "name" => "MyWallet", + "identifier" => wallet.identifier, + "account_uuid" => account.uuid + }, + encrypted_changes: %{} + ) + end end end diff --git a/apps/ewallet/priv/repo/report_minimum.exs b/apps/ewallet/priv/repo/report_minimum.exs index 19c0cbc7f..56a8ebc28 100644 --- a/apps/ewallet/priv/repo/report_minimum.exs +++ b/apps/ewallet/priv/repo/report_minimum.exs @@ -1,6 +1,6 @@ alias EWallet.CLI alias EWalletConfig.Config -alias EWalletDB.AuthToken +alias EWalletDB.{AuthToken, Seeder} # :prod environment does not have a default :base_url value and should not have one. # But we have a fallback value here so we can generate a friendly output message for seeding. @@ -15,7 +15,7 @@ admin_api_swagger_ui_url = base_url <> "/api/admin/docs" api_key_id = Application.get_env(:ewallet, :seed_admin_api_key).id api_key = Application.get_env(:ewallet, :seed_admin_api_key).key admin = Application.get_env(:ewallet, :seed_admin_user) -{:ok, admin_auth_token} = AuthToken.generate(admin, :admin_api) +{:ok, admin_auth_token} = AuthToken.generate(admin, :admin_api, %Seeder{}) # Output the seeding result CLI.heading("Setting up the OmiseGO eWallet Server") diff --git a/apps/ewallet_api/lib/ewallet_api/v1/controllers/auth_controller.ex b/apps/ewallet_api/lib/ewallet_api/v1/controllers/auth_controller.ex index 9de5110f9..f1746222b 100644 --- a/apps/ewallet_api/lib/ewallet_api/v1/controllers/auth_controller.ex +++ b/apps/ewallet_api/lib/ewallet_api/v1/controllers/auth_controller.ex @@ -2,7 +2,7 @@ defmodule EWalletAPI.V1.AuthController do use EWalletAPI, :controller import EWalletAPI.V1.ErrorHandler alias EWalletAPI.V1.{ClientAuthPlug, EndUserAuthenticator} - alias EWallet.Web.{Orchestrator, V1.AuthTokenOverlay} + alias EWallet.Web.{Orchestrator, Originator, V1.AuthTokenOverlay} alias EWalletDB.{AuthToken, User} @doc """ @@ -18,7 +18,8 @@ defmodule EWalletAPI.V1.AuthController do true <- conn.assigns.authenticated || {:error, :invalid_login_credentials}, true <- User.get_status(conn.assigns.end_user) == :active || {:error, :email_not_verified}, - {:ok, auth_token} <- AuthToken.generate(conn.assigns.end_user, :ewallet_api), + originator <- Originator.extract(conn.assigns), + {:ok, auth_token} <- AuthToken.generate(conn.assigns.end_user, :ewallet_api, originator), {:ok, auth_token} = Orchestrator.one(auth_token, AuthTokenOverlay, attrs) do render(conn, :auth_token, %{auth_token: auth_token}) else diff --git a/apps/ewallet_api/test/ewallet_api/v1/controllers/auth_controller_test.exs b/apps/ewallet_api/test/ewallet_api/v1/controllers/auth_controller_test.exs index fac3f47ff..8341cf097 100644 --- a/apps/ewallet_api/test/ewallet_api/v1/controllers/auth_controller_test.exs +++ b/apps/ewallet_api/test/ewallet_api/v1/controllers/auth_controller_test.exs @@ -1,7 +1,7 @@ defmodule EWalletAPI.V1.AuthControllerTest do use EWalletAPI.ConnCase, async: true alias Utils.Helpers.Crypto - alias EWalletDB.User + alias EWalletDB.{User, AuthToken} describe "/user.login" do setup do @@ -105,6 +105,32 @@ defmodule EWalletAPI.V1.AuthControllerTest do assert response["data"]["description"] == "Invalid parameter provided. `password` can't be blank." end + + test "generates an activity log", context do + timestamp = DateTime.utc_now() + + response = client_request("/user.login", context.request_data) + + assert response["success"] == true + auth_token = get_last_inserted(AuthToken) + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: context.user, + target: auth_token, + changes: %{ + "owner_app" => "ewallet_api", + "token" => auth_token.token, + "user_uuid" => context.user.uuid + }, + encrypted_changes: %{} + ) + end end describe "/me.logout" do @@ -115,5 +141,30 @@ defmodule EWalletAPI.V1.AuthControllerTest do assert response["success"] == true assert response["data"] == %{} end + + test "generates an activity log" do + timestamp = DateTime.utc_now() + + response = client_request("/me.logout") + + assert response["success"] == true + user = get_test_user() + auth_token = get_last_inserted(AuthToken) + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 1 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: user, + target: auth_token, + changes: %{ + "expired" => true + }, + encrypted_changes: %{} + ) + end end end diff --git a/apps/ewallet_api/test/ewallet_api/v1/controllers/signup_controller_test.exs b/apps/ewallet_api/test/ewallet_api/v1/controllers/signup_controller_test.exs index f56c7b3e6..e19950e1d 100644 --- a/apps/ewallet_api/test/ewallet_api/v1/controllers/signup_controller_test.exs +++ b/apps/ewallet_api/test/ewallet_api/v1/controllers/signup_controller_test.exs @@ -4,7 +4,7 @@ defmodule EWalletAPI.V1.SignupControllerTest do alias EWallet.Web.Preloader alias EWalletAPI.V1.VerifyEmailController alias EWallet.VerificationEmail - alias EWalletDB.{Invite, User} + alias EWalletDB.{Invite, User, AccountUser, Repo} alias ActivityLogger.System describe "/user.signup" do @@ -98,6 +98,90 @@ defmodule EWalletAPI.V1.SignupControllerTest do assert response["data"]["code"] == "user:passwords_mismatch" assert response["data"]["description"] == "The provided passwords do not match." end + + test "generates an activity log" do + timestamp = DateTime.utc_now() + + response = + client_request("/user.signup", %{ + email: "test_user_signup@example.com", + password: "the_password", + password_confirmation: "the_password" + }) + + assert response["success"] == true + + logs = get_all_activity_logs_since(timestamp) + user = get_last_inserted(User) + invite = get_last_inserted(Invite) + wallet = User.get_primary_wallet(user) + account_user = get_last_inserted(AccountUser) + assert Enum.count(logs) == 5 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: user, + target: wallet, + changes: %{ + "identifier" => "primary", + "name" => "primary", + "user_uuid" => user.uuid + }, + encrypted_changes: %{} + ) + + logs + |> Enum.at(1) + |> assert_activity_log( + action: "insert", + originator: user, + target: user, + changes: %{ + "email" => "test_user_signup@example.com", + "password_hash" => user.password_hash + }, + encrypted_changes: %{} + ) + + logs + |> Enum.at(2) + |> assert_activity_log( + action: "update", + originator: invite, + target: user, + changes: %{"invite_uuid" => invite.uuid}, + encrypted_changes: %{} + ) + + logs + |> Enum.at(3) + |> assert_activity_log( + action: "insert", + originator: user, + target: invite, + changes: %{ + "success_url" => "http://localhost:4000/pages/client/v1/verify_email/success", + "token" => invite.token, + "user_uuid" => user.uuid + }, + encrypted_changes: %{} + ) + + logs + |> Enum.at(4) + |> assert_activity_log( + action: "insert", + originator: user, + target: account_user, + changes: %{ + "account_uuid" => account_user.account_uuid, + "user_uuid" => user.uuid + }, + encrypted_changes: %{} + ) + end end describe "verify_email/2" do @@ -109,7 +193,8 @@ defmodule EWalletAPI.V1.SignupControllerTest do %{ user: invite.user, - token: invite.token + token: invite.token, + invite: invite } end @@ -160,5 +245,42 @@ defmodule EWalletAPI.V1.SignupControllerTest do assert response["data"]["description"] == "There is no pending email verification for the provided email and token." end + + test "generates an activity log", context do + timestamp = DateTime.utc_now() + + response = + client_request("/user.verify_email", %{ + email: context.user.email, + token: context.token + }) + + assert response["success"] == true + + logs = get_all_activity_logs_since(timestamp) + + invite = Repo.get_by(Invite, uuid: context.invite.uuid) + assert Enum.count(logs) == 2 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "update", + originator: context.user, + target: context.user, + changes: %{"invite_uuid" => nil}, + encrypted_changes: %{} + ) + + logs + |> Enum.at(1) + |> assert_activity_log( + action: "update", + originator: context.user, + target: invite, + changes: %{"verified_at" => NaiveDateTime.to_iso8601(invite.verified_at)}, + encrypted_changes: %{} + ) + end end end diff --git a/apps/ewallet_api/test/ewallet_api/v1/controllers/transaction_consumption_controller_test.exs b/apps/ewallet_api/test/ewallet_api/v1/controllers/transaction_consumption_controller_test.exs index 8c3e5a711..5c0fd5ede 100644 --- a/apps/ewallet_api/test/ewallet_api/v1/controllers/transaction_consumption_controller_test.exs +++ b/apps/ewallet_api/test/ewallet_api/v1/controllers/transaction_consumption_controller_test.exs @@ -2,7 +2,7 @@ defmodule EWalletAPI.V1.TransactionConsumptionControllerTest do use EWalletAPI.ConnCase, async: true alias EWallet.TestEndpoint alias EWallet.Web.{Date, Orchestrator} - alias EWalletDB.{Account, Repo, Transaction, TransactionConsumption, User} + alias EWalletDB.{Account, Repo, Transaction, TransactionConsumption, User, TransactionRequest} alias Phoenix.Socket.Broadcast alias EWallet.Web.V1.{ @@ -608,5 +608,167 @@ defmodule EWalletAPI.V1.TransactionConsumptionControllerTest do assert response["success"] == false end + + test "generates an activity log", meta do + transaction_request = + insert( + :transaction_request, + allow_amount_override: false, + amount: 1 * meta.token.subunit_to_unit, + correlation_id: "tBUI9WCJ", + encrypted_metadata: %{"a_key" => "a_value"}, + metadata: %{"a_key" => "a_value"}, + require_confirmation: false, + token_uuid: meta.token.uuid, + user_uuid: meta.alice.uuid, + wallet: meta.alice_wallet, + type: "send" + ) + + set_initial_balance(%{ + address: meta.alice_wallet.address, + token: meta.token, + amount: 150_000 + }) + + timestamp = DateTime.utc_now() + + response = + client_request("/me.consume_transaction_request", %{ + address: nil, + encrypted_metadata: %{"a_key" => "a_value"}, + formatted_transaction_request_id: transaction_request.id, + idempotency_token: "JXcTFKJK", + metadata: %{"a_key" => "a_value"}, + token_id: meta.token.id + }) + + assert response["success"] == true + + transaction_request = TransactionRequest.get(transaction_request.id) + transaction_consumption = TransactionConsumption.get(response["data"]["id"]) + transaction = get_last_inserted(Transaction) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 7 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: meta.bob, + target: transaction_consumption, + changes: %{ + "estimated_at" => NaiveDateTime.to_iso8601(transaction_consumption.estimated_at), + "estimated_consumption_amount" => 100, + "estimated_rate" => 1.0, + "estimated_request_amount" => 100, + "idempotency_token" => "JXcTFKJK", + "token_uuid" => meta.token.uuid, + "transaction_request_uuid" => transaction_request.uuid, + "wallet_address" => meta.bob_wallet.address, + "metadata" => %{"a_key" => "a_value"}, + "user_uuid" => meta.bob.uuid + }, + encrypted_changes: %{"encrypted_metadata" => %{"a_key" => "a_value"}} + ) + + logs + |> Enum.at(1) + |> assert_activity_log( + action: "update", + originator: :system, + target: transaction_consumption, + changes: %{ + "approved_at" => NaiveDateTime.to_iso8601(transaction_consumption.approved_at), + "status" => "approved" + }, + encrypted_changes: %{} + ) + + logs + |> Enum.at(2) + |> assert_activity_log( + action: "insert", + originator: transaction_consumption, + target: transaction, + changes: %{ + "calculated_at" => NaiveDateTime.to_iso8601(transaction.calculated_at), + "from" => meta.alice_wallet.address, + "from_user_uuid" => meta.alice.uuid, + "from_amount" => 100, + "from_token_uuid" => meta.token.uuid, + "idempotency_token" => "JXcTFKJK", + "rate" => 1.0, + "to" => meta.bob_wallet.address, + "to_amount" => 100, + "to_token_uuid" => meta.token.uuid, + "to_user_uuid" => meta.bob.uuid, + "metadata" => %{"a_key" => "a_value"} + }, + encrypted_changes: %{ + "payload" => %{ + "encrypted_metadata" => %{"a_key" => "a_value"}, + "exchange_account_id" => nil, + "exchange_wallet_address" => nil, + "from_address" => meta.alice_wallet.address, + "from_amount" => 100, + "from_token_id" => meta.token.id, + "idempotency_token" => "JXcTFKJK", + "metadata" => %{"a_key" => "a_value"}, + "to_address" => meta.bob_wallet.address, + "to_amount" => 100, + "to_token_id" => meta.token.id + }, + "encrypted_metadata" => %{"a_key" => "a_value"} + } + ) + + logs + |> Enum.at(3) + |> assert_activity_log( + action: "update", + originator: :system, + target: transaction, + changes: %{ + "local_ledger_uuid" => transaction.local_ledger_uuid, + "status" => "confirmed" + }, + encrypted_changes: %{} + ) + + logs + |> Enum.at(4) + |> assert_activity_log( + action: "update", + originator: transaction, + target: transaction_consumption, + changes: %{ + "confirmed_at" => NaiveDateTime.to_iso8601(transaction_consumption.confirmed_at), + "status" => "confirmed", + "transaction_uuid" => transaction.uuid + }, + encrypted_changes: %{} + ) + + logs + |> Enum.at(5) + |> assert_activity_log( + action: "update", + originator: :system, + target: transaction_request, + changes: %{"consumptions_count" => 1}, + encrypted_changes: %{} + ) + + logs + |> Enum.at(6) + |> assert_activity_log( + action: "update", + originator: :system, + target: transaction_request, + changes: %{"updated_at" => NaiveDateTime.to_iso8601(transaction_request.updated_at)}, + encrypted_changes: %{} + ) + end end end diff --git a/apps/ewallet_api/test/ewallet_api/v1/controllers/transaction_controller_test.exs b/apps/ewallet_api/test/ewallet_api/v1/controllers/transaction_controller_test.exs index 78d292573..91e82d705 100644 --- a/apps/ewallet_api/test/ewallet_api/v1/controllers/transaction_controller_test.exs +++ b/apps/ewallet_api/test/ewallet_api/v1/controllers/transaction_controller_test.exs @@ -521,6 +521,83 @@ defmodule EWalletAPI.V1.TransactionControllerTest do } } end + + test "generates an activity log" do + user_1 = get_test_user() + {:ok, user_2} = :user |> params_for() |> User.insert() + wallet_1 = User.get_primary_wallet(user_1) + wallet_2 = User.get_primary_wallet(user_2) + + token = insert(:token) + + set_initial_balance(%{ + address: wallet_1.address, + token: token, + amount: 200_000 + }) + + timestamp = DateTime.utc_now() + + response = + client_request("/me.create_transaction", %{ + idempotency_token: UUID.generate(), + from_address: wallet_1.address, + to_address: wallet_2.address, + token_id: token.id, + amount: 100 * token.subunit_to_unit, + metadata: %{something: "interesting"}, + encrypted_metadata: %{something: "secret"} + }) + + assert response["success"] == true + + transaction = Transaction.get(response["data"]["id"]) + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 2 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: user_1, + target: transaction, + changes: %{ + "from" => wallet_1.address, + "from_amount" => 10_000, + "from_token_uuid" => token.uuid, + "from_user_uuid" => user_1.uuid, + "idempotency_token" => transaction.idempotency_token, + "metadata" => %{"something" => "interesting"}, + "to" => wallet_2.address, + "to_amount" => 10_000, + "to_token_uuid" => token.uuid, + "to_user_uuid" => user_2.uuid + }, + encrypted_changes: %{ + "encrypted_metadata" => %{"something" => "secret"}, + "payload" => %{ + "amount" => 10_000, + "encrypted_metadata" => %{"something" => "secret"}, + "from_address" => wallet_1.address, + "from_user_id" => user_1.id, + "idempotency_token" => transaction.idempotency_token, + "metadata" => %{"something" => "interesting"}, + "to_address" => wallet_2.address, + "token_id" => token.id + } + } + ) + + logs + |> Enum.at(1) + |> assert_activity_log( + action: "update", + originator: :system, + target: transaction, + changes: %{"local_ledger_uuid" => transaction.local_ledger_uuid, "status" => "confirmed"}, + encrypted_changes: %{} + ) + end end describe "/me.create_transaction with exchange" do diff --git a/apps/ewallet_api/test/ewallet_api/v1/controllers/transaction_request_controller_test.exs b/apps/ewallet_api/test/ewallet_api/v1/controllers/transaction_request_controller_test.exs index d663f6430..3162ddec4 100644 --- a/apps/ewallet_api/test/ewallet_api/v1/controllers/transaction_request_controller_test.exs +++ b/apps/ewallet_api/test/ewallet_api/v1/controllers/transaction_request_controller_test.exs @@ -264,6 +264,52 @@ defmodule EWalletAPI.V1.TransactionRequestControllerTest do } } end + + test "generates an activity log" do + user = get_test_user() + token = insert(:token) + wallet = User.get_primary_wallet(user) + + timestamp = DateTime.utc_now() + + response = + client_request("/me.create_transaction_request", %{ + type: "send", + token_id: token.id + }) + + assert response["success"] == true + + request = TransactionRequest.get(response["data"]["id"]) + + logs = get_all_activity_logs_since(timestamp) + assert Enum.count(logs) == 2 + + logs + |> Enum.at(0) + |> assert_activity_log( + action: "insert", + originator: user, + target: request, + changes: %{ + "token_uuid" => token.uuid, + "type" => "send", + "user_uuid" => user.uuid, + "wallet_address" => wallet.address + }, + encrypted_changes: %{} + ) + + logs + |> Enum.at(1) + |> assert_activity_log( + action: "update", + originator: :system, + target: request, + changes: %{"consumptions_count" => 0}, + encrypted_changes: %{} + ) + end end describe "/me.get_transaction_request" do diff --git a/apps/ewallet_db/lib/ewallet_db/account.ex b/apps/ewallet_db/lib/ewallet_db/account.ex index 84b4c9b1a..bd43e5388 100644 --- a/apps/ewallet_db/lib/ewallet_db/account.ex +++ b/apps/ewallet_db/lib/ewallet_db/account.ex @@ -44,7 +44,7 @@ defmodule EWalletDB.Account do field(:relative_depth, :integer, virtual: true) field(:avatar, EWalletDB.Uploaders.Avatar.Type) field(:metadata, :map, default: %{}) - field(:encrypted_metadata, EWalletConfig.Encrypted.Map, default: %{}) + field(:encrypted_metadata, EWalletDB.Encrypted.Map, default: %{}) many_to_many( :categories, diff --git a/apps/ewallet_db/lib/ewallet_db/auth_token.ex b/apps/ewallet_db/lib/ewallet_db/auth_token.ex index 30f2763ab..43e1a3d5d 100644 --- a/apps/ewallet_db/lib/ewallet_db/auth_token.ex +++ b/apps/ewallet_db/lib/ewallet_db/auth_token.ex @@ -85,7 +85,7 @@ defmodule EWalletDB.AuthToken do Generate an auth token for the specified user, then returns the auth token string. """ - def generate(%User{} = user, owner_app) when is_atom(owner_app) do + def generate(%User{} = user, owner_app, originator) when is_atom(owner_app) do account = User.get_account(user) attrs = %{ @@ -93,13 +93,13 @@ defmodule EWalletDB.AuthToken do user_uuid: user.uuid, account_uuid: if(account, do: account.uuid, else: nil), token: Crypto.generate_base64_key(@key_length), - originator: user + originator: originator } insert(attrs) end - def generate(_, _), do: {:error, :invalid_parameter} + def generate(_, _, _), do: {:error, :invalid_parameter} @doc """ Retrieves an auth token using the specified token. diff --git a/apps/ewallet_db/lib/ewallet_db/token.ex b/apps/ewallet_db/lib/ewallet_db/token.ex index 9e3f4b454..b1abd7a7b 100644 --- a/apps/ewallet_db/lib/ewallet_db/token.ex +++ b/apps/ewallet_db/lib/ewallet_db/token.ex @@ -43,7 +43,7 @@ defmodule EWalletDB.Token do # false field(:locked, :boolean) field(:metadata, :map, default: %{}) - field(:encrypted_metadata, EWalletConfig.Encrypted.Map, default: %{}) + field(:encrypted_metadata, EWalletDB.Encrypted.Map, default: %{}) field(:enabled, :boolean) diff --git a/apps/ewallet_db/lib/ewallet_db/transaction.ex b/apps/ewallet_db/lib/ewallet_db/transaction.ex index 70ecf1c21..53d9f5902 100644 --- a/apps/ewallet_db/lib/ewallet_db/transaction.ex +++ b/apps/ewallet_db/lib/ewallet_db/transaction.ex @@ -38,7 +38,7 @@ defmodule EWalletDB.Transaction do # internal / external field(:type, :string, default: @internal) # Payload received from client - field(:payload, EWalletConfig.Encrypted.Map) + field(:payload, EWalletDB.Encrypted.Map) # Response returned by ledger field(:local_ledger_uuid, :string) field(:error_code, :string) @@ -49,7 +49,7 @@ defmodule EWalletDB.Transaction do field(:calculated_at, :naive_datetime) field(:metadata, :map, default: %{}) - field(:encrypted_metadata, EWalletConfig.Encrypted.Map, default: %{}) + field(:encrypted_metadata, EWalletDB.Encrypted.Map, default: %{}) belongs_to( :from_token, @@ -185,7 +185,7 @@ defmodule EWalletDB.Transaction do :to, :from ], - encrypted: [:encrypted_metadata] + encrypted: [:encrypted_metadata, :payload] ) |> validate_number(:from_amount, less_than: 100_000_000_000_000_000_000_000_000_000_000_000) |> validate_number(:to_amount, less_than: 100_000_000_000_000_000_000_000_000_000_000_000) diff --git a/apps/ewallet_db/lib/ewallet_db/transaction_request.ex b/apps/ewallet_db/lib/ewallet_db/transaction_request.ex index 380b28ed0..649f31864 100644 --- a/apps/ewallet_db/lib/ewallet_db/transaction_request.ex +++ b/apps/ewallet_db/lib/ewallet_db/transaction_request.ex @@ -50,7 +50,7 @@ defmodule EWalletDB.TransactionRequest do field(:expiration_reason, :string) field(:allow_amount_override, :boolean, default: true) field(:metadata, :map, default: %{}) - field(:encrypted_metadata, EWalletConfig.Encrypted.Map, default: %{}) + field(:encrypted_metadata, EWalletDB.Encrypted.Map, default: %{}) has_many( :consumptions, diff --git a/apps/ewallet_db/lib/ewallet_db/types/map.ex b/apps/ewallet_db/lib/ewallet_db/types/map.ex new file mode 100644 index 000000000..8862dd203 --- /dev/null +++ b/apps/ewallet_db/lib/ewallet_db/types/map.ex @@ -0,0 +1,5 @@ +defmodule EWalletDB.Encrypted.Map do + @moduledoc false + + use Cloak.Fields.Map, vault: EWalletDB.Vault +end diff --git a/apps/ewallet_db/lib/ewallet_db/user.ex b/apps/ewallet_db/lib/ewallet_db/user.ex index aacef6cfa..8b5330671 100644 --- a/apps/ewallet_db/lib/ewallet_db/user.ex +++ b/apps/ewallet_db/lib/ewallet_db/user.ex @@ -39,7 +39,7 @@ defmodule EWalletDB.User do field(:password_hash, :string) field(:provider_user_id, :string) field(:metadata, :map, default: %{}) - field(:encrypted_metadata, EWalletConfig.Encrypted.Map, default: %{}) + field(:encrypted_metadata, EWalletDB.Encrypted.Map, default: %{}) field(:avatar, EWalletDB.Uploaders.Avatar.Type) field(:enabled, :boolean, default: true) diff --git a/apps/ewallet_db/lib/ewallet_db/wallet.ex b/apps/ewallet_db/lib/ewallet_db/wallet.ex index 8632492c7..ac41cf635 100644 --- a/apps/ewallet_db/lib/ewallet_db/wallet.ex +++ b/apps/ewallet_db/lib/ewallet_db/wallet.ex @@ -45,7 +45,7 @@ defmodule EWalletDB.Wallet do field(:name, :string) field(:identifier, :string) field(:metadata, :map, default: %{}) - field(:encrypted_metadata, EWalletConfig.Encrypted.Map, default: %{}) + field(:encrypted_metadata, EWalletDB.Encrypted.Map, default: %{}) field(:enabled, :boolean) activity_logging() diff --git a/apps/ewallet_db/priv/repo/seeds/02_auth_token.exs b/apps/ewallet_db/priv/repo/seeds/02_auth_token.exs index dff0c127b..2437cdeb7 100644 --- a/apps/ewallet_db/priv/repo/seeds/02_auth_token.exs +++ b/apps/ewallet_db/priv/repo/seeds/02_auth_token.exs @@ -1,6 +1,6 @@ defmodule EWalletDB.Repo.Seeds.AuthTokenSeed do alias EWallet.Web.Preloader - alias EWalletDB.{AuthToken, User} + alias EWalletDB.{AuthToken, User, Seeder} def seed do [ @@ -13,7 +13,7 @@ defmodule EWalletDB.Repo.Seeds.AuthTokenSeed do user = User.get_by_email(args[:admin_email]) owner_app = :admin_api - case AuthToken.generate(user, owner_app) do + case AuthToken.generate(user, owner_app, %Seeder{}) do {:ok, token} -> {:ok, token} = Preloader.preload_one(token, :user) diff --git a/apps/ewallet_db/priv/repo/seeds_sample/01_auth_token.exs b/apps/ewallet_db/priv/repo/seeds_sample/01_auth_token.exs index c3a2a5e6e..e38729c91 100644 --- a/apps/ewallet_db/priv/repo/seeds_sample/01_auth_token.exs +++ b/apps/ewallet_db/priv/repo/seeds_sample/01_auth_token.exs @@ -1,6 +1,6 @@ defmodule EWalletDB.Repo.Seeds.AuthTokenSampleSeed do alias EWallet.Web.Preloader - alias EWalletDB.{AuthToken, User} + alias EWalletDB.{AuthToken, User, Seeder} def seed do [ @@ -12,7 +12,7 @@ defmodule EWalletDB.Repo.Seeds.AuthTokenSampleSeed do def run(writer, args) do user = User.get_by_provider_user_id("provider_user_id01") - case AuthToken.generate(user, :ewallet_api) do + case AuthToken.generate(user, :ewallet_api, %Seeder{}) do {:ok, token} -> {:ok, token} = Preloader.preload_one(token, :user) diff --git a/apps/ewallet_db/test/ewallet_db/auth_token_test.exs b/apps/ewallet_db/test/ewallet_db/auth_token_test.exs index 521230c67..75d5832f7 100644 --- a/apps/ewallet_db/test/ewallet_db/auth_token_test.exs +++ b/apps/ewallet_db/test/ewallet_db/auth_token_test.exs @@ -12,7 +12,7 @@ defmodule EWalletDB.AuthTokenTest do role = insert(:role, name: "admin") {:ok, _} = Membership.assign(user, account, role, %System{}) - {res, auth_token} = AuthToken.generate(user, @owner_app) + {res, auth_token} = AuthToken.generate(user, @owner_app, %System{}) assert res == :ok assert String.length(auth_token.token) == 43 @@ -20,7 +20,7 @@ defmodule EWalletDB.AuthTokenTest do test "returns error if user is invalid" do account = insert(:account) - {res, reason} = AuthToken.generate(account, @owner_app) + {res, reason} = AuthToken.generate(account, @owner_app, %System{}) assert res == :error assert reason == :invalid_parameter @@ -32,8 +32,8 @@ defmodule EWalletDB.AuthTokenTest do role = insert(:role, name: "admin") {:ok, _} = Membership.assign(user, account, role, %System{}) - {:ok, token1} = AuthToken.generate(user, @owner_app) - {:ok, token2} = AuthToken.generate(user, @owner_app) + {:ok, token1} = AuthToken.generate(user, @owner_app, %System{}) + {:ok, token2} = AuthToken.generate(user, @owner_app, %System{}) token_count = user @@ -52,7 +52,7 @@ defmodule EWalletDB.AuthTokenTest do account = insert(:account) role = insert(:role, name: "admin") {:ok, _} = Membership.assign(user, account, role, %System{}) - {:ok, auth_token} = AuthToken.generate(user, @owner_app) + {:ok, auth_token} = AuthToken.generate(user, @owner_app, %System{}) auth_user = AuthToken.authenticate(auth_token.token, @owner_app) assert auth_user.uuid == user.uuid @@ -92,7 +92,7 @@ defmodule EWalletDB.AuthTokenTest do role = insert(:role, name: "admin") {:ok, _} = Membership.assign(user, account, role, %System{}) - {:ok, auth_token} = AuthToken.generate(user, @owner_app) + {:ok, auth_token} = AuthToken.generate(user, @owner_app, %System{}) auth_user = AuthToken.authenticate(user.id, auth_token.token, @owner_app) assert auth_user.uuid == user.uuid @@ -103,8 +103,8 @@ defmodule EWalletDB.AuthTokenTest do account = insert(:account) role = insert(:role, name: "admin") {:ok, _} = Membership.assign(user, account, role, %System{}) - {:ok, token1} = AuthToken.generate(user, @owner_app) - {:ok, token2} = AuthToken.generate(user, @owner_app) + {:ok, token1} = AuthToken.generate(user, @owner_app, %System{}) + {:ok, token2} = AuthToken.generate(user, @owner_app, %System{}) assert AuthToken.authenticate(user.id, token1.token, @owner_app) assert AuthToken.authenticate(user.id, token2.token, @owner_app) @@ -122,7 +122,7 @@ defmodule EWalletDB.AuthTokenTest do account = insert(:account) role = insert(:role, name: "admin") {:ok, _} = Membership.assign(user, account, role, %System{}) - {:ok, auth_token} = AuthToken.generate(user, @owner_app) + {:ok, auth_token} = AuthToken.generate(user, @owner_app, %System{}) another_user = insert(:admin) assert AuthToken.authenticate(another_user.id, auth_token.token, @owner_app) == false @@ -134,7 +134,7 @@ defmodule EWalletDB.AuthTokenTest do role = insert(:role, name: "admin") {:ok, _} = Membership.assign(user, account, role, %System{}) - {:ok, auth_token} = AuthToken.generate(user, :different_app) + {:ok, auth_token} = AuthToken.generate(user, :different_app, %System{}) assert AuthToken.authenticate(user.id, auth_token.token, @owner_app) == false end @@ -144,7 +144,7 @@ defmodule EWalletDB.AuthTokenTest do account = insert(:account) role = insert(:role, name: "admin") {:ok, _} = Membership.assign(user, account, role, %System{}) - {:ok, _} = AuthToken.generate(user, @owner_app) + {:ok, _} = AuthToken.generate(user, @owner_app, %System{}) assert AuthToken.authenticate(user.id, "unmatched", @owner_app) == false end @@ -154,7 +154,7 @@ defmodule EWalletDB.AuthTokenTest do account = insert(:account) role = insert(:role, name: "admin") {:ok, _} = Membership.assign(user, account, role, %System{}) - {:ok, _} = AuthToken.generate(user, @owner_app) + {:ok, _} = AuthToken.generate(user, @owner_app, %System{}) assert AuthToken.authenticate(user.id, nil, @owner_app) == false end @@ -189,9 +189,9 @@ defmodule EWalletDB.AuthTokenTest do role = insert(:role, name: "admin") {:ok, _} = Membership.assign(user, account, role, %System{}) - {:ok, token1} = AuthToken.generate(user, @owner_app) + {:ok, token1} = AuthToken.generate(user, @owner_app, %System{}) token1_string = token1.token - {:ok, token2} = AuthToken.generate(user, @owner_app) + {:ok, token2} = AuthToken.generate(user, @owner_app, %System{}) token2_string = token2.token # Ensure tokens are usable. @@ -208,9 +208,9 @@ defmodule EWalletDB.AuthTokenTest do role = insert(:role, name: "admin") {:ok, _} = Membership.assign(user, account, role, %System{}) - {:ok, token1} = AuthToken.generate(user, @owner_app) + {:ok, token1} = AuthToken.generate(user, @owner_app, %System{}) token1_string = token1.token - {:ok, token2} = AuthToken.generate(user, @owner_app) + {:ok, token2} = AuthToken.generate(user, @owner_app, %System{}) token2_string = token2.token # Ensure tokens are usable. From a535a8f2ee8cfc5d15392039adb7f540fedb639f Mon Sep 17 00:00:00 2001 From: mederic Date: Thu, 13 Dec 2018 14:30:13 +0700 Subject: [PATCH 18/23] Fix PR comments --- .../lib/activity_logger/activity_log.ex | 15 ++++--- .../lib/activity_logger/activity_repo.ex | 2 +- .../migrations/20180907173648_add_audit.exs | 1 + ...prefix_from_adt_to_log_in_activity_log.exs | 41 +++++++++++++++++++ .../lib/ewallet_config/config.ex | 12 +++--- .../lib/ewallet_config/setting.ex | 8 ++-- apps/utils/lib/utils.ex | 18 -------- apps/utils/test/utils_test.exs | 8 ---- 8 files changed, 60 insertions(+), 45 deletions(-) create mode 100644 apps/activity_logger/priv/repo/migrations/20181213064648_rename_id_prefix_from_adt_to_log_in_activity_log.exs delete mode 100644 apps/utils/lib/utils.ex delete mode 100644 apps/utils/test/utils_test.exs diff --git a/apps/activity_logger/lib/activity_logger/activity_log.ex b/apps/activity_logger/lib/activity_logger/activity_log.ex index e45c101c6..922691be1 100644 --- a/apps/activity_logger/lib/activity_logger/activity_log.ex +++ b/apps/activity_logger/lib/activity_logger/activity_log.ex @@ -12,11 +12,10 @@ defmodule ActivityLogger.ActivityLog do Repo } - @primary_key {:uuid, UUID, autogenerate: true} @primary_key {:uuid, UUID, autogenerate: true} schema "activity_log" do - external_id(prefix: "adt_") + external_id(prefix: "log_") field(:action, :string) @@ -57,13 +56,13 @@ defmodule ActivityLogger.ActivityLog do ]) end - @spec get_schema(String.t()) :: Atom.t() + @spec get_schema(String.t()) :: atom() def get_schema(type) do config = Application.get_env(:activity_logger, :activity_log_types_to_schemas) Map.fetch!(config, type) end - @spec get_type(Atom.t()) :: String.t() + @spec get_type(atom()) :: String.t() def get_type(schema) do config = Application.get_env(:activity_logger, :schemas_to_activity_log_types) Map.fetch!(config, schema) @@ -82,7 +81,7 @@ defmodule ActivityLogger.ActivityLog do |> Repo.all() end - @spec all_for_target(Atom.t(), UUID.t()) :: [%ActivityLog{}] + @spec all_for_target(atom(), UUID.t()) :: [%ActivityLog{}] def all_for_target(schema, uuid) do schema |> get_type() @@ -164,7 +163,7 @@ defmodule ActivityLogger.ActivityLog do defp remove_forbidden(changes, prevent_saving) do changes - |> Enum.filter(fn {key, value} -> !Enum.member?(prevent_saving, key) end) + |> Enum.filter(fn {key, _} -> !Enum.member?(prevent_saving, key) end) |> Enum.into(%{}) end @@ -177,7 +176,7 @@ defmodule ActivityLogger.ActivityLog do defp format_changes(changes, encrypted_fields) do changes - |> Enum.filter(fn {key, value} -> + |> Enum.filter(fn {key, _} -> !Enum.member?(encrypted_fields, key) end) |> format_changes(nil) @@ -191,7 +190,7 @@ defmodule ActivityLogger.ActivityLog do end defp format_change(field, value) do - {field, value} + {field, format_value(value)} end defp format_value(%Changeset{} = value) do diff --git a/apps/activity_logger/lib/activity_logger/activity_repo.ex b/apps/activity_logger/lib/activity_logger/activity_repo.ex index a73ec810f..ec83fd7e0 100644 --- a/apps/activity_logger/lib/activity_logger/activity_repo.ex +++ b/apps/activity_logger/lib/activity_logger/activity_repo.ex @@ -48,7 +48,7 @@ defmodule ActivityLogger.ActivityRepo do |> handle_perform_result(:delete, changeset) end - @spec perform(Atom.t(), %Changeset{}, Keyword.t(), Multi.t()) :: + @spec perform(atom(), %Changeset{}, Keyword.t(), Multi.t()) :: {:ok, any()} | {:error, any()} | {:error, :no_originator_given} diff --git a/apps/activity_logger/priv/repo/migrations/20180907173648_add_audit.exs b/apps/activity_logger/priv/repo/migrations/20180907173648_add_audit.exs index 38ba78561..2a87b3a54 100644 --- a/apps/activity_logger/priv/repo/migrations/20180907173648_add_audit.exs +++ b/apps/activity_logger/priv/repo/migrations/20180907173648_add_audit.exs @@ -23,5 +23,6 @@ defmodule ActivityLogger.Repo.Migrations.AddAudit do create index(:audit, [:target_uuid, :target_type]) create index(:audit, [:originator_uuid, :originator_type]) + create unique_index(:activity_log, [:id]) end end diff --git a/apps/activity_logger/priv/repo/migrations/20181213064648_rename_id_prefix_from_adt_to_log_in_activity_log.exs b/apps/activity_logger/priv/repo/migrations/20181213064648_rename_id_prefix_from_adt_to_log_in_activity_log.exs new file mode 100644 index 000000000..1f86f9e0f --- /dev/null +++ b/apps/activity_logger/priv/repo/migrations/20181213064648_rename_id_prefix_from_adt_to_log_in_activity_log.exs @@ -0,0 +1,41 @@ +defmodule ActivityLogger.Repo.Migrations.RenameIdPrefixFromAdtToLogInActivityLog do + use Ecto.Migration + import Ecto.Query + alias ActivityLogger.Repo + + @table_name "activity_log" + @from_prefix "adt_" + @to_prefix "log_" + + defp get_all(table_name) do + query = from(t in table_name, + select: [t.uuid, t.id], + lock: "FOR UPDATE") + + Repo.all(query) + end + + def up do + for [uuid, id] <- get_all(@table_name) do + id + |> String.replace_prefix(@from_prefix, @to_prefix) + |> update_id(@table_name, uuid) + end + end + + def down do + for [uuid, id] <- get_all(@table_name) do + id + |> String.replace_prefix(@to_prefix, @from_prefix) + |> update_id(@table_name, uuid) + end + end + + defp update_id(id, table_name, uuid) do + query = from(t in table_name, + where: t.uuid == ^uuid, + update: [set: [id: ^id]]) + + Repo.update_all(query, []) + end +end diff --git a/apps/ewallet_config/lib/ewallet_config/config.ex b/apps/ewallet_config/lib/ewallet_config/config.ex index 5bf2818c6..f3028c30b 100644 --- a/apps/ewallet_config/lib/ewallet_config/config.ex +++ b/apps/ewallet_config/lib/ewallet_config/config.ex @@ -64,18 +64,18 @@ defmodule EWalletConfig.Config do {:reply, res, registered_apps} end - @spec handle_call(:reload, Atom.t(), [Atom.t()]) :: :ok + @spec handle_call(:reload, atom(), [atom()]) :: :ok def handle_call(:reload, _from, registered_apps) do reload_registered_apps(registered_apps) {:reply, :ok, registered_apps} end - @spec get_registered_apps(pid()) :: [{Atom.t(), [Atom.t()]}] + @spec get_registered_apps(pid()) :: [{atom(), [atom()]}] def get_registered_apps(pid \\ __MODULE__) do GenServer.call(pid, :get_registered_apps) end - @spec register_and_load(Atom.t(), [Atom.t()]) :: [{Atom.t(), [Atom.t()]}] + @spec register_and_load(atom(), [atom()]) :: [{atom(), [atom()]}] def register_and_load(app, settings, pid \\ __MODULE__) def register_and_load(app, settings, {name, node}) do @@ -86,7 +86,7 @@ defmodule EWalletConfig.Config do GenServer.call(pid, {:register_and_load, app, settings}) end - @spec reload_config(Atom.t()) :: :ok + @spec reload_config(atom()) :: :ok def reload_config(pid \\ __MODULE__) do GenServer.call(pid, :reload) trigger_nodes_reload() @@ -104,7 +104,7 @@ defmodule EWalletConfig.Config do end) end - @spec update(map(), Atom.t()) :: [{:ok, %Setting{}} | {:error, Atom.t()}] + @spec update(map(), atom()) :: [{:ok, %Setting{}} | {:error, atom()}] def update(attrs, pid \\ __MODULE__) do {config_pid, attrs} = get_config_pid(attrs) @@ -135,7 +135,7 @@ defmodule EWalletConfig.Config do defp get_config_pid(attrs), do: {nil, attrs} - @spec insert_all_defaults(map(), map(), Atom.t()) :: :ok + @spec insert_all_defaults(map(), map(), atom()) :: :ok def insert_all_defaults(originator, opts \\ %{}, pid \\ __MODULE__) do :ok = Setting.insert_all_defaults(originator, opts) reload_config(pid) diff --git a/apps/ewallet_config/lib/ewallet_config/setting.ex b/apps/ewallet_config/lib/ewallet_config/setting.ex index 556124041..437957ecf 100644 --- a/apps/ewallet_config/lib/ewallet_config/setting.ex +++ b/apps/ewallet_config/lib/ewallet_config/setting.ex @@ -88,7 +88,7 @@ defmodule EWalletConfig.Setting do @doc """ Retrieves a setting's value by its string name. """ - @spec get_value(String.t() | Atom.t()) :: any() + @spec get_value(String.t() | atom()) :: any() def get_value(key, default \\ nil) def get_value(key, default) when is_atom(key) do @@ -162,7 +162,7 @@ defmodule EWalletConfig.Setting do defp return_tx_result({:error, _}), do: {:error, :setting_insert_failed} @spec update(String.t(), map()) :: - {:ok, %Setting{}} | {:error, Atom.t()} | {:error, Changeset.t()} + {:ok, %Setting{}} | {:error, atom()} | {:error, Changeset.t()} def update(nil, _), do: {:error, :setting_not_found} def update(key, attrs) when is_atom(key) and is_map(attrs) do @@ -186,7 +186,7 @@ defmodule EWalletConfig.Setting do end end - @spec update_all(List.t()) :: [{:ok, %Setting{}} | {:error, Atom.t()} | {:error, Changeset.t()}] + @spec update_all(List.t()) :: [{:ok, %Setting{}} | {:error, atom()} | {:error, Changeset.t()}] def update_all(attrs) when is_list(attrs) do case Keyword.keyword?(attrs) do true -> update_all_with_keyword_list(attrs) @@ -194,7 +194,7 @@ defmodule EWalletConfig.Setting do end end - @spec update_all(map()) :: [{:ok, %Setting{}} | {:error, Atom.t()} | {:error, Changeset.t()}] + @spec update_all(map()) :: [{:ok, %Setting{}} | {:error, atom()} | {:error, Changeset.t()}] def update_all(attrs) do originator = attrs[:originator] diff --git a/apps/utils/lib/utils.ex b/apps/utils/lib/utils.ex deleted file mode 100644 index 2f1ab833d..000000000 --- a/apps/utils/lib/utils.ex +++ /dev/null @@ -1,18 +0,0 @@ -defmodule Utils do - @moduledoc """ - Documentation for Utils. - """ - - @doc """ - Hello world. - - ## Examples - - iex> Utils.hello - :world - - """ - def hello do - :world - end -end diff --git a/apps/utils/test/utils_test.exs b/apps/utils/test/utils_test.exs deleted file mode 100644 index 977b8ecb8..000000000 --- a/apps/utils/test/utils_test.exs +++ /dev/null @@ -1,8 +0,0 @@ -defmodule UtilsTest do - use ExUnit.Case - doctest Utils - - test "greets the world" do - assert Utils.hello() == :world - end -end From cb87f6a2f0ee05f8cdb4b6a3a82176e5221808b5 Mon Sep 17 00:00:00 2001 From: mederic Date: Fri, 14 Dec 2018 11:29:50 +0700 Subject: [PATCH 19/23] Remove EWalletConfig module dependency from ActivityLogger --- .../support/activity_logger_test_helper.ex | 19 ------------------- .../configuration_controller_test.exs | 4 ++-- .../configuration_controller_test.exs | 4 ++-- 3 files changed, 4 insertions(+), 23 deletions(-) diff --git a/apps/activity_logger/test/support/activity_logger_test_helper.ex b/apps/activity_logger/test/support/activity_logger_test_helper.ex index c80379bca..7e0b26585 100644 --- a/apps/activity_logger/test/support/activity_logger_test_helper.ex +++ b/apps/activity_logger/test/support/activity_logger_test_helper.ex @@ -5,25 +5,6 @@ defmodule ActivityLogger.ActivityLoggerTestHelper do use ExUnit.CaseTemplate import Ecto.Query alias ActivityLogger.{ActivityLog, System, Repo} - alias EWalletConfig.{Setting, StoredSetting} - - def assert_activity_log( - log, - action: action, - originator: originator, - target: %Setting{} = target, - changes: changes, - encrypted_changes: encrypted_changes - ) do - assert_activity_log( - log, - action: action, - originator: originator, - target: %StoredSetting{uuid: target.uuid}, - changes: changes, - encrypted_changes: encrypted_changes - ) - end def assert_activity_log( log, diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/configuration_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/configuration_controller_test.exs index dd6b3099a..a847107aa 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/configuration_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/configuration_controller_test.exs @@ -1,6 +1,6 @@ defmodule AdminAPI.V1.AdminAuth.ConfigurationControllerTest do use AdminAPI.ConnCase, async: true - alias EWalletConfig.Config + alias EWalletConfig.{Config, StoredSetting} describe "/configuration.get" do test "returns a list of settings and pagination data" do @@ -151,7 +151,7 @@ defmodule AdminAPI.V1.AdminAuth.ConfigurationControllerTest do |> assert_activity_log( action: "update", originator: get_test_admin(), - target: setting, + target: %StoredSetting{uuid: setting.uuid}, changes: %{"data" => %{"value" => "new_base_url.example"}, "position" => setting.position}, encrypted_changes: %{} ) diff --git a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/configuration_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/configuration_controller_test.exs index d0c283d44..6568f9157 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/configuration_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/configuration_controller_test.exs @@ -1,6 +1,6 @@ defmodule AdminAPI.V1.ProviderAuth.ConfigurationControllerTest do use AdminAPI.ConnCase, async: true - alias EWalletConfig.Config + alias EWalletConfig.{Config, StoredSetting} describe "/configuration.get" do test "returns a list of settings and pagination data" do @@ -151,7 +151,7 @@ defmodule AdminAPI.V1.ProviderAuth.ConfigurationControllerTest do |> assert_activity_log( action: "update", originator: get_test_key(), - target: setting, + target: %StoredSetting{uuid: setting.uuid}, changes: %{"data" => %{"value" => "new_base_url.example"}, "position" => setting.position}, encrypted_changes: %{} ) From b075b1e655ef44e0fa51b8b23f1459a94f4e41e4 Mon Sep 17 00:00:00 2001 From: mederic Date: Fri, 14 Dec 2018 11:41:20 +0700 Subject: [PATCH 20/23] Fix wrong table name in migration --- .../priv/repo/migrations/20180907173648_add_audit.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/activity_logger/priv/repo/migrations/20180907173648_add_audit.exs b/apps/activity_logger/priv/repo/migrations/20180907173648_add_audit.exs index 2a87b3a54..efdf62720 100644 --- a/apps/activity_logger/priv/repo/migrations/20180907173648_add_audit.exs +++ b/apps/activity_logger/priv/repo/migrations/20180907173648_add_audit.exs @@ -23,6 +23,6 @@ defmodule ActivityLogger.Repo.Migrations.AddAudit do create index(:audit, [:target_uuid, :target_type]) create index(:audit, [:originator_uuid, :originator_type]) - create unique_index(:activity_log, [:id]) + create unique_index(:audit, [:id]) end end From c967f152f352199cab44ead352be388046294014 Mon Sep 17 00:00:00 2001 From: mederic Date: Fri, 14 Dec 2018 14:15:04 +0700 Subject: [PATCH 21/23] Rename indexes and pkey in migration --- ...129045732_rename_audit_to_activity_log.exs | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/apps/activity_logger/priv/repo/migrations/20181129045732_rename_audit_to_activity_log.exs b/apps/activity_logger/priv/repo/migrations/20181129045732_rename_audit_to_activity_log.exs index c85fe2f75..39472fe5f 100644 --- a/apps/activity_logger/priv/repo/migrations/20181129045732_rename_audit_to_activity_log.exs +++ b/apps/activity_logger/priv/repo/migrations/20181129045732_rename_audit_to_activity_log.exs @@ -1,7 +1,33 @@ defmodule ActivityLogger.Repo.Migrations.RenameAuditToActivityLogger do use Ecto.Migration - def change do + def up do rename table(:audit), to: table(:activity_log) + rename_constraint(:activity_log, "audit_pkey", "activity_log_pkey") + create_index(:activity_log) + drop_index(:audit) + end + + def down do + rename table(:activity_log), to: table(:audit) + rename_constraint(:audit, "activity_log_pkey", "audit_pkey") + create_index(:audit) + drop_index(:activity_log) + end + + defp drop_index(table) do + drop index(table, [:target_uuid, :target_type]) + drop index(table, [:originator_uuid, :originator_type]) + drop unique_index(table, [:id]) + end + + defp create_index(table) do + create index(table, [:target_uuid, :target_type]) + create index(table, [:originator_uuid, :originator_type]) + create unique_index(table, [:id]) + end + + defp rename_constraint(table, from_constraint, to_constraint) do + execute ~s/ALTER TABLE "#{table}" RENAME CONSTRAINT "#{from_constraint}" TO "#{to_constraint}"/ end end From 6f597bc4467b78e68d7942f968ea9b28fddc084b Mon Sep 17 00:00:00 2001 From: mederic Date: Fri, 14 Dec 2018 17:09:42 +0700 Subject: [PATCH 22/23] Add more tests for ActivityLog.insert/3 --- .../activity_logger/activity_log_test.exs | 168 ++++++++++++++---- .../support/activity_logger_test_helper.ex | 16 +- .../test/support/test_document.ex | 3 +- .../admin_auth/account_controller_test.exs | 4 +- .../provider_auth/account_controller_test.exs | 4 +- 5 files changed, 151 insertions(+), 44 deletions(-) diff --git a/apps/activity_logger/test/activity_logger/activity_log_test.exs b/apps/activity_logger/test/activity_logger/activity_log_test.exs index b797f1ef2..1599dd632 100644 --- a/apps/activity_logger/test/activity_logger/activity_log_test.exs +++ b/apps/activity_logger/test/activity_logger/activity_log_test.exs @@ -1,6 +1,8 @@ defmodule ActivityLogger.ActivityLogTest do use ExUnit.Case + use ActivityLogger.ActivityLogging import ActivityLogger.Factory + import ActivityLogger.ActivityLoggerTestHelper alias Ecto.Adapters.SQL.Sandbox alias ActivityLogger.{ @@ -110,14 +112,12 @@ defmodule ActivityLogger.ActivityLogTest do originator: %System{} }) - activity_log = ActivityLog.get_initial_activity_log("test_user", user.uuid) - - assert activity_log.originator_type == "test_user" - assert activity_log.originator_uuid == initial_originator.uuid - assert activity_log.target_type == "test_user" - assert activity_log.target_uuid == user.uuid - assert activity_log.action == "insert" - assert activity_log.inserted_at != nil + ActivityLog.get_initial_activity_log("test_user", user.uuid) + |> assert_activity_log( + action: "insert", + originator: initial_originator, + target: user + ) end end @@ -141,6 +141,100 @@ defmodule ActivityLogger.ActivityLogTest do end end + describe "ActivityLog.insert/3" do + setup do + admin = insert(:test_user) + + attrs = %{ + title: "A title", + body: "some body that we don't want to save", + secret_data: %{something: "cool"}, + originator: admin + } + + record = insert(:test_document, attrs) + + %{attrs: attrs, record: record} + end + + test "inserts everything in target_changes by default", meta do + changeset = + %TestDocument{} + |> cast_and_validate_required_for_activity_log( + meta.attrs, + cast: [:title, :body, :secret_data], + required: [:title] + ) + + {:ok, activity_log} = ActivityLog.insert(:insert, changeset, meta.record) + + assert_activity_log( + activity_log, + action: "insert", + originator: meta.attrs.originator, + target: meta.record, + changes: %{ + title: meta.attrs.title, + body: meta.attrs.body, + secret_data: %{something: "cool"} + }, + encrypted_changes: %{} + ) + end + + test "inserts encrypted fields in encrypted_changes", meta do + changeset = + %TestDocument{} + |> cast_and_validate_required_for_activity_log( + meta.attrs, + cast: [:title, :body, :secret_data], + required: [:title], + encrypted: [:secret_data] + ) + + {:ok, activity_log} = ActivityLog.insert(:insert, changeset, meta.record) + + assert_activity_log( + activity_log, + action: "insert", + originator: meta.attrs.originator, + target: meta.record, + changes: %{ + title: meta.attrs.title, + body: meta.attrs.body + }, + encrypted_changes: %{ + secret_data: meta.attrs.secret_data + } + ) + end + + test "does not insert fields protected with `prevent_saving`", meta do + changeset = + %TestDocument{} + |> cast_and_validate_required_for_activity_log( + meta.attrs, + cast: [:title, :body, :secret_data], + required: [:title], + prevent_saving: [:body] + ) + + {:ok, activity_log} = ActivityLog.insert(:insert, changeset, meta.record) + + assert_activity_log( + activity_log, + action: "insert", + originator: meta.attrs.originator, + target: meta.record, + changes: %{ + title: meta.attrs.title, + secret_data: meta.attrs.secret_data + }, + encrypted_changes: %{} + ) + end + end + describe "ActivityLog.insert_record_with_activity_log/2" do test "inserts an activity_log and a document with encrypted metadata" do admin = insert(:test_user) @@ -157,20 +251,18 @@ defmodule ActivityLogger.ActivityLogTest do assert res == :ok - assert activity_log.action == "insert" - assert activity_log.originator_type == "test_user" - assert activity_log.originator_uuid == admin.uuid - assert activity_log.target_type == "test_document" - assert activity_log.target_uuid == record.uuid - - assert activity_log.target_changes == %{ - "title" => record.title, - "body" => record.body - } - - assert activity_log.target_encrypted_changes == %{ - "secret_data" => %{"something" => "cool"} - } + assert_activity_log( + activity_log, + action: "insert", + originator: admin, + target: record, + changes: %{ + "title" => record.title + }, + encrypted_changes: %{ + "secret_data" => %{"something" => "cool"} + } + ) assert record |> ActivityLog.all_for_target() |> length() == 1 end @@ -196,17 +288,16 @@ defmodule ActivityLogger.ActivityLogTest do assert res == :ok - assert activity_log.action == "update" - assert activity_log.originator_type == "test_user" - assert activity_log.originator_uuid == admin.uuid - assert activity_log.target_type == "test_user" - assert activity_log.target_uuid == record.uuid - - assert activity_log.target_changes == %{ - "username" => record.username - } - - assert activity_log.target_encrypted_changes == %{} + assert_activity_log( + activity_log, + action: "update", + originator: admin, + target: record, + changes: %{ + "username" => record.username + }, + encrypted_changes: %{} + ) assert user |> ActivityLog.all_for_target() |> length() == 2 end @@ -220,11 +311,12 @@ defmodule ActivityLogger.ActivityLogTest do assert res == :ok - assert activity_log.action == "insert" - assert activity_log.originator_type == "test_user" - assert activity_log.originator_uuid == admin.uuid - assert activity_log.target_type == "test_user" - assert activity_log.target_uuid == record.uuid + assert_activity_log( + activity_log, + action: "insert", + originator: admin, + target: record + ) assert record |> ActivityLog.all_for_target() |> length() == 1 end diff --git a/apps/activity_logger/test/support/activity_logger_test_helper.ex b/apps/activity_logger/test/support/activity_logger_test_helper.ex index 7e0b26585..cb2e46771 100644 --- a/apps/activity_logger/test/support/activity_logger_test_helper.ex +++ b/apps/activity_logger/test/support/activity_logger_test_helper.ex @@ -42,7 +42,7 @@ defmodule ActivityLogger.ActivityLoggerTestHelper do assert log.target_encrypted_changes == encrypted_changes end - def assert_simple_activity_log( + def assert_activity_log( log, action: action, originator_type: o_type, @@ -54,6 +54,20 @@ defmodule ActivityLogger.ActivityLoggerTestHelper do assert log.target_type == t_type end + def assert_activity_log( + log, + action: action, + originator: originator, + target: target + ) do + assert log.action == action + assert log.inserted_at != nil + assert log.originator_type == ActivityLog.get_type(originator.__struct__) + assert log.originator_uuid == originator.uuid + assert log.target_type == ActivityLog.get_type(target.__struct__) + assert log.target_uuid == target.uuid + end + def get_all_activity_logs(schema) do type = ActivityLog.get_type(schema.__struct__) diff --git a/apps/activity_logger/test/support/test_document.ex b/apps/activity_logger/test/support/test_document.ex index f15fa486b..6a2d1c3c8 100644 --- a/apps/activity_logger/test/support/test_document.ex +++ b/apps/activity_logger/test/support/test_document.ex @@ -29,7 +29,8 @@ defmodule ActivityLogger.TestDocument do attrs, cast: [:title, :body, :secret_data], required: [:title], - encrypted: [:secret_data] + encrypted: [:secret_data], + prevent_saving: [:body] ) end diff --git a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/account_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/account_controller_test.exs index ebc5c633b..7ff7131e3 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/admin_auth/account_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/admin_auth/account_controller_test.exs @@ -276,7 +276,7 @@ defmodule AdminAPI.V1.AdminAuth.AccountControllerTest do logs |> Enum.at(0) - |> assert_simple_activity_log( + |> assert_activity_log( action: "insert", originator_type: "account", target_type: "wallet" @@ -284,7 +284,7 @@ defmodule AdminAPI.V1.AdminAuth.AccountControllerTest do logs |> Enum.at(1) - |> assert_simple_activity_log( + |> assert_activity_log( action: "insert", originator_type: "account", target_type: "wallet" diff --git a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/account_controller_test.exs b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/account_controller_test.exs index f67ff38c5..3d13412a7 100644 --- a/apps/admin_api/test/admin_api/v1/controllers/provider_auth/account_controller_test.exs +++ b/apps/admin_api/test/admin_api/v1/controllers/provider_auth/account_controller_test.exs @@ -151,7 +151,7 @@ defmodule AdminAPI.V1.ProviderAuth.AccountControllerTest do logs |> Enum.at(0) - |> assert_simple_activity_log( + |> assert_activity_log( action: "insert", originator_type: "account", target_type: "wallet" @@ -159,7 +159,7 @@ defmodule AdminAPI.V1.ProviderAuth.AccountControllerTest do logs |> Enum.at(1) - |> assert_simple_activity_log( + |> assert_activity_log( action: "insert", originator_type: "account", target_type: "wallet" From 1d2d7c9e703b57dc14b01412858deea25f9b9a41 Mon Sep 17 00:00:00 2001 From: mederic Date: Fri, 14 Dec 2018 17:39:15 +0700 Subject: [PATCH 23/23] Add a test with both encrypted and prevent_saving --- .../activity_logger/activity_log_test.exs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/apps/activity_logger/test/activity_logger/activity_log_test.exs b/apps/activity_logger/test/activity_logger/activity_log_test.exs index 1599dd632..b5a2783af 100644 --- a/apps/activity_logger/test/activity_logger/activity_log_test.exs +++ b/apps/activity_logger/test/activity_logger/activity_log_test.exs @@ -233,6 +233,34 @@ defmodule ActivityLogger.ActivityLogTest do encrypted_changes: %{} ) end + + test "Inserts encrypted_changes, but does not insert fields protected with `prevent_saving`", + meta do + changeset = + %TestDocument{} + |> cast_and_validate_required_for_activity_log( + meta.attrs, + cast: [:title, :body, :secret_data], + required: [:title], + prevent_saving: [:body], + encrypted: [:secret_data] + ) + + {:ok, activity_log} = ActivityLog.insert(:insert, changeset, meta.record) + + assert_activity_log( + activity_log, + action: "insert", + originator: meta.attrs.originator, + target: meta.record, + changes: %{ + title: meta.attrs.title + }, + encrypted_changes: %{ + secret_data: meta.attrs.secret_data + } + ) + end end describe "ActivityLog.insert_record_with_activity_log/2" do