Skip to content

Commit

Permalink
Merge pull request #4 from inaka/cabol.111223616.auth_plug
Browse files Browse the repository at this point in the history
- Added authentication plug.
  • Loading branch information
igaray committed Jan 8, 2016
2 parents 32cdbae + ec62ccb commit a95f1ff
Show file tree
Hide file tree
Showing 14 changed files with 296 additions and 26 deletions.
15 changes: 12 additions & 3 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,16 @@ defmodule CredoServer.Mixfile do
# Configuration for the OTP application.
def application do
[mod: {CredoServer, []},
applications: [:phoenix, :phoenix_html, :cowboy, :logger,
:phoenix_ecto, :postgrex]]
applications: [
:phoenix,
:phoenix_html,
:cowboy,
:logger,
:phoenix_ecto,
:postgrex,
:tentacat
]
]
end

# Specifies which paths to compile per environment.
Expand All @@ -31,7 +39,8 @@ defmodule CredoServer.Mixfile do
{:postgrex, ">= 0.0.0"},
{:phoenix_html, "~> 2.1"},
{:phoenix_live_reload, "~> 1.0", only: :dev},
{:cowboy, "~> 1.0"}]
{:cowboy, "~> 1.0"},
{:tentacat, "~> 0.3.1"}]
end

# Aliases are shortcut or tasks specific to the current project.
Expand Down
13 changes: 11 additions & 2 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
%{"cowboy": {:hex, :cowboy, "1.0.4"},
%{"certifi": {:hex, :certifi, "0.3.0"},
"cowboy": {:hex, :cowboy, "1.0.4"},
"cowlib": {:hex, :cowlib, "1.0.2"},
"decimal": {:hex, :decimal, "1.1.0"},
"ecto": {:hex, :ecto, "1.0.7"},
"exjsx": {:hex, :exjsx, "3.2.0"},
"fs": {:hex, :fs, "0.9.2"},
"hackney": {:hex, :hackney, "1.4.8"},
"httpoison": {:hex, :httpoison, "0.8.0"},
"idna": {:hex, :idna, "1.0.3"},
"jsx": {:hex, :jsx, "2.6.2"},
"mimerl": {:hex, :mimerl, "1.0.2"},
"phoenix": {:hex, :phoenix, "1.0.4"},
"phoenix_ecto": {:hex, :phoenix_ecto, "1.2.0"},
"phoenix_html": {:hex, :phoenix_html, "2.2.0"},
Expand All @@ -11,4 +18,6 @@
"poison": {:hex, :poison, "1.5.0"},
"poolboy": {:hex, :poolboy, "1.5.1"},
"postgrex": {:hex, :postgrex, "0.9.1"},
"ranch": {:hex, :ranch, "1.2.0"}}
"ranch": {:hex, :ranch, "1.2.0"},
"ssl_verify_hostname": {:hex, :ssl_verify_hostname, "1.0.5"},
"tentacat": {:hex, :tentacat, "0.3.1"}}
8 changes: 0 additions & 8 deletions test/controllers/page_controller_test.exs

This file was deleted.

68 changes: 68 additions & 0 deletions test/controllers/repository_controller_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
defmodule CredoServer.RepositoryControllerTest do
use CredoServer.ConnCase

alias CredoServer.Repository

@valid_attrs %{
full_name: "some content",
github_id: 42,
html_url: "some content",
name: "some content",
private: true,
status: "some content"
}
@invalid_attrs %{}

setup do
conn = conn() |> put_req_header("accept", "application/json")
{:ok, conn: conn}
end

test "lists all entries on index", %{conn: conn} do
conn = get conn, repository_path(conn, :index)
assert json_response(conn, 200)["data"] == []
end

test "shows chosen resource", %{conn: conn} do
repository = Repo.insert! %Repository{}
conn = get conn, repository_path(conn, :show, repository)
assert json_response(conn, 200)["data"] == %{"id" => repository.id}
end

test "does not show resource and instead throw error when id is nonexistent", %{conn: conn} do
assert_raise Ecto.NoResultsError, fn ->
get conn, repository_path(conn, :show, -1)
end
end

test "creates and renders resource when data is valid", %{conn: conn} do
conn = post conn, repository_path(conn, :create), repository: @valid_attrs
assert json_response(conn, 201)["data"]["id"]
assert Repo.get_by(Repository, @valid_attrs)
end

test "does not create resource and renders errors when data is invalid", %{conn: conn} do
conn = post conn, repository_path(conn, :create), repository: @invalid_attrs
assert json_response(conn, 422)["errors"] != %{}
end

test "updates and renders chosen resource when data is valid", %{conn: conn} do
repository = Repo.insert! %Repository{}
conn = put conn, repository_path(conn, :update, repository), repository: @valid_attrs
assert json_response(conn, 200)["data"]["id"]
assert Repo.get_by(Repository, @valid_attrs)
end

test "does not update chosen resource and renders errors when data is invalid", %{conn: conn} do
repository = Repo.insert! %Repository{}
conn = put conn, repository_path(conn, :update, repository), repository: @invalid_attrs
assert json_response(conn, 422)["errors"] != %{}
end

test "deletes chosen resource", %{conn: conn} do
repository = Repo.insert! %Repository{}
conn = delete conn, repository_path(conn, :delete, repository)
assert response(conn, 204)
refute Repo.get(Repository, repository.id)
end
end
9 changes: 8 additions & 1 deletion test/models/repository_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,14 @@ defmodule CredoServer.RepoTest do

alias CredoServer.Repository

@valid_attrs %{full_name: "some content", github_id: 42, html_url: "some content", name: "some content", private: true, status: "some content"}
@valid_attrs %{
full_name: "some content",
github_id: 42,
html_url: "some content",
name: "some content",
private: true,
status: "some content"
}
@invalid_attrs %{}

test "changeset with valid attributes" do
Expand Down
10 changes: 9 additions & 1 deletion test/models/user_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,15 @@ defmodule CredoServer.UserTest do

alias CredoServer.User

@valid_attrs %{auth_expires: "2010-04-17 14:00:00", auth_token: "some content", email: "some content", email_code: "some content", github_token: "some content", name: "some content", synced_at: "2010-04-17 14:00:00", username: "some content"}
@valid_attrs %{
auth_expires: "2010-04-17 14:00:00",
auth_token: "some content",
email: "some content",
email_code: "some content",
github_token: "some content",
name: "some content",
synced_at: "2010-04-17 14:00:00",
username: "some content"}
@invalid_attrs %{}

test "changeset with valid attributes" do
Expand Down
57 changes: 57 additions & 0 deletions web/controllers/repository_controller.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
defmodule CredoServer.RepositoryController do
use CredoServer.Web, :controller

alias CredoServer.Repository

plug :scrub_params, "repository" when action in [:create, :update]

def index(conn, _params) do
repositories = Repo.all(Repository)
render(conn, "index.json", repositories: repositories)
end

def create(conn, %{"repository" => repository_params}) do
changeset = Repository.changeset(%Repository{}, repository_params)

case Repo.insert(changeset) do
{:ok, repository} ->
conn
|> put_status(:created)
|> put_resp_header("location", repository_path(conn, :show, repository))
|> render("show.json", repository: repository)
{:error, changeset} ->
conn
|> put_status(:unprocessable_entity)
|> render(CredoServer.ChangesetView, "error.json", changeset: changeset)
end
end

def show(conn, %{"id" => id}) do
repository = Repo.get!(Repository, id)
render(conn, "show.json", repository: repository)
end

def update(conn, %{"id" => id, "repository" => repository_params}) do
repository = Repo.get!(Repository, id)
changeset = Repository.changeset(repository, repository_params)

case Repo.update(changeset) do
{:ok, repository} ->
render(conn, "show.json", repository: repository)
{:error, changeset} ->
conn
|> put_status(:unprocessable_entity)
|> render(CredoServer.ChangesetView, "error.json", changeset: changeset)
end
end

def delete(conn, %{"id" => id}) do
repository = Repo.get!(Repository, id)

# Here we use delete! (with a bang) because we expect
# it to always work (and if it does not, it will raise).
Repo.delete!(repository)

send_resp(conn, :no_content, "")
end
end
2 changes: 1 addition & 1 deletion web/models/repository.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ defmodule CredoServer.Repository do
use CredoServer.Web, :model

schema "repositories" do
belongs_to :user_id, CredoServer.User
belongs_to :user, CredoServer.User
field :github_id, :integer
field :name, :string
field :full_name, :string
Expand Down
22 changes: 22 additions & 0 deletions web/models/user.ex
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
defmodule CredoServer.User do
use CredoServer.Web, :model

import Ecto.Query
alias CredoServer.Repo

schema "users" do
field :name, :string
Expand Down Expand Up @@ -27,4 +30,23 @@ defmodule CredoServer.User do
model
|> cast(params, @required_fields, @optional_fields)
end

@doc """
Gets an user that matches with the given `token` and token hasn't expired.
"""
def find_by_auth_token(token) do
now = Ecto.DateTime.utc
users =
from(u in CredoServer.User)
|> where([u], u.auth_token == ^token and u.auth_expires > ^now)
|> Repo.all

case users do
[one] -> one
[] -> nil
other -> raise Ecto.MultipleResultsError,
queryable: CredoServer.User,
count: length(other)
end
end
end
78 changes: 78 additions & 0 deletions web/plugs/auth_plug.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
defmodule CredoServer.Plug.Auth do
@moduledoc """
A Plug to authenticate HTTP requests.
## Examples
CredoServer.Plug.Auth.call(conn, [])
"""

@behaviour Plug

import Plug.Conn
require Logger
alias CredoServer.Repo
alias CredoServer.User

## Plug callbacks

def init(opts) do
opts
end

def call(conn, _opts) do
case get_token(conn) do
nil ->
assign(conn, :user, nil)
token ->
user = User.find_by_auth_token(token)
assign(conn, :user, user)
end
end

## Private functions

defp get_token(conn) do
new_conn = fetch_cookies conn
new_conn.req_cookies["token"]
end

@doc """
Authentication macro. This macro contains a function-based plug
to be used in the controllers in order to be able to authenticate
the HTTP request in each controller.
## Example
defmodule CredoServer.TestController do
use CredoServer.Web, :controller
import CredoServer.Router.Helpers
plug :authenticate when action in [:show]
def show(conn, _opts) do
# logic
end
end
"""
defmacro __using__(opts) do
quote bind_quoted: [opts: opts] do

def init(opts) do
opts
end

def authenticate(conn, _opts) do
if conn.assigns.user do
conn
else
conn
|> send_resp(401, "UNAUTHORIZED")
|> halt()
end
end

defoverridable [init: 1, authenticate: 2]
end
end
end
15 changes: 5 additions & 10 deletions web/router.ex
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
defmodule CredoServer.Router do
use CredoServer.Web, :router

pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_flash
plug :protect_from_forgery
plug :put_secure_browser_headers
end

pipeline :api do
plug :accepts, ["json"]
plug CredoServer.Plug.Auth
end

scope "/", CredoServer do
pipe_through :browser # Use the default browser stack
pipe_through :api

resources "/repos", RepositoryController

get "/", PageController, :index
get "/test", TestController, :show
end
end
9 changes: 9 additions & 0 deletions web/views/changeset_view.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
defmodule CredoServer.ChangesetView do
use CredoServer.Web, :view

def render("error.json", %{changeset: changeset}) do
# When encoded, the changeset returns its errors
# as a JSON object. So we just pass it forward.
%{errors: changeset}
end
end
15 changes: 15 additions & 0 deletions web/views/repository_view.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
defmodule CredoServer.RepositoryView do
use CredoServer.Web, :view

def render("index.json", %{repositories: repositories}) do
%{data: render_many(repositories, CredoServer.RepositoryView, "repository.json")}
end

def render("show.json", %{repository: repository}) do
%{data: render_one(repository, CredoServer.RepositoryView, "repository.json")}
end

def render("repository.json", %{repository: repository}) do
%{id: repository.id}
end
end
Loading

0 comments on commit a95f1ff

Please sign in to comment.