commit 9cdfb6eb9cd30c4e06a7d9fef53e519983827d81 Author: Simponic Date: Mon Dec 19 01:37:10 2022 -0700 Initial commit! diff --git a/.formatter.exs b/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..16acc84 --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +server-*.tar + +# Temporary files, for example, from tests. +/tmp/ + +# Private files, like configuration secrets or keys. +/priv/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e4aaab9 --- /dev/null +++ b/README.md @@ -0,0 +1,21 @@ +# Server + +**TODO: Add description** + +## Installation + +If [available in Hex](https://hex.pm/docs/publish), the package can be installed +by adding `server` to your list of dependencies in `mix.exs`: + +```elixir +def deps do + [ + {:server, "~> 0.1.0"} + ] +end +``` + +Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) +and published on [HexDocs](https://hexdocs.pm). Once published, the docs can +be found at . + diff --git a/config/.gitignore b/config/.gitignore new file mode 100644 index 0000000..e6fa00c --- /dev/null +++ b/config/.gitignore @@ -0,0 +1 @@ +prod.exs \ No newline at end of file diff --git a/config/config.exs b/config/config.exs new file mode 100644 index 0000000..807824a --- /dev/null +++ b/config/config.exs @@ -0,0 +1,11 @@ +import Config + +config :esshd, + enabled: true, + priv_dir: Path.join(Path.dirname(__DIR__), "priv/keys"), + handler: {Chessh.Shell, :on_shell, 4}, + port: 42069, + public_key_authenticator: Chessh.Auth.KeyAuthenticator, + password_authenticator: Chessh.Auth.PasswordAuthenticator + +import_config "#{config_env()}.exs" diff --git a/config/dev.exs b/config/dev.exs new file mode 100644 index 0000000..f71d02e --- /dev/null +++ b/config/dev.exs @@ -0,0 +1,9 @@ +import Config + +config :chessh, Chessh.Repo, + database: "chessh", + username: "postgres", + password: "postgres", + hostname: "localhost" + +config :chessh, ecto_repos: [Chessh.Repo] diff --git a/config/test.exs b/config/test.exs new file mode 100644 index 0000000..b46bc25 --- /dev/null +++ b/config/test.exs @@ -0,0 +1,9 @@ +import Config + +config :chessh, Chessh.Repo, + database: "chessh-test", + username: "postgres", + password: "postgres", + hostname: "localhost" + +config :chessh, ecto_repos: [Chessh.Repo] diff --git a/lib/auth/keys.ex b/lib/auth/keys.ex new file mode 100644 index 0000000..3e0f142 --- /dev/null +++ b/lib/auth/keys.ex @@ -0,0 +1,8 @@ +defmodule Chessh.Auth.KeyAuthenticator do + use Sshd.PublicKeyAuthenticator + require Logger + + def authenticate(_, _, _) do + false + end +end diff --git a/lib/auth/password.ex b/lib/auth/password.ex new file mode 100644 index 0000000..5e31b33 --- /dev/null +++ b/lib/auth/password.ex @@ -0,0 +1,7 @@ +defmodule Chessh.Auth.PasswordAuthenticator do + use Sshd.PasswordAuthenticator + + def authenticate(_username, _password) do + true + end +end diff --git a/lib/chessh.ex b/lib/chessh.ex new file mode 100644 index 0000000..42bb21f --- /dev/null +++ b/lib/chessh.ex @@ -0,0 +1,7 @@ +defmodule Chessh do + require Logger + + def hello() do + :world + end +end diff --git a/lib/chessh/application.ex b/lib/chessh/application.ex new file mode 100644 index 0000000..c760532 --- /dev/null +++ b/lib/chessh/application.ex @@ -0,0 +1,9 @@ +defmodule Chessh.Application do + use Application + + def start(_, _) do + children = [Chessh.Repo] + opts = [strategy: :one_for_one, name: Chessh.Supervisor] + Supervisor.start_link(children, opts) + end +end diff --git a/lib/chessh/shell.ex b/lib/chessh/shell.ex new file mode 100644 index 0000000..f9f475d --- /dev/null +++ b/lib/chessh/shell.ex @@ -0,0 +1,18 @@ +defmodule Chessh.Shell do + use Sshd.ShellHandler + + def on_shell(_username, _public_key, _ip, _port) do + :ok = + IO.puts( + "Interactive example SSH shell - type exit ENTER to quit and it is running on #{inspect(self())}" + ) + end + + def on_connect(_username, _ip, _port, _method) do + Logger.debug("Connection established") + end + + def on_disconnect(_username, _ip, _port) do + Logger.debug("Connection disestablished") + end +end diff --git a/lib/schema/player.ex b/lib/schema/player.ex new file mode 100644 index 0000000..7d9bb6e --- /dev/null +++ b/lib/schema/player.ex @@ -0,0 +1,81 @@ +defmodule Chessh.Player do + use Ecto.Schema + import Ecto.Changeset + + @derive {Inspect, except: [:password]} + schema "players" do + field(:username, :string) + + field(:password, :string, virtual: true) + field(:hashed_password, :string) + + timestamps() + end + + def registration_changeset(user, attrs, opts \\ []) do + user + |> cast(attrs, [:username, :password]) + |> validate_username() + |> validate_password(opts) + end + + def password_changeset(user, attrs, opts \\ []) do + user + |> cast(attrs, [:password]) + |> validate_confirmation(:password, message: "does not match password") + |> validate_password(opts) + end + + def valid_password?(%Chessh.Player{hashed_password: hashed_password}, password) + when is_binary(hashed_password) and byte_size(password) > 0 do + Bcrypt.verify_pass(password, hashed_password) + end + + def valid_password?(_, _) do + Bcrypt.no_user_verify() + false + end + + def validate_current_password(changeset, password) do + if valid_password?(changeset.data, password) do + changeset + else + add_error(changeset, :current_password, "is not valid") + end + end + + defp validate_username(changeset) do + changeset + |> validate_required([:username]) + |> validate_length(:username, min: 2, max: 12) + |> validate_format(:username, ~r/^[a-zA-Z0-9_\-]*$/, + message: "only letters, numbers, underscores, and hyphens allowed" + ) + |> unique_constraint(:username) + |> lowercase(:username) + end + + defp validate_password(changeset, opts) do + changeset + |> validate_required([:password]) + |> validate_length(:password, min: 8, max: 80) + |> maybe_hash_password(opts) + end + + defp maybe_hash_password(changeset, opts) do + hash_password? = Keyword.get(opts, :hash_password, true) + password = get_change(changeset, :password) + + if hash_password? && password && changeset.valid? do + changeset + |> put_change(:hashed_password, Bcrypt.hash_pwd_salt(password)) + |> delete_change(:password) + else + changeset + end + end + + defp lowercase(changeset, field) do + Map.update!(changeset, field, &String.downcase/1) + end +end diff --git a/lib/schema/repo.ex b/lib/schema/repo.ex new file mode 100644 index 0000000..27d81b9 --- /dev/null +++ b/lib/schema/repo.ex @@ -0,0 +1,5 @@ +defmodule Chessh.Repo do + use Ecto.Repo, + otp_app: :chessh, + adapter: Ecto.Adapters.Postgres +end diff --git a/mix.exs b/mix.exs new file mode 100644 index 0000000..01a7a6b --- /dev/null +++ b/mix.exs @@ -0,0 +1,32 @@ +defmodule Chessh.MixProject do + use Mix.Project + + def project do + [ + app: :chessh, + version: "0.1.0", + elixir: "~> 1.14", + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:esshd, :logger] + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + {:chess, "~> 0.4.1"}, + {:esshd, "~> 0.2.1"}, + {:ecto, "~> 3.9"}, + {:ecto_sql, "~> 3.9"}, + {:postgrex, "~> 0.16.5"}, + {:bcrypt_elixir, "~> 3.0"} + ] + end +end diff --git a/mix.lock b/mix.lock new file mode 100644 index 0000000..ec97627 --- /dev/null +++ b/mix.lock @@ -0,0 +1,14 @@ +%{ + "bcrypt_elixir": {:hex, :bcrypt_elixir, "3.0.1", "9be815469e6bfefec40fa74658ecbbe6897acfb57614df1416eeccd4903f602c", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "486bb95efb645d1efc6794c1ddd776a186a9a713abf06f45708a6ce324fb96cf"}, + "chess": {:hex, :chess, "0.4.1", "34c04abed2db81e0c56476c8e74fd85ef4e1bae23a4cd528e0ce8a052ada976f", [:mix], [], "hexpm", "692e0def99dc25af4af2413839a4605a2a0a713c2646f9afcf3a47c76a6de43d"}, + "comeonin": {:hex, :comeonin, "5.3.3", "2c564dac95a35650e9b6acfe6d2952083d8a08e4a89b93a481acb552b325892e", [:mix], [], "hexpm", "3e38c9c2cb080828116597ca8807bb482618a315bfafd98c90bc22a821cc84df"}, + "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"}, + "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"}, + "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_sql": {:hex, :ecto_sql, "3.9.1", "9bd5894eecc53d5b39d0c95180d4466aff00e10679e13a5cfa725f6f85c03c22", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5fd470a4fff2e829bbf9dcceb7f3f9f6d1e49b4241e802f614de6b8b67c51118"}, + "elixir_make": {:hex, :elixir_make, "0.7.2", "e83548b0500e654d1a595f1134af4862a2e92ec3282ec4c2a17641e9aa45ee73", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "05fb44abf9582381c2eb1b73d485a55288c581071de0ee3ee1084ee69d6a8e5f"}, + "esshd": {:hex, :esshd, "0.2.1", "cded6a329c32bc3b3c15828bcd34203227bbef310db3c39a6f3c55cf5b29cd34", [:mix], [], "hexpm", "b058b56af53aba1c23522d72a3c39ab7f302e509af1c0ba1a748f00d93053c4d"}, + "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"}, + "telemetry": {:hex, :telemetry, "1.1.0", "a589817034a27eab11144ad24d5c0f9fab1f58173274b1e9bae7074af9cbee51", [:rebar3], [], "hexpm", "b727b2a1f75614774cff2d7565b64d0dfa5bd52ba517f16543e6fc7efcc0df48"}, +} diff --git a/test/server_test.exs b/test/server_test.exs new file mode 100644 index 0000000..d710e5c --- /dev/null +++ b/test/server_test.exs @@ -0,0 +1,8 @@ +defmodule ChesshTest do + use ExUnit.Case + doctest Chessh + + test "greets the world" do + assert Chessh.hello() == :world + end +end diff --git a/test/test_helper.exs b/test/test_helper.exs new file mode 100644 index 0000000..869559e --- /dev/null +++ b/test/test_helper.exs @@ -0,0 +1 @@ +ExUnit.start()