Update
This commit is contained in:
parent
7a4207c58a
commit
2cfc459cd0
5 changed files with 246 additions and 86 deletions
|
@ -1,5 +1,48 @@
|
||||||
defmodule Nulla.HTTPSignature do
|
defmodule Nulla.HTTPSignature do
|
||||||
import Plug.Conn
|
import Plug.Conn
|
||||||
|
alias Nulla.Models.User
|
||||||
|
|
||||||
|
def make_header(body, inbox_url, actor) do
|
||||||
|
digest = "SHA-256=" <> (:crypto.hash(:sha256, body) |> Base.encode64())
|
||||||
|
date = DateTime.utc_now() |> Calendar.strftime("%a, %d %b %Y %H:%M:%S GMT")
|
||||||
|
uri = URI.parse(inbox_url)
|
||||||
|
|
||||||
|
signature_string = """
|
||||||
|
(request-target): post #{uri.path}
|
||||||
|
host: #{uri.host}
|
||||||
|
date: #{date}
|
||||||
|
digest: #{digest}
|
||||||
|
"""
|
||||||
|
|
||||||
|
user = User.get_user(id: actor.id)
|
||||||
|
|
||||||
|
private_key =
|
||||||
|
case :public_key.pem_decode(user.privateKeyPem) do
|
||||||
|
[entry] -> :public_key.pem_entry_decode(entry)
|
||||||
|
_ -> raise "Invalid PEM format"
|
||||||
|
end
|
||||||
|
|
||||||
|
signature =
|
||||||
|
:public_key.sign(signature_string, :sha256, private_key)
|
||||||
|
|> Base.encode64()
|
||||||
|
|
||||||
|
signature_header =
|
||||||
|
"""
|
||||||
|
keyId="#{actor.publicKey["id"]}",
|
||||||
|
algorithm="rsa-sha256",
|
||||||
|
headers="(request-target) host date digest",
|
||||||
|
signature="#{signature}"
|
||||||
|
"""
|
||||||
|
|> String.replace("\n", "")
|
||||||
|
|> String.trim()
|
||||||
|
|
||||||
|
[
|
||||||
|
{"Content-Type", "application/activity+json"},
|
||||||
|
{"Date", date},
|
||||||
|
{"Digest", digest},
|
||||||
|
{"Signature", signature_header}
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
def verify(conn, public_key_pem) do
|
def verify(conn, public_key_pem) do
|
||||||
with [sig_header] <- get_req_header(conn, "signature"),
|
with [sig_header] <- get_req_header(conn, "signature"),
|
|
@ -134,4 +134,25 @@ defmodule Nulla.Models.Actor do
|
||||||
def get_actor(by) when is_map(by) or is_list(by) do
|
def get_actor(by) when is_map(by) or is_list(by) do
|
||||||
Repo.get_by(__MODULE__, by)
|
Repo.get_by(__MODULE__, by)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_or_create_actor(actor_json) do
|
||||||
|
ap_id = actor_json["id"]
|
||||||
|
|
||||||
|
case __MODULE__.get_actor(ap_id: ap_id) do
|
||||||
|
nil ->
|
||||||
|
params =
|
||||||
|
actor_json
|
||||||
|
|> Map.put("ap_id", ap_id)
|
||||||
|
|> Map.delete("id")
|
||||||
|
|> Map.put("domain", URI.parse(ap_id).host)
|
||||||
|
|
||||||
|
case __MODULE__.create_actor(params) do
|
||||||
|
{:ok, actor} -> {:ok, actor}
|
||||||
|
{:error, changeset} -> {:error, {:actor_creation_failed, changeset}}
|
||||||
|
end
|
||||||
|
|
||||||
|
actor ->
|
||||||
|
{:ok, actor}
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -65,6 +65,23 @@ defmodule Nulla.Models.Relation do
|
||||||
Repo.get_by(__MODULE__, by)
|
Repo.get_by(__MODULE__, by)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get_or_create_relation(local_actor_id, remote_actor_id) do
|
||||||
|
case __MODULE__.get_relation(local_actor_id: local_actor_id, remote_actor_id: remote_actor_id) do
|
||||||
|
nil ->
|
||||||
|
case __MODULE__.create_relation(%{
|
||||||
|
followed_by: true,
|
||||||
|
local_actor_id: local_actor_id,
|
||||||
|
remote_actor_id: remote_actor_id
|
||||||
|
}) do
|
||||||
|
{:ok, relation} -> {:ok, relation}
|
||||||
|
{:error, changeset} -> {:error, {:actor_creation_failed, changeset}}
|
||||||
|
end
|
||||||
|
|
||||||
|
relation ->
|
||||||
|
{:ok, relation}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def count_following(local_actor_id) do
|
def count_following(local_actor_id) do
|
||||||
__MODULE__
|
__MODULE__
|
||||||
|> where([r], r.local_actor_id == ^local_actor_id and r.following == true)
|
|> where([r], r.local_actor_id == ^local_actor_id and r.following == true)
|
||||||
|
|
|
@ -4,11 +4,82 @@ defmodule NullaWeb.InboxController do
|
||||||
alias Nulla.Snowflake
|
alias Nulla.Snowflake
|
||||||
alias Nulla.HTTPSignature
|
alias Nulla.HTTPSignature
|
||||||
alias Nulla.Utils
|
alias Nulla.Utils
|
||||||
alias Nulla.Models.User
|
|
||||||
alias Nulla.Models.Actor
|
alias Nulla.Models.Actor
|
||||||
alias Nulla.Models.Relation
|
alias Nulla.Models.Relation
|
||||||
alias Nulla.Models.Activity
|
alias Nulla.Models.Activity
|
||||||
|
|
||||||
|
def inbox(conn, %{
|
||||||
|
"id" => _create_id,
|
||||||
|
"type" => "Create",
|
||||||
|
"actor" => _actor_uri,
|
||||||
|
"object" => _target_uri
|
||||||
|
}) do
|
||||||
|
send_resp(conn, 200, "")
|
||||||
|
end
|
||||||
|
|
||||||
|
def inbox(conn, %{
|
||||||
|
"id" => _read_id,
|
||||||
|
"type" => "Read",
|
||||||
|
"actor" => _actor_uri,
|
||||||
|
"object" => _target_uri
|
||||||
|
}) do
|
||||||
|
send_resp(conn, 200, "")
|
||||||
|
end
|
||||||
|
|
||||||
|
def inbox(conn, %{
|
||||||
|
"id" => _update_id,
|
||||||
|
"type" => "Update",
|
||||||
|
"actor" => _actor_uri,
|
||||||
|
"object" => _target_uri
|
||||||
|
}) do
|
||||||
|
send_resp(conn, 200, "")
|
||||||
|
end
|
||||||
|
|
||||||
|
def inbox(conn, %{
|
||||||
|
"id" => _delete_id,
|
||||||
|
"type" => "Delete",
|
||||||
|
"actor" => _actor_uri,
|
||||||
|
"object" => _target_uri
|
||||||
|
}) do
|
||||||
|
send_resp(conn, 200, "")
|
||||||
|
end
|
||||||
|
|
||||||
|
def inbox(conn, %{
|
||||||
|
"id" => _add_id,
|
||||||
|
"type" => "Add",
|
||||||
|
"actor" => _actor_uri,
|
||||||
|
"object" => _target_uri
|
||||||
|
}) do
|
||||||
|
send_resp(conn, 200, "")
|
||||||
|
end
|
||||||
|
|
||||||
|
def inbox(conn, %{
|
||||||
|
"id" => _view_id,
|
||||||
|
"type" => "View",
|
||||||
|
"actor" => _actor_uri,
|
||||||
|
"object" => _target_uri
|
||||||
|
}) do
|
||||||
|
send_resp(conn, 200, "")
|
||||||
|
end
|
||||||
|
|
||||||
|
def inbox(conn, %{
|
||||||
|
"id" => _move_id,
|
||||||
|
"type" => "Move",
|
||||||
|
"actor" => _actor_uri,
|
||||||
|
"object" => _target_uri
|
||||||
|
}) do
|
||||||
|
send_resp(conn, 200, "")
|
||||||
|
end
|
||||||
|
|
||||||
|
def inbox(conn, %{
|
||||||
|
"id" => _undo_id,
|
||||||
|
"type" => "Undo",
|
||||||
|
"actor" => _actor_uri,
|
||||||
|
"object" => _target_uri
|
||||||
|
}) do
|
||||||
|
send_resp(conn, 200, "")
|
||||||
|
end
|
||||||
|
|
||||||
def inbox(conn, %{
|
def inbox(conn, %{
|
||||||
"id" => follow_id,
|
"id" => follow_id,
|
||||||
"type" => "Follow",
|
"type" => "Follow",
|
||||||
|
@ -20,7 +91,7 @@ defmodule NullaWeb.InboxController do
|
||||||
with local_actor <- Actor.get_actor(ap_id: target_uri),
|
with local_actor <- Actor.get_actor(ap_id: target_uri),
|
||||||
{:ok, remote_actor_json} <- Utils.fetch_remote_actor(actor_uri),
|
{:ok, remote_actor_json} <- Utils.fetch_remote_actor(actor_uri),
|
||||||
:ok <- HTTPSignature.verify(conn, remote_actor_json["publicKey"]["publicKeyPem"]),
|
:ok <- HTTPSignature.verify(conn, remote_actor_json["publicKey"]["publicKeyPem"]),
|
||||||
{:ok, remote_actor} <- get_or_create_actor(remote_actor_json),
|
{:ok, remote_actor} <- Actor.get_or_create_actor(remote_actor_json),
|
||||||
{:ok, follow_activity} <-
|
{:ok, follow_activity} <-
|
||||||
Activity.create_activity(%{
|
Activity.create_activity(%{
|
||||||
ap_id: follow_id,
|
ap_id: follow_id,
|
||||||
|
@ -36,8 +107,26 @@ defmodule NullaWeb.InboxController do
|
||||||
actor: local_actor.ap_id,
|
actor: local_actor.ap_id,
|
||||||
object: Jason.encode!(follow_activity)
|
object: Jason.encode!(follow_activity)
|
||||||
}),
|
}),
|
||||||
{:ok, _relation} <- get_or_create_relation(local_actor.id, remote_actor.id),
|
{:ok, _relation} <- Relation.get_or_create_relation(local_actor.id, remote_actor.id) do
|
||||||
:ok <- deliver_accept(accept_activity, remote_actor_json["inbox"], local_actor) do
|
activity = %Activity{accept_activity | object: Jason.decode!(accept_activity.object)}
|
||||||
|
body = Jason.encode!(ActivityPub.activity(activity))
|
||||||
|
headers = HTTPSignature.make_header(body, remote_actor_json["inbox"], local_actor)
|
||||||
|
request = Finch.build(:post, remote_actor_json["inbox"], headers, body)
|
||||||
|
|
||||||
|
case Finch.request(request, Nulla.Finch) do
|
||||||
|
{:ok, %Finch.Response{status: code}} when code in 200..299 ->
|
||||||
|
IO.puts("Accept delivered successfully")
|
||||||
|
:ok
|
||||||
|
|
||||||
|
{:ok, %Finch.Response{status: code, body: resp}} ->
|
||||||
|
IO.inspect({:error, code, resp}, label: "Failed to deliver Accept")
|
||||||
|
{:error, {:http_error, code}}
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
IO.inspect(reason, label: "Finch delivery failed")
|
||||||
|
{:error, reason}
|
||||||
|
end
|
||||||
|
|
||||||
send_resp(conn, 200, "")
|
send_resp(conn, 200, "")
|
||||||
else
|
else
|
||||||
error ->
|
error ->
|
||||||
|
@ -46,98 +135,88 @@ defmodule NullaWeb.InboxController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp get_or_create_actor(remote_actor_json) do
|
def inbox(conn, %{
|
||||||
ap_id = remote_actor_json["id"]
|
"id" => _accept_id,
|
||||||
|
"type" => "Accept",
|
||||||
case Actor.get_actor(ap_id: ap_id) do
|
"actor" => _actor_uri,
|
||||||
nil ->
|
"object" => _target_uri
|
||||||
params =
|
}) do
|
||||||
remote_actor_json
|
send_resp(conn, 200, "")
|
||||||
|> Map.put("ap_id", ap_id)
|
|
||||||
|> Map.delete("id")
|
|
||||||
|> Map.put("domain", URI.parse(ap_id).host)
|
|
||||||
|
|
||||||
case Actor.create_actor(params) do
|
|
||||||
{:ok, actor} -> {:ok, actor}
|
|
||||||
{:error, changeset} -> {:error, {:actor_creation_failed, changeset}}
|
|
||||||
end
|
|
||||||
|
|
||||||
actor ->
|
|
||||||
{:ok, actor}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp get_or_create_relation(local_actor_id, remote_actor_id) do
|
def inbox(conn, %{
|
||||||
case Relation.get_relation(local_actor_id: local_actor_id, remote_actor_id: remote_actor_id) do
|
"id" => _reject_id,
|
||||||
nil ->
|
"type" => "Reject",
|
||||||
Relation.create_relation(%{
|
"actor" => _actor_uri,
|
||||||
followed_by: true,
|
"object" => _target_uri
|
||||||
local_actor_id: local_actor_id,
|
}) do
|
||||||
remote_actor_id: remote_actor_id
|
send_resp(conn, 200, "")
|
||||||
})
|
|
||||||
|
|
||||||
relation ->
|
|
||||||
{:ok, relation}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp deliver_accept(accept_activity, inbox_url, local_actor) do
|
def inbox(conn, %{
|
||||||
accept_activity = %Activity{accept_activity | object: Jason.decode!(accept_activity.object)}
|
"id" => _block_id,
|
||||||
body = Jason.encode!(ActivityPub.activity(accept_activity))
|
"type" => "Block",
|
||||||
digest = "SHA-256=" <> (:crypto.hash(:sha256, body) |> Base.encode64())
|
"actor" => _actor_uri,
|
||||||
date = DateTime.utc_now() |> Calendar.strftime("%a, %d %b %Y %H:%M:%S GMT")
|
"object" => _target_uri
|
||||||
uri = URI.parse(inbox_url)
|
}) do
|
||||||
|
send_resp(conn, 200, "")
|
||||||
|
end
|
||||||
|
|
||||||
signature_string = """
|
def inbox(conn, %{
|
||||||
(request-target): post #{uri.path}
|
"id" => _join_id,
|
||||||
host: #{uri.host}
|
"type" => "Join",
|
||||||
date: #{date}
|
"actor" => _actor_uri,
|
||||||
digest: #{digest}
|
"object" => _target_uri
|
||||||
"""
|
}) do
|
||||||
|
send_resp(conn, 200, "")
|
||||||
|
end
|
||||||
|
|
||||||
user = User.get_user(id: local_actor.id)
|
def inbox(conn, %{
|
||||||
|
"id" => _leave_id,
|
||||||
|
"type" => "Leave",
|
||||||
|
"actor" => _actor_uri,
|
||||||
|
"object" => _target_uri
|
||||||
|
}) do
|
||||||
|
send_resp(conn, 200, "")
|
||||||
|
end
|
||||||
|
|
||||||
private_key =
|
def inbox(conn, %{
|
||||||
case :public_key.pem_decode(user.privateKeyPem) do
|
"id" => _like_id,
|
||||||
[entry] -> :public_key.pem_entry_decode(entry)
|
"type" => "Like",
|
||||||
_ -> raise "Invalid PEM format"
|
"actor" => _actor_uri,
|
||||||
end
|
"object" => _target_uri
|
||||||
|
}) do
|
||||||
|
send_resp(conn, 200, "")
|
||||||
|
end
|
||||||
|
|
||||||
signature =
|
def inbox(conn, %{
|
||||||
:public_key.sign(signature_string, :sha256, private_key)
|
"id" => _dislike_id,
|
||||||
|> Base.encode64()
|
"type" => "Dislike",
|
||||||
|
"actor" => _actor_uri,
|
||||||
|
"object" => _target_uri
|
||||||
|
}) do
|
||||||
|
send_resp(conn, 200, "")
|
||||||
|
end
|
||||||
|
|
||||||
signature_header =
|
def inbox(conn, %{
|
||||||
"""
|
"id" => _announce_id,
|
||||||
keyId="#{local_actor.publicKey["id"]}",
|
"type" => "Announce",
|
||||||
algorithm="rsa-sha256",
|
"actor" => _actor_uri,
|
||||||
headers="(request-target) host date digest",
|
"object" => _target_uri
|
||||||
signature="#{signature}"
|
}) do
|
||||||
"""
|
send_resp(conn, 200, "")
|
||||||
|> String.replace("\n", "")
|
end
|
||||||
|> String.trim()
|
|
||||||
|
|
||||||
headers = [
|
def inbox(conn, %{
|
||||||
{"Content-Type", "application/activity+json"},
|
"id" => _question_id,
|
||||||
{"Date", date},
|
"type" => "Question",
|
||||||
{"Digest", digest},
|
"actor" => _actor_uri,
|
||||||
{"Signature", signature_header}
|
"object" => _target_uri
|
||||||
]
|
}) do
|
||||||
|
send_resp(conn, 200, "")
|
||||||
|
end
|
||||||
|
|
||||||
request = Finch.build(:post, inbox_url, headers, body)
|
def inbox(conn, _params) do
|
||||||
|
send_resp(conn, 400, "")
|
||||||
case Finch.request(request, Nulla.Finch) do
|
|
||||||
{:ok, %Finch.Response{status: code}} when code in 200..299 ->
|
|
||||||
IO.puts("Accept delivered successfully")
|
|
||||||
:ok
|
|
||||||
|
|
||||||
{:ok, %Finch.Response{status: code, body: resp}} ->
|
|
||||||
IO.inspect({:error, code, resp}, label: "Failed to deliver Accept")
|
|
||||||
{:error, {:http_error, code}}
|
|
||||||
|
|
||||||
{:error, reason} ->
|
|
||||||
IO.inspect(reason, label: "Finch delivery failed")
|
|
||||||
{:error, reason}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue