summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimponic <loganhunt@simponic.xyz>2022-12-19 01:37:10 -0700
committerSimponic <loganhunt@simponic.xyz>2022-12-19 01:39:10 -0700
commit9cdfb6eb9cd30c4e06a7d9fef53e519983827d81 (patch)
tree4622a1809df6a6424f1b5f9f10c72dd02f9fc176
downloadchessh-9cdfb6eb9cd30c4e06a7d9fef53e519983827d81.tar.gz
chessh-9cdfb6eb9cd30c4e06a7d9fef53e519983827d81.zip
Initial commit!
-rw-r--r--.formatter.exs4
-rw-r--r--.gitignore29
-rw-r--r--README.md21
-rw-r--r--config/.gitignore1
-rw-r--r--config/config.exs11
-rw-r--r--config/dev.exs9
-rw-r--r--config/test.exs9
-rw-r--r--lib/auth/keys.ex8
-rw-r--r--lib/auth/password.ex7
-rw-r--r--lib/chessh.ex7
-rw-r--r--lib/chessh/application.ex9
-rw-r--r--lib/chessh/shell.ex18
-rw-r--r--lib/schema/player.ex81
-rw-r--r--lib/schema/repo.ex5
-rw-r--r--mix.exs32
-rw-r--r--mix.lock14
-rw-r--r--test/server_test.exs8
-rw-r--r--test/test_helper.exs1
18 files changed, 274 insertions, 0 deletions
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 <https://hexdocs.pm/server>.
+
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()