Add authentication for web UI

This commit is contained in:
Shadowfacts 2019-03-23 19:42:38 -04:00
parent 762675b38c
commit 74a82967dd
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
10 changed files with 210 additions and 43 deletions

View File

@ -32,9 +32,9 @@ defmodule Frenzy.UpdateFeeds do
defp schedule_update() do defp schedule_update() do
# 15 minutes # 15 minutes
# Process.send_after(self(), :update_feeds, 15 * 60 * 1000) Process.send_after(self(), :update_feeds, 15 * 60 * 1000)
# 1 minutes # 1 minutes
Process.send_after(self(), :update_feeds, 60 * 1000) # Process.send_after(self(), :update_feeds, 60 * 1000)
end end
defp update_feeds() do defp update_feeds() do

View File

@ -5,8 +5,28 @@ defmodule FrenzyWeb.FeedController do
alias FrenzyWeb.Endpoint alias FrenzyWeb.Endpoint
import Ecto.Query import Ecto.Query
plug :user_owns_feed
defp user_owns_feed(%Plug.Conn{path_params: %{"id" => id}} = conn, _opts) do
user = conn.assigns[:user]
feed = Repo.get(Feed, id)
if Enum.any?(user.groups, fn g -> g.id == feed.group_id end) do
conn
|> assign(:feed, feed)
else
conn
|> put_flash(:error, "You do not have permission to access that resource.")
|> redirect(to: Routes.group_path(Endpoint, :index))
|> halt()
end
end
defp user_owns_feed(conn, _opts), do: conn
def show(conn, %{"id" => id}) do def show(conn, %{"id" => id}) do
feed = Repo.get(Feed, id) |> Repo.preload(:filter) feed = conn.assigns[:feed]
items = Repo.all(from Item, where: [feed_id: ^id, tombstone: false], order_by: [desc: :date]) items = Repo.all(from Item, where: [feed_id: ^id, tombstone: false], order_by: [desc: :date])
render(conn, "show.html", %{ render(conn, "show.html", %{
@ -32,28 +52,28 @@ defmodule FrenzyWeb.FeedController do
redirect(conn, to: Routes.group_path(Endpoint, :show, group_id)) redirect(conn, to: Routes.group_path(Endpoint, :show, group_id))
end end
def delete(conn, %{"id" => id}) do def delete(conn, _params) do
feed = Repo.get(Feed, id) feed = conn.assigns[:feed]
{:ok, _} = Repo.delete(feed) {:ok, _} = Repo.delete(feed)
redirect(conn, to: Routes.group_path(Endpoint, :show, feed.group_id)) redirect(conn, to: Routes.group_path(Endpoint, :show, feed.group_id))
end end
def enable_filter(conn, %{"id" => id}) do def enable_filter(conn, _params) do
feed = Repo.get(Feed, id) |> Repo.preload(:filter) feed = conn.assigns[:feed] |> Repo.preload(:filter)
changeset = Feed.changeset(feed, %{filter_enabled: true}) changeset = Feed.changeset(feed, %{filter_enabled: true})
Repo.update(changeset) Repo.update(changeset)
redirect(conn, to: Routes.feed_path(Endpoint, :show, id)) redirect(conn, to: Routes.feed_path(Endpoint, :show, feed.id))
end end
def disable_filter(conn, %{"id" => id}) do def disable_filter(conn, _params) do
feed = Repo.get(Feed, id) |> Repo.preload(:filter) feed = conn.assigns[:feed] |> Repo.preload(:filter)
changeset = Feed.changeset(feed, %{filter_enabled: false}) changeset = Feed.changeset(feed, %{filter_enabled: false})
Repo.update(changeset) Repo.update(changeset)
redirect(conn, to: Routes.feed_path(Endpoint, :show, id)) redirect(conn, to: Routes.feed_path(Endpoint, :show, feed.id))
end end
def refresh(conn, %{"id" => id}) do def refresh(conn, _params) do
feed = Repo.get(Feed, id) |> Repo.preload(:filter) feed = conn.assgins[:feed] |> Repo.preload(:filter)
feed = Frenzy.UpdateFeeds.refresh(Frenzy.UpdateFeeds, feed) feed = Frenzy.UpdateFeeds.refresh(Frenzy.UpdateFeeds, feed)
redirect(conn, to: Routes.feed_path(Endpoint, :show, feed.id)) redirect(conn, to: Routes.feed_path(Endpoint, :show, feed.id))
end end

View File

@ -3,20 +3,39 @@ defmodule FrenzyWeb.GroupController do
alias Frenzy.{Repo, Group, Feed} alias Frenzy.{Repo, Group, Feed}
alias FrenzyWeb.Router.Helpers, as: Routes alias FrenzyWeb.Router.Helpers, as: Routes
alias FrenzyWeb.Endpoint alias FrenzyWeb.Endpoint
import Ecto.Query
plug :user_owns_group
defp user_owns_group(%Plug.Conn{path_params: %{"id" => id}} = conn, _opts) do
user = conn.assigns[:user]
group = Repo.get(Group, id)
if Enum.any?(user.groups, fn g -> g.id == group.id end) do
conn
|> assign(:group, group)
else
conn
|> put_flash(:error, "You do not have permission to acess that resource.")
|> redirect(to: Routes.group_path(Endpoint, :index))
|> halt()
end
end
defp user_owns_group(conn, _opts), do: conn
def index(conn, _params) do def index(conn, _params) do
groups = Repo.all(from group in Group, preload: [:feeds]) groups = conn.assigns[:user].groups
render(conn, "index.html", groups: groups) render(conn, "index.html", groups: groups)
end end
def show(conn, %{"id" => id}) do def show(conn, _params) do
group = Repo.get(Group, id) |> Repo.preload(:feeds) group = conn.assigns[:group] |> Repo.preload(:feeds)
create_feed_changeset = create_feed_changeset =
Feed.changeset( Feed.changeset(
%Feed{ %Feed{
group_id: id group_id: group.id
}, },
%{} %{}
) )
@ -34,15 +53,21 @@ defmodule FrenzyWeb.GroupController do
end end
def create(conn, %{"group" => %{"title" => title}}) do def create(conn, %{"group" => %{"title" => title}}) do
changeset = Group.changeset(%Group{title: title}, %{}) user = conn.assigns[:user]
changeset =
Ecto.build_assoc(user, :groups, %{
title: title
})
{:ok, group} = Repo.insert(changeset) {:ok, group} = Repo.insert(changeset)
redirect(conn, to: Routes.group_path(Endpoint, :index)) redirect(conn, to: Routes.group_path(Endpoint, :show, group.id))
end end
def delete(conn, %{"id" => id}) do def delete(conn, _params) do
group = Repo.get(Group, id) group = conn.assigns[:group]
{:ok, _} = Repo.delete(group) {:ok, _} = Repo.delete(group)
redirect(conn, to: Routes.group_path(Endpoint, :index)) redirect(conn, to: Routes.group_path(Endpoint, :index))
end end

View File

@ -4,33 +4,60 @@ defmodule FrenzyWeb.ItemController do
alias FrenzyWeb.Router.Helpers, as: Routes alias FrenzyWeb.Router.Helpers, as: Routes
alias FrenzyWeb.Endpoint alias FrenzyWeb.Endpoint
def show(conn, %{"id" => id}) do plug :user_owns_item
defp user_owns_item(%Plug.Conn{path_params: %{"id" => id}} = conn, _opts) do
user = conn.assigns[:user]
item = Repo.get(Item, id) item = Repo.get(Item, id)
feed = Repo.get(Feed, item.feed_id)
feeds = Enum.flat_map(user.groups, fn g -> g.feeds end)
if Enum.any?(feeds, fn f -> f.id == item.feed_id end) do
conn
|> assign(:item, item)
else
conn
|> put_flash(:error, "You do not have permission to access that resource.")
|> redirect(to: Routes.group_path(Endpoint, :index))
|> halt()
end
end
defp user_owns_item(conn, _opts), do: conn
def show(conn, _params) do
item = conn.assigns[:item] |> Repo.preload(:feed)
render(conn, "show.html", %{ render(conn, "show.html", %{
item: item, item: item,
feed: feed feed: item.feed
}) })
end end
def read(conn, %{"id" => id}) do def read(conn, _params) do
item = Repo.get(Item, id) |> Repo.preload(:feed) item = conn.assigns[:item] |> Repo.preload(:feed)
changeset = Item.changeset(item, %{
read: true, changeset =
read_date: Timex.now Item.changeset(item, %{
read: true,
read_date: Timex.now()
}) })
Repo.update(changeset) Repo.update(changeset)
redirect(conn, to: Routes.item_path(Endpoint, :show, id)) redirect(conn, to: Routes.item_path(Endpoint, :show, item.id))
end end
def unread(conn, %{"id" => id}) do def unread(conn, _params) do
item = Repo.get(Item, id) |> Repo.preload(:feed) item = conn.assigns[:item] |> Repo.preload(:feed)
changeset = Item.changeset(item, %{
read: false, changeset =
read_date: nil Item.changeset(item, %{
read: false,
read_date: nil
}) })
Repo.update(changeset)
redirect(conn, to: Routes.item_path(Endpoint, :show, id))
end
Repo.update(changeset)
redirect(conn, to: Routes.item_path(Endpoint, :show, item.id))
end
end end

View File

@ -0,0 +1,31 @@
defmodule FrenzyWeb.LoginController do
use FrenzyWeb, :controller
alias Frenzy.{Repo, User}
alias FrenzyWeb.Router.Helpers, as: Routes
alias FrenzyWeb.Endpoint
import Ecto.Query
def login(conn, _params) do
render(conn, "login.html")
end
@error_message "Invalid username or password"
def login_post(conn, %{"username" => username, "password" => password}) do
user = Repo.get_by(User, username: username)
case Bcrypt.check_pass(user, password) do
{:ok, user} ->
user_token = Phoenix.Token.sign(Endpoint, "user token", user.id)
conn
|> put_session(:user_token, user_token)
|> redirect(to: Routes.group_path(Endpoint, :index))
{:error, _reason} ->
conn
|> put_flash(:error, @error_message)
|> redirect(to: Routes.login_path(Endpoint, :login))
end
end
end

View File

@ -0,0 +1,31 @@
defmodule FrenzyWeb.Plug.Authenticate do
import Plug.Conn
alias Frenzy.{Repo, User}
alias FrenzyWeb.Router.Helpers, as: Routes
alias FrenzyWeb.Endpoint
def init(opts), do: opts
def call(conn, _opts) do
user_token = get_session(conn, :user_token)
case Phoenix.Token.verify(Endpoint, "user token", user_token, max_age: 24 * 60 * 60) do
{:error, _reason} ->
conn
|> Phoenix.Controller.redirect(to: Routes.login_path(Endpoint, :login))
|> halt()
{:ok, user_id} ->
case Repo.get(User, user_id) do
nil ->
conn
|> Phoenix.Controller.redirect(to: Routes.login_path(Endpoint, :login))
|> halt()
user ->
user = Repo.preload(user, groups: [:feeds])
assign(conn, :user, user)
end
end
end
end

View File

@ -7,7 +7,15 @@ defmodule FrenzyWeb.Router do
plug :fetch_flash plug :fetch_flash
plug :protect_from_forgery plug :protect_from_forgery
plug :put_secure_browser_headers plug :put_secure_browser_headers
plug BasicAuth, use_config: {:frenzy, :auth} end
pipeline :authenticate do
# plug :accepts, ["html"]
# plug :fetch_session
# plug :fetch_flash
# plug :protect_from_forgery
# plug :put_secure_browser_headers
plug FrenzyWeb.Plug.Authenticate
end end
pipeline :api do pipeline :api do
@ -17,6 +25,14 @@ defmodule FrenzyWeb.Router do
scope "/", FrenzyWeb do scope "/", FrenzyWeb do
pipe_through :browser pipe_through :browser
get "/login", LoginController, :login
post "/login", LoginController, :login_post
end
scope "/", FrenzyWeb do
pipe_through :browser
pipe_through :authenticate
get "/", GroupController, :index get "/", GroupController, :index
resources "/groups", GroupController, except: [:edit, :update] resources "/groups", GroupController, except: [:edit, :update]

View File

@ -0,0 +1,15 @@
<% IO.inspect(get_flash(@conn, :error)) %>
<%= form_tag Routes.login_path(@conn, :login_post), method: :post do %>
<div class="form-group">
<label for="username">Username</label>
<input type="text" name="username" id="username">
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" name="password" id="password">
</div>
<div class="form-group">
<%= submit "Log In" %>
</div>
<% end %>

View File

@ -0,0 +1,3 @@
defmodule FrenzyWeb.LoginView do
use FrenzyWeb, :view
end

View File

@ -47,7 +47,6 @@ defmodule Frenzy.MixProject do
{:fiet, git: "https://github.com/shadowfacts/fiet.git", branch: "master"}, {:fiet, git: "https://github.com/shadowfacts/fiet.git", branch: "master"},
{:timex, "~> 3.0"}, {:timex, "~> 3.0"},
{:readability, git: "https://github.com/shadowfacts/readability.git", branch: "master"}, {:readability, git: "https://github.com/shadowfacts/readability.git", branch: "master"},
{:basic_auth, "~> 2.2.2"},
{:bcrypt_elixir, "~> 2.0"} {:bcrypt_elixir, "~> 2.0"}
] ]
end end