architex/lib/architex_web/federation/http_client.ex

93 lines
3 KiB
Elixir
Raw Normal View History

2021-09-01 12:43:55 +00:00
defmodule ArchitexWeb.Federation.HTTPClient do
use Tesla
2021-09-01 12:43:55 +00:00
alias ArchitexWeb.Endpoint
alias ArchitexWeb.Federation.Request.GetSigningKeys
alias ArchitexWeb.Federation.Middleware.SignRequest
alias ArchitexWeb.Router.Helpers, as: RouteHelpers
# TODO: Maybe create database-backed homeserver struct to pass to client function.
# TODO: Fix error propagation.
2021-09-01 12:43:55 +00:00
@adapter {Tesla.Adapter.Finch, name: ArchitexWeb.HTTPClient}
def client(server_name) do
Tesla.client(
[
{Tesla.Middleware.Opts, [server_name: server_name]},
SignRequest,
{Tesla.Middleware.BaseUrl, "http://" <> server_name},
Tesla.Middleware.JSON
],
@adapter
)
end
def get_signing_keys(client) do
path = RouteHelpers.key_path(Endpoint, :get_signing_keys)
2021-08-13 11:45:10 +00:00
with {:ok,
%GetSigningKeys{server_name: server_name, verify_keys: verify_keys, signatures: sigs} =
response} <- tesla_request(:get, client, path, GetSigningKeys),
2021-09-01 12:43:55 +00:00
serializable_response <- Architex.to_serializable_map(response),
serializable_response <- Map.drop(serializable_response, [:signatures]),
2021-09-01 12:43:55 +00:00
{:ok, encoded_body} <- Architex.encode_canonical_json(serializable_response),
server_sigs when not is_nil(server_sigs) <- sigs[server_name] do
2021-08-13 11:45:10 +00:00
# For each verify key, check if there is a matching signature.
# If not, invalidate the whole response.
Enum.all?(verify_keys, fn {key_id, %{"key" => key}} ->
with true <- Map.has_key?(server_sigs, key_id),
2021-09-01 12:43:55 +00:00
{:ok, decoded_key} <- Architex.decode_base64(key),
{:ok, decoded_sig} <- Architex.decode_base64(server_sigs[key_id]) do
Architex.sign_verify(decoded_sig, encoded_body, decoded_key)
2021-08-13 11:45:10 +00:00
else
_ -> false
2021-08-13 11:45:10 +00:00
end
end)
|> then(fn
true -> {:ok, response}
false -> :error
end)
else
_ -> :error
end
end
def query_profile(client, user_id, field \\ nil) do
2021-08-10 16:02:53 +00:00
path = RouteHelpers.query_path(Endpoint, :profile) |> Tesla.build_url(user_id: user_id)
path = if field, do: Tesla.build_url(path, field: field), else: path
Tesla.get(client, path)
end
2021-08-13 11:45:10 +00:00
def get_event(client, event_id) do
path = RouteHelpers.event_path(Endpoint, :event, event_id)
Tesla.get(client, path)
end
def get_state(client, room_id, event_id) do
path =
RouteHelpers.event_path(Endpoint, :state, room_id) |> Tesla.build_url(event_id: event_id)
Tesla.get(client, path)
end
def get_state_ids(client, room_id, event_id) do
path =
2021-08-22 13:19:48 +00:00
RouteHelpers.event_path(Endpoint, :state_ids, room_id)
|> Tesla.build_url(event_id: event_id)
Tesla.get(client, path)
end
2021-08-13 11:45:10 +00:00
defp tesla_request(method, client, path, request_schema) do
with {:ok, %Tesla.Env{body: body}} <- Tesla.request(client, url: path, method: method),
%Ecto.Changeset{valid?: true} = cs <- apply(request_schema, :changeset, [body]) do
{:ok, Ecto.Changeset.apply_changes(cs)}
else
_ -> :error
2021-08-13 11:45:10 +00:00
end
end
end