Skip to content

Commit b5d5f6d

Browse files
author
Yashin Santos
committed
Merge branch 'master' into feat/create_admin_endpoint
2 parents 9ecdfb7 + 55c8f37 commit b5d5f6d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+521
-174
lines changed

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66

77
## Requirements
88

9-
- Elixir `1.10` using `OTP-23`;
10-
- Erlang `23.0`;
9+
- Elixir `1.11` using `OTP-23`;
10+
- Erlang `23.1`;
1111
- Docker-compose (Just when running in dev enviroment);
1212

1313
## Running it locally
@@ -36,11 +36,11 @@ To get the user and application data check out the database on `localhost:8181`
3636
```elixir
3737
# Getting all user identities
3838
# The user password will be `admin`
39-
ResourceManager.Repo.all(ResourceManager.Identity.Schemas.User) |> ResourceManager.Repo.preload([:scopes])
39+
ResourceManager.Repo.all(ResourceManager.Identities.Schemas.User) |> ResourceManager.Repo.preload([:scopes])
4040

4141
# Getting all client application identities
4242
# Check out for the client secret
43-
ResourceManager.Repo.all(ResourceManager.Identity.Schemas.ClientApplication) |> ResourceManager.Repo.preload([:scopes])
43+
ResourceManager.Repo.all(ResourceManager.Identities.Schemas.ClientApplication) |> ResourceManager.Repo.preload([:scopes])
4444
```
4545

4646
### Making requests
@@ -57,4 +57,4 @@ We use some libraries in order to keep our code as consistent as possible.
5757

5858
- [Credo](https://github.com/rrrene/credo) (run using `mix credo --strict`);
5959
- [Dialyzer](https://github.com/jeremyjh/dialyxir) (run using `mix dialyzer`);
60-
- [Coveralls](https://github.com/parroty/excoveralls) (run using `mix coveralls`);
60+
- [Coveralls](https://github.com/parroty/excoveralls) (run using `mix coveralls`);

apps/authenticator/lib/crypto/commands/fake_verify_hash.ex

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ defmodule Authenticator.Crypto.Commands.FakeVerifyHash do
33
Simulates a hash verification using the given algorithm.
44
"""
55

6-
@behaviour ResourceManager.Credentials.Ports.FakeVerifyHash
6+
@typedoc "All possible hash algorithms"
7+
@type algorithms :: :argon2 | :bcrypt | :pbkdf2
78

8-
@impl true
9+
@doc "Fake a hash verification using the given algorithm"
10+
@spec execute(algorithm :: algorithms()) :: false
911
def execute(:argon2), do: Argon2.no_user_verify()
1012
def execute(:bcrypt), do: Bcrypt.no_user_verify()
1113
def execute(:pbkdf2), do: Pbkdf2.no_user_verify()

apps/authenticator/lib/crypto/commands/generate_hash.ex

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@ defmodule Authenticator.Crypto.Commands.GenerateHash do
2323
with a sliding computational cost and generally used to reduce vulnerabilities to brute force attacks.
2424
"""
2525

26-
@behaviour ResourceManager.Credentials.Ports.GenerateHash
26+
@typedoc "All possible hash algorithms"
27+
@type algorithms :: :argon2 | :bcrypt | :pbkdf2
2728

28-
@impl true
29+
@doc "Generates a hash using the given algorithm"
30+
@spec execute(value :: String.t(), algorithm :: algorithms()) :: String.t()
2931
def execute(value, :argon2) when is_binary(value), do: Argon2.hash_pwd_salt(value)
3032
def execute(value, :bcrypt) when is_binary(value), do: Bcrypt.hash_pwd_salt(value)
3133
def execute(value, :pbkdf2) when is_binary(value), do: Pbkdf2.hash_pwd_salt(value)

apps/authenticator/lib/crypto/commands/verify_hash.ex

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@ defmodule Authenticator.Crypto.Commands.VerifyHash do
33
Verify if a given hash matches the given value.
44
"""
55

6-
@behaviour ResourceManager.Credentials.Ports.VerifyHash
6+
@typedoc "All possible hash algorithms"
7+
@type algorithms :: :argon2 | :bcrypt | :pbkdf2
78

8-
@impl true
9+
@doc "Verifies if a credential matches the given secret"
10+
@spec execute(credential :: map(), value :: String.t()) :: boolean()
911
def execute(%{password: %{password_hash: hash, algorithm: algorithm}}, password)
1012
when is_binary(password),
1113
do: execute(password, hash, String.to_atom(algorithm))
1214

13-
@impl true
15+
@doc "Verifies if a hash matches the given credential using the passed algorithm"
16+
@spec execute(value :: String.t(), hash :: String.t(), algorithm :: algorithms()) :: boolean()
1417
def execute(value, hash, :argon2)
1518
when is_binary(value) and is_binary(hash),
1619
do: Argon2.verify_pass(value, hash)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
defmodule Authenticator.SignIn.Commands.GetTemporarillyBlocked do
2+
@moduledoc """
3+
Get subject authentication attempts that failed consecutively and return
4+
it's username or client_id.
5+
"""
6+
7+
require Logger
8+
9+
alias Authenticator.SignIn.{ApplicationAttempts, UserAttempts}
10+
11+
# Maximum failed attempts before block
12+
@max_attempts 5
13+
14+
# 10 minutes interval
15+
@max_interval 60 * 10 * -1
16+
17+
@doc """
18+
Return the identities that failed more than #{@max_attempts} times on sign in
19+
in #{@max_interval} seconds.
20+
"""
21+
@spec execute(identity_type :: :user | :application) :: {:ok, list(String.t())}
22+
def execute(:user), do: {:ok, UserAttempts.list(get_filters())}
23+
def execute(:application), do: {:ok, ApplicationAttempts.list(get_filters())}
24+
25+
# Query filters
26+
defp get_filters, do: [temporarilly_blocked: {@max_attempts, created_after()}]
27+
defp created_after, do: NaiveDateTime.add(NaiveDateTime.utc_now(), @max_interval, :second)
28+
end

apps/authenticator/lib/sign_in/schemas/application_attempt.ex

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,12 @@ defmodule Authenticator.SignIn.Schemas.ApplicationAttempt do
4747
defp custom_query(query, {:ids, ids}), do: where(query, [c], c.id in ^ids)
4848
defp custom_query(query, {:created_after, date}), do: where(query, [c], c.inserted_at > ^date)
4949
defp custom_query(query, {:created_before, date}), do: where(query, [c], c.inserted_at < ^date)
50+
51+
defp custom_query(query, {:temporarilly_blocked, {max_attempts, date}}) do
52+
query
53+
|> where([c], c.inserted_at > ^date)
54+
|> group_by([c], c.client_id)
55+
|> having([c], count(c.client_id) > ^max_attempts)
56+
|> select([c], c.client_id)
57+
end
5058
end

apps/authenticator/lib/sign_in/schemas/user_attempt.ex

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,12 @@ defmodule Authenticator.SignIn.Schemas.UserAttempt do
4747
defp custom_query(query, {:ids, ids}), do: where(query, [c], c.id in ^ids)
4848
defp custom_query(query, {:created_after, date}), do: where(query, [c], c.inserted_at > ^date)
4949
defp custom_query(query, {:created_before, date}), do: where(query, [c], c.inserted_at < ^date)
50+
51+
defp custom_query(query, {:temporarilly_blocked, {max_attempts, date}}) do
52+
query
53+
|> where([c], c.inserted_at > ^date)
54+
|> group_by([c], c.username)
55+
|> having([c], count(c.username) > ^max_attempts)
56+
|> select([c], c.username)
57+
end
5058
end
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
defmodule Authenticator.SignIn.Commands.GetTemporarillyBlockedTest do
2+
use Authenticator.DataCase, async: true
3+
4+
alias Authenticator.SignIn.Commands.GetTemporarillyBlocked
5+
6+
describe "#{GetTemporarillyBlocked}.execute/1" do
7+
test "succeeds and return users to block temporarilly" do
8+
assert [%{username: username} | _] =
9+
Enum.map(1..15, fn _ ->
10+
insert!(:user_sign_in_attempt, username: "myusername", was_successful: false)
11+
end)
12+
13+
assert {:ok, [^username | _]} = GetTemporarillyBlocked.execute(:user)
14+
end
15+
16+
test "succeeds and return applications to block temporarilly" do
17+
assert [%{client_id: client_id} | _] =
18+
Enum.map(1..15, fn _ ->
19+
insert!(:application_sign_in_attempt,
20+
client_id: "myclientid",
21+
was_successful: false
22+
)
23+
end)
24+
25+
assert {:ok, [^client_id | _]} = GetTemporarillyBlocked.execute(:application)
26+
end
27+
end
28+
end

apps/resource_manager/lib/credentials/blocklist_password_manager.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ defmodule ResourceManager.Credentials.BlocklistPasswordManager do
3737

3838
# coveralls-ignore-stop
3939

40-
@doc "Update session statuses and save on cache"
40+
@doc "Update blocked password list on cache"
4141
@spec execute() :: {:ok, :managed} | {:error, :update_failed | :failed_to_cache}
4242
def execute, do: manage_passwords()
4343

apps/resource_manager/lib/credentials/ports/fake_verify_hash.ex

Lines changed: 0 additions & 18 deletions
This file was deleted.

0 commit comments

Comments
 (0)