diff --git a/config/config.exs b/config/config.exs index cbda7a0..b180368 100644 --- a/config/config.exs +++ b/config/config.exs @@ -26,6 +26,8 @@ config :logger, :console, # Use Jason for JSON parsing in Phoenix config :phoenix, :json_library, Jason +config :matrix_server, MatrixServer.Repo, migration_timestamps: [type: :utc_datetime] + # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. import_config "#{Mix.env()}.exs" diff --git a/config/dev.exs b/config/dev.exs index 1c3550e..dea1427 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -2,8 +2,8 @@ use Mix.Config # Configure your database config :matrix_server, MatrixServer.Repo, - username: "postgres", - password: "postgres", + username: "matrix_server", + password: "matrix_server", database: "matrix_server_dev", hostname: "localhost", show_sensitive_data_on_connection_error: true, @@ -55,3 +55,5 @@ config :phoenix, :stacktrace_depth, 20 # Initialize plugs at runtime for faster development compilation config :phoenix, :plug_init_mode, :runtime + +config :matrix_server, :server_name, "localhost" diff --git a/lib/matrix_server/account.ex b/lib/matrix_server/account.ex new file mode 100644 index 0000000..fd17395 --- /dev/null +++ b/lib/matrix_server/account.ex @@ -0,0 +1,51 @@ +defmodule MatrixServer.Account do + use Ecto.Schema + import Ecto.{Changeset, Query} + alias MatrixServer.{Repo, Account} + + @max_mxid_length 255 + @localpart_regex ~r/^([a-z0-9\._=\/])+$/ + + @primary_key {:localpart, :string, []} + schema "accounts" do + field :password_hash, :string, redact: true + + timestamps(updated_at: false) + end + + def available?(localpart) when is_binary(localpart) do + if Regex.match?(@localpart_regex, localpart) and + String.length(localpart) <= localpart_length() do + if Repo.one!( + Account + |> where([a], a.localpart == ^localpart) + |> select([a], count(a)) + ) == 0 do + :ok + else + {:error, :user_in_use} + end + else + {:error, :invalid_username} + end + end + + def changeset(%Account{} = account, attrs) do + account + |> cast(attrs, [:localpart, :password_hash]) + |> validate_required([:localpart, :password_hash]) + |> validate_length(:password_hash, max: 60) + |> validate_format(:localpart, @localpart_regex) + |> validate_length(:localpart, max: localpart_length()) + |> unique_constraint(:localpart, name: :accounts_pkey) + end + + defp localpart_length do + # Subtract the "@" and ":" in the MXID. + @max_mxid_length - 2 - String.length(server_name()) + end + + defp server_name do + Application.get_env(:matrix_server, :server_name) + end +end diff --git a/lib/matrix_server_web/channels/user_socket.ex b/lib/matrix_server_web/channels/user_socket.ex deleted file mode 100644 index 5cca723..0000000 --- a/lib/matrix_server_web/channels/user_socket.ex +++ /dev/null @@ -1,35 +0,0 @@ -defmodule MatrixServerWeb.UserSocket do - use Phoenix.Socket - - ## Channels - # channel "room:*", MatrixServerWeb.RoomChannel - - # 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. - @impl true - def connect(_params, socket, _connect_info) 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: - # - # MatrixServerWeb.Endpoint.broadcast("user_socket:#{user.id}", "disconnect", %{}) - # - # Returning `nil` makes this socket anonymous. - @impl true - def id(_socket), do: nil -end diff --git a/lib/matrix_server_web/controllers/account_controller.ex b/lib/matrix_server_web/controllers/account_controller.ex new file mode 100644 index 0000000..6e8c7aa --- /dev/null +++ b/lib/matrix_server_web/controllers/account_controller.ex @@ -0,0 +1,28 @@ +defmodule MatrixServerWeb.AccountController do + use MatrixServerWeb, :controller + alias MatrixServer.Account + + def register(conn, _params) do + conn + end + + def available(conn, params) do + localpart = Map.get(params, "username", "") + + {status, data} = + case Account.available?(localpart) do + :ok -> + {200, %{available: true}} + + {:error, :user_in_use} -> + {400, %{errcode: "M_USER_IN_USE", error: "Desired user ID is already taken."}} + + {:error, :invalid_username} -> + {400, %{errocode: "M_INVALID_USERNAME", error: "Desired user ID is invalid."}} + end + + conn + |> put_status(status) + |> json(data) + end +end diff --git a/lib/matrix_server_web/endpoint.ex b/lib/matrix_server_web/endpoint.ex index 9854850..e64ce55 100644 --- a/lib/matrix_server_web/endpoint.ex +++ b/lib/matrix_server_web/endpoint.ex @@ -10,10 +10,6 @@ defmodule MatrixServerWeb.Endpoint do signing_salt: "IGPHtnAo" ] - socket "/socket", MatrixServerWeb.UserSocket, - websocket: true, - longpoll: false - # Serve at "/" the static files from "priv/static" directory. # # You should set gzip to true if you are running phx.digest diff --git a/lib/matrix_server_web/router.ex b/lib/matrix_server_web/router.ex index d1dfc49..550a37c 100644 --- a/lib/matrix_server_web/router.ex +++ b/lib/matrix_server_web/router.ex @@ -5,7 +5,12 @@ defmodule MatrixServerWeb.Router do plug :accepts, ["json"] end - scope "/api", MatrixServerWeb do + scope "/_matrix", MatrixServerWeb do pipe_through :api + + scope "/client/r0", as: :client do + post "/register", AccountController, :register + get "/register/available", AccountController, :available + end end end diff --git a/mix.exs b/mix.exs index 7a8a74f..ae3400c 100644 --- a/mix.exs +++ b/mix.exs @@ -40,7 +40,8 @@ defmodule MatrixServer.MixProject do {:telemetry_metrics, "~> 0.4"}, {:telemetry_poller, "~> 0.4"}, {:jason, "~> 1.0"}, - {:plug_cowboy, "~> 2.0"} + {:plug_cowboy, "~> 2.0"}, + {:bcrypt_elixir, "~> 2.3"} ] end diff --git a/mix.lock b/mix.lock index 64b4082..423c4c8 100644 --- a/mix.lock +++ b/mix.lock @@ -1,4 +1,6 @@ %{ + "bcrypt_elixir": {:hex, :bcrypt_elixir, "2.3.0", "6cb662d5c1b0a8858801cf20997bd006e7016aa8c52959c9ef80e0f34fb60b7a", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "2c81d61d4f6ed0e5cf7bf27a9109b791ff216a1034b3d541327484f46dd43769"}, + "comeonin": {:hex, :comeonin, "5.3.2", "5c2f893d05c56ae3f5e24c1b983c2d5dfb88c6d979c9287a76a7feb1e1d8d646", [:mix], [], "hexpm", "d0993402844c49539aeadb3fe46a3c9bd190f1ecf86b6f9ebd71957534c95f04"}, "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.3.1", "ebd1a1d7aff97f27c66654e78ece187abdc646992714164380d8a041eda16754", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "3a6efd3366130eab84ca372cbd4a7d3c3a97bdfcfb4911233b035d117063f0af"}, @@ -7,6 +9,7 @@ "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, "ecto": {:hex, :ecto, "3.6.2", "efdf52acfc4ce29249bab5417415bd50abd62db7b0603b8bab0d7b996548c2bc", [: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", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "efad6dfb04e6f986b8a3047822b0f826d9affe8e4ebdd2aeedbfcb14fd48884e"}, "ecto_sql": {:hex, :ecto_sql, "3.6.2", "9526b5f691701a5181427634c30655ac33d11e17e4069eff3ae1176c764e0ba3", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.6.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.4.0 or ~> 0.5.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5ec9d7e6f742ea39b63aceaea9ac1d1773d574ea40df5a53ef8afbd9242fdb6b"}, + "elixir_make": {:hex, :elixir_make, "0.6.2", "7dffacd77dec4c37b39af867cedaabb0b59f6a871f89722c25b28fcd4bd70530", [:mix], [], "hexpm", "03e49eadda22526a7e5279d53321d1cced6552f344ba4e03e619063de75348d9"}, "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, "mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"}, "phoenix": {:hex, :phoenix, "1.5.9", "a6368d36cfd59d917b37c44386e01315bc89f7609a10a45a22f47c007edf2597", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.13 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.1.2 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7e4bce20a67c012f1fbb0af90e5da49fa7bf0d34e3a067795703b74aef75427d"}, diff --git a/priv/repo/migrations/20210622142112_add_accounts_table.exs b/priv/repo/migrations/20210622142112_add_accounts_table.exs new file mode 100644 index 0000000..45aede3 --- /dev/null +++ b/priv/repo/migrations/20210622142112_add_accounts_table.exs @@ -0,0 +1,11 @@ +defmodule MatrixServer.Repo.Migrations.AddAccountsTable do + use Ecto.Migration + + def change do + create table(:accounts, primary_key: false) do + add :localpart, :string, primary_key: true, null: false + add :password_hash, :string, size: 60, null: false + timestamps(updated_at: false) + end + end +end