Skip to content

Commit

Permalink
Allow manual control over authentication process
Browse files Browse the repository at this point in the history
  • Loading branch information
lexmag committed Nov 9, 2014
1 parent de376d9 commit 683f9f1
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 30 deletions.
33 changes: 31 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,45 @@ Add Blaguth on top of a Plug Stack as follows:
```elixir
defmodule CavePlug do
import Plug.Conn
use Plug.Router
use Plug.Builder

plug Blaguth, realm: "Secret",
credentials: {"Ali Baba", "Open Sesame"}

plug :index

def index(conn, _opts) do
send_resp(conn, 200, "Hello Ali Baba")
end
end
```

If you need more precise control over authentication process:

```elixir
defmodule AdvancedPlug do
import Plug.Conn
use Plug.Router

plug Blaguth

plug :match
plug :dispatch

get "/" do
send_resp(conn, 200, "Hello Ali Baba")
send_resp(conn, 200, "Everyone can see me!")
end

get "/secret" do
if authenticated?(conn.assigns) do
send_resp(conn, 200, "I'm only accessible if you know the password")
else
Blaguth.halt_with_login(conn, "Secret")
end
end

defp authenticated?(%{credentials: {user, pass}}) do
User.authenticate(user, pass)
end
end
```
Expand Down
21 changes: 16 additions & 5 deletions lib/blaguth.ex
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
defmodule Blaguth do
alias Plug.Conn

def init([]), do: nil
def init(opts) do
{realm, opts} = Keyword.pop(opts, :realm, "Restricted Area")

init(realm, opts)
end

defp init(realm, credentials: {username, password}) when is_binary(realm) do
%{realm: realm, creds: username <> ":" <> password}
defp init(realm, credentials: {user, pass}) when is_binary(realm) do
{realm, user <> ":" <> pass}
end

def call(conn, config) do
Expand All @@ -30,15 +31,25 @@ defmodule Blaguth do

defp decode_creds({conn, _}), do: {conn, nil}

defp assert_creds({conn, val}, %{creds: val}),
defp assert_creds({conn, val}, nil) do
destructure([user, pass], split_creds(val))

Conn.assign(conn, :credentials, {user, pass})
end

defp assert_creds({conn, val}, {_, val}),
do: conn

defp assert_creds({conn, _}, %{realm: realm}),
defp assert_creds({conn, _}, {realm, _}),
do: halt_with_login(conn, realm)

defp halt_with_login(conn, realm) do
def halt_with_login(conn, realm) do
Conn.put_resp_header(conn, "Www-Authenticate", "Basic realm=\"" <> realm <> "\"")
|> Conn.send_resp(401, "HTTP Basic: Access denied.\n")
|> Conn.halt
end

defp split_creds(nil), do: []
defp split_creds(val),
do: :binary.split(val, ":")
end
83 changes: 60 additions & 23 deletions test/blaguth_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -17,53 +17,90 @@ defmodule BlaguthTest do
end
end

defp call(conn) do
CavePlug.call(conn, [])
defmodule PassthruPlug do
import Plug.Conn
use Plug.Builder

plug Blaguth

plug :authenticate
plug :index

defp authenticate(conn, _opts) do
case conn.assigns[:credentials] do
{"James", "837737"} -> conn
_ -> Blaguth.halt_with_login(conn, "Top secret")
end
end

defp index(conn, _opts) do
assign(conn, :logged_in, true)
|> send_resp(200, "Wellcome James")
end
end

defp assert_unauthorized(conn) do
defp call(plug, headers) do
conn(:get, "/", [], headers: headers)
|> plug.call([])
end

defp assert_unauthorized(conn, realm) do
assert conn.status == 401
assert get_resp_header(conn, "Www-Authenticate") == ["Basic realm=\"Secret\""]
assert get_resp_header(conn, "Www-Authenticate") == ["Basic realm=\"" <> realm <> "\""]
refute conn.assigns[:logged_in]
end

defp assert_authorized(conn, content) do
assert conn.status == 200
assert conn.resp_body == content
assert conn.assigns[:logged_in]
end

defp auth_header(creds) do
{"authorization", "Basic " <> Base.encode64(creds)}
end

test "request without credentials" do
conn = call(conn(:get, "/"))
conn = call(CavePlug, [])

assert_unauthorized conn
assert_unauthorized conn, "Secret"
end

test "request with invalid credentials" do
headers = [{"authorization", "Basic " <> Base.encode64("Thief:Open Sesame")}]
conn = call(CavePlug, [auth_header("Thief:Open Sesame")])

conn = call(conn(:get, "/", [], headers: headers))

assert_unauthorized conn
assert_unauthorized conn, "Secret"
end

test "request with valid credentials" do
headers = [{"authorization", "Basic " <> Base.encode64("Ali Baba:Open Sesame")}]

conn = call(conn(:get, "/", [], headers: headers))
conn = call(CavePlug, [auth_header("Ali Baba:Open Sesame")])

assert conn.status == 200
assert conn.resp_body == "Hello Ali Baba"
assert conn.assigns[:logged_in]
assert_authorized conn, "Hello Ali Baba"
end

test "request with malformed credentials" do
headers = [{"authorization", "Basic Zm9)"}]

conn = call(conn(:get, "/", [], headers: headers))
conn = call(CavePlug, [{"authorization", "Basic Zm9)"}])

assert_unauthorized conn
assert_unauthorized conn, "Secret"
end

test "request with wrong scheme" do
headers = [{"authorization", "Bearer " <> Base.encode64("Ali Baba:Open Sesame")}]
conn = call(CavePlug, [
{"authorization", "Bearer " <> Base.encode64("Ali Baba:Open Sesame")}
])

assert_unauthorized conn, "Secret"
end

test "manual handling for invalid credentials" do
conn = call(PassthruPlug, [auth_header("James")])

assert_unauthorized conn, "Top secret"
end

conn = call(conn(:get, "/", [], headers: headers))
test "manual handling for valid credentials" do
conn = call(PassthruPlug, [auth_header("James:837737")])

assert_unauthorized conn
assert_authorized conn, "Wellcome James"
end
end

0 comments on commit 683f9f1

Please sign in to comment.