Implement client invite endpoint
Refactor room server to automatically determine auth events Add documentation in various places
This commit is contained in:
parent
6f8c224d50
commit
bcc6cbb24b
15 changed files with 345 additions and 209 deletions
|
@ -6,12 +6,15 @@ defmodule MatrixServer.RoomServer do
|
|||
The RoomServers are supervised by a DynamicSupervisor RoomServer.Supervisor.
|
||||
"""
|
||||
|
||||
@typep t :: map()
|
||||
|
||||
use GenServer
|
||||
|
||||
import Ecto.Query
|
||||
import Ecto.Changeset
|
||||
|
||||
alias MatrixServer.{Repo, Room, Event, StateResolution, JoinedRoom, Account}
|
||||
alias MatrixServer.{Repo, Room, Event, StateResolution, Account, JoinedRoom}
|
||||
alias MatrixServer.Types.UserId
|
||||
alias MatrixServer.StateResolution.Authorization
|
||||
alias MatrixServerWeb.Client.Request.CreateRoom
|
||||
|
||||
|
@ -35,7 +38,7 @@ defmodule MatrixServer.RoomServer do
|
|||
def get_room_server(room_id) do
|
||||
# TODO: Might be wise to use a transaction here to prevent race conditions.
|
||||
case Repo.one(from r in Room, where: r.id == ^room_id) do
|
||||
%Room{state: serialized_state_set} ->
|
||||
%Room{state: serialized_state_set} = room ->
|
||||
case Registry.lookup(@registry, room_id) do
|
||||
[{pid, _}] ->
|
||||
{:ok, pid}
|
||||
|
@ -43,7 +46,7 @@ defmodule MatrixServer.RoomServer do
|
|||
[] ->
|
||||
opts = [
|
||||
name: {:via, Registry, {@registry, room_id}},
|
||||
room_id: room_id,
|
||||
room: room,
|
||||
serialized_state_set: serialized_state_set
|
||||
]
|
||||
|
||||
|
@ -62,11 +65,7 @@ defmodule MatrixServer.RoomServer do
|
|||
Events are inserted into the new room depending on the input `input` and according
|
||||
to the [Matrix documentation](https://matrix.org/docs/spec/client_server/r0.6.1#post-matrix-client-r0-createroom).
|
||||
"""
|
||||
@spec create_room(
|
||||
pid(),
|
||||
MatrixServer.Account.t(),
|
||||
MatrixServerWeb.Client.Request.CreateRoom.t()
|
||||
) :: {:ok, String.t()} | {:error, atom()}
|
||||
@spec create_room(pid(), Account.t(), CreateRoom.t()) :: {:ok, String.t()} | {:error, atom()}
|
||||
def create_room(pid, account, input) do
|
||||
GenServer.call(pid, {:create_room, account, input})
|
||||
end
|
||||
|
@ -100,11 +99,19 @@ defmodule MatrixServer.RoomServer do
|
|||
GenServer.call(pid, {:get_state_ids_at_event, event})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Invite the a user to this room.
|
||||
"""
|
||||
@spec invite(pid(), Account.t(), String.t()) :: :ok | {:error, atom()}
|
||||
def invite(pid, account, user_id) do
|
||||
GenServer.call(pid, {:invite, account, user_id})
|
||||
end
|
||||
|
||||
### Implementation
|
||||
|
||||
@impl true
|
||||
def init(opts) do
|
||||
room_id = Keyword.fetch!(opts, :room_id)
|
||||
room = Keyword.fetch!(opts, :room)
|
||||
serialized_state_set = Keyword.fetch!(opts, :serialized_state_set)
|
||||
state_event_ids = Enum.map(serialized_state_set, fn [_, _, event_id] -> event_id end)
|
||||
|
||||
|
@ -116,14 +123,16 @@ defmodule MatrixServer.RoomServer do
|
|||
{{type, state_key}, event}
|
||||
end)
|
||||
|
||||
{:ok, %{room_id: room_id, state_set: state_set}}
|
||||
{:ok, %{room: room, state_set: state_set}}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_call({:create_room, account, input}, _from, %{room_id: room_id} = state) do
|
||||
def handle_call(
|
||||
{:create_room, account, input},
|
||||
_from,
|
||||
%{room: %Room{id: room_id} = room} = state
|
||||
) do
|
||||
# TODO: power_level_content_override, initial_state, invite, invite_3pid
|
||||
room = Repo.one!(from r in Room, where: r.id == ^room_id)
|
||||
|
||||
case Repo.transaction(create_room_insert_events(room, account, input)) do
|
||||
{:ok, state_set} -> {:reply, {:ok, room_id}, %{state | state_set: state_set}}
|
||||
{:error, reason} -> {:reply, {:error, reason}, state}
|
||||
|
@ -184,36 +193,56 @@ defmodule MatrixServer.RoomServer do
|
|||
{:reply, {state_events, auth_chain}, state}
|
||||
end
|
||||
|
||||
def handle_call({:invite, account, user_id}, _from, %{room: room, state_set: state_set} = state) do
|
||||
case Repo.transaction(invite_insert_event(room, state_set, account, user_id)) do
|
||||
{:ok, state_set} -> {:reply, :ok, %{state | state_set: state_set}}
|
||||
{:error, reason} -> {:reply, {:error, reason}, state}
|
||||
end
|
||||
end
|
||||
|
||||
@spec invite_insert_event(Room.t(), t(), Account.t(), String.t()) ::
|
||||
(() -> {:ok, t()} | {:error, atom()})
|
||||
defp invite_insert_event(room, state_set, account, user_id) do
|
||||
invite_event = Event.invite(room, account, user_id)
|
||||
|
||||
fn ->
|
||||
case finalize_and_insert_event(invite_event, state_set, room) do
|
||||
{:ok, state_set, room} ->
|
||||
_ = update_room_state_set(room, state_set)
|
||||
state_set
|
||||
|
||||
{:error, reason} ->
|
||||
Repo.rollback(reason)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@spec create_room_insert_events(Room.t(), Account.t(), CreateRoom.t()) ::
|
||||
(() -> {:ok, t()} | {:error, atom()})
|
||||
defp create_room_insert_events(room, account, %CreateRoom{
|
||||
room_version: room_version,
|
||||
preset: preset,
|
||||
name: name,
|
||||
topic: topic
|
||||
}) do
|
||||
events =
|
||||
([
|
||||
Event.create_room(room, account, room_version),
|
||||
Event.join(room, account),
|
||||
Event.power_levels(room, account)
|
||||
] ++
|
||||
room_creation_preset(account, preset, room) ++
|
||||
[
|
||||
if(name, do: Event.name(room, account, name)),
|
||||
if(topic, do: Event.topic(room, account, topic))
|
||||
])
|
||||
|> Enum.reject(&Kernel.is_nil/1)
|
||||
|
||||
fn ->
|
||||
state_set = %{}
|
||||
|
||||
with create_room <- Event.create_room(room, account, room_version),
|
||||
{:ok, state_set, create_room, room} <-
|
||||
verify_and_insert_event(create_room, state_set, room),
|
||||
join_creator <- Event.join(room, account, [create_room]),
|
||||
{:ok, state_set, join_creator, room} <-
|
||||
verify_and_insert_event(join_creator, state_set, room),
|
||||
{:ok, _} <- insert_joined_room_assoc(account, room),
|
||||
pls <- Event.power_levels(room, account, [create_room, join_creator]),
|
||||
{:ok, state_set, pls, room} <- verify_and_insert_event(pls, state_set, room) do
|
||||
auth_events = [create_room, join_creator, pls]
|
||||
preset_events = room_creation_preset(account, preset, room, auth_events)
|
||||
name_event = if name, do: Event.name(room, account, name, auth_events)
|
||||
topic_event = if topic, do: Event.topic(room, account, topic, auth_events)
|
||||
|
||||
remaining_events =
|
||||
Enum.reject(preset_events ++ [name_event, topic_event], &Kernel.is_nil/1)
|
||||
|
||||
result =
|
||||
Enum.reduce_while(remaining_events, {state_set, room}, fn event, {state_set, room} ->
|
||||
case verify_and_insert_event(event, state_set, room) do
|
||||
{:ok, state_set, _event, room} -> {:cont, {state_set, room}}
|
||||
Enum.reduce_while(events, {%{}, room}, fn event, {state_set, room} ->
|
||||
case finalize_and_insert_event(event, state_set, room) do
|
||||
{:ok, state_set, room} -> {:cont, {state_set, room}}
|
||||
{:error, reason} -> {:halt, {:error, reason}}
|
||||
end
|
||||
end)
|
||||
|
@ -223,37 +252,36 @@ defmodule MatrixServer.RoomServer do
|
|||
Repo.rollback(reason)
|
||||
|
||||
{state_set, room} ->
|
||||
_ = update_room_state_set(room, state_set)
|
||||
state_set
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@spec update_room_state_set(Room.t(), t()) :: Room.t()
|
||||
defp update_room_state_set(room, state_set) do
|
||||
serialized_state_set =
|
||||
Enum.map(state_set, fn {{type, state_key}, event} ->
|
||||
[type, state_key, event.event_id]
|
||||
end)
|
||||
|
||||
Repo.update!(change(room, state: serialized_state_set))
|
||||
state_set
|
||||
end
|
||||
else
|
||||
_ -> Repo.rollback(:event_creation)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp insert_joined_room_assoc(%Account{localpart: localpart}, %Room{id: room_id}) do
|
||||
Repo.insert(%JoinedRoom{localpart: localpart, room_id: room_id})
|
||||
end
|
||||
|
||||
# TODO: trusted_private_chat:
|
||||
# All invitees are given the same power level as the room creator.
|
||||
defp room_creation_preset(account, nil, %Room{visibility: visibility} = room, auth_events) do
|
||||
@spec room_creation_preset(Account.t(), String.t() | nil, Room.t()) :: [Event.t()]
|
||||
defp room_creation_preset(account, nil, %Room{visibility: visibility} = room) do
|
||||
preset =
|
||||
case visibility do
|
||||
:public -> "public_chat"
|
||||
:private -> "private_chat"
|
||||
end
|
||||
|
||||
room_creation_preset(account, preset, room, auth_events)
|
||||
room_creation_preset(account, preset, room)
|
||||
end
|
||||
|
||||
defp room_creation_preset(account, preset, room, auth_events) do
|
||||
defp room_creation_preset(account, preset, room) do
|
||||
{join_rule, his_vis, guest_access} =
|
||||
case preset do
|
||||
"private_chat" -> {"invite", "shared", "can_join"}
|
||||
|
@ -262,17 +290,80 @@ defmodule MatrixServer.RoomServer do
|
|||
end
|
||||
|
||||
[
|
||||
Event.join_rules(room, account, join_rule, auth_events),
|
||||
Event.history_visibility(room, account, his_vis, auth_events),
|
||||
Event.guest_access(room, account, guest_access, auth_events)
|
||||
Event.join_rules(room, account, join_rule),
|
||||
Event.history_visibility(room, account, his_vis),
|
||||
Event.guest_access(room, account, guest_access)
|
||||
]
|
||||
end
|
||||
|
||||
defp verify_and_insert_event(
|
||||
@spec finalize_and_insert_event(Event.t(), t(), Room.t()) ::
|
||||
{:ok, t(), Room.t()} | {:error, atom()}
|
||||
defp finalize_and_insert_event(
|
||||
event,
|
||||
current_state_set,
|
||||
state_set,
|
||||
%Room{forward_extremities: forward_extremities} = room
|
||||
) do
|
||||
event =
|
||||
event
|
||||
|> Map.put(:auth_events, auth_events_for_event(event, state_set))
|
||||
|> Map.put(:prev_events, forward_extremities)
|
||||
|
||||
case Event.post_process(event) do
|
||||
{:ok, event} -> verify_and_insert_event(event, state_set, room)
|
||||
_ -> {:error, :event_creation}
|
||||
end
|
||||
end
|
||||
|
||||
@spec auth_events_for_event(Event.t(), t()) :: [{String.t(), String.t()}]
|
||||
defp auth_events_for_event(%Event{type: "m.room.create"}, _), do: []
|
||||
|
||||
defp auth_events_for_event(
|
||||
%Event{sender: sender} = event,
|
||||
state_set
|
||||
) do
|
||||
state_pairs =
|
||||
[{"m.room.create", ""}, {"m.room.power_levels", ""}, {"m.room.member", to_string(sender)}] ++
|
||||
auth_events_for_member_event(event)
|
||||
|
||||
state_set
|
||||
|> Map.take(state_pairs)
|
||||
|> Map.values()
|
||||
|> Enum.map(& &1.event_id)
|
||||
end
|
||||
|
||||
@spec auth_events_for_member_event(Event.t()) :: [{String.t(), String.t()}]
|
||||
defp auth_events_for_member_event(
|
||||
%Event{
|
||||
type: "m.room.member",
|
||||
state_key: state_key,
|
||||
content: %{"membership" => membership}
|
||||
} = event
|
||||
) do
|
||||
[
|
||||
{"m.room.member", state_key},
|
||||
if(membership in ["join", "invite"], do: {"m.room.join_rules", ""}),
|
||||
third_party_invite_state_pair(event)
|
||||
]
|
||||
|> Enum.reject(&Kernel.is_nil/1)
|
||||
end
|
||||
|
||||
defp auth_events_for_member_event(_), do: []
|
||||
|
||||
@spec third_party_invite_state_pair(Event.t()) :: {String.t(), String.t()} | nil
|
||||
defp third_party_invite_state_pair(%Event{
|
||||
content: %{
|
||||
"membership" => "invite",
|
||||
"third_party_invite" => %{"signed" => %{"token" => token}}
|
||||
}
|
||||
}) do
|
||||
{"m.room.third_party_invite", token}
|
||||
end
|
||||
|
||||
defp third_party_invite_state_pair(_), do: nil
|
||||
|
||||
@spec verify_and_insert_event(Event.t(), t(), Room.t()) ::
|
||||
{:ok, t(), Room.t()} | {:error, atom()}
|
||||
defp verify_and_insert_event(event, current_state_set, room) do
|
||||
# TODO: Correct error values.
|
||||
# Check the following things:
|
||||
# 1. TODO: Is a valid event, otherwise it is dropped.
|
||||
|
@ -281,10 +372,7 @@ defmodule MatrixServer.RoomServer do
|
|||
# 4. Passes authorization rules based on the event's auth events, otherwise it is rejected.
|
||||
# 5. Passes authorization rules based on the state at the event, otherwise it is rejected.
|
||||
# 6. Passes authorization rules based on the current state of the room, otherwise it is "soft failed".
|
||||
event = %Event{event | prev_events: forward_extremities}
|
||||
|
||||
with {:ok, event} <- Event.post_process(event),
|
||||
true <- Event.prevalidate(event),
|
||||
with true <- Event.prevalidate(event),
|
||||
true <- Authorization.authorized_by_auth_events?(event),
|
||||
state_set <- StateResolution.resolve(event, false),
|
||||
true <- Authorization.authorized?(event, state_set),
|
||||
|
@ -292,9 +380,28 @@ defmodule MatrixServer.RoomServer do
|
|||
room = Room.update_forward_extremities(event, room)
|
||||
event = Repo.insert!(event)
|
||||
state_set = StateResolution.resolve_forward_extremities(event)
|
||||
{:ok, state_set, event, room}
|
||||
_ = update_joined_rooms(event, room)
|
||||
|
||||
{:ok, state_set, room}
|
||||
else
|
||||
_ -> {:error, :authorization}
|
||||
end
|
||||
end
|
||||
|
||||
@spec update_joined_rooms(Event.t(), Room.t()) :: JoinedRoom.t() | nil
|
||||
defp update_joined_rooms(
|
||||
%Event{
|
||||
type: "m.room.member",
|
||||
sender: %UserId{localpart: localpart, domain: domain},
|
||||
content: %{"membership" => "join"}
|
||||
},
|
||||
%Room{id: room_id}
|
||||
) do
|
||||
# TODO: Also remove joined rooms.
|
||||
if domain == MatrixServer.server_name() do
|
||||
Repo.insert(%JoinedRoom{localpart: localpart, room_id: room_id})
|
||||
end
|
||||
end
|
||||
|
||||
defp update_joined_rooms(_, _), do: nil
|
||||
end
|
||||
|
|
|
@ -74,11 +74,7 @@ defmodule MatrixServer.Event do
|
|||
end
|
||||
|
||||
@spec create_room(Room.t(), Account.t(), String.t()) :: t()
|
||||
def create_room(
|
||||
room,
|
||||
%Account{localpart: localpart} = creator,
|
||||
room_version
|
||||
) do
|
||||
def create_room(room, %Account{localpart: localpart} = creator, room_version) do
|
||||
mxid = MatrixServer.get_mxid(localpart)
|
||||
|
||||
%Event{
|
||||
|
@ -92,8 +88,8 @@ defmodule MatrixServer.Event do
|
|||
}
|
||||
end
|
||||
|
||||
@spec join(Room.t(), Account.t(), [t()]) :: t()
|
||||
def join(room, %Account{localpart: localpart} = sender, auth_events) do
|
||||
@spec join(Room.t(), Account.t()) :: t()
|
||||
def join(room, %Account{localpart: localpart} = sender) do
|
||||
mxid = MatrixServer.get_mxid(localpart)
|
||||
|
||||
%Event{
|
||||
|
@ -102,17 +98,12 @@ defmodule MatrixServer.Event do
|
|||
state_key: mxid,
|
||||
content: %{
|
||||
"membership" => "join"
|
||||
},
|
||||
auth_events: Enum.map(auth_events, & &1.event_id)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
@spec power_levels(Room.t(), Account.t(), [t()]) :: t()
|
||||
def power_levels(
|
||||
room,
|
||||
%Account{localpart: localpart} = sender,
|
||||
auth_events
|
||||
) do
|
||||
@spec power_levels(Room.t(), Account.t()) :: t()
|
||||
def power_levels(room, %Account{localpart: localpart} = sender) do
|
||||
mxid = MatrixServer.get_mxid(localpart)
|
||||
|
||||
%Event{
|
||||
|
@ -134,73 +125,79 @@ defmodule MatrixServer.Event do
|
|||
"notifications" => %{
|
||||
"room" => 50
|
||||
}
|
||||
},
|
||||
auth_events: Enum.map(auth_events, & &1.event_id)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
@spec name(Room.t(), Account.t(), String.t(), [t()]) :: %Event{}
|
||||
def name(room, sender, name, auth_events) do
|
||||
@spec name(Room.t(), Account.t(), String.t()) :: %Event{}
|
||||
def name(room, sender, name) do
|
||||
%Event{
|
||||
new(room, sender)
|
||||
| type: "m.room.name",
|
||||
state_key: "",
|
||||
content: %{
|
||||
"name" => name
|
||||
},
|
||||
auth_events: Enum.map(auth_events, & &1.event_id)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
@spec topic(Room.t(), Account.t(), String.t(), [t()]) :: t()
|
||||
def topic(room, sender, topic, auth_events) do
|
||||
@spec topic(Room.t(), Account.t(), String.t()) :: t()
|
||||
def topic(room, sender, topic) do
|
||||
%Event{
|
||||
new(room, sender)
|
||||
| type: "m.room.topic",
|
||||
state_key: "",
|
||||
content: %{
|
||||
"topic" => topic
|
||||
},
|
||||
auth_events: Enum.map(auth_events, & &1.event_id)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
@spec join_rules(Room.t(), Account.t(), String.t(), [t()]) :: t()
|
||||
def join_rules(room, sender, join_rule, auth_events) do
|
||||
@spec join_rules(Room.t(), Account.t(), String.t()) :: t()
|
||||
def join_rules(room, sender, join_rule) do
|
||||
%Event{
|
||||
new(room, sender)
|
||||
| type: "m.room.join_rules",
|
||||
state_key: "",
|
||||
content: %{
|
||||
"join_rule" => join_rule
|
||||
},
|
||||
auth_events: Enum.map(auth_events, & &1.event_id)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
@spec history_visibility(Room.t(), Account.t(), String.t(), [t()]) :: t()
|
||||
def history_visibility(room, sender, history_visibility, auth_events) do
|
||||
@spec history_visibility(Room.t(), Account.t(), String.t()) :: t()
|
||||
def history_visibility(room, sender, history_visibility) do
|
||||
%Event{
|
||||
new(room, sender)
|
||||
| type: "m.room.history_visibility",
|
||||
state_key: "",
|
||||
content: %{
|
||||
"history_visibility" => history_visibility
|
||||
},
|
||||
auth_events: Enum.map(auth_events, & &1.event_id)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
@spec guest_access(Room.t(), Account.t(), String.t(), [t()]) :: t()
|
||||
def guest_access(room, sender, guest_access, auth_events) do
|
||||
@spec guest_access(Room.t(), Account.t(), String.t()) :: t()
|
||||
def guest_access(room, sender, guest_access) do
|
||||
%Event{
|
||||
new(room, sender)
|
||||
| type: "m.room.guest_access",
|
||||
state_key: "",
|
||||
content: %{
|
||||
"guest_access" => guest_access
|
||||
},
|
||||
auth_events: Enum.map(auth_events, & &1.event_id)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
@spec invite(Room.t(), Account.t(), String.t()) :: t()
|
||||
def invite(room, sender, user_id) do
|
||||
%Event{
|
||||
new(room, sender)
|
||||
| type: "m.room.member",
|
||||
state_key: user_id,
|
||||
content: %{
|
||||
"membership" => "invite"
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -3,6 +3,11 @@ defmodule MatrixServer.JoinedRoom do
|
|||
|
||||
alias MatrixServer.{Account, Room}
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
localpart: String.t(),
|
||||
room_id: String.t()
|
||||
}
|
||||
|
||||
@primary_key false
|
||||
schema "joined_rooms" do
|
||||
belongs_to :account, Account,
|
||||
|
|
|
@ -20,9 +20,12 @@ defmodule MatrixServer.StateResolution.Authorization do
|
|||
do: prev_events == []
|
||||
|
||||
# Check rule: 5.2.1
|
||||
def authorized?(%Event{type: "m.room.member", state_key: state_key}, %{
|
||||
{"m.room.create", ""} => %Event{content: %{"creator" => creator}}
|
||||
}),
|
||||
def authorized?(
|
||||
%Event{type: "m.room.member", state_key: state_key, prev_events: [create_id]},
|
||||
%{
|
||||
{"m.room.create", ""} => %Event{event_id: create_id, content: %{"creator" => creator}}
|
||||
}
|
||||
),
|
||||
do: state_key == creator
|
||||
|
||||
def authorized?(
|
||||
|
@ -309,6 +312,9 @@ defmodule MatrixServer.StateResolution.Authorization do
|
|||
|> Repo.all()
|
||||
|> Enum.reduce(%{}, &update_state_set/2)
|
||||
|
||||
IO.inspect(event)
|
||||
IO.inspect(state_set)
|
||||
|
||||
authorized?(event, state_set)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,6 +7,11 @@ defmodule MatrixServerWeb.Client.AccountController do
|
|||
alias MatrixServer.{Account, Repo}
|
||||
alias Plug.Conn
|
||||
|
||||
@doc """
|
||||
Checks to see if a username is available, and valid, for the server.
|
||||
|
||||
Action for GET /_matrix/client/r0/register/available.
|
||||
"""
|
||||
def available(conn, params) do
|
||||
localpart = Map.get(params, "username", "")
|
||||
|
||||
|
@ -21,6 +26,11 @@ defmodule MatrixServerWeb.Client.AccountController do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Gets information about the owner of a given access token.
|
||||
|
||||
Action for GET /_matrix/client/r0/account/whoami.
|
||||
"""
|
||||
def whoami(%Conn{assigns: %{account: %Account{localpart: localpart}}} = conn, _params) do
|
||||
data = %{user_id: get_mxid(localpart)}
|
||||
|
||||
|
@ -29,6 +39,11 @@ defmodule MatrixServerWeb.Client.AccountController do
|
|||
|> json(data)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Invalidates an existing access token, so that it can no longer be used for authorization.
|
||||
|
||||
Action for POST /_matrix/client/r0/logout.
|
||||
"""
|
||||
def logout(%Conn{assigns: %{device: device}} = conn, _params) do
|
||||
case Repo.delete(device) do
|
||||
{:ok, _} ->
|
||||
|
@ -41,6 +56,12 @@ defmodule MatrixServerWeb.Client.AccountController do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Invalidates all access tokens for a user, so that they can no longer be used
|
||||
for authorization.
|
||||
|
||||
Action for POST /_matrix/client/r0/logout/all.
|
||||
"""
|
||||
def logout_all(%Conn{assigns: %{account: account}} = conn, _params) do
|
||||
Repo.delete_all(Ecto.assoc(account, :devices))
|
||||
|
||||
|
|
|
@ -5,6 +5,11 @@ defmodule MatrixServerWeb.Client.AliasesController do
|
|||
|
||||
alias MatrixServer.Alias
|
||||
|
||||
@doc """
|
||||
Create a new mapping from room alias to room ID.
|
||||
|
||||
Action for PUT /_matrix/client/r0/directory/room/{roomAlias}.
|
||||
"""
|
||||
def create(conn, %{"alias" => alias, "room_id" => room_id}) do
|
||||
case Alias.create(alias, room_id) do
|
||||
{:ok, _} ->
|
||||
|
|
|
@ -5,6 +5,11 @@ defmodule MatrixServerWeb.Client.InfoController do
|
|||
|
||||
@supported_versions ["r0.6.1"]
|
||||
|
||||
@doc """
|
||||
Gets the versions of the specification supported by the server.
|
||||
|
||||
Action for GET /_matrix/client/versions.
|
||||
"""
|
||||
def versions(conn, _params) do
|
||||
data = %{versions: @supported_versions}
|
||||
|
||||
|
|
|
@ -10,6 +10,11 @@ defmodule MatrixServerWeb.Client.LoginController do
|
|||
|
||||
@login_type "m.login.password"
|
||||
|
||||
@doc """
|
||||
Gets the homeserver's supported login types to authenticate users.
|
||||
|
||||
Action for GET /_matrix/client/r0/login.
|
||||
"""
|
||||
def login_types(conn, _params) do
|
||||
data = %{flows: [%{type: @login_type}]}
|
||||
|
||||
|
@ -18,6 +23,12 @@ defmodule MatrixServerWeb.Client.LoginController do
|
|||
|> json(data)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Authenticates the user, and issues an access token they can use to
|
||||
authorize themself in subsequent requests.
|
||||
|
||||
Action for POST /_matrix/client/r0/login.
|
||||
"""
|
||||
def login(
|
||||
conn,
|
||||
%{"type" => @login_type, "identifier" => %{"type" => "m.id.user"}} = params
|
||||
|
|
|
@ -10,6 +10,11 @@ defmodule MatrixServerWeb.Client.RegisterController do
|
|||
|
||||
@register_type "m.login.dummy"
|
||||
|
||||
@doc """
|
||||
Register for an account on this homeserver.
|
||||
|
||||
Action for POST /_matrix/client/r0/register.
|
||||
"""
|
||||
def register(conn, %{"auth" => %{"type" => @register_type}} = params) do
|
||||
case Register.changeset(params) do
|
||||
%Changeset{valid?: true} = cs ->
|
||||
|
|
|
@ -4,11 +4,17 @@ defmodule MatrixServerWeb.Client.RoomController do
|
|||
import MatrixServerWeb.Error
|
||||
import Ecto.{Changeset, Query}
|
||||
|
||||
alias MatrixServer.{Repo, Room}
|
||||
alias MatrixServer.{Repo, Room, RoomServer}
|
||||
alias MatrixServer.Types.UserId
|
||||
alias MatrixServerWeb.Client.Request.CreateRoom
|
||||
alias Ecto.Changeset
|
||||
alias Plug.Conn
|
||||
|
||||
@doc """
|
||||
Create a new room with various configuration options.
|
||||
|
||||
Action for POST /_matrix/client/r0/createRoom.
|
||||
"""
|
||||
def create(%Conn{assigns: %{account: account}} = conn, params) do
|
||||
case CreateRoom.changeset(params) do
|
||||
%Changeset{valid?: true} = cs ->
|
||||
|
@ -32,8 +38,14 @@ defmodule MatrixServerWeb.Client.RoomController do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
This API returns a list of the user's current rooms.
|
||||
|
||||
Action for GET /_matrix/client/r0/joined_rooms.
|
||||
"""
|
||||
def joined_rooms(%Conn{assigns: %{account: account}} = conn, _params) do
|
||||
joined_room_ids = account
|
||||
joined_room_ids =
|
||||
account
|
||||
|> Ecto.assoc(:joined_rooms)
|
||||
|> select([jr], jr.id)
|
||||
|> Repo.all()
|
||||
|
@ -46,4 +58,32 @@ defmodule MatrixServerWeb.Client.RoomController do
|
|||
|> put_status(200)
|
||||
|> json(data)
|
||||
end
|
||||
|
||||
@doc """
|
||||
This API invites a user to participate in a particular room.
|
||||
|
||||
Action for POST /_matrix/client/r0/rooms/{roomId}/invite.
|
||||
"""
|
||||
def invite(%Conn{assigns: %{account: account}} = conn, %{
|
||||
"room_id" => room_id,
|
||||
"user_id" => user_id
|
||||
}) do
|
||||
with {:ok, _} <- UserId.cast(user_id),
|
||||
{:ok, pid} <- RoomServer.get_room_server(room_id) do
|
||||
case RoomServer.invite(pid, account, user_id) do
|
||||
:ok ->
|
||||
conn
|
||||
|> send_resp(200, [])
|
||||
|> halt()
|
||||
|
||||
{:error, _} ->
|
||||
put_error(conn, :unknown)
|
||||
end
|
||||
else
|
||||
:error -> put_error(conn, :invalid_param, "Given user ID is invalid.")
|
||||
{:error, :not_found} -> put_error(conn, :not_found, "The given room was not found.")
|
||||
end
|
||||
end
|
||||
|
||||
def invite(conn, _), do: put_error(conn, :missing_param)
|
||||
end
|
||||
|
|
|
@ -8,6 +8,11 @@ defmodule MatrixServerWeb.Federation.EventController do
|
|||
alias MatrixServer.{Repo, Event, RoomServer}
|
||||
alias MatrixServerWeb.Federation.Transaction
|
||||
|
||||
@doc """
|
||||
Retrieves a single event.
|
||||
|
||||
Action for GET /_matrix/federation/v1/event/{eventId}.
|
||||
"""
|
||||
def event(%Plug.Conn{assigns: %{origin: origin}} = conn, %{"event_id" => event_id}) do
|
||||
query =
|
||||
Event
|
||||
|
@ -39,6 +44,11 @@ defmodule MatrixServerWeb.Federation.EventController do
|
|||
|
||||
def event(conn, _), do: put_error(conn, :missing_param)
|
||||
|
||||
@doc """
|
||||
Retrieves a snapshot of a room's state at a given event.
|
||||
|
||||
Action for GET /_matrix/federation/v1/state/{roomId}.
|
||||
"""
|
||||
def state(%Plug.Conn{assigns: %{origin: origin}} = conn, %{
|
||||
"event_id" => event_id,
|
||||
"room_id" => room_id
|
||||
|
@ -48,6 +58,11 @@ defmodule MatrixServerWeb.Federation.EventController do
|
|||
|
||||
def state(conn, _), do: put_error(conn, :missing_param)
|
||||
|
||||
@doc """
|
||||
Retrieves a snapshot of a room's state at a given event, in the form of event IDs.
|
||||
|
||||
Action for GET /_matrix/federation/v1/state_ids/{roomId}.
|
||||
"""
|
||||
def state_ids(%Plug.Conn{assigns: %{origin: origin}} = conn, %{
|
||||
"event_id" => event_id,
|
||||
"room_id" => room_id
|
||||
|
|
|
@ -5,6 +5,11 @@ defmodule MatrixServerWeb.Federation.KeyController do
|
|||
|
||||
alias MatrixServer.KeyServer
|
||||
|
||||
@doc """
|
||||
Gets the homeserver's published signing keys.
|
||||
|
||||
Action for GET /_matrix/key/v2/server/{keyId}.
|
||||
"""
|
||||
def get_signing_keys(conn, _params) do
|
||||
keys =
|
||||
KeyServer.get_own_signing_keys()
|
||||
|
|
|
@ -31,6 +31,12 @@ defmodule MatrixServerWeb.Federation.QueryController do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Performs a query to get profile information, such as a display name or avatar,
|
||||
for a given user.
|
||||
|
||||
Action for GET /_matrix/federation/v1/query/profile.
|
||||
"""
|
||||
def profile(conn, params) do
|
||||
with {:ok, %ProfileRequest{user_id: %UserId{localpart: localpart, domain: domain}}} <-
|
||||
ProfileRequest.validate(params) do
|
||||
|
|
|
@ -56,6 +56,10 @@ defmodule MatrixServerWeb.Router do
|
|||
scope "/directory/room" do
|
||||
put "/:alias", AliasesController, :create
|
||||
end
|
||||
|
||||
scope "/rooms/:room_id" do
|
||||
post "/invite", RoomController, :invite
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -12,99 +12,3 @@ Repo.insert(%Device{
|
|||
display_name: "My Android",
|
||||
localpart: "chuck"
|
||||
})
|
||||
|
||||
# Auth difference example from here:
|
||||
# https://matrix.org/docs/guides/implementing-stateres#auth-differences
|
||||
|
||||
alice =
|
||||
Repo.insert!(%Account{
|
||||
localpart: "alice",
|
||||
password_hash: Bcrypt.hash_pwd_salt("sneed")
|
||||
})
|
||||
|
||||
bob =
|
||||
Repo.insert!(%Account{
|
||||
localpart: "bob",
|
||||
password_hash: Bcrypt.hash_pwd_salt("sneed")
|
||||
})
|
||||
|
||||
charlie =
|
||||
Repo.insert!(%Account{
|
||||
localpart: "charlie",
|
||||
password_hash: Bcrypt.hash_pwd_salt("sneed")
|
||||
})
|
||||
|
||||
room =
|
||||
Repo.insert!(%Room{
|
||||
id: "room1",
|
||||
visibility: :public
|
||||
})
|
||||
|
||||
create_room =
|
||||
Repo.insert!(
|
||||
Event.create_room(room, alice, "v1", false)
|
||||
|> Map.put(:origin_server_ts, timestamp.(0))
|
||||
|> Event.post_process()
|
||||
|> elem(1)
|
||||
)
|
||||
|
||||
Repo.insert!(
|
||||
Event.join(room, alice, false)
|
||||
|> Map.put(:prev_events, ["create"])
|
||||
|> Map.put(:auth_events, ["create"])
|
||||
|> Map.put(:origin_server_ts, timestamp.(1))
|
||||
|> Map.put(:event_id, "join_alice")
|
||||
)
|
||||
|
||||
Repo.insert!(
|
||||
Event.join(room, bob)
|
||||
|> elem(1)
|
||||
|> Map.put(:prev_events, ["join_alice"])
|
||||
|> Map.put(:auth_events, ["create"])
|
||||
|> Map.put(:origin_server_ts, timestamp.(2))
|
||||
|> Map.put(:event_id, "join_bob")
|
||||
)
|
||||
|
||||
Repo.insert!(
|
||||
Event.join(room, charlie)
|
||||
|> elem(1)
|
||||
|> Map.put(:prev_events, ["join_bob"])
|
||||
|> Map.put(:auth_events, ["create"])
|
||||
|> Map.put(:origin_server_ts, timestamp.(3))
|
||||
|> Map.put(:event_id, "join_charlie")
|
||||
)
|
||||
|
||||
%Event{content: content} = event = Event.power_levels(room, alice) |> elem(1)
|
||||
event = %Event{event | content: %{content | "users" => %{"alice" => 100, "bob" => 100}}}
|
||||
|
||||
Repo.insert!(
|
||||
event
|
||||
|> Map.put(:prev_events, ["join_alice"])
|
||||
|> Map.put(:auth_events, ["create", "join_alice"])
|
||||
|> Map.put(:origin_server_ts, timestamp.(4))
|
||||
|> Map.put(:event_id, "a")
|
||||
)
|
||||
|
||||
%Event{content: content} = event = Event.power_levels(room, bob) |> elem(1)
|
||||
|
||||
event = %Event{
|
||||
event
|
||||
| content: %{content | "users" => %{"alice" => 100, "bob" => 100, "charlie" => 100}}
|
||||
}
|
||||
|
||||
Repo.insert!(
|
||||
event
|
||||
|> Map.put(:prev_events, ["a"])
|
||||
|> Map.put(:auth_events, ["create", "join_bob", "a"])
|
||||
|> Map.put(:origin_server_ts, timestamp.(5))
|
||||
|> Map.put(:event_id, "b")
|
||||
)
|
||||
|
||||
Repo.insert!(
|
||||
Event.topic(room, alice, "sneed")
|
||||
|> elem(1)
|
||||
|> Map.put(:prev_events, ["a"])
|
||||
|> Map.put(:auth_events, ["create", "join_alice", "a"])
|
||||
|> Map.put(:origin_server_ts, timestamp.(5))
|
||||
|> Map.put(:event_id, "fork")
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue