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})
|
||||
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
|
||||
|
||||
@impl true
|
||||
|
@ -218,6 +226,29 @@ defmodule MatrixServer.RoomServer do
|
|||
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.
|
||||
@spec join_insert_event(Room.t(), t(), Account.t()) :: (() -> {:ok, t()} | {:error, atom()})
|
||||
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 MatrixServerWeb.Client.Request.{Register, Login}
|
||||
alias Ecto.Multi
|
||||
alias Ecto.{Multi, Changeset}
|
||||
|
||||
@type t :: %__MODULE__{
|
||||
password_hash: String.t()
|
||||
|
@ -25,6 +25,10 @@ defmodule MatrixServer.Account do
|
|||
timestamps(updated_at: false)
|
||||
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
|
||||
if Regex.match?(MatrixServer.localpart_regex(), localpart) and
|
||||
String.length(localpart) <= localpart_length() do
|
||||
|
@ -42,6 +46,10 @@ defmodule MatrixServer.Account do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Return an multi to register a new user.
|
||||
"""
|
||||
@spec register(Register.t()) :: Multi.t()
|
||||
def register(%Register{} = input) do
|
||||
account_params = %{
|
||||
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)
|
||||
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
|
||||
localpart = try_get_localpart(input.identifier.user)
|
||||
|
||||
|
@ -86,6 +98,10 @@ defmodule MatrixServer.Account do
|
|||
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
|
||||
Device
|
||||
|> where([d], d.access_token == ^access_token)
|
||||
|
@ -94,6 +110,7 @@ defmodule MatrixServer.Account do
|
|||
|> Repo.one()
|
||||
end
|
||||
|
||||
@spec changeset(map(), map()) :: Changeset.t()
|
||||
def changeset(account, params \\ %{}) do
|
||||
# TODO: fix password_hash in params
|
||||
account
|
||||
|
@ -105,11 +122,13 @@ defmodule MatrixServer.Account do
|
|||
|> unique_constraint(:localpart, name: :accounts_pkey)
|
||||
end
|
||||
|
||||
@spec localpart_length :: integer()
|
||||
defp localpart_length do
|
||||
# Subtract the "@" and ":" in the MXID.
|
||||
@max_mxid_length - 2 - String.length(MatrixServer.server_name())
|
||||
end
|
||||
|
||||
@spec try_get_localpart(String.t()) :: String.t()
|
||||
defp try_get_localpart("@" <> rest = user_id) do
|
||||
case String.split(rest, ":", parts: 2) do
|
||||
[localpart, _] -> localpart
|
||||
|
@ -118,4 +137,12 @@ defmodule MatrixServer.Account do
|
|||
end
|
||||
|
||||
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
|
||||
|
|
|
@ -6,6 +6,13 @@ defmodule MatrixServer.Device do
|
|||
alias MatrixServer.{Account, Device, Repo}
|
||||
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
|
||||
schema "devices" do
|
||||
field :device_id, :string, primary_key: true
|
||||
|
|
|
@ -201,6 +201,18 @@ defmodule MatrixServer.Event do
|
|||
}
|
||||
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()
|
||||
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
|
||||
|
|
|
@ -76,19 +76,6 @@ defmodule MatrixServer.StateResolution.Authorization do
|
|||
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?(
|
||||
%Event{
|
||||
type: "m.room.member",
|
||||
|
@ -99,24 +86,30 @@ defmodule MatrixServer.StateResolution.Authorization do
|
|||
state_set
|
||||
) do
|
||||
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
|
||||
cond do
|
||||
sender_membership != "join" ->
|
||||
false
|
||||
if to_string(sender) == state_key do
|
||||
# Check rule: 5.4.1
|
||||
sender_membership in ["invite", "join"]
|
||||
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) ->
|
||||
false
|
||||
# Check rules: 5.4.2, 5.4.3, 5.4.4
|
||||
cond do
|
||||
sender_membership != "join" ->
|
||||
false
|
||||
|
||||
has_power_level?(to_string(sender), power_levels, :kick) and target_pl < sender_pl ->
|
||||
true
|
||||
target_membership == "ban" and not has_power_level?(to_string(sender), power_levels, :ban) ->
|
||||
false
|
||||
|
||||
true ->
|
||||
false
|
||||
has_power_level?(to_string(sender), power_levels, :kick) and target_pl < sender_pl ->
|
||||
true
|
||||
|
||||
true ->
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -312,6 +305,7 @@ defmodule MatrixServer.StateResolution.Authorization do
|
|||
|> Repo.all()
|
||||
|> Enum.reduce(%{}, &update_state_set/2)
|
||||
|
||||
IO.inspect(event)
|
||||
authorized?(event, state_set)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -112,4 +112,29 @@ defmodule MatrixServerWeb.Client.RoomController do
|
|||
end
|
||||
|
||||
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
|
||||
|
|
|
@ -3,6 +3,13 @@ defmodule MatrixServerWeb.Client.Request.Login do
|
|||
|
||||
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
|
||||
embedded_schema do
|
||||
field :type, :string
|
||||
|
|
|
@ -5,6 +5,14 @@ defmodule MatrixServerWeb.Client.Request.Register do
|
|||
|
||||
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
|
||||
embedded_schema do
|
||||
field :device_id, :string
|
||||
|
|
|
@ -60,6 +60,7 @@ defmodule MatrixServerWeb.Router do
|
|||
scope "/rooms/:room_id" do
|
||||
post "/invite", RoomController, :invite
|
||||
post "/join", RoomController, :join
|
||||
post "/leave", RoomController, :leave
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue