1
0
mirror of https://github.com/mgerb/classic-wow-forums synced 2026-01-10 17:12:48 +00:00
This commit is contained in:
2017-12-31 20:19:44 -06:00
commit e856cc5172
37 changed files with 1196 additions and 0 deletions

9
lib/myapp.ex Normal file
View File

@@ -0,0 +1,9 @@
defmodule MyApp do
@moduledoc """
MyApp keeps the contexts that define your domain
and business logic.
Contexts are also responsible for managing your data, regardless
if it comes from the database, an external API or others.
"""
end

31
lib/myapp/application.ex Normal file
View File

@@ -0,0 +1,31 @@
defmodule MyApp.Application do
use Application
# See https://hexdocs.pm/elixir/Application.html
# for more information on OTP Applications
def start(_type, _args) do
import Supervisor.Spec
# Define workers and child supervisors to be supervised
children = [
# Start the Ecto repository
supervisor(MyApp.Repo, []),
# Start the endpoint when the application starts
supervisor(MyAppWeb.Endpoint, []),
# Start your own worker by calling: MyApp.Worker.start_link(arg1, arg2, arg3)
# worker(MyApp.Worker, [arg1, arg2, arg3]),
]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end
# Tell Phoenix to update the endpoint configuration
# whenever the application is updated.
def config_change(changed, _new, removed) do
MyAppWeb.Endpoint.config_change(changed, removed)
:ok
end
end

33
lib/myapp/auth/auth.ex Normal file
View File

@@ -0,0 +1,33 @@
defmodule MyApp.Guardian do
use Guardian, otp_app: :myapp
def subject_for_token(resource, _claims) do
# You can use any value for the subject of your token but
# it should be useful in retrieving the resource later, see
# how it being used on `resource_from_claims/1` function.
# A unique `id` is a good subject, a non-unique email address
# is a poor subject.
sub = to_string(resource["id"])
{: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
# 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
# def resource_from_claims(_claims) do
# {:error, :reason_for_error}
# end
end

10
lib/myapp/auth/handler.ex Normal file
View File

@@ -0,0 +1,10 @@
defmodule MyApp.Auth.ErrorHandler do
import Plug.Conn
alias MyAppWeb.Response
def auth_error(conn, {type, _reason}, _opts) do
conn
|> put_status(401)
|> Response.json(to_string(type))
end
end

View File

@@ -0,0 +1,9 @@
defmodule MyApp.Guardian.AuthPipeline.JSON do
use Guardian.Plug.Pipeline, otp_app: :MyApp,
module: MyApp.Guardian,
error_handler: MyApp.Auth.ErrorHandler
plug Guardian.Plug.VerifyHeader, realm: "Bearer"
plug Guardian.Plug.EnsureAuthenticated
plug Guardian.Plug.LoadResource, allow_blank: true
end

View File

@@ -0,0 +1,50 @@
defmodule MyApp.BattleNet.Auth do
alias MyApp.BattleNet.User
alias MyApp.JWT
def token_uri, do: "https://us.battle.net/oauth/token"
def get_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)
req_options = [hackney: [basic_auth: {client_id, client_secret}]]
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({: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
defp get_req_body(code) do
redirect_uri = Application.get_env(:myapp, :bnet_redirect_uri)
{:form, [
grant_type: "authorization_code",
scope: "wow.profile",
code: code,
redirect_uri: redirect_uri,
]}
end
end

View File

@@ -0,0 +1,16 @@
defmodule MyApp.BattleNet.User do
defstruct id: nil, battletag: nil
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}
end
end
defp resource_url(path, access_token) do
"#{api_url}/#{path}?access_token=#{access_token}"
end
end

14
lib/myapp/jwt.ex Normal file
View File

@@ -0,0 +1,14 @@
defmodule MyApp.JWT do
alias MyApp.Guardian
# ~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"}
end
end
end

11
lib/myapp/repo.ex Normal file
View File

@@ -0,0 +1,11 @@
defmodule MyApp.Repo do
use Ecto.Repo, otp_app: :myapp
@doc """
Dynamically loads the repository url from the
DATABASE_URL environment variable.
"""
def init(_, opts) do
{:ok, Keyword.put(opts, :url, System.get_env("DATABASE_URL"))}
end
end

64
lib/myapp_web.ex Normal file
View File

@@ -0,0 +1,64 @@
defmodule MyAppWeb do
@moduledoc """
The entrypoint for defining your web interface, such
as controllers, views, channels and so on.
This can be used in your application as:
use MyAppWeb, :controller
use MyAppWeb, :view
The definitions below will be executed for every view,
controller, etc, so keep them short and clean, focused
on imports, uses and aliases.
Do NOT define functions inside the quoted expressions
below. Instead, define any helper function in modules
and import those modules here.
"""
def controller do
quote do
use Phoenix.Controller, namespace: MyAppWeb
import Plug.Conn
import MyAppWeb.Router.Helpers
import MyAppWeb.Gettext
end
end
def view do
quote do
use Phoenix.View, root: "lib/myapp_web/templates",
namespace: MyAppWeb
# Import convenience functions from controllers
import Phoenix.Controller, only: [get_flash: 2, view_module: 1]
import MyAppWeb.Router.Helpers
import MyAppWeb.ErrorHelpers
import MyAppWeb.Gettext
end
end
def router do
quote do
use Phoenix.Router
import Plug.Conn
import Phoenix.Controller
end
end
def channel do
quote do
use Phoenix.Channel
import MyAppWeb.Gettext
end
end
@doc """
When used, dispatch to the appropriate controller/view/etc.
"""
defmacro __using__(which) when is_atom(which) do
apply(__MODULE__, which, [])
end
end

View File

@@ -0,0 +1,37 @@
defmodule MyAppWeb.UserSocket do
use Phoenix.Socket
## Channels
# channel "room:*", MyAppWeb.RoomChannel
## Transports
transport :websocket, Phoenix.Transports.WebSocket
# transport :longpoll, Phoenix.Transports.LongPoll
# Socket params are passed from the client and can
# be used to verify and authenticate a user. After
# verification, you can put default assigns into
# the socket that will be set for all channels, ie
#
# {:ok, assign(socket, :user_id, verified_user_id)}
#
# To deny connection, return `:error`.
#
# See `Phoenix.Token` documentation for examples in
# performing token verification on connect.
def connect(_params, socket) do
{:ok, socket}
end
# Socket id's are topics that allow you to identify all sockets for a given user:
#
# def id(socket), do: "user_socket:#{socket.assigns.user_id}"
#
# Would allow you to broadcast a "disconnect" event and terminate
# all active sockets and channels for a given user:
#
# MyAppWeb.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{})
#
# Returning `nil` makes this socket anonymous.
def id(_socket), do: nil
end

View File

@@ -0,0 +1,20 @@
defmodule MyAppWeb.BattleNetController do
use MyAppWeb, :controller
alias MyAppWeb.Response
alias MyApp.BattleNet.Auth
# https://us.battle.net/oauth/authorize?redirect_uri=https://localhost/api/battlenet/authorize&scope=wow.profile&client_id=vxqv32fddxsy6cmk6259amtymbuzmfrq&response_type=code
def authorize(conn, %{"code" => code}) when not is_nil(code) do
{output, status} = case Auth.get_token(code) do
{:ok, token} -> {token, 200}
{:error, err} -> {err, 400}
end
conn
|>put_status(status)
|>Response.json(output)
end
end

View File

@@ -0,0 +1,12 @@
defmodule MyAppWeb.UserController do
use MyAppWeb, :controller
alias MyAppWeb.Response
def index(conn, params) do
IO.inspect(conn)
IO.inspect(params)
conn
|> Response.json("Auth works!")
end
end

55
lib/myapp_web/endpoint.ex Normal file
View File

@@ -0,0 +1,55 @@
defmodule MyAppWeb.Endpoint do
use Phoenix.Endpoint, otp_app: :myapp
socket "/socket", MyAppWeb.UserSocket
# Serve at "/" the static files from "priv/static" directory.
#
# You should set gzip to true if you are running phoenix.digest
# when deploying your static files in production.
plug Plug.Static,
at: "/", from: :myapp, gzip: false,
only: ~w(css fonts images js favicon.ico robots.txt)
# Code reloading can be explicitly enabled under the
# :code_reloader configuration of your endpoint.
if code_reloading? do
plug Phoenix.CodeReloader
end
plug Plug.RequestId
plug Plug.Logger
plug Plug.Parsers,
parsers: [:urlencoded, :multipart, :json],
pass: ["*/*"],
json_decoder: Poison
plug Plug.MethodOverride
plug Plug.Head
# The session will be stored in the cookie and signed,
# this means its contents can be read but not tampered with.
# Set :encryption_salt if you would also like to encrypt it.
plug Plug.Session,
store: :cookie,
key: "_myapp_key",
signing_salt: "WENlVlsb"
plug MyAppWeb.Router
@doc """
Callback invoked for dynamically configuring the endpoint.
It receives the endpoint configuration and checks if
configuration should be loaded from the system environment.
"""
def init(_key, config) do
if config[:load_from_system_env] do
port = System.get_env("PORT") || raise "expected the PORT environment variable to be set"
{:ok, Keyword.put(config, :http, [:inet6, port: port])}
else
{:ok, config}
end
end
end

24
lib/myapp_web/gettext.ex Normal file
View File

@@ -0,0 +1,24 @@
defmodule MyAppWeb.Gettext do
@moduledoc """
A module providing Internationalization with a gettext-based API.
By using [Gettext](https://hexdocs.pm/gettext),
your module gains a set of macros for translations, for example:
import MyAppWeb.Gettext
# Simple translation
gettext "Here is the string to translate"
# Plural translation
ngettext "Here is the string to translate",
"Here are the strings to translate",
3
# Domain-based translation
dgettext "errors", "Here is the error message to translate"
See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.
"""
use Gettext, otp_app: :myapp
end

22
lib/myapp_web/response.ex Normal file
View File

@@ -0,0 +1,22 @@
defmodule MyAppWeb.Response do
# error handling based on http status
def json(conn, data) do
output = cond do
conn.status && conn.status >= 400 ->
%{
error: %{
status: conn.status,
message: data,
}
}
true -> %{
data: data
}
end
Phoenix.Controller.json(conn, output)
end
end

28
lib/myapp_web/router.ex Normal file
View File

@@ -0,0 +1,28 @@
defmodule MyAppWeb.Router do
use MyAppWeb, :router
alias MyApp.Guardian.AuthPipeline
pipeline :api do
plug :accepts, ["json"]
end
pipeline :api_auth do
plug AuthPipeline.JSON
end
# Other scopes may use custom stacks.
scope "/api", MyAppWeb do
pipe_through [:api]
scope "/battlenet" do
get "/authorize", BattleNetController, :authorize
end
scope "/user" do
# authenticated routes
pipe_through [:api_auth]
get "/", UserController, :index
end
end
end

View File

@@ -0,0 +1,29 @@
defmodule MyAppWeb.ErrorHelpers do
@moduledoc """
Conveniences for translating and building error messages.
"""
@doc """
Translates an error message using gettext.
"""
def translate_error({msg, opts}) do
# Because error messages were defined within Ecto, we must
# call the Gettext module passing our Gettext backend. We
# also use the "errors" domain as translations are placed
# in the errors.po file.
# Ecto will pass the :count keyword if the error message is
# meant to be pluralized.
# On your own code and templates, depending on whether you
# need the message to be pluralized or not, this could be
# written simply as:
#
# dngettext "errors", "1 file", "%{count} files", count
# dgettext "errors", "is invalid"
#
if count = opts[:count] do
Gettext.dngettext(MyAppWeb.Gettext, "errors", msg, msg, count, opts)
else
Gettext.dgettext(MyAppWeb.Gettext, "errors", msg, opts)
end
end
end

View File

@@ -0,0 +1,17 @@
defmodule MyAppWeb.ErrorView do
use MyAppWeb, :view
def render("404.json", _assigns) do
%{errors: %{detail: "Page not found"}}
end
def render("500.json", _assigns) do
%{errors: %{detail: "Internal server error"}}
end
# In case no render clause matches or no
# template is found, let's render it as 500
def template_not_found(_template, assigns) do
render "500.json", assigns
end
end