Skip to content

Instantly share code, notes, and snippets.

@International
Forked from mihai-ciupina/auth\auth.ex
Created April 12, 2018 10:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save International/11f503e23b082732697a023a6e5b67cd to your computer and use it in GitHub Desktop.
Save International/11f503e23b082732697a023a6e5b67cd to your computer and use it in GitHub Desktop.
guardian authentication and authorization https://stackoverflow.solutions/question/show_question_details/558
defmodule MyApp.Auth do
import Comeonin.Bcrypt, only: [checkpw: 2, dummy_checkpw: 0]
alias MyApp.Accounts.User
alias MyApp.Repo
def login(conn, user) do
conn
|> Guardian.Plug.sign_in(user)
end
def login_by_email_and_pass(conn, email, given_pass) do
user = Repo.get_by(User, email: email)
cond do
user && checkpw(given_pass, user.password_hash) ->
{:ok, login(conn, user)}
user ->
{:error, :unauthorized, conn}
true ->
dummy_checkpw
{:error, :not_found, conn}
end
end
def logout(conn) do
Guardian.Plug.sign_out(conn)
end
end
defmodule MyApp.GuardianErrorHandler do
import MyWeb.Router.Helpers
def unauthenticated(conn, _params) do
conn
|> Phoenix.Controller.put_flash(:error, "You must be signed in to access that page.")
|> Phoenix.Controller.redirect(to: session_path(conn, :new))
end
end
# Guardian configuration
config :guardian, Guardian,
issuer: "MyApp.#{Mix.env}",
ttl: {30, :days},
verify_issuer: true,
serializer: MyApp.GuardianSerializer,
secret_key: to_string(Mix.env) <> "SuPerseCret_aBraCadabrA_bis"
defmodule MyApp.Accounts.User do
use Ecto.Schema
import Ecto.Changeset
alias MyApp.Accounts.User
schema "users" do
field :email, :string
field :name, :string
field :role, :string
field :password, :string, virtual: true
field :password_hash, :string
timestamps()
end
@doc false
def changeset(%User{} = user, attrs) do
user
|> cast(attrs, [:email, :name, :password_hash, :role])
|> validate_required([:email, :name, :password_hash, :role])
|> unique_constraint(:email)
end
def update_changeset(%User{} = user, attrs) do
user
|> changeset(attrs)
|> validate_length(:password, min: 6, max: 100)
|> validate_confirmation(:password, message: "passwords do not match")
|> validate_format(:email, ~r/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/)
|> hash_password
end
def registration_changeset(%User{} = user, attrs) do
user
|> changeset(attrs)
|> validate_required(~w(password)a)
|> validate_length(:password, min: 6, max: 100)
|> validate_confirmation(:password, message: "passwords do not match", required: true)
|> validate_format(:email, ~r/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/)
|> hash_password
end
defp hash_password(changeset) do
case changeset do
%Ecto.Changeset{valid?: true, changes: %{password: password}} ->
put_change(changeset, :password_hash, Comeonin.Bcrypt.hashpwsalt(password))
_ ->
changeset
end
end
end
defmodule MyApp.CurrentUser do
import Plug.Conn
import Guardian.Plug
def init(opts), do: opts
def call(conn, _opts) do
current_user = current_resource(conn)
assign(conn, :current_user, current_user)
end
end
defmodule MyApp.GuardianSerializer do
@behaviour Guardian.Serializer
alias MyApp.Repo
alias MyApp.Accounts.User
def for_token(user = %User{}), do: {:ok, "User:#{user.id}"}
def for_token(_), do: {:error, "Unknown resource type"}
def from_token("User:" <> id), do: {:ok, Repo.get(User, id)}
def from_token(_), do: {:error, "Unknown resource type"}
end
defmodule MyAppWeb.SessionController do
use MyAppWeb, :controller
import Guardian.Plug
plug :scrub_params, "session" when action in ~w(create)a
def new(conn, _) do
current_user = current_resource(conn)
unless current_user do
render conn, "new.html"
else
conn |> redirect(to: page_path(conn, :index))
end
end
def create(conn, %{"session" => %{"email" => email, "password" => password}}) do
case MyApp.Auth.login_by_email_and_pass(conn, email, password) do
{:ok, conn} ->
conn
|> put_flash(:info, "You are signed in!")
|> redirect(to: page_path(conn, :index))
{:error, reason, conn} ->
conn
|> put_flash(:error, reason)
|> render("new.html")
end
end
def delete(conn, _) do
conn
|> MyApp.Auth.logout
|> put_flash(:info, "You signed out!")
|> redirect(to: page_path(conn, :index))
end
end
defmodule MyAppWeb.UserController do
use MyAppWeb, :controller
alias MyApp.Accounts
alias MyApp.Accounts.User
def index(conn, _params) do
users = Accounts.list_users()
render(conn, "index.html", users: users)
end
def new(conn, _params) do
changeset = Accounts.change_user(%User{})
render(conn, "new.html", changeset: changeset)
end
def create(conn, %{"user" => user_params}) do
case Accounts.create_user(user_params) do
{:ok, user} ->
conn
|> put_flash(:info, "User created successfully.")
|> redirect(to: session_path(conn, :new))
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, "new.html", changeset: changeset)
end
end
def show(conn, %{"id" => id}) do
user = Accounts.get_user!(id)
render(conn, "show.html", user: user)
end
def edit(conn, %{"id" => id}) do
user = Accounts.get_user!(id)
changeset = Accounts.change_user(user)
render(conn, "edit.html", user: user, changeset: changeset)
end
def update(conn, %{"id" => id, "user" => user_params}) do
user = Accounts.get_user!(id)
case Accounts.update_user(user, user_params) do
{:ok, user} ->
conn
|> put_flash(:info, "User updated successfully.")
|> redirect(to: user_path(conn, :show, user))
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, "edit.html", user: user, changeset: changeset)
end
end
def delete(conn, %{"id" => id}) do
user = Accounts.get_user!(id)
{:ok, _user} = Accounts.delete_user(user)
conn
|> put_flash(:info, "User deleted successfully.")
|> redirect(to: user_path(conn, :index))
end
end
..
pipeline :with_session do
plug Guardian.Plug.VerifySession
plug Guardian.Plug.LoadResource
plug MyApp.CurrentUser
end
..
pipeline :login_required do
plug Guardian.Plug.EnsureAuthenticated, handler: MyApp.GuardianErrorHandler
end
..
scope "/", MyAppWeb do
pipe_through [:browser, :with_session] # Use the default browser stack
..
resources "/users", UserController, only: [:new, :create]
# registered user zone
scope "/" do
pipe_through [:login_required]
resources "/users", UserController, only: [:edit, :show, :update]
end
end
..
<%= if @current_user do %>
<li><%= @current_user.email %> (<%= @current_user.id %>)</li>
<li>
<%= link "Sign out", to: session_path(@conn, :delete,
@current_user),
method: "delete" %>
</li>
<% else %>
<li><%= link "Register", to: user_path(@conn, :new) %></li>
<li><%= link "Sign in", to: session_path(@conn, :new) %></li>
<% end %>
<%= render "_header.html", conn: @conn, current_user: @current_user %>
<div class="content-wrapper" style="min-height: 916px;">
<div class="content">
<h1>Sign in</h1>
<%= form_for @conn, session_path(@conn, :create), [as: :session], fn f -> %>
<div class="form-group">
<%= text_input f, :email, placeholder: "Email", class: "form-control", type: "email" %>
</div>
<div class="form-group">
<%= password_input f, :password, placeholder: "Password", class: "form-control" %>
</div>
<%= submit "Sign in", class: "btn btn-primary" %>
<% end %>
</div>
</div>
<%= form_for @changeset, @action, fn f -> %>
<%= if @changeset.action do %>
<div class="alert alert-danger">
<p>Oops, something went wrong! Please check the errors below.</p>
</div>
<% end %>
<div class="form-group">
<%= label f, :name, class: "control-label" %>
<%= text_input f, :name, class: "form-control" %>
<%= error_tag f, :name %>
</div>
<div class="form-group">
<%= label f, :email, class: "control-label" %>
<%= email_input f, :email, class: "form-control" %>
<%= error_tag f, :email %>
</div>
<div class="form-group">
<%= label f, :password, class: "control-label" %>
<%= password_input f, :password, placeholder: "Password", class: "form-control" %>
<%= error_tag f, :password %>
</div>
<div class="form-group">
<%= label f, :password_confirmation, class: "control-label" %>
<%= password_input f, :password_confirmation, placeholder: "Password confirmation", class: "form-control" %>
<%= error_tag f, :password_confirmation %>
</div>
<div class="form-group">
<%= submit "Submit", class: "btn btn-primary" %>
</div>
<% end %>
defmodule MyAppWeb.SessionView do
use MyAppWeb, :view
end
defp deps do
[
..
{:comeonin, "~> 2.5"},
{:guardian, "~> 0.12.0"},
..
]
end
# lib/my_app_web/router.ex
..
pipeline :with_session do
plug Guardian.Plug.VerifySession
plug Guardian.Plug.LoadResource
plug MyApp.CurrentUser
end
..
pipeline :login_required do
plug Guardian.Plug.EnsureAuthenticated, handler: MyApp.GuardianErrorHandler
end
..
scope "/", MyAppWeb do
pipe_through [:browser, :with_session] # Use the default browser stack
..
resources "/users", UserController, only: [:new, :create]
# registered user zone
scope "/" do
pipe_through [:login_required]
resources "/users", UserController, only: [:edit, :show, :update]
end
end
..
defmodule MyApp.Repo.Migrations.CreateMyApp.Accounts.User do
use Ecto.Migration
def change do
create table(:users) do
add :name, :string, null: false
add :email, :string, null: false
add :role, :string, null: false
add :password_hash, :string
timestamps()
end
create unique_index(:users, [:email])
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment