Implement client leave endpoint
Add documentation and type specs
This commit is contained in:
parent
a1475c1a46
commit
ae860a768c
9 changed files with 140 additions and 28 deletions
|
@ -114,6 +114,14 @@ defmodule MatrixServer.RoomServer do
|
||||||
GenServer.call(pid, {:join, account})
|
GenServer.call(pid, {:join, account})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Leave a room.
|
||||||
|
"""
|
||||||
|
@spec leave(pid(), Account.t()) :: :ok | {:error, atom()}
|
||||||
|
def leave(pid, account) do
|
||||||
|
GenServer.call(pid, {:leave, account})
|
||||||
|
end
|
||||||
|
|
||||||
### Implementation
|
### Implementation
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
|
@ -218,6 +226,29 @@ defmodule MatrixServer.RoomServer do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def handle_call({:leave, account}, _from, %{room: room, state_set: state_set} = state) do
|
||||||
|
case Repo.transaction(leave_insert_event(room, state_set, account)) do
|
||||||
|
{:ok, state_set} -> {:reply, :ok, %{state | state_set: state_set}}
|
||||||
|
{:error, reason} -> {:reply, {:error, reason}, state}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec leave_insert_event(Room.t(), t(), Account.t()) :: (() -> {:ok, t()} | {:error, atom()})
|
||||||
|
defp leave_insert_event(room, state_set, account) do
|
||||||
|
leave_event = Event.leave(room, account)
|
||||||
|
|
||||||
|
fn ->
|
||||||
|
case finalize_and_insert_event(leave_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
|
||||||
|
|
||||||
# Get a function that inserts a join event into the room for the given account.
|
# Get a function that inserts a join event into the room for the given account.
|
||||||
@spec join_insert_event(Room.t(), t(), Account.t()) :: (() -> {:ok, t()} | {:error, atom()})
|
@spec join_insert_event(Room.t(), t(), Account.t()) :: (() -> {:ok, t()} | {:error, atom()})
|
||||||
defp join_insert_event(room, state_set, account) do
|
defp join_insert_event(room, state_set, account) do
|
||||||
|
|
|
@ -5,7 +5,7 @@ defmodule MatrixServer.Account do
|
||||||
|
|
||||||
alias MatrixServer.{Repo, Account, Device, Room, JoinedRoom}
|
alias MatrixServer.{Repo, Account, Device, Room, JoinedRoom}
|
||||||
alias MatrixServerWeb.Client.Request.{Register, Login}
|
alias MatrixServerWeb.Client.Request.{Register, Login}
|
||||||
alias Ecto.Multi
|
alias Ecto.{Multi, Changeset}
|
||||||
|
|
||||||
@type t :: %__MODULE__{
|
@type t :: %__MODULE__{
|
||||||
password_hash: String.t()
|
password_hash: String.t()
|
||||||
|
@ -25,6 +25,10 @@ defmodule MatrixServer.Account do
|
||||||
timestamps(updated_at: false)
|
timestamps(updated_at: false)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Reports whether the given user localpart is available on this server.
|
||||||
|
"""
|
||||||
|
@spec available?(String.t()) :: :ok | {:error, :user_in_use | :invalid_username}
|
||||||
def available?(localpart) when is_binary(localpart) do
|
def available?(localpart) when is_binary(localpart) do
|
||||||
if Regex.match?(MatrixServer.localpart_regex(), localpart) and
|
if Regex.match?(MatrixServer.localpart_regex(), localpart) and
|
||||||
String.length(localpart) <= localpart_length() do
|
String.length(localpart) <= localpart_length() do
|
||||||
|
@ -42,6 +46,10 @@ defmodule MatrixServer.Account do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Return an multi to register a new user.
|
||||||
|
"""
|
||||||
|
@spec register(Register.t()) :: Multi.t()
|
||||||
def register(%Register{} = input) do
|
def register(%Register{} = input) do
|
||||||
account_params = %{
|
account_params = %{
|
||||||
localpart: input.username || MatrixServer.random_string(10, ?a..?z),
|
localpart: input.username || MatrixServer.random_string(10, ?a..?z),
|
||||||
|
@ -62,6 +70,10 @@ defmodule MatrixServer.Account do
|
||||||
|> Multi.run(:device_with_access_token, &Device.insert_new_access_token/2)
|
|> Multi.run(:device_with_access_token, &Device.insert_new_access_token/2)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Return a function to log a user in.
|
||||||
|
"""
|
||||||
|
@spec login(Login.t()) :: (Ecto.Repo.t() -> {:error, any()} | {:ok, Device.t()})
|
||||||
def login(%Login{} = input) do
|
def login(%Login{} = input) do
|
||||||
localpart = try_get_localpart(input.identifier.user)
|
localpart = try_get_localpart(input.identifier.user)
|
||||||
|
|
||||||
|
@ -86,6 +98,10 @@ defmodule MatrixServer.Account do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Get a device and its associated account using the device's access token.
|
||||||
|
"""
|
||||||
|
@spec by_access_token(String.t()) :: {Account.t(), Device.t()} | nil
|
||||||
def by_access_token(access_token) do
|
def by_access_token(access_token) do
|
||||||
Device
|
Device
|
||||||
|> where([d], d.access_token == ^access_token)
|
|> where([d], d.access_token == ^access_token)
|
||||||
|
@ -94,6 +110,7 @@ defmodule MatrixServer.Account do
|
||||||
|> Repo.one()
|
|> Repo.one()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec changeset(map(), map()) :: Changeset.t()
|
||||||
def changeset(account, params \\ %{}) do
|
def changeset(account, params \\ %{}) do
|
||||||
# TODO: fix password_hash in params
|
# TODO: fix password_hash in params
|
||||||
account
|
account
|
||||||
|
@ -105,11 +122,13 @@ defmodule MatrixServer.Account do
|
||||||
|> unique_constraint(:localpart, name: :accounts_pkey)
|
|> unique_constraint(:localpart, name: :accounts_pkey)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec localpart_length :: integer()
|
||||||
defp localpart_length do
|
defp localpart_length do
|
||||||
# Subtract the "@" and ":" in the MXID.
|
# Subtract the "@" and ":" in the MXID.
|
||||||
@max_mxid_length - 2 - String.length(MatrixServer.server_name())
|
@max_mxid_length - 2 - String.length(MatrixServer.server_name())
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec try_get_localpart(String.t()) :: String.t()
|
||||||
defp try_get_localpart("@" <> rest = user_id) do
|
defp try_get_localpart("@" <> rest = user_id) do
|
||||||
case String.split(rest, ":", parts: 2) do
|
case String.split(rest, ":", parts: 2) do
|
||||||
[localpart, _] -> localpart
|
[localpart, _] -> localpart
|
||||||
|
@ -118,4 +137,12 @@ defmodule MatrixServer.Account do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp try_get_localpart(localpart), do: localpart
|
defp try_get_localpart(localpart), do: localpart
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Get the matrix user ID of an account.
|
||||||
|
"""
|
||||||
|
@spec get_mxid(Account.t()) :: String.t()
|
||||||
|
def get_mxid(%Account{localpart: localpart}) do
|
||||||
|
"@" <> localpart <> ":" <> MatrixServer.server_name()
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,6 +6,13 @@ defmodule MatrixServer.Device do
|
||||||
alias MatrixServer.{Account, Device, Repo}
|
alias MatrixServer.{Account, Device, Repo}
|
||||||
alias MatrixServerWeb.Client.Request.Login
|
alias MatrixServerWeb.Client.Request.Login
|
||||||
|
|
||||||
|
@type t :: %__MODULE__{
|
||||||
|
device_id: String.t(),
|
||||||
|
access_token: String.t(),
|
||||||
|
display_name: String.t(),
|
||||||
|
localpart: String.t()
|
||||||
|
}
|
||||||
|
|
||||||
@primary_key false
|
@primary_key false
|
||||||
schema "devices" do
|
schema "devices" do
|
||||||
field :device_id, :string, primary_key: true
|
field :device_id, :string, primary_key: true
|
||||||
|
|
|
@ -201,6 +201,18 @@ defmodule MatrixServer.Event do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec leave(Room.t(), Account.t()) :: t()
|
||||||
|
def leave(room, sender) do
|
||||||
|
%Event{
|
||||||
|
new(room, sender)
|
||||||
|
| type: "m.room.member",
|
||||||
|
state_key: Account.get_mxid(sender),
|
||||||
|
content: %{
|
||||||
|
"membership" => "leave"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
@spec is_control_event(t()) :: boolean()
|
@spec is_control_event(t()) :: boolean()
|
||||||
def is_control_event(%Event{type: "m.room.power_levels", state_key: ""}), do: true
|
def is_control_event(%Event{type: "m.room.power_levels", state_key: ""}), do: true
|
||||||
def is_control_event(%Event{type: "m.room.join_rules", state_key: ""}), do: true
|
def is_control_event(%Event{type: "m.room.join_rules", state_key: ""}), do: true
|
||||||
|
|
|
@ -76,19 +76,6 @@ defmodule MatrixServer.StateResolution.Authorization do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def authorized?(
|
|
||||||
%Event{
|
|
||||||
type: "m.room.member",
|
|
||||||
sender: sender,
|
|
||||||
content: %{"membership" => "leave"},
|
|
||||||
state_key: sender
|
|
||||||
},
|
|
||||||
state_set
|
|
||||||
) do
|
|
||||||
# Check rule: 5.4.1
|
|
||||||
get_membership(to_string(sender), state_set) in ["invite", "join"]
|
|
||||||
end
|
|
||||||
|
|
||||||
def authorized?(
|
def authorized?(
|
||||||
%Event{
|
%Event{
|
||||||
type: "m.room.member",
|
type: "m.room.member",
|
||||||
|
@ -99,24 +86,30 @@ defmodule MatrixServer.StateResolution.Authorization do
|
||||||
state_set
|
state_set
|
||||||
) do
|
) do
|
||||||
sender_membership = get_membership(to_string(sender), state_set)
|
sender_membership = get_membership(to_string(sender), state_set)
|
||||||
target_membership = get_membership(state_key, state_set)
|
|
||||||
power_levels = get_power_levels(state_set)
|
|
||||||
sender_pl = get_user_power_level(to_string(sender), power_levels)
|
|
||||||
target_pl = get_user_power_level(state_key, power_levels)
|
|
||||||
|
|
||||||
# Check rules: 5.4.2, 5.4.3, 5.4.4
|
if to_string(sender) == state_key do
|
||||||
cond do
|
# Check rule: 5.4.1
|
||||||
sender_membership != "join" ->
|
sender_membership in ["invite", "join"]
|
||||||
false
|
else
|
||||||
|
target_membership = get_membership(state_key, state_set)
|
||||||
|
power_levels = get_power_levels(state_set)
|
||||||
|
sender_pl = get_user_power_level(to_string(sender), power_levels)
|
||||||
|
target_pl = get_user_power_level(state_key, power_levels)
|
||||||
|
|
||||||
target_membership == "ban" and not has_power_level?(to_string(sender), power_levels, :ban) ->
|
# Check rules: 5.4.2, 5.4.3, 5.4.4
|
||||||
false
|
cond do
|
||||||
|
sender_membership != "join" ->
|
||||||
|
false
|
||||||
|
|
||||||
has_power_level?(to_string(sender), power_levels, :kick) and target_pl < sender_pl ->
|
target_membership == "ban" and not has_power_level?(to_string(sender), power_levels, :ban) ->
|
||||||
true
|
false
|
||||||
|
|
||||||
true ->
|
has_power_level?(to_string(sender), power_levels, :kick) and target_pl < sender_pl ->
|
||||||
false
|
true
|
||||||
|
|
||||||
|
true ->
|
||||||
|
false
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -312,6 +305,7 @@ defmodule MatrixServer.StateResolution.Authorization do
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
|> Enum.reduce(%{}, &update_state_set/2)
|
|> Enum.reduce(%{}, &update_state_set/2)
|
||||||
|
|
||||||
|
IO.inspect(event)
|
||||||
authorized?(event, state_set)
|
authorized?(event, state_set)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -112,4 +112,29 @@ defmodule MatrixServerWeb.Client.RoomController do
|
||||||
end
|
end
|
||||||
|
|
||||||
def join(conn, _), do: put_error(conn, :missing_param)
|
def join(conn, _), do: put_error(conn, :missing_param)
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
This API stops a user participating in a particular room.
|
||||||
|
|
||||||
|
Action for POST /_matrix/client/r0/rooms/{roomId}/leave.
|
||||||
|
"""
|
||||||
|
def leave(%Conn{assigns: %{account: account}} = conn, %{"room_id" => room_id}) do
|
||||||
|
case RoomServer.get_room_server(room_id) do
|
||||||
|
{:ok, pid} ->
|
||||||
|
case RoomServer.leave(pid, account) do
|
||||||
|
:ok ->
|
||||||
|
conn
|
||||||
|
|> send_resp(200, [])
|
||||||
|
|> halt()
|
||||||
|
|
||||||
|
{:error, _} ->
|
||||||
|
put_error(conn, :unknown)
|
||||||
|
end
|
||||||
|
|
||||||
|
{:error, :not_found} ->
|
||||||
|
put_error(conn, :not_found, "The given room was not found.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def leave(conn, _), do: put_error(conn, :missing_param)
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,6 +3,13 @@ defmodule MatrixServerWeb.Client.Request.Login do
|
||||||
|
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
@type t :: %__MODULE__{
|
||||||
|
type: String.t(),
|
||||||
|
password: String.t(),
|
||||||
|
device_id: String.t(),
|
||||||
|
initial_device_display_name: String.t()
|
||||||
|
}
|
||||||
|
|
||||||
@primary_key false
|
@primary_key false
|
||||||
embedded_schema do
|
embedded_schema do
|
||||||
field :type, :string
|
field :type, :string
|
||||||
|
|
|
@ -5,6 +5,14 @@ defmodule MatrixServerWeb.Client.Request.Register do
|
||||||
|
|
||||||
alias Ecto.Changeset
|
alias Ecto.Changeset
|
||||||
|
|
||||||
|
@type t :: %__MODULE__{
|
||||||
|
device_id: String.t(),
|
||||||
|
initial_device_display_name: String.t(),
|
||||||
|
password: String.t(),
|
||||||
|
username: String.t(),
|
||||||
|
inhibit_login: boolean()
|
||||||
|
}
|
||||||
|
|
||||||
@primary_key false
|
@primary_key false
|
||||||
embedded_schema do
|
embedded_schema do
|
||||||
field :device_id, :string
|
field :device_id, :string
|
||||||
|
|
|
@ -60,6 +60,7 @@ defmodule MatrixServerWeb.Router do
|
||||||
scope "/rooms/:room_id" do
|
scope "/rooms/:room_id" do
|
||||||
post "/invite", RoomController, :invite
|
post "/invite", RoomController, :invite
|
||||||
post "/join", RoomController, :join
|
post "/join", RoomController, :join
|
||||||
|
post "/leave", RoomController, :leave
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue