Add authentication for Fever API

This commit is contained in:
Shadowfacts 2019-03-24 11:00:46 -04:00
parent 74a82967dd
commit 9fa6968f6f
Signed by: shadowfacts
GPG Key ID: 94A5AB95422746E5
5 changed files with 145 additions and 68 deletions

View File

@ -6,6 +6,8 @@ defmodule Frenzy.User do
field :username, :string field :username, :string
field :password, :string, virtual: true field :password, :string, virtual: true
field :password_hash, :string field :password_hash, :string
field :fever_password, :string, virtual: true
field :fever_auth_token, :string
has_many :groups, Frenzy.Group, on_delete: :delete_all has_many :groups, Frenzy.Group, on_delete: :delete_all
@ -21,16 +23,23 @@ defmodule Frenzy.User do
def registration_changeset(user, attrs) do def registration_changeset(user, attrs) do
user user
|> cast(attrs, [:username, :password]) |> cast(attrs, [:username, :password, :fever_password])
|> validate_length(:password, min: 8) |> validate_length(:password, min: 8)
|> validate_length(:fever_password, min: 8)
|> put_password_hash() |> put_password_hash()
end end
defp put_password_hash( defp put_password_hash(
%Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset %Ecto.Changeset{
valid?: true,
changes: %{username: username, password: password, fever_password: fever_password}
} = changeset
) do ) do
change(changeset, Bcrypt.add_hash(password)) changeset
|> change(Bcrypt.add_hash(password))
|> change(%{
fever_auth_token:
:crypto.hash(:md5, "#{username}:#{fever_password}") |> Base.encode16(case: :lower)
})
end end
defp put_password_hash(changeset), do: changeset
end end

View File

@ -73,7 +73,7 @@ defmodule FrenzyWeb.FeedController do
end end
def refresh(conn, _params) do def refresh(conn, _params) do
feed = conn.assgins[:feed] |> Repo.preload(:filter) feed = conn.assigns[: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

@ -1,6 +1,6 @@
defmodule FrenzyWeb.FeverController do defmodule FrenzyWeb.FeverController do
use FrenzyWeb, :controller use FrenzyWeb, :controller
alias Frenzy.{Repo, Group, Feed, Item} alias Frenzy.{Repo, User, Group, Feed, Item}
import Ecto.Query import Ecto.Query
plug :api_check plug :api_check
@ -22,95 +22,133 @@ defmodule FrenzyWeb.FeverController do
:invalid -> :invalid ->
resp(conn, 401, "Invalid API key") resp(conn, 401, "Invalid API key")
:ok -> {:ok, user} ->
json(conn, fever_response(params)) user = Repo.preload(user, groups: [:feeds])
res = fever_response(user, params)
json(conn, res)
end end
end end
defp validate_key(api_key) do defp validate_key(api_key) do
auth = Application.get_env(:frenzy, :auth) case Repo.get_by(User, fever_auth_token: api_key) do
username = auth[:username] nil ->
password = auth[:password] :invalid
expected = :crypto.hash(:md5, "#{username}:#{password}") |> Base.encode16(case: :lower)
case api_key |> String.downcase() do user ->
^expected -> :ok {:ok, user}
_ -> :invalid end
# auth = Application.get_env(:frenzy, :auth)
# username = auth[:username]
# password = auth[:password]
# expected = :crypto.hash(:md5, "#{username}:#{password}") |> Base.encode16(case: :lower)
# case api_key |> String.downcase() do
# ^expected -> :ok
# _ -> :invalid
# end
end
defp fever_response(user, params) do
%{api_version: 2, auth: 1}
|> mark(user, params)
|> unread_recently_read(user, params)
|> feeds(user, params)
|> groups(user, params)
|> feeds_groups(user, params)
|> favicons(user, params)
|> links(user, params)
|> unread(user, params)
|> saved(user, params)
|> items(user, params)
end
defp get_user_item(user, id) do
item = Repo.get(Item, id) |> Repo.preload(:feed)
if not is_nil(item) do
feeds = Enum.flat_map(user.groups, fn g -> g.feeds end)
if Enum.any?(feeds, fn f -> f.id == item.feed_id end) do
{:ok, item}
else
{:error, "item does not belong to given user"}
end
else
{:error, "item does not exist"}
end end
end end
defp fever_response(params) do defp mark(res, user, %{"mark" => "item", "id" => id, "as" => as}) do
%{api_version: 2, auth: 1} with {:ok, item} <- get_user_item(user, id) do
|> mark(params) diff =
|> unread_recently_read(params) case as do
|> feeds(params) "read" ->
|> groups(params) %{read: true, read_date: Timex.now()}
|> feeds_groups(params)
|> favicons(params)
|> links(params)
|> unread(params)
|> saved(params)
|> items(params)
end
defp mark(res, %{"mark" => "item", "id" => id, "as" => as}) do "unread" ->
item = Repo.get(Item, id) |> Repo.preload(:feed) %{read: false, read_date: nil}
diff = _ ->
case as do %{}
"read" -> end
%{read: true, read_date: Timex.now()}
"unread" -> changeset = Item.changeset(item, diff)
%{read: false, read_date: nil} Repo.update(changeset)
end
_ ->
%{}
end
changeset = Item.changeset(item, diff)
Repo.update(changeset)
res res
end end
defp mark(res, _), do: res defp mark(res, _, _), do: res
defp unread_recently_read(res, %{"unread_recently_read" => 1}) do defp unread_recently_read(res, user, %{"unread_recently_read" => 1}) do
Repo.all(from i in Item, where: i.read, where: i.read_date >= from_now(-1, "hour")) feed_ids =
user.groups
|> Enum.flat_map(fn g -> g.feeds end)
|> Enum.map(fn f -> f.id end)
Repo.all(
from i in Item,
where: i.feed_id in ^feed_ids,
where: i.read,
where: i.read_date >= from_now(-1, "hour")
)
|> Enum.map(fn i -> Item.changeset(i, %{read: false, read_date: nil}) end) |> Enum.map(fn i -> Item.changeset(i, %{read: false, read_date: nil}) end)
|> Enum.map(&Repo.update/1) |> Enum.map(&Repo.update/1)
res res
end end
defp unread_recently_read(res, _), do: res defp unread_recently_read(res, _, _), do: res
defp feeds(res, %{"feeds" => _}) do defp feeds(res, user, %{"feeds" => _}) do
feeds = feeds =
Repo.all(Feed) user.groups
|> Enum.flat_map(fn g -> g.feeds end)
|> Enum.map(&Feed.to_fever/1) |> Enum.map(&Feed.to_fever/1)
res res
|> Map.put(:feeds, feeds) |> Map.put(:feeds, feeds)
end end
defp feeds(res, _), do: res defp feeds(res, _, _), do: res
defp groups(res, %{"groups" => _}) do defp groups(res, user, %{"groups" => _}) do
groups = groups =
Repo.all(Group) user.groups
|> Enum.map(&Group.to_fever_group/1) |> Enum.map(&Group.to_fever_group/1)
res res
|> Map.put(:groups, groups) |> Map.put(:groups, groups)
end end
defp groups(res, _), do: res defp groups(res, _, _), do: res
defp feeds_groups(res, params) do defp feeds_groups(res, user, params) do
if Map.has_key?(params, "feeds") or Map.has_key?(params, "groups") do if Map.has_key?(params, "feeds") or Map.has_key?(params, "groups") do
feeds_groups = feeds_groups =
Repo.all(from Group, preload: [:feeds]) user.groups
|> Enum.map(&Group.to_fever_feeds_group/1) |> Enum.map(&Group.to_fever_feeds_group/1)
res res
@ -120,23 +158,28 @@ defmodule FrenzyWeb.FeverController do
end end
end end
defp favicons(res, %{"favicons" => _}) do defp favicons(res, _user, %{"favicons" => _}) do
res res
|> Map.put(:favicons, []) |> Map.put(:favicons, [])
end end
defp favicons(res, _), do: res defp favicons(res, _, _), do: res
defp links(res, %{"links" => _}) do defp links(res, _user, %{"links" => _}) do
res res
|> Map.put(:links, []) |> Map.put(:links, [])
end end
defp links(res, _), do: res defp links(res, _, _), do: res
defp unread(res, user, %{"unread_item_ids" => _}) do
feed_ids =
user.groups
|> Enum.flat_map(fn g -> g.feeds end)
|> Enum.map(fn f -> f.id end)
defp unread(res, %{"unread_item_ids" => _}) do
unread = unread =
Repo.all(from Item, where: [read: false]) Repo.all(from i in Item, where: i.feed_id in ^feed_ids, where: [read: false])
|> Enum.map(fn item -> item.id end) |> Enum.map(fn item -> item.id end)
|> Enum.join(",") |> Enum.join(",")
@ -144,16 +187,21 @@ defmodule FrenzyWeb.FeverController do
|> Map.put(:unread_item_ids, unread) |> Map.put(:unread_item_ids, unread)
end end
defp unread(res, _), do: res defp unread(res, _, _), do: res
defp saved(res, %{"saved_item_ids" => _}) do defp saved(res, _user, %{"saved_item_ids" => _}) do
res res
|> Map.put(:saved_item_ids, "") |> Map.put(:saved_item_ids, "")
end end
defp saved(res, _), do: res defp saved(res, _, _), do: res
defp items(res, user, %{"items" => _} = params) do
feed_ids =
user.groups
|> Enum.flat_map(fn g -> g.feeds end)
|> Enum.map(fn f -> f.id end)
defp items(res, %{"items" => _} = params) do
items = items =
cond do cond do
Map.has_key?(params, "with_ids") -> Map.has_key?(params, "with_ids") ->
@ -161,8 +209,15 @@ defmodule FrenzyWeb.FeverController do
|> String.split(",") |> String.split(",")
|> Enum.map(fn id -> |> Enum.map(fn id ->
{id, _} = id |> String.trim() |> Integer.parse() {id, _} = id |> String.trim() |> Integer.parse()
Repo.get(Item, id) item = Repo.get(Item, id)
if item.feed_id in feed_ids do
item
else
nil
end
end) end)
|> Enum.reject(&is_nil/1)
Map.has_key?(params, "since_id") -> Map.has_key?(params, "since_id") ->
since = Repo.get(Item, params["since_id"]) since = Repo.get(Item, params["since_id"])
@ -170,6 +225,7 @@ defmodule FrenzyWeb.FeverController do
Repo.all( Repo.all(
from i in Item, from i in Item,
where: i.feed_id in ^feed_ids,
where: i.inserted_at > ^since.inserted_at, where: i.inserted_at > ^since.inserted_at,
order_by: [asc: :id], order_by: [asc: :id],
limit: 50 limit: 50
@ -181,6 +237,7 @@ defmodule FrenzyWeb.FeverController do
Repo.all( Repo.all(
from i in Item, from i in Item,
where: i.feed_id in ^feed_ids,
where: i.inserted_at < ^max.inserted_at, where: i.inserted_at < ^max.inserted_at,
order_by: [desc: :id], order_by: [desc: :id],
limit: 50 limit: 50
@ -201,5 +258,5 @@ defmodule FrenzyWeb.FeverController do
|> Map.put(:total_items, Enum.count(items)) |> Map.put(:total_items, Enum.count(items))
end end
defp items(res, _), do: res defp items(res, _, _), do: res
end end

View File

@ -6,11 +6,13 @@ defmodule Mix.Tasks.Frenzy.User do
def run(["add"]) do def run(["add"]) do
username = IO.gets("Username: ") |> String.trim() username = IO.gets("Username: ") |> String.trim()
password = IO.gets("Password: ") |> String.trim() password = IO.gets("Password: ") |> String.trim()
fever_password = IO.gets("Fever Password: ") |> String.trim()
changeset = changeset =
User.registration_changeset(%User{}, %{ User.registration_changeset(%User{}, %{
username: username, username: username,
password: password password: password,
fever_password: fever_password
}) })
Mix.Task.run("app.start") Mix.Task.run("app.start")

View File

@ -0,0 +1,9 @@
defmodule Frenzy.Repo.Migrations.UsersAddFeverToken do
use Ecto.Migration
def change do
alter table(:users) do
add :fever_auth_token, :string
end
end
end