From f6422cb3701169f0491bd0e7d89baded79439452 Mon Sep 17 00:00:00 2001
From: ilja
Date: Sun, 4 Aug 2024 09:49:37 +0200
Subject: [PATCH 01/95] Use FEP-c16b: formatting mfm functions
See
We can't use the HTML content as-is.
[FEP-c16b](https://codeberg.org/fediverse/fep/src/branch/main/fep/c16b/fep-c16b.md) was
written to have a "scrubber friendly" way of representing MFM functions in HTML. Here
we add support in the backend for the functions Foundkey supports. The front-ends then
needs to add support to make sure the HTML representation is turned into a correct view.
(I.e. by help of CSS and Javascript)
FEP-c16b also has a discovery mechanism to indicate to recieving servers that they can
use the `content` directly. This is not implemented in this commit
---
mix.exs | 4 ++--
mix.lock | 43 ++++++++++++++++-------------------
priv/scrubbers/default.ex | 48 +++++++++++++++++++++++++++++++++++++++
3 files changed, 69 insertions(+), 26 deletions(-)
diff --git a/mix.exs b/mix.exs
index 7ffc450e2..5bcbc5f4c 100644
--- a/mix.exs
+++ b/mix.exs
@@ -195,8 +195,8 @@ defp deps do
{:elasticsearch,
git: "https://akkoma.dev/AkkomaGang/elasticsearch-elixir.git", ref: "main"},
{:mfm_parser,
- git: "https://akkoma.dev/AkkomaGang/mfm-parser.git",
- ref: "b21ab7754024af096f2d14247574f55f0063295b"},
+ git: "https://codeberg.org/ilja/mfm_parser.git",
+ ref: "be09e20e7a2fad2e9964ec4c563201d3fd1c4391"},
## dev & test
{:ex_doc, "~> 0.30", only: :dev, runtime: false},
diff --git a/mix.lock b/mix.lock
index fbecdc528..ea2b6e2d5 100644
--- a/mix.lock
+++ b/mix.lock
@@ -8,24 +8,23 @@
"cachex": {:hex, :cachex, "3.6.0", "14a1bfbeee060dd9bec25a5b6f4e4691e3670ebda28c8ba2884b12fe30b36bf8", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "ebf24e373883bc8e0c8d894a63bbe102ae13d918f790121f5cfe6e485cc8e2e2"},
"calendar": {:hex, :calendar, "1.0.0", "f52073a708528482ec33d0a171954ca610fe2bd28f1e871f247dc7f1565fa807", [:mix], [{:tzdata, "~> 0.1.201603 or ~> 0.5.20 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "990e9581920c82912a5ee50e62ff5ef96da6b15949a2ee4734f935fdef0f0a6f"},
"captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "90f6ce7672f70f56708792a98d98bd05176c9176", [ref: "90f6ce7672f70f56708792a98d98bd05176c9176"]},
- "castore": {:hex, :castore, "1.0.7", "b651241514e5f6956028147fe6637f7ac13802537e895a724f90bf3e36ddd1dd", [:mix], [], "hexpm", "da7785a4b0d2a021cd1292a60875a784b6caef71e76bf4917bdee1f390455cf5"},
+ "castore": {:hex, :castore, "1.0.8", "dedcf20ea746694647f883590b82d9e96014057aff1d44d03ec90f36a5c0dc6e", [:mix], [], "hexpm", "0b2b66d2ee742cb1d9cb8c8be3b43c3a70ee8651f37b75a8b982e036752983f1"},
"certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
"comeonin": {:hex, :comeonin, "5.4.0", "246a56ca3f41d404380fc6465650ddaa532c7f98be4bda1b4656b3a37cc13abe", [:mix], [], "hexpm", "796393a9e50d01999d56b7b8420ab0481a7538d0caf80919da493b4a6e51faf1"},
"concurrent_limiter": {:git, "https://akkoma.dev/AkkomaGang/concurrent-limiter.git", "a9e0b3d64574bdba761f429bb4fba0cf687b3338", [ref: "a9e0b3d64574bdba761f429bb4fba0cf687b3338"]},
- "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
"cors_plug": {:hex, :cors_plug, "3.0.3", "7c3ac52b39624bc616db2e937c282f3f623f25f8d550068b6710e58d04a0e330", [:mix], [{:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3f2d759e8c272ed3835fab2ef11b46bddab8c1ab9528167bd463b6452edf830d"},
"cowboy": {:hex, :cowboy, "2.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"},
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
"cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"},
- "credo": {:hex, :credo, "1.7.6", "b8f14011a5443f2839b04def0b252300842ce7388f3af177157c86da18dfbeea", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "146f347fb9f8cbc5f7e39e3f22f70acbef51d441baa6d10169dd604bfbc55296"},
+ "credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"},
"custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"},
- "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"},
+ "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"},
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
"dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"},
- "earmark": {:hex, :earmark, "1.4.46", "8c7287bd3137e99d26ae4643e5b7ef2129a260e3dcf41f251750cb4563c8fb81", [:mix], [], "hexpm", "798d86db3d79964e759ddc0c077d5eb254968ed426399fbf5a62de2b5ff8910a"},
- "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"},
+ "earmark": {:hex, :earmark, "1.4.47", "7e7596b84fe4ebeb8751e14cbaeaf4d7a0237708f2ce43630cfd9065551f94ca", [:mix], [], "hexpm", "3e96bebea2c2d95f3b346a7ff22285bc68a99fbabdad9b655aa9c6be06c698f8"},
+ "earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"},
"eblurhash": {:hex, :eblurhash, "1.2.2", "7da4255aaea984b31bb71155f673257353b0e0554d0d30dcf859547e74602582", [:rebar3], [], "hexpm", "8c20ca00904de023a835a9dcb7b7762fed32264c85a80c3cafa85288e405044c"},
"ecto": {:hex, :ecto, "3.10.3", "eb2ae2eecd210b4eb8bece1217b297ad4ff824b4384c0e3fdd28aaf96edd6135", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "44bec74e2364d491d70f7e42cd0d690922659d329f6465e89feb8a34e8cd3433"},
"ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"},
@@ -34,20 +33,19 @@
"elasticsearch": {:git, "https://akkoma.dev/AkkomaGang/elasticsearch-elixir.git", "6cd946f75f6ab9042521a009d1d32d29a90113ca", [ref: "main"]},
"elixir_make": {:hex, :elixir_make, "0.8.4", "4960a03ce79081dee8fe119d80ad372c4e7badb84c493cc75983f9d3bc8bde0f", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "6e7f1d619b5f61dfabd0a20aa268e575572b542ac31723293a4c1a567d5ef040"},
"elixir_xml_to_map": {:hex, :elixir_xml_to_map, "3.1.0", "4d6260486a8cce59e4bf3575fe2dd2a24766546ceeef9f93fcec6f7c62a2827a", [:mix], [{:erlsom, "~> 1.4", [hex: :erlsom, repo: "hexpm", optional: false]}], "hexpm", "8fe5f2e75f90bab07ee2161120c2dc038ebcae8135554f5582990f1c8c21f911"},
- "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
+ "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"},
"erlsom": {:hex, :erlsom, "1.5.1", "c8fe2babd33ff0846403f6522328b8ab676f896b793634cfe7ef181c05316c03", [:rebar3], [], "hexpm", "7965485494c5844dd127656ac40f141aadfa174839ec1be1074e7edf5b4239eb"},
"eternal": {:hex, :eternal, "1.2.2", "d1641c86368de99375b98d183042dd6c2b234262b8d08dfd72b9eeaafc2a1abd", [:mix], [], "hexpm", "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782"},
"ex_aws": {:hex, :ex_aws, "2.5.4", "86c5bb870a49e0ab6f5aa5dd58cf505f09d2624ebe17530db3c1b61c88a673af", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e82bd0091bb9a5bb190139599f922ff3fc7aebcca4374d65c99c4e23aa6d1625"},
"ex_aws_s3": {:hex, :ex_aws_s3, "2.5.3", "422468e5c3e1a4da5298e66c3468b465cfd354b842e512cb1f6fbbe4e2f5bdaf", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "4f09dd372cc386550e484808c5ac5027766c8d0cd8271ccc578b82ee6ef4f3b8"},
"ex_const": {:hex, :ex_const, "0.3.0", "9d79516679991baf540ef445438eef1455ca91cf1a3c2680d8fb9e5bea2fe4de", [:mix], [], "hexpm", "76546322abb9e40ee4a2f454cf1c8a5b25c3672fa79bed1ea52c31e0d2428ca9"},
- "ex_doc": {:hex, :ex_doc, "0.34.0", "ab95e0775db3df71d30cf8d78728dd9261c355c81382bcd4cefdc74610bef13e", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "60734fb4c1353f270c3286df4a0d51e65a2c1d9fba66af3940847cc65a8066d7"},
- "ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"},
+ "ex_doc": {:hex, :ex_doc, "0.34.2", "13eedf3844ccdce25cfd837b99bea9ad92c4e511233199440488d217c92571e8", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "5ce5f16b41208a50106afed3de6a2ed34f4acfd65715b82a0b84b49d995f95c1"},
+ "ex_machina": {:hex, :ex_machina, "2.8.0", "a0e847b5712065055ec3255840e2c78ef9366634d62390839d4880483be38abe", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "79fe1a9c64c0c1c1fab6c4fa5d871682cb90de5885320c187d117004627a7729"},
"ex_syslogger": {:hex, :ex_syslogger, "2.0.0", "de6de5c5472a9c4fdafb28fa6610e381ae79ebc17da6490b81d785d68bd124c9", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}, {:syslog, "~> 1.1.0", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "a52b2fe71764e9e6ecd149ab66635812f68e39279cbeee27c52c0e35e8b8019e"},
"excoveralls": {:hex, :excoveralls, "0.16.1", "0bd42ed05c7d2f4d180331a20113ec537be509da31fed5c8f7047ce59ee5a7c5", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "dae763468e2008cf7075a64cb1249c97cb4bc71e236c5c2b5e5cdf1cfa2bf138"},
"expo": {:hex, :expo, "0.4.1", "1c61d18a5df197dfda38861673d392e642649a9cef7694d2f97a587b2cfb319b", [:mix], [], "hexpm", "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47"},
"fast_html": {:hex, :fast_html, "2.3.0", "08c1d8ead840dd3060ba02c761bed9f37f456a1ddfe30bcdcfee8f651cec06a6", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}], "hexpm", "f18e3c7668f82d3ae0b15f48d48feeb257e28aa5ab1b0dbf781c7312e5da029d"},
"fast_sanitize": {:hex, :fast_sanitize, "0.2.3", "67b93dfb34e302bef49fec3aaab74951e0f0602fd9fa99085987af05bd91c7a5", [:mix], [{:fast_html, "~> 2.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "e8ad286d10d0386e15d67d0ee125245ebcfbc7d7290b08712ba9013c8c5e56e2"},
- "file_ex": {:git, "https://akkoma.dev/AkkomaGang/file_ex.git", "cc7067c7d446c2526e9ecf91d40896b088851569", [ref: "cc7067c7d446c2526e9ecf91d40896b088851569"]},
"file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"},
"finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"},
"flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "31fc8090fde1acd267c07c36ea7365b8604055f897d3a53dd967658c691bd827"},
@@ -61,7 +59,7 @@
"httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"inet_cidr": {:hex, :inet_cidr, "1.0.8", "d26bb7bdbdf21ae401ead2092bf2bb4bf57fe44a62f5eaa5025280720ace8a40", [:mix], [], "hexpm", "d5b26da66603bb56c933c65214c72152f0de9a6ea53618b56d63302a68f6a90e"},
- "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"},
+ "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
"joken": {:hex, :joken, "2.6.1", "2ca3d8d7f83bf7196296a3d9b2ecda421a404634bfc618159981a960020480a1", [:mix], [{:jose, "~> 1.11.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "ab26122c400b3d254ce7d86ed066d6afad27e70416df947cdcb01e13a7382e68"},
"jose": {:hex, :jose, "1.11.10", "a903f5227417bd2a08c8a00a0cbcc458118be84480955e8d251297a425723f83", [:mix, :rebar3], [], "hexpm", "0d6cd36ff8ba174db29148fc112b5842186b68a90ce9fc2b3ec3afe76593e614"},
"jumper": {:hex, :jumper, "1.0.2", "68cdcd84472a00ac596b4e6459a41b3062d4427cbd4f1e8c8793c5b54f1406a7", [:mix], [], "hexpm", "9b7782409021e01ab3c08270e26f36eb62976a38c1aa64b2eaf6348422f165e1"},
@@ -70,11 +68,11 @@
"majic": {:git, "https://akkoma.dev/AkkomaGang/majic.git", "80540b36939ec83f48e76c61e5000e0fd67706f0", [ref: "80540b36939ec83f48e76c61e5000e0fd67706f0"]},
"makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"},
"makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"},
- "makeup_erlang": {:hex, :makeup_erlang, "1.0.0", "6f0eff9c9c489f26b69b61440bf1b238d95badae49adac77973cbacae87e3c2e", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "ea7a9307de9d1548d2a72d299058d1fd2339e3d398560a0e46c27dab4891e4d2"},
+ "makeup_erlang": {:hex, :makeup_erlang, "1.0.1", "c7f58c120b2b5aa5fd80d540a89fdf866ed42f1f3994e4fe189abebeab610839", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "8a89a1eeccc2d798d6ea15496a6e4870b75e014d1af514b1b71fa33134f57814"},
"meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
- "mfm_parser": {:git, "https://akkoma.dev/AkkomaGang/mfm-parser.git", "b21ab7754024af096f2d14247574f55f0063295b", [ref: "b21ab7754024af096f2d14247574f55f0063295b"]},
- "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"},
+ "mfm_parser": {:git, "https://codeberg.org/ilja/mfm_parser.git", "be09e20e7a2fad2e9964ec4c563201d3fd1c4391", [ref: "be09e20e7a2fad2e9964ec4c563201d3fd1c4391"]},
+ "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"},
"mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"},
"mint": {:hex, :mint, "1.5.2", "4805e059f96028948870d23d7783613b7e6b0e2fb4e98d720383852a760067fd", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "d77d9e9ce4eb35941907f1d3df38d8f750c357865353e21d335bdcdf6d892a02"},
"mock": {:hex, :mock, "0.3.8", "7046a306b71db2488ef54395eeb74df0a7f335a7caca4a3d3875d1fc81c884dd", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "7fa82364c97617d79bb7d15571193fc0c4fe5afd0c932cef09426b3ee6fe2022"},
@@ -83,23 +81,22 @@
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
"nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
- "oban": {:hex, :oban, "2.17.10", "c3e5bd739b5c3fdc38eba1d43ab270a8c6ca4463bb779b7705c69400b0d87678", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4afd027b8e2bc3c399b54318b4f46ee8c40251fb55a285cb4e38b5363f0ee7c4"},
- "open_api_spex": {:hex, :open_api_spex, "3.19.1", "65ccb5d06e3d664d1eec7c5ea2af2289bd2f37897094a74d7219fb03fc2b5994", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "392895827ce2984a3459c91a484e70708132d8c2c6c5363972b4b91d6bbac3dd"},
+ "oban": {:hex, :oban, "2.17.12", "33fb0cbfb92b910d48dd91a908590fe3698bb85eacec8cd0d9bc6aa13dddd6d6", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7a647d6cd6bb300073db17faabce22d80ae135da3baf3180a064fa7c4fa046e3"},
+ "open_api_spex": {:hex, :open_api_spex, "3.20.1", "ce5b3db013cd7337ab147f39fa2d4d627ddeeb4ff3fea34792f43d7e2e654605", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0 or ~> 6.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "dc9c383949d0fc4b20b73103ac20af39dad638b3a15c0e6281853c2fc7cc3cc8"},
"parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},
- "phoenix": {:hex, :phoenix, "1.7.12", "1cc589e0eab99f593a8aa38ec45f15d25297dd6187ee801c8de8947090b5a9d3", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "d646192fbade9f485b01bc9920c139bfdd19d0f8df3d73fd8eaf2dfbe0d2837c"},
- "phoenix_ecto": {:hex, :phoenix_ecto, "4.6.1", "96798325fab2fed5a824ca204e877b81f9afd2e480f581e81f7b4b64a5a477f2", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.17", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "0ae544ff99f3c482b0807c5cec2c8289e810ecacabc04959d82c3337f4703391"},
+ "phoenix": {:hex, :phoenix, "1.7.14", "a7d0b3f1bc95987044ddada111e77bd7f75646a08518942c72a8440278ae7825", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "c7859bc56cc5dfef19ecfc240775dae358cbaa530231118a9e014df392ace61a"},
+ "phoenix_ecto": {:hex, :phoenix_ecto, "4.6.2", "3b83b24ab5a2eb071a20372f740d7118767c272db386831b2e77638c4dcc606d", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "3f94d025f59de86be00f5f8c5dd7b5965a3298458d21ab1c328488be3b5fcd59"},
"phoenix_html": {:hex, :phoenix_html, "3.3.4", "42a09fc443bbc1da37e372a5c8e6755d046f22b9b11343bf885067357da21cb3", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0249d3abec3714aff3415e7ee3d9786cb325be3151e6c4b3021502c585bf53fb"},
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.7.2", "97cc4ff2dba1ebe504db72cb45098cb8e91f11160528b980bd282cc45c73b29c", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.3", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0e5fdf063c7a3b620c566a30fcf68b7ee02e5e46fe48ee46a6ec3ba382dc05b7"},
"phoenix_live_view": {:hex, :phoenix_live_view, "0.18.18", "1f38fbd7c363723f19aad1a04b5490ff3a178e37daaf6999594d5f34796c47fc", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a5810d0472f3189ede6d2a95bda7f31c6113156b91784a3426cb0ab6a6d85214"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"},
"phoenix_swoosh": {:hex, :phoenix_swoosh, "1.2.1", "b74ccaa8046fbc388a62134360ee7d9742d5a8ae74063f34eb050279de7a99e1", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "4000eeba3f9d7d1a6bf56d2bd56733d5cadf41a7f0d8ffe5bb67e7d667e204a2"},
"phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},
- "phoenix_view": {:hex, :phoenix_view, "2.0.3", "4d32c4817fce933693741deeb99ef1392619f942633dde834a5163124813aad3", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "cd34049af41be2c627df99cd4eaa71fc52a328c0c3d8e7d4aa28f880c30e7f64"},
- "plug": {:hex, :plug, "1.16.0", "1d07d50cb9bb05097fdf187b31cf087c7297aafc3fed8299aac79c128a707e47", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cbf53aa1f5c4d758a7559c0bd6d59e286c2be0c6a1fac8cc3eee2f638243b93e"},
+ "phoenix_view": {:hex, :phoenix_view, "2.0.4", "b45c9d9cf15b3a1af5fb555c674b525391b6a1fe975f040fb4d913397b31abf4", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "4e992022ce14f31fe57335db27a28154afcc94e9983266835bb3040243eb620b"},
+ "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"},
"plug_cowboy": {:hex, :plug_cowboy, "2.7.1", "87677ffe3b765bc96a89be7960f81703223fe2e21efa42c125fcd0127dd9d6b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "02dbd5f9ab571b864ae39418db7811618506256f6d13b4a45037e5fe78dc5de3"},
"plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},
"plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "79fd4fcf34d110605c26560cbae8f23c603ec4158c08298bd4360fdea90bb5cf"},
- "poison": {:hex, :poison, "5.0.0", "d2b54589ab4157bbb82ec2050757779bfed724463a544b6e20d79855a9e43b24", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "11dc6117c501b80c62a7594f941d043982a1bd05a1184280c0d9166eb4d8d3fc"},
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
"postgrex": {:hex, :postgrex, "0.17.5", "0483d054938a8dc069b21bdd636bf56c487404c241ce6c319c1f43588246b281", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "50b8b11afbb2c4095a3ba675b4f055c416d0f3d7de6633a595fc131a828a67eb"},
"pot": {:hex, :pot, "1.0.2", "13abb849139fdc04ab8154986abbcb63bdee5de6ed2ba7e1713527e33df923dd", [:rebar3], [], "hexpm", "78fe127f5a4f5f919d6ea5a2a671827bd53eb9d37e5b4128c0ad3df99856c2e0"},
@@ -116,11 +113,9 @@
"table_rex": {:hex, :table_rex, "4.0.0", "3c613a68ebdc6d4d1e731bc973c233500974ec3993c99fcdabb210407b90959b", [:mix], [], "hexpm", "c35c4d5612ca49ebb0344ea10387da4d2afe278387d4019e4d8111e815df8f55"},
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.2", "2caabe9344ec17eafe5403304771c3539f3b6e2f7fb6a6f602558c825d0d0bfb", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9b43db0dc33863930b9ef9d27137e78974756f5f198cae18409970ed6fa5b561"},
- "telemetry_metrics_prometheus": {:hex, :telemetry_metrics_prometheus, "1.1.0", "1cc23e932c1ef9aa3b91db257ead31ea58d53229d407e059b29bb962c1505a13", [:mix], [{:plug_cowboy, "~> 2.1", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:telemetry_metrics_prometheus_core, "~> 1.0", [hex: :telemetry_metrics_prometheus_core, repo: "hexpm", optional: false]}], "hexpm", "d43b3659b3244da44fe0275b717701542365d4519b79d9ce895b9719c1ce4d26"},
"telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.1.0", "4e15f6d7dbedb3a4e3aed2262b7e1407f166fcb9c30ca3f96635dfbbef99965c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0dd10e7fe8070095df063798f82709b0a1224c31b8baf6278b423898d591a069"},
"telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"},
- "temple": {:git, "https://akkoma.dev/AkkomaGang/temple.git", "066a699ade472d8fa42a9d730b29a61af9bc8b59", [ref: "066a699ade472d8fa42a9d730b29a61af9bc8b59"]},
- "tesla": {:hex, :tesla, "1.9.0", "8c22db6a826e56a087eeb8cdef56889731287f53feeb3f361dec5d4c8efb6f14", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "7c240c67e855f7e63e795bf16d6b3f5115a81d1f44b7fe4eadbf656bae0fef8a"},
+ "tesla": {:hex, :tesla, "1.12.0", "170d64a3f566b9b614d60f53b206b2b91db8e29a48fed7553c40baf5b4c5f1fd", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "2fd07d9e3fdb0e39dd2cb16064e28ce02808a26cf33a44d269ef3b1fc20fc914"},
"timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"},
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},
"tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"},
diff --git a/priv/scrubbers/default.ex b/priv/scrubbers/default.ex
index 74de910fd..9f222bce1 100644
--- a/priv/scrubbers/default.ex
+++ b/priv/scrubbers/default.ex
@@ -61,6 +61,34 @@ defmodule Pleroma.HTML.Scrubber.Default do
Meta.allow_tag_with_this_attribute_values(:span, "class", [
"h-card",
"quote-inline",
+ # "FEP-c16b: Formatting MFM functions" tags that Akkoma supports
+ # NOTE: Maybe it would be better to have something like "allow `mfm-*`,
+ # but at moment of writing this is not a thing in the HTML parser we use
+ # The following are the non-animated MFM
+ "mfm-center",
+ "mfm-flip",
+ "mfm-font",
+ "mfm-blur",
+ "mfm-rotate",
+ "mfm-x2",
+ "mfm-x3",
+ "mfm-x4",
+ "mfm-position",
+ "mfm-scale",
+ "mfm-fg",
+ "mfm-bg",
+ # The following are the animated MFM
+ "mfm-jelly",
+ "mfm-twitch",
+ "mfm-shake",
+ "mfm-spin",
+ "mfm-jump",
+ "mfm-bounce",
+ "mfm-rainbow",
+ "mfm-tada",
+ "mfm-sparkle",
+ # MFM legacy
+ # This is for backwards compatibility with posts formatted on Akkoma before support for FEP-c16b
"mfm",
"mfm _mfm_tada_",
"mfm _mfm_jelly_",
@@ -79,6 +107,26 @@ defmodule Pleroma.HTML.Scrubber.Default do
])
Meta.allow_tag_with_these_attributes(:span, [
+ # "FEP-c16b: Formatting MFM functions" attributes that Akkoma supports
+ # NOTE: Maybe it would be better to have something like "allow `data-mfm-*`,
+ # but at moment of writing this is not a thing in the HTML parser we use
+ "data-mfm-h",
+ "data-mfm-v",
+ "data-mfm-x",
+ "data-mfm-y",
+ "data-mfm-alternate",
+ "data-mfm-speed",
+ "data-mfm-deg",
+ "data-mfm-left",
+ "data-mfm-serif",
+ "data-mfm-monospace",
+ "data-mfm-cursive",
+ "data-mfm-fantasy",
+ "data-mfm-emoji",
+ "data-mfm-math",
+ "data-mfm-color",
+ # MFM legacy
+ # This is for backwards compatibility with posts formatted on Akkoma before support for FEP-c16b
"data-x",
"data-y",
"data-h",
From 90adb3cff573313e9490e19b12e44a30755be9a7 Mon Sep 17 00:00:00 2001
From: ilja
Date: Sun, 11 Aug 2024 14:36:34 +0200
Subject: [PATCH 02/95] Fix tests
There was one test who used MFM and now failed due to the new representation. This is now adapted so it doesn't fail any more.
There was another test failing, but I don't see how this could have been affected by the MFM changes...
But I did draw in newer dependencies, so I thought maybe a newer EARMARK dependency was now failing, and indeed.
By explicitly asking for 1.4.46 (according to mix.lock the version it was before), it now works again.
This is what was failing. It seems that earmark 1.4.47 removed everything before the comment, which it should not do.
1) test format_input/3 with markdown raw HTML (Pleroma.Web.CommonAPI.UtilsTest)
test/pleroma/web/common_api/utils_test.exs:213
Assertion with == failed
code: assert result == ~s"OwO"
left: ""
right: "OwO"
stacktrace:
test/pleroma/web/common_api/utils_test.exs:216: (test)
---
mix.exs | 2 +-
mix.lock | 2 +-
.../object_validators/article_note_page_validator_test.exs | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/mix.exs b/mix.exs
index 5bcbc5f4c..697c050c8 100644
--- a/mix.exs
+++ b/mix.exs
@@ -144,7 +144,7 @@ defp deps do
{:ex_aws, "~> 2.4"},
{:ex_aws_s3, "~> 2.4"},
{:sweet_xml, "~> 0.7"},
- {:earmark, "~> 1.4"},
+ {:earmark, "1.4.46"},
{:bbcode_pleroma, "~> 0.2.0"},
{:argon2_elixir, "~> 3.1"},
{:cors_plug, "~> 3.0"},
diff --git a/mix.lock b/mix.lock
index ea2b6e2d5..d703f6ce9 100644
--- a/mix.lock
+++ b/mix.lock
@@ -23,7 +23,7 @@
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
"dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"},
- "earmark": {:hex, :earmark, "1.4.47", "7e7596b84fe4ebeb8751e14cbaeaf4d7a0237708f2ce43630cfd9065551f94ca", [:mix], [], "hexpm", "3e96bebea2c2d95f3b346a7ff22285bc68a99fbabdad9b655aa9c6be06c698f8"},
+ "earmark": {:hex, :earmark, "1.4.46", "8c7287bd3137e99d26ae4643e5b7ef2129a260e3dcf41f251750cb4563c8fb81", [:mix], [], "hexpm", "798d86db3d79964e759ddc0c077d5eb254968ed426399fbf5a62de2b5ff8910a"},
"earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"},
"eblurhash": {:hex, :eblurhash, "1.2.2", "7da4255aaea984b31bb71155f673257353b0e0554d0d30dcf859547e74602582", [:rebar3], [], "hexpm", "8c20ca00904de023a835a9dcb7b7762fed32264c85a80c3cafa85288e405044c"},
"ecto": {:hex, :ecto, "3.10.3", "eb2ae2eecd210b4eb8bece1217b297ad4ff824b4384c0e3fdd28aaf96edd6135", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "44bec74e2364d491d70f7e42cd0d690922659d329f6465e89feb8a34e8cd3433"},
diff --git a/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs
index 4e96f3200..64a322cd5 100644
--- a/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs
+++ b/test/pleroma/web/activity_pub/object_validators/article_note_page_validator_test.exs
@@ -157,7 +157,7 @@ test "a misskey MFM status with a content field should work and be linked", _ do
assert content =~ "@oops_not_a_mention"
assert content =~
- "mfm goes here
aaa"
+ "mfm goes here aaa"
assert content =~ "some text
newline"
end
From f646e78b48f1a7debf318539c596e8ef61be8dbd Mon Sep 17 00:00:00 2001
From: ilja space
Date: Sun, 1 Dec 2024 11:43:37 +0100
Subject: [PATCH 03/95] Add "FEP-c16b: Formatting MFM functions" to
FEDERATION.md
---
FEDERATION.md | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/FEDERATION.md b/FEDERATION.md
index 4524ec4e2..f72ee11d4 100644
--- a/FEDERATION.md
+++ b/FEDERATION.md
@@ -12,6 +12,7 @@
- [FEP-67ff: FEDERATION](https://codeberg.org/fediverse/fep/src/branch/main/fep/67ff/fep-67ff.md)
- [FEP-f1d5: NodeInfo in Fediverse Software](https://codeberg.org/fediverse/fep/src/branch/main/fep/f1d5/fep-f1d5.md)
- [FEP-fffd: Proxy Objects](https://codeberg.org/fediverse/fep/src/branch/main/fep/fffd/fep-fffd.md)
+- [FEP-c16b: Formatting MFM functions](https://codeberg.org/fediverse/fep/src/branch/main/fep/c16b/fep-c16b.md)
## ActivityPub
@@ -30,6 +31,10 @@ Akkoma does not perform JSON-LD processing.
All AP S2S POST requests to Akkoma instances MUST be signed.
Depending on instance configuration the same may be true for GET requests.
+### FEP-c16b: Formatting MFM functions
+
+The optional extension term `htmlMfm` is currently not used.
+
## Nodeinfo
Akkoma provides many additional entries in its nodeinfo response,
From 2624258cfde39341a9af6d4da909850106f2f70b Mon Sep 17 00:00:00 2001
From: ilja space
Date: Mon, 2 Dec 2024 12:35:21 +0100
Subject: [PATCH 04/95] Add "FEP-c16b: Formatting MFM functions" to
CHANGELOG.md
---
CHANGELOG.md | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 74a925a3f..b93732fdf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,19 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
+## UNRELEASED
+
+### BREAKING
+- The HTML content for new posts (both Client-to-Server as well as Server-to-Server communication) will now use a different formatting to represent MFM. See [FEP-c16b](https://codeberg.org/fediverse/fep/src/branch/main/fep/c16b/fep-c16b.md) for more details.
+
+### Fixed
+
+### Changed
+
+### Added
+
+### Removed
+
## 3.13.3
## BREAKING
From c17681ae1e4df46a6626d9d7be58bbfefeefe537 Mon Sep 17 00:00:00 2001
From: "Haelwenn (lanodan) Monnier"
Date: Fri, 5 May 2023 10:53:09 +0200
Subject: [PATCH 05/95] Purge obsolete ap_enabled indicator
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
It was used to migrate OStatus connections to ActivityPub if possible,
but support for OStatus was long since dropped, all new actors always AP
and if anything wasn't migrated before, their instance is already marked
as unreachable anyway.
The associated logic was also buggy in several ways and deleted users
got set to ap_enabled=false also causing some issues.
This patch is a pretty direct port of the original Pleroma MR;
follow-up commits will further fix and clean up remaining issues.
Changes made (other than trivial merge conflict resolutions):
- converted CHANGELOG format
- adapted migration id for Akkoma’s timeline
- removed ap_enabled from additional tests
Ported-from: https://git.pleroma.social/pleroma/pleroma/-/merge_requests/3880
---
CHANGELOG.md | 7 ++
lib/pleroma/user.ex | 13 +---
lib/pleroma/web/activity_pub/activity_pub.ex | 45 ++++++-------
.../object_validators/add_remove_validator.ex | 3 +-
lib/pleroma/web/activity_pub/publisher.ex | 2 -
.../web/activity_pub/transmogrifier.ex | 42 ------------
lib/pleroma/web/federator.ex | 13 +---
lib/pleroma/workers/transmogrifier_worker.ex | 15 -----
.../20241213000000_remove_user_ap_enabled.exs | 13 ++++
test/pleroma/user_test.exs | 11 +---
.../activity_pub_controller_test.exs | 1 -
.../web/activity_pub/activity_pub_test.exs | 1 -
.../web/activity_pub/publisher_test.exs | 18 ++----
.../web/activity_pub/transmogrifier_test.exs | 64 -------------------
test/pleroma/web/common_api_test.exs | 2 +-
test/pleroma/web/federator_test.exs | 6 +-
test/support/factory.ex | 3 +-
17 files changed, 57 insertions(+), 202 deletions(-)
delete mode 100644 lib/pleroma/workers/transmogrifier_worker.ex
create mode 100644 priv/repo/migrations/20241213000000_remove_user_ap_enabled.exs
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 42ed630fb..b0448b0d8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## Unreleased
+## Added
+
+## Fixed
+
+## Changed
+- Dropped obsolete `ap_enabled` indicator from user table and associated buggy logic
+
## 2025.01.01
Hotfix: Federation could break if a null value found its way into `should_federate?\1`
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index dfeab0410..3042d86f2 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -127,7 +127,6 @@ defmodule Pleroma.User do
field(:domain_blocks, {:array, :string}, default: [])
field(:is_active, :boolean, default: true)
field(:no_rich_text, :boolean, default: false)
- field(:ap_enabled, :boolean, default: false)
field(:is_moderator, :boolean, default: false)
field(:is_admin, :boolean, default: false)
field(:show_role, :boolean, default: true)
@@ -473,7 +472,6 @@ def remote_user_changeset(struct \\ %User{local: false}, params) do
:shared_inbox,
:nickname,
:avatar,
- :ap_enabled,
:banner,
:background,
:is_locked,
@@ -1006,11 +1004,7 @@ def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
end
def maybe_direct_follow(%User{} = follower, %User{} = followed) do
- if not ap_enabled?(followed) do
- follow(follower, followed)
- else
- {:ok, follower, followed}
- end
+ {:ok, follower, followed}
end
@doc "A mass follow for local users. Respects blocks in both directions but does not create activities."
@@ -1826,7 +1820,6 @@ def purge_user_changeset(user) do
confirmation_token: nil,
domain_blocks: [],
is_active: false,
- ap_enabled: false,
is_moderator: false,
is_admin: false,
mastofe_settings: nil,
@@ -2073,10 +2066,6 @@ def get_public_key_for_ap_id(ap_id) do
end
end
- def ap_enabled?(%User{local: true}), do: true
- def ap_enabled?(%User{ap_enabled: ap_enabled}), do: ap_enabled
- def ap_enabled?(_), do: false
-
@doc "Gets or fetch a user by uri or nickname."
@spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
def get_or_fetch("http://" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 9b28e64d9..494a27421 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -1626,7 +1626,6 @@ defp object_to_user_data(data, additional) do
%{
ap_id: data["id"],
uri: get_actor_url(data["url"]),
- ap_enabled: true,
banner: normalize_image(data["image"]),
background: normalize_image(data["backgroundUrl"]),
fields: fields,
@@ -1743,7 +1742,7 @@ def user_data_from_user_object(data, additional \\ []) do
end
end
- def fetch_and_prepare_user_from_ap_id(ap_id, additional \\ []) do
+ defp fetch_and_prepare_user_from_ap_id(ap_id, additional) do
with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id),
{:valid, {:ok, _, _}} <- {:valid, UserValidator.validate(data, [])},
{:ok, data} <- user_data_from_user_object(data, additional) do
@@ -1857,31 +1856,27 @@ def enqueue_pin_fetches(_), do: nil
def make_user_from_ap_id(ap_id, additional \\ []) do
user = User.get_cached_by_ap_id(ap_id)
- if user && !User.ap_enabled?(user) do
- Transmogrifier.upgrade_user_from_ap_id(ap_id)
- else
- with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id, additional) do
- user =
- if data.ap_id != ap_id do
- User.get_cached_by_ap_id(data.ap_id)
- else
- user
- end
-
- if user do
- user
- |> User.remote_user_changeset(data)
- |> User.update_and_set_cache()
- |> tap(fn _ -> enqueue_pin_fetches(data) end)
+ with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id, additional) do
+ user =
+ if data.ap_id != ap_id do
+ User.get_cached_by_ap_id(data.ap_id)
else
- maybe_handle_clashing_nickname(data)
-
- data
- |> User.remote_user_changeset()
- |> Repo.insert()
- |> User.set_cache()
- |> tap(fn _ -> enqueue_pin_fetches(data) end)
+ user
end
+
+ if user do
+ user
+ |> User.remote_user_changeset(data)
+ |> User.update_and_set_cache()
+ |> tap(fn _ -> enqueue_pin_fetches(data) end)
+ else
+ maybe_handle_clashing_nickname(data)
+
+ data
+ |> User.remote_user_changeset()
+ |> Repo.insert()
+ |> User.set_cache()
+ |> tap(fn _ -> enqueue_pin_fetches(data) end)
end
end
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex b/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex
index fc482c9c0..b2fa35831 100644
--- a/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex
@@ -73,6 +73,7 @@ defp maybe_refetch_user(%User{featured_address: address} = user) when is_binary(
end
defp maybe_refetch_user(%User{ap_id: ap_id}) do
- Pleroma.Web.ActivityPub.Transmogrifier.upgrade_user_from_ap_id(ap_id)
+ # Maybe it could use User.get_or_fetch_by_ap_id to avoid refreshing too often
+ User.fetch_by_ap_id(ap_id)
end
end
diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex
index 07f430805..056de24bc 100644
--- a/lib/pleroma/web/activity_pub/publisher.ex
+++ b/lib/pleroma/web/activity_pub/publisher.ex
@@ -219,7 +219,6 @@ def publish(%User{} = actor, %{data: %{"bcc" => bcc}} = activity)
inboxes =
recipients
- |> Enum.filter(&User.ap_enabled?/1)
|> Enum.map(fn actor -> actor.inbox end)
|> Enum.filter(fn inbox -> should_federate?(inbox) end)
|> Instances.filter_reachable()
@@ -261,7 +260,6 @@ def publish(%User{} = actor, %Activity{} = activity) do
json = Jason.encode!(data)
recipients(actor, activity)
- |> Enum.filter(fn user -> User.ap_enabled?(user) end)
|> Enum.map(fn %User{} = user ->
determine_inbox(activity, user)
end)
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 5c4db39b9..251ba6540 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -21,7 +21,6 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
alias Pleroma.Web.Federator
- alias Pleroma.Workers.TransmogrifierWorker
import Ecto.Query
@@ -1007,47 +1006,6 @@ defp strip_internal_tags(%{"tag" => tags} = object) do
defp strip_internal_tags(object), do: object
- def perform(:user_upgrade, user) do
- # we pass a fake user so that the followers collection is stripped away
- old_follower_address = User.ap_followers(%User{nickname: user.nickname})
-
- from(
- a in Activity,
- where: ^old_follower_address in a.recipients,
- update: [
- set: [
- recipients:
- fragment(
- "array_replace(?,?,?)",
- a.recipients,
- ^old_follower_address,
- ^user.follower_address
- )
- ]
- ]
- )
- |> Repo.update_all([])
- end
-
- def upgrade_user_from_ap_id(ap_id) do
- with %User{local: false} = user <- User.get_cached_by_ap_id(ap_id),
- {:ok, data} <- ActivityPub.fetch_and_prepare_user_from_ap_id(ap_id),
- {:ok, user} <- update_user(user, data) do
- ActivityPub.enqueue_pin_fetches(user)
- TransmogrifierWorker.enqueue("user_upgrade", %{"user_id" => user.id})
- {:ok, user}
- else
- %User{} = user -> {:ok, user}
- e -> e
- end
- end
-
- defp update_user(user, data) do
- user
- |> User.remote_user_changeset(data)
- |> User.update_and_set_cache()
- end
-
def maybe_fix_user_url(%{"url" => url} = data) when is_map(url) do
Map.put(data, "url", url["href"])
end
diff --git a/lib/pleroma/web/federator.ex b/lib/pleroma/web/federator.ex
index 3a00424c6..aaf89412f 100644
--- a/lib/pleroma/web/federator.ex
+++ b/lib/pleroma/web/federator.ex
@@ -6,7 +6,6 @@ defmodule Pleroma.Web.Federator do
alias Pleroma.Activity
alias Pleroma.Object.Containment
alias Pleroma.User
- alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.Federator.Publisher
@@ -92,7 +91,7 @@ def perform(:incoming_ap_doc, params) do
# NOTE: we use the actor ID to do the containment, this is fine because an
# actor shouldn't be acting on objects outside their own AP server.
- with {_, {:ok, _user}} <- {:actor, ap_enabled_actor(actor)},
+ with {_, {:ok, _user}} <- {:actor, User.get_or_fetch_by_ap_id(actor)},
nil <- Activity.normalize(params["id"]),
{_, :ok} <-
{:correct_origin?, Containment.contain_origin_from_id(actor, params)},
@@ -122,14 +121,4 @@ def perform(:incoming_ap_doc, params) do
{:error, e}
end
end
-
- def ap_enabled_actor(id) do
- user = User.get_cached_by_ap_id(id)
-
- if User.ap_enabled?(user) do
- {:ok, user}
- else
- ActivityPub.make_user_from_ap_id(id)
- end
- end
end
diff --git a/lib/pleroma/workers/transmogrifier_worker.ex b/lib/pleroma/workers/transmogrifier_worker.ex
deleted file mode 100644
index b39c1ea62..000000000
--- a/lib/pleroma/workers/transmogrifier_worker.ex
+++ /dev/null
@@ -1,15 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2021 Pleroma Authors
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Workers.TransmogrifierWorker do
- alias Pleroma.User
-
- use Pleroma.Workers.WorkerHelper, queue: "transmogrifier"
-
- @impl Oban.Worker
- def perform(%Job{args: %{"op" => "user_upgrade", "user_id" => user_id}}) do
- user = User.get_cached_by_id(user_id)
- Pleroma.Web.ActivityPub.Transmogrifier.perform(:user_upgrade, user)
- end
-end
diff --git a/priv/repo/migrations/20241213000000_remove_user_ap_enabled.exs b/priv/repo/migrations/20241213000000_remove_user_ap_enabled.exs
new file mode 100644
index 000000000..0aea41324
--- /dev/null
+++ b/priv/repo/migrations/20241213000000_remove_user_ap_enabled.exs
@@ -0,0 +1,13 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2023 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Repo.Migrations.RemoveUserApEnabled do
+ use Ecto.Migration
+
+ def change do
+ alter table(:users) do
+ remove(:ap_enabled, :boolean, default: false, null: false)
+ end
+ end
+end
diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs
index ac886aaf9..0d5c9faec 100644
--- a/test/pleroma/user_test.exs
+++ b/test/pleroma/user_test.exs
@@ -1694,7 +1694,6 @@ test "delete/1 purges a user when they wouldn't be fully deleted" do
confirmation_token: "qqqq",
domain_blocks: ["lain.com"],
is_active: false,
- ap_enabled: true,
is_moderator: true,
is_admin: true,
mastofe_settings: %{"a" => "b"},
@@ -1734,7 +1733,6 @@ test "delete/1 purges a user when they wouldn't be fully deleted" do
confirmation_token: nil,
domain_blocks: [],
is_active: false,
- ap_enabled: false,
is_moderator: false,
is_admin: false,
mastofe_settings: nil,
@@ -2217,8 +2215,7 @@ test "updates the counters normally on following/getting a follow when disabled"
insert(:user,
local: false,
follower_address: "http://remote.org/users/masto_closed/followers",
- following_address: "http://remote.org/users/masto_closed/following",
- ap_enabled: true
+ following_address: "http://remote.org/users/masto_closed/following"
)
assert other_user.following_count == 0
@@ -2239,8 +2236,7 @@ test "synchronizes the counters with the remote instance for the followed when e
insert(:user,
local: false,
follower_address: "http://remote.org/users/masto_closed/followers",
- following_address: "http://remote.org/users/masto_closed/following",
- ap_enabled: true
+ following_address: "http://remote.org/users/masto_closed/following"
)
assert other_user.following_count == 0
@@ -2261,8 +2257,7 @@ test "synchronizes the counters with the remote instance for the follower when e
insert(:user,
local: false,
follower_address: "http://remote.org/users/masto_closed/followers",
- following_address: "http://remote.org/users/masto_closed/following",
- ap_enabled: true
+ following_address: "http://remote.org/users/masto_closed/following"
)
assert other_user.following_count == 0
diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
index 5b3697244..4cc7f93f5 100644
--- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
@@ -579,7 +579,6 @@ test "it inserts an incoming activity into the database" <>
user =
insert(:user,
ap_id: "https://mastodon.example.org/users/raymoo",
- ap_enabled: true,
local: false,
last_refreshed_at: nil
)
diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs
index c8f93f84d..7990b7ef5 100644
--- a/test/pleroma/web/activity_pub/activity_pub_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_test.exs
@@ -178,7 +178,6 @@ test "it returns a user" do
{:ok, user} = ActivityPub.make_user_from_ap_id(user_id)
assert user.ap_id == user_id
assert user.nickname == "admin@mastodon.example.org"
- assert user.ap_enabled
assert user.follower_address == "http://mastodon.example.org/users/admin/followers"
end
diff --git a/test/pleroma/web/activity_pub/publisher_test.exs b/test/pleroma/web/activity_pub/publisher_test.exs
index 5896568b8..b90422d61 100644
--- a/test/pleroma/web/activity_pub/publisher_test.exs
+++ b/test/pleroma/web/activity_pub/publisher_test.exs
@@ -306,15 +306,13 @@ test "publish to url with with different ports" do
follower =
insert(:user, %{
local: false,
- inbox: "https://domain.com/users/nick1/inbox",
- ap_enabled: true
+ inbox: "https://domain.com/users/nick1/inbox"
})
another_follower =
insert(:user, %{
local: false,
- inbox: "https://rejected.com/users/nick2/inbox",
- ap_enabled: true
+ inbox: "https://rejected.com/users/nick2/inbox"
})
actor =
@@ -386,8 +384,7 @@ test "publish to url with with different ports" do
follower =
insert(:user, %{
local: false,
- inbox: "https://domain.com/users/nick1/inbox",
- ap_enabled: true
+ inbox: "https://domain.com/users/nick1/inbox"
})
actor =
@@ -425,8 +422,7 @@ test "publish to url with with different ports" do
follower =
insert(:user, %{
local: false,
- inbox: "https://domain.com/users/nick1/inbox",
- ap_enabled: true
+ inbox: "https://domain.com/users/nick1/inbox"
})
actor = insert(:user, follower_address: follower.ap_id)
@@ -461,15 +457,13 @@ test "publish to url with with different ports" do
fetcher =
insert(:user,
local: false,
- inbox: "https://domain.com/users/nick1/inbox",
- ap_enabled: true
+ inbox: "https://domain.com/users/nick1/inbox"
)
another_fetcher =
insert(:user,
local: false,
- inbox: "https://domain2.com/users/nick1/inbox",
- ap_enabled: true
+ inbox: "https://domain2.com/users/nick1/inbox"
)
actor = insert(:user)
diff --git a/test/pleroma/web/activity_pub/transmogrifier_test.exs b/test/pleroma/web/activity_pub/transmogrifier_test.exs
index 1be69317c..94df25100 100644
--- a/test/pleroma/web/activity_pub/transmogrifier_test.exs
+++ b/test/pleroma/web/activity_pub/transmogrifier_test.exs
@@ -8,7 +8,6 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
@moduletag :mocked
alias Pleroma.Activity
alias Pleroma.Object
- alias Pleroma.Tests.ObanHelpers
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Utils
@@ -348,69 +347,6 @@ test "Updates of Notes are handled" do
end
end
- describe "user upgrade" do
- test "it upgrades a user to activitypub" do
- user =
- insert(:user, %{
- nickname: "rye@niu.moe",
- local: false,
- ap_id: "https://niu.moe/users/rye",
- follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})
- })
-
- user_two = insert(:user)
- Pleroma.FollowingRelationship.follow(user_two, user, :follow_accept)
-
- {:ok, activity} = CommonAPI.post(user, %{status: "test"})
- {:ok, unrelated_activity} = CommonAPI.post(user_two, %{status: "test"})
- assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients
-
- user = User.get_cached_by_id(user.id)
- assert user.note_count == 1
-
- {:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye")
- ObanHelpers.perform_all()
-
- assert user.ap_enabled
- assert user.note_count == 1
- assert user.follower_address == "https://niu.moe/users/rye/followers"
- assert user.following_address == "https://niu.moe/users/rye/following"
-
- user = User.get_cached_by_id(user.id)
- assert user.note_count == 1
-
- activity = Activity.get_by_id(activity.id)
- assert user.follower_address in activity.recipients
-
- assert %{
- "url" => [
- %{
- "href" =>
- "https://cdn.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
- }
- ]
- } = user.avatar
-
- assert %{
- "url" => [
- %{
- "href" =>
- "https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
- }
- ]
- } = user.banner
-
- refute "..." in activity.recipients
-
- unrelated_activity = Activity.get_by_id(unrelated_activity.id)
- refute user.follower_address in unrelated_activity.recipients
-
- user_two = User.get_cached_by_id(user_two.id)
- assert User.following?(user_two, user)
- refute "..." in User.following(user_two)
- end
- end
-
describe "actor rewriting" do
test "it fixes the actor URL property to be a proper URI" do
data = %{
diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs
index 379cf85b8..b32334389 100644
--- a/test/pleroma/web/common_api_test.exs
+++ b/test/pleroma/web/common_api_test.exs
@@ -1129,7 +1129,7 @@ test "removes a pending follow for a local user" do
test "cancels a pending follow for a remote user" do
follower = insert(:user)
- followed = insert(:user, is_locked: true, local: false, ap_enabled: true)
+ followed = insert(:user, is_locked: true, local: false)
assert {:ok, follower, followed, %{id: _activity_id, data: %{"state" => "pending"}}} =
CommonAPI.follow(follower, followed)
diff --git a/test/pleroma/web/federator_test.exs b/test/pleroma/web/federator_test.exs
index d3cc239cf..c9a13632a 100644
--- a/test/pleroma/web/federator_test.exs
+++ b/test/pleroma/web/federator_test.exs
@@ -79,16 +79,14 @@ test "it federates only to reachable instances via AP" do
local: false,
nickname: "nick1@domain.com",
ap_id: "https://domain.com/users/nick1",
- inbox: inbox1,
- ap_enabled: true
+ inbox: inbox1
})
insert(:user, %{
local: false,
nickname: "nick2@domain2.com",
ap_id: "https://domain2.com/users/nick2",
- inbox: inbox2,
- ap_enabled: true
+ inbox: inbox2
})
dt = NaiveDateTime.utc_now()
diff --git a/test/support/factory.ex b/test/support/factory.ex
index 54e5f91b7..7797fc9b8 100644
--- a/test/support/factory.ex
+++ b/test/support/factory.ex
@@ -62,8 +62,7 @@ def user_factory(attrs \\ %{}) do
last_digest_emailed_at: NaiveDateTime.utc_now(),
last_refreshed_at: NaiveDateTime.utc_now(),
notification_settings: %Pleroma.User.NotificationSetting{},
- multi_factor_authentication_settings: %Pleroma.MFA.Settings{},
- ap_enabled: true
+ multi_factor_authentication_settings: %Pleroma.MFA.Settings{}
}
urls =
From 0f4a7a185f85e3a2a2965f41949a7f1348b53e22 Mon Sep 17 00:00:00 2001
From: Oneric
Date: Fri, 13 Dec 2024 01:06:18 +0100
Subject: [PATCH 06/95] Drop ap_enabled indicator from atom feeds
---
lib/pleroma/web/templates/feed/feed/_author.atom.eex | 3 ---
lib/pleroma/web/templates/feed/feed/_author.rss.eex | 3 ---
lib/pleroma/web/templates/feed/feed/_tag_author.atom.eex | 3 ---
3 files changed, 9 deletions(-)
diff --git a/lib/pleroma/web/templates/feed/feed/_author.atom.eex b/lib/pleroma/web/templates/feed/feed/_author.atom.eex
index 25cbffada..309d37dc7 100644
--- a/lib/pleroma/web/templates/feed/feed/_author.atom.eex
+++ b/lib/pleroma/web/templates/feed/feed/_author.atom.eex
@@ -11,7 +11,4 @@
<%= if User.banner_url(@user) do %>
<% end %>
- <%= if @user.local do %>
- true
- <% end %>
diff --git a/lib/pleroma/web/templates/feed/feed/_author.rss.eex b/lib/pleroma/web/templates/feed/feed/_author.rss.eex
index 526aeddcf..6eb82af67 100644
--- a/lib/pleroma/web/templates/feed/feed/_author.rss.eex
+++ b/lib/pleroma/web/templates/feed/feed/_author.rss.eex
@@ -11,7 +11,4 @@
<%= if User.banner_url(@user) do %>
<%= User.banner_url(@user) %>
<% end %>
- <%= if @user.local do %>
- true
- <% end %>
diff --git a/lib/pleroma/web/templates/feed/feed/_tag_author.atom.eex b/lib/pleroma/web/templates/feed/feed/_tag_author.atom.eex
index 997c4936e..35c686089 100644
--- a/lib/pleroma/web/templates/feed/feed/_tag_author.atom.eex
+++ b/lib/pleroma/web/templates/feed/feed/_tag_author.atom.eex
@@ -8,9 +8,6 @@
<%= if User.banner_url(@actor) do %>
<% end %>
- <%= if @actor.local do %>
- true
- <% end %>
<%= @actor.nickname %>
<%= @actor.name %>
From 4859f386246f0206d60f1279f94c5d4b69ddd09f Mon Sep 17 00:00:00 2001
From: Oneric
Date: Fri, 13 Dec 2024 01:09:35 +0100
Subject: [PATCH 07/95] add_remove_validator: limit refetch rate to 1 per 5s
This matches the maximum_age used when processing Move activities
---
.../activity_pub/object_validators/add_remove_validator.ex | 6 ++++--
.../transmogrifier/add_remove_handling_test.exs | 1 +
2 files changed, 5 insertions(+), 2 deletions(-)
diff --git a/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex b/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex
index b2fa35831..c13f7d442 100644
--- a/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex
@@ -73,7 +73,9 @@ defp maybe_refetch_user(%User{featured_address: address} = user) when is_binary(
end
defp maybe_refetch_user(%User{ap_id: ap_id}) do
- # Maybe it could use User.get_or_fetch_by_ap_id to avoid refreshing too often
- User.fetch_by_ap_id(ap_id)
+ # If the user didn't expose a featured collection before,
+ # recheck now so we can verify perms for add/remove.
+ # But wait at least 5s to avoid rapid refetches in edge cases
+ User.get_or_fetch_by_ap_id(ap_id, maximum_age: 5)
end
end
diff --git a/test/pleroma/web/activity_pub/transmogrifier/add_remove_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/add_remove_handling_test.exs
index c2b5f2cc8..f95d298e0 100644
--- a/test/pleroma/web/activity_pub/transmogrifier/add_remove_handling_test.exs
+++ b/test/pleroma/web/activity_pub/transmogrifier/add_remove_handling_test.exs
@@ -102,6 +102,7 @@ test "Add/Remove activities for remote users without featured address" do
user =
user
|> Ecto.Changeset.change(featured_address: nil)
+ |> Ecto.Changeset.change(last_refreshed_at: ~N[2013-03-14 11:50:00.000000])
|> Repo.update!()
%{host: host} = URI.parse(user.ap_id)
From ead44c6671c55f55d565decee68434b699f4a99b Mon Sep 17 00:00:00 2001
From: Oneric
Date: Fri, 13 Dec 2024 01:10:26 +0100
Subject: [PATCH 08/95] federator: don't fetch the user for no reason
The return value is never used here; later stages which actually need it
fetch the user themselves and it doesn't matter wheter we wait for the
fech here or later (if needed at all).
Even more, this early fetch always fails if the user was already deleted
or never known to begin with, but we get something referencing it; e.g.
the very Delete action carrying out the user deletion.
This prevents processing of the Delete, but before that it will be
reattempted several times, each time attempring to fetch the
non-existing profile, wasting resources.
---
lib/pleroma/web/federator.ex | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/lib/pleroma/web/federator.ex b/lib/pleroma/web/federator.ex
index aaf89412f..adf8298da 100644
--- a/lib/pleroma/web/federator.ex
+++ b/lib/pleroma/web/federator.ex
@@ -91,8 +91,7 @@ def perform(:incoming_ap_doc, params) do
# NOTE: we use the actor ID to do the containment, this is fine because an
# actor shouldn't be acting on objects outside their own AP server.
- with {_, {:ok, _user}} <- {:actor, User.get_or_fetch_by_ap_id(actor)},
- nil <- Activity.normalize(params["id"]),
+ with nil <- Activity.normalize(params["id"]),
{_, :ok} <-
{:correct_origin?, Containment.contain_origin_from_id(actor, params)},
{:ok, activity} <- Transmogrifier.handle_incoming(params) do
From 25d24cc5f65aa13b22ad15dcd486083e4c355cfe Mon Sep 17 00:00:00 2001
From: Oneric
Date: Sat, 14 Dec 2024 02:02:04 +0000
Subject: [PATCH 09/95] validators/add_remove: don't crash on failure to
resolve reference
It allows for informed error handling and retry/discard job
decisions lateron which a future commit will add.
---
.../object_validators/add_remove_validator.ex | 22 +++++++++++++------
1 file changed, 15 insertions(+), 7 deletions(-)
diff --git a/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex b/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex
index c13f7d442..47f1c8618 100644
--- a/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/add_remove_validator.ex
@@ -9,6 +9,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AddRemoveValidator do
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
require Pleroma.Constants
+ require Logger
alias Pleroma.User
@@ -27,14 +28,21 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AddRemoveValidator do
end
def cast_and_validate(data) do
- {:ok, actor} = User.get_or_fetch_by_ap_id(data["actor"])
+ with {_, {:ok, actor}} <- {:user, User.get_or_fetch_by_ap_id(data["actor"])},
+ {_, {:ok, actor}} <- {:feataddr, maybe_refetch_user(actor)} do
+ data
+ |> maybe_fix_data_for_mastodon(actor)
+ |> cast_data()
+ |> validate_data(actor)
+ else
+ {:feataddr, _} ->
+ {:error,
+ {:validate,
+ "Actor doesn't provide featured collection address to verify against: #{data["id"]}"}}
- {:ok, actor} = maybe_refetch_user(actor)
-
- data
- |> maybe_fix_data_for_mastodon(actor)
- |> cast_data()
- |> validate_data(actor)
+ {:user, _} ->
+ {:error, :link_resolve_failed}
+ end
end
defp maybe_fix_data_for_mastodon(data, actor) do
From ed4019e7a36b9049fadf96ca3ca9157ca29ba1a0 Mon Sep 17 00:00:00 2001
From: Oneric
Date: Tue, 29 Oct 2024 00:18:15 +0100
Subject: [PATCH 10/95] workers: make custom filtering ahead of enqueue
possible
---
lib/pleroma/workers/worker_helper.ex | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/lib/pleroma/workers/worker_helper.ex b/lib/pleroma/workers/worker_helper.ex
index 6d27151de..ea9ce9d3b 100644
--- a/lib/pleroma/workers/worker_helper.ex
+++ b/lib/pleroma/workers/worker_helper.ex
@@ -38,7 +38,7 @@ defmacro __using__(opts) do
alias Oban.Job
- def enqueue(op, params, worker_args \\ []) do
+ defp do_enqueue(op, params, worker_args \\ []) do
params = Map.merge(%{"op" => op}, params)
queue_atom = String.to_atom(unquote(queue))
worker_args = worker_args ++ WorkerHelper.worker_args(queue_atom)
@@ -48,11 +48,16 @@ def enqueue(op, params, worker_args \\ []) do
|> Oban.insert()
end
+ def enqueue(op, params, worker_args \\ []),
+ do: do_enqueue(op, params, worker_args)
+
@impl Oban.Worker
def timeout(_job) do
queue_atom = String.to_atom(unquote(queue))
Config.get([:workers, :timeout, queue_atom], :timer.minutes(1))
end
+
+ defoverridable enqueue: 3
end
end
end
From d283ac52c34ec0d75ac344dca60f689298cce9cd Mon Sep 17 00:00:00 2001
From: Oneric
Date: Tue, 29 Oct 2024 01:41:27 +0100
Subject: [PATCH 11/95] Don't create noop SearchIndexingWorker jobs for passive
index
---
lib/pleroma/search/database_search.ex | 6 ----
lib/pleroma/search/search_backend.ex | 2 ++
lib/pleroma/workers/search_indexing_worker.ex | 29 ++++++++++++++-----
3 files changed, 24 insertions(+), 13 deletions(-)
diff --git a/lib/pleroma/search/database_search.ex b/lib/pleroma/search/database_search.ex
index 3dc6245e8..0ded3cb17 100644
--- a/lib/pleroma/search/database_search.ex
+++ b/lib/pleroma/search/database_search.ex
@@ -40,12 +40,6 @@ def search(user, search_query, options \\ []) do
end
end
- @impl true
- def add_to_index(_activity), do: nil
-
- @impl true
- def remove_from_index(_object), do: nil
-
def maybe_restrict_author(query, %User{} = author) do
Activity.Queries.by_author(query, author)
end
diff --git a/lib/pleroma/search/search_backend.ex b/lib/pleroma/search/search_backend.ex
index 56e3b7de5..f7a1b55b9 100644
--- a/lib/pleroma/search/search_backend.ex
+++ b/lib/pleroma/search/search_backend.ex
@@ -14,4 +14,6 @@ defmodule Pleroma.Search.SearchBackend do
from index.
"""
@callback remove_from_index(object :: Pleroma.Object.t()) :: {:ok, any()} | {:error, any()}
+
+ @optional_callbacks add_to_index: 1, remove_from_index: 1
end
diff --git a/lib/pleroma/workers/search_indexing_worker.ex b/lib/pleroma/workers/search_indexing_worker.ex
index 518a44c0a..cd6bee1b5 100644
--- a/lib/pleroma/workers/search_indexing_worker.ex
+++ b/lib/pleroma/workers/search_indexing_worker.ex
@@ -1,23 +1,38 @@
defmodule Pleroma.Workers.SearchIndexingWorker do
use Pleroma.Workers.WorkerHelper, queue: "search_indexing"
- @impl Oban.Worker
+ defp search_module(), do: Pleroma.Config.get!([Pleroma.Search, :module])
+ def enqueue("add_to_index", params, worker_args) do
+ if Kernel.function_exported?(search_module(), :add_to_index, 1) do
+ do_enqueue("add_to_index", params, worker_args)
+ else
+ # XXX: or {:ok, nil} to more closely match Oban.inset()'s {:ok, job}?
+ # or similar to unique coflict: %Oban.Job{conflict?: true} (but omitting all other fileds...)
+ :ok
+ end
+ end
+
+ def enqueue("remove_from_index", params, worker_args) do
+ if Kernel.function_exported?(search_module(), :remove_from_index, 1) do
+ do_enqueue("remove_from_index", params, worker_args)
+ else
+ :ok
+ end
+ end
+
+ @impl Oban.Worker
def perform(%Job{args: %{"op" => "add_to_index", "activity" => activity_id}}) do
activity = Pleroma.Activity.get_by_id_with_object(activity_id)
- search_module = Pleroma.Config.get([Pleroma.Search, :module])
-
- search_module.add_to_index(activity)
+ search_module().add_to_index(activity)
:ok
end
def perform(%Job{args: %{"op" => "remove_from_index", "object" => object_id}}) do
- search_module = Pleroma.Config.get([Pleroma.Search, :module])
-
# Fake the object so we can remove it from the index without having to keep it in the DB
- search_module.remove_from_index(%Pleroma.Object{id: object_id})
+ search_module().remove_from_index(%Pleroma.Object{id: object_id})
:ok
end
From 92544e8f99658be53161caa8c4b5498ae2271d10 Mon Sep 17 00:00:00 2001
From: Oneric
Date: Tue, 29 Oct 2024 01:41:54 +0100
Subject: [PATCH 12/95] Don't enqueue a plethora of unnecessary NodeInfoFetcher
jobs
There were two issues leading to needles effort:
Most importnatly, the use of AP IDs as "source_url" meant multiple
simultaneous jobs got scheduled for the same instance even with the
default unique settings.
Also jobs were scheduled uncontionally for each processed AP object
meaning we incured oberhead from managing Oban jobs even if we knew it
wasn't necessary. By comparison the single query to check if an update
is needed should be cheaper overall.
---
lib/pleroma/instances/instance.ex | 8 +++++++
.../workers/nodeinfo_fetcher_worker.ex | 23 ++++++++++++++++++-
.../web/activity_pub/side_effects_test.exs | 2 +-
3 files changed, 31 insertions(+), 2 deletions(-)
diff --git a/lib/pleroma/instances/instance.ex b/lib/pleroma/instances/instance.ex
index 5c70748b6..63362eb28 100644
--- a/lib/pleroma/instances/instance.ex
+++ b/lib/pleroma/instances/instance.ex
@@ -158,6 +158,14 @@ def needs_update(%Instance{metadata_updated_at: metadata_updated_at}) do
NaiveDateTime.diff(now, metadata_updated_at) > 86_400
end
+ def needs_update(%URI{host: host}) do
+ with %Instance{} = instance <- Repo.get_by(Instance, %{host: host}) do
+ needs_update(instance)
+ else
+ _ -> true
+ end
+ end
+
def local do
%Instance{
host: Pleroma.Web.Endpoint.host(),
diff --git a/lib/pleroma/workers/nodeinfo_fetcher_worker.ex b/lib/pleroma/workers/nodeinfo_fetcher_worker.ex
index 27492e1e3..32907bac9 100644
--- a/lib/pleroma/workers/nodeinfo_fetcher_worker.ex
+++ b/lib/pleroma/workers/nodeinfo_fetcher_worker.ex
@@ -1,9 +1,30 @@
defmodule Pleroma.Workers.NodeInfoFetcherWorker do
- use Pleroma.Workers.WorkerHelper, queue: "nodeinfo_fetcher"
+ use Pleroma.Workers.WorkerHelper,
+ queue: "nodeinfo_fetcher",
+ unique: [
+ keys: [:op, :source_url],
+ # old jobs still get pruned after a short while
+ period: :infinity,
+ states: Oban.Job.states()
+ ]
alias Oban.Job
alias Pleroma.Instances.Instance
+ def enqueue(op, %{"source_url" => ap_id} = params, worker_args) do
+ # reduce to base url to avoid enqueueing unneccessary duplicates
+ domain =
+ ap_id
+ |> URI.parse()
+ |> URI.merge("/")
+
+ if Instance.needs_update(domain) do
+ do_enqueue(op, %{params | "source_url" => URI.to_string(domain)}, worker_args)
+ else
+ :ok
+ end
+ end
+
@impl Oban.Worker
def perform(%Job{
args: %{"op" => "process", "source_url" => domain}
diff --git a/test/pleroma/web/activity_pub/side_effects_test.exs b/test/pleroma/web/activity_pub/side_effects_test.exs
index 28a591d3c..64a1fe6e6 100644
--- a/test/pleroma/web/activity_pub/side_effects_test.exs
+++ b/test/pleroma/web/activity_pub/side_effects_test.exs
@@ -46,7 +46,7 @@ test "it queues a fetch of instance information" do
assert_enqueued(
worker: Pleroma.Workers.NodeInfoFetcherWorker,
- args: %{"op" => "process", "source_url" => "https://wowee.example.com/users/1"}
+ args: %{"op" => "process", "source_url" => "https://wowee.example.com/"}
)
end
end
From 280652651c96954e383ccc60065b9b5eb4542a8c Mon Sep 17 00:00:00 2001
From: Oneric
Date: Sat, 23 Nov 2024 19:04:11 +0100
Subject: [PATCH 13/95] rich_media: don't reattempt parsing on rejected URLs
---
lib/pleroma/web/rich_media/backfill.ex | 4 +++
lib/pleroma/web/rich_media/parser.ex | 15 ++++++------
test/pleroma/web/rich_media/parser_test.exs | 27 ++++++++++++++++-----
3 files changed, 33 insertions(+), 13 deletions(-)
diff --git a/lib/pleroma/web/rich_media/backfill.ex b/lib/pleroma/web/rich_media/backfill.ex
index 6b2373b01..8c54a0916 100644
--- a/lib/pleroma/web/rich_media/backfill.ex
+++ b/lib/pleroma/web/rich_media/backfill.ex
@@ -57,6 +57,10 @@ def run(%{"url" => url, "url_hash" => url_hash} = args) do
Logger.debug("Rich media error for #{url}: :content_type is #{type}")
negative_cache(url_hash, :timer.minutes(30))
+ {:error, {:url, reason}} ->
+ Logger.debug("Rich media error for #{url}: refusing URL #{inspect(reason)}")
+ negative_cache(url_hash, :timer.minutes(180))
+
e ->
Logger.debug("Rich media error for #{url}: #{inspect(e)}")
{:error, e}
diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex
index 7f6b5d388..7c5fed2bf 100644
--- a/lib/pleroma/web/rich_media/parser.ex
+++ b/lib/pleroma/web/rich_media/parser.ex
@@ -16,12 +16,13 @@ def parse(nil), do: nil
@spec parse(String.t()) :: {:ok, map()} | {:error, any()}
def parse(url) do
with {_, true} <- {:config, @config_impl.get([:rich_media, :enabled])},
- :ok <- validate_page_url(url),
+ {_, :ok} <- {:url, validate_page_url(url)},
{:ok, data} <- parse_url(url) do
data = Map.put(data, "url", url)
{:ok, data}
else
{:config, _} -> {:error, :rich_media_disabled}
+ {:url, {:error, reason}} -> {:error, {:url, reason}}
e -> e
end
end
@@ -62,7 +63,7 @@ defp clean_parsed_data(data) do
|> Map.new()
end
- @spec validate_page_url(URI.t() | binary()) :: :ok | :error
+ @spec validate_page_url(URI.t() | binary()) :: :ok | {:error, term()}
defp validate_page_url(page_url) when is_binary(page_url) do
validate_tld = @config_impl.get([Pleroma.Formatter, :validate_tld])
@@ -74,20 +75,20 @@ defp validate_page_url(page_url) when is_binary(page_url) do
defp validate_page_url(%URI{host: host, scheme: "https"}) do
cond do
Linkify.Parser.ip?(host) ->
- :error
+ {:error, :ip}
host in @config_impl.get([:rich_media, :ignore_hosts], []) ->
- :error
+ {:error, :ignore_hosts}
get_tld(host) in @config_impl.get([:rich_media, :ignore_tld], []) ->
- :error
+ {:error, :ignore_tld}
true ->
:ok
end
end
- defp validate_page_url(_), do: :error
+ defp validate_page_url(_), do: {:error, "scheme mismatch"}
defp parse_uri(true, url) do
url
@@ -95,7 +96,7 @@ defp parse_uri(true, url) do
|> validate_page_url
end
- defp parse_uri(_, _), do: :error
+ defp parse_uri(_, _), do: {:error, "not an URL"}
defp get_tld(host) do
host
diff --git a/test/pleroma/web/rich_media/parser_test.exs b/test/pleroma/web/rich_media/parser_test.exs
index a5f2563a2..bf7864aa7 100644
--- a/test/pleroma/web/rich_media/parser_test.exs
+++ b/test/pleroma/web/rich_media/parser_test.exs
@@ -109,25 +109,40 @@ test "does a HEAD request to check if the body is html" do
test "refuses to crawl incomplete URLs" do
url = "example.com/ogp"
- assert :error == Parser.parse(url)
+ assert {:error, {:url, "scheme mismatch"}} == Parser.parse(url)
+ end
+
+ test "refuses to crawl plain HTTP and other scheme URL" do
+ [
+ "http://example.com/ogp",
+ "ftp://example.org/dist/"
+ ]
+ |> Enum.each(fn url ->
+ res = Parser.parse(url)
+
+ assert {:error, {:url, "scheme mismatch"}} == res or
+ {:error, {:url, "not an URL"}} == res
+ end)
end
test "refuses to crawl malformed URLs" do
url = "example.com[]/ogp"
- assert :error == Parser.parse(url)
+ assert {:error, {:url, "not an URL"}} == Parser.parse(url)
end
test "refuses to crawl URLs of private network from posts" do
[
- "http://127.0.0.1:4000/notice/9kCP7VNyPJXFOXDrgO",
+ "https://127.0.0.1:4000/notice/9kCP7VNyPJXFOXDrgO",
"https://10.111.10.1/notice/9kCP7V",
"https://172.16.32.40/notice/9kCP7V",
- "https://192.168.10.40/notice/9kCP7V",
- "https://pleroma.local/notice/9kCP7V"
+ "https://192.168.10.40/notice/9kCP7V"
]
|> Enum.each(fn url ->
- assert :error == Parser.parse(url)
+ assert {:error, {:url, :ip}} == Parser.parse(url)
end)
+
+ url = "https://pleroma.local/notice/9kCP7V"
+ assert {:error, {:url, :ignore_tld}} == Parser.parse(url)
end
test "returns error when disabled" do
From 041dedb86e3ae65b648ab6d3839aff72d3366e0b Mon Sep 17 00:00:00 2001
From: Oneric
Date: Tue, 29 Oct 2024 01:59:41 +0100
Subject: [PATCH 14/95] Don't reattempt RichMediaBackfill by default
Retrying seems unlikely to be helpful:
- if it timed out, chances are the short delay before reattempting
won't give the remote enough time to recover from its outage and
a longer delay makes the job pointless as users likely scrolled
further already. (Likely this is already be the case after the
first 20s timeout)
- if the remote data is so borked we couldn't even parse it far enough
for an "invalid metadata" error, chances are it will remain borked
upon reattempt
---
config/config.exs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/config/config.exs b/config/config.exs
index 03e373d8f..5a6169506 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -602,7 +602,7 @@
federator_incoming: 5,
federator_outgoing: 5,
search_indexing: 2,
- rich_media_backfill: 3
+ rich_media_backfill: 1
],
timeout: [
activity_expiration: :timer.seconds(5),
From f9724b58795a33817479508c3a6a6e637ebe6096 Mon Sep 17 00:00:00 2001
From: Oneric
Date: Tue, 29 Oct 2024 01:47:54 +0100
Subject: [PATCH 15/95] =?UTF-8?q?Don=E2=80=99t=20reattempt=20insertion=20o?=
=?UTF-8?q?f=20already=20known=20objects?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Might happen if we receive e.g. a Like before the Note arrives
in our inbox and we thus already queried the Note ourselves.
---
lib/pleroma/workers/receiver_worker.ex | 1 +
test/pleroma/web/federator_test.exs | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/lib/pleroma/workers/receiver_worker.ex b/lib/pleroma/workers/receiver_worker.ex
index a663a63fe..453400e6b 100644
--- a/lib/pleroma/workers/receiver_worker.ex
+++ b/lib/pleroma/workers/receiver_worker.ex
@@ -14,6 +14,7 @@ def perform(%Job{args: %{"op" => "incoming_ap_doc", "params" => params}}) do
else
{:error, :origin_containment_failed} -> {:discard, :origin_containment_failed}
{:error, {:reject, reason}} -> {:discard, reason}
+ {:error, :already_present} -> {:discard, :already_present}
{:error, _} = e -> e
e -> {:error, e}
end
diff --git a/test/pleroma/web/federator_test.exs b/test/pleroma/web/federator_test.exs
index c9a13632a..30da35669 100644
--- a/test/pleroma/web/federator_test.exs
+++ b/test/pleroma/web/federator_test.exs
@@ -132,7 +132,7 @@ test "successfully processes incoming AP docs with correct origin" do
assert {:ok, _activity} = ObanHelpers.perform(job)
assert {:ok, job} = Federator.incoming_ap_doc(params)
- assert {:error, :already_present} = ObanHelpers.perform(job)
+ assert {:discard, :already_present} = ObanHelpers.perform(job)
end
test "successfully normalises public scope descriptors" do
From 9f4d3a936f161fb80f84490c3482992b78d68de9 Mon Sep 17 00:00:00 2001
From: Oneric
Date: Sat, 23 Nov 2024 22:44:37 +0100
Subject: [PATCH 16/95] cosmetic/receiver_worker: reformat error cases
The next commit adds a multi-statement case
and then mix format will enforce this anyway
---
lib/pleroma/workers/receiver_worker.ex | 19 ++++++++++++++-----
1 file changed, 14 insertions(+), 5 deletions(-)
diff --git a/lib/pleroma/workers/receiver_worker.ex b/lib/pleroma/workers/receiver_worker.ex
index 453400e6b..b28442042 100644
--- a/lib/pleroma/workers/receiver_worker.ex
+++ b/lib/pleroma/workers/receiver_worker.ex
@@ -12,11 +12,20 @@ def perform(%Job{args: %{"op" => "incoming_ap_doc", "params" => params}}) do
with {:ok, res} <- Federator.perform(:incoming_ap_doc, params) do
{:ok, res}
else
- {:error, :origin_containment_failed} -> {:discard, :origin_containment_failed}
- {:error, {:reject, reason}} -> {:discard, reason}
- {:error, :already_present} -> {:discard, :already_present}
- {:error, _} = e -> e
- e -> {:error, e}
+ {:error, :origin_containment_failed} ->
+ {:discard, :origin_containment_failed}
+
+ {:error, {:reject, reason}} ->
+ {:discard, reason}
+
+ {:error, :already_present} ->
+ {:discard, :already_present}
+
+ {:error, _} = e ->
+ e
+
+ e ->
+ {:error, e}
end
end
end
From be2c857845bc2fdfe10d4217136fea9cfed7f4e1 Mon Sep 17 00:00:00 2001
From: Oneric
Date: Sat, 23 Nov 2024 22:50:20 +0100
Subject: [PATCH 17/95] receiver_worker: don't reattempt invalid documents
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Ideally we’d like to split this up more and count most invalid documents
as an error, but silently drop e.g. Deletes for unknown objects.
However, this is hard to extract from the changeset and jobs canceled
with :discard don’t count as exceptions and I’m not aware of a idiomatic
way to cancel further retries while retaining the exception status.
Thus at least keep a log, but since superfluous "Delete"s
seem kinda frequent, don't log at error, only info level.
---
lib/pleroma/workers/receiver_worker.ex | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/lib/pleroma/workers/receiver_worker.ex b/lib/pleroma/workers/receiver_worker.ex
index b28442042..193b500f4 100644
--- a/lib/pleroma/workers/receiver_worker.ex
+++ b/lib/pleroma/workers/receiver_worker.ex
@@ -3,6 +3,8 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.ReceiverWorker do
+ require Logger
+
alias Pleroma.Web.Federator
use Pleroma.Workers.WorkerHelper, queue: "federator_incoming"
@@ -21,6 +23,16 @@ def perform(%Job{args: %{"op" => "incoming_ap_doc", "params" => params}}) do
{:error, :already_present} ->
{:discard, :already_present}
+ # invalid data or e.g. deleting an object we don't know about anyway
+ {:error, {:error, {:validate, issue}}} ->
+ Logger.info("Received invalid AP document: #{inspect(issue)}")
+ {:discard, :invalid}
+
+ # rarer, but sometimes there’s an additional :error in front
+ {:error, {:error, {:error, {:validate, issue}}}} ->
+ Logger.info("Received invalid AP document: (3e) #{inspect(issue)}")
+ {:discard, :invalid}
+
{:error, _} = e ->
e
From cbb0d4b0a85429a32dc3e80dd70fafe40ef4209c Mon Sep 17 00:00:00 2001
From: Oneric
Date: Wed, 4 Dec 2024 01:59:02 +0100
Subject: [PATCH 18/95] receiver_worker: log unecpected errors
This can't handle process crash errors
but i hope those get a stacktrace logged by default
---
lib/pleroma/workers/receiver_worker.ex | 2 ++
1 file changed, 2 insertions(+)
diff --git a/lib/pleroma/workers/receiver_worker.ex b/lib/pleroma/workers/receiver_worker.ex
index 193b500f4..6a8f89a03 100644
--- a/lib/pleroma/workers/receiver_worker.ex
+++ b/lib/pleroma/workers/receiver_worker.ex
@@ -34,9 +34,11 @@ def perform(%Job{args: %{"op" => "incoming_ap_doc", "params" => params}}) do
{:discard, :invalid}
{:error, _} = e ->
+ Logger.error("Unexpected AP doc error: #{inspect(e)} from #{inspect(params)}")
e
e ->
+ Logger.error("Unexpected AP doc error: (raw) #{inspect(e)} from #{inspect(params)}")
{:error, e}
end
end
From 8b5183cb7428a305344acfe797889d7fab67436a Mon Sep 17 00:00:00 2001
From: Oneric
Date: Sun, 24 Nov 2024 17:45:08 +0100
Subject: [PATCH 19/95] stats: fix stat spec
---
lib/pleroma/stats.ex | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/lib/pleroma/stats.ex b/lib/pleroma/stats.ex
index c47a0f9de..88740d155 100644
--- a/lib/pleroma/stats.ex
+++ b/lib/pleroma/stats.ex
@@ -39,7 +39,8 @@ def force_update do
@spec get_stats() :: %{
domain_count: non_neg_integer(),
status_count: non_neg_integer(),
- user_count: non_neg_integer()
+ user_count: non_neg_integer(),
+ remote_user_count: non_neg_integer()
}
def get_stats do
%{stats: stats} = GenServer.call(__MODULE__, :get_state)
@@ -60,7 +61,8 @@ def get_peers do
stats: %{
domain_count: non_neg_integer(),
status_count: non_neg_integer(),
- user_count: non_neg_integer()
+ user_count: non_neg_integer(),
+ remote_user_count: non_neg_integer()
}
}
def calculate_stat_data do
From 138b1aea2f4b80574156bf6cf7e108528086036d Mon Sep 17 00:00:00 2001
From: Oneric
Date: Sat, 23 Nov 2024 23:12:50 +0100
Subject: [PATCH 20/95] stats: use cheaper peers query
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This query is one of the top cost offenders during an instances
lifetime. For small instances it was shown to take up 30-50% percent of
the total database query time, while for bigger isntaces it still held
a spot in the top 3 — alost as or even more expensive overall than
timeline queries!
The good news is, there’s a cheaper way using the instance table:
no need to process each entry, no need to filter NULLs
and no need to dedupe. EXPLAIN estimates the cost of the
old query as 13272.39 and the cost of the new query as 395.74
for me; i.e. a 33-fold reduction.
Results can slightly differ. E.g. we might have an old user
predating the instance tables existence and no interaction with since
or no instance table entry due to failure to query nodeinfo.
Conversely, we might have an instance entry but all known users got
deleted since.
However, this seems unproblematic in practice
and well worth the perf improvment.
Given the previous query didn’t exclude unreachable instances
neither does the new query.
---
lib/pleroma/stats.ex | 8 ++++----
.../mastodon_api/controllers/instance_controller_test.exs | 4 ++++
2 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/lib/pleroma/stats.ex b/lib/pleroma/stats.ex
index 88740d155..7bbe089f8 100644
--- a/lib/pleroma/stats.ex
+++ b/lib/pleroma/stats.ex
@@ -10,6 +10,7 @@ defmodule Pleroma.Stats do
alias Pleroma.CounterCache
alias Pleroma.Repo
alias Pleroma.User
+ alias Pleroma.Instances.Instance
@interval :timer.seconds(300)
@@ -66,14 +67,13 @@ def get_peers do
}
}
def calculate_stat_data do
+ # instances table has an unique constraint on the host column
peers =
from(
- u in User,
- select: fragment("distinct split_part(?, '@', 2)", u.nickname),
- where: u.local != ^true
+ i in Instance,
+ select: i.host
)
|> Repo.all()
- |> Enum.filter(& &1)
domain_count = Enum.count(peers)
diff --git a/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs
index 7b400d1ee..39e9629eb 100644
--- a/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs
@@ -61,7 +61,9 @@ test "get instance stats", %{conn: conn} do
{:ok, _user2} = User.set_activation(user2, false)
insert(:user, %{local: false, nickname: "u@peer1.com"})
+ insert(:instance, %{domain: "peer1.com"})
insert(:user, %{local: false, nickname: "u@peer2.com"})
+ insert(:instance, %{domain: "peer2.com"})
{:ok, _} = Pleroma.Web.CommonAPI.post(user, %{status: "cofe"})
@@ -81,7 +83,9 @@ test "get instance stats", %{conn: conn} do
test "get peers", %{conn: conn} do
insert(:user, %{local: false, nickname: "u@peer1.com"})
+ insert(:instance, %{domain: "peer1.com"})
insert(:user, %{local: false, nickname: "u@peer2.com"})
+ insert(:instance, %{domain: "peer2.com"})
Pleroma.Stats.force_update()
From 8e5defe6ca1421e41e543f507a9a65c0617c7571 Mon Sep 17 00:00:00 2001
From: Oneric
Date: Wed, 11 Dec 2024 03:03:14 +0100
Subject: [PATCH 21/95] stats: estimate remote user count
This value is currently only used by Prometheus metrics
but (after optimisng the peer query inthe preceeding commit)
the most costly part of instance stats.
---
CHANGELOG.md | 2 +
lib/pleroma/stats.ex | 20 +++++-----
...00_remote_user_count_estimate_function.exs | 38 +++++++++++++++++++
3 files changed, 49 insertions(+), 11 deletions(-)
create mode 100644 priv/repo/migrations/20241211000000_remote_user_count_estimate_function.exs
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b0448b0d8..ced89d2b3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## Changed
- Dropped obsolete `ap_enabled` indicator from user table and associated buggy logic
+- The remote user count in prometheus metrics is now an estimate instead of an exact number
+ since the latter proved unreasonably costly to obtain for a merely nice-to-have statistic
## 2025.01.01
diff --git a/lib/pleroma/stats.ex b/lib/pleroma/stats.ex
index 7bbe089f8..f33c378dd 100644
--- a/lib/pleroma/stats.ex
+++ b/lib/pleroma/stats.ex
@@ -79,24 +79,22 @@ def calculate_stat_data do
status_count = Repo.aggregate(User.Query.build(%{local: true}), :sum, :note_count)
- users_query =
+ # there are few enough local users for postgres to use an index scan
+ # (also here an exact count is a bit more important)
+ user_count =
from(u in User,
where: u.is_active == true,
where: u.local == true,
where: not is_nil(u.nickname),
where: not u.invisible
)
+ |> Repo.aggregate(:count, :id)
- remote_users_query =
- from(u in User,
- where: u.is_active == true,
- where: u.local == false,
- where: not is_nil(u.nickname),
- where: not u.invisible
- )
-
- user_count = Repo.aggregate(users_query, :count, :id)
- remote_user_count = Repo.aggregate(remote_users_query, :count, :id)
+ # but mostly numerous remote users leading to a full a full table scan
+ # (ecto currently doesn't allow building queries without explicit table)
+ %{rows: [[remote_user_count]]} =
+ "SELECT estimate_remote_user_count();"
+ |> Pleroma.Repo.query!()
%{
peers: peers,
diff --git a/priv/repo/migrations/20241211000000_remote_user_count_estimate_function.exs b/priv/repo/migrations/20241211000000_remote_user_count_estimate_function.exs
new file mode 100644
index 000000000..010f068a5
--- /dev/null
+++ b/priv/repo/migrations/20241211000000_remote_user_count_estimate_function.exs
@@ -0,0 +1,38 @@
+# Akkoma: Magically expressive social media
+# Copyright © 2024 Akkoma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Repo.Migrations.RemoteUserCountEstimateFunction do
+ use Ecto.Migration
+
+ @function_name "estimate_remote_user_count"
+
+ def up() do
+ # yep, this EXPLAIN (ab)use is blessed by the PostgreSQL wiki:
+ # https://wiki.postgresql.org/wiki/Count_estimate
+ """
+ CREATE OR REPLACE FUNCTION #{@function_name}()
+ RETURNS integer
+ LANGUAGE plpgsql AS $$
+ DECLARE plan jsonb;
+ BEGIN
+ EXECUTE '
+ EXPLAIN (FORMAT JSON)
+ SELECT *
+ FROM public.users
+ WHERE local = false AND
+ is_active = true AND
+ invisible = false AND
+ nickname IS NOT NULL;
+ ' INTO plan;
+ RETURN plan->0->'Plan'->'Plan Rows';
+ END;
+ $$;
+ """
+ |> execute()
+ end
+
+ def down() do
+ execute("DROP FUNCTION IF EXISTS #{@function_name}()")
+ end
+end
From 0ba5c3649d4b8e4f3707e93ff22a2f0f59464bf6 Mon Sep 17 00:00:00 2001
From: Oneric
Date: Sat, 14 Dec 2024 15:40:52 +0100
Subject: [PATCH 22/95] federator: don't nest {:error, _} tuples
It makes decisions based on error sources harder since all possible
nesting levels need to be checked for. As shown by the return values
handled in the receiver worker something else still nests those,
but this is a first start.
---
lib/pleroma/web/federator.ex | 6 +++++-
lib/pleroma/workers/receiver_worker.ex | 6 +++---
2 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/lib/pleroma/web/federator.ex b/lib/pleroma/web/federator.ex
index adf8298da..ce1dd8fa8 100644
--- a/lib/pleroma/web/federator.ex
+++ b/lib/pleroma/web/federator.ex
@@ -117,7 +117,11 @@ def perform(:incoming_ap_doc, params) do
e ->
# Just drop those for now
Logger.debug(fn -> "Unhandled activity\n" <> Jason.encode!(params, pretty: true) end)
- {:error, e}
+
+ case e do
+ {:error, _} -> e
+ _ -> {:error, e}
+ end
end
end
end
diff --git a/lib/pleroma/workers/receiver_worker.ex b/lib/pleroma/workers/receiver_worker.ex
index 6a8f89a03..3eccba812 100644
--- a/lib/pleroma/workers/receiver_worker.ex
+++ b/lib/pleroma/workers/receiver_worker.ex
@@ -24,13 +24,13 @@ def perform(%Job{args: %{"op" => "incoming_ap_doc", "params" => params}}) do
{:discard, :already_present}
# invalid data or e.g. deleting an object we don't know about anyway
- {:error, {:error, {:validate, issue}}} ->
+ {:error, {:validate, issue}} ->
Logger.info("Received invalid AP document: #{inspect(issue)}")
{:discard, :invalid}
# rarer, but sometimes there’s an additional :error in front
- {:error, {:error, {:error, {:validate, issue}}}} ->
- Logger.info("Received invalid AP document: (3e) #{inspect(issue)}")
+ {:error, {:error, {:validate, issue}}} ->
+ Logger.info("Received invalid AP document: (2e) #{inspect(issue)}")
{:discard, :invalid}
{:error, _} = e ->
From 0cd4040db684b8d85b7a2e53417adf1fe536c31b Mon Sep 17 00:00:00 2001
From: Oneric
Date: Wed, 4 Dec 2024 02:09:49 +0100
Subject: [PATCH 23/95] Error out earlier on missing mandatory reference
This is the only user of fetch_actor_and_object which previously just
always preteneded to be successful. For all the activity types handled
here, we absolutely need the referenced object to be able to process it
(other than Announce whether or not processing those activity types for
unknown remote objects is desirable in the first place is up for debate)
All other users of the similar fetch_actor already properly check success.
Note, this currently lumps all reolv failure reasons together,
so even e.g. boosts of MRF rejected posts will still exhaust all
retries. The following commit improves on this.
---
lib/pleroma/web/activity_pub/object_validator.ex | 9 ++++++---
lib/pleroma/web/activity_pub/transmogrifier.ex | 5 ++++-
lib/pleroma/workers/receiver_worker.ex | 7 +++++++
3 files changed, 17 insertions(+), 4 deletions(-)
diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex
index cb0cc9ed7..e44f2fdee 100644
--- a/lib/pleroma/web/activity_pub/object_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validator.ex
@@ -253,9 +253,12 @@ def fetch_actor(object) do
end
def fetch_actor_and_object(object) do
- fetch_actor(object)
- Object.normalize(object["object"], fetch: true)
- :ok
+ with {:ok, %User{}} <- fetch_actor(object),
+ %Object{} <- Object.normalize(object["object"], fetch: true) do
+ :ok
+ else
+ _ -> :error
+ end
end
defp for_each_history_item(
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 251ba6540..02253a2c8 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -519,10 +519,13 @@ defp handle_incoming_normalised(
defp handle_incoming_normalised(%{"type" => type} = data, _options)
when type in ~w{Like EmojiReact Announce Add Remove} do
- with :ok <- ObjectValidator.fetch_actor_and_object(data),
+ with {_, :ok} <- {:link, ObjectValidator.fetch_actor_and_object(data)},
{:ok, activity, _meta} <- Pipeline.common_pipeline(data, local: false) do
{:ok, activity}
else
+ {:link, _} ->
+ {:error, :link_resolve_failed}
+
e ->
{:error, e}
end
diff --git a/lib/pleroma/workers/receiver_worker.ex b/lib/pleroma/workers/receiver_worker.ex
index 3eccba812..4cde93503 100644
--- a/lib/pleroma/workers/receiver_worker.ex
+++ b/lib/pleroma/workers/receiver_worker.ex
@@ -33,6 +33,13 @@ def perform(%Job{args: %{"op" => "incoming_ap_doc", "params" => params}}) do
Logger.info("Received invalid AP document: (2e) #{inspect(issue)}")
{:discard, :invalid}
+ # failed to resolve a necessary referenced remote AP object;
+ # might be temporary server/network trouble thus reattempt
+ {:error, :link_resolve_failed} = e ->
+ # TODO: lower to debug for PR!
+ Logger.info("Failed to resolve AP link; may retry: #{inspect(params)}")
+ e
+
{:error, _} = e ->
Logger.error("Unexpected AP doc error: #{inspect(e)} from #{inspect(params)}")
e
From 2c756005320ac861dae090eafc1f54b18f8dc99e Mon Sep 17 00:00:00 2001
From: Oneric
Date: Sat, 14 Dec 2024 19:33:42 +0100
Subject: [PATCH 24/95] federation/incoming: improve link_resolve retry
decision
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
To facilitate this ObjectValidator.fetch_actor_and_object is adapted to
return an informative error. Otherwise we’d be unable to make an
informed decision on retrying or not later. There’s no point in
retrying to fetch MRF-blocked stuff or private posts for example.
---
lib/pleroma/user.ex | 4 ++++
.../web/activity_pub/object_validator.ex | 21 +++++++++++++++++--
.../web/activity_pub/transmogrifier.ex | 9 ++++++++
lib/pleroma/workers/receiver_worker.ex | 4 +++-
4 files changed, 35 insertions(+), 3 deletions(-)
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 3042d86f2..2e09a89b6 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -1999,6 +1999,10 @@ def get_or_fetch_by_ap_id(ap_id, options \\ []) do
{%User{} = user, _} ->
{:ok, user}
+ {_, {:error, {:reject, :mrf}}} ->
+ Logger.debug("Rejected to fetch user due to MRF: #{ap_id}")
+ {:error, {:reject, :mrf}}
+
e ->
Logger.error("Could not fetch user #{ap_id}, #{inspect(e)}")
{:error, :not_found}
diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex
index e44f2fdee..93980f35b 100644
--- a/lib/pleroma/web/activity_pub/object_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validator.ex
@@ -15,6 +15,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Object
alias Pleroma.Object.Containment
+ alias Pleroma.Object.Fetcher
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.AddRemoveValidator
@@ -253,11 +254,27 @@ def fetch_actor(object) do
end
def fetch_actor_and_object(object) do
+ # Fetcher.fetch_object_from_id already first does a local db lookup
with {:ok, %User{}} <- fetch_actor(object),
- %Object{} <- Object.normalize(object["object"], fetch: true) do
+ {:ap_id, id} when is_binary(id) <-
+ {:ap_id, Pleroma.Web.ActivityPub.Utils.get_ap_id(object["object"])},
+ {:ok, %Object{}} <- Fetcher.fetch_object_from_id(id) do
:ok
else
- _ -> :error
+ {:ap_id, id} ->
+ {:error, {:validate, "Invalid AP id: #{inspect(id)}"}}
+
+ # if actor: late post from a previously unknown, deleted profile
+ # if object: private post we're not allowed to access
+ # (other HTTP replies might just indicate a temporary network failure though!)
+ {:error, e} when e in [:not_found, :forbidden] ->
+ {:error, :ignore}
+
+ {:error, _} = e ->
+ e
+
+ e ->
+ {:error, e}
end
end
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 02253a2c8..51912c6a8 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -523,6 +523,15 @@ defp handle_incoming_normalised(%{"type" => type} = data, _options)
{:ok, activity, _meta} <- Pipeline.common_pipeline(data, local: false) do
{:ok, activity}
else
+ {:link, {:error, :ignore}} ->
+ {:error, :ignore}
+
+ {:link, {:error, {:validate, _}} = e} ->
+ e
+
+ {:link, {:error, {:reject, _}} = e} ->
+ e
+
{:link, _} ->
{:error, :link_resolve_failed}
diff --git a/lib/pleroma/workers/receiver_worker.ex b/lib/pleroma/workers/receiver_worker.ex
index 4cde93503..16241a75f 100644
--- a/lib/pleroma/workers/receiver_worker.ex
+++ b/lib/pleroma/workers/receiver_worker.ex
@@ -23,6 +23,9 @@ def perform(%Job{args: %{"op" => "incoming_ap_doc", "params" => params}}) do
{:error, :already_present} ->
{:discard, :already_present}
+ {:error, :ignore} ->
+ {:discard, :ignore}
+
# invalid data or e.g. deleting an object we don't know about anyway
{:error, {:validate, issue}} ->
Logger.info("Received invalid AP document: #{inspect(issue)}")
@@ -36,7 +39,6 @@ def perform(%Job{args: %{"op" => "incoming_ap_doc", "params" => params}}) do
# failed to resolve a necessary referenced remote AP object;
# might be temporary server/network trouble thus reattempt
{:error, :link_resolve_failed} = e ->
- # TODO: lower to debug for PR!
Logger.info("Failed to resolve AP link; may retry: #{inspect(params)}")
e
From 05bbdbf388766a98898cf13efdaca2bbe0c1dada Mon Sep 17 00:00:00 2001
From: Oneric
Date: Wed, 11 Dec 2024 03:41:33 +0100
Subject: [PATCH 25/95] nodeinfo: lower log level of regular actions to debug
---
lib/pleroma/instances/instance.ex | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lib/pleroma/instances/instance.ex b/lib/pleroma/instances/instance.ex
index 63362eb28..105556900 100644
--- a/lib/pleroma/instances/instance.ex
+++ b/lib/pleroma/instances/instance.ex
@@ -188,7 +188,7 @@ def update_metadata(%URI{host: host} = uri) do
defp do_update_metadata(%URI{host: host} = uri, existing_record) do
if existing_record do
if needs_update(existing_record) do
- Logger.info("Updating metadata for #{host}")
+ Logger.debug("Updating metadata for #{host}")
favicon = scrape_favicon(uri)
nodeinfo = scrape_nodeinfo(uri)
@@ -207,7 +207,7 @@ defp do_update_metadata(%URI{host: host} = uri, existing_record) do
favicon = scrape_favicon(uri)
nodeinfo = scrape_nodeinfo(uri)
- Logger.info("Creating metadata for #{host}")
+ Logger.debug("Creating metadata for #{host}")
%Instance{}
|> changeset(%{
From bcf3e101f6ebc19aa34adbc093a362571ffc48dc Mon Sep 17 00:00:00 2001
From: Oneric
Date: Wed, 11 Dec 2024 03:42:00 +0100
Subject: [PATCH 26/95] rich_media: lower log level of update
---
lib/pleroma/web/rich_media/backfill.ex | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/pleroma/web/rich_media/backfill.ex b/lib/pleroma/web/rich_media/backfill.ex
index 8c54a0916..16730ec5a 100644
--- a/lib/pleroma/web/rich_media/backfill.ex
+++ b/lib/pleroma/web/rich_media/backfill.ex
@@ -86,7 +86,7 @@ defp maybe_schedule_expiration(url, fields) do
end
defp stream_update(%{"activity_id" => activity_id}) do
- Logger.info("Rich media backfill: streaming update for activity #{activity_id}")
+ Logger.debug("Rich media backfill: streaming update for activity #{activity_id}")
Pleroma.Activity.get_by_id(activity_id)
|> Pleroma.Activity.normalize()
From 09736431e0736664f95a4e2bba7cb63889ffcb6a Mon Sep 17 00:00:00 2001
From: Oneric
Date: Sat, 14 Dec 2024 21:03:16 +0100
Subject: [PATCH 27/95] Don't spam logs about deleted users
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
User.get_or_fetch_by_(apid|nickname) are the only external users of fetch_and_prepare_user_from_ap_id,
thus there’s no point in duplicating logging, expecially not at error level.
Currently (duplicated) _not_found errors for users make up the bulk of my logs
and are created almost every second. Deleted users are a common occurence and not
worth logging outside of debug
---
lib/pleroma/user.ex | 10 +++++++++-
lib/pleroma/web/activity_pub/activity_pub.ex | 11 ++++-------
.../activity_pub/mrf/anti_link_spam_policy_test.exs | 8 ++++----
test/pleroma/web/activity_pub/relay_test.exs | 4 ++--
.../web/twitter_api/remote_follow_controller_test.exs | 2 +-
5 files changed, 20 insertions(+), 15 deletions(-)
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 2e09a89b6..2f4ac1b72 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -2003,8 +2003,16 @@ def get_or_fetch_by_ap_id(ap_id, options \\ []) do
Logger.debug("Rejected to fetch user due to MRF: #{ap_id}")
{:error, {:reject, :mrf}}
- e ->
+ {_, {:error, :not_found}} ->
+ Logger.debug("User doesn't exist (anymore): #{ap_id}")
+ {:error, :not_found}
+
+ {_, {:error, e}} ->
Logger.error("Could not fetch user #{ap_id}, #{inspect(e)}")
+ {:error, e}
+
+ e ->
+ Logger.error("Unexpected error condition while fetching user #{ap_id}, #{inspect(e)}")
{:error, :not_found}
end
end
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 494a27421..224767b80 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -1750,19 +1750,16 @@ defp fetch_and_prepare_user_from_ap_id(ap_id, additional) do
else
# If this has been deleted, only log a debug and not an error
{:error, {"Object has been deleted", _, _} = e} ->
- Logger.debug("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
- {:error, e}
+ Logger.debug("User was explicitly deleted #{ap_id}, #{inspect(e)}")
+ {:error, :not_found}
- {:reject, reason} = e ->
- Logger.debug("Rejected user #{ap_id}: #{inspect(reason)}")
+ {:reject, _reason} = e ->
{:error, e}
{:valid, reason} ->
- Logger.debug("Data is not a valid user #{ap_id}: #{inspect(reason)}")
- {:error, "Not a user"}
+ {:error, {:validate, reason}}
{:error, e} ->
- Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}")
{:error, e}
end
end
diff --git a/test/pleroma/web/activity_pub/mrf/anti_link_spam_policy_test.exs b/test/pleroma/web/activity_pub/mrf/anti_link_spam_policy_test.exs
index 6182e9717..291108da9 100644
--- a/test/pleroma/web/activity_pub/mrf/anti_link_spam_policy_test.exs
+++ b/test/pleroma/web/activity_pub/mrf/anti_link_spam_policy_test.exs
@@ -241,11 +241,11 @@ test "it rejects posts without links" do
assert capture_log(fn ->
{:reject, _} = AntiLinkSpamPolicy.filter(message)
- end) =~ "[error] Could not decode user at fetch http://invalid.actor"
+ end) =~ "[error] Could not fetch user http://invalid.actor,"
assert capture_log(fn ->
{:reject, _} = AntiLinkSpamPolicy.filter(update_message)
- end) =~ "[error] Could not decode user at fetch http://invalid.actor"
+ end) =~ "[error] Could not fetch user http://invalid.actor,"
end
test "it rejects posts with links" do
@@ -259,11 +259,11 @@ test "it rejects posts with links" do
assert capture_log(fn ->
{:reject, _} = AntiLinkSpamPolicy.filter(message)
- end) =~ "[error] Could not decode user at fetch http://invalid.actor"
+ end) =~ "[error] Could not fetch user http://invalid.actor,"
assert capture_log(fn ->
{:reject, _} = AntiLinkSpamPolicy.filter(update_message)
- end) =~ "[error] Could not decode user at fetch http://invalid.actor"
+ end) =~ "[error] Could not fetch user http://invalid.actor,"
end
end
diff --git a/test/pleroma/web/activity_pub/relay_test.exs b/test/pleroma/web/activity_pub/relay_test.exs
index 99cc2071e..b1c927b49 100644
--- a/test/pleroma/web/activity_pub/relay_test.exs
+++ b/test/pleroma/web/activity_pub/relay_test.exs
@@ -29,7 +29,7 @@ test "relay actor is invisible" do
test "returns errors when user not found" do
assert capture_log(fn ->
{:error, _} = Relay.follow("test-ap-id")
- end) =~ "Could not decode user at fetch"
+ end) =~ "Could not fetch user test-ap-id,"
end
test "returns activity" do
@@ -48,7 +48,7 @@ test "returns activity" do
test "returns errors when user not found" do
assert capture_log(fn ->
{:error, _} = Relay.unfollow("test-ap-id")
- end) =~ "Could not decode user at fetch"
+ end) =~ "Could not fetch user test-ap-id,"
end
test "returns activity" do
diff --git a/test/pleroma/web/twitter_api/remote_follow_controller_test.exs b/test/pleroma/web/twitter_api/remote_follow_controller_test.exs
index 5a94e4396..66e19b5ef 100644
--- a/test/pleroma/web/twitter_api/remote_follow_controller_test.exs
+++ b/test/pleroma/web/twitter_api/remote_follow_controller_test.exs
@@ -132,7 +132,7 @@ test "show follow page with error when user can not be fetched by `acct` link",
|> html_response(200)
assert response =~ "Error fetching user"
- end) =~ ":not_found"
+ end) =~ "User doesn't exist (anymore): https://mastodon.social/users/not_found"
end
end
From caa4fbe326a4c66df51901fc4aee61df0db1211a Mon Sep 17 00:00:00 2001
From: Oneric
Date: Tue, 17 Dec 2024 00:14:27 +0100
Subject: [PATCH 28/95] user: avoid database work on superfluous pin
The only thing this does is changing the updated_at field of the user.
Afaict this came to be because prior to pins federating this was split
into two functions, one of which created a changeset, the other applying
a given changeset. When this was merged the bits were just copied into
place.
---
lib/pleroma/user.ex | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 2f4ac1b72..697597e1b 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -2581,10 +2581,10 @@ def add_pinned_object_id(%User{} = user, object_id) do
[pinned_objects: "You have already pinned the maximum number of statuses"]
end
end)
+ |> update_and_set_cache()
else
- change(user)
+ {:ok, user}
end
- |> update_and_set_cache()
end
@spec remove_pinned_object_id(User.t(), String.t()) :: {:ok, t()} | {:error, term()}
From b0387dee14b027a38b1d98311fc14ead60ff2900 Mon Sep 17 00:00:00 2001
From: Oneric
Date: Wed, 18 Dec 2024 00:55:48 +0100
Subject: [PATCH 29/95] Gracefully ignore Undo activities referring to unknown
objects
---
.../web/activity_pub/transmogrifier.ex | 14 ++++++++++++++
.../web/activity_pub/transmogrifier_test.exs | 19 +++++++++++++++++++
2 files changed, 33 insertions(+)
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 51912c6a8..e75542eb4 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -604,6 +604,20 @@ defp handle_incoming_normalised(
when type in ["Like", "EmojiReact", "Announce", "Block"] do
with {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
{:ok, activity}
+ else
+ {:error, {:validate, {:error, %Ecto.Changeset{errors: errors}}}} = e ->
+ # If we never saw the activity being undone, no need to do anything.
+ # Inspectinging the validation error content is a bit akward, but looking up the Activity
+ # ahead of time here would be too costly since Activity queries are not cached
+ # and there's no way atm to pass the retrieved result along along
+ if errors[:object] == {"can't find object", []} do
+ {:error, :ignore}
+ else
+ e
+ end
+
+ e ->
+ e
end
end
diff --git a/test/pleroma/web/activity_pub/transmogrifier_test.exs b/test/pleroma/web/activity_pub/transmogrifier_test.exs
index 94df25100..be07a0fe4 100644
--- a/test/pleroma/web/activity_pub/transmogrifier_test.exs
+++ b/test/pleroma/web/activity_pub/transmogrifier_test.exs
@@ -52,6 +52,25 @@ test "it works for incoming unfollows with an existing follow" do
refute User.following?(User.get_cached_by_ap_id(data["actor"]), user)
end
+ test "it ignores Undo activities for unknown objects" do
+ undo_data = %{
+ "id" => "https://remote.com/undo",
+ "type" => "Undo",
+ "actor" => "https:://remote.com/users/unknown",
+ "object" => %{
+ "id" => "https://remote.com/undone_activity/unknown",
+ "type" => "Like"
+ }
+ }
+
+ assert {:error, :ignore} == Transmogrifier.handle_incoming(undo_data)
+
+ user = insert(:user, local: false, ap_id: "https://remote.com/users/known")
+ undo_data = %{undo_data | "actor" => user.ap_id}
+
+ assert {:error, :ignore} == Transmogrifier.handle_incoming(undo_data)
+ end
+
test "it accepts Flag activities" do
user = insert(:user)
other_user = insert(:user)
From 7ad5f8d3c0a02d4b2e93d605403ac6d8558244ec Mon Sep 17 00:00:00 2001
From: Oneric
Date: Wed, 18 Dec 2024 01:07:31 +0100
Subject: [PATCH 30/95] object_validators: only query relevant table for object
Most of them actually only accept either activities or a
non-activity object later; querying both is then a waste
of resources and may create false positives.
---
.../activity_pub/object_validators/common_validations.ex | 6 +++++-
.../web/activity_pub/object_validators/delete_validator.ex | 5 ++++-
.../activity_pub/object_validators/emoji_react_validator.ex | 2 +-
.../web/activity_pub/object_validators/like_validator.ex | 2 +-
.../web/activity_pub/object_validators/undo_validator.ex | 2 +-
5 files changed, 12 insertions(+), 5 deletions(-)
diff --git a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex
index be5074348..f28cdca92 100644
--- a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex
@@ -54,10 +54,14 @@ def validate_actor_presence(cng, options \\ []) do
def validate_object_presence(cng, options \\ []) do
field_name = Keyword.get(options, :field_name, :object)
allowed_types = Keyword.get(options, :allowed_types, false)
+ allowed_categories = Keyword.get(options, :allowed_object_categores, [:object, :activity])
cng
|> validate_change(field_name, fn field_name, object_id ->
- object = Object.get_cached_by_ap_id(object_id) || Activity.get_by_ap_id(object_id)
+ object =
+ (:object in allowed_categories && Object.get_cached_by_ap_id(object_id)) ||
+ (:activity in allowed_categories && Activity.get_by_ap_id(object_id)) ||
+ nil
cond do
!object ->
diff --git a/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex
index a08e8ebe0..2dcb9a5d6 100644
--- a/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex
@@ -61,7 +61,10 @@ defp validate_data(cng) do
|> validate_inclusion(:type, ["Delete"])
|> validate_delete_actor(:actor)
|> validate_modification_rights()
- |> validate_object_or_user_presence(allowed_types: @deletable_types)
+ |> validate_object_or_user_presence(
+ allowed_types: @deletable_types,
+ allowed_object_categories: [:object]
+ )
|> add_deleted_activity_id()
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex b/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex
index bda67feee..9cafeeb14 100644
--- a/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/emoji_react_validator.ex
@@ -129,7 +129,7 @@ defp validate_data(data_cng) do
|> validate_inclusion(:type, ["EmojiReact"])
|> validate_required([:id, :type, :object, :actor, :context, :to, :cc, :content])
|> validate_actor_presence()
- |> validate_object_presence()
+ |> validate_object_presence(allowed_object_categories: [:object])
|> validate_emoji()
|> maybe_validate_tag_presence()
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex
index 35e000d72..44bb0c238 100644
--- a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex
@@ -66,7 +66,7 @@ defp validate_data(data_cng) do
|> validate_inclusion(:type, ["Like"])
|> validate_required([:id, :type, :object, :actor, :context, :to, :cc])
|> validate_actor_presence()
- |> validate_object_presence()
+ |> validate_object_presence(allowed_object_categories: [:object])
|> validate_existing_like()
end
diff --git a/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex b/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex
index 703643e3f..06516f6c7 100644
--- a/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/undo_validator.ex
@@ -44,7 +44,7 @@ defp validate_data(data_cng) do
|> validate_inclusion(:type, ["Undo"])
|> validate_required([:id, :type, :object, :actor, :to, :cc])
|> validate_undo_actor(:actor)
- |> validate_object_presence()
+ |> validate_object_presence(allowed_object_categories: [:activity])
|> validate_undo_rights()
end
From 92bf93a4f7b29273f3985898c8393d7540b67935 Mon Sep 17 00:00:00 2001
From: Oneric
Date: Wed, 18 Dec 2024 01:21:56 +0100
Subject: [PATCH 31/95] transmogrifier: avoid crashes on non-validation Delte
errors
Happens e.g. for duplicated Deletes.
The remaining tombstone object no longer has an actor,
leading to an error response during side-effect handling.
---
lib/pleroma/web/activity_pub/transmogrifier.ex | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index e75542eb4..8cbfe4ebf 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -572,6 +572,12 @@ defp handle_incoming_normalised(
else
_ -> e
end
+
+ {:error, _} = e ->
+ e
+
+ e ->
+ {:error, e}
end
end
From ac2327c8fc6201953b1acf4efb70ef952a5444cf Mon Sep 17 00:00:00 2001
From: Oneric
Date: Wed, 18 Dec 2024 01:27:32 +0100
Subject: [PATCH 32/95] transmogrfier: be more selective about Delete retry
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
If something else renders the Delete invalid,
there’s no point in retrying anyway
---
.../web/activity_pub/transmogrifier.ex | 26 ++++++++++++-------
1 file changed, 16 insertions(+), 10 deletions(-)
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 8cbfe4ebf..973f950fb 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -560,17 +560,23 @@ defp handle_incoming_normalised(
Pipeline.common_pipeline(data, local: false) do
{:ok, activity}
else
- {:error, {:validate, _}} = e ->
- # Check if we have a create activity for this
- with {:ok, object_id} <- ObjectValidators.ObjectID.cast(data["object"]),
- %Activity{data: %{"actor" => actor}} <-
- Activity.create_by_object_ap_id(object_id) |> Repo.one(),
- # We have one, insert a tombstone and retry
- {:ok, tombstone_data, _} <- Builder.tombstone(actor, object_id),
- {:ok, _tombstone} <- Object.create(tombstone_data) do
- handle_incoming(data)
+ {:error, {:validate, {:error, %Ecto.Changeset{errors: errors}}}} = e ->
+ if errors[:object] == {"can't find object", []} do
+ # Check if we have a create activity for this
+ # (e.g. from a db prune without --prune-activities)
+ # We'd still like to process side effects so insert a tombstone and retry
+ with {:ok, object_id} <- ObjectValidators.ObjectID.cast(data["object"]),
+ %Activity{data: %{"actor" => actor}} <-
+ Activity.create_by_object_ap_id(object_id) |> Repo.one(),
+ # We have one, insert a tombstone and retry
+ {:ok, tombstone_data, _} <- Builder.tombstone(actor, object_id),
+ {:ok, _tombstone} <- Object.create(tombstone_data) do
+ handle_incoming(data)
+ else
+ _ -> e
+ end
else
- _ -> e
+ e
end
{:error, _} = e ->
From cd8e6a4235fe96359e03f7251e5dc874ce930577 Mon Sep 17 00:00:00 2001
From: Oneric
Date: Wed, 18 Dec 2024 01:34:33 +0100
Subject: [PATCH 33/95] transmogrifier: gracefully ignore duplicated object
deletes
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The object lookup is later repeated in the validator, but due to
caching shouldn't incur any noticeable performance impact.
It’s actually preferable to check here, since it avoids the otherwise
occuring user lookup and overhead from starting and aborting a
transaction
---
lib/pleroma/object.ex | 5 +++++
lib/pleroma/web/activity_pub/transmogrifier.ex | 18 ++++++++++++++----
2 files changed, 19 insertions(+), 4 deletions(-)
diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex
index 5d84bb286..40c1534b9 100644
--- a/lib/pleroma/object.ex
+++ b/lib/pleroma/object.ex
@@ -215,6 +215,11 @@ def get_cached_by_ap_id(ap_id) do
end
end
+ # Intentionally accepts non-Object arguments!
+ @spec is_tombstone_object?(term()) :: boolean()
+ def is_tombstone_object?(%Object{data: %{"type" => "Tombstone"}}), do: true
+ def is_tombstone_object?(_), do: false
+
def make_tombstone(%Object{data: %{"id" => id, "type" => type}}, deleted \\ DateTime.utc_now()) do
%ObjectTombstone{
id: id,
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 973f950fb..01f39e985 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -556,19 +556,29 @@ defp handle_incoming_normalised(
%{"type" => "Delete"} = data,
_options
) do
- with {:ok, activity, _} <-
- Pipeline.common_pipeline(data, local: false) do
+ oid_result = ObjectValidators.ObjectID.cast(data["object"])
+
+ with {_, {:ok, object_id}} <- {:object_id, oid_result},
+ object <- Object.get_cached_by_ap_id(object_id),
+ {_, false} <- {:tombstone, Object.is_tombstone_object?(object) && !data["actor"]},
+ {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
{:ok, activity}
else
+ {:object_id, _} ->
+ {:error, {:validate, "Invalid object id: #{data["object"]}"}}
+
+ {:tombstone, true} ->
+ {:error, :ignore}
+
{:error, {:validate, {:error, %Ecto.Changeset{errors: errors}}}} = e ->
if errors[:object] == {"can't find object", []} do
# Check if we have a create activity for this
# (e.g. from a db prune without --prune-activities)
- # We'd still like to process side effects so insert a tombstone and retry
+ # We'd still like to process side effects so insert a fake tombstone and retry
+ # (real tombstones from Object.delete do not have an actor field)
with {:ok, object_id} <- ObjectValidators.ObjectID.cast(data["object"]),
%Activity{data: %{"actor" => actor}} <-
Activity.create_by_object_ap_id(object_id) |> Repo.one(),
- # We have one, insert a tombstone and retry
{:ok, tombstone_data, _} <- Builder.tombstone(actor, object_id),
{:ok, _tombstone} <- Object.create(tombstone_data) do
handle_incoming(data)
From 2ddff7e3860cd0076a6d9a881e6d747835247922 Mon Sep 17 00:00:00 2001
From: Oneric
Date: Wed, 18 Dec 2024 21:55:32 +0100
Subject: [PATCH 34/95] transmogrifier: gracefully ignore Delete of unknown
objects
It's quite common to receive spurious Deletes,
so we neither want to waste resources on retrying
nor spam "invalid AP" logs
---
lib/pleroma/web/activity_pub/transmogrifier.ex | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 01f39e985..6e6244b7c 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -577,12 +577,13 @@ defp handle_incoming_normalised(
# We'd still like to process side effects so insert a fake tombstone and retry
# (real tombstones from Object.delete do not have an actor field)
with {:ok, object_id} <- ObjectValidators.ObjectID.cast(data["object"]),
- %Activity{data: %{"actor" => actor}} <-
- Activity.create_by_object_ap_id(object_id) |> Repo.one(),
+ {_, %Activity{data: %{"actor" => actor}}} <-
+ {:create, Activity.create_by_object_ap_id(object_id) |> Repo.one()},
{:ok, tombstone_data, _} <- Builder.tombstone(actor, object_id),
{:ok, _tombstone} <- Object.create(tombstone_data) do
handle_incoming(data)
else
+ {:create, _} -> {:error, :ignore}
_ -> e
end
else
From 8fa51700d47b8375fd31f60b3d958ab1bb7da8aa Mon Sep 17 00:00:00 2001
From: Oneric
Date: Sat, 21 Dec 2024 16:23:06 +0100
Subject: [PATCH 35/95] changelog: summarise preceeding perf tweaks
---
CHANGELOG.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ced89d2b3..6172134e2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Dropped obsolete `ap_enabled` indicator from user table and associated buggy logic
- The remote user count in prometheus metrics is now an estimate instead of an exact number
since the latter proved unreasonably costly to obtain for a merely nice-to-have statistic
+- Various other tweaks improving stat query performance and avoiding unecessary work on received AP documents
## 2025.01.01
From b2b63ad89f439543f14970353582a6ea5828d8f0 Mon Sep 17 00:00:00 2001
From: Floatingghost
Date: Fri, 17 Jan 2025 09:38:09 +0000
Subject: [PATCH 36/95] add oban web
---
lib/pleroma/web/router.ex | 3 +++
mix.exs | 5 +++--
mix.lock | 18 ++++++++++--------
3 files changed, 16 insertions(+), 10 deletions(-)
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index c227d0d4a..a38017d59 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -5,6 +5,7 @@
defmodule Pleroma.Web.Router do
use Pleroma.Web, :router
import Phoenix.LiveDashboard.Router
+ import Oban.Web.Router
pipeline :accepts_html do
plug(:accepts, ["html"])
@@ -903,6 +904,8 @@ defmodule Pleroma.Web.Router do
metrics: {Pleroma.Web.Telemetry, :live_dashboard_metrics},
csp_nonce_assign_key: :csp_nonce
)
+
+ oban_dashboard("/oban/dashboard", csp_nonce_assign_key: :csp_nonce)
end
# Test-only routes needed to test action dispatching and plug chain execution
diff --git a/mix.exs b/mix.exs
index e7eebb815..fe04ff5fe 100644
--- a/mix.exs
+++ b/mix.exs
@@ -117,7 +117,7 @@ defp deps do
[
{:phoenix, "~> 1.7.0"},
{:phoenix_view, "~> 2.0"},
- {:phoenix_live_dashboard, "~> 0.7.2"},
+ {:phoenix_live_dashboard, "~> 0.8.6"},
{:tzdata, "~> 1.1.1"},
{:plug_cowboy, "~> 2.6"},
{:phoenix_pubsub, "~> 2.1"},
@@ -126,7 +126,8 @@ defp deps do
{:ecto_enum, "~> 1.4"},
{:ecto_sql, "~> 3.10.0"},
{:postgrex, "~> 0.17.2"},
- {:oban, "~> 2.17.8"},
+ {:oban, "~> 2.19.0"},
+ {:oban_web, "~> 2.11.0"},
{:gettext, "~> 0.22.3"},
{:bcrypt_elixir, "~> 3.0.1"},
{:fast_sanitize, "~> 0.2.3"},
diff --git a/mix.lock b/mix.lock
index 8c86567a6..679af5c75 100644
--- a/mix.lock
+++ b/mix.lock
@@ -8,7 +8,7 @@
"cachex": {:hex, :cachex, "3.6.0", "14a1bfbeee060dd9bec25a5b6f4e4691e3670ebda28c8ba2884b12fe30b36bf8", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "ebf24e373883bc8e0c8d894a63bbe102ae13d918f790121f5cfe6e485cc8e2e2"},
"calendar": {:hex, :calendar, "1.0.0", "f52073a708528482ec33d0a171954ca610fe2bd28f1e871f247dc7f1565fa807", [:mix], [{:tzdata, "~> 0.1.201603 or ~> 0.5.20 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "990e9581920c82912a5ee50e62ff5ef96da6b15949a2ee4734f935fdef0f0a6f"},
"captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "6630c42aaaab124e697b4e513190c89d8b64e410", [ref: "6630c42aaaab124e697b4e513190c89d8b64e410"]},
- "castore": {:hex, :castore, "1.0.9", "5cc77474afadf02c7c017823f460a17daa7908e991b0cc917febc90e466a375c", [:mix], [], "hexpm", "5ea956504f1ba6f2b4eb707061d8e17870de2bee95fb59d512872c2ef06925e7"},
+ "castore": {:hex, :castore, "1.0.11", "4bbd584741601eb658007339ea730b082cc61f3554cf2e8f39bf693a11b49073", [:mix], [], "hexpm", "e03990b4db988df56262852f20de0f659871c35154691427a5047f4967a16a62"},
"certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
"comeonin": {:hex, :comeonin, "5.5.0", "364d00df52545c44a139bad919d7eacb55abf39e86565878e17cebb787977368", [:mix], [], "hexpm", "6287fc3ba0aad34883cbe3f7949fc1d1e738e5ccdce77165bc99490aa69f47fb"},
@@ -21,7 +21,7 @@
"credo": {:hex, :credo, "1.7.8", "9722ba1681e973025908d542ec3d95db5f9c549251ba5b028e251ad8c24ab8c5", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cb9e87cc64f152f3ed1c6e325e7b894dea8f5ef2e41123bd864e3cd5ceb44968"},
"custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"},
"db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"},
- "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
+ "decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"},
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
"dialyxir": {:hex, :dialyxir, "1.4.4", "fb3ce8741edeaea59c9ae84d5cec75da00fa89fe401c72d6e047d11a61f65f70", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "cd6111e8017ccd563e65621a4d9a4a1c5cd333df30cebc7face8029cacb4eff6"},
"earmark": {:hex, :earmark, "1.4.46", "8c7287bd3137e99d26ae4643e5b7ef2129a260e3dcf41f251750cb4563c8fb81", [:mix], [], "hexpm", "798d86db3d79964e759ddc0c077d5eb254968ed426399fbf5a62de2b5ff8910a"},
@@ -29,7 +29,7 @@
"eblurhash": {:hex, :eblurhash, "1.2.2", "7da4255aaea984b31bb71155f673257353b0e0554d0d30dcf859547e74602582", [:rebar3], [], "hexpm", "8c20ca00904de023a835a9dcb7b7762fed32264c85a80c3cafa85288e405044c"},
"ecto": {:hex, :ecto, "3.10.3", "eb2ae2eecd210b4eb8bece1217b297ad4ff824b4384c0e3fdd28aaf96edd6135", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "44bec74e2364d491d70f7e42cd0d690922659d329f6465e89feb8a34e8cd3433"},
"ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"},
- "ecto_psql_extras": {:hex, :ecto_psql_extras, "0.8.2", "79350a53246ac5ec27326d208496aebceb77fa82a91744f66a9154560f0759d3", [:mix], [{:ecto_sql, "~> 3.7", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "> 0.16.0 and < 0.20.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1 or ~> 4.0.0", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "6149c1c4a5ba6602a76cb09ee7a269eb60dab9694a1dbbb797f032555212de75"},
+ "ecto_psql_extras": {:hex, :ecto_psql_extras, "0.8.3", "0c1df205bd051eaf599b3671e75356b121aa71eac09b63ecf921cb1a080c072e", [:mix], [{:ecto_sql, "~> 3.7", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "> 0.16.0 and < 0.20.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1 or ~> 4.0.0", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "d0e35ea160359e759a2993a00c3a5389a9ca7ece6df5d0753fa927f988c7351a"},
"ecto_sql": {:hex, :ecto_sql, "3.10.2", "6b98b46534b5c2f8b8b5f03f126e75e2a73c64f3c071149d32987a5378b0fdbd", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "68c018debca57cb9235e3889affdaec7a10616a4e3a80c99fa1d01fdafaa9007"},
"elasticsearch": {:git, "https://akkoma.dev/AkkomaGang/elasticsearch-elixir.git", "6cd946f75f6ab9042521a009d1d32d29a90113ca", [ref: "main"]},
"elixir_make": {:hex, :elixir_make, "0.8.4", "4960a03ce79081dee8fe119d80ad372c4e7badb84c493cc75983f9d3bc8bde0f", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "6e7f1d619b5f61dfabd0a20aa268e575572b542ac31723293a4c1a567d5ef040"},
@@ -84,14 +84,16 @@
"nimble_ownership": {:hex, :nimble_ownership, "1.0.0", "3f87744d42c21b2042a0aa1d48c83c77e6dd9dd357e425a038dd4b49ba8b79a1", [:mix], [], "hexpm", "7c16cc74f4e952464220a73055b557a273e8b1b7ace8489ec9d86e9ad56cb2cc"},
"nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
- "oban": {:hex, :oban, "2.17.12", "33fb0cbfb92b910d48dd91a908590fe3698bb85eacec8cd0d9bc6aa13dddd6d6", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7a647d6cd6bb300073db17faabce22d80ae135da3baf3180a064fa7c4fa046e3"},
+ "oban": {:hex, :oban, "2.19.0", "dfb8fa028ce7e7cf3be3481a47a7c8ebf9428d6df0aa58c1388a8e63f7ff2797", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:igniter, "~> 0.5", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aa3eb7cfa2aea8ecc4df4787b92ddb61ad5a598f07560937d1dd5dbb1ed225e2"},
+ "oban_met": {:hex, :oban_met, "1.0.1", "737db0064567b923d3f35efd1d3009dd1435d60ee6f98dbb55dbb83db8f4f4fa", [:mix], [{:oban, "~> 2.18", [hex: :oban, repo: "hexpm", optional: false]}], "hexpm", "0492d841f880b76c5b73081bc70ebea20ebacc08e871345f72c2270513f09957"},
+ "oban_web": {:hex, :oban_web, "2.11.0", "8b2a23331ef7e60eabb4118a141880d89812820321b21f289f1696bcf3058810", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:oban, "~> 2.19", [hex: :oban, repo: "hexpm", optional: false]}, {:oban_met, "~> 1.0", [hex: :oban_met, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.7", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}], "hexpm", "a573f27bf7cb054ff2a694116428dc6fedc18e20a20d10a74934b7c9e473e562"},
"open_api_spex": {:hex, :open_api_spex, "3.21.2", "6a704f3777761feeb5657340250d6d7332c545755116ca98f33d4b875777e1e5", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0 or ~> 6.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "f42ae6ed668b895ebba3e02773cfb4b41050df26f803f2ef634c72a7687dc387"},
"parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},
- "phoenix": {:hex, :phoenix, "1.7.14", "a7d0b3f1bc95987044ddada111e77bd7f75646a08518942c72a8440278ae7825", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "c7859bc56cc5dfef19ecfc240775dae358cbaa530231118a9e014df392ace61a"},
+ "phoenix": {:hex, :phoenix, "1.7.18", "5310c21443514be44ed93c422e15870aef254cf1b3619e4f91538e7529d2b2e4", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "1797fcc82108442a66f2c77a643a62980f342bfeb63d6c9a515ab8294870004e"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.6.3", "f686701b0499a07f2e3b122d84d52ff8a31f5def386e03706c916f6feddf69ef", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "909502956916a657a197f94cc1206d9a65247538de8a5e186f7537c895d95764"},
"phoenix_html": {:hex, :phoenix_html, "3.3.4", "42a09fc443bbc1da37e372a5c8e6755d046f22b9b11343bf885067357da21cb3", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0249d3abec3714aff3415e7ee3d9786cb325be3151e6c4b3021502c585bf53fb"},
- "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.7.2", "97cc4ff2dba1ebe504db72cb45098cb8e91f11160528b980bd282cc45c73b29c", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.3", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0e5fdf063c7a3b620c566a30fcf68b7ee02e5e46fe48ee46a6ec3ba382dc05b7"},
- "phoenix_live_view": {:hex, :phoenix_live_view, "0.18.18", "1f38fbd7c363723f19aad1a04b5490ff3a178e37daaf6999594d5f34796c47fc", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a5810d0472f3189ede6d2a95bda7f31c6113156b91784a3426cb0ab6a6d85214"},
+ "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.6", "7b1f0327f54c9eb69845fd09a77accf922f488c549a7e7b8618775eb603a62c7", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "1681ab813ec26ca6915beb3414aa138f298e17721dc6a2bde9e6eb8a62360ff6"},
+ "phoenix_live_view": {:hex, :phoenix_live_view, "1.0.2", "e7b1dd68c86326e2c45cc81da41e332cc8aa7228a7161e2c811dcd7f1dd14db1", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8a40265b0cd7d3a35f136dfa3cc048e3b198fc3718763411a78c323a44ebebee"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"},
"phoenix_swoosh": {:hex, :phoenix_swoosh, "1.2.1", "b74ccaa8046fbc388a62134360ee7d9742d5a8ae74063f34eb050279de7a99e1", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "4000eeba3f9d7d1a6bf56d2bd56733d5cadf41a7f0d8ffe5bb67e7d667e204a2"},
"phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},
@@ -131,6 +133,6 @@
"vex": {:hex, :vex, "0.9.2", "fe061acc9e0907d983d46b51bf35d58176f0fe6eb7ba3b33c9336401bf42b6d1", [:mix], [], "hexpm", "76e709a9762e98c6b462dfce92e9b5dfbf712839227f2da8add6dd11549b12cb"},
"web_push_encryption": {:hex, :web_push_encryption, "0.3.1", "76d0e7375142dfee67391e7690e89f92578889cbcf2879377900b5620ee4708d", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.11.1", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "4f82b2e57622fb9337559058e8797cb0df7e7c9790793bdc4e40bc895f70e2a2"},
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
- "websock_adapter": {:hex, :websock_adapter, "0.5.7", "65fa74042530064ef0570b75b43f5c49bb8b235d6515671b3d250022cb8a1f9e", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "d0f478ee64deddfec64b800673fd6e0c8888b079d9f3444dd96d2a98383bdbd1"},
+ "websock_adapter": {:hex, :websock_adapter, "0.5.8", "3b97dc94e407e2d1fc666b2fb9acf6be81a1798a2602294aac000260a7c4a47d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "315b9a1865552212b5f35140ad194e67ce31af45bcee443d4ecb96b5fd3f3782"},
"websockex": {:hex, :websockex, "0.4.3", "92b7905769c79c6480c02daacaca2ddd49de936d912976a4d3c923723b647bf0", [:mix], [], "hexpm", "95f2e7072b85a3a4cc385602d42115b73ce0b74a9121d0d6dbbf557645ac53e4"},
}
From fb3de8045a354fcb189a66b4155508ed9c870b69 Mon Sep 17 00:00:00 2001
From: Oneric
Date: Tue, 7 Jan 2025 02:27:45 +0100
Subject: [PATCH 37/95] Expose Port IO stats via Prometheus
---
lib/pleroma/web/telemetry.ex | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/lib/pleroma/web/telemetry.ex b/lib/pleroma/web/telemetry.ex
index 0ddb2c903..8d43e366f 100644
--- a/lib/pleroma/web/telemetry.ex
+++ b/lib/pleroma/web/telemetry.ex
@@ -208,8 +208,10 @@ defp summary_fallback_metrics(byte_unit \\ :byte) do
dist_metrics ++ vm_metrics
end
- defp common_metrics do
+ defp common_metrics(byte_unit \\ :byte) do
[
+ last_value("vm.portio.in.total", unit: {:byte, byte_unit}),
+ last_value("vm.portio.out.total", unit: {:byte, byte_unit}),
last_value("pleroma.local_users.total"),
last_value("pleroma.domains.total"),
last_value("pleroma.local_statuses.total"),
@@ -220,14 +222,22 @@ defp common_metrics do
def prometheus_metrics,
do: common_metrics() ++ distribution_metrics() ++ summary_fallback_metrics()
- def live_dashboard_metrics, do: common_metrics() ++ summary_metrics(:megabyte)
+ def live_dashboard_metrics, do: common_metrics(:megabyte) ++ summary_metrics(:megabyte)
defp periodic_measurements do
[
+ {__MODULE__, :io_stats, []},
{__MODULE__, :instance_stats, []}
]
end
+ def io_stats do
+ # All IO done via erlang ports, i.e. mostly network but also e.g. fasthtml_workers. NOT disk IO!
+ {{:input, input}, {:output, output}} = :erlang.statistics(:io)
+ :telemetry.execute([:vm, :portio, :in], %{total: input}, %{})
+ :telemetry.execute([:vm, :portio, :out], %{total: output}, %{})
+ end
+
def instance_stats do
stats = Stats.get_stats()
:telemetry.execute([:pleroma, :local_users], %{total: stats.user_count}, %{})
From 4701aa2a386d88707f5d6232203aed6b959b52db Mon Sep 17 00:00:00 2001
From: Oneric
Date: Sun, 15 Dec 2024 02:50:20 +0100
Subject: [PATCH 38/95] receiver_worker: log processes crashes
Oban cataches crashes to handle job failure and retry,
thus it never bubbles up all the way and nothing is logged by default.
For better debugging, catch and log any crashes.
---
lib/pleroma/workers/receiver_worker.ex | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/lib/pleroma/workers/receiver_worker.ex b/lib/pleroma/workers/receiver_worker.ex
index 16241a75f..13493ec8b 100644
--- a/lib/pleroma/workers/receiver_worker.ex
+++ b/lib/pleroma/workers/receiver_worker.ex
@@ -50,5 +50,13 @@ def perform(%Job{args: %{"op" => "incoming_ap_doc", "params" => params}}) do
Logger.error("Unexpected AP doc error: (raw) #{inspect(e)} from #{inspect(params)}")
{:error, e}
end
+ rescue
+ err ->
+ Logger.error(
+ "Receiver worker CRASH on #{inspect(params)} with: #{Exception.format(:error, err, __STACKTRACE__)}"
+ )
+
+ # reraise to let oban handle transaction conflicts without deductig an attempt
+ reraise err, __STACKTRACE__
end
end
From 46148c082522bcb4b4c87e0f7a5b3800269cc20c Mon Sep 17 00:00:00 2001
From: Oneric
Date: Tue, 7 Jan 2025 04:10:58 +0100
Subject: [PATCH 39/95] Don't return garbage on failed collection fetches
And for now treat partial fetches as a success, since for all
current users partial collection data is better than no data at all.
If an error occurred while fetching a page, this previously
returned a bogus {:ok, {:error, _}} success, causing the error
to be attached to the object as an reply list subsequently
leading to the whole post getting rejected during validation.
Also the pinned collection caller did not actually handle
the preexisting error case resulting in process crashes.
---
lib/pleroma/collections/fetcher.ex | 25 +++++++++++++-------
lib/pleroma/web/activity_pub/activity_pub.ex | 22 ++++++++++-------
2 files changed, 29 insertions(+), 18 deletions(-)
diff --git a/lib/pleroma/collections/fetcher.ex b/lib/pleroma/collections/fetcher.ex
index 9ab883cc2..a4c3463a2 100644
--- a/lib/pleroma/collections/fetcher.ex
+++ b/lib/pleroma/collections/fetcher.ex
@@ -14,7 +14,7 @@ defmodule Akkoma.Collections.Fetcher do
@spec fetch_collection(String.t() | map()) :: {:ok, [Pleroma.Object.t()]} | {:error, any()}
def fetch_collection(ap_id) when is_binary(ap_id) do
with {:ok, page} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id) do
- {:ok, objects_from_collection(page)}
+ partial_as_success(objects_from_collection(page))
else
e ->
Logger.error("Could not fetch collection #{ap_id} - #{inspect(e)}")
@@ -24,9 +24,12 @@ def fetch_collection(ap_id) when is_binary(ap_id) do
def fetch_collection(%{"type" => type} = page)
when type in ["Collection", "OrderedCollection", "CollectionPage", "OrderedCollectionPage"] do
- {:ok, objects_from_collection(page)}
+ partial_as_success(objects_from_collection(page))
end
+ defp partial_as_success({:partial, items}), do: {:ok, items}
+ defp partial_as_success(res), do: res
+
defp items_in_page(%{"type" => type, "orderedItems" => items})
when is_list(items) and type in ["OrderedCollection", "OrderedCollectionPage"],
do: items
@@ -53,11 +56,11 @@ defp objects_from_collection(%{"type" => type, "first" => %{"id" => id}})
fetch_page_items(id)
end
- defp objects_from_collection(_page), do: []
+ defp objects_from_collection(_page), do: {:ok, []}
defp fetch_page_items(id, items \\ []) do
if Enum.count(items) >= Config.get([:activitypub, :max_collection_objects]) do
- items
+ {:ok, items}
else
with {:ok, page} <- Fetcher.fetch_and_contain_remote_object_from_id(id) do
objects = items_in_page(page)
@@ -65,18 +68,22 @@ defp fetch_page_items(id, items \\ []) do
if Enum.count(objects) > 0 do
maybe_next_page(page, items ++ objects)
else
- items
+ {:ok, items}
end
else
{:error, :not_found} ->
- items
+ {:ok, items}
{:error, :forbidden} ->
- items
+ {:ok, items}
{:error, error} ->
Logger.error("Could not fetch page #{id} - #{inspect(error)}")
- {:error, error}
+
+ case items do
+ [] -> {:error, error}
+ _ -> {:partial, items}
+ end
end
end
end
@@ -85,5 +92,5 @@ defp maybe_next_page(%{"next" => id}, items) when is_binary(id) do
fetch_page_items(id, items)
end
- defp maybe_next_page(_, items), do: items
+ defp maybe_next_page(_, items), do: {:ok, items}
end
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 224767b80..746d18639 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -1797,7 +1797,7 @@ def pin_data_from_featured_collection(%{
else
e ->
Logger.error("Could not decode featured collection at fetch #{first}, #{inspect(e)}")
- {:ok, %{}}
+ %{}
end
end
@@ -1807,14 +1807,18 @@ def pin_data_from_featured_collection(
} = collection
)
when type in ["OrderedCollection", "Collection"] do
- {:ok, objects} = Collections.Fetcher.fetch_collection(collection)
-
- # Items can either be a map _or_ a string
- objects
- |> Map.new(fn
- ap_id when is_binary(ap_id) -> {ap_id, NaiveDateTime.utc_now()}
- %{"id" => object_ap_id} -> {object_ap_id, NaiveDateTime.utc_now()}
- end)
+ with {:ok, objects} <- Collections.Fetcher.fetch_collection(collection) do
+ # Items can either be a map _or_ a string
+ objects
+ |> Map.new(fn
+ ap_id when is_binary(ap_id) -> {ap_id, NaiveDateTime.utc_now()}
+ %{"id" => object_ap_id} -> {object_ap_id, NaiveDateTime.utc_now()}
+ end)
+ else
+ e ->
+ Logger.warning("Failed to fetch featured collection #{collection}, #{inspect(e)}")
+ %{}
+ end
end
def pin_data_from_featured_collection(obj) do
From 1b09b9fc22d6f168f3ee28fc4441282e4f7ba917 Mon Sep 17 00:00:00 2001
From: Oneric
Date: Thu, 9 Jan 2025 20:40:59 +0100
Subject: [PATCH 40/95] static_fe: fix HTML quotation for upload alt text
Reported by riley on IRC
---
.../web/templates/static_fe/static_fe/_attachment.html.eex | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/pleroma/web/templates/static_fe/static_fe/_attachment.html.eex b/lib/pleroma/web/templates/static_fe/static_fe/_attachment.html.eex
index f5bbe9a07..e58af2888 100644
--- a/lib/pleroma/web/templates/static_fe/static_fe/_attachment.html.eex
+++ b/lib/pleroma/web/templates/static_fe/static_fe/_attachment.html.eex
@@ -1,4 +1,4 @@
-" title="<%= @name %>">
+
<%= if @nsfw do %>
<%= gettext("Hover to show content") %>
From a1c841a122382224d6752c4b4c2c8902bd1f4a8b Mon Sep 17 00:00:00 2001
From: Oneric
Date: Thu, 9 Jan 2025 21:00:18 +0100
Subject: [PATCH 41/95] federation.md: list FEP-dc88 formatting mathematics
Implemented by https://akkoma.dev/AkkomaGang/akkoma/pulls/642
---
FEDERATION.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/FEDERATION.md b/FEDERATION.md
index 4524ec4e2..93eff05ca 100644
--- a/FEDERATION.md
+++ b/FEDERATION.md
@@ -10,6 +10,7 @@
## Supported FEPs
- [FEP-67ff: FEDERATION](https://codeberg.org/fediverse/fep/src/branch/main/fep/67ff/fep-67ff.md)
+- [FEP-dc88: Formatting Mathematics](https://codeberg.org/fediverse/fep/src/branch/main/fep/dc88/fep-dc88.md)
- [FEP-f1d5: NodeInfo in Fediverse Software](https://codeberg.org/fediverse/fep/src/branch/main/fep/f1d5/fep-f1d5.md)
- [FEP-fffd: Proxy Objects](https://codeberg.org/fediverse/fep/src/branch/main/fep/fffd/fep-fffd.md)
From f0a99b4595f11c8415155ffa32d4653db11c7bae Mon Sep 17 00:00:00 2001
From: Oneric
Date: Mon, 10 Feb 2025 04:40:51 +0100
Subject: [PATCH 42/95] article_note_validator: fix handling of Mastodon-style
replies collections
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The first collection page is (sometimes?) inlined
which caused crashes when attempting to log the fetch failure.
But there’s no need to fetch and we can treat it like the other inline collection
---
.../object_validators/article_note_page_validator.ex | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex b/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex
index d1cd496db..e52986502 100644
--- a/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/article_note_page_validator.ex
@@ -71,7 +71,7 @@ defp fix_tag(data), do: Map.drop(data, ["tag"])
defp fix_replies(%{"replies" => replies} = data) when is_list(replies), do: data
- defp fix_replies(%{"replies" => %{"first" => first}} = data) do
+ defp fix_replies(%{"replies" => %{"first" => first}} = data) when is_binary(first) do
with {:ok, replies} <- Akkoma.Collections.Fetcher.fetch_collection(first) do
Map.put(data, "replies", replies)
else
@@ -81,6 +81,10 @@ defp fix_replies(%{"replies" => %{"first" => first}} = data) do
end
end
+ defp fix_replies(%{"replies" => %{"first" => %{"items" => replies}}} = data)
+ when is_list(replies),
+ do: Map.put(data, "replies", replies)
+
defp fix_replies(%{"replies" => %{"items" => replies}} = data) when is_list(replies),
do: Map.put(data, "replies", replies)
From 7998a0034691722ae2f7d78932c212f66385f58d Mon Sep 17 00:00:00 2001
From: Oneric
Date: Fri, 14 Feb 2025 21:57:28 +0100
Subject: [PATCH 43/95] cosmetic/rich_media/parser: fix typo
---
lib/pleroma/web/rich_media/parser.ex | 2 +-
test/pleroma/web/rich_media/parser_test.exs | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/lib/pleroma/web/rich_media/parser.ex b/lib/pleroma/web/rich_media/parser.ex
index 7c5fed2bf..15eaaf735 100644
--- a/lib/pleroma/web/rich_media/parser.ex
+++ b/lib/pleroma/web/rich_media/parser.ex
@@ -96,7 +96,7 @@ defp parse_uri(true, url) do
|> validate_page_url
end
- defp parse_uri(_, _), do: {:error, "not an URL"}
+ defp parse_uri(_, _), do: {:error, "not a URL"}
defp get_tld(host) do
host
diff --git a/test/pleroma/web/rich_media/parser_test.exs b/test/pleroma/web/rich_media/parser_test.exs
index bf7864aa7..77e37784b 100644
--- a/test/pleroma/web/rich_media/parser_test.exs
+++ b/test/pleroma/web/rich_media/parser_test.exs
@@ -121,13 +121,13 @@ test "refuses to crawl plain HTTP and other scheme URL" do
res = Parser.parse(url)
assert {:error, {:url, "scheme mismatch"}} == res or
- {:error, {:url, "not an URL"}} == res
+ {:error, {:url, "not a URL"}} == res
end)
end
test "refuses to crawl malformed URLs" do
url = "example.com[]/ogp"
- assert {:error, {:url, "not an URL"}} == Parser.parse(url)
+ assert {:error, {:url, "not a URL"}} == Parser.parse(url)
end
test "refuses to crawl URLs of private network from posts" do
From 1c2eb4d79963baef822cdb313ac68b2d9c84cbe2 Mon Sep 17 00:00:00 2001
From: Oneric
Date: Fri, 14 Feb 2025 21:59:22 +0100
Subject: [PATCH 44/95] cosmetic/object: drop is_ prefix from
is_tombstone_object?
The question mark suffix already implies it being an indicator function
---
lib/pleroma/object.ex | 6 +++---
lib/pleroma/web/activity_pub/transmogrifier.ex | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex
index 40c1534b9..d496cb23f 100644
--- a/lib/pleroma/object.ex
+++ b/lib/pleroma/object.ex
@@ -216,9 +216,9 @@ def get_cached_by_ap_id(ap_id) do
end
# Intentionally accepts non-Object arguments!
- @spec is_tombstone_object?(term()) :: boolean()
- def is_tombstone_object?(%Object{data: %{"type" => "Tombstone"}}), do: true
- def is_tombstone_object?(_), do: false
+ @spec tombstone_object?(term()) :: boolean()
+ def tombstone_object?(%Object{data: %{"type" => "Tombstone"}}), do: true
+ def tombstone_object?(_), do: false
def make_tombstone(%Object{data: %{"id" => id, "type" => type}}, deleted \\ DateTime.utc_now()) do
%ObjectTombstone{
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 6e6244b7c..ecf6d9cd8 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -560,7 +560,7 @@ defp handle_incoming_normalised(
with {_, {:ok, object_id}} <- {:object_id, oid_result},
object <- Object.get_cached_by_ap_id(object_id),
- {_, false} <- {:tombstone, Object.is_tombstone_object?(object) && !data["actor"]},
+ {_, false} <- {:tombstone, Object.tombstone_object?(object) && !data["actor"]},
{:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
{:ok, activity}
else
From 98c7e9534ec6ba1f6ab61f0db8f847b160390836 Mon Sep 17 00:00:00 2001
From: Oneric
Date: Mon, 23 Dec 2024 14:14:47 +0100
Subject: [PATCH 45/95] Drop obsolete APNG mime override
Commit 9d2c558f64fd7853f9317e74821d6687eb7a8886 bumped
to a mime package version including the upstream fix.
---
config/config.exs | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/config/config.exs b/config/config.exs
index 5a6169506..48ebe91d2 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -166,10 +166,7 @@
"application/xrd+xml" => ["xrd+xml"],
"application/jrd+json" => ["jrd+json"],
"application/activity+json" => ["activity+json"],
- "application/ld+json" => ["activity+json"],
- # Can be removed when bumping MIME past 2.0.5
- # see https://akkoma.dev/AkkomaGang/akkoma/issues/657
- "image/apng" => ["apng"]
+ "application/ld+json" => ["activity+json"]
}
config :mime, :extensions, %{
From c8e0f7848bd72434a30e450a5af8c356edb7fbcb Mon Sep 17 00:00:00 2001
From: Oneric
Date: Mon, 23 Dec 2024 14:19:30 +0100
Subject: [PATCH 46/95] Migrate back to upstream Plug.Static
Commit a924e117fd3059a047dc49348ac4de19b4db5ddd bumped the
plug package to 1.16.0 which includes our upstream patch.
Resolves: https://akkoma.dev/AkkomaGang/akkoma/issues/734
---
lib/pleroma/web/plugs/instance_static.ex | 4 +-
.../web/plugs/static_no_content_type.ex | 469 ------------------
lib/pleroma/web/plugs/uploaded_media.ex | 4 +-
3 files changed, 4 insertions(+), 473 deletions(-)
delete mode 100644 lib/pleroma/web/plugs/static_no_content_type.ex
diff --git a/lib/pleroma/web/plugs/instance_static.ex b/lib/pleroma/web/plugs/instance_static.ex
index b72b604a1..ec188986b 100644
--- a/lib/pleroma/web/plugs/instance_static.ex
+++ b/lib/pleroma/web/plugs/instance_static.ex
@@ -62,10 +62,10 @@ defp call_static(%{request_path: request_path} = conn, opts, from) do
opts =
opts
|> Map.put(:from, from)
- |> Map.put(:set_content_type, false)
+ |> Map.put(:content_type, false)
conn
|> set_static_content_type(request_path)
- |> Pleroma.Web.Plugs.StaticNoCT.call(opts)
+ |> Plug.Static.call(opts)
end
end
diff --git a/lib/pleroma/web/plugs/static_no_content_type.ex b/lib/pleroma/web/plugs/static_no_content_type.ex
deleted file mode 100644
index ea00a2d5d..000000000
--- a/lib/pleroma/web/plugs/static_no_content_type.ex
+++ /dev/null
@@ -1,469 +0,0 @@
-# This is almost identical to Plug.Static from Plug 1.15.3 (2024-01-16)
-# It being copied is a temporary measure to fix an urgent bug without
-# needing to wait for merge of a suitable patch upstream
-# The differences are:
-# - this leading comment
-# - renaming of the module from 'Plug.Static' to 'Pleroma.Web.Plugs.StaticNoCT'
-# - additon of set_content_type option
-
-defmodule Pleroma.Web.Plugs.StaticNoCT do
- @moduledoc """
- A plug for serving static assets.
-
- It requires two options:
-
- * `:at` - the request path to reach for static assets.
- It must be a string.
-
- * `:from` - the file system path to read static assets from.
- It can be either: a string containing a file system path, an
- atom representing the application name (where assets will
- be served from `priv/static`), a tuple containing the
- application name and the directory to serve assets from (besides
- `priv/static`), or an MFA tuple.
-
- The preferred form is to use `:from` with an atom or tuple, since
- it will make your application independent from the starting directory.
- For example, if you pass:
-
- plug Plug.Static, from: "priv/app/path"
-
- Plug.Static will be unable to serve assets if you build releases
- or if you change the current directory. Instead do:
-
- plug Plug.Static, from: {:app_name, "priv/app/path"}
-
- If a static asset cannot be found, `Plug.Static` simply forwards
- the connection to the rest of the pipeline.
-
- ## Cache mechanisms
-
- `Plug.Static` uses etags for HTTP caching. This means browsers/clients
- should cache assets on the first request and validate the cache on
- following requests, not downloading the static asset once again if it
- has not changed. The cache-control for etags is specified by the
- `cache_control_for_etags` option and defaults to `"public"`.
-
- However, `Plug.Static` also supports direct cache control by using
- versioned query strings. If the request query string starts with
- "?vsn=", `Plug.Static` assumes the application is versioning assets
- and does not set the `ETag` header, meaning the cache behaviour will
- be specified solely by the `cache_control_for_vsn_requests` config,
- which defaults to `"public, max-age=31536000"`.
-
- ## Options
-
- * `:encodings` - list of 2-ary tuples where first value is value of
- the `Accept-Encoding` header and second is extension of the file to
- be served if given encoding is accepted by client. Entries will be tested
- in order in list, so entries higher in list will be preferred. Defaults
- to: `[]`.
-
- In addition to setting this value directly it supports 2 additional
- options for compatibility reasons:
-
- + `:brotli` - will append `{"br", ".br"}` to the encodings list.
- + `:gzip` - will append `{"gzip", ".gz"}` to the encodings list.
-
- Additional options will be added in the above order (Brotli takes
- preference over Gzip) to reflect older behaviour which was set due
- to fact that Brotli in general provides better compression ratio than
- Gzip.
-
- * `:cache_control_for_etags` - sets the cache header for requests
- that use etags. Defaults to `"public"`.
-
- * `:etag_generation` - specify a `{module, function, args}` to be used
- to generate an etag. The `path` of the resource will be passed to
- the function, as well as the `args`. If this option is not supplied,
- etags will be generated based off of file size and modification time.
- Note it is [recommended for the etag value to be quoted](https://tools.ietf.org/html/rfc7232#section-2.3),
- which Plug won't do automatically.
-
- * `:cache_control_for_vsn_requests` - sets the cache header for
- requests starting with "?vsn=" in the query string. Defaults to
- `"public, max-age=31536000"`.
-
- * `:only` - filters which requests to serve. This is useful to avoid
- file system access on every request when this plug is mounted
- at `"/"`. For example, if `only: ["images", "favicon.ico"]` is
- specified, only files in the "images" directory and the
- "favicon.ico" file will be served by `Plug.Static`.
- Note that `Plug.Static` matches these filters against request
- uri and not against the filesystem. When requesting
- a file with name containing non-ascii or special characters,
- you should use urlencoded form. For example, you should write
- `only: ["file%20name"]` instead of `only: ["file name"]`.
- Defaults to `nil` (no filtering).
-
- * `:only_matching` - a relaxed version of `:only` that will
- serve any request as long as one of the given values matches the
- given path. For example, `only_matching: ["images", "favicon"]`
- will match any request that starts at "images" or "favicon",
- be it "/images/foo.png", "/images-high/foo.png", "/favicon.ico"
- or "/favicon-high.ico". Such matches are useful when serving
- digested files at the root. Defaults to `nil` (no filtering).
-
- * `:headers` - other headers to be set when serving static assets. Specify either
- an enum of key-value pairs or a `{module, function, args}` to return an enum. The
- `conn` will be passed to the function, as well as the `args`.
-
- * `:content_types` - custom MIME type mapping. As a map with filename as key
- and content type as value. For example:
- `content_types: %{"apple-app-site-association" => "application/json"}`.
-
- * `:set_content_type` - by default Plug.Static (re)sets the content type header
- using auto-detection and the `:content_types` map. But when set to `false`
- no content-type header will be inserted instead retaining the original
- value or lack thereof. This can be useful when custom logic for appropiate
- content types is needed which cannot be reasonably expressed as a static
- filename map.
-
- ## Examples
-
- This plug can be mounted in a `Plug.Builder` pipeline as follows:
-
- defmodule MyPlug do
- use Plug.Builder
-
- plug Plug.Static,
- at: "/public",
- from: :my_app,
- only: ~w(images robots.txt)
- plug :not_found
-
- def not_found(conn, _) do
- send_resp(conn, 404, "not found")
- end
- end
-
- """
-
- @behaviour Plug
- @allowed_methods ~w(GET HEAD)
-
- import Plug.Conn
- alias Plug.Conn
-
- # In this module, the `:prim_file` Erlang module along with the `:file_info`
- # record are used instead of the more common and Elixir-y `File` module and
- # `File.Stat` struct, respectively. The reason behind this is performance: all
- # the `File` operations pass through a single process in order to support node
- # operations that we simply don't need when serving assets.
-
- require Record
- Record.defrecordp(:file_info, Record.extract(:file_info, from_lib: "kernel/include/file.hrl"))
-
- defmodule InvalidPathError do
- defexception message: "invalid path for static asset", plug_status: 400
- end
-
- @impl true
- def init(opts) do
- from =
- case Keyword.fetch!(opts, :from) do
- {_, _} = from -> from
- {_, _, _} = from -> from
- from when is_atom(from) -> {from, "priv/static"}
- from when is_binary(from) -> from
- _ -> raise ArgumentError, ":from must be an atom, a binary or a tuple"
- end
-
- encodings =
- opts
- |> Keyword.get(:encodings, [])
- |> maybe_add("br", ".br", Keyword.get(opts, :brotli, false))
- |> maybe_add("gzip", ".gz", Keyword.get(opts, :gzip, false))
-
- %{
- encodings: encodings,
- only_rules: {Keyword.get(opts, :only, []), Keyword.get(opts, :only_matching, [])},
- qs_cache: Keyword.get(opts, :cache_control_for_vsn_requests, "public, max-age=31536000"),
- et_cache: Keyword.get(opts, :cache_control_for_etags, "public"),
- et_generation: Keyword.get(opts, :etag_generation, nil),
- headers: Keyword.get(opts, :headers, %{}),
- content_types: Keyword.get(opts, :content_types, %{}),
- set_content_type: Keyword.get(opts, :set_content_type, true),
- from: from,
- at: opts |> Keyword.fetch!(:at) |> Plug.Router.Utils.split()
- }
- end
-
- @impl true
- def call(
- conn = %Conn{method: meth},
- %{at: at, only_rules: only_rules, from: from, encodings: encodings} = options
- )
- when meth in @allowed_methods do
- segments = subset(at, conn.path_info)
-
- if allowed?(only_rules, segments) do
- segments = Enum.map(segments, &uri_decode/1)
-
- if invalid_path?(segments) do
- raise InvalidPathError, "invalid path for static asset: #{conn.request_path}"
- end
-
- path = path(from, segments)
- range = get_req_header(conn, "range")
- encoding = file_encoding(conn, path, range, encodings)
- serve_static(encoding, conn, segments, range, options)
- else
- conn
- end
- end
-
- def call(conn, _options) do
- conn
- end
-
- defp uri_decode(path) do
- # TODO: Remove rescue as this can't fail from Elixir v1.13
- try do
- URI.decode(path)
- rescue
- ArgumentError ->
- raise InvalidPathError
- end
- end
-
- defp allowed?(_only_rules, []), do: false
- defp allowed?({[], []}, _list), do: true
-
- defp allowed?({full, prefix}, [h | _]) do
- h in full or (prefix != [] and match?({0, _}, :binary.match(h, prefix)))
- end
-
- defp maybe_put_content_type(conn, false, _, _), do: conn
-
- defp maybe_put_content_type(conn, _, types, filename) do
- content_type = Map.get(types, filename) || MIME.from_path(filename)
-
- conn
- |> put_resp_header("content-type", content_type)
- end
-
- defp serve_static({content_encoding, file_info, path}, conn, segments, range, options) do
- %{
- qs_cache: qs_cache,
- et_cache: et_cache,
- et_generation: et_generation,
- headers: headers,
- content_types: types,
- set_content_type: set_content_type
- } = options
-
- case put_cache_header(conn, qs_cache, et_cache, et_generation, file_info, path) do
- {:stale, conn} ->
- filename = List.last(segments)
-
- conn
- |> maybe_put_content_type(set_content_type, types, filename)
- |> put_resp_header("accept-ranges", "bytes")
- |> maybe_add_encoding(content_encoding)
- |> merge_headers(headers)
- |> serve_range(file_info, path, range, options)
-
- {:fresh, conn} ->
- conn
- |> maybe_add_vary(options)
- |> send_resp(304, "")
- |> halt()
- end
- end
-
- defp serve_static(:error, conn, _segments, _range, _options) do
- conn
- end
-
- defp serve_range(conn, file_info, path, [range], options) do
- file_info(size: file_size) = file_info
-
- with %{"bytes" => bytes} <- Plug.Conn.Utils.params(range),
- {range_start, range_end} <- start_and_end(bytes, file_size) do
- send_range(conn, path, range_start, range_end, file_size, options)
- else
- _ -> send_entire_file(conn, path, options)
- end
- end
-
- defp serve_range(conn, _file_info, path, _range, options) do
- send_entire_file(conn, path, options)
- end
-
- defp start_and_end("-" <> rest, file_size) do
- case Integer.parse(rest) do
- {last, ""} when last > 0 and last <= file_size -> {file_size - last, file_size - 1}
- _ -> :error
- end
- end
-
- defp start_and_end(range, file_size) do
- case Integer.parse(range) do
- {first, "-"} when first >= 0 ->
- {first, file_size - 1}
-
- {first, "-" <> rest} when first >= 0 ->
- case Integer.parse(rest) do
- {last, ""} when last >= first -> {first, min(last, file_size - 1)}
- _ -> :error
- end
-
- _ ->
- :error
- end
- end
-
- defp send_range(conn, path, 0, range_end, file_size, options) when range_end == file_size - 1 do
- send_entire_file(conn, path, options)
- end
-
- defp send_range(conn, path, range_start, range_end, file_size, _options) do
- length = range_end - range_start + 1
-
- conn
- |> put_resp_header("content-range", "bytes #{range_start}-#{range_end}/#{file_size}")
- |> send_file(206, path, range_start, length)
- |> halt()
- end
-
- defp send_entire_file(conn, path, options) do
- conn
- |> maybe_add_vary(options)
- |> send_file(200, path)
- |> halt()
- end
-
- defp maybe_add_encoding(conn, nil), do: conn
- defp maybe_add_encoding(conn, ce), do: put_resp_header(conn, "content-encoding", ce)
-
- defp maybe_add_vary(conn, %{encodings: encodings}) do
- # If we serve gzip or brotli at any moment, we need to set the proper vary
- # header regardless of whether we are serving gzip content right now.
- # See: http://www.fastly.com/blog/best-practices-for-using-the-vary-header/
- if encodings != [] do
- update_in(conn.resp_headers, &[{"vary", "Accept-Encoding"} | &1])
- else
- conn
- end
- end
-
- defp put_cache_header(
- %Conn{query_string: "vsn=" <> _} = conn,
- qs_cache,
- _et_cache,
- _et_generation,
- _file_info,
- _path
- )
- when is_binary(qs_cache) do
- {:stale, put_resp_header(conn, "cache-control", qs_cache)}
- end
-
- defp put_cache_header(conn, _qs_cache, et_cache, et_generation, file_info, path)
- when is_binary(et_cache) do
- etag = etag_for_path(file_info, et_generation, path)
-
- conn =
- conn
- |> put_resp_header("cache-control", et_cache)
- |> put_resp_header("etag", etag)
-
- if etag in get_req_header(conn, "if-none-match") do
- {:fresh, conn}
- else
- {:stale, conn}
- end
- end
-
- defp put_cache_header(conn, _, _, _, _, _) do
- {:stale, conn}
- end
-
- defp etag_for_path(file_info, et_generation, path) do
- case et_generation do
- {module, function, args} ->
- apply(module, function, [path | args])
-
- nil ->
- file_info(size: size, mtime: mtime) = file_info
- <", {size, mtime} |> :erlang.phash2() |> Integer.to_string(16)::binary, ?">>
- end
- end
-
- defp file_encoding(conn, path, [_range], _encodings) do
- # We do not support compression for range queries.
- file_encoding(conn, path, nil, [])
- end
-
- defp file_encoding(conn, path, _range, encodings) do
- encoded =
- Enum.find_value(encodings, fn {encoding, ext} ->
- if file_info = accept_encoding?(conn, encoding) && regular_file_info(path <> ext) do
- {encoding, file_info, path <> ext}
- end
- end)
-
- cond do
- not is_nil(encoded) ->
- encoded
-
- file_info = regular_file_info(path) ->
- {nil, file_info, path}
-
- true ->
- :error
- end
- end
-
- defp regular_file_info(path) do
- case :prim_file.read_file_info(path) do
- {:ok, file_info(type: :regular) = file_info} ->
- file_info
-
- _ ->
- nil
- end
- end
-
- defp accept_encoding?(conn, encoding) do
- encoding? = &String.contains?(&1, [encoding, "*"])
-
- Enum.any?(get_req_header(conn, "accept-encoding"), fn accept ->
- accept |> Plug.Conn.Utils.list() |> Enum.any?(encoding?)
- end)
- end
-
- defp maybe_add(list, key, value, true), do: list ++ [{key, value}]
- defp maybe_add(list, _key, _value, false), do: list
-
- defp path({module, function, arguments}, segments)
- when is_atom(module) and is_atom(function) and is_list(arguments),
- do: Enum.join([apply(module, function, arguments) | segments], "/")
-
- defp path({app, from}, segments) when is_atom(app) and is_binary(from),
- do: Enum.join([Application.app_dir(app), from | segments], "/")
-
- defp path(from, segments),
- do: Enum.join([from | segments], "/")
-
- defp subset([h | expected], [h | actual]), do: subset(expected, actual)
- defp subset([], actual), do: actual
- defp subset(_, _), do: []
-
- defp invalid_path?(list) do
- invalid_path?(list, :binary.compile_pattern(["/", "\\", ":", "\0"]))
- end
-
- defp invalid_path?([h | _], _match) when h in [".", "..", ""], do: true
- defp invalid_path?([h | t], match), do: String.contains?(h, match) or invalid_path?(t)
- defp invalid_path?([], _match), do: false
-
- defp merge_headers(conn, {module, function, args}) do
- merge_headers(conn, apply(module, function, [conn | args]))
- end
-
- defp merge_headers(conn, headers) do
- merge_resp_headers(conn, headers)
- end
-end
diff --git a/lib/pleroma/web/plugs/uploaded_media.ex b/lib/pleroma/web/plugs/uploaded_media.ex
index 746203087..357fcb432 100644
--- a/lib/pleroma/web/plugs/uploaded_media.ex
+++ b/lib/pleroma/web/plugs/uploaded_media.ex
@@ -88,12 +88,12 @@ defp get_media(conn, {:static_dir, directory}, opts) do
Map.get(opts, :static_plug_opts)
|> Map.put(:at, [@path])
|> Map.put(:from, directory)
- |> Map.put(:set_content_type, false)
+ |> Map.put(:content_type, false)
conn =
conn
|> set_content_type(opts, conn.request_path)
- |> Pleroma.Web.Plugs.StaticNoCT.call(static_opts)
+ |> Plug.Static.call(static_opts)
if conn.halted do
conn
From 7151ef4718e0d096d587829bf872ccfd7fa4810b Mon Sep 17 00:00:00 2001
From: Oneric
Date: Wed, 30 Oct 2024 23:18:10 +0100
Subject: [PATCH 47/95] Add SafeZip module
This will replace all the slightly different safety workarounds at
different ZIP handling sites and ensure safety is actually consistently
enforced everywhere while also making code cleaner and easiert to
follow.
---
lib/pleroma/safe_zip.ex | 216 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 216 insertions(+)
create mode 100644 lib/pleroma/safe_zip.ex
diff --git a/lib/pleroma/safe_zip.ex b/lib/pleroma/safe_zip.ex
new file mode 100644
index 000000000..35fe2be19
--- /dev/null
+++ b/lib/pleroma/safe_zip.ex
@@ -0,0 +1,216 @@
+# Akkoma: Magically expressive social media
+# Copyright © 2024 Akkoma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.SafeZip do
+ @moduledoc """
+ Wraps the subset of Erlang's zip module we’d like to use
+ but enforces path-traversal safety everywhere and other checks.
+
+ For convenience almost all functions accept both elixir strings and charlists,
+ but output elixir strings themselves. However, this means the input parameter type
+ can no longer be used to distinguish archive file paths from archive binary data in memory,
+ thus where needed both a _data and _file variant are provided.
+ """
+
+ @type text() :: String.t() | [char()]
+
+ defp is_safe_path?(path) do
+ # Path accepts elixir’s chardata()
+ case Path.safe_relative(path) do
+ {:ok, _} -> true
+ _ -> false
+ end
+ end
+
+ defp is_safe_type?(file_type) do
+ if file_type in [:regular, :directory] do
+ true
+ else
+ false
+ end
+ end
+
+ defp maybe_add_file(_type, _path_charlist, nil), do: nil
+
+ defp maybe_add_file(:regular, path_charlist, file_list),
+ do: [to_string(path_charlist) | file_list]
+
+ defp maybe_add_file(_type, _path_charlist, file_list), do: file_list
+
+ @spec check_safe_archive_and_maybe_list_files(binary() | [char()], [term()], boolean()) ::
+ {:ok, [String.t()]} | {:error, reason :: term()}
+ defp check_safe_archive_and_maybe_list_files(archive, opts, list) do
+ acc = if list, do: [], else: nil
+
+ with {:ok, table} <- :zip.table(archive, opts) do
+ Enum.reduce_while(table, {:ok, acc}, fn
+ # ZIP comment
+ {:zip_comment, _}, acc ->
+ {:cont, acc}
+
+ # File entry
+ {:zip_file, path, info, _comment, _offset, _comp_size}, {:ok, fl} ->
+ with {_, type} <- {:get_type, elem(info, 2)},
+ {_, true} <- {:type, is_safe_type?(type)},
+ {_, true} <- {:safe_path, is_safe_path?(path)} do
+ {:cont, {:ok, maybe_add_file(type, path, fl)}}
+ else
+ {:get_type, e} ->
+ {:halt,
+ {:error, "Couldn't determine file type of ZIP entry at #{path} (#{inspect(e)})"}}
+
+ {:type, _} ->
+ {:halt, {:error, "Potentially unsafe file type in ZIP at: #{path}"}}
+
+ {:safe_path, _} ->
+ {:halt, {:error, "Unsafe path in ZIP: #{path}"}}
+ end
+
+ # new OTP version?
+ _, _acc ->
+ {:halt, {:error, "Unknown ZIP record type"}}
+ end)
+ end
+ end
+
+ @spec check_safe_archive_and_list_files(binary() | [char()], [term()]) ::
+ {:ok, [String.t()]} | {:error, reason :: term()}
+ defp check_safe_archive_and_list_files(archive, opts \\ []) do
+ check_safe_archive_and_maybe_list_files(archive, opts, true)
+ end
+
+ @spec check_safe_archive(binary() | [char()], [term()]) :: :ok | {:error, reason :: term()}
+ defp check_safe_archive(archive, opts \\ []) do
+ case check_safe_archive_and_maybe_list_files(archive, opts, false) do
+ {:ok, _} -> :ok
+ error -> error
+ end
+ end
+
+ @spec check_safe_file_list([text()], text()) :: :ok | {:error, term()}
+ defp check_safe_file_list([], _), do: :ok
+
+ defp check_safe_file_list([path | tail], cwd) do
+ with {_, true} <- {:path, is_safe_path?(path)},
+ {_, {:ok, fstat}} <- {:stat, File.stat(Path.expand(path, cwd))},
+ {_, true} <- {:type, is_safe_type?(fstat.type)} do
+ check_safe_file_list(tail, cwd)
+ else
+ {:path, _} ->
+ {:error, "Unsafe path escaping cwd: #{path}"}
+
+ {:stat, e} ->
+ {:error, "Unable to check file type of #{path}: #{inspect(e)}"}
+
+ {:type, _} ->
+ {:error, "Unsafe type at #{path}"}
+ end
+ end
+
+ defp check_safe_file_list(_, _), do: {:error, "Malformed file_list"}
+
+ @doc """
+ Checks whether the archive data contais file entries for all paths from fset
+
+ Note this really only accepts entries corresponding to regular _files_,
+ if a path is contained as for example an directory, this does not count as a match.
+ """
+ @spec contains_all_data?(binary(), MapSet.t()) :: true | false
+ def contains_all_data?(archive_data, fset) do
+ with {:ok, table} <- :zip.table(archive_data) do
+ remaining =
+ Enum.reduce(table, fset, fn
+ {:zip_file, path, info, _comment, _offset, _comp_size}, fset ->
+ if elem(info, 2) == :regular do
+ MapSet.delete(fset, path)
+ else
+ fset
+ end
+
+ _, _ ->
+ fset
+ end)
+ |> MapSet.size()
+
+ if remaining == 0, do: true, else: false
+ else
+ _ -> false
+ end
+ end
+
+ @doc """
+ List all file entries in ZIP, or error if invalid or unsafe.
+
+ Note this really only lists regular files, no directories, ZIP comments or other types!
+ """
+ @spec list_dir_file(text()) :: {:ok, [String.t()]} | {:error, reason :: term()}
+ def list_dir_file(archive) do
+ path = to_charlist(archive)
+ check_safe_archive_and_list_files(path)
+ end
+
+ defp stringify_zip({:ok, {fname, data}}), do: {:ok, {to_string(fname), data}}
+ defp stringify_zip({:ok, fname}), do: {:ok, to_string(fname)}
+ defp stringify_zip(ret), do: ret
+
+ @spec zip(text(), text(), [text()], boolean()) ::
+ {:ok, file_name :: String.t()}
+ | {:ok, {file_name :: String.t(), file_data :: binary()}}
+ | {:error, reason :: term()}
+ def zip(name, file_list, cwd, memory \\ false) do
+ opts = [{:cwd, to_charlist(cwd)}]
+ opts = if memory, do: [:memory | opts], else: opts
+
+ with :ok <- check_safe_file_list(file_list, cwd) do
+ file_list = for f <- file_list, do: to_charlist(f)
+ name = to_charlist(name)
+ stringify_zip(:zip.zip(name, file_list, opts))
+ end
+ end
+
+ @spec unzip_file(text(), text(), [text()] | nil) ::
+ {:ok, [String.t()]}
+ | {:error, reason :: term()}
+ | {:error, {name :: text(), reason :: term()}}
+ def unzip_file(archive, target_dir, file_list \\ nil) do
+ do_unzip(to_charlist(archive), to_charlist(target_dir), file_list)
+ end
+
+ @spec unzip_data(binary(), text(), [text()] | nil) ::
+ {:ok, [String.t()]}
+ | {:error, reason :: term()}
+ | {:error, {name :: text(), reason :: term()}}
+ def unzip_data(archive, target_dir, file_list \\ nil) do
+ do_unzip(archive, to_charlist(target_dir), file_list)
+ end
+
+ defp stringify_unzip({:ok, [{_fname, _data} | _] = filebinlist}),
+ do: {:ok, Enum.map(filebinlist, fn {fname, data} -> {to_string(fname), data} end)}
+
+ defp stringify_unzip({:ok, [_fname | _] = filelist}),
+ do: {:ok, Enum.map(filelist, fn fname -> to_string(fname) end)}
+
+ defp stringify_unzip({:error, {fname, term}}), do: {:error, {to_string(fname), term}}
+ defp stringify_unzip(ret), do: ret
+
+ @spec do_unzip(binary() | [char()], text(), [text()] | nil) ::
+ {:ok, [String.t()]}
+ | {:error, reason :: term()}
+ | {:error, {name :: text(), reason :: term()}}
+ defp do_unzip(archive, target_dir, file_list) do
+ opts =
+ if file_list != nil do
+ [
+ file_list: for(f <- file_list, do: to_charlist(f)),
+ cwd: target_dir
+ ]
+ else
+ [cwd: target_dir]
+ end
+
+ with :ok <- check_safe_archive(archive) do
+ stringify_unzip(:zip.unzip(archive, opts))
+ end
+ end
+end
From 96fe080e6eea08aeedf0cd982e997168d3d10d48 Mon Sep 17 00:00:00 2001
From: Oneric
Date: Wed, 30 Oct 2024 23:24:05 +0100
Subject: [PATCH 48/95] Convert all raw :zip usage to SafeZip
Notably at least two instances were not properly guarded from path
traversal attack before and are only now fixed by using SafeZip:
- frontend installation did never check for malicious paths.
But given a malicious froontend could already, e.g. steal
all user tokens even without this, in the real world
admins should only use frontends from trusted sources
and the practical implications are minimal
- the emoji pack update/upload API taking a ZIP file
did not protect against path traversal. While atm
only admins can use these emoji endpoints, emoji
packs are typically considered "harmless" and used
without prior verification from various sources.
Thus this appears more concerning.
---
lib/mix/tasks/pleroma/emoji.ex | 15 ++-----
lib/pleroma/emoji/pack.ex | 75 +++++++++++++++-------------------
lib/pleroma/frontend.ex | 23 +++--------
lib/pleroma/user/backup.ex | 7 ++--
4 files changed, 43 insertions(+), 77 deletions(-)
diff --git a/lib/mix/tasks/pleroma/emoji.ex b/lib/mix/tasks/pleroma/emoji.ex
index aa8131254..2b13b3257 100644
--- a/lib/mix/tasks/pleroma/emoji.ex
+++ b/lib/mix/tasks/pleroma/emoji.ex
@@ -93,6 +93,7 @@ def run(["get-packs" | args]) do
)
files = fetch_and_decode!(files_loc)
+ files_to_unzip = for({_, f} <- files, do: f)
shell_info(IO.ANSI.format(["Unpacking ", :bright, pack_name]))
@@ -103,17 +104,7 @@ def run(["get-packs" | args]) do
pack_name
])
- files_to_unzip =
- Enum.map(
- files,
- fn {_, f} -> to_charlist(f) end
- )
-
- {:ok, _} =
- :zip.unzip(binary_archive,
- cwd: to_charlist(pack_path),
- file_list: files_to_unzip
- )
+ {:ok, _} = Pleroma.SafeZip.unzip_data(binary_archive, pack_path, files_to_unzip)
shell_info(IO.ANSI.format(["Writing pack.json for ", :bright, pack_name]))
@@ -202,7 +193,7 @@ def run(["gen-pack" | args]) do
tmp_pack_dir = Path.join(System.tmp_dir!(), "emoji-pack-#{name}")
- {:ok, _} = :zip.unzip(binary_archive, cwd: String.to_charlist(tmp_pack_dir))
+ {:ok, _} = Pleroma.SafeZip.unzip_data(binary_archive, tmp_pack_dir)
emoji_map = Pleroma.Emoji.Loader.make_shortcode_to_file_map(tmp_pack_dir, exts)
diff --git a/lib/pleroma/emoji/pack.ex b/lib/pleroma/emoji/pack.ex
index e95320a07..fdd31d165 100644
--- a/lib/pleroma/emoji/pack.ex
+++ b/lib/pleroma/emoji/pack.ex
@@ -25,6 +25,7 @@ defmodule Pleroma.Emoji.Pack do
alias Pleroma.Emoji
alias Pleroma.Emoji.Pack
alias Pleroma.Utils
+ alias Pleroma.SafeZip
# Invalid/Malicious names are supposed to be filtered out before path joining,
# but there are many entrypoints to affected functions so as the code changes
@@ -95,22 +96,20 @@ def delete(name) do
end
end
- @spec unpack_zip_emojies(list(tuple())) :: list(map())
- defp unpack_zip_emojies(zip_files) do
- Enum.reduce(zip_files, [], fn
- {_, path, s, _, _, _}, acc when elem(s, 2) == :regular ->
- with(
- filename <- Path.basename(path),
- shortcode <- Path.basename(filename, Path.extname(filename)),
- false <- Emoji.exist?(shortcode)
- ) do
- [%{path: path, filename: path, shortcode: shortcode} | acc]
- else
- _ -> acc
- end
-
- _, acc ->
- acc
+ @spec map_zip_emojies(list(String.t())) :: list(map())
+ defp map_zip_emojies(zip_files) do
+ Enum.reduce(zip_files, [], fn path, acc ->
+ with(
+ filename <- Path.basename(path),
+ shortcode <- Path.basename(filename, Path.extname(filename)),
+ # note: this only checks the shortcode, if an emoji already exists on the same path, but
+ # with a different shortcode, the existing one will be degraded to an alias of the new
+ false <- Emoji.exist?(shortcode)
+ ) do
+ [%{path: path, filename: path, shortcode: shortcode} | acc]
+ else
+ _ -> acc
+ end
end)
end
@@ -118,15 +117,12 @@ defp unpack_zip_emojies(zip_files) do
{:ok, t()}
| {:error, File.posix() | atom()}
def add_file(%Pack{} = pack, _, _, %Plug.Upload{content_type: "application/zip"} = file) do
- with {:ok, zip_files} <- :zip.table(to_charlist(file.path)),
- [_ | _] = emojies <- unpack_zip_emojies(zip_files),
+ with {:ok, zip_files} <- SafeZip.list_dir_file(file.path),
+ [_ | _] = emojies <- map_zip_emojies(zip_files),
{:ok, tmp_dir} <- Utils.tmp_dir("emoji") do
try do
{:ok, _emoji_files} =
- :zip.unzip(
- to_charlist(file.path),
- [{:file_list, Enum.map(emojies, & &1[:path])}, {:cwd, to_charlist(tmp_dir)}]
- )
+ SafeZip.unzip_file(file.path, tmp_dir, Enum.map(emojies, & &1[:path]))
{_, updated_pack} =
Enum.map_reduce(emojies, pack, fn item, emoji_pack ->
@@ -446,16 +442,9 @@ defp downloadable?(pack) do
end
defp create_archive_and_cache(pack, hash) do
- files = [
- ~c"pack.json"
- | Enum.map(pack.files, fn {_, file} ->
- {:ok, file} = Path.safe_relative(file)
- to_charlist(file)
- end)
- ]
-
- {:ok, {_, result}} =
- :zip.zip(~c"#{pack.name}.zip", files, [:memory, cwd: to_charlist(pack.path)])
+ pack_file_list = Enum.into(pack.files, [], fn {_, f} -> f end)
+ files = ["pack.json" | pack_file_list]
+ {:ok, {_, result}} = SafeZip.zip("#{pack.name}.zip", files, pack.path, true)
ttl_per_file = Pleroma.Config.get!([:emoji, :shared_pack_cache_seconds_per_file])
overall_ttl = :timer.seconds(ttl_per_file * Enum.count(files))
@@ -626,11 +615,10 @@ defp copy_as(remote_pack, local_name) do
defp unzip(archive, pack_info, remote_pack, local_pack) do
with :ok <- File.mkdir_p!(local_pack.path) do
- files = Enum.map(remote_pack["files"], fn {_, path} -> to_charlist(path) end)
+ files = Enum.map(remote_pack["files"], fn {_, path} -> path end)
# Fallback cannot contain a pack.json file
- files = if pack_info[:fallback], do: files, else: [~c"pack.json" | files]
-
- :zip.unzip(archive, cwd: to_charlist(local_pack.path), file_list: files)
+ files = if pack_info[:fallback], do: files, else: ["pack.json" | files]
+ SafeZip.unzip_data(archive, local_pack.path, files)
end
end
@@ -693,13 +681,14 @@ defp update_sha_and_save_metadata(pack, data) do
end
defp validate_has_all_files(pack, zip) do
- with {:ok, f_list} <- :zip.unzip(zip, [:memory]) do
- # Check if all files from the pack.json are in the archive
- pack.files
- |> Enum.all?(fn {_, from_manifest} ->
- List.keyfind(f_list, to_charlist(from_manifest), 0)
+ # Check if all files from the pack.json are in the archive
+ eset =
+ Enum.reduce(pack.files, MapSet.new(), fn
+ {_, file}, s -> MapSet.put(s, to_charlist(file))
end)
- |> if(do: :ok, else: {:error, :incomplete})
- end
+
+ if SafeZip.contains_all_data?(zip, eset),
+ do: :ok,
+ else: {:error, :incomplete}
end
end
diff --git a/lib/pleroma/frontend.ex b/lib/pleroma/frontend.ex
index a309d8467..3ce7910cf 100644
--- a/lib/pleroma/frontend.ex
+++ b/lib/pleroma/frontend.ex
@@ -70,25 +70,12 @@ defp download_or_unzip(_frontend_info, temp_dir, file) do
end
def unzip(zip, dest) do
- with {:ok, unzipped} <- :zip.unzip(zip, [:memory]) do
- File.rm_rf!(dest)
- File.mkdir_p!(dest)
+ File.rm_rf!(dest)
+ File.mkdir_p!(dest)
- Enum.each(unzipped, fn {filename, data} ->
- path = filename
-
- new_file_path = Path.join(dest, path)
-
- new_file_path
- |> Path.dirname()
- |> File.rm()
-
- new_file_path
- |> Path.dirname()
- |> File.mkdir_p!()
-
- File.write!(new_file_path, data)
- end)
+ case Pleroma.SafeZip.unzip_data(zip, dest) do
+ {:ok, _} -> :ok
+ error -> error
end
end
diff --git a/lib/pleroma/user/backup.ex b/lib/pleroma/user/backup.ex
index de967abe3..21c2948a0 100644
--- a/lib/pleroma/user/backup.ex
+++ b/lib/pleroma/user/backup.ex
@@ -119,7 +119,7 @@ def process(%__MODULE__{} = backup) do
end
end
- @files [~c"actor.json", ~c"outbox.json", ~c"likes.json", ~c"bookmarks.json"]
+ @files ["actor.json", "outbox.json", "likes.json", "bookmarks.json"]
def export(%__MODULE__{} = backup) do
backup = Repo.preload(backup, :user)
name = String.trim_trailing(backup.file_name, ".zip")
@@ -130,10 +130,9 @@ def export(%__MODULE__{} = backup) do
:ok <- statuses(dir, backup.user),
:ok <- likes(dir, backup.user),
:ok <- bookmarks(dir, backup.user),
- {:ok, zip_path} <-
- :zip.create(String.to_charlist(dir <> ".zip"), @files, cwd: String.to_charlist(dir)),
+ {:ok, zip_path} <- Pleroma.SafeZip.zip(dir <> ".zip", @files, dir),
{:ok, _} <- File.rm_rf(dir) do
- {:ok, to_string(zip_path)}
+ {:ok, zip_path}
end
end
From 4231345f4e44438aca8f01bf49cf3419a21440a2 Mon Sep 17 00:00:00 2001
From: Oneric
Date: Fri, 14 Feb 2025 21:52:29 +0100
Subject: [PATCH 49/95] cosmetic/emoji/pack: fix spelling
There might be further debate about "emoji" vs "emojis" for the plural
but a grep shows the latter is already widely used in our codebase.
---
lib/pleroma/emoji/pack.ex | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/lib/pleroma/emoji/pack.ex b/lib/pleroma/emoji/pack.ex
index fdd31d165..a841f5ac6 100644
--- a/lib/pleroma/emoji/pack.ex
+++ b/lib/pleroma/emoji/pack.ex
@@ -96,8 +96,8 @@ def delete(name) do
end
end
- @spec map_zip_emojies(list(String.t())) :: list(map())
- defp map_zip_emojies(zip_files) do
+ @spec map_zip_emojis(list(String.t())) :: list(map())
+ defp map_zip_emojis(zip_files) do
Enum.reduce(zip_files, [], fn path, acc ->
with(
filename <- Path.basename(path),
@@ -118,14 +118,14 @@ defp map_zip_emojies(zip_files) do
| {:error, File.posix() | atom()}
def add_file(%Pack{} = pack, _, _, %Plug.Upload{content_type: "application/zip"} = file) do
with {:ok, zip_files} <- SafeZip.list_dir_file(file.path),
- [_ | _] = emojies <- map_zip_emojies(zip_files),
+ [_ | _] = emojis <- map_zip_emojis(zip_files),
{:ok, tmp_dir} <- Utils.tmp_dir("emoji") do
try do
{:ok, _emoji_files} =
- SafeZip.unzip_file(file.path, tmp_dir, Enum.map(emojies, & &1[:path]))
+ SafeZip.unzip_file(file.path, tmp_dir, Enum.map(emojis, & &1[:path]))
{_, updated_pack} =
- Enum.map_reduce(emojies, pack, fn item, emoji_pack ->
+ Enum.map_reduce(emojis, pack, fn item, emoji_pack ->
emoji_file = %Plug.Upload{
filename: item[:filename],
path: path_join_safe(tmp_dir, item[:path])
From d68a5f6c56c2cb68456a3a375460ae1a189e9417 Mon Sep 17 00:00:00 2001
From: Oneric
Date: Mon, 30 Dec 2024 17:59:21 +0100
Subject: [PATCH 50/95] Protected against counterfeit local docs being posted
Only possible if actor keys leaked first
thus log with alert level
---
lib/pleroma/web/federator.ex | 8 ++++
.../activity_pub_controller_test.exs | 8 ++--
test/pleroma/workers/receiver_worker_test.exs | 37 ++++++++++++++++++-
3 files changed, 48 insertions(+), 5 deletions(-)
diff --git a/lib/pleroma/web/federator.ex b/lib/pleroma/web/federator.ex
index ce1dd8fa8..ab0cabb56 100644
--- a/lib/pleroma/web/federator.ex
+++ b/lib/pleroma/web/federator.ex
@@ -94,6 +94,7 @@ def perform(:incoming_ap_doc, params) do
with nil <- Activity.normalize(params["id"]),
{_, :ok} <-
{:correct_origin?, Containment.contain_origin_from_id(actor, params)},
+ {_, :ok, _} <- {:local, Containment.contain_local_fetch(actor), actor},
{:ok, activity} <- Transmogrifier.handle_incoming(params) do
{:ok, activity}
else
@@ -101,6 +102,13 @@ def perform(:incoming_ap_doc, params) do
Logger.debug("Origin containment failure for #{params["id"]}")
{:error, :origin_containment_failed}
+ {:local, _, actor} ->
+ Logger.alert(
+ "Received incoming AP doc with valid signature for local actor #{actor}! Likely key leak!\n#{inspect(params)}"
+ )
+
+ {:error, :origin_containment_failed}
+
%Activity{} ->
Logger.debug("Already had #{params["id"]}")
{:error, :already_present}
diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
index 4cc7f93f5..7cf280a17 100644
--- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
@@ -16,7 +16,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
alias Pleroma.Web.ActivityPub.ObjectView
alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.ActivityPub.UserView
- alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.Endpoint
alias Pleroma.Workers.ReceiverWorker
@@ -1113,7 +1112,8 @@ test "it clears `unreachable` federation status of the sender", %{conn: conn, da
end
test "it removes all follower collections but actor's", %{conn: conn} do
- [actor, recipient] = insert_pair(:user)
+ actor = insert(:user, local: false)
+ recipient = insert(:user, local: true)
actor = with_signing_key(actor)
to = [
@@ -1127,7 +1127,7 @@ test "it removes all follower collections but actor's", %{conn: conn} do
data = %{
"@context" => ["https://www.w3.org/ns/activitystreams"],
"type" => "Create",
- "id" => Utils.generate_activity_id(),
+ "id" => actor.ap_id <> "/create/12345",
"to" => to,
"cc" => cc,
"actor" => actor.ap_id,
@@ -1137,7 +1137,7 @@ test "it removes all follower collections but actor's", %{conn: conn} do
"cc" => cc,
"content" => "It's a note",
"attributedTo" => actor.ap_id,
- "id" => Utils.generate_object_id()
+ "id" => actor.ap_id <> "/note/12345"
}
}
diff --git a/test/pleroma/workers/receiver_worker_test.exs b/test/pleroma/workers/receiver_worker_test.exs
index e816d8db5..9fd0e1fa8 100644
--- a/test/pleroma/workers/receiver_worker_test.exs
+++ b/test/pleroma/workers/receiver_worker_test.exs
@@ -7,13 +7,16 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do
use Oban.Testing, repo: Pleroma.Repo
@moduletag :mocked
+ import ExUnit.CaptureLog
import Mock
import Pleroma.Factory
+ alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Workers.ReceiverWorker
test "it ignores MRF reject" do
- params = insert(:note).data
+ user = insert(:user, local: false)
+ params = insert(:note, user: user, data: %{"id" => user.ap_id <> "/note/1"}).data
with_mock Pleroma.Web.ActivityPub.Transmogrifier,
handle_incoming: fn _ -> {:reject, "MRF"} end do
@@ -23,4 +26,36 @@ test "it ignores MRF reject" do
})
end
end
+
+ test "it errors on receiving local documents" do
+ actor = insert(:user, local: true)
+ recipient = insert(:user, local: true)
+
+ to = [recipient.ap_id]
+ cc = []
+
+ params = %{
+ "@context" => ["https://www.w3.org/ns/activitystreams"],
+ "type" => "Create",
+ "id" => Utils.generate_activity_id(),
+ "to" => to,
+ "cc" => cc,
+ "actor" => actor.ap_id,
+ "object" => %{
+ "type" => "Note",
+ "to" => to,
+ "cc" => cc,
+ "content" => "It's a note",
+ "attributedTo" => actor.ap_id,
+ "id" => Utils.generate_object_id()
+ }
+ }
+
+ assert capture_log(fn ->
+ assert {:discard, :origin_containment_failed} ==
+ ReceiverWorker.perform(%Oban.Job{
+ args: %{"op" => "incoming_ap_doc", "params" => params}
+ })
+ end) =~ "[alert] Received incoming AP doc with valid signature for local actor"
+ end
end
From b5fa8c6d09af4c19b7b75bd932de1576c7cb070f Mon Sep 17 00:00:00 2001
From: Oneric
Date: Mon, 30 Dec 2024 19:11:51 +0100
Subject: [PATCH 51/95] readme: drop mention of YunoHost package
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
It’s no longer listed in the catalogue and
the git repo wasn't updated in over a year
---
README.md | 3 ---
docs/docs/installation/yunohost_en.md | 9 ---------
2 files changed, 12 deletions(-)
delete mode 100644 docs/docs/installation/yunohost_en.md
diff --git a/README.md b/README.md
index e4aa25715..8d35212aa 100644
--- a/README.md
+++ b/README.md
@@ -54,9 +54,6 @@ If your platform is not supported, or you just want to be able to edit the sourc
### Docker
Docker installation is supported via [this setup](https://docs.akkoma.dev/stable/installation/docker_en/)
-### Packages
-Akkoma is packaged for [YunoHost](https://yunohost.org) and can be found and installed from the [YunoHost app catalogue](https://yunohost.org/#/apps).
-
### Compilation Troubleshooting
If you ever encounter compilation issues during the updating of Akkoma, you can try these commands and see if they fix things:
diff --git a/docs/docs/installation/yunohost_en.md b/docs/docs/installation/yunohost_en.md
deleted file mode 100644
index 0d3adb4fe..000000000
--- a/docs/docs/installation/yunohost_en.md
+++ /dev/null
@@ -1,9 +0,0 @@
-# Installing on Yunohost
-
-[YunoHost](https://yunohost.org) is a server operating system aimed at self-hosting. The YunoHost community maintains a package of Akkoma which allows you to install Akkoma on YunoHost. You can install it via the normal way through the admin web interface, or through the CLI. More information can be found at [the repo of the package](https://github.com/YunoHost-Apps/akkoma_ynh).
-
-## Questions
-
-Questions and problems related to the YunoHost parts can be done through the [YunoHost channels](https://yunohost.org/en/help).
-
-For questions about Akkoma, check out the [Akkoma community channels](../../#community-channels).
From 366065c0f635fc14b6b086783ffb844485f73d52 Mon Sep 17 00:00:00 2001
From: Oneric
Date: Sat, 11 Jan 2025 21:25:00 +0100
Subject: [PATCH 52/95] fetcher: split out core object fetch validation
To allow reuse for adapted key validation logic
---
lib/pleroma/object/fetcher.ex | 41 +++++++++++++++++++++++++----------
1 file changed, 29 insertions(+), 12 deletions(-)
diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex
index 11ed57ed5..0f129f318 100644
--- a/lib/pleroma/object/fetcher.ex
+++ b/lib/pleroma/object/fetcher.ex
@@ -265,6 +265,28 @@ def fetch_and_contain_remote_object_from_id(%{"id" => id}, is_ap_id),
def fetch_and_contain_remote_object_from_id(id, is_ap_id) when is_binary(id) do
Logger.debug("Fetching object #{id} via AP [ap_id=#{is_ap_id}]")
+ fetch_and_contain_remote_ap_doc(
+ id,
+ is_ap_id,
+ fn final_uri, data -> {Containment.contain_id_to_fetch(final_uri, data), data["id"]} end
+ )
+ end
+
+ def fetch_and_contain_remote_object_from_id(_id, _is_ap_id),
+ do: {:error, :invalid_id}
+
+ # Fetches an AP document and performing variable security checks on it.
+ #
+ # Note that the received documents "id" matching the final host domain
+ # is always enforced before the custom ID check runs.
+ @spec fetch_and_contain_remote_ap_doc(
+ String.t(),
+ boolean(),
+ (String.t(), Map.t() -> {:ok | :error, String.t() | term()})
+ ) :: {:ok, Map.t()} | {:reject, term()} | {:error, term()}
+ defp fetch_and_contain_remote_ap_doc(id, is_ap_id, verify_id) do
+ Logger.debug("Dereferencing AP doc #{}")
+
with {:valid_uri_scheme, true} <- {:valid_uri_scheme, String.starts_with?(id, "http")},
%URI{} = uri <- URI.parse(id),
{:mrf_reject_check, {:ok, nil}} <-
@@ -277,7 +299,7 @@ def fetch_and_contain_remote_object_from_id(id, is_ap_id) when is_binary(id) do
true <- !is_ap_id || final_id == id,
{:ok, data} <- safe_json_decode(body),
{_, :ok} <- {:containment, Containment.contain_origin(final_id, data)},
- {_, _, :ok} <- {:strict_id, data["id"], Containment.contain_id_to_fetch(final_id, data)} do
+ {_, {:ok, _}} <- {:strict_id, verify_id.(final_id, data)} do
unless Instances.reachable?(final_id) do
Instances.set_reachable(final_id)
end
@@ -289,14 +311,12 @@ def fetch_and_contain_remote_object_from_id(id, is_ap_id) when is_binary(id) do
# Similarly keys, either use a fragment ID and are a subobjects or a distinct ID
# but for compatibility are still a subobject presenting their owning actors ID at the toplevel.
# Refetching _once_ from the listed id, should yield a strict match afterwards.
- {:strict_id, ap_id, _} = e ->
- case is_ap_id do
- false ->
- fetch_and_contain_remote_object_from_id(ap_id, true)
-
- true ->
- log_fetch_error(id, e)
- {:error, :id_mismatch}
+ {:strict_id, {_error, ap_id}} = e ->
+ if !is_ap_id and is_binary(ap_id) do
+ fetch_and_contain_remote_ap_doc(ap_id, true, verify_id)
+ else
+ log_fetch_error(id, e)
+ {:error, :id_mismatch}
end
{:mrf_reject_check, _} = e ->
@@ -327,9 +347,6 @@ def fetch_and_contain_remote_object_from_id(id, is_ap_id) when is_binary(id) do
end
end
- def fetch_and_contain_remote_object_from_id(_id, _is_ap_id),
- do: {:error, :invalid_id}
-
# HOPEFULLY TEMPORARY
# Basically none of our Tesla mocks in tests set the (supposed to
# exist for Tesla proper) url parameter for their responses
From 70fe99d1967723edce96e8b842d88eb770c8628a Mon Sep 17 00:00:00 2001
From: Oneric
Date: Sat, 11 Jan 2025 21:25:00 +0100
Subject: [PATCH 53/95] Prevent key-actor mapping poisoning and key take overs
Previously there were mainly two attack vectors:
- for raw keys the owner <-> key mapping wasn't verified at all
- keys were retrieved with refetching allowed
and only the top-level ID was sanitised while
usually keys are but a subobject
This reintroduces public key checks in the user actor,
previously removed in 9728e2f8f71e3a33f9f6ae60da79145c09b08e06
but now adapted to account for the new mapping mechanism.
---
lib/pleroma/object/containment.ex | 12 +++++
lib/pleroma/object/fetcher.ex | 36 +++++++++++++
lib/pleroma/user/signing_key.ex | 2 +-
.../object_validators/user_validator.ex | 18 ++++++-
..._keyid_from_admin@mastdon.example.org.json | 54 +++++++++++++++++++
test/pleroma/user_test.exs | 30 +++++++++++
.../activity_pub_controller_test.exs | 20 +++----
test/support/http_request_mock.ex | 15 +++++-
8 files changed, 174 insertions(+), 13 deletions(-)
create mode 100644 test/fixtures/tesla_mock/evil@remote.example_with_keyid_from_admin@mastdon.example.org.json
diff --git a/lib/pleroma/object/containment.ex b/lib/pleroma/object/containment.ex
index 7b1cc37bd..b0944e782 100644
--- a/lib/pleroma/object/containment.ex
+++ b/lib/pleroma/object/containment.ex
@@ -157,4 +157,16 @@ def same_origin(id1, id2) do
compare_uris(uri1, uri2)
end
+
+ @doc """
+ Checks whether a key_id - owner_id pair are acceptable.
+
+ While in theory keys and actors on different domain could be verified
+ by fetching both and checking the links on both ends (if different at all),
+ this requires extra fetches and there are no known implementations with split
+ actor and key domains, thus atm this simply requires same domain.
+ """
+ def contain_key_user(key_id, user_id) do
+ same_origin(key_id, user_id)
+ end
end
diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex
index 0f129f318..c76974adc 100644
--- a/lib/pleroma/object/fetcher.ex
+++ b/lib/pleroma/object/fetcher.ex
@@ -275,6 +275,42 @@ def fetch_and_contain_remote_object_from_id(id, is_ap_id) when is_binary(id) do
def fetch_and_contain_remote_object_from_id(_id, _is_ap_id),
do: {:error, :invalid_id}
+ def fetch_and_contain_remote_key(id) do
+ Logger.debug("Fetching remote actor key #{id}")
+
+ fetch_and_contain_remote_ap_doc(
+ id,
+ # key IDs are alwas AP IDs which should resolve directly and exactly
+ true,
+ fn
+ final_uri, %{"id" => user_id, "publicKey" => %{"id" => key_id}} ->
+ # For non-fragment keys as used e.g. by GTS, the "id" won't match the fetch URI,
+ # but the key ID will. Thus do NOT strict check the top-lelve id, but byte-exact
+ # check the key ID (since for later lookups we need byte-exact matches).
+ # This relies on fetching already enforcing a domain match for toplevel id and host
+ with :ok <- Containment.contain_key_user(key_id, user_id),
+ true <- key_id == final_uri do
+ {:ok, key_id}
+ else
+ _ -> {:error, key_id}
+ end
+
+ final_uri,
+ %{"type" => "CryptographicKey", "id" => key_id, "owner" => user_id, "publicKeyPem" => _} ->
+ # XXX: refactor into separate function isntead of duplicating
+ with :ok <- Containment.contain_key_user(key_id, user_id),
+ true <- key_id == final_uri do
+ {:ok, key_id}
+ else
+ _ -> {:error, nil}
+ end
+
+ _, _ ->
+ {:error, nil}
+ end
+ )
+ end
+
# Fetches an AP document and performing variable security checks on it.
#
# Note that the received documents "id" matching the final host domain
diff --git a/lib/pleroma/user/signing_key.ex b/lib/pleroma/user/signing_key.ex
index 87149aa58..04cdced59 100644
--- a/lib/pleroma/user/signing_key.ex
+++ b/lib/pleroma/user/signing_key.ex
@@ -196,7 +196,7 @@ def fetch_remote_key(key_id) do
Logger.debug("Fetching remote key: #{key_id}")
with {:ok, _body} = resp <-
- Pleroma.Object.Fetcher.fetch_and_contain_remote_object_from_id(key_id),
+ Pleroma.Object.Fetcher.fetch_and_contain_remote_key(key_id),
{:ok, ap_id, public_key_pem} <- handle_signature_response(resp) do
Logger.debug("Fetched remote key: #{ap_id}")
# fetch the user
diff --git a/lib/pleroma/web/activity_pub/object_validators/user_validator.ex b/lib/pleroma/web/activity_pub/object_validators/user_validator.ex
index b80068e37..f75e2f87e 100644
--- a/lib/pleroma/web/activity_pub/object_validators/user_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/user_validator.ex
@@ -22,7 +22,8 @@ def validate(object, meta)
def validate(%{"type" => type, "id" => _id} = data, meta)
when type in Pleroma.Constants.actor_types() do
- with :ok <- validate_inbox(data),
+ with :ok <- validate_pubkey(data),
+ :ok <- validate_inbox(data),
:ok <- contain_collection_origin(data) do
{:ok, data, meta}
else
@@ -33,6 +34,21 @@ def validate(%{"type" => type, "id" => _id} = data, meta)
def validate(_, _), do: {:error, "Not a user object"}
+ defp validate_pubkey(%{"id" => user_id, "publicKey" => %{"id" => pk_id, "publicKeyPem" => _key}}) do
+ with {_, true} <- {:user, is_binary(user_id)},
+ {_, true} <- {:key, is_binary(pk_id)},
+ :ok <- Containment.contain_key_user(pk_id, user_id) do
+ :ok
+ else
+ {:user, _} -> {:error, "Invalid user id: #{inspect(user_id)}"}
+ {:key, _} -> {:error, "Invalid key id: #{inspect(pk_id)}"}
+ :error -> {:error, "Problematic actor-key pairing: #{user_id} - #{pk_id}"}
+ end
+ end
+
+ # pubkey is optional atm
+ defp validate_pubkey(_data), do: :ok
+
defp validate_inbox(%{"id" => id, "inbox" => inbox}) do
case Containment.same_origin(id, inbox) do
:ok -> :ok
diff --git a/test/fixtures/tesla_mock/evil@remote.example_with_keyid_from_admin@mastdon.example.org.json b/test/fixtures/tesla_mock/evil@remote.example_with_keyid_from_admin@mastdon.example.org.json
new file mode 100644
index 000000000..f6c801096
--- /dev/null
+++ b/test/fixtures/tesla_mock/evil@remote.example_with_keyid_from_admin@mastdon.example.org.json
@@ -0,0 +1,54 @@
+{
+ "@context": [
+ "https://www.w3.org/ns/activitystreams",
+ "https://w3id.org/security/v1",
+ {
+ "manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
+ "sensitive": "as:sensitive",
+ "movedTo": "as:movedTo",
+ "Hashtag": "as:Hashtag",
+ "ostatus": "http://ostatus.org#",
+ "atomUri": "ostatus:atomUri",
+ "inReplyToAtomUri": "ostatus:inReplyToAtomUri",
+ "conversation": "ostatus:conversation",
+ "toot": "http://joinmastodon.org/ns#",
+ "Emoji": "toot:Emoji",
+ "alsoKnownAs": {
+ "@id": "as:alsoKnownAs",
+ "@type": "@id"
+ }
+ }
+ ],
+ "id": "http://remote.example/users/with_key_id_of_admin-mastodon.example.org",
+ "type": "Person",
+ "following": "http://remote.example/users/evil/following",
+ "followers": "http://remote.example/users/evil/followers",
+ "inbox": "http://remote.example/users/evil/inbox",
+ "outbox": "http://remote.example/users/evil/outbox",
+ "preferredUsername": "evil",
+ "name": null,
+ "discoverable": "true",
+ "summary": "hii",
+ "url": "http://remote.example/@evil",
+ "manuallyApprovesFollowers": false,
+ "publicKey": {
+ "id": "http://mastodon.example.org/users/admin#main-key",
+ "owner": "http://remote.example/users/evil",
+ "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtc4Tir+3ADhSNF6VKrtW\nOU32T01w7V0yshmQei38YyiVwVvFu8XOP6ACchkdxbJ+C9mZud8qWaRJKVbFTMUG\nNX4+6Q+FobyuKrwN7CEwhDALZtaN2IPbaPd6uG1B7QhWorrY+yFa8f2TBM3BxnUy\nI4T+bMIZIEYG7KtljCBoQXuTQmGtuffO0UwJksidg2ffCF5Q+K//JfQagJ3UzrR+\nZXbKMJdAw4bCVJYs4Z5EhHYBwQWiXCyMGTd7BGlmMkY6Av7ZqHKC/owp3/0EWDNz\nNqF09Wcpr3y3e8nA10X40MJqp/wR+1xtxp+YGbq/Cj5hZGBG7etFOmIpVBrDOhry\nBwIDAQAB\n-----END PUBLIC KEY-----\n"
+ },
+ "attachment": [],
+ "endpoints": {
+ "sharedInbox": "http://remote.example/inbox"
+ },
+ "icon": {
+ "type": "Image",
+ "mediaType": "image/jpeg",
+ "url": "https://cdn.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
+ },
+ "image": {
+ "type": "Image",
+ "mediaType": "image/png",
+ "url": "https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
+ },
+ "alsoKnownAs": []
+}
diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs
index 0d5c9faec..b280a269c 100644
--- a/test/pleroma/user_test.exs
+++ b/test/pleroma/user_test.exs
@@ -10,6 +10,7 @@ defmodule Pleroma.UserTest do
alias Pleroma.Repo
alias Pleroma.Tests.ObanHelpers
alias Pleroma.User
+ alias Pleroma.User.SigningKey
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.CommonAPI
@@ -908,6 +909,35 @@ test "it doesn't fail on invalid alsoKnownAs entries" do
assert {:ok, %User{also_known_as: []}} =
User.get_or_fetch_by_ap_id("https://mbp.example.com/")
end
+
+ test "doesn't allow key_id poisoning" do
+ {:error, {:validate, {:error, "Problematic actor-key pairing:" <> _}}} =
+ User.fetch_by_ap_id(
+ "http://remote.example/users/with_key_id_of_admin-mastodon.example.org"
+ )
+
+ used_key_id = "http://mastodon.example.org/users/admin#main-key"
+ refute Repo.get_by(SigningKey, key_id: used_key_id)
+
+ {:ok, user} = User.fetch_by_ap_id("http://mastodon.example.org/users/admin")
+ user = SigningKey.load_key(user)
+
+ # ensure we checked for the right key before
+ assert user.signing_key.key_id == used_key_id
+ end
+
+ test "doesn't allow key_id takeovers" do
+ {:ok, user} = User.fetch_by_ap_id("http://mastodon.example.org/users/admin")
+ user = SigningKey.load_key(user)
+
+ {:error, {:validate, {:error, "Problematic actor-key pairing:" <> _}}} =
+ User.fetch_by_ap_id(
+ "http://remote.example/users/with_key_id_of_admin-mastodon.example.org"
+ )
+
+ refreshed_sk = Repo.get_by(SigningKey, key_id: user.signing_key.key_id)
+ assert refreshed_sk.user_id == user.id
+ end
end
test "returns an ap_id for a user" do
diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
index 7cf280a17..49bbb9d63 100644
--- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
@@ -561,7 +561,7 @@ test "it inserts an incoming activity into the database", %{conn: conn} do
|> assign(:valid_signature, true)
|> put_req_header(
"signature",
- "keyId=\"http://mastodon.example.org/users/admin/main-key\""
+ "keyId=\"http://mastodon.example.org/users/admin#main-key\""
)
|> put_req_header("content-type", "application/activity+json")
|> post("/inbox", data)
@@ -679,7 +679,7 @@ test "accepts Add/Remove activities", %{conn: conn} do
|> String.replace("{{nickname}}", "lain")
actor = "https://example.com/users/lain"
- key_id = "#{actor}/main-key"
+ key_id = "#{actor}#main-key"
insert(:user,
ap_id: actor,
@@ -741,7 +741,7 @@ test "accepts Add/Remove activities", %{conn: conn} do
assert "ok" ==
conn
|> assign(:valid_signature, true)
- |> put_req_header("signature", "keyId=\"#{actor}/main-key\"")
+ |> put_req_header("signature", "keyId=\"#{actor}#main-key\"")
|> put_req_header("content-type", "application/activity+json")
|> post("/inbox", data)
|> json_response(200)
@@ -764,7 +764,7 @@ test "accepts Add/Remove activities", %{conn: conn} do
assert "ok" ==
conn
|> assign(:valid_signature, true)
- |> put_req_header("signature", "keyId=\"#{actor}/main-key\"")
+ |> put_req_header("signature", "keyId=\"#{actor}#main-key\"")
|> put_req_header("content-type", "application/activity+json")
|> post("/inbox", data)
|> json_response(200)
@@ -790,7 +790,7 @@ test "mastodon pin/unpin", %{conn: conn} do
|> String.replace("{{nickname}}", "lain")
actor = "https://example.com/users/lain"
- key_id = "#{actor}/main-key"
+ key_id = "#{actor}#main-key"
sender =
insert(:user,
@@ -883,7 +883,7 @@ test "mastodon pin/unpin", %{conn: conn} do
assert "ok" ==
conn
|> assign(:valid_signature, true)
- |> put_req_header("signature", "keyId=\"#{actor}/main-key\"")
+ |> put_req_header("signature", "keyId=\"#{actor}#main-key\"")
|> put_req_header("content-type", "application/activity+json")
|> post("/inbox", data)
|> json_response(200)
@@ -915,7 +915,7 @@ test "it inserts an incoming activity into the database", %{conn: conn, data: da
conn =
conn
|> assign(:valid_signature, true)
- |> put_req_header("signature", "keyId=\"#{data["actor"]}/main-key\"")
+ |> put_req_header("signature", "keyId=\"#{data["actor"]}#main-key\"")
|> put_req_header("content-type", "application/activity+json")
|> post("/users/#{user.nickname}/inbox", data)
@@ -939,7 +939,7 @@ test "it accepts messages with to as string instead of array", %{conn: conn, dat
conn =
conn
|> assign(:valid_signature, true)
- |> put_req_header("signature", "keyId=\"#{data["actor"]}/main-key\"")
+ |> put_req_header("signature", "keyId=\"#{data["actor"]}#main-key\"")
|> put_req_header("content-type", "application/activity+json")
|> post("/users/#{user.nickname}/inbox", data)
@@ -961,7 +961,7 @@ test "it accepts messages with cc as string instead of array", %{conn: conn, dat
conn =
conn
|> assign(:valid_signature, true)
- |> put_req_header("signature", "keyId=\"#{data["actor"]}/main-key\"")
+ |> put_req_header("signature", "keyId=\"#{data["actor"]}#main-key\"")
|> put_req_header("content-type", "application/activity+json")
|> post("/users/#{user.nickname}/inbox", data)
@@ -988,7 +988,7 @@ test "it accepts messages with bcc as string instead of array", %{conn: conn, da
conn =
conn
|> assign(:valid_signature, true)
- |> put_req_header("signature", "keyId=\"#{data["actor"]}/main-key\"")
+ |> put_req_header("signature", "keyId=\"#{data["actor"]}#main-key\"")
|> put_req_header("content-type", "application/activity+json")
|> post("/users/#{user.nickname}/inbox", data)
diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex
index d14434333..4d2ad559d 100644
--- a/test/support/http_request_mock.ex
+++ b/test/support/http_request_mock.ex
@@ -424,7 +424,7 @@ def get("http://mastodon.example.org/users/admin", _, _, _) do
}}
end
- def get("http://mastodon.example.org/users/admin/main-key", _, _, _) do
+ def get("http://mastodon.example.org/users/admin#main-key", _, _, _) do
{:ok,
%Tesla.Env{
status: 200,
@@ -433,6 +433,19 @@ def get("http://mastodon.example.org/users/admin/main-key", _, _, _) do
}}
end
+ def get("http://remote.example/users/with_key_id_of_admin-mastodon.example.org" = url, _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ url: url,
+ body:
+ File.read!(
+ "test/fixtures/tesla_mock/evil@remote.example_with_keyid_from_admin@mastdon.example.org.json"
+ ),
+ headers: activitypub_object_headers()
+ }}
+ end
+
def get(
"http://mastodon.example.org/users/admin/statuses/99512778738411822/replies?min_id=99512778738411824&page=true",
_,
From cc5c1bb10c99fbd90ca396caa01115be973fb27d Mon Sep 17 00:00:00 2001
From: Oneric
Date: Sat, 11 Jan 2025 21:25:00 +0100
Subject: [PATCH 54/95] signing_key: cleanup code
In particular this avoids an unecessary roundtrip
over user_id when searching a key via its primary key_id
---
lib/pleroma/user/signing_key.ex | 21 ++++++++++-----------
1 file changed, 10 insertions(+), 11 deletions(-)
diff --git a/lib/pleroma/user/signing_key.ex b/lib/pleroma/user/signing_key.ex
index 04cdced59..69e13355a 100644
--- a/lib/pleroma/user/signing_key.ex
+++ b/lib/pleroma/user/signing_key.ex
@@ -174,12 +174,12 @@ def private_key(%User{} = user) do
Will either return the key if it exists locally, or fetch it from the remote instance.
"""
def get_or_fetch_by_key_id(key_id) do
- case key_id_to_user_id(key_id) do
+ case Repo.get_by(__MODULE__, key_id: key_id) do
nil ->
fetch_remote_key(key_id)
- user_id ->
- {:ok, Repo.get_by(__MODULE__, user_id: user_id)}
+ key ->
+ {:ok, key}
end
end
@@ -195,12 +195,11 @@ def get_or_fetch_by_key_id(key_id) do
def fetch_remote_key(key_id) do
Logger.debug("Fetching remote key: #{key_id}")
- with {:ok, _body} = resp <-
+ with {:ok, resp_body} <-
Pleroma.Object.Fetcher.fetch_and_contain_remote_key(key_id),
- {:ok, ap_id, public_key_pem} <- handle_signature_response(resp) do
+ {:ok, ap_id, public_key_pem} <- handle_signature_response(resp_body),
+ {:ok, user} <- User.get_or_fetch_by_ap_id(ap_id) do
Logger.debug("Fetched remote key: #{ap_id}")
- # fetch the user
- {:ok, user} = User.get_or_fetch_by_ap_id(ap_id)
# store the key
key = %__MODULE__{
user_id: user.id,
@@ -227,7 +226,7 @@ defp extract_key_details(%{"id" => ap_id, "publicKey" => public_key}) do
end
end
- defp handle_signature_response({:ok, body}) do
+ defp handle_signature_response(body) do
case body do
%{
"type" => "CryptographicKey",
@@ -247,9 +246,9 @@ defp handle_signature_response({:ok, body}) do
%{"error" => error} ->
{:error, error}
+
+ other ->
+ {:error, "Could not process key: #{inspect(other)}"}
end
end
-
- defp handle_signature_response({:error, e}), do: {:error, e}
- defp handle_signature_response(other), do: {:error, "Could not fetch key: #{inspect(other)}"}
end
From 3460f417769487cf25e2a022666882a9d595ef0d Mon Sep 17 00:00:00 2001
From: Oneric
Date: Sat, 11 Jan 2025 21:25:00 +0100
Subject: [PATCH 55/95] Fix user updates
User updates broke with the migration to separate signing keys
since user data carries signing keys but we didn't allow the
association data to be updated.
---
lib/pleroma/user.ex | 4 ++-
test/pleroma/user_test.exs | 50 ++++++++++++++++++++++++++++++++++++++
2 files changed, 53 insertions(+), 1 deletion(-)
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 697597e1b..5a54ab842 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -222,7 +222,9 @@ defmodule Pleroma.User do
# FOR THE FUTURE: We might want to make this a one-to-many relationship
# it's entirely possible right now, but we don't have a use case for it
- has_one(:signing_key, SigningKey, foreign_key: :user_id)
+ # XXX: in the future we’d also like to parse and honour key expiration times
+ # instead of blindly accepting any change in signing keys
+ has_one(:signing_key, SigningKey, foreign_key: :user_id, on_replace: :update)
timestamps()
end
diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs
index b280a269c..39f3ee020 100644
--- a/test/pleroma/user_test.exs
+++ b/test/pleroma/user_test.exs
@@ -2232,6 +2232,56 @@ test "removes report notifs when user isn't superuser any more" do
# is not a superuser any more
assert [] = Notification.for_user(user)
end
+
+ test "updates public key pem" do
+ # note: in the future key updates might be limited to announced expirations
+ user_org =
+ insert(:user, local: false)
+ |> with_signing_key()
+
+ old_pub = user_org.signing_key.public_key
+ new_pub = "BEGIN RSA public key"
+ refute old_pub == new_pub
+
+ {:ok, %User{} = user} =
+ User.update_and_set_cache(user_org, %{signing_key: %{public_key: new_pub}})
+
+ user = SigningKey.load_key(user)
+
+ assert user.signing_key.public_key == new_pub
+ end
+
+ test "updates public key id if valid" do
+ # note: in the future key updates might be limited to announced expirations
+ user_org =
+ insert(:user, local: false)
+ |> with_signing_key()
+
+ old_kid = user_org.signing_key.key_id
+ new_kid = old_kid <> "_v2"
+
+ {:ok, %User{} = user} =
+ User.update_and_set_cache(user_org, %{signing_key: %{key_id: new_kid}})
+
+ user = SigningKey.load_key(user)
+
+ assert user.signing_key.key_id == new_kid
+ refute Repo.get_by(SigningKey, key_id: old_kid)
+ end
+
+ test "refuses to sever existing key-user mappings" do
+ user1 =
+ insert(:user, local: false)
+ |> with_signing_key()
+
+ user2 =
+ insert(:user, local: false)
+ |> with_signing_key()
+
+ assert_raise Ecto.ConstraintError, fn ->
+ User.update_and_set_cache(user2, %{signing_key: %{key_id: user1.signing_key.key_id}})
+ end
+ end
end
describe "following/followers synchronization" do
From 2a4587f201b06a0a0a2dacaacd77ba3b82955902 Mon Sep 17 00:00:00 2001
From: Oneric
Date: Sat, 11 Jan 2025 21:25:00 +0100
Subject: [PATCH 56/95] Fix SigningKey db schema
---
...20250112000000_signing_key_nullability.exs | 24 +++++++++++++++++++
1 file changed, 24 insertions(+)
create mode 100644 priv/repo/migrations/20250112000000_signing_key_nullability.exs
diff --git a/priv/repo/migrations/20250112000000_signing_key_nullability.exs b/priv/repo/migrations/20250112000000_signing_key_nullability.exs
new file mode 100644
index 000000000..02862a5c0
--- /dev/null
+++ b/priv/repo/migrations/20250112000000_signing_key_nullability.exs
@@ -0,0 +1,24 @@
+defmodule Pleroma.Repo.Migrations.SigningKeyNullability do
+ use Ecto.Migration
+
+ import Ecto.Query
+
+ def up() do
+ # Delete existing NULL entries; they are useless
+ Pleroma.User.SigningKey
+ |> where([s], is_nil(s.user_id) or is_nil(s.public_key))
+ |> Pleroma.Repo.delete_all()
+
+ alter table(:signing_keys) do
+ modify :user_id, :uuid, null: false
+ modify :public_key, :text, null: false
+ end
+ end
+
+ def down() do
+ alter table(:signing_keys) do
+ modify :user_id, :uuid, null: true
+ modify :public_key, :text, null: true
+ end
+ end
+end
From ea2de1f28a1c783c553289637e6d54b6009a501e Mon Sep 17 00:00:00 2001
From: Oneric
Date: Sat, 11 Jan 2025 21:25:00 +0100
Subject: [PATCH 57/95] signing_key: ensure only one key per user exists
Fixes: AkkomaGang/akkoma issue 858
---
lib/pleroma/user/signing_key.ex | 13 +++++--
...50112000001_signing_key_unique_user_id.exs | 36 +++++++++++++++++++
2 files changed, 47 insertions(+), 2 deletions(-)
create mode 100644 priv/repo/migrations/20250112000001_signing_key_unique_user_id.exs
diff --git a/lib/pleroma/user/signing_key.ex b/lib/pleroma/user/signing_key.ex
index 69e13355a..705302013 100644
--- a/lib/pleroma/user/signing_key.ex
+++ b/lib/pleroma/user/signing_key.ex
@@ -201,13 +201,22 @@ def fetch_remote_key(key_id) do
{:ok, user} <- User.get_or_fetch_by_ap_id(ap_id) do
Logger.debug("Fetched remote key: #{ap_id}")
# store the key
- key = %__MODULE__{
+ key = %{
user_id: user.id,
public_key: public_key_pem,
key_id: key_id
}
- Repo.insert(key, on_conflict: :replace_all, conflict_target: :key_id)
+ key_cs =
+ cast(%__MODULE__{}, key, [:user_id, :public_key, :key_id])
+ |> unique_constraint(:user_id)
+
+ Repo.insert(key_cs,
+ # while this should never run for local users anyway, etc make sure we really never loose privkey info!
+ on_conflict: {:replace_all_except, [:inserted_at, :private_key]},
+ # if the key owner overlaps with a distinct existing key entry, this intetionally still errros
+ conflict_target: :key_id
+ )
else
e ->
Logger.debug("Failed to fetch remote key: #{inspect(e)}")
diff --git a/priv/repo/migrations/20250112000001_signing_key_unique_user_id.exs b/priv/repo/migrations/20250112000001_signing_key_unique_user_id.exs
new file mode 100644
index 000000000..00a716db8
--- /dev/null
+++ b/priv/repo/migrations/20250112000001_signing_key_unique_user_id.exs
@@ -0,0 +1,36 @@
+defmodule Pleroma.Repo.Migrations.SigningKeyUniqueUserId do
+ use Ecto.Migration
+
+ import Ecto.Query
+
+ def up() do
+ # If dupes exists for any local user we do NOT want to delete the genuine privkey alongside the fake.
+ # Instead just filter out anything pertaining to local users, if dupes exists manual intervention
+ # is required anyway and index creation will just fail later (check against legacy field in users table)
+ dupes =
+ Pleroma.User.SigningKey
+ |> join(:inner, [s], u in Pleroma.User, on: s.user_id == u.id)
+ |> group_by([s], s.user_id)
+ |> having([], count() > 1)
+ |> having([_s, u], not fragment("bool_or(?)", u.local))
+ |> select([s], s.user_id)
+
+ # Delete existing remote duplicates
+ # they’ll be reinserted on the next user update
+ # or proactively fetched when receiving a signature from it
+ Pleroma.User.SigningKey
+ |> where([s], s.user_id in subquery(dupes))
+ |> Pleroma.Repo.delete_all()
+
+ drop_if_exists(index(:signing_keys, [:user_id]))
+
+ create_if_not_exists(
+ index(:signing_keys, [:user_id], name: :signing_keys_user_id_index, unique: true)
+ )
+ end
+
+ def down() do
+ drop_if_exists(index(:signing_keys, [:user_id]))
+ create_if_not_exists(index(:signing_keys, [:user_id], name: :signing_keys_user_id_index))
+ end
+end
From 898b98e5ddb7e8acaeac93ecb1ca0948a8c7a923 Mon Sep 17 00:00:00 2001
From: Oneric
Date: Sat, 11 Jan 2025 21:25:00 +0100
Subject: [PATCH 58/95] db: drop legacy key fields in users table
---
lib/pleroma/user.ex | 1 -
.../20240625220752_move_signing_keys.exs | 2 +-
...112000002_users_drop_legacy_key_fields.exs | 31 +++++++++++++++++++
test/pleroma/user_test.exs | 1 -
4 files changed, 32 insertions(+), 3 deletions(-)
create mode 100644 priv/repo/migrations/20250112000002_users_drop_legacy_key_fields.exs
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 5a54ab842..666464cab 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -100,7 +100,6 @@ defmodule Pleroma.User do
field(:password_hash, :string)
field(:password, :string, virtual: true)
field(:password_confirmation, :string, virtual: true)
- field(:keys, :string)
field(:ap_id, :string)
field(:avatar, :map, default: %{})
field(:local, :boolean, default: true)
diff --git a/priv/repo/migrations/20240625220752_move_signing_keys.exs b/priv/repo/migrations/20240625220752_move_signing_keys.exs
index f5569ce09..743b2585a 100644
--- a/priv/repo/migrations/20240625220752_move_signing_keys.exs
+++ b/priv/repo/migrations/20240625220752_move_signing_keys.exs
@@ -11,7 +11,7 @@ def up do
# Also this MUST use select, else the migration will fail in future installs with new user fields!
from(u in Pleroma.User,
where: u.local == true,
- select: {u.id, u.keys, u.ap_id}
+ select: {u.id, fragment("?.keys", u), u.ap_id}
)
|> Repo.stream(timeout: :infinity)
|> Enum.each(fn
diff --git a/priv/repo/migrations/20250112000002_users_drop_legacy_key_fields.exs b/priv/repo/migrations/20250112000002_users_drop_legacy_key_fields.exs
new file mode 100644
index 000000000..c65817369
--- /dev/null
+++ b/priv/repo/migrations/20250112000002_users_drop_legacy_key_fields.exs
@@ -0,0 +1,31 @@
+defmodule Pleroma.Repo.Migrations.UsersDropLegacyKeyFields do
+ use Ecto.Migration
+
+ def up() do
+ alter table(:users) do
+ remove :keys, :text
+ remove :public_key, :text
+ end
+ end
+
+ def down() do
+ # Using raw query since the "keys" field may not exist in the Elixir Ecto schema
+ # causing issues when migrating data back and this requires column adds to be raw query too
+ """
+ ALTER TABLE public.users
+ ADD COLUMN keys text,
+ ADD COLUMN public_key text;
+ """
+ |> Pleroma.Repo.query!([], timeout: :infinity)
+
+ """
+ UPDATE public.users AS u
+ SET keys = s.private_key
+ FROM public.signing_keys AS s
+ WHERE s.user_id = u.id AND
+ u.local AND
+ s.private_key IS NOT NULL;
+ """
+ |> Pleroma.Repo.query!([], timeout: :infinity)
+ end
+end
diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs
index 39f3ee020..d69551985 100644
--- a/test/pleroma/user_test.exs
+++ b/test/pleroma/user_test.exs
@@ -1708,7 +1708,6 @@ test "delete/1 purges a user when they wouldn't be fully deleted" do
bio: "eyy lmao",
name: "qqqqqqq",
password_hash: "pdfk2$1b3n159001",
- keys: "RSA begin buplic key",
avatar: %{"a" => "b"},
tags: ["qqqqq"],
banner: %{"a" => "b"},
From 8a0d1309768a7b47215b3bb32b12fee4fb7f1699 Mon Sep 17 00:00:00 2001
From: Oneric
Date: Sat, 11 Jan 2025 21:25:00 +0100
Subject: [PATCH 59/95] Add tests for SigninKey module
---
test/pleroma/user/signing_key_tests.ex | 305 +++++++++++++++++++++++++
1 file changed, 305 insertions(+)
create mode 100644 test/pleroma/user/signing_key_tests.ex
diff --git a/test/pleroma/user/signing_key_tests.ex b/test/pleroma/user/signing_key_tests.ex
new file mode 100644
index 000000000..f4ea245d9
--- /dev/null
+++ b/test/pleroma/user/signing_key_tests.ex
@@ -0,0 +1,305 @@
+# Akkoma: Magically expressive social media
+# Copyright © 2024 Akkoma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.User.SigningKeyTests do
+ alias Pleroma.User
+ alias Pleroma.User.SigningKey
+ alias Pleroma.Repo
+
+ use Pleroma.DataCase
+ use Oban.Testing, repo: Pleroma.Repo
+
+ import Pleroma.Factory
+
+ defp maybe_put(map, _, nil), do: map
+ defp maybe_put(map, key, val), do: Kernel.put_in(map, key, val)
+
+ defp get_body_actor(key_id \\ nil, user_id \\ nil, owner_id \\ nil) do
+ owner_id = owner_id || user_id
+
+ File.read!("test/fixtures/tesla_mock/admin@mastdon.example.org.json")
+ |> Jason.decode!()
+ |> maybe_put(["id"], user_id)
+ |> maybe_put(["publicKey", "id"], key_id)
+ |> maybe_put(["publicKey", "owner"], owner_id)
+ |> Jason.encode!()
+ end
+
+ defp get_body_rawkey(key_id, owner, pem \\ "RSA begin buplic key") do
+ %{
+ "type" => "CryptographicKey",
+ "id" => key_id,
+ "owner" => owner,
+ "publicKeyPem" => pem
+ }
+ |> Jason.encode!()
+ end
+
+ defmacro mock_tesla(
+ url,
+ get_body,
+ status \\ 200,
+ headers \\ []
+ ) do
+ quote do
+ Tesla.Mock.mock(fn
+ %{method: :get, url: unquote(url)} ->
+ %Tesla.Env{
+ status: unquote(status),
+ body: unquote(get_body),
+ url: unquote(url),
+ headers: [
+ {"content-type",
+ "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""}
+ | unquote(headers)
+ ]
+ }
+ end)
+ end
+ end
+
+ describe "succesfully" do
+ test "inserts key and new user on fetch" do
+ ap_id_actor = "https://mastodon.example.org/signing-key-test/actor"
+ ap_id_key = ap_id_actor <> "#main-key"
+ ap_doc = get_body_actor(ap_id_key, ap_id_actor)
+ mock_tesla(ap_id_actor, ap_doc)
+
+ {:ok, %SigningKey{} = key} = SigningKey.fetch_remote_key(ap_id_key)
+ user = User.get_by_id(key.user_id)
+
+ assert match?(%User{}, user)
+ user = SigningKey.load_key(user)
+
+ assert user.ap_id == ap_id_actor
+ assert user.signing_key.key_id == ap_id_key
+ assert user.signing_key.key_id == key.key_id
+ assert user.signing_key.private_key == nil
+ end
+
+ test "updates existing key" do
+ user =
+ insert(:user, local: false, domain: "mastodon.example.org")
+ |> with_signing_key()
+
+ ap_id_actor = user.ap_id
+ ap_doc = get_body_actor(user.signing_key.key_id, ap_id_actor)
+ mock_tesla(ap_id_actor, ap_doc)
+
+ old_pem = user.signing_key.public_key
+ old_priv = user.signing_key.private_key
+
+ # note: the returned value does not fully match the value stored in the database
+ # since inserted_at isn't changed on upserts
+ {:ok, %SigningKey{} = key} = SigningKey.fetch_remote_key(user.signing_key.key_id)
+
+ refreshed_key = Repo.get_by(SigningKey, key_id: key.key_id)
+ assert match?(%SigningKey{}, refreshed_key)
+ refute refreshed_key.public_key == old_pem
+ assert refreshed_key.private_key == old_priv
+ assert refreshed_key.user_id == user.id
+ assert key.public_key == refreshed_key.public_key
+ end
+
+ test "finds known key by key_id" do
+ sk = insert(:signing_key, key_id: "https://remote.example/signing-key-test/some-kown-key")
+ {:ok, key} = SigningKey.get_or_fetch_by_key_id(sk.key_id)
+ assert sk == key
+ end
+
+ test "finds key for remote user" do
+ user_with_preload =
+ insert(:user, local: false)
+ |> with_signing_key()
+
+ user = User.get_by_id(user_with_preload.id)
+ assert !match?(%SigningKey{}, user.signing_key)
+
+ user = SigningKey.load_key(user)
+ assert match?(%SigningKey{}, user.signing_key)
+
+ # the initial "with_signing_key" doesn't set timestamps, and meta differs (loaded vs built)
+ # thus clear affected fields before comparison
+ found_sk = %{user.signing_key | inserted_at: nil, updated_at: nil, __meta__: nil}
+ ref_sk = %{user_with_preload.signing_key | __meta__: nil}
+ assert found_sk == ref_sk
+ end
+
+ test "finds remote user id by key id" do
+ user =
+ insert(:user, local: false)
+ |> with_signing_key()
+
+ uid = SigningKey.key_id_to_user_id(user.signing_key.key_id)
+ assert uid == user.id
+ end
+
+ test "finds remote user ap id by key id" do
+ user =
+ insert(:user, local: false)
+ |> with_signing_key()
+
+ uapid = SigningKey.key_id_to_ap_id(user.signing_key.key_id)
+ assert uapid == user.ap_id
+ end
+ end
+
+ test "won't fetch keys for local users" do
+ user =
+ insert(:user, local: true)
+ |> with_signing_key()
+
+ {:error, _} = SigningKey.fetch_remote_key(user.signing_key.key_id)
+ end
+
+ test "fails insert with overlapping key owner" do
+ user =
+ insert(:user, local: false)
+ |> with_signing_key()
+
+ second_key_id =
+ user.signing_key.key_id
+ |> URI.parse()
+ |> Map.put(:fragment, nil)
+ |> Map.put(:query, nil)
+ |> URI.to_string()
+ |> then(fn id -> id <> "/second_key" end)
+
+ ap_doc = get_body_rawkey(second_key_id, user.ap_id)
+ mock_tesla(second_key_id, ap_doc)
+
+ res = SigningKey.fetch_remote_key(second_key_id)
+
+ assert match?({:error, %{errors: _}}, res)
+ {:error, cs} = res
+ assert Keyword.has_key?(cs.errors, :user_id)
+ end
+
+ test "Fetched raw SigningKeys cannot take over arbitrary users" do
+ # in theory cross-domain key and actor are fine, IF and ONLY IF
+ # the actor also links back to this key, but this isn’t supported atm anyway
+ user =
+ insert(:user, local: false)
+ |> with_signing_key()
+
+ remote_key_id = "https://remote.example/keys/for_local"
+ keydoc = get_body_rawkey(remote_key_id, user.ap_id)
+ mock_tesla(remote_key_id, keydoc)
+
+ {:error, _} = SigningKey.fetch_remote_key(remote_key_id)
+
+ refreshed_org_key = Repo.get_by(SigningKey, key_id: user.signing_key.key_id)
+ refreshed_user_key = Repo.get_by(SigningKey, user_id: user.id)
+ assert match?(%SigningKey{}, refreshed_org_key)
+ assert match?(%SigningKey{}, refreshed_user_key)
+
+ actor_host = URI.parse(user.ap_id).host
+ org_key_host = URI.parse(refreshed_org_key.key_id).host
+ usr_key_host = URI.parse(refreshed_user_key.key_id).host
+ assert actor_host == org_key_host
+ assert actor_host == usr_key_host
+ refute usr_key_host == "remote.example"
+
+ assert refreshed_user_key == refreshed_org_key
+ assert user.signing_key.key_id == refreshed_org_key.key_id
+ end
+
+ test "Fetched non-raw SigningKey cannot take over arbitrary users" do
+ # this actually comes free with our fetch ID checks, but lets verify it here too for good measure
+ user =
+ insert(:user, local: false)
+ |> with_signing_key()
+
+ remote_key_id = "https://remote.example/keys#for_local"
+ keydoc = get_body_actor(remote_key_id, user.ap_id, user.ap_id)
+ mock_tesla(remote_key_id, keydoc)
+
+ {:error, _} = SigningKey.fetch_remote_key(remote_key_id)
+
+ refreshed_org_key = Repo.get_by(SigningKey, key_id: user.signing_key.key_id)
+ refreshed_user_key = Repo.get_by(SigningKey, user_id: user.id)
+ assert match?(%SigningKey{}, refreshed_org_key)
+ assert match?(%SigningKey{}, refreshed_user_key)
+
+ actor_host = URI.parse(user.ap_id).host
+ org_key_host = URI.parse(refreshed_org_key.key_id).host
+ usr_key_host = URI.parse(refreshed_user_key.key_id).host
+ assert actor_host == org_key_host
+ assert actor_host == usr_key_host
+ refute usr_key_host == "remote.example"
+
+ assert refreshed_user_key == refreshed_org_key
+ assert user.signing_key.key_id == refreshed_org_key.key_id
+ end
+
+ test "remote users sharing signing key ID don't break our database" do
+ # in principle a valid setup using this can be cosntructed,
+ # but so far not observed in practice and our db scheme cannot handle it.
+ # Thus make sure it doesn't break our db anything but gets rejected
+ key_id = "https://mastodon.example.org/the_one_key"
+
+ user1 =
+ insert(:user, local: false, domain: "mastodon.example.org")
+ |> with_signing_key(%{key_id: key_id})
+
+ key_owner = "https://mastodon.example.org/#"
+
+ user2_ap_id = user1.ap_id <> "22"
+ user2_doc = get_body_actor(user1.signing_key.key_id, user2_ap_id, key_owner)
+
+ user3_ap_id = user1.ap_id <> "333"
+ user3_doc = get_body_actor(user1.signing_key.key_id, user2_ap_id)
+
+ standalone_key_doc =
+ get_body_rawkey(key_id, "https://mastodon.example.org/#", user1.signing_key.public_key)
+
+ ap_headers = [
+ {"content-type", "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""}
+ ]
+
+ Tesla.Mock.mock(fn
+ %{method: :get, url: ^key_id} ->
+ %Tesla.Env{
+ status: 200,
+ body: standalone_key_doc,
+ url: key_id,
+ headers: ap_headers
+ }
+
+ %{method: :get, url: ^user2_ap_id} ->
+ %Tesla.Env{
+ status: 200,
+ body: user2_doc,
+ url: user2_ap_id,
+ headers: ap_headers
+ }
+
+ %{method: :get, url: ^user3_ap_id} ->
+ %Tesla.Env{
+ status: 200,
+ body: user3_doc,
+ url: user3_ap_id,
+ headers: ap_headers
+ }
+ end)
+
+ {:error, _} = SigningKey.fetch_remote_key(key_id)
+
+ {:ok, user2} = User.get_or_fetch_by_ap_id(user2_ap_id)
+ {:ok, user3} = User.get_or_fetch_by_ap_id(user3_ap_id)
+
+ {:ok, db_key} = SigningKey.get_or_fetch_by_key_id(key_id)
+
+ keys =
+ from(s in SigningKey, where: s.key_id == ^key_id)
+ |> Repo.all()
+
+ assert match?([%SigningKey{}], keys)
+ assert [db_key] == keys
+ assert db_key.user_id == user1.id
+ assert match?({:ok, _}, SigningKey.public_key(user1))
+ assert {:error, "key not found"} == SigningKey.public_key(user2)
+ assert {:error, "key not found"} == SigningKey.public_key(user3)
+ end
+end
From ee61ce61a77421eedc19c1bcde07a7cf001331c5 Mon Sep 17 00:00:00 2001
From: Oneric
Date: Sat, 11 Jan 2025 21:25:00 +0100
Subject: [PATCH 60/95] changelog: summarise preceeding changes
---
CHANGELOG.md | 3 +++
1 file changed, 3 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6172134e2..43fff4dda 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## Added
## Fixed
+- fixed some holes in SigningKey verification potentially allowing they key-user mapping to be poisoned
+- frontend ZIP files can no longer traverse to paths outside their install dir
+- fixed user updates trying but failing to renew signing key information
## Changed
- Dropped obsolete `ap_enabled` indicator from user table and associated buggy logic
From bc79bd0edf27e01675a5b392e6da5352bcd9ce7c Mon Sep 17 00:00:00 2001
From: Oneric
Date: Sat, 11 Jan 2025 21:25:00 +0100
Subject: [PATCH 61/95] cosmetic/test/user: replace deprecated clear_config
syntax
---
test/pleroma/user_test.exs | 7 +------
1 file changed, 1 insertion(+), 6 deletions(-)
diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs
index d69551985..f68bf29bc 100644
--- a/test/pleroma/user_test.exs
+++ b/test/pleroma/user_test.exs
@@ -461,12 +461,7 @@ test "it sends a welcome message if it is set" do
}
)
- setup do:
- clear_config(:mrf,
- policies: [
- Pleroma.Web.ActivityPub.MRF.SimplePolicy
- ]
- )
+ setup do: clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.SimplePolicy])
test "it sends a welcome email message if it is set" do
welcome_user = insert(:user)
From 51642a90c523379ae4982dbe0e5e99bff1019ff1 Mon Sep 17 00:00:00 2001
From: Oneric
Date: Sun, 12 Jan 2025 23:43:03 +0100
Subject: [PATCH 62/95] signature: drop unecessary round trip over user
We already got the key.
---
lib/pleroma/signature.ex | 27 +++++---------------
lib/pleroma/user.ex | 11 --------
lib/pleroma/user/signing_key.ex | 24 +++++++----------
lib/pleroma/web/plugs/http_signature_plug.ex | 10 +++++---
test/pleroma/signature_test.exs | 10 --------
test/pleroma/user_test.exs | 4 ---
6 files changed, 22 insertions(+), 64 deletions(-)
diff --git a/lib/pleroma/signature.ex b/lib/pleroma/signature.ex
index bc3baf433..86065a603 100644
--- a/lib/pleroma/signature.ex
+++ b/lib/pleroma/signature.ex
@@ -9,24 +9,11 @@ defmodule Pleroma.Signature do
alias Pleroma.User.SigningKey
require Logger
- def key_id_to_actor_id(key_id) do
- case SigningKey.key_id_to_ap_id(key_id) do
- nil ->
- # hm, we SHOULD have gotten this in the pipeline before we hit here!
- Logger.error("Could not figure out who owns the key #{key_id}")
- {:error, :key_owner_not_found}
-
- key ->
- {:ok, key}
- end
- end
-
def fetch_public_key(conn) do
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
- {:ok, %SigningKey{}} <- SigningKey.get_or_fetch_by_key_id(kid),
- {:ok, actor_id} <- key_id_to_actor_id(kid),
- {:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
- {:ok, public_key}
+ {:ok, %SigningKey{} = sk} <- SigningKey.get_or_fetch_by_key_id(kid),
+ {:ok, decoded_key} <- SigningKey.public_key_decoded(sk) do
+ {:ok, decoded_key}
else
e ->
{:error, e}
@@ -35,10 +22,10 @@ def fetch_public_key(conn) do
def refetch_public_key(conn) do
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
- {:ok, %SigningKey{}} <- SigningKey.get_or_fetch_by_key_id(kid),
- {:ok, actor_id} <- key_id_to_actor_id(kid),
- {:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
- {:ok, public_key}
+ # TODO: force a refetch of stale keys (perhaps with a backoff time based on updated_at)
+ {:ok, %SigningKey{} = sk} <- SigningKey.get_or_fetch_by_key_id(kid),
+ {:ok, decoded_key} <- SigningKey.public_key_decoded(sk) do
+ {:ok, decoded_key}
else
e ->
{:error, e}
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 666464cab..5f3ddf64a 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -2068,17 +2068,6 @@ defp create_service_actor(uri, nickname) do
defdelegate public_key(user), to: SigningKey
- def get_public_key_for_ap_id(ap_id) do
- with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
- {:ok, public_key} <- SigningKey.public_key(user) do
- {:ok, public_key}
- else
- e ->
- Logger.error("Could not get public key for #{ap_id}.\n#{inspect(e)}")
- {:error, e}
- end
- end
-
@doc "Gets or fetch a user by uri or nickname."
@spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()}
def get_or_fetch("http://" <> _host = uri), do: get_or_fetch_by_ap_id(uri)
diff --git a/lib/pleroma/user/signing_key.ex b/lib/pleroma/user/signing_key.ex
index 705302013..708bbbe19 100644
--- a/lib/pleroma/user/signing_key.ex
+++ b/lib/pleroma/user/signing_key.ex
@@ -115,24 +115,18 @@ def private_pem_to_public_pem(private_pem) do
{:ok, :public_key.pem_encode([public_key])}
end
- @spec public_key(User.t()) :: {:ok, binary()} | {:error, String.t()}
+ @spec public_key(__MODULE__) :: {:ok, binary()} | {:error, String.t()}
@doc """
- Given a user, return the public key for that user in binary format.
+ Return public key data in binary format.
"""
- def public_key(%User{} = user) do
- case Repo.preload(user, :signing_key) do
- %User{signing_key: %__MODULE__{public_key: public_key_pem}} ->
- key =
- public_key_pem
- |> :public_key.pem_decode()
- |> hd()
- |> :public_key.pem_entry_decode()
+ def public_key_decoded(%__MODULE__{public_key: public_key_pem}) do
+ decoded =
+ public_key_pem
+ |> :public_key.pem_decode()
+ |> hd()
+ |> :public_key.pem_entry_decode()
- {:ok, key}
-
- _ ->
- {:error, "key not found"}
- end
+ {:ok, decoded}
end
def public_key(_), do: {:error, "key not found"}
diff --git a/lib/pleroma/web/plugs/http_signature_plug.ex b/lib/pleroma/web/plugs/http_signature_plug.ex
index 06527cada..c8df805fe 100644
--- a/lib/pleroma/web/plugs/http_signature_plug.ex
+++ b/lib/pleroma/web/plugs/http_signature_plug.ex
@@ -8,8 +8,8 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
use Pleroma.Web, :verified_routes
alias Pleroma.Activity
- alias Pleroma.Signature
alias Pleroma.Instances
+ alias Pleroma.User.SigningKey
require Logger
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
@@ -140,15 +140,17 @@ defp maybe_require_signature(conn), do: conn
defp signature_host(conn) do
with {:key_id, %{"keyId" => kid}} <- {:key_id, HTTPSignatures.signature_for_conn(conn)},
- {:actor_id, {:ok, actor_id}} <- {:actor_id, Signature.key_id_to_actor_id(kid)} do
+ {:actor_id, actor_id, _} when actor_id != nil <-
+ {:actor_id, SigningKey.key_id_to_ap_id(kid), kid} do
actor_id
else
{:key_id, e} ->
Logger.error("Failed to extract key_id from signature: #{inspect(e)}")
nil
- {:actor_id, e} ->
- Logger.error("Failed to extract actor_id from signature: #{inspect(e)}")
+ {:actor_id, _, kid} ->
+ # SigningKeys SHOULD have been fetched before this gets called!
+ Logger.error("Failed to extract actor_id from signature: signing key #{kid} not known")
nil
end
end
diff --git a/test/pleroma/signature_test.exs b/test/pleroma/signature_test.exs
index 86e2b138a..0e0105534 100644
--- a/test/pleroma/signature_test.exs
+++ b/test/pleroma/signature_test.exs
@@ -114,16 +114,6 @@ test "it returns signature headers" do
end
end
- describe "key_id_to_actor_id/1" do
- test "it reverses the key id to actor id" do
- user =
- insert(:user)
- |> with_signing_key()
-
- assert Signature.key_id_to_actor_id(user.signing_key.key_id) == {:ok, user.ap_id}
- end
- end
-
describe "signed_date" do
test "it returns formatted current date" do
with_mock(NaiveDateTime, utc_now: fn -> ~N[2019-08-23 18:11:24.822233] end) do
diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs
index f68bf29bc..be8e21dcb 100644
--- a/test/pleroma/user_test.exs
+++ b/test/pleroma/user_test.exs
@@ -1820,10 +1820,6 @@ test "unsuggests a user" do
end
end
- test "get_public_key_for_ap_id fetches a user that's not in the db" do
- assert {:ok, _key} = User.get_public_key_for_ap_id("http://mastodon.example.org/users/admin")
- end
-
describe "per-user rich-text filtering" do
test "html_filter_policy returns default policies, when rich-text is enabled" do
user = insert(:user)
From a7b4e4bfd99f4c6b86f276b5245c21f3a08149d1 Mon Sep 17 00:00:00 2001
From: Oneric
Date: Mon, 13 Jan 2025 00:05:42 +0100
Subject: [PATCH 63/95] signature: distinguish error sources and log fetch
issues
---
lib/pleroma/signature.ex | 22 ++++++++++++++++------
1 file changed, 16 insertions(+), 6 deletions(-)
diff --git a/lib/pleroma/signature.ex b/lib/pleroma/signature.ex
index 86065a603..39de8e9f1 100644
--- a/lib/pleroma/signature.ex
+++ b/lib/pleroma/signature.ex
@@ -10,23 +10,33 @@ defmodule Pleroma.Signature do
require Logger
def fetch_public_key(conn) do
- with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
- {:ok, %SigningKey{} = sk} <- SigningKey.get_or_fetch_by_key_id(kid),
- {:ok, decoded_key} <- SigningKey.public_key_decoded(sk) do
+ with {_, %{"keyId" => kid}} <- {:keyid, HTTPSignatures.signature_for_conn(conn)},
+ {_, {:ok, %SigningKey{} = sk}, _} <-
+ {:fetch, SigningKey.get_or_fetch_by_key_id(kid), kid},
+ {_, {:ok, decoded_key}} <- {:decode, SigningKey.public_key_decoded(sk)} do
{:ok, decoded_key}
else
+ {:fetch, error, kid} ->
+ Logger.error("Failed to acquire key from signature: #{kid} #{inspect(error)}")
+ {:error, {:fetch, error}}
+
e ->
{:error, e}
end
end
def refetch_public_key(conn) do
- with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
+ with {_, %{"keyId" => kid}} <- {:keyid, HTTPSignatures.signature_for_conn(conn)},
# TODO: force a refetch of stale keys (perhaps with a backoff time based on updated_at)
- {:ok, %SigningKey{} = sk} <- SigningKey.get_or_fetch_by_key_id(kid),
- {:ok, decoded_key} <- SigningKey.public_key_decoded(sk) do
+ {_, {:ok, %SigningKey{} = sk}, _} <-
+ {:fetch, SigningKey.get_or_fetch_by_key_id(kid), kid},
+ {_, {:ok, decoded_key}} <- {:decode, SigningKey.public_key_decoded(sk)} do
{:ok, decoded_key}
else
+ {:fetch, error, kid} ->
+ Logger.error("Failed to refresh stale key from signature: #{kid} #{inspect(error)}")
+ {:error, {:fetch, error}}
+
e ->
{:error, e}
end
From 9cc5fe9a5fb98130b627733c01ed4de0ca7e9aa3 Mon Sep 17 00:00:00 2001
From: Oneric
Date: Mon, 13 Jan 2025 00:24:01 +0100
Subject: [PATCH 64/95] signature: refetch key upon verification failure
This matches behaviour prioir to the SigningKey migration
and the expected semantics of the http_signatures lib.
Additionally add a min interval paramter, to avoid
refetch floods on bugs causing incompatible signatures
(like e.g. currently with Bridgy)
---
config/config.exs | 1 +
lib/pleroma/signature.ex | 12 +++++++++---
lib/pleroma/user/signing_key.ex | 17 +++++++++++++++++
test/pleroma/signature_test.exs | 11 +++++++++++
4 files changed, 38 insertions(+), 3 deletions(-)
diff --git a/config/config.exs b/config/config.exs
index 48ebe91d2..be6a0d81c 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -370,6 +370,7 @@
note_replies_output_limit: 5,
sign_object_fetches: true,
authorized_fetch_mode: false,
+ min_key_refetch_interval: 86_400,
max_collection_objects: 50
config :pleroma, :streamer,
diff --git a/lib/pleroma/signature.ex b/lib/pleroma/signature.ex
index 39de8e9f1..f926f1aa6 100644
--- a/lib/pleroma/signature.ex
+++ b/lib/pleroma/signature.ex
@@ -27,12 +27,18 @@ def fetch_public_key(conn) do
def refetch_public_key(conn) do
with {_, %{"keyId" => kid}} <- {:keyid, HTTPSignatures.signature_for_conn(conn)},
- # TODO: force a refetch of stale keys (perhaps with a backoff time based on updated_at)
- {_, {:ok, %SigningKey{} = sk}, _} <-
- {:fetch, SigningKey.get_or_fetch_by_key_id(kid), kid},
+ {_, {:ok, %SigningKey{} = sk}, _} <- {:fetch, SigningKey.refresh_by_key_id(kid), kid},
{_, {:ok, decoded_key}} <- {:decode, SigningKey.public_key_decoded(sk)} do
{:ok, decoded_key}
else
+ {:fetch, {:error, :too_young}, kid} ->
+ Logger.debug("Refusing to refetch recently updated key: #{kid}")
+ {:error, {:fetch, :too_young}}
+
+ {:fetch, {:error, :unknown}, kid} ->
+ Logger.warning("Attempted to refresh unknown key; this should not happen: #{kid}")
+ {:error, {:fetch, :unknown}}
+
{:fetch, error, kid} ->
Logger.error("Failed to refresh stale key from signature: #{kid} #{inspect(error)}")
{:error, {:fetch, error}}
diff --git a/lib/pleroma/user/signing_key.ex b/lib/pleroma/user/signing_key.ex
index 708bbbe19..91aa25a4e 100644
--- a/lib/pleroma/user/signing_key.ex
+++ b/lib/pleroma/user/signing_key.ex
@@ -218,6 +218,23 @@ def fetch_remote_key(key_id) do
end
end
+ defp refresh_key(%__MODULE__{} = key) do
+ min_backoff = Pleroma.Config.get!([:activitypub, :min_key_refetch_interval])
+
+ if Timex.diff(Timex.now(), key.updated_at, :seconds) >= min_backoff do
+ fetch_remote_key(key.key_id)
+ else
+ {:error, :too_young}
+ end
+ end
+
+ def refresh_by_key_id(key_id) do
+ case Repo.get_by(__MODULE__, key_id: key_id) do
+ nil -> {:error, :unknown}
+ key -> refresh_key(key)
+ end
+ end
+
# Take the response from the remote instance and extract the key details
# will check if the key ID matches the owner of the key, if not, error
defp extract_key_details(%{"id" => ap_id, "publicKey" => public_key}) do
diff --git a/test/pleroma/signature_test.exs b/test/pleroma/signature_test.exs
index 0e0105534..731605804 100644
--- a/test/pleroma/signature_test.exs
+++ b/test/pleroma/signature_test.exs
@@ -56,8 +56,19 @@ test "it returns error if public key is nil" do
describe "refetch_public_key/1" do
test "it returns key" do
+ clear_config([:activitypub, :min_key_refetch_interval], 0)
ap_id = "https://mastodon.social/users/lambadalambda"
+ %Pleroma.User{signing_key: sk} =
+ Pleroma.User.get_or_fetch_by_ap_id(ap_id)
+ |> then(fn {:ok, u} -> u end)
+ |> Pleroma.User.SigningKey.load_key()
+
+ {:ok, _} =
+ %{sk | public_key: "-----BEGIN PUBLIC KEY-----\nasdfghjkl"}
+ |> Ecto.Changeset.change()
+ |> Pleroma.Repo.update()
+
assert Signature.refetch_public_key(make_fake_conn(ap_id)) == {:ok, @rsa_public_key}
end
end
From d8e40173bfce556d7bc12d1f7685ff6b46c3df10 Mon Sep 17 00:00:00 2001
From: Oneric
Date: Sun, 19 Jan 2025 05:06:06 +0100
Subject: [PATCH 65/95] http_signatures: tweak order of route aliases
We expect most requests to be made for the actual canonical ID,
so check this one first (starting without query headers matching the
predominant albeit spec-breaking version).
Also avoid unnecessary rerewrites of the digest header on each route
alias by just setting it once before iterating through aliases.
---
lib/pleroma/web/plugs/http_signature_plug.ex | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/lib/pleroma/web/plugs/http_signature_plug.ex b/lib/pleroma/web/plugs/http_signature_plug.ex
index c8df805fe..195a9dc1c 100644
--- a/lib/pleroma/web/plugs/http_signature_plug.ex
+++ b/lib/pleroma/web/plugs/http_signature_plug.ex
@@ -77,10 +77,6 @@ defp assign_valid_signature_on_route_aliases(conn, [path | rest]) do
|> put_req_header("(request-target)", request_target)
|> maybe_put_created_psudoheader()
|> maybe_put_expires_psudoheader()
- |> case do
- %{assigns: %{digest: digest}} = conn -> put_req_header(conn, "digest", digest)
- conn -> conn
- end
conn
|> assign(:valid_signature, HTTPSignatures.validate_conn(conn))
@@ -93,7 +89,13 @@ defp maybe_assign_valid_signature(conn) do
# set (request-target) header to the appropriate value
# we also replace the digest header with the one we computed
possible_paths =
- route_aliases(conn) ++ [conn.request_path, conn.request_path <> "?#{conn.query_string}"]
+ [conn.request_path, conn.request_path <> "?#{conn.query_string}" | route_aliases(conn)]
+
+ conn =
+ case conn do
+ %{assigns: %{digest: digest}} = conn -> put_req_header(conn, "digest", digest)
+ conn -> conn
+ end
assign_valid_signature_on_route_aliases(conn, possible_paths)
else
From 11ad4711ebf5a8f23a94b5d9e3956b8c073a1b1d Mon Sep 17 00:00:00 2001
From: Oneric
Date: Sun, 19 Jan 2025 05:57:33 +0100
Subject: [PATCH 66/95] signing_key: don't retrieve superfluous fields when
loading ap_id
---
lib/pleroma/user/signing_key.ex | 14 ++++----------
1 file changed, 4 insertions(+), 10 deletions(-)
diff --git a/lib/pleroma/user/signing_key.ex b/lib/pleroma/user/signing_key.ex
index 91aa25a4e..9d052b6bf 100644
--- a/lib/pleroma/user/signing_key.ex
+++ b/lib/pleroma/user/signing_key.ex
@@ -56,16 +56,10 @@ def key_id_to_user_id(key_id) do
def key_id_to_ap_id(key_id) do
Logger.debug("Looking up key ID: #{key_id}")
- result =
- from(sk in __MODULE__, where: sk.key_id == ^key_id)
- |> join(:inner, [sk], u in User, on: sk.user_id == u.id)
- |> select([sk, u], %{user: u})
- |> Repo.one()
-
- case result do
- %{user: %User{ap_id: ap_id}} -> ap_id
- _ -> nil
- end
+ from(sk in __MODULE__, where: sk.key_id == ^key_id)
+ |> join(:inner, [sk], u in User, on: sk.user_id == u.id)
+ |> select([sk, u], u.ap_id)
+ |> Repo.one()
end
@spec generate_rsa_pem() :: {:ok, binary()}
From 8243fc0ef482a28daf2bcae2c64a9510bdb76489 Mon Sep 17 00:00:00 2001
From: Oneric
Date: Fri, 31 Jan 2025 19:51:53 +0100
Subject: [PATCH 67/95] federation: strip internal fields from incoming updates
and history
When note editing support was added, it was omitted to strip internal
fields from edited notes and their history.
This was uncovered due to Mastodon inlining the like count as a "likes"
collection conflicting with our internal "likes" list causing validation
failures. In a spot check with likes/like_count it was not possible to
inject those internal fields into the local db via Update, but this
was not extensively tested for all fields and avenues.
Similarly address normalisation did not normalise addressing in the
object history, although this was never at risk of being exploitable.
The revision history of the Pleroma MR adding edit support reveals
recusrive stripping was intentionally avoided, since it will end up
removing e.g. emoji from outgoing activities. This appears to still
be true. However, all current internal fields ("pleroma_interal"
appears to be unused) contain data already publicised otherwise anyway.
In the interest of fixing a federation bug (and at worst potential data
injection) quickly outgoing stripping is left non-recursive for now.
Of course the ultimate fix here is to not mix remote and internal data
into the same map in the first place, but unfortunately having a single
map of all truth is a core assumption of *oma's AP doc processing.
Changing this is a masive undertaking and not suitable for providing
a short-term fix.
---
.../web/activity_pub/transmogrifier.ex | 54 ++++--
.../web/activity_pub/transmogrifier_test.exs | 177 ++++++++++++++++++
2 files changed, 219 insertions(+), 12 deletions(-)
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index ecf6d9cd8..9ed54fa6e 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -29,6 +29,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
@doc """
Modifies an incoming AP object (mastodon format) to our internal format.
+ (This only deals with non-activity AP objects)
"""
def fix_object(object, options \\ []) do
object
@@ -44,6 +45,38 @@ def fix_object(object, options \\ []) do
|> fix_content_map()
|> fix_addressing()
|> fix_summary()
+ |> fix_history(&fix_object/1)
+ end
+
+ defp maybe_fix_object(%{"attributedTo" => _} = object), do: fix_object(object)
+ defp maybe_fix_object(object), do: object
+
+ defp fix_history(%{"formerRepresentations" => %{"orderedItems" => list}} = obj, fix_fun)
+ when is_list(list) do
+ update_in(obj["formerRepresentations"]["orderedItems"], fn h -> Enum.map(h, fix_fun) end)
+ end
+
+ defp fix_history(obj, _), do: obj
+
+ defp fix_recursive(obj, fun) do
+ # unlike Erlang, Elixir does not support recursive inline functions
+ # which would allow us to avoid reconstructing this on every recursion
+ rec_fun = fn
+ obj when is_map(obj) -> fix_recursive(obj, fun)
+ # there may be simple AP IDs in history (or object field)
+ obj -> obj
+ end
+
+ obj
+ |> fun.()
+ |> fix_history(rec_fun)
+ |> then(fn
+ %{"object" => object} = doc when is_map(object) ->
+ update_in(doc["object"], rec_fun)
+
+ apdoc ->
+ apdoc
+ end)
end
def fix_summary(%{"summary" => nil} = object) do
@@ -414,17 +447,10 @@ defp get_reported(objects) do
end
def handle_incoming(data, options \\ []) do
- data = normalise_addressing_public(data)
-
- data =
- if data["object"] != nil do
- object = normalise_addressing_public(data["object"])
- Map.put(data, "object", object)
- else
- data
- end
-
- handle_incoming_normalised(data, options)
+ data
+ |> fix_recursive(&normalise_addressing_public/1)
+ |> fix_recursive(&strip_internal_fields/1)
+ |> handle_incoming_normalised(options)
end
defp handle_incoming_normalised(data, options)
@@ -490,7 +516,6 @@ defp handle_incoming_normalised(
object =
data["object"]
- |> strip_internal_fields()
|> fix_type(fetch_options)
|> fix_in_reply_to(fetch_options)
|> fix_quote_url(fetch_options)
@@ -545,6 +570,9 @@ defp handle_incoming_normalised(
_options
)
when type in ~w{Update Block Follow Accept Reject} do
+ fixed_obj = maybe_fix_object(data["object"])
+ data = if fixed_obj != nil, do: %{data | "object" => fixed_obj}, else: data
+
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
{:ok, activity, _} <-
Pipeline.common_pipeline(data, local: false) do
@@ -1043,6 +1071,8 @@ def prepare_attachments(object) do
Map.put(object, "attachment", attachments)
end
+ # for outgoing docs immediately stripping internal fields recursively breaks later emoji transformations
+ # (XXX: it would be better to reorder operations so we can always use recursive stripping)
def strip_internal_fields(object) do
Map.drop(object, Pleroma.Constants.object_internal_fields())
end
diff --git a/test/pleroma/web/activity_pub/transmogrifier_test.exs b/test/pleroma/web/activity_pub/transmogrifier_test.exs
index be07a0fe4..b90692370 100644
--- a/test/pleroma/web/activity_pub/transmogrifier_test.exs
+++ b/test/pleroma/web/activity_pub/transmogrifier_test.exs
@@ -164,6 +164,183 @@ test "it accepts quote posts" do
# It fetched the quoted post
assert Object.normalize("https://misskey.io/notes/8vs6wxufd0")
end
+
+ test "doesn't allow remote edits to fake local likes" do
+ # as a spot check for no internal fields getting injected
+ now = DateTime.utc_now()
+ pub_date = DateTime.to_iso8601(Timex.subtract(now, Timex.Duration.from_minutes(3)))
+ edit_date = DateTime.to_iso8601(now)
+
+ local_user = insert(:user)
+
+ create_data = %{
+ "type" => "Create",
+ "id" => "http://mastodon.example.org/users/admin/statuses/2619539638/activity",
+ "actor" => "http://mastodon.example.org/users/admin",
+ "to" => ["as:Public"],
+ "cc" => [],
+ "object" => %{
+ "type" => "Note",
+ "id" => "http://mastodon.example.org/users/admin/statuses/2619539638",
+ "attributedTo" => "http://mastodon.example.org/users/admin",
+ "to" => ["as:Public"],
+ "cc" => [],
+ "published" => pub_date,
+ "content" => "miaow",
+ "likes" => [local_user.ap_id]
+ }
+ }
+
+ update_data =
+ create_data
+ |> Map.put("type", "Update")
+ |> Map.put("id", create_data["object"]["id"] <> "/update/1")
+ |> put_in(["object", "content"], "miaow :3")
+ |> put_in(["object", "updated"], edit_date)
+ |> put_in(["object", "formerRepresentations"], %{
+ "type" => "OrderedCollection",
+ "totalItems" => 1,
+ "orderedItems" => [create_data["object"]]
+ })
+
+ {:ok, %Pleroma.Activity{} = activity} = Transmogrifier.handle_incoming(create_data)
+ %Pleroma.Object{} = object = Object.get_by_ap_id(activity.data["object"])
+ assert object.data["content"] == "miaow"
+ assert object.data["likes"] == []
+ assert object.data["like_count"] == 0
+
+ {:ok, %Pleroma.Activity{} = activity} = Transmogrifier.handle_incoming(update_data)
+ %Pleroma.Object{} = object = Object.get_by_ap_id(activity.data["object"]["id"])
+ assert object.data["content"] == "miaow :3"
+ assert object.data["likes"] == []
+ assert object.data["like_count"] == 0
+ end
+
+ test "doesn't trip over remote likes in notes" do
+ now = DateTime.utc_now()
+ pub_date = DateTime.to_iso8601(Timex.subtract(now, Timex.Duration.from_minutes(3)))
+ edit_date = DateTime.to_iso8601(now)
+
+ create_data = %{
+ "type" => "Create",
+ "id" => "http://mastodon.example.org/users/admin/statuses/3409297097/activity",
+ "actor" => "http://mastodon.example.org/users/admin",
+ "to" => ["as:Public"],
+ "cc" => [],
+ "object" => %{
+ "type" => "Note",
+ "id" => "http://mastodon.example.org/users/admin/statuses/3409297097",
+ "attributedTo" => "http://mastodon.example.org/users/admin",
+ "to" => ["as:Public"],
+ "cc" => [],
+ "published" => pub_date,
+ "content" => "miaow",
+ "likes" => %{
+ "id" => "http://mastodon.example.org/users/admin/statuses/3409297097/likes",
+ "totalItems" => 0,
+ "type" => "Collection"
+ }
+ }
+ }
+
+ update_data =
+ create_data
+ |> Map.put("type", "Update")
+ |> Map.put("id", create_data["object"]["id"] <> "/update/1")
+ |> put_in(["object", "content"], "miaow :3")
+ |> put_in(["object", "updated"], edit_date)
+ |> put_in(["object", "likes", "totalItems"], 666)
+ |> put_in(["object", "formerRepresentations"], %{
+ "type" => "OrderedCollection",
+ "totalItems" => 1,
+ "orderedItems" => [create_data["object"]]
+ })
+
+ {:ok, %Pleroma.Activity{} = activity} = Transmogrifier.handle_incoming(create_data)
+ %Pleroma.Object{} = object = Object.get_by_ap_id(activity.data["object"])
+ assert object.data["content"] == "miaow"
+ assert object.data["likes"] == []
+ assert object.data["like_count"] == 0
+
+ {:ok, %Pleroma.Activity{} = activity} = Transmogrifier.handle_incoming(update_data)
+ %Pleroma.Object{} = object = Object.get_by_ap_id(activity.data["object"]["id"])
+ assert object.data["content"] == "miaow :3"
+ assert object.data["likes"] == []
+ # in the future this should retain remote likes, but for now:
+ assert object.data["like_count"] == 0
+ end
+
+ test "doesn't trip over remote likes in polls" do
+ now = DateTime.utc_now()
+ pub_date = DateTime.to_iso8601(Timex.subtract(now, Timex.Duration.from_minutes(3)))
+ edit_date = DateTime.to_iso8601(now)
+
+ create_data = %{
+ "type" => "Create",
+ "id" => "http://mastodon.example.org/users/admin/statuses/2471790073/activity",
+ "actor" => "http://mastodon.example.org/users/admin",
+ "to" => ["as:Public"],
+ "cc" => [],
+ "object" => %{
+ "type" => "Question",
+ "id" => "http://mastodon.example.org/users/admin/statuses/2471790073",
+ "attributedTo" => "http://mastodon.example.org/users/admin",
+ "to" => ["as:Public"],
+ "cc" => [],
+ "published" => pub_date,
+ "content" => "vote!",
+ "anyOf" => [
+ %{
+ "type" => "Note",
+ "name" => "a",
+ "replies" => %{
+ "type" => "Collection",
+ "totalItems" => 3
+ }
+ },
+ %{
+ "type" => "Note",
+ "name" => "b",
+ "replies" => %{
+ "type" => "Collection",
+ "totalItems" => 1
+ }
+ }
+ ],
+ "likes" => %{
+ "id" => "http://mastodon.example.org/users/admin/statuses/2471790073/likes",
+ "totalItems" => 0,
+ "type" => "Collection"
+ }
+ }
+ }
+
+ update_data =
+ create_data
+ |> Map.put("type", "Update")
+ |> Map.put("id", create_data["object"]["id"] <> "/update/1")
+ |> put_in(["object", "content"], "vote now!")
+ |> put_in(["object", "updated"], edit_date)
+ |> put_in(["object", "likes", "totalItems"], 666)
+ |> put_in(["object", "formerRepresentations"], %{
+ "type" => "OrderedCollection",
+ "totalItems" => 1,
+ "orderedItems" => [create_data["object"]]
+ })
+
+ {:ok, %Pleroma.Activity{} = activity} = Transmogrifier.handle_incoming(create_data)
+ %Pleroma.Object{} = object = Object.get_by_ap_id(activity.data["object"])
+ assert object.data["content"] == "vote!"
+ assert object.data["likes"] == []
+ assert object.data["like_count"] == 0
+
+ {:ok, %Pleroma.Activity{} = activity} = Transmogrifier.handle_incoming(update_data)
+ %Pleroma.Object{} = object = Object.get_by_ap_id(activity.data["object"]["id"])
+ assert object.data["content"] == "vote now!"
+ assert object.data["likes"] == []
+ # in the future this should retain remote likes, but for now:
+ assert object.data["like_count"] == 0
+ end
end
describe "prepare outgoing" do
From 7c23793e55a9f66f8d8491f3642265c47d4cfa95 Mon Sep 17 00:00:00 2001
From: Oneric
Date: Sat, 15 Feb 2025 14:18:04 +0100
Subject: [PATCH 68/95] changelog: add entries for preceding commits
---
CHANGELOG.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 43fff4dda..949fc32f0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,12 +12,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- fixed some holes in SigningKey verification potentially allowing they key-user mapping to be poisoned
- frontend ZIP files can no longer traverse to paths outside their install dir
- fixed user updates trying but failing to renew signing key information
+- fixed signing key refresh on key rotation
## Changed
- Dropped obsolete `ap_enabled` indicator from user table and associated buggy logic
- The remote user count in prometheus metrics is now an estimate instead of an exact number
since the latter proved unreasonably costly to obtain for a merely nice-to-have statistic
- Various other tweaks improving stat query performance and avoiding unecessary work on received AP documents
+- HTTP signatures now test the most likely request-target alias first cutting down on overhead
## 2025.01.01
From da7998e89e54f9b83e48ab60900522e4f947e1cb Mon Sep 17 00:00:00 2001
From: Floatingghost
Date: Sun, 23 Feb 2025 16:16:17 +0000
Subject: [PATCH 69/95] put oban route under a known prefix
---
lib/pleroma/web/router.ex | 2 +-
lib/pleroma/web/templates/masto_fe/fedibird.html.heex | 2 +-
lib/pleroma/web/templates/masto_fe/glitchsoc.html.heex | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index a38017d59..5803e6a02 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -905,7 +905,7 @@ defmodule Pleroma.Web.Router do
csp_nonce_assign_key: :csp_nonce
)
- oban_dashboard("/oban/dashboard", csp_nonce_assign_key: :csp_nonce)
+ oban_dashboard("/akkoma/oban", csp_nonce_assign_key: :csp_nonce)
end
# Test-only routes needed to test action dispatching and plug chain execution
diff --git a/lib/pleroma/web/templates/masto_fe/fedibird.html.heex b/lib/pleroma/web/templates/masto_fe/fedibird.html.heex
index 7070bd8d8..45438798a 100644
--- a/lib/pleroma/web/templates/masto_fe/fedibird.html.heex
+++ b/lib/pleroma/web/templates/masto_fe/fedibird.html.heex
@@ -5,7 +5,7 @@
- <%= Config.get([:instance, :name]) %>
+ {Config.get([:instance, :name])}
diff --git a/lib/pleroma/web/templates/masto_fe/glitchsoc.html.heex b/lib/pleroma/web/templates/masto_fe/glitchsoc.html.heex
index 469c201a5..7fef592c5 100644
--- a/lib/pleroma/web/templates/masto_fe/glitchsoc.html.heex
+++ b/lib/pleroma/web/templates/masto_fe/glitchsoc.html.heex
@@ -5,7 +5,7 @@
- <%= Config.get([:instance, :name]) %>
+ {Config.get([:instance, :name])}
From 13d650602b5a70c4d08932d50333edcae545ddf8 Mon Sep 17 00:00:00 2001
From: Floatingghost
Date: Sun, 23 Feb 2025 16:32:55 +0000
Subject: [PATCH 70/95] update deps
---
CHANGELOG.md | 1 +
mix.exs | 8 +++---
mix.lock | 70 ++++++++++++++++++++++++++--------------------------
3 files changed, 40 insertions(+), 39 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6172134e2..d7e4e1c6a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## Unreleased
## Added
+- Oban (worker) dashboard at `/akkoma/oban`
## Fixed
diff --git a/mix.exs b/mix.exs
index fe04ff5fe..e49c5cf36 100644
--- a/mix.exs
+++ b/mix.exs
@@ -121,11 +121,11 @@ defp deps do
{:tzdata, "~> 1.1.1"},
{:plug_cowboy, "~> 2.6"},
{:phoenix_pubsub, "~> 2.1"},
- {:phoenix_ecto, "~> 4.4"},
+ {:phoenix_ecto, "~> 4.6"},
{:inet_cidr, "~> 1.0.0"},
{:ecto_enum, "~> 1.4"},
- {:ecto_sql, "~> 3.10.0"},
- {:postgrex, "~> 0.17.2"},
+ {:ecto_sql, "~> 3.12.0"},
+ {:postgrex, "~> 0.20.0"},
{:oban, "~> 2.19.0"},
{:oban_web, "~> 2.11.0"},
{:gettext, "~> 0.22.3"},
@@ -192,7 +192,7 @@ defp deps do
git: "https://github.com/FloatingGhost/pleroma-contrib-search-parser.git",
ref: "08971a81e68686f9ac465cfb6661d51c5e4e1e7f"},
{:nimble_parsec, "~> 1.3", override: true},
- {:ecto_psql_extras, "~> 0.7"},
+ {:ecto_psql_extras, "~> 0.8"},
{:elasticsearch,
git: "https://akkoma.dev/AkkomaGang/elasticsearch-elixir.git", ref: "main"},
{:mfm_parser,
diff --git a/mix.lock b/mix.lock
index 679af5c75..4a732a3f0 100644
--- a/mix.lock
+++ b/mix.lock
@@ -9,52 +9,52 @@
"calendar": {:hex, :calendar, "1.0.0", "f52073a708528482ec33d0a171954ca610fe2bd28f1e871f247dc7f1565fa807", [:mix], [{:tzdata, "~> 0.1.201603 or ~> 0.5.20 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "990e9581920c82912a5ee50e62ff5ef96da6b15949a2ee4734f935fdef0f0a6f"},
"captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "6630c42aaaab124e697b4e513190c89d8b64e410", [ref: "6630c42aaaab124e697b4e513190c89d8b64e410"]},
"castore": {:hex, :castore, "1.0.11", "4bbd584741601eb658007339ea730b082cc61f3554cf2e8f39bf693a11b49073", [:mix], [], "hexpm", "e03990b4db988df56262852f20de0f659871c35154691427a5047f4967a16a62"},
- "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"},
+ "certifi": {:hex, :certifi, "2.14.0", "ed3bef654e69cde5e6c022df8070a579a79e8ba2368a00acf3d75b82d9aceeed", [:rebar3], [], "hexpm", "ea59d87ef89da429b8e905264fdec3419f84f2215bb3d81e07a18aac919026c3"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
- "comeonin": {:hex, :comeonin, "5.5.0", "364d00df52545c44a139bad919d7eacb55abf39e86565878e17cebb787977368", [:mix], [], "hexpm", "6287fc3ba0aad34883cbe3f7949fc1d1e738e5ccdce77165bc99490aa69f47fb"},
+ "comeonin": {:hex, :comeonin, "5.5.1", "5113e5f3800799787de08a6e0db307133850e635d34e9fab23c70b6501669510", [:mix], [], "hexpm", "65aac8f19938145377cee73973f192c5645873dcf550a8a6b18187d17c13ccdb"},
"concurrent_limiter": {:git, "https://akkoma.dev/AkkomaGang/concurrent-limiter.git", "a9e0b3d64574bdba761f429bb4fba0cf687b3338", [ref: "a9e0b3d64574bdba761f429bb4fba0cf687b3338"]},
"connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
"cors_plug": {:hex, :cors_plug, "3.0.3", "7c3ac52b39624bc616db2e937c282f3f623f25f8d550068b6710e58d04a0e330", [:mix], [{:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3f2d759e8c272ed3835fab2ef11b46bddab8c1ab9528167bd463b6452edf830d"},
- "cowboy": {:hex, :cowboy, "2.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"},
+ "cowboy": {:hex, :cowboy, "2.13.0", "09d770dd5f6a22cc60c071f432cd7cb87776164527f205c5a6b0f24ff6b38990", [:make, :rebar3], [{:cowlib, ">= 2.14.0 and < 3.0.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, ">= 1.8.0 and < 3.0.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "e724d3a70995025d654c1992c7b11dbfea95205c047d86ff9bf1cda92ddc5614"},
"cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"},
- "cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"},
- "credo": {:hex, :credo, "1.7.8", "9722ba1681e973025908d542ec3d95db5f9c549251ba5b028e251ad8c24ab8c5", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cb9e87cc64f152f3ed1c6e325e7b894dea8f5ef2e41123bd864e3cd5ceb44968"},
+ "cowlib": {:hex, :cowlib, "2.14.0", "623791c56c1cc9df54a71a9c55147a401549917f00a2e48a6ae12b812c586ced", [:make, :rebar3], [], "hexpm", "0af652d1550c8411c3b58eed7a035a7fb088c0b86aff6bc504b0bc3b7f791aa2"},
+ "credo": {:hex, :credo, "1.7.11", "d3e805f7ddf6c9c854fd36f089649d7cf6ba74c42bc3795d587814e3c9847102", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "56826b4306843253a66e47ae45e98e7d284ee1f95d53d1612bb483f88a8cf219"},
"custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"},
"db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"},
"decimal": {:hex, :decimal, "2.3.0", "3ad6255aa77b4a3c4f818171b12d237500e63525c2fd056699967a3e7ea20f62", [:mix], [], "hexpm", "a4d66355cb29cb47c3cf30e71329e58361cfcb37c34235ef3bf1d7bf3773aeac"},
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
- "dialyxir": {:hex, :dialyxir, "1.4.4", "fb3ce8741edeaea59c9ae84d5cec75da00fa89fe401c72d6e047d11a61f65f70", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "cd6111e8017ccd563e65621a4d9a4a1c5cd333df30cebc7face8029cacb4eff6"},
+ "dialyxir": {:hex, :dialyxir, "1.4.5", "ca1571ac18e0f88d4ab245f0b60fa31ff1b12cbae2b11bd25d207f865e8ae78a", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "b0fb08bb8107c750db5c0b324fa2df5ceaa0f9307690ee3c1f6ba5b9eb5d35c3"},
"earmark": {:hex, :earmark, "1.4.46", "8c7287bd3137e99d26ae4643e5b7ef2129a260e3dcf41f251750cb4563c8fb81", [:mix], [], "hexpm", "798d86db3d79964e759ddc0c077d5eb254968ed426399fbf5a62de2b5ff8910a"},
- "earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"},
+ "earmark_parser": {:hex, :earmark_parser, "1.4.43", "34b2f401fe473080e39ff2b90feb8ddfeef7639f8ee0bbf71bb41911831d77c5", [:mix], [], "hexpm", "970a3cd19503f5e8e527a190662be2cee5d98eed1ff72ed9b3d1a3d466692de8"},
"eblurhash": {:hex, :eblurhash, "1.2.2", "7da4255aaea984b31bb71155f673257353b0e0554d0d30dcf859547e74602582", [:rebar3], [], "hexpm", "8c20ca00904de023a835a9dcb7b7762fed32264c85a80c3cafa85288e405044c"},
- "ecto": {:hex, :ecto, "3.10.3", "eb2ae2eecd210b4eb8bece1217b297ad4ff824b4384c0e3fdd28aaf96edd6135", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "44bec74e2364d491d70f7e42cd0d690922659d329f6465e89feb8a34e8cd3433"},
+ "ecto": {:hex, :ecto, "3.12.5", "4a312960ce612e17337e7cefcf9be45b95a3be6b36b6f94dfb3d8c361d631866", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6eb18e80bef8bb57e17f5a7f068a1719fbda384d40fc37acb8eb8aeca493b6ea"},
"ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"},
- "ecto_psql_extras": {:hex, :ecto_psql_extras, "0.8.3", "0c1df205bd051eaf599b3671e75356b121aa71eac09b63ecf921cb1a080c072e", [:mix], [{:ecto_sql, "~> 3.7", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "> 0.16.0 and < 0.20.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1 or ~> 4.0.0", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "d0e35ea160359e759a2993a00c3a5389a9ca7ece6df5d0753fa927f988c7351a"},
- "ecto_sql": {:hex, :ecto_sql, "3.10.2", "6b98b46534b5c2f8b8b5f03f126e75e2a73c64f3c071149d32987a5378b0fdbd", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "68c018debca57cb9235e3889affdaec7a10616a4e3a80c99fa1d01fdafaa9007"},
+ "ecto_psql_extras": {:hex, :ecto_psql_extras, "0.8.7", "49943fe6bce07281fe3adfc2a23d3794e2acc644dfe98411cb5712ffecb6ad1a", [:mix], [{:ecto_sql, "~> 3.7", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "> 0.16.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1 or ~> 4.0", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "ac0a0bce57ffe36b30fac2a2d0d427b04de016e6af5db6f4b41afa1241f39cda"},
+ "ecto_sql": {:hex, :ecto_sql, "3.12.1", "c0d0d60e85d9ff4631f12bafa454bc392ce8b9ec83531a412c12a0d415a3a4d0", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aff5b958a899762c5f09028c847569f7dfb9cc9d63bdb8133bff8a5546de6bf5"},
"elasticsearch": {:git, "https://akkoma.dev/AkkomaGang/elasticsearch-elixir.git", "6cd946f75f6ab9042521a009d1d32d29a90113ca", [ref: "main"]},
- "elixir_make": {:hex, :elixir_make, "0.8.4", "4960a03ce79081dee8fe119d80ad372c4e7badb84c493cc75983f9d3bc8bde0f", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "6e7f1d619b5f61dfabd0a20aa268e575572b542ac31723293a4c1a567d5ef040"},
+ "elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"},
"elixir_xml_to_map": {:hex, :elixir_xml_to_map, "3.1.0", "4d6260486a8cce59e4bf3575fe2dd2a24766546ceeef9f93fcec6f7c62a2827a", [:mix], [{:erlsom, "~> 1.4", [hex: :erlsom, repo: "hexpm", optional: false]}], "hexpm", "8fe5f2e75f90bab07ee2161120c2dc038ebcae8135554f5582990f1c8c21f911"},
"erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"},
- "erlsom": {:hex, :erlsom, "1.5.1", "c8fe2babd33ff0846403f6522328b8ab676f896b793634cfe7ef181c05316c03", [:rebar3], [], "hexpm", "7965485494c5844dd127656ac40f141aadfa174839ec1be1074e7edf5b4239eb"},
+ "erlsom": {:hex, :erlsom, "1.5.2", "3e47c53a199136fb4d20d5479edb7c9229f31624534c062633951c8d14dcd276", [:rebar3], [], "hexpm", "4e765cc677fb30509f7b628ff2914e124cf4dcc0fac1c0a62ee4dcee24215b5d"},
"eternal": {:hex, :eternal, "1.2.2", "d1641c86368de99375b98d183042dd6c2b234262b8d08dfd72b9eeaafc2a1abd", [:mix], [], "hexpm", "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782"},
- "ex_aws": {:hex, :ex_aws, "2.5.6", "6f642e0f82eff10a9b470044f084b81a791cf15b393d647ea5f3e65da2794e3d", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:req, "~> 0.3", [hex: :req, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c69eec59e31fdd89d0beeb1d97e16518dd1b23ad95b3d5c9f1dcfec23d97f960"},
- "ex_aws_s3": {:hex, :ex_aws_s3, "2.5.4", "87aaf4a2f24a48f516d7f5aaced9d128dd5d0f655c4431f9037a11a85c71109c", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "c06e7f68b33f7c0acba1361dbd951c79661a28f85aa2e0582990fccca4425355"},
+ "ex_aws": {:hex, :ex_aws, "2.5.8", "0393cfbc5e4a9e7017845451a015d836a670397100aa4c86901980e2a2c5f7d4", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:req, "~> 0.3", [hex: :req, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8f79777b7932168956c8cc3a6db41f5783aa816eb50de356aed3165a71e5f8c3"},
+ "ex_aws_s3": {:hex, :ex_aws_s3, "2.5.6", "d135983bbd8b6df6350dfd83999437725527c1bea151e5055760bfc9b2d17c20", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "9874e12847e469ca2f13a5689be04e546c16f63caf6380870b7f25bf7cb98875"},
"ex_const": {:hex, :ex_const, "0.3.0", "9d79516679991baf540ef445438eef1455ca91cf1a3c2680d8fb9e5bea2fe4de", [:mix], [], "hexpm", "76546322abb9e40ee4a2f454cf1c8a5b25c3672fa79bed1ea52c31e0d2428ca9"},
- "ex_doc": {:hex, :ex_doc, "0.34.2", "13eedf3844ccdce25cfd837b99bea9ad92c4e511233199440488d217c92571e8", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "5ce5f16b41208a50106afed3de6a2ed34f4acfd65715b82a0b84b49d995f95c1"},
+ "ex_doc": {:hex, :ex_doc, "0.37.2", "2a3aa7014094f0e4e286a82aa5194a34dd17057160988b8509b15aa6c292720c", [:mix], [{:earmark_parser, "~> 1.4.42", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "4dfa56075ce4887e4e8b1dcc121cd5fcb0f02b00391fd367ff5336d98fa49049"},
"ex_machina": {:hex, :ex_machina, "2.8.0", "a0e847b5712065055ec3255840e2c78ef9366634d62390839d4880483be38abe", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "79fe1a9c64c0c1c1fab6c4fa5d871682cb90de5885320c187d117004627a7729"},
"ex_syslogger": {:hex, :ex_syslogger, "2.0.0", "de6de5c5472a9c4fdafb28fa6610e381ae79ebc17da6490b81d785d68bd124c9", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}, {:syslog, "~> 1.1.0", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "a52b2fe71764e9e6ecd149ab66635812f68e39279cbeee27c52c0e35e8b8019e"},
"excoveralls": {:hex, :excoveralls, "0.16.1", "0bd42ed05c7d2f4d180331a20113ec537be509da31fed5c8f7047ce59ee5a7c5", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "dae763468e2008cf7075a64cb1249c97cb4bc71e236c5c2b5e5cdf1cfa2bf138"},
"expo": {:hex, :expo, "0.4.1", "1c61d18a5df197dfda38861673d392e642649a9cef7694d2f97a587b2cfb319b", [:mix], [], "hexpm", "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47"},
- "fast_html": {:hex, :fast_html, "2.3.0", "08c1d8ead840dd3060ba02c761bed9f37f456a1ddfe30bcdcfee8f651cec06a6", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}], "hexpm", "f18e3c7668f82d3ae0b15f48d48feeb257e28aa5ab1b0dbf781c7312e5da029d"},
+ "fast_html": {:hex, :fast_html, "2.4.1", "73142526cee294b0ec8cf122483f32c861e4a9d988c60cd04f63ce7fd9e5a620", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 1.1", [hex: :nimble_pool, repo: "hexpm", optional: false]}], "hexpm", "767a63ecc941d3fc0e0e9609ded1a5e798398e5b1bf4d2f47bcb5992a86b32cf"},
"fast_sanitize": {:hex, :fast_sanitize, "0.2.3", "67b93dfb34e302bef49fec3aaab74951e0f0602fd9fa99085987af05bd91c7a5", [:mix], [{:fast_html, "~> 2.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "e8ad286d10d0386e15d67d0ee125245ebcfbc7d7290b08712ba9013c8c5e56e2"},
"file_ex": {:git, "https://akkoma.dev/AkkomaGang/file_ex.git", "cc7067c7d446c2526e9ecf91d40896b088851569", [ref: "cc7067c7d446c2526e9ecf91d40896b088851569"]},
- "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"},
+ "file_system": {:hex, :file_system, "1.1.0", "08d232062284546c6c34426997dd7ef6ec9f8bbd090eb91780283c9016840e8f", [:mix], [], "hexpm", "bfcf81244f416871f2a2e15c1b515287faa5db9c6bcf290222206d120b3d43f6"},
"finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"},
"flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "31fc8090fde1acd267c07c36ea7365b8604055f897d3a53dd967658c691bd827"},
- "floki": {:hex, :floki, "0.36.3", "1102f93b16a55bc5383b85ae3ec470f82dee056eaeff9195e8afdf0ef2a43c30", [:mix], [], "hexpm", "fe0158bff509e407735f6d40b3ee0d7deb47f3f3ee7c6c182ad28599f9f6b27a"},
+ "floki": {:hex, :floki, "0.37.0", "b83e0280bbc6372f2a403b2848013650b16640cd2470aea6701f0632223d719e", [:mix], [], "hexpm", "516a0c15a69f78c47dc8e0b9b3724b29608aa6619379f91b1ffa47109b5d0dd3"},
"gen_smtp": {:hex, :gen_smtp, "1.2.0", "9cfc75c72a8821588b9b9fe947ae5ab2aed95a052b81237e0928633a13276fd3", [:rebar3], [{:ranch, ">= 1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "5ee0375680bca8f20c4d85f58c2894441443a743355430ff33a783fe03296779"},
"gettext": {:hex, :gettext, "0.22.3", "c8273e78db4a0bb6fba7e9f0fd881112f349a3117f7f7c598fa18c66c888e524", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "935f23447713954a6866f1bb28c3a878c4c011e802bcd68a726f5e558e4b64bd"},
- "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"},
+ "hackney": {:hex, :hackney, "1.22.0", "4efc68df70322d4d2e3d2744e9bd191a39a0cb8d08c35379a08d9fb0f040d595", [:rebar3], [{:certifi, "~> 2.14.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "628569e451820950382be3d3e6481d7c59997e606c7823bddb4ce5d10812dfcb"},
"hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"},
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
"http_signatures": {:git, "https://akkoma.dev/AkkomaGang/http_signatures.git", "d44c43d66758c6a73eaa4da9cffdbee0c5da44ae", [ref: "d44c43d66758c6a73eaa4da9cffdbee0c5da44ae"]},
@@ -66,34 +66,34 @@
"jose": {:hex, :jose, "1.11.10", "a903f5227417bd2a08c8a00a0cbcc458118be84480955e8d251297a425723f83", [:mix, :rebar3], [], "hexpm", "0d6cd36ff8ba174db29148fc112b5842186b68a90ce9fc2b3ec3afe76593e614"},
"jumper": {:hex, :jumper, "1.0.2", "68cdcd84472a00ac596b4e6459a41b3062d4427cbd4f1e8c8793c5b54f1406a7", [:mix], [], "hexpm", "9b7782409021e01ab3c08270e26f36eb62976a38c1aa64b2eaf6348422f165e1"},
"linkify": {:hex, :linkify, "0.5.3", "5f8143d8f61f5ff08d3aeeff47ef6509492b4948d8f08007fbf66e4d2246a7f2", [:mix], [], "hexpm", "3ef35a1377d47c25506e07c1c005ea9d38d700699d92ee92825f024434258177"},
- "mail": {:hex, :mail, "0.4.2", "959229676ef11dfdfb1269ce4a611622cb70415a1d6f925d79e547848bafc14d", [:mix], [], "hexpm", "08e5b70c72b8d1605cb88ef2df2c7e41d002210a621503ea1c13f1a7916b6bd3"},
+ "mail": {:hex, :mail, "0.4.3", "16df84500780980826d9b52059d24c08fdd75c98f178a7a7ea809ea83fb70542", [:mix], [], "hexpm", "164975550b977e47cab431c403b0e90c8ce542036d32c7189b83839d8d7d391b"},
"majic": {:git, "https://akkoma.dev/AkkomaGang/majic.git", "80540b36939ec83f48e76c61e5000e0fd67706f0", [ref: "80540b36939ec83f48e76c61e5000e0fd67706f0"]},
- "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"},
- "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"},
- "makeup_erlang": {:hex, :makeup_erlang, "1.0.1", "c7f58c120b2b5aa5fd80d540a89fdf866ed42f1f3994e4fe189abebeab610839", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "8a89a1eeccc2d798d6ea15496a6e4870b75e014d1af514b1b71fa33134f57814"},
+ "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"},
+ "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"},
+ "makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"},
"meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mfm_parser": {:git, "https://akkoma.dev/AkkomaGang/mfm-parser.git", "b21ab7754024af096f2d14247574f55f0063295b", [ref: "b21ab7754024af096f2d14247574f55f0063295b"]},
"mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"},
"mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"},
"mint": {:hex, :mint, "1.5.2", "4805e059f96028948870d23d7783613b7e6b0e2fb4e98d720383852a760067fd", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "d77d9e9ce4eb35941907f1d3df38d8f750c357865353e21d335bdcdf6d892a02"},
- "mock": {:hex, :mock, "0.3.8", "7046a306b71db2488ef54395eeb74df0a7f335a7caca4a3d3875d1fc81c884dd", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "7fa82364c97617d79bb7d15571193fc0c4fe5afd0c932cef09426b3ee6fe2022"},
+ "mock": {:hex, :mock, "0.3.9", "10e44ad1f5962480c5c9b9fa779c6c63de9bd31997c8e04a853ec990a9d841af", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "9e1b244c4ca2551bb17bb8415eed89e40ee1308e0fbaed0a4fdfe3ec8a4adbd3"},
"mogrify": {:hex, :mogrify, "0.9.3", "238c782f00271dace01369ad35ae2e9dd020feee3443b9299ea5ea6bed559841", [:mix], [], "hexpm", "0189b1e1de27455f2b9ae8cf88239cefd23d38de9276eb5add7159aea51731e6"},
"mox": {:hex, :mox, "1.2.0", "a2cd96b4b80a3883e3100a221e8adc1b98e4c3a332a8fc434c39526babafd5b3", [:mix], [{:nimble_ownership, "~> 1.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}], "hexpm", "c7b92b3cc69ee24a7eeeaf944cd7be22013c52fcb580c1f33f50845ec821089a"},
"nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"},
- "nimble_ownership": {:hex, :nimble_ownership, "1.0.0", "3f87744d42c21b2042a0aa1d48c83c77e6dd9dd357e425a038dd4b49ba8b79a1", [:mix], [], "hexpm", "7c16cc74f4e952464220a73055b557a273e8b1b7ace8489ec9d86e9ad56cb2cc"},
- "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
+ "nimble_ownership": {:hex, :nimble_ownership, "1.0.1", "f69fae0cdd451b1614364013544e66e4f5d25f36a2056a9698b793305c5aa3a6", [:mix], [], "hexpm", "3825e461025464f519f3f3e4a1f9b68c47dc151369611629ad08b636b73bb22d"},
+ "nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"},
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
- "oban": {:hex, :oban, "2.19.0", "dfb8fa028ce7e7cf3be3481a47a7c8ebf9428d6df0aa58c1388a8e63f7ff2797", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:igniter, "~> 0.5", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aa3eb7cfa2aea8ecc4df4787b92ddb61ad5a598f07560937d1dd5dbb1ed225e2"},
+ "oban": {:hex, :oban, "2.19.2", "11e635c49b4f422814eb96a4d78974c9e67d62a20a969e5fd50db040d32c08ba", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:igniter, "~> 0.5", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "de8314b00b31d17f98fd2c76781f80c1cfc8621122b41830c0834486c44e1087"},
"oban_met": {:hex, :oban_met, "1.0.1", "737db0064567b923d3f35efd1d3009dd1435d60ee6f98dbb55dbb83db8f4f4fa", [:mix], [{:oban, "~> 2.18", [hex: :oban, repo: "hexpm", optional: false]}], "hexpm", "0492d841f880b76c5b73081bc70ebea20ebacc08e871345f72c2270513f09957"},
- "oban_web": {:hex, :oban_web, "2.11.0", "8b2a23331ef7e60eabb4118a141880d89812820321b21f289f1696bcf3058810", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:oban, "~> 2.19", [hex: :oban, repo: "hexpm", optional: false]}, {:oban_met, "~> 1.0", [hex: :oban_met, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.7", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}], "hexpm", "a573f27bf7cb054ff2a694116428dc6fedc18e20a20d10a74934b7c9e473e562"},
+ "oban_web": {:hex, :oban_web, "2.11.1", "646492d92177a43ff869535f0c92380e17148f45bc2c8dc8f74c69f8a2874bad", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:oban, "~> 2.19", [hex: :oban, repo: "hexpm", optional: false]}, {:oban_met, "~> 1.0", [hex: :oban_met, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.7", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}], "hexpm", "d853c6af3f7c20d03a2bf7b1baad71835d50fbb98af05004e9b51da558b90b01"},
"open_api_spex": {:hex, :open_api_spex, "3.21.2", "6a704f3777761feeb5657340250d6d7332c545755116ca98f33d4b875777e1e5", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0 or ~> 6.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "f42ae6ed668b895ebba3e02773cfb4b41050df26f803f2ef634c72a7687dc387"},
"parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},
- "phoenix": {:hex, :phoenix, "1.7.18", "5310c21443514be44ed93c422e15870aef254cf1b3619e4f91538e7529d2b2e4", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "1797fcc82108442a66f2c77a643a62980f342bfeb63d6c9a515ab8294870004e"},
+ "phoenix": {:hex, :phoenix, "1.7.20", "6bababaf27d59f5628f9b608de902a021be2cecefb8231e1dbdc0a2e2e480e9b", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "6be2ab98302e8784a31829e0d50d8bdfa81a23cd912c395bafd8b8bfb5a086c2"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.6.3", "f686701b0499a07f2e3b122d84d52ff8a31f5def386e03706c916f6feddf69ef", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "909502956916a657a197f94cc1206d9a65247538de8a5e186f7537c895d95764"},
"phoenix_html": {:hex, :phoenix_html, "3.3.4", "42a09fc443bbc1da37e372a5c8e6755d046f22b9b11343bf885067357da21cb3", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0249d3abec3714aff3415e7ee3d9786cb325be3151e6c4b3021502c585bf53fb"},
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.6", "7b1f0327f54c9eb69845fd09a77accf922f488c549a7e7b8618775eb603a62c7", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "1681ab813ec26ca6915beb3414aa138f298e17721dc6a2bde9e6eb8a62360ff6"},
- "phoenix_live_view": {:hex, :phoenix_live_view, "1.0.2", "e7b1dd68c86326e2c45cc81da41e332cc8aa7228a7161e2c811dcd7f1dd14db1", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "8a40265b0cd7d3a35f136dfa3cc048e3b198fc3718763411a78c323a44ebebee"},
+ "phoenix_live_view": {:hex, :phoenix_live_view, "1.0.4", "327491b033e79db2f887b065c5a2993228449091883d74cfa1baa12f8c98d5eb", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a9865316ddf8d78f382d63af278d20436b52d262b60239956817a61279514366"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"},
"phoenix_swoosh": {:hex, :phoenix_swoosh, "1.2.1", "b74ccaa8046fbc388a62134360ee7d9742d5a8ae74063f34eb050279de7a99e1", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "4000eeba3f9d7d1a6bf56d2bd56733d5cadf41a7f0d8ffe5bb67e7d667e204a2"},
"phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},
@@ -104,26 +104,26 @@
"plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "79fd4fcf34d110605c26560cbae8f23c603ec4158c08298bd4360fdea90bb5cf"},
"poison": {:hex, :poison, "5.0.0", "d2b54589ab4157bbb82ec2050757779bfed724463a544b6e20d79855a9e43b24", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "11dc6117c501b80c62a7594f941d043982a1bd05a1184280c0d9166eb4d8d3fc"},
"poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"},
- "postgrex": {:hex, :postgrex, "0.17.5", "0483d054938a8dc069b21bdd636bf56c487404c241ce6c319c1f43588246b281", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "50b8b11afbb2c4095a3ba675b4f055c416d0f3d7de6633a595fc131a828a67eb"},
+ "postgrex": {:hex, :postgrex, "0.20.0", "363ed03ab4757f6bc47942eff7720640795eb557e1935951c1626f0d303a3aed", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "d36ef8b36f323d29505314f704e21a1a038e2dc387c6409ee0cd24144e187c0f"},
"pot": {:hex, :pot, "1.0.2", "13abb849139fdc04ab8154986abbcb63bdee5de6ed2ba7e1713527e33df923dd", [:rebar3], [], "hexpm", "78fe127f5a4f5f919d6ea5a2a671827bd53eb9d37e5b4128c0ad3df99856c2e0"},
- "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
+ "ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"},
"recon": {:hex, :recon, "2.5.6", "9052588e83bfedfd9b72e1034532aee2a5369d9d9343b61aeb7fbce761010741", [:mix, :rebar3], [], "hexpm", "96c6799792d735cc0f0fd0f86267e9d351e63339cbe03df9d162010cefc26bb0"},
"remote_ip": {:hex, :remote_ip, "1.1.0", "cb308841595d15df3f9073b7c39243a1dd6ca56e5020295cb012c76fbec50f2d", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "616ffdf66aaad6a72fc546dabf42eed87e2a99e97b09cbd92b10cc180d02ed74"},
"search_parser": {:git, "https://github.com/FloatingGhost/pleroma-contrib-search-parser.git", "08971a81e68686f9ac465cfb6661d51c5e4e1e7f", [ref: "08971a81e68686f9ac465cfb6661d51c5e4e1e7f"]},
"sleeplocks": {:hex, :sleeplocks, "1.1.3", "96a86460cc33b435c7310dbd27ec82ca2c1f24ae38e34f8edde97f756503441a", [:rebar3], [], "hexpm", "d3b3958552e6eb16f463921e70ae7c767519ef8f5be46d7696cc1ed649421321"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
"statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"},
- "sweet_xml": {:hex, :sweet_xml, "0.7.4", "a8b7e1ce7ecd775c7e8a65d501bc2cd933bff3a9c41ab763f5105688ef485d08", [:mix], [], "hexpm", "e7c4b0bdbf460c928234951def54fe87edf1a170f6896675443279e2dbeba167"},
+ "sweet_xml": {:hex, :sweet_xml, "0.7.5", "803a563113981aaac202a1dbd39771562d0ad31004ddbfc9b5090bdcd5605277", [:mix], [], "hexpm", "193b28a9b12891cae351d81a0cead165ffe67df1b73fe5866d10629f4faefb12"},
"swoosh": {:hex, :swoosh, "1.14.4", "94e9dba91f7695a10f49b0172c4a4cb658ef24abef7e8140394521b7f3bbb2d4", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.4 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "081c5a590e4ba85cc89baddf7b2beecf6c13f7f84a958f1cd969290815f0f026"},
"syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"},
- "table_rex": {:hex, :table_rex, "4.0.0", "3c613a68ebdc6d4d1e731bc973c233500974ec3993c99fcdabb210407b90959b", [:mix], [], "hexpm", "c35c4d5612ca49ebb0344ea10387da4d2afe278387d4019e4d8111e815df8f55"},
+ "table_rex": {:hex, :table_rex, "4.1.0", "fbaa8b1ce154c9772012bf445bfb86b587430fb96f3b12022d3f35ee4a68c918", [:mix], [], "hexpm", "95932701df195d43bc2d1c6531178fc8338aa8f38c80f098504d529c43bc2601"},
"telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"},
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.2", "2caabe9344ec17eafe5403304771c3539f3b6e2f7fb6a6f602558c825d0d0bfb", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9b43db0dc33863930b9ef9d27137e78974756f5f198cae18409970ed6fa5b561"},
"telemetry_metrics_prometheus": {:hex, :telemetry_metrics_prometheus, "1.1.0", "1cc23e932c1ef9aa3b91db257ead31ea58d53229d407e059b29bb962c1505a13", [:mix], [{:plug_cowboy, "~> 2.1", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:telemetry_metrics_prometheus_core, "~> 1.0", [hex: :telemetry_metrics_prometheus_core, repo: "hexpm", optional: false]}], "hexpm", "d43b3659b3244da44fe0275b717701542365d4519b79d9ce895b9719c1ce4d26"},
"telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.1.0", "4e15f6d7dbedb3a4e3aed2262b7e1407f166fcb9c30ca3f96635dfbbef99965c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0dd10e7fe8070095df063798f82709b0a1224c31b8baf6278b423898d591a069"},
"telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"},
"temple": {:git, "https://akkoma.dev/AkkomaGang/temple.git", "066a699ade472d8fa42a9d730b29a61af9bc8b59", [ref: "066a699ade472d8fa42a9d730b29a61af9bc8b59"]},
- "tesla": {:hex, :tesla, "1.13.0", "24a068a48d107080dd7c943a593997eee265977a38020eb2ab657cca78a12502", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:mox, "~> 1.0", [hex: :mox, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "7b8fc8f6b0640fa0d090af7889d12eb396460e044b6f8688a8e55e30406a2200"},
+ "tesla": {:hex, :tesla, "1.14.1", "71c5b031b4e089c0fbfb2b362e24b4478465773ae4ef569760a8c2899ad1e73c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.21", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:mox, "~> 1.0", [hex: :mox, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "c1dde8140a49a3bef5bb622356e77ac5a24ad0c8091f12c3b7fc1077ce797155"},
"timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"},
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},
"tzdata": {:hex, :tzdata, "1.1.2", "45e5f1fcf8729525ec27c65e163be5b3d247ab1702581a94674e008413eef50b", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "cec7b286e608371602318c414f344941d5eb0375e14cfdab605cca2fe66cba8b"},
From c2f60c922807976b262910ca257d511c1c108c1f Mon Sep 17 00:00:00 2001
From: Floatingghost
Date: Sun, 23 Feb 2025 16:51:48 +0000
Subject: [PATCH 71/95] add a snapshot test for api prefixes
---
.formatter.exs | 3 +--
mix.exs | 5 +++--
mix.lock | 10 +++++++++
test/pleroma/web/router_test.exs | 38 ++++++++++++++++++++++++++++++++
test/test_helper.exs | 1 +
5 files changed, 53 insertions(+), 4 deletions(-)
create mode 100644 test/pleroma/web/router_test.exs
diff --git a/.formatter.exs b/.formatter.exs
index a96afe758..9a84d134b 100644
--- a/.formatter.exs
+++ b/.formatter.exs
@@ -1,6 +1,5 @@
[
- import_deps: [:ecto, :ecto_sql, :phoenix],
- subdirectories: ["priv/*/migrations"],
+ import_deps: [:mneme, :ecto, :ecto_sql, :phoenix],
plugins: [Phoenix.LiveView.HTMLFormatter],
inputs: [
"mix.exs",
diff --git a/mix.exs b/mix.exs
index e49c5cf36..d7cd43ca1 100644
--- a/mix.exs
+++ b/mix.exs
@@ -14,7 +14,7 @@ def project do
aliases: aliases(),
deps: deps(),
test_coverage: [tool: ExCoveralls],
- preferred_cli_env: ["coveralls.html": :test],
+ preferred_cli_env: ["coveralls.html": :test, "mneme.test": :test, "mneme.watch": :test],
# Docs
name: "Akkoma",
homepage_url: "https://akkoma.dev/",
@@ -210,7 +210,8 @@ defp deps do
{:dialyxir, "~> 1.3", only: [:dev], runtime: false},
{:elixir_xml_to_map, "~> 3.0", only: :test},
{:mint, "~> 1.5.1", override: true},
- {:nimble_pool, "~> 1.0", override: true}
+ {:nimble_pool, "~> 1.0", override: true},
+ {:mneme, "~> 0.10.2", only: [:dev, :test]}
] ++ oauth_deps()
end
diff --git a/mix.lock b/mix.lock
index 4a732a3f0..a57643e75 100644
--- a/mix.lock
+++ b/mix.lock
@@ -54,13 +54,16 @@
"floki": {:hex, :floki, "0.37.0", "b83e0280bbc6372f2a403b2848013650b16640cd2470aea6701f0632223d719e", [:mix], [], "hexpm", "516a0c15a69f78c47dc8e0b9b3724b29608aa6619379f91b1ffa47109b5d0dd3"},
"gen_smtp": {:hex, :gen_smtp, "1.2.0", "9cfc75c72a8821588b9b9fe947ae5ab2aed95a052b81237e0928633a13276fd3", [:rebar3], [{:ranch, ">= 1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "5ee0375680bca8f20c4d85f58c2894441443a743355430ff33a783fe03296779"},
"gettext": {:hex, :gettext, "0.22.3", "c8273e78db4a0bb6fba7e9f0fd881112f349a3117f7f7c598fa18c66c888e524", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "935f23447713954a6866f1bb28c3a878c4c011e802bcd68a726f5e558e4b64bd"},
+ "glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"},
"hackney": {:hex, :hackney, "1.22.0", "4efc68df70322d4d2e3d2744e9bd191a39a0cb8d08c35379a08d9fb0f040d595", [:rebar3], [{:certifi, "~> 2.14.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "628569e451820950382be3d3e6481d7c59997e606c7823bddb4ce5d10812dfcb"},
"hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"},
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
"http_signatures": {:git, "https://akkoma.dev/AkkomaGang/http_signatures.git", "d44c43d66758c6a73eaa4da9cffdbee0c5da44ae", [ref: "d44c43d66758c6a73eaa4da9cffdbee0c5da44ae"]},
"httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
+ "igniter": {:hex, :igniter, "0.5.27", "7c633dd99150e9cad68285ec8ad7e15833ff0c72d46774ed3be7728c661ec4cb", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "3042a71d4466e9c9b98a23d182eb02014a1c4802a35de0fa8233263d27c99550"},
"inet_cidr": {:hex, :inet_cidr, "1.0.8", "d26bb7bdbdf21ae401ead2092bf2bb4bf57fe44a62f5eaa5025280720ace8a40", [:mix], [], "hexpm", "d5b26da66603bb56c933c65214c72152f0de9a6ea53618b56d63302a68f6a90e"},
+ "inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"},
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
"joken": {:hex, :joken, "2.6.2", "5daaf82259ca603af4f0b065475099ada1b2b849ff140ccd37f4b6828ca6892a", [:mix], [{:jose, "~> 1.11.10", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "5134b5b0a6e37494e46dbf9e4dad53808e5e787904b7c73972651b51cce3d72b"},
"jose": {:hex, :jose, "1.11.10", "a903f5227417bd2a08c8a00a0cbcc458118be84480955e8d251297a425723f83", [:mix, :rebar3], [], "hexpm", "0d6cd36ff8ba174db29148fc112b5842186b68a90ce9fc2b3ec3afe76593e614"},
@@ -77,6 +80,7 @@
"mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"},
"mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"},
"mint": {:hex, :mint, "1.5.2", "4805e059f96028948870d23d7783613b7e6b0e2fb4e98d720383852a760067fd", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "d77d9e9ce4eb35941907f1d3df38d8f750c357865353e21d335bdcdf6d892a02"},
+ "mneme": {:hex, :mneme, "0.10.2", "f263ff74e993ef9eb160e04b608a149881e601ed6f5508c9afcafb578a184b52", [:mix], [{:file_system, "~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:igniter, "~> 0.3.76 or ~> 0.4.0 or ~> 0.5.0", [hex: :igniter, repo: "hexpm", optional: false]}, {:nimble_options, "~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:owl, "~> 0.9", [hex: :owl, repo: "hexpm", optional: false]}, {:rewrite, "~> 1.0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "3b9493fc114c4bb0f6232e021620ffd7944819b9b9105a5b286b6dc907f7720a"},
"mock": {:hex, :mock, "0.3.9", "10e44ad1f5962480c5c9b9fa779c6c63de9bd31997c8e04a853ec990a9d841af", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "9e1b244c4ca2551bb17bb8415eed89e40ee1308e0fbaed0a4fdfe3ec8a4adbd3"},
"mogrify": {:hex, :mogrify, "0.9.3", "238c782f00271dace01369ad35ae2e9dd020feee3443b9299ea5ea6bed559841", [:mix], [], "hexpm", "0189b1e1de27455f2b9ae8cf88239cefd23d38de9276eb5add7159aea51731e6"},
"mox": {:hex, :mox, "1.2.0", "a2cd96b4b80a3883e3100a221e8adc1b98e4c3a332a8fc434c39526babafd5b3", [:mix], [{:nimble_ownership, "~> 1.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}], "hexpm", "c7b92b3cc69ee24a7eeeaf944cd7be22013c52fcb580c1f33f50845ec821089a"},
@@ -88,6 +92,7 @@
"oban_met": {:hex, :oban_met, "1.0.1", "737db0064567b923d3f35efd1d3009dd1435d60ee6f98dbb55dbb83db8f4f4fa", [:mix], [{:oban, "~> 2.18", [hex: :oban, repo: "hexpm", optional: false]}], "hexpm", "0492d841f880b76c5b73081bc70ebea20ebacc08e871345f72c2270513f09957"},
"oban_web": {:hex, :oban_web, "2.11.1", "646492d92177a43ff869535f0c92380e17148f45bc2c8dc8f74c69f8a2874bad", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:oban, "~> 2.19", [hex: :oban, repo: "hexpm", optional: false]}, {:oban_met, "~> 1.0", [hex: :oban_met, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.7", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}], "hexpm", "d853c6af3f7c20d03a2bf7b1baad71835d50fbb98af05004e9b51da558b90b01"},
"open_api_spex": {:hex, :open_api_spex, "3.21.2", "6a704f3777761feeb5657340250d6d7332c545755116ca98f33d4b875777e1e5", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0 or ~> 6.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "f42ae6ed668b895ebba3e02773cfb4b41050df26f803f2ef634c72a7687dc387"},
+ "owl": {:hex, :owl, "0.12.2", "65906b525e5c3ef51bab6cba7687152be017aebe1da077bb719a5ee9f7e60762", [:mix], [{:ucwidth, "~> 0.2", [hex: :ucwidth, repo: "hexpm", optional: true]}], "hexpm", "6398efa9e1fea70a04d24231e10dcd66c1ac1aa2da418d20ef5357ec61de2880"},
"parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"},
"phoenix": {:hex, :phoenix, "1.7.20", "6bababaf27d59f5628f9b608de902a021be2cecefb8231e1dbdc0a2e2e480e9b", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.7", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "6be2ab98302e8784a31829e0d50d8bdfa81a23cd912c395bafd8b8bfb5a086c2"},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.6.3", "f686701b0499a07f2e3b122d84d52ff8a31f5def386e03706c916f6feddf69ef", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "909502956916a657a197f94cc1206d9a65247538de8a5e186f7537c895d95764"},
@@ -109,8 +114,12 @@
"ranch": {:hex, :ranch, "2.2.0", "25528f82bc8d7c6152c57666ca99ec716510fe0925cb188172f41ce93117b1b0", [:make, :rebar3], [], "hexpm", "fa0b99a1780c80218a4197a59ea8d3bdae32fbff7e88527d7d8a4787eff4f8e7"},
"recon": {:hex, :recon, "2.5.6", "9052588e83bfedfd9b72e1034532aee2a5369d9d9343b61aeb7fbce761010741", [:mix, :rebar3], [], "hexpm", "96c6799792d735cc0f0fd0f86267e9d351e63339cbe03df9d162010cefc26bb0"},
"remote_ip": {:hex, :remote_ip, "1.1.0", "cb308841595d15df3f9073b7c39243a1dd6ca56e5020295cb012c76fbec50f2d", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "616ffdf66aaad6a72fc546dabf42eed87e2a99e97b09cbd92b10cc180d02ed74"},
+ "req": {:hex, :req, "0.5.8", "50d8d65279d6e343a5e46980ac2a70e97136182950833a1968b371e753f6a662", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.17", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "d7fc5898a566477e174f26887821a3c5082b243885520ee4b45555f5d53f40ef"},
+ "rewrite": {:hex, :rewrite, "1.1.2", "f5a5d10f5fed1491a6ff48e078d4585882695962ccc9e6c779bae025d1f92eda", [:mix], [{:glob_ex, "~> 0.1", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.0", [hex: :sourceror, repo: "hexpm", optional: false]}, {:text_diff, "~> 0.1", [hex: :text_diff, repo: "hexpm", optional: false]}], "hexpm", "7f8b94b1e3528d0a47b3e8b7bfeca559d2948a65fa7418a9ad7d7712703d39d4"},
"search_parser": {:git, "https://github.com/FloatingGhost/pleroma-contrib-search-parser.git", "08971a81e68686f9ac465cfb6661d51c5e4e1e7f", [ref: "08971a81e68686f9ac465cfb6661d51c5e4e1e7f"]},
"sleeplocks": {:hex, :sleeplocks, "1.1.3", "96a86460cc33b435c7310dbd27ec82ca2c1f24ae38e34f8edde97f756503441a", [:rebar3], [], "hexpm", "d3b3958552e6eb16f463921e70ae7c767519ef8f5be46d7696cc1ed649421321"},
+ "sourceror": {:hex, :sourceror, "1.7.1", "599d78f4cc2be7d55c9c4fd0a8d772fd0478e3a50e726697c20d13d02aa056d4", [:mix], [], "hexpm", "cd6f268fe29fa00afbc535e215158680a0662b357dc784646d7dff28ac65a0fc"},
+ "spitfire": {:hex, :spitfire, "0.1.5", "10b041e781bff9544d2fdf00893e1a325758408c5366a9bfa4333072568659b1", [:mix], [], "hexpm", "866a55d21fe827934ff38200111335c9dd311df13cbf2580ed71d84b0a783150"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"},
"statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"},
"sweet_xml": {:hex, :sweet_xml, "0.7.5", "803a563113981aaac202a1dbd39771562d0ad31004ddbfc9b5090bdcd5605277", [:mix], [], "hexpm", "193b28a9b12891cae351d81a0cead165ffe67df1b73fe5866d10629f4faefb12"},
@@ -124,6 +133,7 @@
"telemetry_poller": {:hex, :telemetry_poller, "1.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"},
"temple": {:git, "https://akkoma.dev/AkkomaGang/temple.git", "066a699ade472d8fa42a9d730b29a61af9bc8b59", [ref: "066a699ade472d8fa42a9d730b29a61af9bc8b59"]},
"tesla": {:hex, :tesla, "1.14.1", "71c5b031b4e089c0fbfb2b362e24b4478465773ae4ef569760a8c2899ad1e73c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.21", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:mox, "~> 1.0", [hex: :mox, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "c1dde8140a49a3bef5bb622356e77ac5a24ad0c8091f12c3b7fc1077ce797155"},
+ "text_diff": {:hex, :text_diff, "0.1.0", "1caf3175e11a53a9a139bc9339bd607c47b9e376b073d4571c031913317fecaa", [:mix], [], "hexpm", "d1ffaaecab338e49357b6daa82e435f877e0649041ace7755583a0ea3362dbd7"},
"timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"},
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},
"tzdata": {:hex, :tzdata, "1.1.2", "45e5f1fcf8729525ec27c65e163be5b3d247ab1702581a94674e008413eef50b", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "cec7b286e608371602318c414f344941d5eb0375e14cfdab605cca2fe66cba8b"},
diff --git a/test/pleroma/web/router_test.exs b/test/pleroma/web/router_test.exs
new file mode 100644
index 000000000..913149619
--- /dev/null
+++ b/test/pleroma/web/router_test.exs
@@ -0,0 +1,38 @@
+defmodule Pleroma.Web.RouterTest do
+ use Pleroma.DataCase
+ use Mneme
+
+ test "route prefix stability" do
+ auto_assert(
+ [
+ "api",
+ "main",
+ "ostatus_subscribe",
+ "oauth",
+ "akkoma",
+ "objects",
+ "activities",
+ "notice",
+ "@:nickname",
+ ":nickname",
+ "users",
+ "tags",
+ "mailer",
+ "inbox",
+ "relay",
+ "internal",
+ ".well-known",
+ "nodeinfo",
+ "manifest.json",
+ "web",
+ "auth",
+ "embed",
+ "proxy",
+ "phoenix",
+ "test",
+ "user_exists",
+ "check_password"
+ ] <- Pleroma.Web.Router.get_api_routes()
+ )
+ end
+end
diff --git a/test/test_helper.exs b/test/test_helper.exs
index 6dcb87ff6..909b10365 100644
--- a/test/test_helper.exs
+++ b/test/test_helper.exs
@@ -9,6 +9,7 @@
exclude: [:federated, :erratic] ++ os_exclude
)
+Mneme.start()
Ecto.Adapters.SQL.Sandbox.mode(Pleroma.Repo, :manual)
{:ok, _} = Application.ensure_all_started(:ex_machina)
From 6222936673ea9462ee99d4c6650f3e7f73579e23 Mon Sep 17 00:00:00 2001
From: Floatingghost
Date: Sat, 1 Mar 2025 12:10:23 +0000
Subject: [PATCH 72/95] use akk.dev mfm parser
---
mix.exs | 4 ++--
mix.lock | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/mix.exs b/mix.exs
index 79735392e..e49dc73a0 100644
--- a/mix.exs
+++ b/mix.exs
@@ -196,8 +196,8 @@ defp deps do
{:elasticsearch,
git: "https://akkoma.dev/AkkomaGang/elasticsearch-elixir.git", ref: "main"},
{:mfm_parser,
- git: "https://codeberg.org/ilja/mfm_parser.git",
- ref: "be09e20e7a2fad2e9964ec4c563201d3fd1c4391"},
+ git: "https://akkoma.dev/AkkomaGang/mfm-parser.git",
+ ref: "360a30267a847810a63ab48f606ba227b2ca05f0"},
## dev & test
{:ex_doc, "~> 0.30", only: :dev, runtime: false},
diff --git a/mix.lock b/mix.lock
index 89e771d57..670dfbd6c 100644
--- a/mix.lock
+++ b/mix.lock
@@ -76,7 +76,7 @@
"makeup_erlang": {:hex, :makeup_erlang, "1.0.2", "03e1804074b3aa64d5fad7aa64601ed0fb395337b982d9bcf04029d68d51b6a7", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "af33ff7ef368d5893e4a267933e7744e46ce3cf1f61e2dccf53a111ed3aa3727"},
"meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
- "mfm_parser": {:git, "https://codeberg.org/ilja/mfm_parser.git", "be09e20e7a2fad2e9964ec4c563201d3fd1c4391", [ref: "be09e20e7a2fad2e9964ec4c563201d3fd1c4391"]},
+ "mfm_parser": {:git, "https://akkoma.dev/AkkomaGang/mfm-parser.git", "360a30267a847810a63ab48f606ba227b2ca05f0", [ref: "360a30267a847810a63ab48f606ba227b2ca05f0"]},
"mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"},
"mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"},
"mint": {:hex, :mint, "1.5.2", "4805e059f96028948870d23d7783613b7e6b0e2fb4e98d720383852a760067fd", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "d77d9e9ce4eb35941907f1d3df38d8f750c357865353e21d335bdcdf6d892a02"},
From 7ccc560e4d92395760ffc3aea176b3ddcb3b51a2 Mon Sep 17 00:00:00 2001
From: Floatingghost
Date: Sat, 1 Mar 2025 12:19:43 +0000
Subject: [PATCH 73/95] prepare 2025.03 release
---
CHANGELOG.md | 2 ++
mix.exs | 2 +-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index aa1c73975..f819fb0e8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## Unreleased
+## 2025.03
+
## Added
- Oban (worker) dashboard at `/akkoma/oban`
diff --git a/mix.exs b/mix.exs
index e49dc73a0..1a8c23d1b 100644
--- a/mix.exs
+++ b/mix.exs
@@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do
def project do
[
app: :pleroma,
- version: version("3.14.1"),
+ version: version("3.15.0"),
elixir: "~> 1.14",
elixirc_paths: elixirc_paths(Mix.env()),
compilers: Mix.compilers(),
From d62808e4b64eca2b12379f2a4ba2e9a29828626a Mon Sep 17 00:00:00 2001
From: Floatingghost
Date: Sat, 1 Mar 2025 16:28:12 +0000
Subject: [PATCH 74/95] move /outbox to signed pipeline
---
lib/pleroma/web/router.ex | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 5803e6a02..6e8b80980 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -810,7 +810,6 @@ defmodule Pleroma.Web.Router do
get("/users/:nickname/inbox", ActivityPubController, :read_inbox)
- get("/users/:nickname/outbox", ActivityPubController, :outbox)
get("/users/:nickname/collections/featured", ActivityPubController, :pinned)
end
@@ -826,6 +825,7 @@ defmodule Pleroma.Web.Router do
scope "/", Pleroma.Web.ActivityPub do
pipe_through(:activitypub)
post("/inbox", ActivityPubController, :inbox)
+ get("/users/:nickname/outbox", ActivityPubController, :outbox)
post("/users/:nickname/inbox", ActivityPubController, :inbox)
end
From 59ea358e525a5f8e38a552c44e6722825202b90d Mon Sep 17 00:00:00 2001
From: Floatingghost
Date: Sat, 1 Mar 2025 16:36:04 +0000
Subject: [PATCH 75/95] bump version
---
mix.exs | 2 +-
test/pleroma/web/activity_pub/activity_pub_controller_test.exs | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/mix.exs b/mix.exs
index 1a8c23d1b..8ec107c49 100644
--- a/mix.exs
+++ b/mix.exs
@@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do
def project do
[
app: :pleroma,
- version: version("3.15.0"),
+ version: version("3.15.1"),
elixir: "~> 1.14",
elixirc_paths: elixirc_paths(Mix.env()),
compilers: Mix.compilers(),
diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
index 49bbb9d63..7665471ca 100644
--- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
+++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs
@@ -1412,7 +1412,7 @@ test "it does not return a local note activity when unauthenticated", %{conn: co
|> get("/users/#{user.nickname}/outbox?page=true")
|> json_response(200)
- assert %{"orderedItems" => []} = resp
+ refute Map.has_key?(resp, "orderedItems")
end
test "it returns a note activity in a collection", %{conn: conn} do
From 522a168af627f6bb1d0bbe59b75fa18aff556885 Mon Sep 17 00:00:00 2001
From: Floatingghost
Date: Sat, 1 Mar 2025 17:27:45 +0000
Subject: [PATCH 76/95] force signatures for pinned posts
---
lib/pleroma/web/router.ex | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 6e8b80980..4365afd71 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -809,8 +809,6 @@ defmodule Pleroma.Web.Router do
pipe_through([:activitypub_client])
get("/users/:nickname/inbox", ActivityPubController, :read_inbox)
-
- get("/users/:nickname/collections/featured", ActivityPubController, :pinned)
end
scope "/", Pleroma.Web.ActivityPub do
@@ -827,6 +825,7 @@ defmodule Pleroma.Web.Router do
post("/inbox", ActivityPubController, :inbox)
get("/users/:nickname/outbox", ActivityPubController, :outbox)
post("/users/:nickname/inbox", ActivityPubController, :inbox)
+ get("/users/:nickname/collections/featured", ActivityPubController, :pinned)
end
scope "/relay", Pleroma.Web.ActivityPub do
From 9da2cb881ef166032b5c37f010c886eb97d32f1d Mon Sep 17 00:00:00 2001
From: Floatingghost
Date: Sun, 2 Mar 2025 11:32:40 +0000
Subject: [PATCH 77/95] upgrade oban migrations to v12
---
.../repo/migrations/20250302113138_upgrade_oban_to_v12.exs | 7 +++++++
1 file changed, 7 insertions(+)
create mode 100644 priv/repo/migrations/20250302113138_upgrade_oban_to_v12.exs
diff --git a/priv/repo/migrations/20250302113138_upgrade_oban_to_v12.exs b/priv/repo/migrations/20250302113138_upgrade_oban_to_v12.exs
new file mode 100644
index 000000000..6009d263c
--- /dev/null
+++ b/priv/repo/migrations/20250302113138_upgrade_oban_to_v12.exs
@@ -0,0 +1,7 @@
+defmodule Pleroma.Repo.Migrations.UpgradeObanToV12 do
+ use Ecto.Migration
+
+ def up, do: Oban.Migrations.up(version: 12)
+
+ def down, do: Oban.Migrations.down(version: 12)
+end
From fc2c7400082632fb8ba39900139846f44149d79c Mon Sep 17 00:00:00 2001
From: Floatingghost
Date: Sun, 2 Mar 2025 11:34:09 +0000
Subject: [PATCH 78/95] dependency upgrade
---
mix.lock | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/mix.lock b/mix.lock
index 670dfbd6c..6f539e516 100644
--- a/mix.lock
+++ b/mix.lock
@@ -8,7 +8,7 @@
"cachex": {:hex, :cachex, "3.6.0", "14a1bfbeee060dd9bec25a5b6f4e4691e3670ebda28c8ba2884b12fe30b36bf8", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "ebf24e373883bc8e0c8d894a63bbe102ae13d918f790121f5cfe6e485cc8e2e2"},
"calendar": {:hex, :calendar, "1.0.0", "f52073a708528482ec33d0a171954ca610fe2bd28f1e871f247dc7f1565fa807", [:mix], [{:tzdata, "~> 0.1.201603 or ~> 0.5.20 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "990e9581920c82912a5ee50e62ff5ef96da6b15949a2ee4734f935fdef0f0a6f"},
"captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "6630c42aaaab124e697b4e513190c89d8b64e410", [ref: "6630c42aaaab124e697b4e513190c89d8b64e410"]},
- "castore": {:hex, :castore, "1.0.11", "4bbd584741601eb658007339ea730b082cc61f3554cf2e8f39bf693a11b49073", [:mix], [], "hexpm", "e03990b4db988df56262852f20de0f659871c35154691427a5047f4967a16a62"},
+ "castore": {:hex, :castore, "1.0.12", "053f0e32700cbec356280c0e835df425a3be4bc1e0627b714330ad9d0f05497f", [:mix], [], "hexpm", "3dca286b2186055ba0c9449b4e95b97bf1b57b47c1f2644555879e659960c224"},
"certifi": {:hex, :certifi, "2.14.0", "ed3bef654e69cde5e6c022df8070a579a79e8ba2368a00acf3d75b82d9aceeed", [:rebar3], [], "hexpm", "ea59d87ef89da429b8e905264fdec3419f84f2215bb3d81e07a18aac919026c3"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
"comeonin": {:hex, :comeonin, "5.5.1", "5113e5f3800799787de08a6e0db307133850e635d34e9fab23c70b6501669510", [:mix], [], "hexpm", "65aac8f19938145377cee73973f192c5645873dcf550a8a6b18187d17c13ccdb"},
@@ -55,13 +55,13 @@
"gen_smtp": {:hex, :gen_smtp, "1.2.0", "9cfc75c72a8821588b9b9fe947ae5ab2aed95a052b81237e0928633a13276fd3", [:rebar3], [{:ranch, ">= 1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "5ee0375680bca8f20c4d85f58c2894441443a743355430ff33a783fe03296779"},
"gettext": {:hex, :gettext, "0.22.3", "c8273e78db4a0bb6fba7e9f0fd881112f349a3117f7f7c598fa18c66c888e524", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "935f23447713954a6866f1bb28c3a878c4c011e802bcd68a726f5e558e4b64bd"},
"glob_ex": {:hex, :glob_ex, "0.1.11", "cb50d3f1ef53f6ca04d6252c7fde09fd7a1cf63387714fe96f340a1349e62c93", [:mix], [], "hexpm", "342729363056e3145e61766b416769984c329e4378f1d558b63e341020525de4"},
- "hackney": {:hex, :hackney, "1.22.0", "4efc68df70322d4d2e3d2744e9bd191a39a0cb8d08c35379a08d9fb0f040d595", [:rebar3], [{:certifi, "~> 2.14.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "628569e451820950382be3d3e6481d7c59997e606c7823bddb4ce5d10812dfcb"},
+ "hackney": {:hex, :hackney, "1.23.0", "55cc09077112bcb4a69e54be46ed9bc55537763a96cd4a80a221663a7eafd767", [:rebar3], [{:certifi, "~> 2.14.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "6cd1c04cd15c81e5a493f167b226a15f0938a84fc8f0736ebe4ddcab65c0b44e"},
"hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"},
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
"http_signatures": {:git, "https://akkoma.dev/AkkomaGang/http_signatures.git", "d44c43d66758c6a73eaa4da9cffdbee0c5da44ae", [ref: "d44c43d66758c6a73eaa4da9cffdbee0c5da44ae"]},
"httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
- "igniter": {:hex, :igniter, "0.5.27", "7c633dd99150e9cad68285ec8ad7e15833ff0c72d46774ed3be7728c661ec4cb", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "3042a71d4466e9c9b98a23d182eb02014a1c4802a35de0fa8233263d27c99550"},
+ "igniter": {:hex, :igniter, "0.5.29", "6bf7ddaf15e88ae75f6dad514329530ec8f4721ba14782f6386a7345c1be99fd", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "beb6e0f69fc6d4e3975ffa26c5459fc63fd96f85cfaeba984c2dfd3d7333b6ad"},
"inet_cidr": {:hex, :inet_cidr, "1.0.8", "d26bb7bdbdf21ae401ead2092bf2bb4bf57fe44a62f5eaa5025280720ace8a40", [:mix], [], "hexpm", "d5b26da66603bb56c933c65214c72152f0de9a6ea53618b56d63302a68f6a90e"},
"inflex": {:hex, :inflex, "2.1.0", "a365cf0821a9dacb65067abd95008ca1b0bb7dcdd85ae59965deef2aa062924c", [:mix], [], "hexpm", "14c17d05db4ee9b6d319b0bff1bdf22aa389a25398d1952c7a0b5f3d93162dd8"},
"jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"},
@@ -98,13 +98,13 @@
"phoenix_ecto": {:hex, :phoenix_ecto, "4.6.3", "f686701b0499a07f2e3b122d84d52ff8a31f5def386e03706c916f6feddf69ef", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "909502956916a657a197f94cc1206d9a65247538de8a5e186f7537c895d95764"},
"phoenix_html": {:hex, :phoenix_html, "3.3.4", "42a09fc443bbc1da37e372a5c8e6755d046f22b9b11343bf885067357da21cb3", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0249d3abec3714aff3415e7ee3d9786cb325be3151e6c4b3021502c585bf53fb"},
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.8.6", "7b1f0327f54c9eb69845fd09a77accf922f488c549a7e7b8618775eb603a62c7", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:ecto_sqlite3_extras, "~> 1.1.7 or ~> 1.2.0", [hex: :ecto_sqlite3_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.19 or ~> 1.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "1681ab813ec26ca6915beb3414aa138f298e17721dc6a2bde9e6eb8a62360ff6"},
- "phoenix_live_view": {:hex, :phoenix_live_view, "1.0.4", "327491b033e79db2f887b065c5a2993228449091883d74cfa1baa12f8c98d5eb", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a9865316ddf8d78f382d63af278d20436b52d262b60239956817a61279514366"},
+ "phoenix_live_view": {:hex, :phoenix_live_view, "1.0.5", "f072166f87c44ffaf2b47b65c5ced8c375797830e517bfcf0a006fe7eb113911", [:mix], [{:floki, "~> 0.36", [hex: :floki, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.15", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "94abbc84df8a93a64514fc41528695d7326b6f3095e906b32f264ec4280811f3"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"},
"phoenix_swoosh": {:hex, :phoenix_swoosh, "1.2.1", "b74ccaa8046fbc388a62134360ee7d9742d5a8ae74063f34eb050279de7a99e1", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "4000eeba3f9d7d1a6bf56d2bd56733d5cadf41a7f0d8ffe5bb67e7d667e204a2"},
"phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"},
"phoenix_view": {:hex, :phoenix_view, "2.0.4", "b45c9d9cf15b3a1af5fb555c674b525391b6a1fe975f040fb4d913397b31abf4", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "4e992022ce14f31fe57335db27a28154afcc94e9983266835bb3040243eb620b"},
"plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"},
- "plug_cowboy": {:hex, :plug_cowboy, "2.7.2", "fdadb973799ae691bf9ecad99125b16625b1c6039999da5fe544d99218e662e4", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "245d8a11ee2306094840c000e8816f0cbed69a23fc0ac2bcf8d7835ae019bb2f"},
+ "plug_cowboy": {:hex, :plug_cowboy, "2.7.3", "1304d36752e8bdde213cea59ef424ca932910a91a07ef9f3874be709c4ddb94b", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "77c95524b2aa5364b247fa17089029e73b951ebc1adeef429361eab0bb55819d"},
"plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},
"plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "79fd4fcf34d110605c26560cbae8f23c603ec4158c08298bd4360fdea90bb5cf"},
"poison": {:hex, :poison, "5.0.0", "d2b54589ab4157bbb82ec2050757779bfed724463a544b6e20d79855a9e43b24", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "11dc6117c501b80c62a7594f941d043982a1bd05a1184280c0d9166eb4d8d3fc"},
From b1c0b9e01adcb108b5946fa0c1dc0e2330afd2f0 Mon Sep 17 00:00:00 2001
From: Floatingghost
Date: Sun, 2 Mar 2025 11:49:41 +0000
Subject: [PATCH 79/95] test lowest and highest supported versions on PR
---
.woodpecker/test.yml | 14 ++++++++------
ci/Dockerfile | 13 ++++++-------
ci/build-all.sh | 2 ++
ci/build.sh | 3 +++
ci/build_and_push.sh | 1 -
.../docs/installation/generic_dependencies.include | 2 +-
mix.exs | 2 +-
7 files changed, 21 insertions(+), 16 deletions(-)
create mode 100755 ci/build-all.sh
create mode 100755 ci/build.sh
delete mode 100755 ci/build_and_push.sh
diff --git a/.woodpecker/test.yml b/.woodpecker/test.yml
index bef503752..20a06eba4 100644
--- a/.woodpecker/test.yml
+++ b/.woodpecker/test.yml
@@ -5,16 +5,18 @@ depends_on:
- lint
matrix:
+ # test the lowest and highest versions
ELIXIR_VERSION:
- - 1.14
- 1.15
- - 1.16
+ - 1.18
OTP_VERSION:
- - 25
- - 26
+ - 24
+ - 27
include:
- - ELIXIR_VERSION: 1.16
- OTP_VERSION: 26
+ - ELIXIR_VERSION: 1.15
+ OTP_VERSION: 24
+ - ELIXIR_VERSION: 1.18
+ OTP_VERSION: 27
variables:
- &setup-hex "mix local.hex --force && mix local.rebar --force"
diff --git a/ci/Dockerfile b/ci/Dockerfile
index e6a8b438c..9a399efe3 100644
--- a/ci/Dockerfile
+++ b/ci/Dockerfile
@@ -1,7 +1,6 @@
-FROM elixir:1.9.4
-
-RUN apt-get update &&\
- apt-get install -y libmagic-dev cmake libimage-exiftool-perl ffmpeg &&\
- mix local.hex --force &&\
- mix local.rebar --force
-
+ARG TAG
+FROM docker.io/hexpm/elixir:${TAG}
+RUN apk update
+RUN apk add git gcc g++ musl-dev make cmake file-dev rclone wget zip imagemagick ffmpeg perl-image-exiftool exiftool
+RUN mkdir /src
+WORKDIR /src
diff --git a/ci/build-all.sh b/ci/build-all.sh
new file mode 100755
index 000000000..6aa0fd587
--- /dev/null
+++ b/ci/build-all.sh
@@ -0,0 +1,2 @@
+./build.sh 1.15-otp24 1.15.8-erlang-24.3.4.17-alpine-3.19.7
+./build.sh 1.18-otp27 1.18.2-erlang-27.2.4-alpine-3.19.7
diff --git a/ci/build.sh b/ci/build.sh
new file mode 100755
index 000000000..bf80b9208
--- /dev/null
+++ b/ci/build.sh
@@ -0,0 +1,3 @@
+echo "Building $1 using image tag $2"
+docker build -t docker.io/akkoma/ci-base:$1 --build-arg=version=$1 --build-arg=TAG=$2 .
+docker push docker.io/akkoma/ci-base:$1
diff --git a/ci/build_and_push.sh b/ci/build_and_push.sh
deleted file mode 100755
index 484cc2643..000000000
--- a/ci/build_and_push.sh
+++ /dev/null
@@ -1 +0,0 @@
-docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t git.pleroma.social:5050/pleroma/pleroma/ci-base:latest --push .
diff --git a/docs/docs/installation/generic_dependencies.include b/docs/docs/installation/generic_dependencies.include
index f4d439b83..098a7c7d8 100644
--- a/docs/docs/installation/generic_dependencies.include
+++ b/docs/docs/installation/generic_dependencies.include
@@ -1,7 +1,7 @@
## Required dependencies
* PostgreSQL 12+
-* Elixir 1.14+ (currently tested up to 1.17)
+* Elixir 1.15+ (currently tested up to 1.18)
* Erlang OTP 25+ (currently tested up to OTP27)
* git
* file / libmagic
diff --git a/mix.exs b/mix.exs
index 8ec107c49..8f16d3aac 100644
--- a/mix.exs
+++ b/mix.exs
@@ -5,7 +5,7 @@ def project do
[
app: :pleroma,
version: version("3.15.1"),
- elixir: "~> 1.14",
+ elixir: "~> 1.15",
elixirc_paths: elixirc_paths(Mix.env()),
compilers: Mix.compilers(),
elixirc_options: [warnings_as_errors: warnings_as_errors()],
From f176294d6d1597e13b325f00d1472bd3e0c0a85f Mon Sep 17 00:00:00 2001
From: Floatingghost
Date: Sun, 2 Mar 2025 11:54:00 +0000
Subject: [PATCH 80/95] elixir 1.18 formatting
---
lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex | 6 +++++-
lib/pleroma/web/activity_pub/mrf/simple_policy.ex | 2 +-
.../web/activity_pub/object_validators/user_validator.ex | 5 ++++-
lib/pleroma/web/mastodon_api/views/status_view.ex | 4 +++-
lib/pleroma/workers/mailer_worker.ex | 4 +++-
lib/pleroma/workers/mute_expire_worker.ex | 4 +++-
lib/pleroma/workers/publisher_worker.ex | 4 +++-
7 files changed, 22 insertions(+), 7 deletions(-)
diff --git a/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex b/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex
index 627f52168..bccb1a37e 100644
--- a/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex
@@ -24,7 +24,11 @@ defp score_displayname("federationbot"), do: 1.0
defp score_displayname("fedibot"), do: 1.0
defp score_displayname(_), do: 0.0
- defp determine_if_followbot(%User{nickname: nickname, name: displayname, actor_type: actor_type}) do
+ defp determine_if_followbot(%User{
+ nickname: nickname,
+ name: displayname,
+ actor_type: actor_type
+ }) do
# nickname will be a binary string except when following a relay
nick_score =
if is_binary(nickname) do
diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
index 0b8b846ec..a49d75237 100644
--- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
@@ -108,7 +108,7 @@ defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do
end
defp intersection(list1, list2) do
- list1 -- list1 -- list2
+ list1 -- (list1 -- list2)
end
defp check_followers_only(%{host: actor_host} = _actor_info, object) do
diff --git a/lib/pleroma/web/activity_pub/object_validators/user_validator.ex b/lib/pleroma/web/activity_pub/object_validators/user_validator.ex
index f75e2f87e..5612ded1b 100644
--- a/lib/pleroma/web/activity_pub/object_validators/user_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/user_validator.ex
@@ -34,7 +34,10 @@ def validate(%{"type" => type, "id" => _id} = data, meta)
def validate(_, _), do: {:error, "Not a user object"}
- defp validate_pubkey(%{"id" => user_id, "publicKey" => %{"id" => pk_id, "publicKeyPem" => _key}}) do
+ defp validate_pubkey(%{
+ "id" => user_id,
+ "publicKey" => %{"id" => pk_id, "publicKeyPem" => _key}
+ }) do
with {_, true} <- {:user, is_binary(user_id)},
{_, true} <- {:key, is_binary(pk_id)},
:ok <- Containment.contain_key_user(pk_id, user_id) do
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index a5229f5e4..65aeecfcf 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -617,7 +617,9 @@ def render("context.json", %{activity: activity, activities: activities, user: u
%{ancestors: ancestors, descendants: descendants} =
activities
|> Enum.reverse()
- |> Enum.group_by(fn %{id: id} -> if id < activity.id, do: :ancestors, else: :descendants end)
+ |> Enum.group_by(fn %{id: id} ->
+ if id < activity.id, do: :ancestors, else: :descendants
+ end)
|> Map.put_new(:ancestors, [])
|> Map.put_new(:descendants, [])
diff --git a/lib/pleroma/workers/mailer_worker.ex b/lib/pleroma/workers/mailer_worker.ex
index 592230e7a..48c3fc9d1 100644
--- a/lib/pleroma/workers/mailer_worker.ex
+++ b/lib/pleroma/workers/mailer_worker.ex
@@ -6,7 +6,9 @@ defmodule Pleroma.Workers.MailerWorker do
use Pleroma.Workers.WorkerHelper, queue: "mailer"
@impl Oban.Worker
- def perform(%Job{args: %{"op" => "email", "encoded_email" => encoded_email, "config" => config}}) do
+ def perform(%Job{
+ args: %{"op" => "email", "encoded_email" => encoded_email, "config" => config}
+ }) do
encoded_email
|> Base.decode64!()
|> :erlang.binary_to_term()
diff --git a/lib/pleroma/workers/mute_expire_worker.ex b/lib/pleroma/workers/mute_expire_worker.ex
index 8da903e76..c253027d9 100644
--- a/lib/pleroma/workers/mute_expire_worker.ex
+++ b/lib/pleroma/workers/mute_expire_worker.ex
@@ -6,7 +6,9 @@ defmodule Pleroma.Workers.MuteExpireWorker do
use Pleroma.Workers.WorkerHelper, queue: "mute_expire"
@impl Oban.Worker
- def perform(%Job{args: %{"op" => "unmute_user", "muter_id" => muter_id, "mutee_id" => mutee_id}}) do
+ def perform(%Job{
+ args: %{"op" => "unmute_user", "muter_id" => muter_id, "mutee_id" => mutee_id}
+ }) do
Pleroma.User.unmute(muter_id, mutee_id)
:ok
end
diff --git a/lib/pleroma/workers/publisher_worker.ex b/lib/pleroma/workers/publisher_worker.ex
index 545887071..be94134b9 100644
--- a/lib/pleroma/workers/publisher_worker.ex
+++ b/lib/pleroma/workers/publisher_worker.ex
@@ -13,7 +13,9 @@ def backoff(%Job{attempt: attempt}) when is_integer(attempt) do
end
@impl Oban.Worker
- def perform(%Job{args: %{"op" => "publish", "activity_id" => activity_id, "object_data" => nil}}) do
+ def perform(%Job{
+ args: %{"op" => "publish", "activity_id" => activity_id, "object_data" => nil}
+ }) do
activity = Activity.get_by_id(activity_id)
Federator.perform(:publish, activity)
end
From 842414b9274553cb740517dc1ec51ed50fe4cb94 Mon Sep 17 00:00:00 2001
From: Floatingghost
Date: Sun, 2 Mar 2025 11:56:15 +0000
Subject: [PATCH 81/95] run the lint task on the latest version
---
.woodpecker/lint.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml
index 70e8fc95d..632465fa4 100644
--- a/.woodpecker/lint.yml
+++ b/.woodpecker/lint.yml
@@ -40,7 +40,7 @@ variables:
steps:
lint:
- image: akkoma/ci-base:1.16-otp26
+ image: akkoma/ci-base:1.18-otp27
<<: *on-pr-open
environment:
MIX_ENV: test
From 829af03042fe69d70615dcb76fc0a4f8958d1b21 Mon Sep 17 00:00:00 2001
From: Floatingghost
Date: Sun, 2 Mar 2025 12:19:14 +0000
Subject: [PATCH 82/95] we don't support otp24, bump to 25
---
.woodpecker/test.yml | 4 ++--
ci/build-all.sh | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/.woodpecker/test.yml b/.woodpecker/test.yml
index 20a06eba4..2b8d495bc 100644
--- a/.woodpecker/test.yml
+++ b/.woodpecker/test.yml
@@ -10,11 +10,11 @@ matrix:
- 1.15
- 1.18
OTP_VERSION:
- - 24
+ - 25
- 27
include:
- ELIXIR_VERSION: 1.15
- OTP_VERSION: 24
+ OTP_VERSION: 25
- ELIXIR_VERSION: 1.18
OTP_VERSION: 27
diff --git a/ci/build-all.sh b/ci/build-all.sh
index 6aa0fd587..854a0092f 100755
--- a/ci/build-all.sh
+++ b/ci/build-all.sh
@@ -1,2 +1,2 @@
-./build.sh 1.15-otp24 1.15.8-erlang-24.3.4.17-alpine-3.19.7
+./build.sh 1.15-otp25 1.15.8-erlang-25.3.2.18-alpine-3.19.7
./build.sh 1.18-otp27 1.18.2-erlang-27.2.4-alpine-3.19.7
From 184c62359fab3bbddda19c6f10c65c77b7a31b74 Mon Sep 17 00:00:00 2001
From: Floatingghost
Date: Sun, 2 Mar 2025 13:04:10 +0000
Subject: [PATCH 83/95] drop back to 1.14/OTP25
---
.woodpecker/test.yml | 4 ++--
ci/build-all.sh | 1 +
config/config.exs | 1 +
docs/docs/installation/generic_dependencies.include | 2 +-
4 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/.woodpecker/test.yml b/.woodpecker/test.yml
index 2b8d495bc..f4044000c 100644
--- a/.woodpecker/test.yml
+++ b/.woodpecker/test.yml
@@ -7,13 +7,13 @@ depends_on:
matrix:
# test the lowest and highest versions
ELIXIR_VERSION:
- - 1.15
+ - 1.14
- 1.18
OTP_VERSION:
- 25
- 27
include:
- - ELIXIR_VERSION: 1.15
+ - ELIXIR_VERSION: 1.14
OTP_VERSION: 25
- ELIXIR_VERSION: 1.18
OTP_VERSION: 27
diff --git a/ci/build-all.sh b/ci/build-all.sh
index 854a0092f..8d3132637 100755
--- a/ci/build-all.sh
+++ b/ci/build-all.sh
@@ -1,2 +1,3 @@
+./build.sh 1.14-otp25 1.14.3-erlang-25.3.2-alpine-3.18.0
./build.sh 1.15-otp25 1.15.8-erlang-25.3.2.18-alpine-3.19.7
./build.sh 1.18-otp27 1.18.2-erlang-27.2.4-alpine-3.19.7
diff --git a/config/config.exs b/config/config.exs
index be6a0d81c..f19f6fa76 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -558,6 +558,7 @@
],
email_blacklist: []
+config :oban_met, auto_start: false
config :pleroma, Oban,
repo: Pleroma.Repo,
log: false,
diff --git a/docs/docs/installation/generic_dependencies.include b/docs/docs/installation/generic_dependencies.include
index 098a7c7d8..9e75a0151 100644
--- a/docs/docs/installation/generic_dependencies.include
+++ b/docs/docs/installation/generic_dependencies.include
@@ -1,7 +1,7 @@
## Required dependencies
* PostgreSQL 12+
-* Elixir 1.15+ (currently tested up to 1.18)
+* Elixir 1.14.1+ (currently tested up to 1.18)
* Erlang OTP 25+ (currently tested up to OTP27)
* git
* file / libmagic
From 8e789c62365a1c11eb0f0c1b51f2b67710f99fee Mon Sep 17 00:00:00 2001
From: Floatingghost
Date: Sun, 2 Mar 2025 13:07:03 +0000
Subject: [PATCH 84/95] 1.14.1 min version
---
mix.exs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/mix.exs b/mix.exs
index 8f16d3aac..23c7beab3 100644
--- a/mix.exs
+++ b/mix.exs
@@ -5,7 +5,7 @@ def project do
[
app: :pleroma,
version: version("3.15.1"),
- elixir: "~> 1.15",
+ elixir: "~> 1.14.1",
elixirc_paths: elixirc_paths(Mix.env()),
compilers: Mix.compilers(),
elixirc_options: [warnings_as_errors: warnings_as_errors()],
From 41a4ed1db5601c4da061925fce8df54ed601d7ae Mon Sep 17 00:00:00 2001
From: Floatingghost
Date: Sun, 2 Mar 2025 13:17:52 +0000
Subject: [PATCH 85/95] specify correct version
---
mix.exs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/mix.exs b/mix.exs
index 23c7beab3..ba89b11f4 100644
--- a/mix.exs
+++ b/mix.exs
@@ -5,7 +5,7 @@ def project do
[
app: :pleroma,
version: version("3.15.1"),
- elixir: "~> 1.14.1",
+ elixir: "~> 1.14.1 or ~> 1.15",
elixirc_paths: elixirc_paths(Mix.env()),
compilers: Mix.compilers(),
elixirc_options: [warnings_as_errors: warnings_as_errors()],
From 93200a80731595c94d4b308907cf300aac15896c Mon Sep 17 00:00:00 2001
From: Floatingghost
Date: Sun, 2 Mar 2025 13:36:14 +0000
Subject: [PATCH 86/95] use latest ASDF instructions
---
config/config.exs | 1 +
docs/docs/installation/debian_based_en.md | 10 +++++-----
2 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/config/config.exs b/config/config.exs
index f19f6fa76..e60b2db3a 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -559,6 +559,7 @@
email_blacklist: []
config :oban_met, auto_start: false
+
config :pleroma, Oban,
repo: Pleroma.Repo,
log: false,
diff --git a/docs/docs/installation/debian_based_en.md b/docs/docs/installation/debian_based_en.md
index 442849e69..ba9ee3bd4 100644
--- a/docs/docs/installation/debian_based_en.md
+++ b/docs/docs/installation/debian_based_en.md
@@ -61,15 +61,15 @@ Next install Erlang:
```shell
asdf plugin add erlang https://github.com/asdf-vm/asdf-erlang.git
export KERL_CONFIGURE_OPTIONS="--disable-debug --without-javac"
-asdf install erlang 26.2.5.4
-asdf global erlang 26.2.5.4
+asdf install erlang 27.2.4
+asdf set erlang 27.2.4
```
Now install Elixir:
```shell
-asdf plugin-add elixir https://github.com/asdf-vm/asdf-elixir.git
-asdf install elixir 1.17.3-otp-26
-asdf global elixir 1.17.3-otp-26
+asdf plugin add elixir https://github.com/asdf-vm/asdf-elixir.git
+asdf install elixir 1.18.2-otp-27
+asdf set elixir 1.18.2-otp-27
```
Confirm that Elixir is installed correctly by checking the version:
From 4a05b2d64393d21e0902a90bd85ed2d752746691 Mon Sep 17 00:00:00 2001
From: Floatingghost
Date: Sun, 2 Mar 2025 13:36:52 +0000
Subject: [PATCH 87/95] we do actually want to start oban-met...
---
config/config.exs | 2 --
1 file changed, 2 deletions(-)
diff --git a/config/config.exs b/config/config.exs
index e60b2db3a..be6a0d81c 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -558,8 +558,6 @@
],
email_blacklist: []
-config :oban_met, auto_start: false
-
config :pleroma, Oban,
repo: Pleroma.Repo,
log: false,
From 066d5b48ed463a14129e9015211c769ff61affeb Mon Sep 17 00:00:00 2001
From: Oneric
Date: Mon, 10 Mar 2025 18:45:12 +0100
Subject: [PATCH 88/95] Fix Content-Type sanitisation for emoji and local
uploads
This was accidentally broken in c8e0f7848bd72434a30e450a5af8c356edb7fbcb
due to a one-letter mistake in the plug option name and an absence of
tests. Therefore it was once again possible to serve e.g. Javascript or
CSS payloads via uploads and emoji.
However due to other protections it was still NOT possible for anyone to
serve any payload with an ActivityPub Content-Type. With the CSP policy
hardening from previous JS payload exloits predating the Content-Type
sanitisation, there is currently no known way of abusing this weakened
Content-Type sanitisation, but should be fixed regardless.
This commit fixes the option name and adds tests to ensure
such a regression doesn't occur again in the future.
Reported-by: Lain Soykaf
---
lib/pleroma/web/plugs/instance_static.ex | 2 +-
lib/pleroma/web/plugs/uploaded_media.ex | 2 +-
.../web/content_type_sanitisation_test.exs | 66 +++++++++++++++
.../web/content_type_sanitisation_template.ex | 81 +++++++++++++++++++
4 files changed, 149 insertions(+), 2 deletions(-)
create mode 100644 test/pleroma/web/content_type_sanitisation_test.exs
create mode 100644 test/support/web/content_type_sanitisation_template.ex
diff --git a/lib/pleroma/web/plugs/instance_static.ex b/lib/pleroma/web/plugs/instance_static.ex
index ec188986b..5456634a1 100644
--- a/lib/pleroma/web/plugs/instance_static.ex
+++ b/lib/pleroma/web/plugs/instance_static.ex
@@ -62,7 +62,7 @@ defp call_static(%{request_path: request_path} = conn, opts, from) do
opts =
opts
|> Map.put(:from, from)
- |> Map.put(:content_type, false)
+ |> Map.put(:content_types, false)
conn
|> set_static_content_type(request_path)
diff --git a/lib/pleroma/web/plugs/uploaded_media.ex b/lib/pleroma/web/plugs/uploaded_media.ex
index 357fcb432..15d6190df 100644
--- a/lib/pleroma/web/plugs/uploaded_media.ex
+++ b/lib/pleroma/web/plugs/uploaded_media.ex
@@ -88,7 +88,7 @@ defp get_media(conn, {:static_dir, directory}, opts) do
Map.get(opts, :static_plug_opts)
|> Map.put(:at, [@path])
|> Map.put(:from, directory)
- |> Map.put(:content_type, false)
+ |> Map.put(:content_types, false)
conn =
conn
diff --git a/test/pleroma/web/content_type_sanitisation_test.exs b/test/pleroma/web/content_type_sanitisation_test.exs
new file mode 100644
index 000000000..6aa335b6e
--- /dev/null
+++ b/test/pleroma/web/content_type_sanitisation_test.exs
@@ -0,0 +1,66 @@
+# Akkoma: Magically expressive social media
+# Copyright © 2025 Akkoma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ContentTypeSanitisationTest do
+ use Pleroma.Web.ConnCase, async: true
+
+ alias Pleroma.Web.ContentTypeSanitisationTemplate, as: Template
+
+ require Template
+
+ defp create_file(path, body) do
+ File.write!(path, body)
+ on_exit(fn -> File.rm(path) end)
+ end
+
+ defp upload_dir(),
+ do: Path.join(Pleroma.Uploaders.Local.upload_path(), "test_StaticPlugSanitisationTest")
+
+ defp create_upload(subpath, body) do
+ Path.join(upload_dir(), subpath)
+ |> create_file(body)
+
+ "/media/test_StaticPlugSanitisationTest/#{subpath}"
+ end
+
+ defp emoji_dir(),
+ do:
+ Path.join(
+ Pleroma.Config.get!([:instance, :static_dir]),
+ "emoji/test_StaticPlugSanitisationTest"
+ )
+
+ defp create_emoji(subpath, body) do
+ Path.join(emoji_dir(), subpath)
+ |> create_file(body)
+
+ "/emoji/test_StaticPlugSanitisationTest/#{subpath}"
+ end
+
+ setup_all do
+ File.mkdir_p(upload_dir())
+ File.mkdir_p(emoji_dir())
+
+ on_exit(fn ->
+ File.rm_rf!(upload_dir())
+ File.rm_rf!(emoji_dir())
+ end)
+ end
+
+ describe "sanitises Content-Type of local uploads" do
+ Template.do_all_common_tests(&create_upload/2)
+
+ test "while preserving audio types" do
+ Template.do_audio_test(&create_upload/2, false)
+ end
+ end
+
+ describe "sanitises Content-Type of emoji" do
+ Template.do_all_common_tests(&create_emoji/2)
+
+ test "if audio type" do
+ Template.do_audio_test(&create_emoji/2, true)
+ end
+ end
+end
diff --git a/test/support/web/content_type_sanitisation_template.ex b/test/support/web/content_type_sanitisation_template.ex
new file mode 100644
index 000000000..c785a0e87
--- /dev/null
+++ b/test/support/web/content_type_sanitisation_template.ex
@@ -0,0 +1,81 @@
+# Akkoma: Magically expressive social media
+# Copyright © 2025 Akkoma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ContentTypeSanitisationTemplate do
+ defmacro do_test(create_fun, fname, body, content_type) do
+ quote do
+ url = unquote(create_fun).(unquote(fname), unquote(body))
+ resp = get(build_conn(), url)
+ assert resp.status == 200
+
+ assert Enum.all?(
+ Plug.Conn.get_resp_header(resp, "content-type"),
+ fn e -> e == unquote(content_type) end
+ )
+ end
+ end
+
+ defmacro do_all_common_tests(create_fun) do
+ quote do
+ test "while preserving image types" do
+ unquote(__MODULE__).do_test(
+ unquote(create_fun),
+ "image.jpg",
+ File.read!("test/fixtures/image.jpg"),
+ "image/jpeg"
+ )
+ end
+
+ test "if ActivityPub type" do
+ # this already ought to be impossible from the configured MIME mapping, but let’s make sure
+ unquote(__MODULE__).do_test(
+ unquote(create_fun),
+ "ap.activity+json",
+ "{\"a\": \"b\"}",
+ "application/octet-stream"
+ )
+ end
+
+ test "if PDF type" do
+ unquote(__MODULE__).do_test(
+ unquote(create_fun),
+ "pdf.pdf",
+ "pdf stub",
+ "application/octet-stream"
+ )
+ end
+
+ test "if Javascript type" do
+ unquote(__MODULE__).do_test(
+ unquote(create_fun),
+ "script.js",
+ "alert('miaow');",
+ "application/octet-stream"
+ )
+ end
+
+ test "if CSS type" do
+ unquote(__MODULE__).do_test(
+ unquote(create_fun),
+ "script.js",
+ ".StatusBody {display: none;}",
+ "application/octet-stream"
+ )
+ end
+ end
+ end
+
+ defmacro do_audio_test(create_fun, sanitise \\ false) do
+ quote do
+ expected_type = if unquote(sanitise), do: "application/octet-stream", else: "audio/mpeg"
+
+ unquote(__MODULE__).do_test(
+ unquote(create_fun),
+ "audio.mp3",
+ File.read!("test/fixtures/sound.mp3"),
+ expected_type
+ )
+ end
+ end
+end
From 74182abb5b4b1186e4c68c426ad6ee680ebc804d Mon Sep 17 00:00:00 2001
From: Floatingghost
Date: Tue, 11 Mar 2025 20:48:27 +0000
Subject: [PATCH 89/95] bump version
---
mix.exs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/mix.exs b/mix.exs
index ba89b11f4..d45b802fa 100644
--- a/mix.exs
+++ b/mix.exs
@@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do
def project do
[
app: :pleroma,
- version: version("3.15.1"),
+ version: version("3.15.2"),
elixir: "~> 1.14.1 or ~> 1.15",
elixirc_paths: elixirc_paths(Mix.env()),
compilers: Mix.compilers(),
From efb901bdb5c32dde665649e4747feab873bd4935 Mon Sep 17 00:00:00 2001
From: a
Date: Wed, 12 Mar 2025 20:15:34 +0000
Subject: [PATCH 90/95] fix: docs: arch linux split erlang package
---
docs/docs/installation/arch_linux_en.md | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/docs/docs/installation/arch_linux_en.md b/docs/docs/installation/arch_linux_en.md
index b4b048b86..662466343 100644
--- a/docs/docs/installation/arch_linux_en.md
+++ b/docs/docs/installation/arch_linux_en.md
@@ -10,6 +10,11 @@ This guide will assume that you have administrative rights, either as root or a
* `postgresql`
* `elixir`
+ * `erlang-core`
+ * `erlang-ssl`
+ * `erlang-parsetools`
+ * `erlang-syntax_tools`
+ * `erlang-xmerl`
* `git`
* `base-devel`
* `cmake`
@@ -35,6 +40,7 @@ sudo pacman -Syu
```shell
sudo pacman -S git base-devel elixir cmake file
+sudo pacman -S --asdeps erlang-core erlang-ssl erlang-parsetools erlang-syntax_tools erlang-xmerl
```
### Install PostgreSQL
From dcfae9bfbfb8c3176be5fa4ac6d763d47703f687 Mon Sep 17 00:00:00 2001
From: a
Date: Wed, 12 Mar 2025 20:27:27 +0000
Subject: [PATCH 91/95] erlang-os_mon as well
---
docs/docs/installation/arch_linux_en.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/docs/docs/installation/arch_linux_en.md b/docs/docs/installation/arch_linux_en.md
index 662466343..b01a74903 100644
--- a/docs/docs/installation/arch_linux_en.md
+++ b/docs/docs/installation/arch_linux_en.md
@@ -15,6 +15,7 @@ This guide will assume that you have administrative rights, either as root or a
* `erlang-parsetools`
* `erlang-syntax_tools`
* `erlang-xmerl`
+ * `erlang-os_mon`
* `git`
* `base-devel`
* `cmake`
@@ -40,7 +41,7 @@ sudo pacman -Syu
```shell
sudo pacman -S git base-devel elixir cmake file
-sudo pacman -S --asdeps erlang-core erlang-ssl erlang-parsetools erlang-syntax_tools erlang-xmerl
+sudo pacman -S --asdeps erlang-core erlang-ssl erlang-parsetools erlang-syntax_tools erlang-xmerl erlang-os_mon
```
### Install PostgreSQL
From 7ffbe2ad2618ee002a18c26baf4602af64def52a Mon Sep 17 00:00:00 2001
From: Oneric
Date: Tue, 18 Mar 2025 01:01:47 +0100
Subject: [PATCH 92/95] upload/filter/exiftool/strip: hide warnings from log
---
lib/pleroma/upload/filter/exiftool/strip_metadata.ex | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/pleroma/upload/filter/exiftool/strip_metadata.ex b/lib/pleroma/upload/filter/exiftool/strip_metadata.ex
index a2604a682..7529615d3 100644
--- a/lib/pleroma/upload/filter/exiftool/strip_metadata.ex
+++ b/lib/pleroma/upload/filter/exiftool/strip_metadata.ex
@@ -38,7 +38,7 @@ def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do
args = ["-ignoreMinorErrors", "-overwrite_original" | purge_args] ++ preserve_args ++ [file]
try do
- case System.cmd("exiftool", args, parallelism: true) do
+ case System.cmd("exiftool", args, parallelism: true, stderr_to_stdout: true) do
{_response, 0} -> {:ok, :filtered}
{error, 1} -> {:error, error}
end
From 699c05110174988773d08035eb267cc13477c257 Mon Sep 17 00:00:00 2001
From: a
Date: Wed, 19 Mar 2025 03:15:07 +0000
Subject: [PATCH 93/95] erlang-headless now exists
---
docs/docs/installation/arch_linux_en.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/docs/docs/installation/arch_linux_en.md b/docs/docs/installation/arch_linux_en.md
index b01a74903..a5864105f 100644
--- a/docs/docs/installation/arch_linux_en.md
+++ b/docs/docs/installation/arch_linux_en.md
@@ -41,7 +41,7 @@ sudo pacman -Syu
```shell
sudo pacman -S git base-devel elixir cmake file
-sudo pacman -S --asdeps erlang-core erlang-ssl erlang-parsetools erlang-syntax_tools erlang-xmerl erlang-os_mon
+sudo pacman -S --asdeps erlang-headless
```
### Install PostgreSQL
From ab9a4ce0d54f4c5e318bb56ee0d20b6671cb1630 Mon Sep 17 00:00:00 2001
From: a
Date: Wed, 19 Mar 2025 20:15:57 +0000
Subject: [PATCH 94/95] remove specific split packages, refer only to
erlang-headless or erlang
---
docs/docs/installation/arch_linux_en.md | 7 +------
1 file changed, 1 insertion(+), 6 deletions(-)
diff --git a/docs/docs/installation/arch_linux_en.md b/docs/docs/installation/arch_linux_en.md
index a5864105f..3e6476a94 100644
--- a/docs/docs/installation/arch_linux_en.md
+++ b/docs/docs/installation/arch_linux_en.md
@@ -10,12 +10,7 @@ This guide will assume that you have administrative rights, either as root or a
* `postgresql`
* `elixir`
- * `erlang-core`
- * `erlang-ssl`
- * `erlang-parsetools`
- * `erlang-syntax_tools`
- * `erlang-xmerl`
- * `erlang-os_mon`
+* `erlang-headless` or `erlang`
* `git`
* `base-devel`
* `cmake`
From 984e5a121aaccff6770399db193b123fff49adee Mon Sep 17 00:00:00 2001
From: Oneric
Date: Tue, 8 Apr 2025 23:43:59 +0200
Subject: [PATCH 95/95] api/statuses: allow expires_in to override user-level
status_ttl_default
Fixes: https://akkoma.dev/AkkomaGang/akkoma/issues/898
---
.../controllers/status_controller.ex | 13 ++---
.../controllers/status_controller_test.exs | 51 +++++++++++++++++++
2 files changed, 58 insertions(+), 6 deletions(-)
diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
index acb5f15a0..79d590a7b 100644
--- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
@@ -172,14 +172,15 @@ def create(%{assigns: %{user: user}, body_params: %{status: _} = params} = conn,
|> put_application(conn)
expires_in_seconds =
- if is_nil(user.status_ttl_days),
- do: nil,
- else: 60 * 60 * 24 * user.status_ttl_days
+ Map.get(params, :expires_in) ||
+ (user.status_ttl_days && 60 * 60 * 24 * user.status_ttl_days)
params =
- if is_nil(expires_in_seconds),
- do: params,
- else: Map.put(params, :expires_in, expires_in_seconds)
+ case expires_in_seconds do
+ nil -> params
+ 0 -> Map.delete(params, :expires_in)
+ _ -> Map.put(params, :expires_in, expires_in_seconds)
+ end
with {:ok, activity} <- CommonAPI.post(user, params) do
try_render(conn, "show.json",
diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
index 499596a53..1022aa5c6 100644
--- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
@@ -162,6 +162,57 @@ test "automatically setting a post expiry if status_ttl_days is set" do
)
end
+ test "API paramater overrides user status_ttl_days default" do
+ user = insert(:user, status_ttl_days: 1)
+ %{user: _user, token: _token, conn: conn} = oauth_access(["write:statuses"], user: user)
+
+ conn =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/statuses", %{
+ "status" => "aa chikichiki banban",
+ "expires_in" => 2 * 60 * 60
+ })
+
+ assert %{"id" => id} = json_response_and_validate_schema(conn, 200)
+
+ activity = Activity.get_by_id_with_object(id)
+ {:ok, expires_at, _} = DateTime.from_iso8601(activity.data["expires_at"])
+
+ expiry_delay = Timex.diff(expires_at, DateTime.utc_now(), :minutes)
+ assert(expiry_delay in [120, 119])
+
+ assert_enqueued(
+ worker: Pleroma.Workers.PurgeExpiredActivity,
+ args: %{activity_id: id},
+ scheduled_at: expires_at
+ )
+ end
+
+ test "API paramater can disable expiry from user-level status_ttl_default" do
+ user = insert(:user, status_ttl_days: 1)
+ %{user: _user, token: _token, conn: conn} = oauth_access(["write:statuses"], user: user)
+
+ conn =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/statuses", %{
+ "status" => "aa chikichiki banban",
+ "expires_in" => 0
+ })
+
+ assert %{"id" => id} = json_response_and_validate_schema(conn, 200)
+
+ activity = Activity.get_by_id_with_object(id)
+
+ refute activity.data["expires_at"]
+
+ refute_enqueued(
+ worker: Pleroma.Workers.PurgeExpiredActivity,
+ args: %{activity_id: id}
+ )
+ end
+
test "it fails to create a status if `expires_in` is less or equal than an hour", %{
conn: conn
} do