diff --git a/config/config.exs b/config/config.exs index 3bc85cf..9e4c343 100644 --- a/config/config.exs +++ b/config/config.exs @@ -27,3 +27,11 @@ config :logger, :console, # of this file so it overrides the configuration defined above. import_config "#{Mix.env}.exs" import_config "config.secret.exs" + +# user permissions for app - right now the read/write don't mean anything +config :myapp, MyApp.Guardian, + permissions: %{ + user: [:read, :write], + mod: [:read, :write], + admin: [:read, :write], + } diff --git a/lib/myapp/auth/auth.ex b/lib/myapp/auth/auth.ex index 82d6f30..13e64c2 100644 --- a/lib/myapp/auth/auth.ex +++ b/lib/myapp/auth/auth.ex @@ -1,5 +1,6 @@ defmodule MyApp.Guardian do use Guardian, otp_app: :myapp + use Guardian.Permissions.Bitwise def subject_for_token(resource, _claims) do # You can use any value for the subject of your token but @@ -11,10 +12,6 @@ defmodule MyApp.Guardian do {:ok, sub} end - # def subject_for_token(_, _) do - # {:error, :reason_for_error} - # end - def resource_from_claims(claims) do # Here we'll look up our resource from the claims, the subject can be # found in the `"sub"` key. In `above subject_for_token/2` we returned @@ -25,8 +22,10 @@ defmodule MyApp.Guardian do {:ok, resource} end - # def resource_from_claims(_claims) do - # {:error, :reason_for_error} - # end + @spec add_permissions(map, map) :: map + def add_permissions(claims, permissions) do + claims + |> encode_permissions_into_claims!(permissions) + end end diff --git a/lib/myapp/auth/pipeline.ex b/lib/myapp/auth/pipeline.ex index 50c18df..9320699 100644 --- a/lib/myapp/auth/pipeline.ex +++ b/lib/myapp/auth/pipeline.ex @@ -1,9 +1,39 @@ -defmodule MyApp.Guardian.AuthPipeline.JSON do +defmodule MyApp.Guardian.Auth.Pipeline.User do use Guardian.Plug.Pipeline, otp_app: :MyApp, module: MyApp.Guardian, error_handler: MyApp.Auth.ErrorHandler plug Guardian.Plug.VerifyHeader, realm: "Bearer" + plug Guardian.Permissions.Bitwise, one_of: [ + %{user: [:read, :write]}, + %{mod: [:read, :write]}, + %{admin: [:read, :write]}, + ] + plug Guardian.Plug.EnsureAuthenticated + plug Guardian.Plug.LoadResource, allow_blank: true +end + +defmodule MyApp.Guardian.Auth.Pipeline.Mod do + use Guardian.Plug.Pipeline, otp_app: :MyApp, + module: MyApp.Guardian, + error_handler: MyApp.Auth.ErrorHandler + + plug Guardian.Plug.VerifyHeader, realm: "Bearer" + plug Guardian.Permissions.Bitwise, one_of: [ + %{mod: [:read, :write]}, + %{admin: [:read, :write]}, + ] + plug Guardian.Plug.EnsureAuthenticated + plug Guardian.Plug.LoadResource, allow_blank: true +end + +defmodule MyApp.Guardian.Auth.Pipeline.Admin do + use Guardian.Plug.Pipeline, otp_app: :MyApp, + module: MyApp.Guardian, + error_handler: MyApp.Auth.ErrorHandler + + plug Guardian.Plug.VerifyHeader, realm: "Bearer" + plug Guardian.Permissions.Bitwise, one_of: [%{admin: [:read, :write]}] plug Guardian.Plug.EnsureAuthenticated plug Guardian.Plug.LoadResource, allow_blank: true end diff --git a/lib/myapp/auth/token.ex b/lib/myapp/auth/token.ex new file mode 100644 index 0000000..910ee64 --- /dev/null +++ b/lib/myapp/auth/token.ex @@ -0,0 +1,33 @@ +defmodule MyApp.Guardian.Auth.Token do + alias MyApp.Guardian + + # ~1 year + defp tokenTTL(), do: {52, :weeks} + + @spec add_token_and_map_claims(map | {atom, any}) :: {:ok, map} | {:error, String.t} + def add_token_and_map_claims(user) when is_map(user) do + + claims = user + |> Map.take([:id, :battletag, :battle_net_id, :access_token]) # take values from user object to map to claims + |> Guardian.add_permissions(get_permissions(user)) + + case Guardian.encode_and_sign(user, claims, ttl: tokenTTL()) do + {:ok, token, _claims} -> {:ok, Map.merge(user, %{token: token})} + {:error, error} -> {:error, error} + end + end + + def add_token_and_map_claims({:ok, user}), do: add_token_and_map_claims(user) + def add_token_and_map_claims({:error, error}), do: {:error, error} + + # return permissions base on field in database + defp get_permissions(user) do + case Map.get(user, :permissions) do + "user" -> %{user: [:read, :write]} + "mod" -> %{mod: [:read, :write]} + "admin" -> %{admin: [:read, :write]} + nil -> %{user: [:read, :write]} + end + end + +end diff --git a/lib/myapp/data/thread.ex b/lib/myapp/data/thread.ex index 2dca3a2..69d7d67 100644 --- a/lib/myapp/data/thread.ex +++ b/lib/myapp/data/thread.ex @@ -1,6 +1,5 @@ defmodule MyApp.Data.Thread do use Ecto.Schema - import Ecto.Query import Ecto.Changeset alias MyApp.Repo alias MyApp.Data @@ -16,33 +15,48 @@ defmodule MyApp.Data.Thread do field :sticky, :boolean, default: false field :locked, :boolean, default: false field :edited, :boolean, default: false - timestamps() end - def changeset(thread, params \\ %{}) do + def insert_changeset(thread, params \\ %{}) do thread - |> cast(params, [:id, :title, :category_id, :content, :user_id, :view_count, :last_reply_id, :sticky, :locked, :edited]) + |> cast(params, [:title, :category_id, :content, :user_id]) |> validate_required([:title, :category_id, :content, :user_id]) |> foreign_key_constraint(:category_id) + |> foreign_key_constraint(:user_id) + end + + def update_changeset(thread, params \\ %{}) do + thread + |> cast(params, [:content, :user_id, :sticky, :locked]) + |> force_change(:edited, true) # set edited flag on update + |> validate_required([:content, :user_id]) + |> foreign_key_constraint(:category_id) + |> foreign_key_constraint(:user_id) end def insert_thread(params) do - changeset(%Data.Thread{}, params) + insert_changeset(%Data.Thread{}, params) |> Repo.insert |> Data.Util.process_insert_or_update end def update_thread(params) do - Repo.get(Data.Thread, Map.get(params, "id")) - |> process_update(params) + id = Map.get(params, "id") + if id == nil do + {:error, "Invalid thread"} + else + Repo.get(Data.Thread, id) + |> process_update(params) + end end + # TODO: delete thread + # TODO: check user permissions for sticky/locked defp process_update(thread, _params) when is_nil(thread), do: {:error, "Invalid thread"} defp process_update(thread, params) when not is_nil(thread) do - changeset(thread, Map.take(params, ["content", "edited", "sticky", "locked"])) - |> IO.inspect + update_changeset(thread, params) |> Repo.update |> Data.Util.process_insert_or_update end diff --git a/lib/myapp/jwt.ex b/lib/myapp/jwt.ex deleted file mode 100644 index 7a24344..0000000 --- a/lib/myapp/jwt.ex +++ /dev/null @@ -1,18 +0,0 @@ -defmodule MyApp.JWT do - alias MyApp.Guardian - - # ~1 year - defp tokenTTL(), do: {52, :weeks} - - @spec add_jwt(map | {atom, any}) :: {:ok, map} | {:error, String.t} - def add_jwt(user) when is_map(user) do - case Guardian.encode_and_sign(user, user, ttl: tokenTTL()) do - {:ok, token, _claims} -> {:ok, Map.merge(user, %{token: token})} - {:error, error} -> {:error, error} - end - end - - def add_jwt({:ok, user}), do: add_jwt(user) - def add_jwt({:error, error}), do: {:error, error} - -end diff --git a/lib/myapp_web/controllers/battle_net_controller.ex b/lib/myapp_web/controllers/battle_net_controller.ex index a2eac5d..6f9e6b8 100644 --- a/lib/myapp_web/controllers/battle_net_controller.ex +++ b/lib/myapp_web/controllers/battle_net_controller.ex @@ -3,7 +3,7 @@ defmodule MyAppWeb.BattleNetController do alias MyAppWeb.Response alias MyApp.BattleNet alias MyApp.Data - alias MyApp.JWT + alias MyApp.Guardian.Auth # https://us.battle.net/oauth/authorize?redirect_uri=https://localhost/api/battlenet/authorize&scope=wow.profile&client_id=vxqv32fddxsy6cmk6259amtymbuzmfrq&response_type=code @@ -14,7 +14,7 @@ defmodule MyAppWeb.BattleNetController do |> BattleNet.Auth.get_access_token |> BattleNet.User.get_user |> Data.User.upsert_user - |> JWT.add_jwt + |> Auth.Token.add_token_and_map_claims |> Response.put_resp conn diff --git a/lib/myapp_web/controllers/user_controller.ex b/lib/myapp_web/controllers/user_controller.ex index 63b1597..2ea8c53 100644 --- a/lib/myapp_web/controllers/user_controller.ex +++ b/lib/myapp_web/controllers/user_controller.ex @@ -3,9 +3,7 @@ defmodule MyAppWeb.UserController do alias MyAppWeb.Response @spec index(map, map) :: any - def index(conn, params) do - IO.inspect(conn) - IO.inspect(params) + def index(conn, _params) do conn |> Response.json("Auth works!") end diff --git a/lib/myapp_web/router.ex b/lib/myapp_web/router.ex index 8a824c7..7591be9 100644 --- a/lib/myapp_web/router.ex +++ b/lib/myapp_web/router.ex @@ -1,13 +1,21 @@ defmodule MyAppWeb.Router do use MyAppWeb, :router - alias MyApp.Guardian.AuthPipeline + alias MyApp.Guardian.Auth pipeline :api do plug :accepts, ["json"] end - pipeline :api_auth do - plug AuthPipeline.JSON + pipeline :user_auth do + plug Auth.Pipeline.User + end + + pipeline :mod_auth do + plug Auth.Pipeline.Mod + end + + pipeline :admin_auth do + plug Auth.Pipeline.Admin end # Other scopes may use custom stacks. @@ -17,26 +25,26 @@ defmodule MyAppWeb.Router do scope "/battlenet" do get "/authorize", BattleNetController, :authorize - pipe_through [:api_auth] + pipe_through [:user_auth] get "/characters", BattleNetController, :characters end scope "/user" do # authenticated routes - pipe_through [:api_auth] + pipe_through [:user_auth] get "/", UserController, :index end scope "/thread" do # authenticated routes - pipe_through [:api_auth] + pipe_through [:user_auth] post "/", ThreadController, :insert put "/", ThreadController, :update end - scope "reply" do + scope "/reply" do # authenticated routes - pipe_through [:api_auth] + pipe_through [:user_auth] post "/", ReplyController, :insert end