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
|
defmodule Nulla.HTTPSignature do
|
||||||
def verify(_conn, _actor) do
|
import Plug.Conn
|
||||||
:ok
|
|
||||||
|
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
|
||||||
end
|
end
|
||||||
|
|
|
@ -138,7 +138,7 @@ defmodule Nulla.Models.Actor do
|
||||||
|> Repo.insert()
|
|> Repo.insert()
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_actor(username, domain) do
|
def get_actor(by) when is_map(by) or is_list(by) do
|
||||||
Repo.get_by(__MODULE__, preferredUsername: username, domain: domain)
|
Repo.get_by(__MODULE__, by)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,7 +3,6 @@ defmodule Nulla.Models.User do
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
alias Nulla.Repo
|
alias Nulla.Repo
|
||||||
alias Nulla.Models.User
|
|
||||||
alias Nulla.Models.Session
|
alias Nulla.Models.Session
|
||||||
|
|
||||||
@primary_key {:id, :integer, autogenerate: false}
|
@primary_key {:id, :integer, autogenerate: false}
|
||||||
|
@ -44,17 +43,17 @@ defmodule Nulla.Models.User do
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_user(by) when is_map(by) or is_list(by) do
|
def get_user(by) when is_map(by) or is_list(by) do
|
||||||
Repo.get_by(User, by)
|
Repo.get_by(__MODULE__, by)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_total_users_count() do
|
def get_total_users_count() do
|
||||||
Repo.aggregate(from(u in User), :count, :id)
|
Repo.aggregate(from(u in __MODULE__), :count, :id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_active_users_count(days) do
|
def get_active_users_count(days) do
|
||||||
cutoff = DateTime.add(DateTime.utc_now(), -days * 86400, :second)
|
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)
|
|> Repo.aggregate(:count, :id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,28 +1,12 @@
|
||||||
defmodule Nulla.Utils do
|
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
|
def fetch_remote_actor(uri) do
|
||||||
request =
|
headers = [
|
||||||
Finch.build(:get, uri, [
|
{"Accept", "application/activity+json"},
|
||||||
{"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
|
case Finch.request(request, Finch) do
|
||||||
{:ok, %Finch.Response{status: 200, body: body}} ->
|
{:ok, %Finch.Response{status: 200, body: body}} ->
|
||||||
|
@ -31,6 +15,9 @@ defmodule Nulla.Utils do
|
||||||
_ -> {:error, :invalid_json}
|
_ -> {:error, :invalid_json}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
{:ok, %Finch.Response{status: code}} when code in 300..399 ->
|
||||||
|
{:error, :redirect_not_followed}
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
{:error, :actor_fetch_failed}
|
{:error, :actor_fetch_failed}
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,7 +10,7 @@ defmodule NullaWeb.ActorController do
|
||||||
instance_settings = InstanceSettings.get_instance_settings!()
|
instance_settings = InstanceSettings.get_instance_settings!()
|
||||||
domain = instance_settings.domain
|
domain = instance_settings.domain
|
||||||
|
|
||||||
case Actor.get_actor(username, domain) do
|
case Actor.get_actor(preferredUsername: username, domain: domain) do
|
||||||
nil ->
|
nil ->
|
||||||
conn
|
conn
|
||||||
|> put_status(:not_found)
|
|> put_status(:not_found)
|
||||||
|
|
|
@ -9,7 +9,7 @@ defmodule NullaWeb.FollowController do
|
||||||
instance_settings = InstanceSettings.get_instance_settings!()
|
instance_settings = InstanceSettings.get_instance_settings!()
|
||||||
domain = instance_settings.domain
|
domain = instance_settings.domain
|
||||||
limit = instance_settings.api_limit
|
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)
|
total = Relation.count_following(actor.id)
|
||||||
|
|
||||||
page =
|
page =
|
||||||
|
@ -28,7 +28,7 @@ defmodule NullaWeb.FollowController do
|
||||||
def following(conn, %{"username" => username}) do
|
def following(conn, %{"username" => username}) do
|
||||||
instance_settings = InstanceSettings.get_instance_settings!()
|
instance_settings = InstanceSettings.get_instance_settings!()
|
||||||
domain = instance_settings.domain
|
domain = instance_settings.domain
|
||||||
actor = Actor.get_actor(username, domain)
|
actor = Actor.get_actor(preferredUsername: username, domain: domain)
|
||||||
total = Relation.count_following(actor.id)
|
total = Relation.count_following(actor.id)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|
@ -40,7 +40,7 @@ defmodule NullaWeb.FollowController do
|
||||||
instance_settings = InstanceSettings.get_instance_settings!()
|
instance_settings = InstanceSettings.get_instance_settings!()
|
||||||
domain = instance_settings.domain
|
domain = instance_settings.domain
|
||||||
limit = instance_settings.api_limit
|
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)
|
total = Relation.count_followers(actor.id)
|
||||||
|
|
||||||
page =
|
page =
|
||||||
|
@ -59,7 +59,7 @@ defmodule NullaWeb.FollowController do
|
||||||
def followers(conn, %{"username" => username}) do
|
def followers(conn, %{"username" => username}) do
|
||||||
instance_settings = InstanceSettings.get_instance_settings!()
|
instance_settings = InstanceSettings.get_instance_settings!()
|
||||||
domain = instance_settings.domain
|
domain = instance_settings.domain
|
||||||
actor = Actor.get_actor(username, domain)
|
actor = Actor.get_actor(preferredUsername: username, domain: domain)
|
||||||
total = Relation.count_followers(actor.id)
|
total = Relation.count_followers(actor.id)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|
|
|
@ -11,7 +11,7 @@ defmodule NullaWeb.InboxController do
|
||||||
conn,
|
conn,
|
||||||
%{"id" => follow_id, "type" => "Follow", "actor" => actor_uri, "object" => target_uri}
|
%{"id" => follow_id, "type" => "Follow", "actor" => actor_uri, "object" => target_uri}
|
||||||
) do
|
) 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, remote_actor_json} <- Utils.fetch_remote_actor(actor_uri),
|
||||||
:ok <- HTTPSignature.verify(conn, remote_actor_json),
|
:ok <- HTTPSignature.verify(conn, remote_actor_json),
|
||||||
remote_actor <-
|
remote_actor <-
|
||||||
|
@ -31,7 +31,7 @@ defmodule NullaWeb.InboxController do
|
||||||
accept_activity <-
|
accept_activity <-
|
||||||
Activity.create_activity(%{
|
Activity.create_activity(%{
|
||||||
type: "Accept",
|
type: "Accept",
|
||||||
actor: local_actor.id,
|
actor: local_actor.ap_id,
|
||||||
object: follow_activity
|
object: follow_activity
|
||||||
}),
|
}),
|
||||||
_ <-
|
_ <-
|
||||||
|
|
|
@ -10,7 +10,7 @@ defmodule NullaWeb.OutboxController do
|
||||||
"true" ->
|
"true" ->
|
||||||
instance_settings = InstanceSettings.get_instance_settings!()
|
instance_settings = InstanceSettings.get_instance_settings!()
|
||||||
domain = instance_settings.domain
|
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"])
|
max_id = params["max_id"] && String.to_integer(params["max_id"])
|
||||||
|
|
||||||
notes =
|
notes =
|
||||||
|
@ -44,7 +44,7 @@ defmodule NullaWeb.OutboxController do
|
||||||
_ ->
|
_ ->
|
||||||
instance_settings = InstanceSettings.get_instance_settings!()
|
instance_settings = InstanceSettings.get_instance_settings!()
|
||||||
domain = instance_settings.domain
|
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)
|
total = Note.get_total_notes_count(actor.id)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|
|
|
@ -7,7 +7,7 @@ defmodule NullaWeb.WebfingerController do
|
||||||
def index(conn, %{"resource" => resource}) do
|
def index(conn, %{"resource" => resource}) do
|
||||||
case Regex.run(~r/^acct:(.+)@(.+)$/, resource) do
|
case Regex.run(~r/^acct:(.+)@(.+)$/, resource) do
|
||||||
[_, username, domain] ->
|
[_, username, domain] ->
|
||||||
case Actor.get_actor(username, domain) do
|
case Actor.get_actor(preferredUsername: username, domain: domain) do
|
||||||
nil ->
|
nil ->
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("text/plain")
|
|> put_resp_content_type("text/plain")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue