Skip to content
Open
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
5 changes: 3 additions & 2 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ defmodule Rumbl.Mixfile do
def application do
[mod: {Rumbl, []},
applications: [:phoenix, :phoenix_pubsub, :phoenix_html, :cowboy, :logger, :gettext,
:phoenix_ecto, :postgrex]]
:phoenix_ecto, :postgrex, :comeonin]]
end

# Specifies which paths to compile per environment.
Expand All @@ -37,7 +37,8 @@ defmodule Rumbl.Mixfile do
{:phoenix_html, "~> 2.6"},
{:phoenix_live_reload, "~> 1.0", only: :dev},
{:gettext, "~> 0.11"},
{:cowboy, "~> 1.0"}]
{:cowboy, "~> 1.0"},
{:comeonin, "~> 2.0"}]
end

# Aliases are shortcuts or tasks specific to the current project.
Expand Down
1 change: 1 addition & 0 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
%{
"comeonin": {:hex, :comeonin, "2.6.0", "74c288338b33205f9ce97e2117bb9a2aaab103a1811d243443d76fdb62f904ac", [:make, :mix], [], "hexpm", "bc72f049a1c61048427f557821fc06e273abf09f6829377541475d7b36ac8ac6"},
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm", "4a0850c9be22a43af9920a71ab17c051f5f7d45c209e40269a1938832510e4d9"},
"cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "f4763bbe08233eceed6f24bc4fcc8d71c17cfeafa6439157c57349aa1bb4f17c"},
"cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], [], "hexpm", "db622da03aa039e6366ab953e31186cc8190d32905e33788a1acb22744e6abd2"},
Expand Down
41 changes: 41 additions & 0 deletions web/controllers/auth.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
defmodule Rumbl.Auth do
import Plug.Conn
import Comeonin.Bcrypt, only: [checkpw: 2, dummy_checkpw: 0]

def init(opts) do
Keyword.fetch!(opts, :repo)
end

def call(conn, repo) do
user_id = get_session(conn, :user_id)
user = user_id && repo.get(Rumbl.User, user_id)
assign(conn, :current_user, user)
end

def login(conn, user) do
conn
|> assign(:current_user, user)
|> put_session(:user_id, user.id)
|> configure_session(renew: true)
end

def login_by_username_and_password(conn, username, password, opts) do
repo = Keyword.fetch!(opts, :repo)
user = repo.get_by(Rumbl.User, username: username)

cond do
user && checkpw(password, user.password_hash) ->
{:ok, login(conn, user)}
user ->
{:error, :unauthorized, conn}
true ->
dummy_checkpw()
{:error, :not_found, conn}
end
end

def logout(conn) do
configure_session(conn, drop: true) # destroy the session, or
# delete_session(conn, :user_id) # to delete only the user_id
end
end
26 changes: 26 additions & 0 deletions web/controllers/session_controller.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
defmodule Rumbl.SessionController do
use Rumbl.Web, :controller

def new(conn, _) do
render conn, "new.html"
end

def create(conn, %{"session" => %{"username" => user, "password" => password}}) do
case Rumbl.Auth.login_by_username_and_password(conn, user, password, repo: Repo) do
{:ok, conn} ->
conn
|> put_flash(:info, "Welcome back!")
|> redirect(to: page_path(conn, :index))
{:error, _reason, conn} ->
conn
|> put_flash(:error, "Invalid username/password combination")
|> render("new.html")
end
end

def delete(conn, _) do
conn
|> Rumbl.Auth.logout()
|> redirect(to: page_path(conn, :index))
end
end
17 changes: 15 additions & 2 deletions web/controllers/user_controller.ex
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
defmodule Rumbl.UserController do
use Rumbl.Web, :controller
alias Rumbl.User
plug :authenticate when action in [:index, :show]

def new(conn, _params) do
changeset = User.changeset(%User{})
render conn, "new.html", changeset: changeset
end

def create(conn, %{"user" => user_params}) do
changeset = User.changeset(%User{}, user_params)
changeset = User.registration_changeset(%User{}, user_params)

case Repo.insert(changeset) do
{:ok, user} ->
conn
|> Rumbl.Auth.login(user)
|> put_flash(:info, "#{user.name} account created")
|> redirect(to: user_path(conn, :index))
{:error, changeset} ->
Expand All @@ -21,12 +23,23 @@ defmodule Rumbl.UserController do
end

def index(conn, _params) do
users = Repo.all(Rumbl.User)
users = Repo.all(User)
render conn, "index.html", users: users
end

def show(conn, %{"id" => id}) do
user = Repo.get(User, id)
render conn, "show.html", user: user
end

defp authenticate(conn, _opts) do
if conn.assigns.current_user do
conn
else
conn
|> put_flash(:error, "You must be logged in to access that page")
|> redirect(to: page_path(conn, :index))
|> halt()
end
end
end
16 changes: 16 additions & 0 deletions web/models/user.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,20 @@ defmodule Rumbl.User do
|> cast(params, ~w(name username), [])
|> validate_length(:username, min: 1, max: 20)
end

def registration_changeset(model, params) do
model
|> changeset(params)
|> cast(params, ~w(password), [])
|> validate_length(:password, min: 6, max: 100)
|> put_pass_hash()
end

defp put_pass_hash(changeset) do
case changeset do
%Ecto.Changeset{valid?: true, changes: %{password: pass}} ->
put_change(changeset, :password_hash, Comeonin.Bcrypt.hashpwsalt(pass))
_ -> changeset
end
end
end
2 changes: 2 additions & 0 deletions web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ defmodule Rumbl.Router do
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
plug Rumbl.Auth, repo: Rumbl.Repo
end

pipeline :api do
Expand All @@ -19,6 +20,7 @@ defmodule Rumbl.Router do
get "/", PageController, :index

resources "/users", UserController, only: [:new, :create, :index, :show]
resources "/sessions", SessionController, only: [:new, :create, :delete]
end

# Other scopes may use custom stacks.
Expand Down
20 changes: 13 additions & 7 deletions web/templates/layout/app.html.eex
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,20 @@

<body>
<div class="container">
<header class="header">
<nav role="navigation">
<ul class="nav nav-pills pull-right">
<li><a href="http://www.phoenixframework.org/docs">Get Started</a></li>
</ul>
</nav>
<div class="header">
<ol class="breadcrumb text-right">
<%= if @current_user do %>
<li><%= @current_user.username %></li>
<li>
<%= link "Log out", to: session_path(@conn, :delete, @current_user), method: "delete" %>
</li>
<% else %>
<li><%= link "Register", to: user_path(@conn, :new) %></li>
<li><%= link "Log in", to: session_path(@conn, :new) %></li>
<% end %>
</ol>
<span class="logo"></span>
</header>
</div>

<p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
Expand Down
11 changes: 11 additions & 0 deletions web/templates/session/new.html.eex
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<h1>Login</h1>

<%= form_for @conn, session_path(@conn, :create), [as: :session], fn f -> %>
<div class="form-group">
<%= text_input f, :username, placeholder: "Username", class: "form-control" %>
</div>
<div class="form-group">
<%= password_input f, :password, placeholder: "Password", class: "form-control" %>
</div>
<%= submit "Log in", class: "btn btn-primary" %>
<% end %>
3 changes: 3 additions & 0 deletions web/views/session_view.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
defmodule Rumbl.SessionView do
use Rumbl.Web, :view
end