Update
This commit is contained in:
parent
124149129e
commit
ee68ec870d
9 changed files with 92 additions and 41 deletions
|
@ -1,5 +1,70 @@
|
|||
defmodule Nulla.HTTPSignature do
|
||||
def verify(_conn, _actor) do
|
||||
import Plug.Conn
|
||||
|
||||
def verify(conn, actor_json) do
|
||||
with [sig_header] <- get_req_header(conn, "signature"),
|
||||
signature_map <- parse_signature_header(sig_header),
|
||||
{:ok, signed_string} <- build_signature_string(signature_map["headers"], conn),
|
||||
{:ok, public_key_pem} <- extract_public_key(actor_json),
|
||||
true <- verify_signature(public_key_pem, signed_string, signature_map["signature"]) do
|
||||
:ok
|
||||
else
|
||||
_ -> {:error, :invalid_signature}
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_signature_header(header) do
|
||||
header
|
||||
|> String.split(",")
|
||||
|> Enum.map(fn pair ->
|
||||
[k, v] = String.split(pair, "=", parts: 2)
|
||||
{String.trim(k), String.trim(v, ~s("))}
|
||||
end)
|
||||
|> Enum.into(%{})
|
||||
end
|
||||
|
||||
defp build_signature_string(nil, _conn), do: {:error, :missing_headers}
|
||||
|
||||
defp build_signature_string(headers_str, conn) do
|
||||
headers = String.split(headers_str, " ")
|
||||
|
||||
result =
|
||||
Enum.map(headers, fn header ->
|
||||
line =
|
||||
case header do
|
||||
"(request-target)" ->
|
||||
method = String.downcase(conn.method)
|
||||
|
||||
path =
|
||||
conn.request_path <>
|
||||
if conn.query_string != "", do: "?" <> conn.query_string, else: ""
|
||||
|
||||
"(request-target): #{method} #{path}"
|
||||
|
||||
_ ->
|
||||
value = get_req_header(conn, header) |> List.first()
|
||||
if value, do: "#{header}: #{value}", else: nil
|
||||
end
|
||||
|
||||
line
|
||||
end)
|
||||
|> Enum.reject(&is_nil/1)
|
||||
|> Enum.join("\n")
|
||||
|
||||
{:ok, result}
|
||||
end
|
||||
|
||||
defp extract_public_key(%{"publicKey" => %{"publicKeyPem" => pem}}), do: {:ok, pem}
|
||||
defp extract_public_key(_), do: {:error, :no_public_key}
|
||||
|
||||
defp verify_signature(public_key_pem, signed_string, signature_base64) do
|
||||
public_key =
|
||||
:public_key.pem_decode(public_key_pem)
|
||||
|> hd()
|
||||
|> :public_key.pem_entry_decode()
|
||||
|
||||
signature = Base.decode64!(signature_base64)
|
||||
|
||||
:public_key.verify(signed_string, :sha256, signature, public_key)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -138,7 +138,7 @@ defmodule Nulla.Models.Actor do
|
|||
|> Repo.insert()
|
||||
end
|
||||
|
||||
def get_actor(username, domain) do
|
||||
Repo.get_by(__MODULE__, preferredUsername: username, domain: domain)
|
||||
def get_actor(by) when is_map(by) or is_list(by) do
|
||||
Repo.get_by(__MODULE__, by)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,7 +3,6 @@ defmodule Nulla.Models.User do
|
|||
import Ecto.Changeset
|
||||
import Ecto.Query
|
||||
alias Nulla.Repo
|
||||
alias Nulla.Models.User
|
||||
alias Nulla.Models.Session
|
||||
|
||||
@primary_key {:id, :integer, autogenerate: false}
|
||||
|
@ -44,17 +43,17 @@ defmodule Nulla.Models.User do
|
|||
end
|
||||
|
||||
def get_user(by) when is_map(by) or is_list(by) do
|
||||
Repo.get_by(User, by)
|
||||
Repo.get_by(__MODULE__, by)
|
||||
end
|
||||
|
||||
def get_total_users_count() do
|
||||
Repo.aggregate(from(u in User), :count, :id)
|
||||
Repo.aggregate(from(u in __MODULE__), :count, :id)
|
||||
end
|
||||
|
||||
def get_active_users_count(days) do
|
||||
cutoff = DateTime.add(DateTime.utc_now(), -days * 86400, :second)
|
||||
|
||||
from(u in User, where: u.last_active_at > ^cutoff)
|
||||
from(u in __MODULE__, where: u.last_active_at > ^cutoff)
|
||||
|> Repo.aggregate(:count, :id)
|
||||
end
|
||||
|
||||
|
|
|
@ -1,28 +1,12 @@
|
|||
defmodule Nulla.Utils do
|
||||
alias Nulla.Models.Actor
|
||||
alias Nulla.Models.InstanceSettings
|
||||
|
||||
def resolve_local_actor("https://" <> _ = uri) do
|
||||
case URI.parse(uri).path do
|
||||
"/@" <> username ->
|
||||
instance_settings = InstanceSettings.get_instance_settings!()
|
||||
domain = instance_settings.domain
|
||||
|
||||
case Actor.get_actor(username, domain) do
|
||||
nil -> {:error, :not_found}
|
||||
user -> user
|
||||
end
|
||||
|
||||
_ ->
|
||||
{:error, :invalid_actor}
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_remote_actor(uri) do
|
||||
request =
|
||||
Finch.build(:get, uri, [
|
||||
{"Accept", "application/activity+json"}
|
||||
])
|
||||
headers = [
|
||||
{"Accept", "application/activity+json"},
|
||||
{"User-Agent", "Nulla/1.0"},
|
||||
{"Host", URI.parse(uri).host}
|
||||
]
|
||||
|
||||
request = Finch.build(:get, uri, headers)
|
||||
|
||||
case Finch.request(request, Finch) do
|
||||
{:ok, %Finch.Response{status: 200, body: body}} ->
|
||||
|
@ -31,6 +15,9 @@ defmodule Nulla.Utils do
|
|||
_ -> {:error, :invalid_json}
|
||||
end
|
||||
|
||||
{:ok, %Finch.Response{status: code}} when code in 300..399 ->
|
||||
{:error, :redirect_not_followed}
|
||||
|
||||
_ ->
|
||||
{:error, :actor_fetch_failed}
|
||||
end
|
||||
|
|
|
@ -10,7 +10,7 @@ defmodule NullaWeb.ActorController do
|
|||
instance_settings = InstanceSettings.get_instance_settings!()
|
||||
domain = instance_settings.domain
|
||||
|
||||
case Actor.get_actor(username, domain) do
|
||||
case Actor.get_actor(preferredUsername: username, domain: domain) do
|
||||
nil ->
|
||||
conn
|
||||
|> put_status(:not_found)
|
||||
|
|
|
@ -9,7 +9,7 @@ defmodule NullaWeb.FollowController do
|
|||
instance_settings = InstanceSettings.get_instance_settings!()
|
||||
domain = instance_settings.domain
|
||||
limit = instance_settings.api_limit
|
||||
actor = Actor.get_actor(username, domain)
|
||||
actor = Actor.get_actor(preferredUsername: username, domain: domain)
|
||||
total = Relation.count_following(actor.id)
|
||||
|
||||
page =
|
||||
|
@ -28,7 +28,7 @@ defmodule NullaWeb.FollowController do
|
|||
def following(conn, %{"username" => username}) do
|
||||
instance_settings = InstanceSettings.get_instance_settings!()
|
||||
domain = instance_settings.domain
|
||||
actor = Actor.get_actor(username, domain)
|
||||
actor = Actor.get_actor(preferredUsername: username, domain: domain)
|
||||
total = Relation.count_following(actor.id)
|
||||
|
||||
conn
|
||||
|
@ -40,7 +40,7 @@ defmodule NullaWeb.FollowController do
|
|||
instance_settings = InstanceSettings.get_instance_settings!()
|
||||
domain = instance_settings.domain
|
||||
limit = instance_settings.api_limit
|
||||
actor = Actor.get_actor(username, domain)
|
||||
actor = Actor.get_actor(preferredUsername: username, domain: domain)
|
||||
total = Relation.count_followers(actor.id)
|
||||
|
||||
page =
|
||||
|
@ -59,7 +59,7 @@ defmodule NullaWeb.FollowController do
|
|||
def followers(conn, %{"username" => username}) do
|
||||
instance_settings = InstanceSettings.get_instance_settings!()
|
||||
domain = instance_settings.domain
|
||||
actor = Actor.get_actor(username, domain)
|
||||
actor = Actor.get_actor(preferredUsername: username, domain: domain)
|
||||
total = Relation.count_followers(actor.id)
|
||||
|
||||
conn
|
||||
|
|
|
@ -11,7 +11,7 @@ defmodule NullaWeb.InboxController do
|
|||
conn,
|
||||
%{"id" => follow_id, "type" => "Follow", "actor" => actor_uri, "object" => target_uri}
|
||||
) do
|
||||
with {:ok, local_actor} <- Utils.resolve_local_actor(target_uri),
|
||||
with local_actor <- Actor.get_actor(ap_id: target_uri),
|
||||
{:ok, remote_actor_json} <- Utils.fetch_remote_actor(actor_uri),
|
||||
:ok <- HTTPSignature.verify(conn, remote_actor_json),
|
||||
remote_actor <-
|
||||
|
@ -31,7 +31,7 @@ defmodule NullaWeb.InboxController do
|
|||
accept_activity <-
|
||||
Activity.create_activity(%{
|
||||
type: "Accept",
|
||||
actor: local_actor.id,
|
||||
actor: local_actor.ap_id,
|
||||
object: follow_activity
|
||||
}),
|
||||
_ <-
|
||||
|
|
|
@ -10,7 +10,7 @@ defmodule NullaWeb.OutboxController do
|
|||
"true" ->
|
||||
instance_settings = InstanceSettings.get_instance_settings!()
|
||||
domain = instance_settings.domain
|
||||
actor = Actor.get_actor(username, domain)
|
||||
actor = Actor.get_actor(preferredUsername: username, domain: domain)
|
||||
max_id = params["max_id"] && String.to_integer(params["max_id"])
|
||||
|
||||
notes =
|
||||
|
@ -44,7 +44,7 @@ defmodule NullaWeb.OutboxController do
|
|||
_ ->
|
||||
instance_settings = InstanceSettings.get_instance_settings!()
|
||||
domain = instance_settings.domain
|
||||
actor = Actor.get_actor(username, domain)
|
||||
actor = Actor.get_actor(preferredUsername: username, domain: domain)
|
||||
total = Note.get_total_notes_count(actor.id)
|
||||
|
||||
conn
|
||||
|
|
|
@ -7,7 +7,7 @@ defmodule NullaWeb.WebfingerController do
|
|||
def index(conn, %{"resource" => resource}) do
|
||||
case Regex.run(~r/^acct:(.+)@(.+)$/, resource) do
|
||||
[_, username, domain] ->
|
||||
case Actor.get_actor(username, domain) do
|
||||
case Actor.get_actor(preferredUsername: username, domain: domain) do
|
||||
nil ->
|
||||
conn
|
||||
|> put_resp_content_type("text/plain")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue