Require that ephemeral posts live for at least one hour
authorMike Verdone <spiral@arcseconds.net>
Tue, 23 Jul 2019 14:33:45 +0000 (16:33 +0200)
committerMike Verdone <spiral@arcseconds.net>
Wed, 24 Jul 2019 12:46:08 +0000 (14:46 +0200)
If we didn't put some kind of lifetime requirement on these, I guess you
could annoy people by sending large numbers of ephemeral posts that
provoke notifications but then disappear before anyone can read them.

lib/pleroma/activity_expiration.ex
lib/pleroma/web/common_api/common_api.ex
test/activity_expiration_test.exs
test/support/factory.ex

index a0af5255b7ab8415a3bf06ea32e7fe361f119687..bf57abca493381792dde61e6981dac1c617f498b 100644 (file)
@@ -14,6 +14,7 @@ defmodule Pleroma.ActivityExpiration do
   import Ecto.Query
 
   @type t :: %__MODULE__{}
+  @min_activity_lifetime :timer.hours(1)
 
   schema "activity_expirations" do
     belongs_to(:activity, Activity, type: FlakeId)
@@ -24,6 +25,7 @@ defmodule Pleroma.ActivityExpiration do
     expiration
     |> cast(attrs, [:scheduled_at])
     |> validate_required([:scheduled_at])
+    |> validate_scheduled_at()
   end
 
   def get_by_activity_id(activity_id) do
@@ -47,4 +49,20 @@ defmodule Pleroma.ActivityExpiration do
     |> where([exp], exp.scheduled_at < ^naive_datetime)
     |> Repo.all()
   end
+
+  def validate_scheduled_at(changeset) do
+    validate_change(changeset, :scheduled_at, fn _, scheduled_at ->
+      if not expires_late_enough?(scheduled_at) do
+        [scheduled_at: "an ephemeral activity must live for at least one hour"]
+      else
+        []
+      end
+    end)
+  end
+
+  def expires_late_enough?(scheduled_at) do
+    now = NaiveDateTime.utc_now()
+    diff = NaiveDateTime.diff(scheduled_at, now, :millisecond)
+    diff >= @min_activity_lifetime
+  end
 end
index 0f287af4eb240f84d9d6deef5ded0b3e58bb6597..261d603923eda31239fbbc87ece9c3ca49e256cd 100644 (file)
@@ -196,6 +196,16 @@ defmodule Pleroma.Web.CommonAPI do
     end
   end
 
+  defp check_expiry_date(expiry_str) do
+    {:ok, expiry} = Ecto.Type.cast(:naive_datetime, expiry_str)
+
+    if is_nil(expiry) || ActivityExpiration.expires_late_enough?(expiry) do
+      {:ok, expiry}
+    else
+      {:error, "Expiry date is too soon"}
+    end
+  end
+
   def post(user, %{"status" => status} = data) do
     limit = Pleroma.Config.get([:instance, :limit])
 
@@ -219,7 +229,7 @@ defmodule Pleroma.Web.CommonAPI do
          context <- make_context(in_reply_to),
          cw <- data["spoiler_text"] || "",
          sensitive <- data["sensitive"] || Enum.member?(tags, {"#nsfw", "nsfw"}),
-         {:ok, expires_at} <- Ecto.Type.cast(:naive_datetime, data["expires_at"]),
+         {:ok, expires_at} <- check_expiry_date(data["expires_at"]),
          full_payload <- String.trim(status <> cw),
          :ok <- validate_character_limit(full_payload, attachments, limit),
          object <-
@@ -258,7 +268,7 @@ defmodule Pleroma.Web.CommonAPI do
 
       if expires_at do
         with {:ok, activity} <- result do
-          ActivityExpiration.create(activity, expires_at)
+          {:ok, _} = ActivityExpiration.create(activity, expires_at)
         end
       end
 
index 20566a186b72287150c4abb0d700a4daf7508482..4948fae16b79f317569771ff1ecde5e5f520de4e 100644 (file)
@@ -18,4 +18,10 @@ defmodule Pleroma.ActivityExpirationTest do
     assert length(expirations) == 1
     assert hd(expirations) == expiration_due
   end
+
+  test "denies expirations that don't live long enough" do
+    activity = insert(:note_activity)
+    now = NaiveDateTime.utc_now()
+    assert {:error, _} = ActivityExpiration.create(activity, now)
+  end
 end
index 63fe3a66d30a5a25256e2ee9d48eeb52d9dd7c3a..7a2ddcadac1504bce05d242fcf92e9c66dfd531d 100644 (file)
@@ -158,7 +158,7 @@ defmodule Pleroma.Factory do
   end
 
   def expiration_in_the_future_factory(attrs \\ %{}) do
-    expiration_offset_by_minutes(attrs, 60)
+    expiration_offset_by_minutes(attrs, 61)
   end
 
   def article_activity_factory do