diff --git a/lib/matrix_server.ex b/lib/matrix_server.ex index 3734b87..53213d3 100644 --- a/lib/matrix_server.ex +++ b/lib/matrix_server.ex @@ -29,4 +29,6 @@ defmodule MatrixServer do def random_string(length, alphabet) when length >= 1 do for _ <- 1..length, into: "", do: <> end + + def default_room_version, do: "7" end diff --git a/lib/matrix_server/account.ex b/lib/matrix_server/account.ex index e594cd9..7a5fa04 100644 --- a/lib/matrix_server/account.ex +++ b/lib/matrix_server/account.ex @@ -33,18 +33,18 @@ defmodule MatrixServer.Account do end end - def register(%Register{} = api) do + def register(%Register{} = input) do account_params = %{ - localpart: api.username || MatrixServer.random_string(10, ?a..?z), - password_hash: Bcrypt.hash_pwd_salt(api.password) + localpart: input.username || MatrixServer.random_string(10, ?a..?z), + password_hash: Bcrypt.hash_pwd_salt(input.password) } Multi.new() |> Multi.insert(:account, changeset(%Account{}, account_params)) |> Multi.insert(:device, fn %{account: account} -> device_params = %{ - display_name: api.initial_device_display_name, - device_id: api.device_id || Device.generate_device_id(account.localpart) + display_name: input.initial_device_display_name, + device_id: input.device_id || Device.generate_device_id(account.localpart) } Ecto.build_assoc(account, :devices) @@ -53,14 +53,14 @@ defmodule MatrixServer.Account do |> Multi.run(:device_with_access_token, &Device.insert_new_access_token/2) end - def login(%Login{} = api) do - localpart = try_get_localpart(api.identifier.user) + def login(%Login{} = input) do + localpart = try_get_localpart(input.identifier.user) fn repo -> case repo.one(from a in Account, where: a.localpart == ^localpart) do %Account{password_hash: hash} = account -> - if Bcrypt.verify_pass(api.password, hash) do - case Device.login(api, account) do + if Bcrypt.verify_pass(input.password, hash) do + case Device.login(input, account) do {:ok, device} -> device @@ -87,6 +87,7 @@ defmodule MatrixServer.Account do end def changeset(account, params \\ %{}) do + # TODO: fix password_hash in params account |> cast(params, [:localpart, :password_hash]) |> validate_required([:localpart, :password_hash]) diff --git a/lib/matrix_server/device.ex b/lib/matrix_server/device.ex index 77850bc..40da497 100644 --- a/lib/matrix_server/device.ex +++ b/lib/matrix_server/device.ex @@ -50,16 +50,16 @@ defmodule MatrixServer.Device do "#{localpart}_#{time_string}" end - def login(%Login{} = api, account) do - device_id = api.device_id || generate_device_id(account.localpart) + def login(%Login{} = input, account) do + device_id = input.device_id || generate_device_id(account.localpart) access_token = generate_access_token(account.localpart, device_id) update_query = from(d in Device) |> update(set: [access_token: ^access_token, device_id: ^device_id]) |> then(fn q -> - if api.initial_device_display_name do - update(q, set: [display_name: ^api.initial_device_display_name]) + if input.initial_device_display_name do + update(q, set: [display_name: ^input.initial_device_display_name]) else q end @@ -67,7 +67,7 @@ defmodule MatrixServer.Device do device_params = %{ device_id: device_id, - display_name: api.initial_device_display_name + display_name: input.initial_device_display_name } Ecto.build_assoc(account, :devices) diff --git a/lib/matrix_server/event.ex b/lib/matrix_server/event.ex index 8087836..e4a787c 100644 --- a/lib/matrix_server/event.ex +++ b/lib/matrix_server/event.ex @@ -3,17 +3,19 @@ defmodule MatrixServer.Event do import Ecto.Changeset - alias MatrixServer.Room + alias MatrixServer.{Room, Event, Account} + alias MatrixServerWeb.API.CreateRoom + @primary_key {:event_id, :string, []} schema "events" do field :type, :string - field :timestamp, :naive_datetime + field :origin_server_ts, :integer field :state_key, :string field :sender, :string - field :content, :string + field :content, :map field :prev_events, {:array, :string} field :auth_events, {:array, :string} - belongs_to :room, Room + belongs_to :room, Room, type: :string end def changeset(event, params \\ %{}) do @@ -22,4 +24,84 @@ defmodule MatrixServer.Event do |> cast(params, [:type, :timestamp, :state_key, :sender, :content]) |> validate_required([:type, :timestamp, :sender]) end + + def new(room_id, sender) do + %Event{ + room_id: room_id, + sender: sender, + event_id: generate_event_id(), + origin_server_ts: DateTime.utc_now() |> DateTime.to_unix(), + prev_events: [], + auth_events: [] + } + end + + def create_room(room_id, creator, room_version) do + %Event{ + new(room_id, creator) + | type: "m.room.create", + state_key: "", + content: %{ + creator: creator, + room_version: room_version || MatrixServer.default_room_version() + } + } + end + + def join(room_id, sender) do + %Event{ + new(room_id, sender) + | type: "m.room.member", + state_key: sender, + content: %{ + membership: "invite" + } + } + end + + def room_creation_create_room(%CreateRoom{room_version: room_version}, %Account{ + localpart: localpart + }) do + fn repo, %{room: %Room{id: room_id}} -> + # TODO: state resolution + create_room(room_id, MatrixServer.get_mxid(localpart), room_version) + |> repo.insert() + end + end + + def room_creation_join_creator do + fn repo, + %{ + create_room_event: %Event{sender: creator, event_id: create_room_event_id}, + room: %Room{id: room_id} + } -> + # TODO: state resolution + join(room_id, creator) + |> Map.put(:prev_events, [create_room_event_id]) + |> Map.put(:auth_events, [create_room_event_id]) + |> repo.insert() + end + end + + def room_creation_power_levels(_input) do + fn _repo, %{} -> + {:ok, :ok} + end + end + + def room_creation_name(_input) do + fn _repo, %{} -> + {:ok, :ok} + end + end + + def room_creation_topic(_input) do + fn _repo, %{} -> + {:ok, :ok} + end + end + + def generate_event_id do + "$" <> MatrixServer.random_string(17) <> ":" <> MatrixServer.server_name() + end end diff --git a/lib/matrix_server/room.ex b/lib/matrix_server/room.ex index 801d173..9b518ab 100644 --- a/lib/matrix_server/room.ex +++ b/lib/matrix_server/room.ex @@ -12,18 +12,19 @@ defmodule MatrixServer.Room do field :visibility, Ecto.Enum, values: [:public, :private] end - def create(%CreateRoom{} = api) do - Multi.new() - |> Multi.insert(:room, Room.create_changeset(api)) - end - def changeset(room, params \\ %{}) do - room - |> cast(params, [:visibility]) + cast(room, params, [:visibility]) end - def create_changeset(%CreateRoom{} = api) do - %Room{visibility: api.visibility, id: MatrixServer.random_string(18)} - |> changeset() + def create_changeset(%CreateRoom{} = input) do + visibility = input.visibility || :public + + %Room{} + |> changeset(%{visibility: visibility}) + |> put_change(:id, generate_room_id()) + end + + def generate_room_id do + "!" <> MatrixServer.random_string(18) <> "@" <> MatrixServer.server_name() end end diff --git a/lib/matrix_server/room_server.ex b/lib/matrix_server/room_server.ex index 0c028ee..95fa68a 100644 --- a/lib/matrix_server/room_server.ex +++ b/lib/matrix_server/room_server.ex @@ -1,15 +1,16 @@ defmodule MatrixServer.RoomServer do use GenServer - alias MatrixServer.{Repo, Room} + alias MatrixServer.{Repo, Room, Event} alias MatrixServerWeb.API.CreateRoom + alias Ecto.Multi def start_link(_opts) do GenServer.start_link(__MODULE__, :ok, name: __MODULE__) end - def create_room(params) do - GenServer.call(__MODULE__, {:create_room, params}) + def create_room(%CreateRoom{} = input, account) do + GenServer.call(__MODULE__, {:create_room, input, account}) end @impl true @@ -18,10 +19,18 @@ defmodule MatrixServer.RoomServer do end @impl true - def handle_call({:create_room, %CreateRoom{} = api}, _from, state) do - Room.create(api) - |> Repo.transaction() + def handle_call({:create_room, input, account}, _from, state) do + # TODO: preset events, initial_state events, invite, invite_3pid + result = + Multi.new() + |> Multi.insert(:room, Room.create_changeset(input)) + |> Multi.run(:create_room_event, Event.room_creation_create_room(input, account)) + |> Multi.run(:join_creator_event, Event.room_creation_join_creator()) + |> Multi.run(:power_levels_event, Event.room_creation_power_levels(input)) + |> Multi.run(:name_event, Event.room_creation_name(input)) + |> Multi.run(:topic_event, Event.room_creation_topic(input)) + |> Repo.transaction() - {:reply, :ok, state} + {:reply, result, state} end end diff --git a/lib/matrix_server_web/controllers/auth_controller.ex b/lib/matrix_server_web/controllers/auth_controller.ex index 76631cf..5ce6366 100644 --- a/lib/matrix_server_web/controllers/auth_controller.ex +++ b/lib/matrix_server_web/controllers/auth_controller.ex @@ -14,14 +14,14 @@ defmodule MatrixServerWeb.AuthController do def register(conn, %{"auth" => %{"type" => @register_type}} = params) do case Register.changeset(params) do %Changeset{valid?: true} = cs -> - api = apply_changes(cs) + input = apply_changes(cs) - case Account.register(api) |> Repo.transaction() do + case Account.register(input) |> Repo.transaction() do {:ok, %{device_with_access_token: device}} -> data = %{user_id: MatrixServer.get_mxid(device.localpart)} data = - if not api.inhibit_login do + if not input.inhibit_login do data |> Map.put(:device_id, device.device_id) |> Map.put(:access_token, device.access_token) @@ -73,13 +73,9 @@ defmodule MatrixServerWeb.AuthController do ) do case Login.changeset(params) do %Changeset{valid?: true} = cs -> - api = apply_changes(cs) - # input = - # apply_changes(cs) - # |> Map.from_struct() - # |> MatrixServer.maybe_update_map(:initial_device_display_name, :display_name) + input = apply_changes(cs) - case Account.login(api) |> Repo.transaction() do + case Account.login(input) |> Repo.transaction() do {:ok, device} -> data = %{ user_id: MatrixServer.get_mxid(device.localpart), diff --git a/lib/matrix_server_web/controllers/room_controller.ex b/lib/matrix_server_web/controllers/room_controller.ex index b246513..611d64c 100644 --- a/lib/matrix_server_web/controllers/room_controller.ex +++ b/lib/matrix_server_web/controllers/room_controller.ex @@ -6,13 +6,14 @@ defmodule MatrixServerWeb.RoomController do alias MatrixServerWeb.API.{CreateRoom} alias Ecto.Changeset + alias Plug.Conn - def create(conn, params) do + def create(%Conn{assigns: %{account: account}} = conn, params) do case CreateRoom.changeset(params) do %Changeset{valid?: true} = cs -> - api_struct = apply_changes(cs) - - MatrixServer.RoomServer.create_room(api_struct) + cs + |> apply_changes() + |> MatrixServer.RoomServer.create_room(account) conn |> put_status(200) diff --git a/priv/repo/migrations/20210714150725_change_event_content_to_json.exs b/priv/repo/migrations/20210714150725_change_event_content_to_json.exs new file mode 100644 index 0000000..abdfcf5 --- /dev/null +++ b/priv/repo/migrations/20210714150725_change_event_content_to_json.exs @@ -0,0 +1,10 @@ +defmodule MatrixServer.Repo.Migrations.ChangeEventContentToJson do + use Ecto.Migration + + def change do + execute( + "alter table events alter column content type jsonb using (content::jsonb);", + "alter table events alter column content type character varying(255);" + ) + end +end diff --git a/priv/repo/migrations/20210715093944_change_event_timestamp_to_integer.exs b/priv/repo/migrations/20210715093944_change_event_timestamp_to_integer.exs new file mode 100644 index 0000000..78c3ecd --- /dev/null +++ b/priv/repo/migrations/20210715093944_change_event_timestamp_to_integer.exs @@ -0,0 +1,10 @@ +defmodule MatrixServer.Repo.Migrations.ChangeEventTimestampToInteger do + use Ecto.Migration + + def change do + alter table(:events) do + remove :timestamp, :string, null: false + add :origin_server_ts, :integer, null: false + end + end +end diff --git a/priv/repo/migrations/20210716212330_change_event_id_name.exs b/priv/repo/migrations/20210716212330_change_event_id_name.exs new file mode 100644 index 0000000..99129ce --- /dev/null +++ b/priv/repo/migrations/20210716212330_change_event_id_name.exs @@ -0,0 +1,7 @@ +defmodule MatrixServer.Repo.Migrations.ChangeEventIdName do + use Ecto.Migration + + def change do + rename table(:events), :id, to: :event_id + end +end diff --git a/test/controllers/auth_controller_test.exs b/test/controllers/auth_controller_test.exs index 58777a3..0ff9965 100644 --- a/test/controllers/auth_controller_test.exs +++ b/test/controllers/auth_controller_test.exs @@ -123,6 +123,7 @@ defmodule MatrixServerWeb.AuthControllerTest do test "handles wrong password", %{conn: conn} do Factory.insert(:account, localpart: "sneed", password_hash: Bcrypt.hash_pwd_salt("surprise")) + conn = post_json(conn, Routes.auth_path(Endpoint, :login), @basic_params) assert %{"errcode" => "M_FORBIDDEN"} = json_response(conn, 400)