Github Oauth
This commit is contained in:
parent
bdf99b4ee9
commit
d71138900c
@ -18,4 +18,11 @@ config :chessh, RateLimits,
|
|||||||
player_session_message_burst_ms: 500,
|
player_session_message_burst_ms: 500,
|
||||||
player_session_message_burst_rate: 8
|
player_session_message_burst_rate: 8
|
||||||
|
|
||||||
|
config :chessh, Web,
|
||||||
|
port: 8080,
|
||||||
|
github_oauth_login_url: "https://github.com/login/oauth/access_token",
|
||||||
|
github_user_api_url: "https://api.github.com/user"
|
||||||
|
|
||||||
|
config :joken, default_signer: "secret"
|
||||||
|
|
||||||
import_config "#{config_env()}.exs"
|
import_config "#{config_env()}.exs"
|
||||||
|
23
config/runtime.exs
Normal file
23
config/runtime.exs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import Config
|
||||||
|
|
||||||
|
config :chessh, Web,
|
||||||
|
github_client_id: System.get_env("GITHUB_CLIENT_ID"),
|
||||||
|
github_client_secret: System.get_env("GITHUB_CLIENT_SECRET"),
|
||||||
|
github_user_agent: System.get_env("GITHUB_USER_AGENT")
|
||||||
|
|
||||||
|
if config_env() == :prod do
|
||||||
|
database_url =
|
||||||
|
System.get_env("DATABASE_URL") ||
|
||||||
|
raise """
|
||||||
|
environment variable DATABASE_URL is missing.
|
||||||
|
For example: ecto://USER:PASS@HOST/DATABASE
|
||||||
|
"""
|
||||||
|
|
||||||
|
maybe_ipv6 = if System.get_env("ECTO_IPV6"), do: [:inet6], else: []
|
||||||
|
|
||||||
|
config :chessh, Chessh.Repo,
|
||||||
|
# ssl: true,
|
||||||
|
url: database_url,
|
||||||
|
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"),
|
||||||
|
socket_options: maybe_ipv6
|
||||||
|
end
|
@ -1,5 +0,0 @@
|
|||||||
defmodule Chessh do
|
|
||||||
def hello() do
|
|
||||||
:world
|
|
||||||
end
|
|
||||||
end
|
|
@ -15,7 +15,16 @@ defmodule Chessh.Application do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def start(_, _) do
|
def start(_, _) do
|
||||||
children = [Chessh.Repo, Chessh.SSH.Daemon]
|
children = [
|
||||||
|
Chessh.Repo,
|
||||||
|
Chessh.SSH.Daemon,
|
||||||
|
Plug.Cowboy.child_spec(
|
||||||
|
scheme: :http,
|
||||||
|
plug: Chessh.Web.Endpoint,
|
||||||
|
options: [port: Application.get_env(:chessh, Web)[:port]]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
opts = [strategy: :one_for_one, name: Chessh.Supervisor]
|
opts = [strategy: :one_for_one, name: Chessh.Supervisor]
|
||||||
|
|
||||||
with {:ok, pid} <- Supervisor.start_link(children, opts) do
|
with {:ok, pid} <- Supervisor.start_link(children, opts) do
|
||||||
|
@ -11,13 +11,20 @@ defmodule Chessh.Key do
|
|||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defimpl Jason.Encoder, for: Chessh.Key do
|
||||||
|
def encode(value, opts) do
|
||||||
|
Jason.Encode.map(Map.take(value, [:id, :key, :name]), opts)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def changeset(key, attrs) do
|
def changeset(key, attrs) do
|
||||||
key
|
key
|
||||||
|> cast(update_encode_key(attrs, :key), [:key])
|
|> cast(update_encode_key(attrs, :key), [:key, :player_id])
|
||||||
|> cast(attrs, [:name])
|
|> cast(attrs, [:name])
|
||||||
|> validate_required([:key, :name])
|
|> validate_required([:key, :name])
|
||||||
|> validate_format(:key, ~r/[\-\w\d]+ [^ ]+$/, message: "invalid public ssh key")
|
|> validate_format(:key, ~r/[\-\w\d]+ [^ ]+$/, message: "invalid public ssh key")
|
||||||
|> validate_format(:key, ~r/^(?!ssh-dss).+/, message: "DSA keys are not supported")
|
|> validate_format(:key, ~r/^(?!ssh-dss).+/, message: "DSA keys are not supported")
|
||||||
|
|> unique_constraint([:player_id, :key], message: "Player already has that key")
|
||||||
end
|
end
|
||||||
|
|
||||||
def encode_key(key) do
|
def encode_key(key) do
|
||||||
|
@ -5,6 +5,8 @@ defmodule Chessh.Player do
|
|||||||
|
|
||||||
@derive {Inspect, except: [:password]}
|
@derive {Inspect, except: [:password]}
|
||||||
schema "players" do
|
schema "players" do
|
||||||
|
field(:github_id, :integer)
|
||||||
|
|
||||||
field(:username, :string)
|
field(:username, :string)
|
||||||
|
|
||||||
field(:password, :string, virtual: true)
|
field(:password, :string, virtual: true)
|
||||||
@ -26,7 +28,7 @@ defmodule Chessh.Player do
|
|||||||
|
|
||||||
def registration_changeset(player, attrs, opts \\ []) do
|
def registration_changeset(player, attrs, opts \\ []) do
|
||||||
player
|
player
|
||||||
|> cast(attrs, [:username, :password])
|
|> cast(attrs, [:username, :password, :github_id])
|
||||||
|> validate_username()
|
|> validate_username()
|
||||||
|> validate_password(opts)
|
|> validate_password(opts)
|
||||||
end
|
end
|
||||||
|
3
lib/chessh/web/token.ex
Normal file
3
lib/chessh/web/token.ex
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
defmodule Chessh.Web.Token do
|
||||||
|
use Joken.Config
|
||||||
|
end
|
262
lib/chessh/web/web.ex
Normal file
262
lib/chessh/web/web.ex
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
defmodule Chessh.Web.Endpoint do
|
||||||
|
alias Chessh.{Player, Repo, Key}
|
||||||
|
alias Chessh.Web.Token
|
||||||
|
use Plug.Router
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
plug(Plug.Logger)
|
||||||
|
plug(:match)
|
||||||
|
|
||||||
|
plug(Plug.Parsers,
|
||||||
|
parsers: [:json],
|
||||||
|
pass: ["application/json"],
|
||||||
|
json_decoder: Jason
|
||||||
|
)
|
||||||
|
|
||||||
|
plug(:dispatch)
|
||||||
|
|
||||||
|
get "/oauth/redirect" do
|
||||||
|
[github_login_url, client_id, client_secret, github_user_api_url, github_user_agent] =
|
||||||
|
get_github_configs()
|
||||||
|
|
||||||
|
resp =
|
||||||
|
case conn.params do
|
||||||
|
%{"code" => req_token} ->
|
||||||
|
case :httpc.request(
|
||||||
|
:post,
|
||||||
|
{String.to_charlist(
|
||||||
|
"#{github_login_url}?client_id=#{client_id}&client_secret=#{client_secret}&code=#{req_token}"
|
||||||
|
), [], 'application/json', ''},
|
||||||
|
[],
|
||||||
|
[]
|
||||||
|
) do
|
||||||
|
{:ok, {{_, 200, 'OK'}, _, resp}} ->
|
||||||
|
URI.decode_query(String.Chars.to_string(resp))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
{status, body} =
|
||||||
|
case resp do
|
||||||
|
%{"access_token" => access_token} ->
|
||||||
|
case :httpc.request(
|
||||||
|
:get,
|
||||||
|
{String.to_charlist(github_user_api_url),
|
||||||
|
[
|
||||||
|
{'Authorization', String.to_charlist("Bearer #{access_token}")},
|
||||||
|
{'User-Agent', github_user_agent}
|
||||||
|
]},
|
||||||
|
[],
|
||||||
|
[]
|
||||||
|
) do
|
||||||
|
{:ok, {{_, 200, 'OK'}, _, user_details}} ->
|
||||||
|
%{"login" => username, "id" => github_id} =
|
||||||
|
Jason.decode!(String.Chars.to_string(user_details))
|
||||||
|
|
||||||
|
%Player{id: id} =
|
||||||
|
Repo.insert!(%Player{github_id: github_id, username: username},
|
||||||
|
on_conflict: [set: [github_id: github_id]],
|
||||||
|
conflict_target: :github_id
|
||||||
|
)
|
||||||
|
|
||||||
|
{200,
|
||||||
|
%{
|
||||||
|
success: true,
|
||||||
|
jwt:
|
||||||
|
Token.generate_and_sign!(%{
|
||||||
|
"uid" => id
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{400, %{errors: "Access token was incorrect. Try again."}}
|
||||||
|
end
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{400, %{errors: "Failed to retrieve token from GitHub. Try again."}}
|
||||||
|
end
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_resp_content_type("application/json")
|
||||||
|
|> send_resp(status, Jason.encode!(body))
|
||||||
|
end
|
||||||
|
|
||||||
|
put "/player/password" do
|
||||||
|
jwt =
|
||||||
|
Enum.find_value(conn.req_headers, fn {header, value} ->
|
||||||
|
if header === "authorization", do: value
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:ok, %{"uid" => uid}} = Token.verify_and_validate(jwt)
|
||||||
|
|
||||||
|
player = Repo.get(Player, uid)
|
||||||
|
|
||||||
|
{status, body} =
|
||||||
|
case conn.body_params do
|
||||||
|
%{"password" => password, "password_confirmation" => password_confirmation} ->
|
||||||
|
case Player.password_changeset(player, %{
|
||||||
|
password: password,
|
||||||
|
password_confirmation: password_confirmation
|
||||||
|
})
|
||||||
|
|> Repo.update() do
|
||||||
|
{:ok, player} ->
|
||||||
|
{200, %{success: true, id: player.id}}
|
||||||
|
|
||||||
|
{:error, %{valid?: false} = changeset} ->
|
||||||
|
{400, %{errors: format_errors(changeset)}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_resp_content_type("application/json")
|
||||||
|
|> send_resp(status, Jason.encode!(body))
|
||||||
|
end
|
||||||
|
|
||||||
|
post "/player/login" do
|
||||||
|
{status, body} =
|
||||||
|
case conn.body_params do
|
||||||
|
%{"username" => username, "password" => password} ->
|
||||||
|
player = Repo.get_by(Player, username: username)
|
||||||
|
|
||||||
|
case Player.valid_password?(player, password) do
|
||||||
|
true ->
|
||||||
|
{
|
||||||
|
200,
|
||||||
|
%{
|
||||||
|
token:
|
||||||
|
Token.generate_and_sign!(%{
|
||||||
|
"uid" => player.id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{
|
||||||
|
400,
|
||||||
|
%{
|
||||||
|
errors: "Invalid credentials"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{
|
||||||
|
400,
|
||||||
|
%{errors: "Username and password must be defined"}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_resp_content_type("application/json")
|
||||||
|
|> send_resp(status, Jason.encode!(body))
|
||||||
|
end
|
||||||
|
|
||||||
|
post "/player/keys" do
|
||||||
|
jwt =
|
||||||
|
Enum.find_value(conn.req_headers, fn {header, value} ->
|
||||||
|
if header === "authorization", do: value
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:ok, %{"uid" => uid}} = Token.verify_and_validate(jwt)
|
||||||
|
|
||||||
|
{status, body} =
|
||||||
|
case conn.body_params do
|
||||||
|
%{"key" => key, "name" => name} ->
|
||||||
|
case Key.changeset(%Key{}, %{player_id: uid, key: key, name: name}) |> Repo.insert() do
|
||||||
|
{:ok, _new_key} ->
|
||||||
|
{
|
||||||
|
200,
|
||||||
|
%{
|
||||||
|
success: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{:error, %{valid?: false} = changeset} ->
|
||||||
|
{
|
||||||
|
400,
|
||||||
|
%{
|
||||||
|
errors: format_errors(changeset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{
|
||||||
|
400,
|
||||||
|
%{errors: "Must define key and name"}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_resp_content_type("application/json")
|
||||||
|
|> send_resp(status, Jason.encode!(body))
|
||||||
|
end
|
||||||
|
|
||||||
|
get "/player/:id/keys" do
|
||||||
|
%{"id" => player_id} = conn.path_params
|
||||||
|
|
||||||
|
keys = (Repo.get(Player, player_id) |> Repo.preload([:keys])).keys
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_resp_content_type("application/json")
|
||||||
|
|> send_resp(200, Jason.encode!(%{keys: keys}))
|
||||||
|
end
|
||||||
|
|
||||||
|
delete "/keys/:id" do
|
||||||
|
%{"id" => key_id} = conn.path_params
|
||||||
|
|
||||||
|
jwt =
|
||||||
|
Enum.find_value(conn.req_headers, fn {header, value} ->
|
||||||
|
if header === "authorization", do: value
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:ok, %{"uid" => uid}} = Token.verify_and_validate(jwt)
|
||||||
|
|
||||||
|
key = Repo.get(Key, key_id)
|
||||||
|
|
||||||
|
{status, body} =
|
||||||
|
if key && uid == key.player_id do
|
||||||
|
case Repo.delete(key) do
|
||||||
|
{:ok, _} ->
|
||||||
|
{200, %{success: true}}
|
||||||
|
|
||||||
|
{:error, changeset} ->
|
||||||
|
{400, %{errors: format_errors(changeset)}}
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if !key do
|
||||||
|
{404, %{errors: "Key not found"}}
|
||||||
|
else
|
||||||
|
{401, %{errors: "You cannot delete that key"}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_resp_content_type("application/json")
|
||||||
|
|> send_resp(status, Jason.encode!(body))
|
||||||
|
end
|
||||||
|
|
||||||
|
match _ do
|
||||||
|
send_resp(conn, 404, "Route undefined")
|
||||||
|
end
|
||||||
|
|
||||||
|
defp format_errors(changeset) do
|
||||||
|
Ecto.Changeset.traverse_errors(changeset, fn {msg, opts} ->
|
||||||
|
Enum.reduce(opts, msg, fn {key, value}, acc ->
|
||||||
|
String.replace(acc, "%{#{key}}", to_string(value))
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp get_github_configs() do
|
||||||
|
Enum.map(
|
||||||
|
[
|
||||||
|
:github_oauth_login_url,
|
||||||
|
:github_client_id,
|
||||||
|
:github_client_secret,
|
||||||
|
:github_user_api_url,
|
||||||
|
:github_user_agent
|
||||||
|
],
|
||||||
|
fn key -> Application.get_env(:chessh, Web)[key] end
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
6
mix.exs
6
mix.exs
@ -17,7 +17,7 @@ defmodule Chessh.MixProject do
|
|||||||
def application do
|
def application do
|
||||||
[
|
[
|
||||||
mod: {Chessh.Application, []},
|
mod: {Chessh.Application, []},
|
||||||
extra_applications: [:logger, :crypto, :syn, :ssh]
|
extra_applications: [:logger, :crypto, :syn, :ssh, :plug_cowboy, :inets, :ssl]
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -34,7 +34,9 @@ defmodule Chessh.MixProject do
|
|||||||
{:bcrypt_elixir, "~> 3.0"},
|
{:bcrypt_elixir, "~> 3.0"},
|
||||||
{:hammer, "~> 6.1"},
|
{:hammer, "~> 6.1"},
|
||||||
{:syn, "~> 3.3"},
|
{:syn, "~> 3.3"},
|
||||||
{:jason, "~> 1.3"}
|
{:jason, "~> 1.3"},
|
||||||
|
{:plug_cowboy, "~> 2.2"},
|
||||||
|
{:joken, "~> 2.5"}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
11
mix.lock
11
mix.lock
@ -4,6 +4,9 @@
|
|||||||
"chess": {:hex, :chess, "0.4.1", "34c04abed2db81e0c56476c8e74fd85ef4e1bae23a4cd528e0ce8a052ada976f", [:mix], [], "hexpm", "692e0def99dc25af4af2413839a4605a2a0a713c2646f9afcf3a47c76a6de43d"},
|
"chess": {:hex, :chess, "0.4.1", "34c04abed2db81e0c56476c8e74fd85ef4e1bae23a4cd528e0ce8a052ada976f", [:mix], [], "hexpm", "692e0def99dc25af4af2413839a4605a2a0a713c2646f9afcf3a47c76a6de43d"},
|
||||||
"comeonin": {:hex, :comeonin, "5.3.3", "2c564dac95a35650e9b6acfe6d2952083d8a08e4a89b93a481acb552b325892e", [:mix], [], "hexpm", "3e38c9c2cb080828116597ca8807bb482618a315bfafd98c90bc22a821cc84df"},
|
"comeonin": {:hex, :comeonin, "5.3.3", "2c564dac95a35650e9b6acfe6d2952083d8a08e4a89b93a481acb552b325892e", [:mix], [], "hexpm", "3e38c9c2cb080828116597ca8807bb482618a315bfafd98c90bc22a821cc84df"},
|
||||||
"connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
|
"connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
|
||||||
|
"cowboy": {:hex, :cowboy, "2.9.0", "865dd8b6607e14cf03282e10e934023a1bd8be6f6bacf921a7e2a96d800cd452", [:make, :rebar3], [{:cowlib, "2.11.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "2c729f934b4e1aa149aff882f57c6372c15399a20d54f65c8d67bef583021bde"},
|
||||||
|
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
|
||||||
|
"cowlib": {:hex, :cowlib, "2.11.0", "0b9ff9c346629256c42ebe1eeb769a83c6cb771a6ee5960bd110ab0b9b872063", [:make, :rebar3], [], "hexpm", "2b3e9da0b21c4565751a6d4901c20d1b4cc25cbb7fd50d91d2ab6dd287bc86a9"},
|
||||||
"db_connection": {:hex, :db_connection, "2.4.3", "3b9aac9f27347ec65b271847e6baeb4443d8474289bd18c1d6f4de655b70c94d", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c127c15b0fa6cfb32eed07465e05da6c815b032508d4ed7c116122871df73c12"},
|
"db_connection": {:hex, :db_connection, "2.4.3", "3b9aac9f27347ec65b271847e6baeb4443d8474289bd18c1d6f4de655b70c94d", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c127c15b0fa6cfb32eed07465e05da6c815b032508d4ed7c116122871df73c12"},
|
||||||
"decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"},
|
"decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"},
|
||||||
"ecto": {:hex, :ecto, "3.9.2", "017db3bc786ff64271108522c01a5d3f6ba0aea5c84912cfb0dd73bf13684108", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "21466d5177e09e55289ac7eade579a642578242c7a3a9f91ad5c6583337a9d15"},
|
"ecto": {:hex, :ecto, "3.9.2", "017db3bc786ff64271108522c01a5d3f6ba0aea5c84912cfb0dd73bf13684108", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "21466d5177e09e55289ac7eade579a642578242c7a3a9f91ad5c6583337a9d15"},
|
||||||
@ -13,9 +16,17 @@
|
|||||||
"esshd": {:hex, :esshd, "0.2.1", "cded6a329c32bc3b3c15828bcd34203227bbef310db3c39a6f3c55cf5b29cd34", [:mix], [], "hexpm", "b058b56af53aba1c23522d72a3c39ab7f302e509af1c0ba1a748f00d93053c4d"},
|
"esshd": {:hex, :esshd, "0.2.1", "cded6a329c32bc3b3c15828bcd34203227bbef310db3c39a6f3c55cf5b29cd34", [:mix], [], "hexpm", "b058b56af53aba1c23522d72a3c39ab7f302e509af1c0ba1a748f00d93053c4d"},
|
||||||
"hammer": {:hex, :hammer, "6.1.0", "f263e3c3e9946bd410ea0336b2abe0cb6260af4afb3a221e1027540706e76c55", [:make, :mix], [{:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}], "hexpm", "b47e415a562a6d072392deabcd58090d8a41182cf9044cdd6b0d0faaaf68ba57"},
|
"hammer": {:hex, :hammer, "6.1.0", "f263e3c3e9946bd410ea0336b2abe0cb6260af4afb3a221e1027540706e76c55", [:make, :mix], [{:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: false]}], "hexpm", "b47e415a562a6d072392deabcd58090d8a41182cf9044cdd6b0d0faaaf68ba57"},
|
||||||
"jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
|
"jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
|
||||||
|
"joken": {:hex, :joken, "2.5.0", "09be497d804b8115eb6f07615cef2e60c2a1008fb89dc0aef0d4c4b4609b99aa", [:mix], [{:jose, "~> 1.11.2", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "22b25c89617c5ed8ca7b31026340a25ea0f9ca7160f9706b79be9ed81fdf74e7"},
|
||||||
|
"jose": {:hex, :jose, "1.11.5", "3bc2d75ffa5e2c941ca93e5696b54978323191988eb8d225c2e663ddfefd515e", [:mix, :rebar3], [], "hexpm", "dcd3b215bafe02ea7c5b23dafd3eb8062a5cd8f2d904fd9caa323d37034ab384"},
|
||||||
|
"mime": {:hex, :mime, "2.0.3", "3676436d3d1f7b81b5a2d2bd8405f412c677558c81b1c92be58c00562bb59095", [:mix], [], "hexpm", "27a30bf0db44d25eecba73755acf4068cbfe26a4372f9eb3e4ea3a45956bff6b"},
|
||||||
|
"plug": {:hex, :plug, "1.14.0", "ba4f558468f69cbd9f6b356d25443d0b796fbdc887e03fa89001384a9cac638f", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bf020432c7d4feb7b3af16a0c2701455cbbbb95e5b6866132cb09eb0c29adc14"},
|
||||||
|
"plug_cowboy": {:hex, :plug_cowboy, "2.6.0", "d1cf12ff96a1ca4f52207c5271a6c351a4733f413803488d75b70ccf44aebec2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "073cf20b753ce6682ed72905cd62a2d4bd9bad1bf9f7feb02a1b8e525bd94fa6"},
|
||||||
|
"plug_crypto": {:hex, :plug_crypto, "1.2.3", "8f77d13aeb32bfd9e654cb68f0af517b371fb34c56c9f2b58fe3df1235c1251a", [:mix], [], "hexpm", "b5672099c6ad5c202c45f5a403f21a3411247f164e4a8fab056e5cd8a290f4a2"},
|
||||||
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
|
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
|
||||||
"postgrex": {:hex, :postgrex, "0.16.5", "fcc4035cc90e23933c5d69a9cd686e329469446ef7abba2cf70f08e2c4b69810", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "edead639dc6e882618c01d8fc891214c481ab9a3788dfe38dd5e37fd1d5fb2e8"},
|
"postgrex": {:hex, :postgrex, "0.16.5", "fcc4035cc90e23933c5d69a9cd686e329469446ef7abba2cf70f08e2c4b69810", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "edead639dc6e882618c01d8fc891214c481ab9a3788dfe38dd5e37fd1d5fb2e8"},
|
||||||
|
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
|
||||||
"syn": {:hex, :syn, "3.3.0", "4684a909efdfea35ce75a9662fc523e4a8a4e8169a3df275e4de4fa63f99c486", [:rebar3], [], "hexpm", "e58ee447bc1094bdd21bf0acc102b1fbf99541a508cd48060bf783c245eaf7d6"},
|
"syn": {:hex, :syn, "3.3.0", "4684a909efdfea35ce75a9662fc523e4a8a4e8169a3df275e4de4fa63f99c486", [:rebar3], [], "hexpm", "e58ee447bc1094bdd21bf0acc102b1fbf99541a508cd48060bf783c245eaf7d6"},
|
||||||
"telemetry": {:hex, :telemetry, "1.1.0", "a589817034a27eab11144ad24d5c0f9fab1f58173274b1e9bae7074af9cbee51", [:rebar3], [], "hexpm", "b727b2a1f75614774cff2d7565b64d0dfa5bd52ba517f16543e6fc7efcc0df48"},
|
"telemetry": {:hex, :telemetry, "1.1.0", "a589817034a27eab11144ad24d5c0f9fab1f58173274b1e9bae7074af9cbee51", [:rebar3], [], "hexpm", "b727b2a1f75614774cff2d7565b64d0dfa5bd52ba517f16543e6fc7efcc0df48"},
|
||||||
|
"ueberauth": {:hex, :ueberauth, "0.10.3", "4a3bd7ab7b5d93d301d264f0f6858392654ee92171f4437d067d1ae227c051d9", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "1394f36a6c64e97f2038cf95228e7e52b4cb75417962e30418fbe9902b30e6d3"},
|
||||||
"uef": {:hex, :uef, "2.6.0", "0aa813d125c429e48c1d5abbab89837d8b7bd0499e6ee2f7dc9cc4287a475cfd", [:rebar3], [], "hexpm", "1585cba305dc7c0a3f75aeab15937b324184673d2922148b432edf9beba73d62"},
|
"uef": {:hex, :uef, "2.6.0", "0aa813d125c429e48c1d5abbab89837d8b7bd0499e6ee2f7dc9cc4287a475cfd", [:rebar3], [], "hexpm", "1585cba305dc7c0a3f75aeab15937b324184673d2922148b432edf9beba73d62"},
|
||||||
}
|
}
|
||||||
|
@ -5,11 +5,13 @@ defmodule Chessh.Repo.Migrations.CreatePlayer do
|
|||||||
execute("CREATE EXTENSION IF NOT EXISTS citext", "")
|
execute("CREATE EXTENSION IF NOT EXISTS citext", "")
|
||||||
|
|
||||||
create table(:players) do
|
create table(:players) do
|
||||||
|
add(:github_id, :integer, null: false)
|
||||||
add(:username, :citext, null: false)
|
add(:username, :citext, null: false)
|
||||||
add(:hashed_password, :string, null: false)
|
add(:hashed_password, :string, null: true)
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
||||||
create(unique_index(:players, [:username]))
|
create(unique_index(:players, [:username]))
|
||||||
|
create(unique_index(:players, [:github_id]))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -6,7 +6,7 @@ defmodule Chessh.Repo.Migrations.AddKeys do
|
|||||||
add(:key, :text, null: false)
|
add(:key, :text, null: false)
|
||||||
add(:name, :string, null: false)
|
add(:name, :string, null: false)
|
||||||
|
|
||||||
add(:player_id, references(:players))
|
add(:player_id, references(:players), null: false)
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
Loading…
Reference in New Issue
Block a user