diff --git a/lib/architex.ex b/lib/architex.ex index 414248f..67c9608 100644 --- a/lib/architex.ex +++ b/lib/architex.ex @@ -154,16 +154,6 @@ defmodule Architex do |> Enum.into(%{}) end - @doc """ - Serialize and encode the given struct. - """ - @spec serialize_and_encode(struct()) :: {:ok, String.t()} | {:error, Jason.EncodeError.t()} - def serialize_and_encode(struct) do - struct - |> to_serializable_map() - |> encode_canonical_json() - end - @doc """ Add a signature to the given map under the `:signatures` key. diff --git a/lib/architex/encodable_map.ex b/lib/architex/encodable_map.ex index 5c67439..96c2f77 100644 --- a/lib/architex/encodable_map.ex +++ b/lib/architex/encodable_map.ex @@ -1,7 +1,6 @@ # https://github.com/michalmuskala/jason/issues/69 defmodule Architex.EncodableMap do alias Architex.EncodableMap - alias Architex.Types.{UserId, RoomId, EventId, GroupId, AliasId} defstruct pairs: [] @@ -15,12 +14,6 @@ defmodule Architex.EncodableMap do pairs = map |> Enum.map(fn - {k, v} - when is_struct(v, UserId) or is_struct(v, RoomId) or is_struct(v, EventId) or - is_struct(v, GroupId) or is_struct(v, AliasId) -> - # Simply convert IDs to a string. - {k, to_string(v)} - {k, v} when is_map(v) -> {k, from_map(v)} diff --git a/lib/architex/room_server.ex b/lib/architex/room_server.ex index da567b8..d32ae55 100644 --- a/lib/architex/room_server.ex +++ b/lib/architex/room_server.ex @@ -398,7 +398,7 @@ defmodule Architex.RoomServer do end end - @spec insert_single_event(Room.t(), t(), Event.t()) :: + @spec insert_single_event(Room.t(), t(), %Event{}) :: (() -> {t(), Room.t(), Event.t()} | {:error, atom()}) defp insert_single_event(room, state_set, event) do fn -> @@ -472,7 +472,7 @@ defmodule Architex.RoomServer do # Get the events for room creation as dictated by the given preset. # TODO: trusted_private_chat: # All invitees are given the same power level as the room creator. - @spec room_creation_preset(Account.t(), String.t() | nil, Room.t()) :: [Event.t()] + @spec room_creation_preset(Account.t(), String.t() | nil, Room.t()) :: [%Event{}] defp room_creation_preset(account, nil, %Room{visibility: visibility} = room) do preset = case visibility do @@ -505,7 +505,7 @@ defmodule Architex.RoomServer do # - Content hash # - Event ID # - Signature - @spec finalize_and_insert_event(Event.t(), t(), Room.t()) :: + @spec finalize_and_insert_event(%Event{}, t(), Room.t()) :: {:ok, t(), Room.t(), Event.t()} | {:error, atom()} defp finalize_and_insert_event( event, @@ -516,6 +516,7 @@ defmodule Architex.RoomServer do event |> Map.put(:auth_events, auth_events_for_event(event, state_set)) |> Map.put(:prev_events, forward_extremities) + |> Map.put(:depth, get_depth(forward_extremities)) case Event.post_process(event) do {:ok, event} -> authenticate_and_insert_event(event, state_set, room) @@ -523,8 +524,17 @@ defmodule Architex.RoomServer do end end + @spec get_depth([String.t()]) :: integer() + defp get_depth(prev_event_ids) do + Event + |> where([e], e.id in ^prev_event_ids) + |> select([e], e.depth) + |> Repo.all() + |> Enum.max(fn -> 0 end) + end + # Get the auth events for an events. - @spec auth_events_for_event(Event.t(), t()) :: [{String.t(), String.t()}] + @spec auth_events_for_event(%Event{}, t()) :: [Event.t()] defp auth_events_for_event(%Event{type: "m.room.create"}, _), do: [] defp auth_events_for_event( diff --git a/lib/architex/schema/event.ex b/lib/architex/schema/event.ex index e924ae8..6a6bccf 100644 --- a/lib/architex/schema/event.ex +++ b/lib/architex/schema/event.ex @@ -6,76 +6,58 @@ defmodule Architex.Event do alias Architex.{Repo, Room, Event, Account, EncodableMap, KeyServer} alias Architex.Types.UserId - # TODO: It seems unsigned is always set, even though it is not specified? + # TODO: It seems unsigned is always set in DB, even though it is not specified? @type t :: %__MODULE__{ - type: String.t(), - origin_server_ts: integer(), - state_key: String.t() | nil, - sender: UserId.t(), - content: map(), - prev_events: [String.t()] | nil, + nid: integer(), + id: String.t(), auth_events: [String.t()], - unsigned: map() | nil, - signatures: map() | nil, - hashes: map() | nil + content: map(), + depth: integer(), + hashes: map(), + origin: String.t(), + origin_server_ts: integer(), + prev_events: [String.t()], + redacts: String.t() | nil, + room_id: String.t(), + sender: UserId.t(), + signatures: map(), + state_key: String.t() | nil, + type: String.t(), + unsigned: map() | nil } - @primary_key {:nid, :id, autogenerate: true} + @primary_key false schema "events" do - field :type, :string - field :origin_server_ts, :integer - field :state_key, :string - field :sender, UserId - field :content, :map - field :prev_events, {:array, :string} - field :auth_events, {:array, :string} - field :unsigned, :map - field :signatures, {:map, {:map, :string}} - field :hashes, {:map, :string} + field :nid, :id, primary_key: true, autogenerate: true field :id, :string + # PDU fields + field :auth_events, {:array, :string} + field :content, :map + field :depth, :integer + field :hashes, {:map, :string} + field :origin, :string + field :origin_server_ts, :integer + field :prev_events, {:array, :string} + field :redacts, :string belongs_to :room, Room, type: :string + field :sender, UserId + field :signatures, {:map, {:map, :string}} + field :state_key, :string + field :type, :string + field :unsigned, :map end - # TODO: Move this to a dedicated function in Event.Formatters. - defimpl Jason.Encoder, for: Event do - @pdu_keys [ - :auth_events, - :content, - :depth, - :hashes, - :origin, - :origin_server_ts, - :prev_events, - :redacts, - :room_id, - :sender, - :signatures, - :state_key, - :type, - :unsigned - ] - - def encode(event, opts) do - event - |> Map.take(@pdu_keys) - |> Map.update!(:sender, &Kernel.to_string/1) - |> Jason.Encode.map(opts) - end - end - - @spec new(Room.t(), Account.t()) :: %Event{} def new(%Room{id: room_id}, %Account{localpart: localpart}) do %Event{ room_id: room_id, sender: %UserId{localpart: localpart, domain: Architex.server_name()}, origin_server_ts: DateTime.utc_now() |> DateTime.to_unix(:millisecond), - prev_events: [], - auth_events: [] + origin: Architex.server_name() } end - @spec custom_message(Room.t(), Account.t(), String.t(), map()) :: t() + @spec custom_message(Room.t(), Account.t(), String.t(), map()) :: %Event{} def custom_message(room, sender, type, content) do %Event{ Event.new(room, sender) @@ -224,7 +206,7 @@ defmodule Architex.Event do defp calculate_content_hash(event) do m = event - |> Architex.to_serializable_map() + |> Architex.Event.Formatters.as_pdu() |> Map.drop([:unsigned, :signature, :hashes]) |> EncodableMap.from_map() @@ -233,11 +215,10 @@ defmodule Architex.Event do end end - @spec redact(t()) :: map() defp redact(%Event{type: type, content: content} = event) do redacted_event = event - |> Architex.to_serializable_map() + |> Architex.Event.Formatters.as_pdu() |> Map.take([ :id, :type, diff --git a/lib/architex/schema/event/formatters.ex b/lib/architex/schema/event/formatters.ex index 02407ca..15a17e5 100644 --- a/lib/architex/schema/event/formatters.ex +++ b/lib/architex/schema/event/formatters.ex @@ -1,6 +1,10 @@ defmodule Architex.Event.Formatters do + @moduledoc """ + Functions to format events in order to convert them to JSON. + """ alias Architex.Event + @spec for_client(Event.t()) :: map() def for_client(%Event{ content: content, type: type, @@ -23,4 +27,42 @@ defmodule Architex.Event.Formatters do data end + + @spec as_pdu(Event.t()) :: map() + def as_pdu(%Event{ + auth_events: auth_events, + content: content, + depth: depth, + hashes: hashes, + origin: origin, + origin_server_ts: origin_server_ts, + prev_events: prev_events, + redacts: redacts, + room_id: room_id, + sender: sender, + signatures: signatures, + state_key: state_key, + type: type, + unsigned: unsigned + }) do + data = %{ + auth_events: auth_events, + content: content, + depth: depth, + hashes: hashes, + origin: origin, + origin_server_ts: origin_server_ts, + prev_events: prev_events, + room_id: room_id, + sender: to_string(sender), + signatures: signatures, + type: type + } + + data = if redacts, do: Map.put(data, :redacts, redacts), else: data + data = if state_key, do: Map.put(data, :state_key, state_key), else: data + data = if unsigned, do: Map.put(data, :unsigned, unsigned), else: data + + data + end end diff --git a/lib/architex/schema/event/generators.ex b/lib/architex/schema/event/generators.ex index 92bae47..abb52ad 100644 --- a/lib/architex/schema/event/generators.ex +++ b/lib/architex/schema/event/generators.ex @@ -1,7 +1,7 @@ defmodule Architex.Event.Join do alias Architex.{Event, Account, Room} - @spec new(Room.t(), Account.t()) :: Event.t() + @spec new(Room.t(), Account.t()) :: %Event{} def new(room, %Account{localpart: localpart} = sender) do mxid = Architex.get_mxid(localpart) @@ -19,7 +19,7 @@ end defmodule Architex.Event.CreateRoom do alias Architex.{Event, Account, Room} - @spec new(Room.t(), Account.t(), String.t()) :: Event.t() + @spec new(Room.t(), Account.t(), String.t()) :: %Event{} def new(room, %Account{localpart: localpart} = creator, room_version) do mxid = Architex.get_mxid(localpart) @@ -38,7 +38,7 @@ end defmodule Architex.Event.PowerLevels do alias Architex.{Event, Account, Room} - @spec new(Room.t(), Account.t()) :: Event.t() + @spec new(Room.t(), Account.t()) :: %Event{} def new(room, %Account{localpart: localpart} = sender) do mxid = Architex.get_mxid(localpart) @@ -69,7 +69,7 @@ end defmodule Architex.Event.Name do alias Architex.{Event, Account, Room} - @spec new(Room.t(), Account.t(), String.t()) :: Event.t() + @spec new(Room.t(), Account.t(), String.t()) :: %Event{} def new(room, sender, name) do %Event{ Event.new(room, sender) @@ -85,7 +85,7 @@ end defmodule Architex.Event.Topic do alias Architex.{Event, Account, Room} - @spec new(Room.t(), Account.t(), String.t()) :: Event.t() + @spec new(Room.t(), Account.t(), String.t()) :: %Event{} def new(room, sender, topic) do %Event{ Event.new(room, sender) @@ -101,7 +101,7 @@ end defmodule Architex.Event.JoinRules do alias Architex.{Event, Account, Room} - @spec new(Room.t(), Account.t(), String.t()) :: Event.t() + @spec new(Room.t(), Account.t(), String.t()) :: %Event{} def new(room, sender, join_rule) do %Event{ Event.new(room, sender) @@ -117,7 +117,7 @@ end defmodule Architex.Event.HistoryVisibility do alias Architex.{Event, Account, Room} - @spec new(Room.t(), Account.t(), String.t()) :: Event.t() + @spec new(Room.t(), Account.t(), String.t()) :: %Event{} def new(room, sender, history_visibility) do %Event{ Event.new(room, sender) @@ -133,7 +133,7 @@ end defmodule Architex.Event.GuestAccess do alias Architex.{Event, Account, Room} - @spec new(Room.t(), Account.t(), String.t()) :: Event.t() + @spec new(Room.t(), Account.t(), String.t()) :: %Event{} def new(room, sender, guest_access) do %Event{ Event.new(room, sender) @@ -149,7 +149,7 @@ end defmodule Architex.Event.Invite do alias Architex.{Event, Account, Room} - @spec new(Room.t(), Account.t(), String.t()) :: Event.t() + @spec new(Room.t(), Account.t(), String.t()) :: %Event{} def new(room, sender, user_id) do %Event{ Event.new(room, sender) @@ -165,7 +165,7 @@ end defmodule Architex.Event.Leave do alias Architex.{Event, Account, Room} - @spec new(Room.t(), Account.t()) :: Event.t() + @spec new(Room.t(), Account.t()) :: %Event{} def new(room, sender) do %Event{ Event.new(room, sender) @@ -181,7 +181,7 @@ end defmodule Architex.Event.Kick do alias Architex.{Event, Account, Room} - @spec new(Room.t(), Account.t(), String.t(), String.t() | nil) :: Event.t() + @spec new(Room.t(), Account.t(), String.t(), String.t() | nil) :: %Event{} def new(room, sender, user_id, reason \\ nil) do content = %{"membership" => "leave"} content = if reason, do: Map.put(content, "reason", reason), else: content @@ -198,7 +198,7 @@ end defmodule Architex.Event.Ban do alias Architex.{Event, Account, Room} - @spec new(Room.t(), Account.t(), String.t(), String.t() | nil) :: Event.t() + @spec new(Room.t(), Account.t(), String.t(), String.t() | nil) :: %Event{} def new(room, sender, user_id, reason \\ nil) do content = %{"membership" => "ban"} content = if reason, do: Map.put(content, "reason", reason), else: content @@ -214,7 +214,7 @@ end defmodule Architex.Event.Unban do alias Architex.{Event, Account, Room} - @spec new(Room.t(), Account.t(), String.t()) :: Event.t() + @spec new(Room.t(), Account.t(), String.t()) :: %Event{} def new(room, sender, user_id) do %Event{ Event.new(room, sender) diff --git a/lib/architex/schema/room.ex b/lib/architex/schema/room.ex index 24fc07c..74a31eb 100644 --- a/lib/architex/schema/room.ex +++ b/lib/architex/schema/room.ex @@ -4,7 +4,7 @@ defmodule Architex.Room do import Ecto.Changeset import Ecto.Query - alias Architex.{Repo, Room, Event, Alias, RoomServer} + alias Architex.{Repo, Room, Event, Alias, RoomServer, Account} alias ArchitexWeb.Client.Request.{CreateRoom, Messages} @type t :: %__MODULE__{ @@ -22,7 +22,7 @@ defmodule Architex.Room do has_many :aliases, Alias, foreign_key: :room_id end - @spec changeset(Room.t(), map()) :: Ecto.Changeset.t() + @spec changeset(%Room{}, map()) :: Ecto.Changeset.t() def changeset(room, params \\ %{}) do cast(room, params, [:visibility]) end diff --git a/lib/architex_web/client/controllers/room_controller.ex b/lib/architex_web/client/controllers/room_controller.ex index c330bdd..68ec9b2 100644 --- a/lib/architex_web/client/controllers/room_controller.ex +++ b/lib/architex_web/client/controllers/room_controller.ex @@ -229,11 +229,7 @@ defmodule ArchitexWeb.Client.RoomController do end end - # GET /_matrix/client/r0/rooms/!atYDsyowueiToUvuqY:localhost:4000/messages - # Parameters: %{"dir" => "b", "from" => "", "limit" => "727", "path" => ["_matrix", "client", "r0", "rooms", "!atYDsyowueiToUvuqY:localhost:4000", "messages"]} def messages(%Conn{assigns: %{account: account}} = conn, %{"room_id" => room_id} = params) do - # IO.inspect(Messages.changeset(%Messages{}, params)) - with {:ok, request} <- Messages.parse(params) do room_query = account diff --git a/lib/architex_web/client/request/create_room.ex b/lib/architex_web/client/request/create_room.ex index 33d507e..3d7d78f 100644 --- a/lib/architex_web/client/request/create_room.ex +++ b/lib/architex_web/client/request/create_room.ex @@ -6,13 +6,13 @@ defmodule ArchitexWeb.Client.Request.CreateRoom do alias Ecto.Changeset @type t :: %__MODULE__{ - visibility: String.t(), - room_alias_name: String.t(), - name: String.t(), - topic: String.t(), - invite: list(String.t()), - room_version: String.t(), - preset: String.t() + visibility: String.t() | nil, + room_alias_name: String.t() | nil, + name: String.t() | nil, + topic: String.t() | nil, + invite: list(String.t()) | nil, + room_version: String.t() | nil, + preset: String.t() | nil } @primary_key false diff --git a/lib/architex_web/federation/controllers/event_controller.ex b/lib/architex_web/federation/controllers/event_controller.ex index 6c048ed..151e932 100644 --- a/lib/architex_web/federation/controllers/event_controller.ex +++ b/lib/architex_web/federation/controllers/event_controller.ex @@ -90,16 +90,23 @@ defmodule ArchitexWeb.Federation.EventController do case RoomServer.get_room_server(room) do {:ok, pid} -> if RoomServer.server_in_room?(pid, origin) do - {state_events, auth_chain} = + # {state_events, auth_chain} = + data = case state_or_state_ids do - :state -> RoomServer.get_state_at_event(pid, event) - :state_ids -> RoomServer.get_state_ids_at_event(pid, event) - end + :state -> + {state_events, auth_chain} = RoomServer.get_state_at_event(pid, event) - data = %{ - auth_chain: auth_chain, - pdus: state_events - } + %{ + auth_chain: Enum.map(auth_chain, &Architex.Event.Formatters.as_pdu/1), + pdus: Enum.map(state_events, &Architex.Event.Formatters.as_pdu/1) + } + + :state_ids -> + {state_event_ids, auth_chain_ids} = + RoomServer.get_state_ids_at_event(pid, event) + + %{auth_chain: auth_chain_ids, pdus: state_event_ids} + end conn |> put_status(200) diff --git a/lib/architex_web/federation/transaction.ex b/lib/architex_web/federation/transaction.ex index 1826479..0366ebd 100644 --- a/lib/architex_web/federation/transaction.ex +++ b/lib/architex_web/federation/transaction.ex @@ -8,7 +8,7 @@ defmodule ArchitexWeb.Federation.Transaction do @type t :: %__MODULE__{ origin: String.t(), origin_server_ts: integer(), - pdus: [Event.t()], + pdus: [map()], edus: [edu()] | nil } @@ -29,7 +29,7 @@ defmodule ArchitexWeb.Federation.Transaction do %Transaction{ origin: Architex.server_name(), origin_server_ts: System.os_time(:millisecond), - pdus: Enum.map(pdu_events, &Architex.to_serializable_map/1), + pdus: Enum.map(pdu_events, &Architex.Event.Formatters.as_pdu/1), edus: edus } end diff --git a/priv/repo/migrations/20210830160818_create_initial_tables.exs b/priv/repo/migrations/20210830160818_create_initial_tables.exs index 5f28c00..f9848dd 100644 --- a/priv/repo/migrations/20210830160818_create_initial_tables.exs +++ b/priv/repo/migrations/20210830160818_create_initial_tables.exs @@ -27,19 +27,23 @@ defmodule Architex.Repo.Migrations.CreateInitialTables do create table(:events, primary_key: false) do add :nid, :serial, primary_key: true - - add :origin_server_ts, :bigint, null: false - add :unsigned, :map, default: %{}, null: true - add :hashes, :map, null: false - add :signatures, :map, null: false add :id, :string, null: false - add :content, :map - add :type, :string, null: false - add :state_key, :string - add :sender, :string, null: false - add :prev_events, {:array, :string}, null: false + + # PDU Fields add :auth_events, {:array, :string}, null: false + add :content, :map, null: false + add :depth, :integer, null: false + add :hashes, :map, null: false + add :origin, :string, null: false + add :origin_server_ts, :bigint, null: false + add :prev_events, {:array, :string}, null: false + add :redacts, :string, null: true add :room_id, references(:rooms, type: :string), null: false + add :sender, :string, null: false + add :signatures, :map, null: false + add :state_key, :string, null: true + add :type, :string, null: false + add :unsigned, :map, default: %{}, null: true end create index(:events, [:id], unique: true) diff --git a/test/controllers/login_controller_test.exs b/test/controllers/login_controller_test.exs index 4d2b1a7..59a8b30 100644 --- a/test/controllers/login_controller_test.exs +++ b/test/controllers/login_controller_test.exs @@ -40,7 +40,7 @@ defmodule ArchitexWeb.LoginControllerTest do test "handles unknown matrix user id", %{conn: conn} do conn = post_json(conn, Routes.login_path(Endpoint, :login), @basic_params) - assert %{"errcode" => "M_FORBIDDEN"} = json_response(conn, 400) + assert %{"errcode" => "M_FORBIDDEN"} = json_response(conn, 403) end test "handles wrong password", %{conn: conn} do @@ -48,7 +48,7 @@ defmodule ArchitexWeb.LoginControllerTest do conn = post_json(conn, Routes.login_path(Endpoint, :login), @basic_params) - assert %{"errcode" => "M_FORBIDDEN"} = json_response(conn, 400) + assert %{"errcode" => "M_FORBIDDEN"} = json_response(conn, 403) end # TODO: Test display name