mirror of
https://github.com/mgerb/classic-wow-forums
synced 2026-01-10 17:12:48 +00:00
init
This commit is contained in:
9
lib/myapp.ex
Normal file
9
lib/myapp.ex
Normal 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
31
lib/myapp/application.ex
Normal 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
33
lib/myapp/auth/auth.ex
Normal 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
10
lib/myapp/auth/handler.ex
Normal 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
|
||||
9
lib/myapp/auth/pipeline.ex
Normal file
9
lib/myapp/auth/pipeline.ex
Normal 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
|
||||
50
lib/myapp/battle_net/auth.ex
Normal file
50
lib/myapp/battle_net/auth.ex
Normal 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
|
||||
|
||||
16
lib/myapp/battle_net/user.ex
Normal file
16
lib/myapp/battle_net/user.ex
Normal 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
14
lib/myapp/jwt.ex
Normal 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
11
lib/myapp/repo.ex
Normal 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
64
lib/myapp_web.ex
Normal 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
|
||||
37
lib/myapp_web/channels/user_socket.ex
Normal file
37
lib/myapp_web/channels/user_socket.ex
Normal 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
|
||||
20
lib/myapp_web/controllers/battle_net_controller.ex
Normal file
20
lib/myapp_web/controllers/battle_net_controller.ex
Normal 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
|
||||
12
lib/myapp_web/controllers/user_controller.ex
Normal file
12
lib/myapp_web/controllers/user_controller.ex
Normal 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
55
lib/myapp_web/endpoint.ex
Normal 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
24
lib/myapp_web/gettext.ex
Normal 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
22
lib/myapp_web/response.ex
Normal 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
28
lib/myapp_web/router.ex
Normal 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
|
||||
29
lib/myapp_web/views/error_helpers.ex
Normal file
29
lib/myapp_web/views/error_helpers.ex
Normal 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
|
||||
17
lib/myapp_web/views/error_view.ex
Normal file
17
lib/myapp_web/views/error_view.ex
Normal 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
|
||||
Reference in New Issue
Block a user