Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/authenticator/lib/sessions/cache.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ defmodule Authenticator.Sessions.Cache do
to be faster in authentication requests.
"""

use Nebulex.Cache, otp_app: :my_app, adapter: Nebulex.Adapters.Local
use Nebulex.Cache, otp_app: :authenticator, adapter: Nebulex.Adapters.Local
end
23 changes: 10 additions & 13 deletions apps/authenticator/lib/sessions/manager.ex
Original file line number Diff line number Diff line change
Expand Up @@ -61,31 +61,28 @@ defmodule Authenticator.Sessions.Manager do
scheduled_to: nil
}

{:ok, state, {:continue, :schedule_work}}
{:ok, state, {:continue, :manage}}
end

@impl true
def handle_continue(:schedule_work, state) do
Logger.info("Session manager scheduling job.")
def handle_continue(:manage, state) do
# Updating session statuses and adding active ones to cache
manage_sessions()

# Scheduling next job
state = schedule_work(state)

# Updating state
state = %{state | updated_at: NaiveDateTime.utc_now()}

{:noreply, state}
end

@impl true
def handle_call(:check, _from, state), do: {:reply, state, state}

@impl true
def handle_info(:manage, state) do
# Updating session statuses and adding active ones to cache
manage_sessions()

# Updating state
state = %{state | updated_at: NaiveDateTime.utc_now()}

{:noreply, state, {:continue, :schedule_work}}
end
def handle_info(:manage, state), do: {:noreply, state, {:continue, :manage}}

# coveralls-ignore-stop

Expand All @@ -104,7 +101,7 @@ defmodule Authenticator.Sessions.Manager do
|> Repo.transaction()
|> case do
{:ok, _response} ->
Logger.info("Succeeds in managing sessions")
Logger.debug("Succeeds in managing sessions")
{:ok, :sessions_updated}

{:error, step, err, _changes} ->
Expand Down
10 changes: 10 additions & 0 deletions apps/resource_manager/lib/credentials/cache.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
defmodule ResourceManager.Credentials.Cache do
@moduledoc """
Passwords credentials generic cache.

We only cache the most common passwords list in order to verify
before accepting an password.
"""

use Nebulex.Cache, otp_app: :resource_manager, adapter: Nebulex.Adapters.Local
end
136 changes: 136 additions & 0 deletions apps/resource_manager/lib/credentials/manager.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
defmodule ResourceManager.Credentials.Manager do
@moduledoc """
GenServer for dealing with session expirations.
"""

use GenServer

require Logger

alias ResourceManager.Credentials.Cache

@typedoc "Credentials manager supervisor state"
@type state :: %{
started_at: NaiveDateTime.t(),
updated_at: NaiveDateTime.t() | nil,
scheduled_to: NaiveDateTime.t() | nil
}

# One hour interval
@schedule_interval 60 * 60

#########
# CLIENT
#########

# coveralls-ignore-start

@doc "Starts the `GenServer"
@spec start_link(args :: keyword()) :: {:ok, pid()} | :ignore | {:error, keyword()}
def start_link(args \\ []), do: GenServer.start_link(__MODULE__, args, name: __MODULE__)

@doc "Checks #{__MODULE__} actual state"
@spec check(process_id :: pid() | __MODULE__) :: state()
def check(pid \\ __MODULE__), do: GenServer.call(pid, :check)

# coveralls-ignore-stop

@doc "Update session statuses and save on cache"
@spec execute() :: {:ok, :managed} | {:error, :update_failed | :failed_to_cache}
def execute, do: manage_passwords()

#########
# SERVER
#########

# coveralls-ignore-start

@impl true
def init(_args) do
Logger.info("Credential manager started")

state = %{
started_at: NaiveDateTime.utc_now(),
updated_at: nil,
scheduled_to: nil
}

{:ok, state, {:continue, :manage}}
end

@impl true
def handle_continue(:manage, state) do
# Updating session statuses and adding active ones to cache
manage_passwords()

state = schedule_work(state)

{:noreply, state}
end

@impl true
def handle_call(:check, _from, state), do: {:reply, state, state}

@impl true
def handle_info(:manage, state), do: {:noreply, state, {:continue, :manage}}

# coveralls-ignore-stop

##########
# Helpers
##########

defp manage_passwords do
if Cache.size() == 0 do
Logger.debug("Credential manager Loading cache from dump")

file_path()
|> File.read!()
|> String.trim()
|> String.split("\n")
|> Enum.map(fn pwd -> %Nebulex.Object{key: pwd, value: pwd, version: 1} end)
|> Cache.set_many()
|> case do
:ok ->
Logger.debug("Credential manager cache loaded with success")
{:ok, :managed}

{:error, _error} ->
Logger.error("Credential manager failed to load cache from file")
{:error, :load_failed}
end
else
Logger.debug("Credential manager cache already loaded")
{:ok, :managed}
end
end

defp file_path do
:resource_manager
|> :code.priv_dir()
|> Path.join("/passwords/common_passwords.txt")
end

# coveralls-ignore-start

defp schedule_work(state) do
interval = schedule_interval()
date_to_schedule = schedule_to(interval)

Process.send_after(__MODULE__, :manage, :timer.seconds(interval))

# Updating state
%{state | scheduled_to: date_to_schedule}
end

defp schedule_to(interval) do
NaiveDateTime.utc_now()
|> NaiveDateTime.add(interval, :second)
|> NaiveDateTime.truncate(:second)
end

defp schedule_interval, do: Keyword.get(config(), :schedule_interval, @schedule_interval)
defp config, do: Application.get_env(:authenticator, __MODULE__, [])

# coveralls-ignore-stop
end
30 changes: 29 additions & 1 deletion apps/resource_manager/lib/credentials/passwords.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,33 @@
defmodule ResourceManager.Credentials.Passwords do
@moduledoc false
@moduledoc """
Passwords are a type of credential used by a subject in authentication requests.

It's generally used by users in order to provide an minimum way to ensure
that a it is who he claim to be when making requests.
"""

use ResourceManager.Domain, schema_model: ResourceManager.Credentials.Schemas.Password

alias ResourceManager.Credentials.Cache

@doc "Checks if the given password is strong enough to be used"
@spec is_strong?(password :: String.t()) :: boolean()
def is_strong?(password) when is_binary(password) do
cond do
String.length(password) < 6 -> false
is_allowed?(password) == false -> false
true -> true
end
end

@doc "Checks if the given password is one of the most common passwords"
@spec is_allowed?(password :: String.t()) :: boolean()
def is_allowed?(password) when is_binary(password) do
password
|> Cache.get()
|> case do
nil -> true
_any -> false
end
end
end
11 changes: 10 additions & 1 deletion apps/resource_manager/lib/credentials/public_keys.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
defmodule ResourceManager.Credentials.PublicKeys do
@moduledoc false
@moduledoc """
Public keys are a type of credential used by a subject in authentication requests.

It uses an asymmetric cryptography that means that pair of keys are generated and used
by the subject. One of then is public and should be saved on our database and the other
is privated and should only be known by the owner.

When a subject requests an access_token it sign it's assertion using the private key and
we use the public one to read it's content and validate the signature.
"""

use ResourceManager.Domain, schema_model: ResourceManager.Credentials.Schemas.PublicKey
end
1 change: 1 addition & 0 deletions apps/resource_manager/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ defmodule ResourceManager.MixProject do
{:argon2_elixir, "~> 2.0"},
{:bcrypt_elixir, "~> 2.2"},
{:pbkdf2_elixir, "~> 1.2"},
{:nebulex, "~> 1.2"},

# Database
{:postgrex, "~> 0.15"},
Expand Down
Loading