1
0
mirror of https://github.com/mgerb/classic-wow-forums synced 2026-01-11 01:22:49 +00:00

authentication with persistence working

This commit is contained in:
2018-01-01 18:55:00 -06:00
parent e856cc5172
commit 135ce3a5d6
14 changed files with 172 additions and 38 deletions

View File

@@ -21,7 +21,6 @@ defmodule MyApp.Guardian do
# the resource id so here we'll rely on that to look it up.
id = claims["sub"]
# resource = MyApp.get_resource_by_id(id)
IO.inspect(claims)
resource = id
{:ok, resource}
end

View File

@@ -4,7 +4,8 @@ defmodule MyApp.BattleNet.Auth do
def token_uri, do: "https://us.battle.net/oauth/token"
def get_token(code) do
@spec get_access_token(String.t) :: {:ok, String.t} | {:error, String.t}
def get_access_token(code) do
client_id = Application.get_env(:myapp, :bnet_client_id)
client_secret = Application.get_env(:myapp, :bnet_client_secret)
redirect_uri = Application.get_env(:myapp, :bnet_redirect_uri)
@@ -14,28 +15,16 @@ defmodule MyApp.BattleNet.Auth do
HTTPoison.request(:post, token_uri, get_req_body(code), [], req_options)
|> parse_body
|> parse_token
|> validate_user
|> generate_jwt
end
defp parse_body({:error, err}), do: {:error, err}
defp parse_body({:ok, %HTTPoison.Response{body: body}}), do: Poison.decode(body)
defp parse_token({:ok, %{"access_token" => token}}), do: {:ok, token}
defp parse_token({:ok, %{"error" => err}}), do: {:error, err}
defp parse_token({:ok, %{"error" => error}}), do: {:error, error}
defp parse_token({:error, err}), do: {:error, "Authentication error"}
defp validate_user({:error, err}), do: {:error, err}
defp validate_user({:ok, token}), do: User.get_user(token)
defp generate_jwt({:error, err}), do: {:error, err}
defp generate_jwt({:ok, user}) do
case JWT.get_jwt(user, user) do
{:ok, token} -> {:ok, Map.merge(user, %{"token" => token})}
{:error, err} -> {:error, err}
end
end
@spec get_req_body(String.t) :: tuple
defp get_req_body(code) do
redirect_uri = Application.get_env(:myapp, :bnet_redirect_uri)
{:form, [

View File

@@ -1,16 +1,32 @@
defmodule MyApp.BattleNet.User do
defstruct id: nil, battletag: nil
@type battle_net_user :: %{"battle_net_id": integer, "battletag": String.t, "access_token": String.t}
def api_url, do: "https://us.api.battle.net"
def get_user(access_token) do
case HTTPoison.get(resource_url("account/user", access_token)) do
{:ok, %HTTPoison.Response{body: body}} -> {:ok, Poison.decode!(body, as: Battlenet.User)}
{:error, err} -> {:error, err}
# grab user information from battle net api - use token for auth
@spec get_user(String.t | {atom, any}) :: {:ok, battle_net_user} | {:error, any}
def get_user(access_token) when is_binary(access_token) do
HTTPoison.get(resource_url("account/user", access_token))
|> parse_user_response(access_token)
end
def get_user({:ok, access_token}), do: get_user(access_token)
def get_user({:error, error}), do: {:error, error}
defp parse_user_response({:error, error}, _), do: {:error, error}
defp parse_user_response({:ok, %HTTPoison.Response{body: body}}, access_token) do
case Poison.decode(body) do
{:ok, user} ->
user = user
|> Map.merge(%{"access_token" => access_token}) # add access token to return map
|> Map.put("battle_net_id", Map.get(user, "id")) # change id key to battle_net_id
|> Map.delete("id") # remove id key
{:ok, user}
{:error, error} -> {:error, error}
end
end
defp resource_url(path, access_token) do
"#{api_url}/#{path}?access_token=#{access_token}"
"#{api_url()}/#{path}?access_token=#{access_token}"
end
end

81
lib/myapp/data/user.ex Normal file
View File

@@ -0,0 +1,81 @@
defmodule MyApp.Data.User do
use Ecto.Schema
import Ecto.Query
import Ecto.Changeset
alias MyApp.Repo
alias MyApp.Data
@derive {Poison.Encoder, except: [:__meta__]}
schema "user" do
field :battle_net_id, :integer
field :battletag, :string
timestamps()
end
def changeset(user, params \\ %{}) do
user
|> cast(params, [:battle_net_id, :battletag])
|> validate_required([:battle_net_id, :battletag])
|> unique_constraint(:battle_net_id)
end
@spec get_user(integer) :: nil | map
defp get_user(battle_net_id) do
query = from u in "user",
where: u.battle_net_id == ^battle_net_id,
select: [:id, :battle_net_id, :battletag]
Repo.one(query)
end
# insert user info in database - if not exists - update battletag if it has changed
@spec upsert_user(%{"battle_net_id": integer, "battletag": String.t} | tuple) :: {:ok, map} | {:error, any}
def upsert_user(params) when is_map(params) do
# check for current user in database
case get_user(Map.get(params, "battle_net_id")) do
nil -> insert_user(params)
user ->
# update user if battletag has changed
if Map.get(user, :battletag) != Map.get(params, "battletag") do
update_battletag(user, params)
else
{:ok, user}
end
end
|> add_access_token(Map.get(params, "access_token"))
end
def upsert_user({:ok, params}), do: upsert_user(params)
def upsert_user({:error, error}), do: {:error, error}
# need to add token back to map because we don't store it in the database
defp add_access_token({:error, error}, _), do: {:error, error}
defp add_access_token({:ok, user}, access_token) do
{:ok, Map.merge(user, %{access_token: access_token})}
end
defp insert_user(params) do
cs = changeset(%Data.User{}, params)
cs
|> Repo.insert
|> process_insert_or_update
end
# it's possible for a user's battle tag to change - if so update it
defp update_battletag(user, params) do
cs = Data.User.changeset(Map.merge(%Data.User{}, user), %{battletag: Map.get(params, "battletag")})
cs
|> Repo.update
|> process_insert_or_update
end
defp process_insert_or_update({:error, changeset}), do: {:error, map_changeset(changeset)}
defp process_insert_or_update({:ok, user}) do
{:ok, Map.take(user, [:id, :battle_net_id, :battletag])} # only grab the fields we need
end
defp map_changeset(changeset) do
Enum.map(changeset.errors, fn {key, val} ->
%{key => elem(val, 0)}
end)
end
end

View File

@@ -4,11 +4,15 @@ defmodule MyApp.JWT do
# ~1 year
defp tokenTTL(), do: {52, :weeks}
def get_jwt(user, claims) do
case Guardian.encode_and_sign(user, claims, ttl: tokenTTL()) do
{:ok, token, _claims} -> {:ok, token}
{:error, _token, _claims} -> {:error, "JWT error"}
@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

View File

@@ -1,5 +1,6 @@
defmodule MyApp.Repo do
use Ecto.Repo, otp_app: :myapp
@dialyzer {:nowarn_function, rollback: 1}
@doc """
Dynamically loads the repository url from the