Blind key rotation and stale duration for profiles

See https://blog.dereferenced.org/the-case-for-blind-key-rotation

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2020-02-14 17:56:36 +01:00
parent 3a753312c1
commit 39b7afd1cd
10 changed files with 164 additions and 11 deletions

View File

@@ -50,7 +50,8 @@ defmodule Mobilizon.Actors.Actor do
mentions: [Mention.t()],
shares: [Share.t()],
owner_shares: [Share.t()],
memberships: [t]
memberships: [t],
last_refreshed_at: DateTime.t()
}
@required_attrs [:preferred_username, :keys, :suspended, :url]
@@ -65,6 +66,7 @@ defmodule Mobilizon.Actors.Actor do
:domain,
:summary,
:manually_approves_followers,
:last_refreshed_at,
:user_id
]
@attrs @required_attrs ++ @optional_attrs
@@ -118,6 +120,7 @@ defmodule Mobilizon.Actors.Actor do
field(:openness, ActorOpenness, default: :moderated)
field(:visibility, ActorVisibility, default: :private)
field(:suspended, :boolean, default: false)
field(:last_refreshed_at, :utc_datetime)
embeds_one(:avatar, File, on_replace: :update)
embeds_one(:banner, File, on_replace: :update)
@@ -261,6 +264,7 @@ defmodule Mobilizon.Actors.Actor do
|> unique_constraint(:url, name: :actors_url_index)
|> unique_constraint(:preferred_username, name: :actors_preferred_username_domain_type_index)
|> validate_format(:preferred_username, ~r/[a-z0-9_]+/)
|> put_change(:last_refreshed_at, DateTime.utc_now() |> DateTime.truncate(:second))
end
@doc """

View File

@@ -260,6 +260,13 @@ defmodule Mobilizon.Actors do
end
end
@spec actor_key_rotation(Actor.t()) :: {:ok, Actor.t()} | {:error, Ecto.Changeset.t()}
def actor_key_rotation(%Actor{} = actor) do
actor
|> Actor.changeset(%{keys: Crypto.generate_rsa_2048_private_key()})
|> Repo.update()
end
@doc """
Returns the list of actors.
"""
@@ -746,6 +753,40 @@ defmodule Mobilizon.Actors do
get_follower_by_followed_and_following(followed_actor, follower_actor)
end
@doc """
Whether the actor needs to be updated.
Local actors obviously don't need to be updated
"""
@spec needs_update?(Actor.t()) :: boolean
def needs_update?(%Actor{domain: nil}), do: false
def needs_update?(%Actor{last_refreshed_at: nil, domain: domain}) when not is_nil(domain),
do: true
def needs_update?(%Actor{domain: domain} = actor) when not is_nil(domain) do
DateTime.diff(DateTime.utc_now(), actor.last_refreshed_at) >=
Application.get_env(:mobilizon, :activitypub)[:actor_stale_period]
end
def needs_update?(_), do: true
@spec should_rotate_actor_key(Actor.t()) :: boolean
def should_rotate_actor_key(%Actor{id: actor_id}) do
with {:ok, value} when is_boolean(value) <- Cachex.exists?(:actor_key_rotation, actor_id) do
value
end
end
@spec schedule_key_rotation(Actor.t(), integer()) :: nil
def schedule_key_rotation(%Actor{id: actor_id} = actor, delay) do
Cachex.put(:actor_key_rotation, actor_id, true)
Workers.Background.enqueue("actor_key_rotation", %{"actor_id" => actor.id}, schedule_in: delay)
:ok
end
@spec remove_banner(Actor.t()) :: {:ok, Actor.t()}
defp remove_banner(%Actor{banner: nil} = actor), do: {:ok, actor}