Skip to content

Commit

Permalink
Merge pull request #6 from cciuenf/feature/authentication
Browse files Browse the repository at this point in the history
feature/authentication
  • Loading branch information
Matheus de Souza Pessanha authored Jul 28, 2021
2 parents 1835c82 + a35fbda commit 09ccb12
Show file tree
Hide file tree
Showing 46 changed files with 1,521 additions and 34 deletions.
5 changes: 5 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ config :logger, :console,
metadata: [:request_id],
backends: [:console, Sentry.LoggerBackend]

config :fuschia, FuschiaWeb.Auth.Guardian,
issuer: "pea_pescarte",
ttl: {3, :days},
secret_key: "BGfPrVpwxq8wpwuRRIyMQtihongnh98GAQPl0awRxCn432HLit9Wo/Q83yrjHH2P"

# Use Jason for JSON parsing in Phoenix
config :phoenix, :json_library, Jason

Expand Down
2 changes: 2 additions & 0 deletions config/prod.exs
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,5 @@ config :fuschia, Fuschia.Repo,
ssl: true,
url: System.get_env("DATABASE_URL"),
pool_size: String.to_integer(System.get_env("POOL_SIZE", "10"))

config :fuschia, Fuschia.Auth.Guardian, secret_key: System.get_env("GUARDIAN_SECRET_KEY")
9 changes: 9 additions & 0 deletions config/runtime.exs
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import Config

# ---------------------------#
# Guardian
# ---------------------------#
config :fuschia, FuschiaWeb.Auth.Pipeline,
module: FuschiaWeb.Auth.Guardian,
error_handler: FuschiaWeb.Auth.ErrorHandler

# ---------------------------#
# Sentry
# ---------------------------#
Expand All @@ -12,3 +19,5 @@ config :sentry,
env: "production"
},
included_environments: [System.get_env("SENTRY_ENV")]

config :timex, timezone: System.get_env("TIMEZONE", "America/Sao_Paulo")
6 changes: 6 additions & 0 deletions config/test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,9 @@ config :fuschia, FuschiaWeb.Endpoint,

# Print only warnings and errors during test
config :logger, level: :warn

try do
import_config "test.secret.exs"
rescue
_ -> nil
end
28 changes: 28 additions & 0 deletions lib/fuschia/context/api_keys.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
defmodule Fuschia.Context.ApiKeys do
@moduledoc """
Public Fuschia ApiKeys API
"""

import Ecto.Query

alias Fuschia.Entities.ApiKey
alias Fuschia.Repo

@spec one :: %ApiKey{}
def one do
query()
|> Repo.one()
end

@spec one_by_key(String.t()) :: %ApiKey{} | nil
def one_by_key(key) do
query()
|> Repo.get_by(key: key)
end

@spec query :: Ecto.Query.t()
def query do
from a in ApiKey,
where: a.active == true
end
end
22 changes: 22 additions & 0 deletions lib/fuschia/context/auth_logs.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
defmodule Fuschia.Context.AuthLogs do
@moduledoc """
Public Fuschia Authentication Logs API
"""

alias Fuschia.Entities.{AuthLog, User}
alias Fuschia.Repo

@spec create(map) :: :ok
def create(attrs) do
%AuthLog{}
|> AuthLog.changeset(attrs)
|> Repo.insert()

:ok
end

@spec create(String.t(), String.t(), %User{}) :: :ok
def create(ip, user_agent, user) do
create(%{"ip" => ip, "user_agent" => user_agent, "user_id" => user.id})
end
end
2 changes: 1 addition & 1 deletion lib/fuschia/context/users.ex
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ defmodule Fuschia.Context.Users do
|> String.trim()

query()
|> where([u], fragment("lower(?)", u.email == ^email))
|> where([u], fragment("lower(?)", u.email) == ^email)
|> where([u], u.ativo == true)
|> order_by([u], desc: u.created_at)
|> limit(1)
Expand Down
15 changes: 15 additions & 0 deletions lib/fuschia/entities/api_key.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
defmodule Fuschia.Entities.ApiKey do
@moduledoc """
API Key Schema
"""

use Fuschia.Schema

schema "api_key" do
field :key, Ecto.UUID
field :description, :string
field :active, :boolean

timestamps()
end
end
30 changes: 30 additions & 0 deletions lib/fuschia/entities/auth_log.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
defmodule Fuschia.Entities.AuthLog do
@moduledoc """
Authentication log
"""

use Fuschia.Schema

import Ecto.Changeset

alias Fuschia.Entities.User
alias Fuschia.Types.TrimmedString

@required_fields ~w(ip user_agent user_id)a

schema "auth_log" do
field :ip, TrimmedString
field :user_agent, TrimmedString

belongs_to :user, User

timestamps(updated_at: false)
end

def changeset(%__MODULE__{} = struct, attrs) do
struct
|> cast(attrs, @required_fields)
|> validate_required(@required_fields)
|> foreign_key_constraint(:user_id)
end
end
9 changes: 6 additions & 3 deletions lib/fuschia/entities/user.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ defmodule Fuschia.Entities.User do
alias Fuschia.Entities.User
alias Fuschia.Types.TrimmedString

@required_fields ~w(nome_completo email cpf)a
@required_fields ~w(nome_completo email cpf data_nasc)a
@optional_fields ~w(password confirmed last_seen nome_completo ativo perfil)a

@valid_perfil ~w(pesquisador pescador admin avulso)
Expand All @@ -23,6 +23,7 @@ defmodule Fuschia.Entities.User do
field :password_hash, TrimmedString
field :confirmed, :boolean
field :email, TrimmedString
field :data_nasc, :date
field :cpf, TrimmedString
field :last_seen, :utc_datetime_usec
field :perfil, TrimmedString
Expand Down Expand Up @@ -105,7 +106,8 @@ defmodule Fuschia.Entities.User do
perfil: struct.perfil,
id: struct.id,
permissoes: struct.permissoes,
cpf: struct.cpf
cpf: struct.cpf,
dataNasc: struct.data_nasc
}
end

Expand Down Expand Up @@ -172,7 +174,8 @@ defmodule Fuschia.Entities.User do
perfil: struct.perfil,
ultimo_login: struct.last_seen,
confirmado: struct.confirmed,
ativo: struct.ativo
ativo: struct.ativo,
data_nasc: struct.data_nasc
}
|> Fuschia.Encoder.encode(opts)
end
Expand Down
3 changes: 3 additions & 0 deletions lib/fuschia/repo.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@ defmodule Fuschia.Repo do
use Ecto.Repo,
otp_app: :fuschia,
adapter: Ecto.Adapters.Postgres

use Paginator,
include_total_count: true
end
66 changes: 66 additions & 0 deletions lib/fuschia_web.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,18 @@ defmodule FuschiaWeb do
and import those modules here.
"""

import Phoenix.Controller, only: [put_view: 2, render: 3]
import Plug.Conn, only: [put_status: 2]

alias FuschiaWeb.FuschiaView

def controller do
quote do
use Phoenix.Controller, namespace: FuschiaWeb

import Plug.Conn
import FuschiaWeb.Gettext
import FuschiaWeb, only: [render_response: 2, render_response: 3]
alias FuschiaWeb.Router.Helpers, as: Routes
end
end
Expand Down Expand Up @@ -69,6 +75,66 @@ defmodule FuschiaWeb do
end
end

@doc """
Get current timestamp
"""
@spec timestamp :: String.t()
def timestamp do
Timex.format!(Timex.now(), "%Y-%m-%dT%H:%M:%SZ", :strftime)
end

@doc """
Return default socket response format
{
"data": {},
"timestamp": "2021-02-01T11:48:52Z"
}
"""
@spec socket_response(map) :: map
def socket_response(payload) do
%{
data: payload,
timestamp: timestamp()
}
end

@doc """
Render success response with data in body or nil if `data` is nil,
so the fallback controller can render the proper response.
Render the paginated view if the `pagination` param is present.
"""
@spec render_response(nil, Plug.Conn.t()) :: nil
def render_response(nil, _conn), do: nil

@spec render_response(map, Plug.Conn.t()) :: Plug.Conn.t()
def render_response(%{data: data, pagination: pagination}, conn) do
conn
|> put_status(:ok)
|> put_view(FuschiaView)
|> render("paginated.json", %{data: data, pagination: pagination})
end

@spec render_response(map | list, Plug.Conn.t()) :: Plug.Conn.t()
def render_response(data, conn) do
conn
|> put_status(:ok)
|> put_view(FuschiaView)
|> render("response.json", data: data)
end

@doc """
Render response with status and data in body
"""
@spec render_response(map | list, Plug.Conn.t(), atom) :: Plug.Conn.t()
def render_response(data, conn, status) do
conn
|> put_status(status)
|> put_view(FuschiaView)
|> render("response.json", data: data)
end

@doc """
When used, dispatch to the appropriate controller/view/etc.
"""
Expand Down
15 changes: 15 additions & 0 deletions lib/fuschia_web/auth/error_handler.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
defmodule FuschiaWeb.Auth.ErrorHandler do
@moduledoc """
Error Handler for Fuschia Auth
"""

import Plug.Conn, only: [send_resp: 3]

@behaviour Guardian.Plug.ErrorHandler

def auth_error(conn, {type, _reason}, _opts) do
body = Jason.encode!(%{message: to_string(type)})

send_resp(conn, :unauthorized, body)
end
end
59 changes: 59 additions & 0 deletions lib/fuschia_web/auth/guardian.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
defmodule FuschiaWeb.Auth.Guardian do
@moduledoc """
Guardian serializer for Fuschia app
"""

use Guardian, otp_app: :fuschia

alias Fuschia.Context.Users
alias Fuschia.Entities.User

def subject_for_token(user, _claims) do
sub = to_string(user.id)

{:ok, sub}
end

def resource_from_claims(claims) do
user =
claims
|> Map.get("sub")
|> Users.one()

{:ok, user}
end

def authenticate(%{"email" => email, "password" => password}) do
case Users.one_by_email(email) do
nil -> {:error, :unauthorized}
user -> validate_password(user, password)
end
end

def user_claims(%{"email" => email}) do
case user = Users.one_with_permissions(email) do
nil -> {:error, :unauthorized}
_ -> {:ok, User.for_jwt(user)}
end
end

def create_token(user) do
{:ok, token, _claims} = encode_and_sign(user)

{:ok, token}
end

defp validate_password(_user, ""), do: {:error, :unauthorized}

defp validate_password(user, password) when is_binary(password) do
with %User{password_hash: password_hash} = user <- user,
false <- is_nil(password_hash),
true <- Bcrypt.verify_pass(password, user.password_hash) do
create_token(user)
else
_ -> {:error, :unauthorized}
end
end

defp validate_password(_user, _password), do: {:error, :unauthorized}
end
11 changes: 11 additions & 0 deletions lib/fuschia_web/auth/pipeline.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
defmodule FuschiaWeb.Auth.Pipeline do
@moduledoc """
Auth Pipeline for Fuschia app
"""

use Guardian.Plug.Pipeline, otp_app: :fuschia

plug Guardian.Plug.VerifyHeader
plug Guardian.Plug.EnsureAuthenticated
plug Guardian.Plug.LoadResource
end
Loading

0 comments on commit 09ccb12

Please sign in to comment.