diff --git a/apps/authenticator/lib/crypto/commands/fake_verify_hash.ex b/apps/authenticator/lib/crypto/commands/fake_verify_hash.ex index 7364604..9954497 100644 --- a/apps/authenticator/lib/crypto/commands/fake_verify_hash.ex +++ b/apps/authenticator/lib/crypto/commands/fake_verify_hash.ex @@ -3,9 +3,11 @@ defmodule Authenticator.Crypto.Commands.FakeVerifyHash do Simulates a hash verification using the given algorithm. """ - @behaviour ResourceManager.Credentials.Ports.FakeVerifyHash + @typedoc "All possible hash algorithms" + @type algorithms :: :argon2 | :bcrypt | :pbkdf2 - @impl true + @doc "Fake a hash verification using the given algorithm" + @spec execute(algorithm :: algorithms()) :: false def execute(:argon2), do: Argon2.no_user_verify() def execute(:bcrypt), do: Bcrypt.no_user_verify() def execute(:pbkdf2), do: Pbkdf2.no_user_verify() diff --git a/apps/authenticator/lib/crypto/commands/generate_hash.ex b/apps/authenticator/lib/crypto/commands/generate_hash.ex index 2eb5484..cbe6563 100644 --- a/apps/authenticator/lib/crypto/commands/generate_hash.ex +++ b/apps/authenticator/lib/crypto/commands/generate_hash.ex @@ -23,9 +23,11 @@ defmodule Authenticator.Crypto.Commands.GenerateHash do with a sliding computational cost and generally used to reduce vulnerabilities to brute force attacks. """ - @behaviour ResourceManager.Credentials.Ports.GenerateHash + @typedoc "All possible hash algorithms" + @type algorithms :: :argon2 | :bcrypt | :pbkdf2 - @impl true + @doc "Generates a hash using the given algorithm" + @spec execute(value :: String.t(), algorithm :: algorithms()) :: String.t() def execute(value, :argon2) when is_binary(value), do: Argon2.hash_pwd_salt(value) def execute(value, :bcrypt) when is_binary(value), do: Bcrypt.hash_pwd_salt(value) def execute(value, :pbkdf2) when is_binary(value), do: Pbkdf2.hash_pwd_salt(value) diff --git a/apps/authenticator/lib/crypto/commands/verify_hash.ex b/apps/authenticator/lib/crypto/commands/verify_hash.ex index 9441a0d..791aab0 100644 --- a/apps/authenticator/lib/crypto/commands/verify_hash.ex +++ b/apps/authenticator/lib/crypto/commands/verify_hash.ex @@ -3,14 +3,17 @@ defmodule Authenticator.Crypto.Commands.VerifyHash do Verify if a given hash matches the given value. """ - @behaviour ResourceManager.Credentials.Ports.VerifyHash + @typedoc "All possible hash algorithms" + @type algorithms :: :argon2 | :bcrypt | :pbkdf2 - @impl true + @doc "Verifies if a credential matches the given secret" + @spec execute(credential :: map(), value :: String.t()) :: boolean() def execute(%{password: %{password_hash: hash, algorithm: algorithm}}, password) when is_binary(password), do: execute(password, hash, String.to_atom(algorithm)) - @impl true + @doc "Verifies if a hash matches the given credential using the passed algorithm" + @spec execute(value :: String.t(), hash :: String.t(), algorithm :: algorithms()) :: boolean() def execute(value, hash, :argon2) when is_binary(value) and is_binary(hash), do: Argon2.verify_pass(value, hash) diff --git a/apps/resource_manager/lib/credentials/ports/fake_verify_hash.ex b/apps/resource_manager/lib/credentials/ports/fake_verify_hash.ex deleted file mode 100644 index 4c54b56..0000000 --- a/apps/resource_manager/lib/credentials/ports/fake_verify_hash.ex +++ /dev/null @@ -1,18 +0,0 @@ -defmodule ResourceManager.Credentials.Ports.FakeVerifyHash do - @moduledoc """ - Port to access Authenticator fake verify hash command. - """ - - @doc "Delegates to #{__MODULE__}.execute/1 command" - @callback execute(algorithm :: :argon2 | :bcrypt | :pbkdf2) :: false - - @doc "Gets the hash and algorithm from the input and verifies if it matches the hash" - @spec execute(algorithm :: :argon2 | :bcrypt | :pbkdf2) :: false - def execute(algorithm), do: implementation().execute(algorithm) - - defp implementation do - :resource_manager - |> Application.get_env(__MODULE__) - |> Keyword.get(:command) - end -end diff --git a/apps/resource_manager/lib/credentials/ports/generate_hash.ex b/apps/resource_manager/lib/credentials/ports/generate_hash.ex deleted file mode 100644 index 9fc0526..0000000 --- a/apps/resource_manager/lib/credentials/ports/generate_hash.ex +++ /dev/null @@ -1,21 +0,0 @@ -defmodule ResourceManager.Credentials.Ports.GenerateHash do - @moduledoc """ - Port to access Authenticator generate hash command. - """ - - @typedoc "All possible hash algorithms" - @type algorithms :: :argon2 | :bcrypt | :pbkdf2 - - @doc "Delegates to #{__MODULE__}.execute/2 command" - @callback execute(secret :: map() | String.t(), algorithm :: algorithms()) :: String.t() - - @doc "Delegates execution to generate hash command" - @spec execute(secret :: String.t(), algorithm :: algorithms()) :: String.t() - def execute(secret, algorithm \\ :argon2), do: implementation().execute(secret, algorithm) - - defp implementation do - :resource_manager - |> Application.get_env(__MODULE__) - |> Keyword.get(:command) - end -end diff --git a/apps/resource_manager/lib/credentials/ports/verify_hash.ex b/apps/resource_manager/lib/credentials/ports/verify_hash.ex deleted file mode 100644 index e37a4b3..0000000 --- a/apps/resource_manager/lib/credentials/ports/verify_hash.ex +++ /dev/null @@ -1,41 +0,0 @@ -defmodule ResourceManager.Credentials.Ports.VerifyHash do - @moduledoc """ - Port to access Authenticator verify hash command. - """ - - alias ResourceManager.Identities.Schemas.{ClientApplication, User} - - @typedoc "All possible hash algorithms" - @type algorithms :: :argon2 | :bcrypt | :pbkdf2 - - @doc "Delegates to #{__MODULE__}.execute/2 command" - @callback execute( - identity :: User.t() | ClientApplication.t(), - secret :: String.t() - ) :: boolean() - - @doc "Delegates to #{__MODULE__}.execute/3 command" - @callback execute( - secret :: String.t(), - hash :: String.t() | nil, - algorithm :: algorithms() - ) :: boolean() - - @doc "Gets the hash and algorithm from the input and verifies if it matches the hash" - @spec execute(identity :: map(), credential :: String.t()) :: boolean() - def execute(entity, secret) - when is_map(entity) and is_binary(secret), - do: implementation().execute(entity, secret) - - @doc "Delegates execution to hash secret command" - @spec execute(secret :: String.t(), hash :: String.t(), algorithm :: algorithms()) :: String.t() - def execute(secret, hash, algorithm \\ :argon2) - when is_binary(secret) and is_binary(hash) and is_atom(algorithm), - do: implementation().execute(secret, hash, algorithm) - - defp implementation do - :resource_manager - |> Application.get_env(__MODULE__) - |> Keyword.get(:command) - end -end diff --git a/apps/resource_manager/lib/identities/manager.ex b/apps/resource_manager/lib/identities/manager.ex index 7b072e4..614ba7b 100644 --- a/apps/resource_manager/lib/identities/manager.ex +++ b/apps/resource_manager/lib/identities/manager.ex @@ -11,8 +11,8 @@ defmodule ResourceManager.Identities.Manager do require Logger alias Ecto.Multi - alias ResourceManager.Identities.Ports.GetTemporarillyBlocked alias ResourceManager.Identities.Schemas.{ClientApplication, User} + alias ResourceManager.Ports.Authenticator alias ResourceManager.Repo @typedoc "Identities manager supervisor state" @@ -86,7 +86,7 @@ defmodule ResourceManager.Identities.Manager do defp manage_identities do Multi.new() |> Multi.run(:get_user_identities, fn _repo, _changes -> - GetTemporarillyBlocked.execute(:user) + Authenticator.get_temporarilly_blocked(:user) end) |> Multi.run(:block_user_identities, fn _repo, %{get_user_identities: usernames} -> block_user_identities(usernames) @@ -95,7 +95,7 @@ defmodule ResourceManager.Identities.Manager do unblock_user_identities() end) |> Multi.run(:get_application_identities, fn _repo, _changes -> - GetTemporarillyBlocked.execute(:application) + Authenticator.get_temporarilly_blocked(:application) end) |> Multi.run(:block_application_identities, fn _, %{get_application_identities: client_ids} -> block_application_identities(client_ids) diff --git a/apps/resource_manager/lib/identities/ports/get_temporarilly_blocked.ex b/apps/resource_manager/lib/identities/ports/get_temporarilly_blocked.ex deleted file mode 100644 index 66d3d0a..0000000 --- a/apps/resource_manager/lib/identities/ports/get_temporarilly_blocked.ex +++ /dev/null @@ -1,18 +0,0 @@ -defmodule ResourceManager.Identities.Ports.GetTemporarillyBlocked do - @moduledoc """ - Port to access Authenticator get temporarilly blocked command. - """ - - @doc "Delegates to #{__MODULE__}.execute/1 command" - @callback execute(subject_type :: :user | :application) :: false - - @doc "Gets the temporarilly blocked subjects" - @spec execute(subject_type :: :user | :application) :: false - def execute(subject_type), do: implementation().execute(subject_type) - - defp implementation do - :resource_manager - |> Application.get_env(__MODULE__) - |> Keyword.get(:command) - end -end diff --git a/apps/resource_manager/lib/identities/schemas/client_application.ex b/apps/resource_manager/lib/identities/schemas/client_application.ex index 03b026a..1b9ce3b 100644 --- a/apps/resource_manager/lib/identities/schemas/client_application.ex +++ b/apps/resource_manager/lib/identities/schemas/client_application.ex @@ -9,9 +9,9 @@ defmodule ResourceManager.Identities.Schemas.ClientApplication do import Ecto.Changeset - alias ResourceManager.Credentials.Ports.GenerateHash alias ResourceManager.Credentials.Schemas.PublicKey alias ResourceManager.Permissions.Schemas.Scope + alias ResourceManager.Ports.Authenticator @typedoc "User schema fields" @type t :: %__MODULE__{ @@ -73,7 +73,7 @@ defmodule ResourceManager.Identities.Schemas.ClientApplication do defp generate_secret(%{valid?: false} = changeset), do: changeset defp generate_secret(changeset) do - secret = GenerateHash.execute(Ecto.UUID.generate(), :bcrypt) + secret = Authenticator.generate_hash(Ecto.UUID.generate(), :bcrypt) put_change(changeset, :secret, secret) end diff --git a/apps/resource_manager/lib/ports/authenticator.ex b/apps/resource_manager/lib/ports/authenticator.ex new file mode 100644 index 0000000..0cee049 --- /dev/null +++ b/apps/resource_manager/lib/ports/authenticator.ex @@ -0,0 +1,37 @@ +defmodule ResourceManager.Ports.Authenticator do + @moduledoc """ + Port to access Authenticator domain commands. + """ + + @typedoc "All possible hash algorithms" + @type algorithms :: :argon2 | :bcrypt | :pbkdf2 + + @doc "Delegates to #{__MODULE__}.fake_verify_hash/1 command" + @callback fake_verify_hash(algorithm :: algorithms()) :: false + + @doc "Delegates to #{__MODULE__}.generate_hash/2 command" + @callback generate_hash(secret :: map() | String.t(), algorithm :: algorithms()) :: String.t() + + @doc "Delegates to #{__MODULE__}.get_temporarilly_blocked/1 command" + @callback get_temporarilly_blocked(subject_type :: :user | :application) :: list(String.t()) + + @doc "Gets the hash and algorithm from the input and verifies if it matches the hash" + @spec fake_verify_hash(algorithm :: algorithms()) :: false + def fake_verify_hash(algorithm), do: implementation().fake_verify_hash(algorithm) + + @doc "Delegates execution to generate hash command" + @spec generate_hash(secret :: String.t(), algorithm :: algorithms()) :: String.t() + def generate_hash(secret, algorithm \\ :argon2), + do: implementation().generate_hash(secret, algorithm) + + @doc "Gets the temporarilly blocked subjects" + @spec get_temporarilly_blocked(subject_type :: :user | :application) :: false + def get_temporarilly_blocked(subject_type), + do: implementation().get_temporarilly_blocked(subject_type) + + defp implementation do + :resource_manager + |> Application.get_env(__MODULE__) + |> Keyword.get(:command) + end +end diff --git a/apps/resource_manager/test/resource_manager/identity/client_applications_test.exs b/apps/resource_manager/test/resource_manager/identity/client_applications_test.exs index ea47f4c..343f927 100644 --- a/apps/resource_manager/test/resource_manager/identity/client_applications_test.exs +++ b/apps/resource_manager/test/resource_manager/identity/client_applications_test.exs @@ -1,9 +1,9 @@ defmodule ResourceManager.Identities.ClientApplicationsTest do use ResourceManager.DataCase, async: true - alias ResourceManager.Credentials.Ports.GenerateHashMock alias ResourceManager.Identities.ClientApplications alias ResourceManager.Identities.Schemas.ClientApplication + alias ResourceManager.Ports.AuthenticatorMock setup do {:ok, client_application: insert!(:client_application)} @@ -13,7 +13,7 @@ defmodule ResourceManager.Identities.ClientApplicationsTest do test "succeed if params are valid" do params = %{name: "my-test-application"} - expect(GenerateHashMock, :execute, fn secret, :bcrypt -> + expect(AuthenticatorMock, :generate_hash, fn secret, :bcrypt -> assert is_binary(secret) gen_hashed_password(Ecto.UUID.generate()) end) diff --git a/apps/resource_manager/test/resource_manager/identity/commands/create_identity_test.exs b/apps/resource_manager/test/resource_manager/identity/commands/create_identity_test.exs index 2a1b691..a4eec02 100644 --- a/apps/resource_manager/test/resource_manager/identity/commands/create_identity_test.exs +++ b/apps/resource_manager/test/resource_manager/identity/commands/create_identity_test.exs @@ -1,9 +1,9 @@ defmodule ResourceManager.Identities.Commands.CreateIdentityTest do use ResourceManager.DataCase, async: true - alias ResourceManager.Credentials.Ports.GenerateHashMock alias ResourceManager.Identities.Commands.CreateIdentity alias ResourceManager.Identities.Schemas.{ClientApplication, User} + alias ResourceManager.Ports.AuthenticatorMock alias ResourceManager.Repo setup do @@ -30,7 +30,7 @@ defmodule ResourceManager.Identities.Commands.CreateIdentityTest do scopes: ctx.scopes } - expect(GenerateHashMock, :execute, fn secret, :bcrypt -> + expect(AuthenticatorMock, :generate_hash, fn secret, :bcrypt -> assert is_binary(secret) gen_hashed_password(Ecto.UUID.generate()) end) diff --git a/apps/resource_manager/test/resource_manager/identity/manager_test.exs b/apps/resource_manager/test/resource_manager/identity/manager_test.exs index 759b29e..02dc6f5 100644 --- a/apps/resource_manager/test/resource_manager/identity/manager_test.exs +++ b/apps/resource_manager/test/resource_manager/identity/manager_test.exs @@ -2,7 +2,7 @@ defmodule ResourceManager.Identities.ManagerTest do use ResourceManager.DataCase, async: true alias ResourceManager.Identities.Manager - alias ResourceManager.Identities.Ports.GetTemporarillyBlockedMock + alias ResourceManager.Ports.AuthenticatorMock alias ResourceManager.Identities.Schemas.{ClientApplication, User} describe "#{Manager}.execute/0" do @@ -11,16 +11,19 @@ defmodule ResourceManager.Identities.ManagerTest do end test "succeeds and temporary blocks users", %{user: %{id: id, username: username}} do - expect(GetTemporarillyBlockedMock, :execute, fn :user -> {:ok, [username]} end) - expect(GetTemporarillyBlockedMock, :execute, fn :application -> {:ok, []} end) + expect(AuthenticatorMock, :get_temporarilly_blocked, fn :user -> {:ok, [username]} end) + expect(AuthenticatorMock, :get_temporarilly_blocked, fn :application -> {:ok, []} end) assert {:ok, :managed} == Manager.execute() assert %{status: "temporary_blocked", blocked_until: %{}} = Repo.get(User, id) end test "succeeds and temporary blocks applications", %{app: %{id: id, client_id: client_id}} do - expect(GetTemporarillyBlockedMock, :execute, fn :user -> {:ok, []} end) - expect(GetTemporarillyBlockedMock, :execute, fn :application -> {:ok, [client_id]} end) + expect(AuthenticatorMock, :get_temporarilly_blocked, fn :user -> {:ok, []} end) + + expect(AuthenticatorMock, :get_temporarilly_blocked, fn :application -> + {:ok, [client_id]} + end) assert {:ok, :managed} == Manager.execute() assert %{status: "temporary_blocked", blocked_until: %{}} = Repo.get(ClientApplication, id) @@ -29,8 +32,8 @@ defmodule ResourceManager.Identities.ManagerTest do test "succeeds and unblock users" do user = insert!(:user, status: "temporary_blocked", blocked_until: blocked_until()) - expect(GetTemporarillyBlockedMock, :execute, fn :user -> {:ok, []} end) - expect(GetTemporarillyBlockedMock, :execute, fn :application -> {:ok, []} end) + expect(AuthenticatorMock, :get_temporarilly_blocked, fn :user -> {:ok, []} end) + expect(AuthenticatorMock, :get_temporarilly_blocked, fn :application -> {:ok, []} end) assert {:ok, :managed} == Manager.execute() assert %{status: "active", blocked_until: nil} = Repo.get(User, user.id) @@ -44,8 +47,11 @@ defmodule ResourceManager.Identities.ManagerTest do blocked_until: blocked_until() ) - expect(GetTemporarillyBlockedMock, :execute, fn :user -> {:ok, []} end) - expect(GetTemporarillyBlockedMock, :execute, fn :application -> {:ok, [app.client_id]} end) + expect(AuthenticatorMock, :get_temporarilly_blocked, fn :user -> {:ok, []} end) + + expect(AuthenticatorMock, :get_temporarilly_blocked, fn :application -> + {:ok, [app.client_id]} + end) assert {:ok, :managed} == Manager.execute() assert %{status: "active", blocked_until: nil} = Repo.get(ClientApplication, app.id) diff --git a/apps/resource_manager/test/support/mocks.ex b/apps/resource_manager/test/support/mocks.ex index 012142e..f3442c0 100644 --- a/apps/resource_manager/test/support/mocks.ex +++ b/apps/resource_manager/test/support/mocks.ex @@ -1,11 +1,6 @@ for module <- [ - # Credential ports mocks - ResourceManager.Credentials.Ports.GenerateHash, - ResourceManager.Credentials.Ports.VerifyHash, - ResourceManager.Credentials.Ports.FakeVerifyHash, - - # Identity ports mocks - ResourceManager.Identities.Ports.GetTemporarillyBlocked + # Authenticator + ResourceManager.Ports.Authenticator ] do Mox.defmock(:"#{module}Mock", for: module) end diff --git a/config/config.exs b/config/config.exs index ff3400f..7a0c702 100644 --- a/config/config.exs +++ b/config/config.exs @@ -28,17 +28,7 @@ config :resource_manager, ResourceManager.Repo, port: 5432, pool_size: 10 -config :resource_manager, ResourceManager.Credentials.Ports.GenerateHash, - command: Authenticator.Crypto.Commands.GenerateHash - -config :resource_manager, ResourceManager.Credentials.Ports.VerifyHash, - command: Authenticator.Crypto.Commands.VerifyHash - -config :resource_manager, ResourceManager.Credentials.Ports.FakeVerifyHash, - command: Authenticator.Crypto.Commands.FakeVerifyHash - -config :resource_manager, ResourceManager.Identities.Ports.GetTemporarillyBlocked, - command: Authenticator.SignIn.Commands.GetTemporarillyBlocked +config :resource_manager, ResourceManager.Ports.Authenticator, command: Authenticator ################ # Authenticator diff --git a/config/test.exs b/config/test.exs index 071565b..f7b8052 100644 --- a/config/test.exs +++ b/config/test.exs @@ -17,17 +17,8 @@ config :resource_manager, ResourceManager.Repo, config :resource_manager, ResourceManager.Application, children: [ResourceManager.Repo, ResourceManager.Credentials.BlocklistPasswordCache] -config :resource_manager, ResourceManager.Credentials.Ports.GenerateHash, - command: ResourceManager.Credentials.Ports.GenerateHashMock - -config :resource_manager, ResourceManager.Credentials.Ports.VerifyHash, - command: ResourceManager.Credentials.Ports.VerifyHashMock - -config :resource_manager, ResourceManager.Credentials.Ports.FakeVerifyHash, - command: ResourceManager.Credentials.Ports.FakeVerifyHashMock - -config :resource_manager, ResourceManager.Identities.Ports.GetTemporarillyBlocked, - command: ResourceManager.Identities.Ports.GetTemporarillyBlockedMock +config :resource_manager, ResourceManager.Ports.Authenticator, + command: ResourceManager.Ports.AuthenticatorMock ################ # Authenticator