Skip to content

Commit

Permalink
Merge branch 'user-change' into elm-console
Browse files Browse the repository at this point in the history
  • Loading branch information
rjsamson committed Mar 24, 2016
2 parents 7fed983 + 4a3825e commit a4ed18b
Show file tree
Hide file tree
Showing 12 changed files with 178 additions and 3 deletions.
5 changes: 3 additions & 2 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ defmodule Stackfooter.Mixfile do
def application do
[mod: {Stackfooter, []},
applications: [:phoenix, :phoenix_html, :cowboy, :logger, :gettext,
:phoenix_ecto, :postgrex, :tzdata, :beaker]]
:phoenix_ecto, :postgrex, :tzdata, :beaker, :comeonin]]
end

# Specifies which paths to compile per environment.
Expand All @@ -41,7 +41,8 @@ defmodule Stackfooter.Mixfile do
{:cowboy, "~> 1.0"},
{:timex, "~> 1.0.0"},
{:beaker, ">= 1.2.0"},
{:excoveralls, "~> 0.4", only: :test}]
{:excoveralls, "~> 0.4", only: :test},
{:comeonin, "~> 2.0"}]
end

# Aliases are shortcut 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
Expand Up @@ -2,6 +2,7 @@
"bureaucrat": {:hex, :bureaucrat, "0.1.2"},
"certifi": {:hex, :certifi, "0.3.0"},
"combine": {:hex, :combine, "0.7.0"},
"comeonin": {:hex, :comeonin, "2.2.0"},
"connection": {:hex, :connection, "1.0.2"},
"cowboy": {:hex, :cowboy, "1.0.4"},
"cowlib": {:hex, :cowlib, "1.0.2"},
Expand Down
14 changes: 14 additions & 0 deletions priv/repo/migrations/20160324014617_create_user.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
defmodule Stackfooter.Repo.Migrations.CreateUser do
use Ecto.Migration

def change do
create table(:users) do
add :username, :string
add :password_hash, :string
add :api_keys, {:array, :string}

timestamps
end

end
end
18 changes: 18 additions & 0 deletions test/models/user_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
defmodule Stackfooter.UserTest do
use Stackfooter.ModelCase

alias Stackfooter.User

@valid_attrs %{api_keys: ["some content"], password_hash: "some content", username: "some content"}
@invalid_attrs %{}

test "changeset with valid attributes" do
changeset = User.changeset(%User{}, @valid_attrs)
assert changeset.valid?
end

test "changeset with invalid attributes" do
changeset = User.changeset(%User{}, @invalid_attrs)
refute changeset.valid?
end
end
5 changes: 4 additions & 1 deletion web/controllers/console_controller.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
defmodule Stackfooter.ConsoleController do
use Stackfooter.Web, :controller

plug :authenticate_user

def index(conn, _params) do
render conn, "index.html", api_key: "1234567"
key = conn.assigns.current_user.api_keys |> List.first
render conn, "index.html", api_key: key
end
end
25 changes: 25 additions & 0 deletions web/controllers/session_controller.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
defmodule Stackfooter.SessionController do
use Stackfooter.Web, :controller

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

def create(conn, %{"session" => %{"username" => user, "password" => pass}}) do
case Stackfooter.UserAuth.login_by_username_and_pass(conn, user, pass, repo: Repo) do
{:ok, conn} ->
conn
|> redirect(to: console_path(conn, :index))
{:error, _reason, conn} ->
conn
|> put_flash(:error, "Invalid username or password")
|> render("new.html")
end
end

def delete(conn, _) do
conn
|> Stackfooter.UserAuth.logout()
|> redirect(to: session_path(conn, :new))
end
end
36 changes: 36 additions & 0 deletions web/models/user.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
defmodule Stackfooter.User do
use Stackfooter.Web, :model

schema "users" do
field :username, :string
field :password_hash, :string
field :password, :string, virtual: true
field :api_keys, {:array, :string}

timestamps
end

@required_fields ~w(username password api_keys)
@optional_fields ~w()

@doc """
Creates a changeset based on the `model` and `params`.
If no params are provided, an invalid changeset is returned
with no validation performed.
"""
def changeset(model, params \\ :empty) do
model
|> cast(params, @required_fields, @optional_fields)
|> 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
60 changes: 60 additions & 0 deletions web/plugs/user_auth.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
defmodule Stackfooter.UserAuth do
import Plug.Conn
import Comeonin.Bcrypt, only: [checkpw: 2, dummy_checkpw: 0]
import Phoenix.Controller
alias Stackfooter.Router.Helpers

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

def call(conn, repo) do
user_id = get_session(conn, :user_id)

cond do
user = conn.assigns[:current_user] ->
conn
user = user_id && repo.get(Stackfooter.User, user_id) ->
assign(conn, :current_user, user)
true ->
assign(conn, :current_user, nil)
end
end

def authenticate_user(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: Helpers.session_path(conn, :new))
|> halt()
end
end

def login_by_username_and_pass(conn, username, given_pass, opts) do
repo = Keyword.fetch!(opts, :repo)
user = repo.get_by(Stackfooter.User, username: username)

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 login(conn, user) do
conn
|> assign(:current_user, user)
|> put_session(:user_id, user.id)
|> configure_session(renew: true)
end

def logout(conn) do
configure_session(conn, drop: true)
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 Stackfooter.Router do
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
plug Stackfooter.UserAuth, repo: Stackfooter.Repo
end

pipeline :api do
Expand All @@ -19,6 +20,7 @@ defmodule Stackfooter.Router do
pipe_through :browser # Use the default browser stack

get "/", PageController, :index
resources "/sessions", SessionController, only: [:new, :create, :delete]
get "/ticker", TickerController, :index
get "/console", ConsoleController, :index
end
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 Stackfooter.SessionView do
use Stackfooter.Web, :view
end
1 change: 1 addition & 0 deletions web/web.ex
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ defmodule Stackfooter.Web do

import Stackfooter.Router.Helpers
import Stackfooter.Gettext
import Stackfooter.UserAuth, only: [authenticate_user: 2]
end
end

Expand Down

0 comments on commit a4ed18b

Please sign in to comment.