forked from AkkomaGang/akkoma
Compare commits
57 commits
5e1a47ce70
...
b31388b5df
Author | SHA1 | Date | |
---|---|---|---|
b31388b5df | |||
615ac14d6d | |||
c0a99df06a | |||
0cb4c35ee4 | |||
c9b3fcc1d3 | |||
11c5838947 | |||
180dc8b472 | |||
d330c57cda | |||
bd64d07082 | |||
9d2c558f64 | |||
ac25b051ae | |||
c5a44a59db | |||
58d5d9d7bf | |||
b6e8fde4dd | |||
bee10eab5e | |||
13215f5f06 | |||
430b376ded | |||
ccf1007883 | |||
6da783b84d | |||
8f322456a0 | |||
9c876cea21 | |||
9728e2f8f7 | |||
b0f7da9ce0 | |||
fc99c694e6 | |||
fbb13fde76 | |||
cbd236aeb5 | |||
71d3bbd7be | |||
98a3dab10a | |||
|
661b7fedb6 | ||
|
8b5aca9619 | ||
f101886709 | |||
09fa7227f6 | |||
d5b0720596 | |||
940792f8ba | |||
3c72b48a05 | |||
6475cf127e | |||
a8a231c5b2 | |||
2901fda29c | |||
bd14440386 | |||
|
b312edac4c | ||
3bb31117e6 | |||
2c5c531c35 | |||
a3101a435b | |||
d488cf476e | |||
ca182a0ae7 | |||
495a1a71e8 | |||
cf19d4901f | |||
07539f7825 | |||
0ab2f2ab45 | |||
95ed4931f8 | |||
7cd3954152 | |||
80a4e30be7 | |||
4ff5293093 | |||
3b197503d2 | |||
c0b2bba55e | |||
4b765b1886 | |||
cba2c5725f |
71 changed files with 1236 additions and 532 deletions
|
@ -87,5 +87,5 @@ steps:
|
||||||
- mix ecto.create
|
- mix ecto.create
|
||||||
- mix ecto.migrate
|
- mix ecto.migrate
|
||||||
- mkdir -p test/tmp
|
- mkdir -p test/tmp
|
||||||
- mix test --preload-modules --exclude erratic --exclude federated --exclude mocked
|
- mix test --preload-modules --exclude erratic --exclude federated --exclude mocked || mix test --failed
|
||||||
- mix test --preload-modules --only mocked
|
- mix test --preload-modules --only mocked || mix test --failed
|
||||||
|
|
|
@ -8,15 +8,23 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
## BREAKING
|
## BREAKING
|
||||||
- Minimum PostgreSQL version is raised to 12
|
- Minimum PostgreSQL version is raised to 12
|
||||||
|
- Swagger UI moved from `/akkoma/swaggerui/` to `/pleroma/swaggerui/`
|
||||||
|
|
||||||
## Added
|
## Added
|
||||||
- Implement [FEP-67ff](https://codeberg.org/fediverse/fep/src/branch/main/fep/67ff/fep-67ff.md) (federation documentation)
|
- Implement [FEP-67ff](https://codeberg.org/fediverse/fep/src/branch/main/fep/67ff/fep-67ff.md) (federation documentation)
|
||||||
- Meilisearch: it is now possible to use separate keys for search and admin actions
|
- Meilisearch: it is now possible to use separate keys for search and admin actions
|
||||||
- New standalone `prune_orphaned_activities` mix task with configurable batch limit
|
- New standalone `prune_orphaned_activities` mix task with configurable batch limit
|
||||||
- The `prune_objects` mix task now accepts a `--limit` parameter for initial object pruning
|
- The `prune_objects` mix task now accepts a `--limit` parameter for initial object pruning
|
||||||
|
- New config option `:instance, :cleanup_attachments_delay`
|
||||||
|
|
||||||
## Fixed
|
## Fixed
|
||||||
- Meilisearch: order of results returned from our REST API now actually matches how Meilisearch ranks results
|
- Meilisearch: order of results returned from our REST API now actually matches how Meilisearch ranks results
|
||||||
|
- Emoji are now federated as anonymous objects, fixing issues with
|
||||||
|
some strict servers e.g. rejecting e.g. remote emoji reactions
|
||||||
|
- AP objects with additional JSON-LD profiles beyond ActivityStreams can now be fetched
|
||||||
|
- Single-selection polls no longer expose the voter_count; MastoAPI demands it be null
|
||||||
|
and this confused some clients leading to vote distributions >100%
|
||||||
|
- Fix “Delete & Redraft” often losing attachments if attachment cleanup was enabled
|
||||||
|
|
||||||
## Changed
|
## Changed
|
||||||
- Refactored Rich Media to cache the content in the database. Fetching operations that could block status rendering have been eliminated.
|
- Refactored Rich Media to cache the content in the database. Fetching operations that could block status rendering have been eliminated.
|
||||||
|
|
|
@ -255,6 +255,7 @@
|
||||||
external_user_synchronization: true,
|
external_user_synchronization: true,
|
||||||
extended_nickname_format: true,
|
extended_nickname_format: true,
|
||||||
cleanup_attachments: false,
|
cleanup_attachments: false,
|
||||||
|
cleanup_attachments_delay: 1800,
|
||||||
multi_factor_authentication: [
|
multi_factor_authentication: [
|
||||||
totp: [
|
totp: [
|
||||||
# digits 6 or 8
|
# digits 6 or 8
|
||||||
|
|
|
@ -51,7 +51,8 @@
|
||||||
hostname: System.get_env("DB_HOST") || "localhost",
|
hostname: System.get_env("DB_HOST") || "localhost",
|
||||||
pool: Ecto.Adapters.SQL.Sandbox,
|
pool: Ecto.Adapters.SQL.Sandbox,
|
||||||
pool_size: 50,
|
pool_size: 50,
|
||||||
queue_target: 5000
|
queue_target: 5000,
|
||||||
|
log: false
|
||||||
|
|
||||||
config :pleroma, :dangerzone, override_repo_pool_size: true
|
config :pleroma, :dangerzone, override_repo_pool_size: true
|
||||||
|
|
||||||
|
|
|
@ -11,4 +11,4 @@ echo "-- Running migrations..."
|
||||||
mix ecto.migrate
|
mix ecto.migrate
|
||||||
|
|
||||||
echo "-- Starting!"
|
echo "-- Starting!"
|
||||||
mix phx.server
|
elixir --erl "+sbwt none +sbwtdcpu none +sbwtdio none" -S mix phx.server
|
||||||
|
|
|
@ -58,6 +58,7 @@ To add configuration to your config file, you can copy it from the base config.
|
||||||
* `registration_reason_length`: Maximum registration reason length (default: `500`).
|
* `registration_reason_length`: Maximum registration reason length (default: `500`).
|
||||||
* `external_user_synchronization`: Enabling following/followers counters synchronization for external users.
|
* `external_user_synchronization`: Enabling following/followers counters synchronization for external users.
|
||||||
* `cleanup_attachments`: Remove attachments along with statuses. Does not affect duplicate files and attachments without status. Enabling this will increase load to database when deleting statuses on larger instances.
|
* `cleanup_attachments`: Remove attachments along with statuses. Does not affect duplicate files and attachments without status. Enabling this will increase load to database when deleting statuses on larger instances.
|
||||||
|
* `cleanup_attachments_delay`: How many seconds to wait after post deletion before attempting to deletion; useful for “delete & redraft” functionality (default: `1800`)
|
||||||
* `show_reactions`: Let favourites and emoji reactions be viewed through the API (default: `true`).
|
* `show_reactions`: Let favourites and emoji reactions be viewed through the API (default: `true`).
|
||||||
* `password_reset_token_validity`: The time after which reset tokens aren't accepted anymore, in seconds (default: one day).
|
* `password_reset_token_validity`: The time after which reset tokens aren't accepted anymore, in seconds (default: one day).
|
||||||
* `local_bubble`: Array of domains representing instances closely related to yours. Used to populate the `bubble` timeline. e.g `["example.com"]`, (default: `[]`)
|
* `local_bubble`: Array of domains representing instances closely related to yours. Used to populate the `bubble` timeline. e.g `["example.com"]`, (default: `[]`)
|
||||||
|
@ -338,7 +339,7 @@ config :pleroma, :frontends,
|
||||||
|
|
||||||
* `:primary` - The frontend that will be served at `/`
|
* `:primary` - The frontend that will be served at `/`
|
||||||
* `:admin` - The frontend that will be served at `/pleroma/admin`
|
* `:admin` - The frontend that will be served at `/pleroma/admin`
|
||||||
* `:swagger` - Config for developers to act as an API reference to be served at `/akkoma/swaggerui/` (trailing slash _needed_). Disabled by default.
|
* `:swagger` - Config for developers to act as an API reference to be served at `/pleroma/swaggerui/` (trailing slash _needed_). Disabled by default.
|
||||||
* `:mastodon` - The mastodon-fe configuration. This shouldn't need to be changed. This is served at `/web` when installed.
|
* `:mastodon` - The mastodon-fe configuration. This shouldn't need to be changed. This is served at `/web` when installed.
|
||||||
|
|
||||||
### :static\_fe
|
### :static\_fe
|
||||||
|
|
|
@ -60,4 +60,4 @@ config :pleroma, :frontends,
|
||||||
|
|
||||||
Then run the [pleroma.frontend cli task](../../administration/CLI_tasks/frontend) with the name of `swagger-ui` to install the distribution files.
|
Then run the [pleroma.frontend cli task](../../administration/CLI_tasks/frontend) with the name of `swagger-ui` to install the distribution files.
|
||||||
|
|
||||||
You will now be able to view documentation at `/akkoma/swaggerui`
|
You will now be able to view documentation at `/pleroma/swaggerui`
|
||||||
|
|
|
@ -6,7 +6,9 @@ probably install frontends.
|
||||||
These are no longer bundled with the distribution and need an extra
|
These are no longer bundled with the distribution and need an extra
|
||||||
command to install.
|
command to install.
|
||||||
|
|
||||||
For most installations, the following will suffice:
|
You **must** run frontend management tasks as the akkoma user,
|
||||||
|
the same way you downloaded the build or cloned the git repo before.
|
||||||
|
But otherwise, for most installations, the following will suffice:
|
||||||
|
|
||||||
=== "OTP"
|
=== "OTP"
|
||||||
```sh
|
```sh
|
||||||
|
@ -28,4 +30,3 @@ For most installations, the following will suffice:
|
||||||
```
|
```
|
||||||
|
|
||||||
For more customised installations, refer to [Frontend Management](../../configuration/frontend_management)
|
For more customised installations, refer to [Frontend Management](../../configuration/frontend_management)
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,9 @@ Environment="MIX_ENV=prod"
|
||||||
; Don't listen epmd on 0.0.0.0
|
; Don't listen epmd on 0.0.0.0
|
||||||
Environment="ERL_EPMD_ADDRESS=127.0.0.1"
|
Environment="ERL_EPMD_ADDRESS=127.0.0.1"
|
||||||
|
|
||||||
|
; Don't busy wait
|
||||||
|
Environment="ERL_AFLAGS=+sbwt none +sbwtdcpu none +sbwtdio none"
|
||||||
|
|
||||||
; Make sure that all paths fit your installation.
|
; Make sure that all paths fit your installation.
|
||||||
; Path to the home directory of the user running the Akkoma service.
|
; Path to the home directory of the user running the Akkoma service.
|
||||||
Environment="HOME=/var/lib/akkoma"
|
Environment="HOME=/var/lib/akkoma"
|
||||||
|
|
|
@ -1,23 +1,43 @@
|
||||||
#!/sbin/openrc-run
|
#!/sbin/openrc-run
|
||||||
supervisor=supervise-daemon
|
supervisor=supervise-daemon
|
||||||
command_user=akkoma:akkoma
|
|
||||||
command_background=1
|
|
||||||
# Ask process to terminate within 30 seconds, otherwise kill it
|
|
||||||
retry="SIGTERM/30/SIGKILL/5"
|
|
||||||
pidfile="/var/run/akkoma.pid"
|
|
||||||
directory=/opt/akkoma
|
|
||||||
healthcheck_delay=60
|
|
||||||
healthcheck_timer=30
|
|
||||||
no_new_privs="yes"
|
no_new_privs="yes"
|
||||||
|
pidfile="/var/run/akkoma.pid"
|
||||||
|
|
||||||
: ${akkoma_port:-4000}
|
# Ask process first to terminate itself within 60s, otherwise kill it
|
||||||
|
retry="SIGTERM/60/SIGKILL/5"
|
||||||
|
|
||||||
# Needs OpenRC >= 0.42
|
# if you really want to use start-stop-daemon instead,
|
||||||
#respawn_max=0
|
# also put the following in the config:
|
||||||
#respawn_delay=5
|
# command_background=1
|
||||||
|
|
||||||
|
# Adjust defaults as needed in /etc/conf.d/akkoma;
|
||||||
|
# no need to directly edit the service file
|
||||||
|
command_user="${command_user:-akkoma:akkoma}"
|
||||||
|
directory="${directory:-/var/lib/akkoma/akkoma}"
|
||||||
|
akkoma_port="${akkoma_port:-4000}"
|
||||||
|
# whether to allow connecting a remote exlixir shell to the running Akkoma instance
|
||||||
|
akkoma_console=${akkoma_console:-NO}
|
||||||
|
|
||||||
|
output_log="${output_log:-/var/log/akkoma}"
|
||||||
|
error_log="${error_log:-/var/log/akkoma}"
|
||||||
|
|
||||||
|
# 0 means unlimited restarts
|
||||||
|
respawn_max="${respawn_max:-0}"
|
||||||
|
respawn_delay="${respawn_delay:-5}"
|
||||||
|
# define respawn period to only count crashes within a
|
||||||
|
# sliding time window towards respawn_max, e.g.:
|
||||||
|
# respawn_period=2850
|
||||||
|
|
||||||
|
healthcheck_delay="${healthcheck_delay:-60}"
|
||||||
|
healthcheck_timer="${healthcheck_timer:-30}"
|
||||||
|
|
||||||
|
MIX_ENV=prod
|
||||||
|
ERL_EPMD_ADDRESS="${ERL_EPMD_ADDRESS:-127.0.0.1}"
|
||||||
|
ERL_AFLAGS="${ERL_AFLAGS:-+sbwt none +sbwtdcpu none +sbwtdio none}"
|
||||||
|
supervise_daemon_args="${supervise_daemon_args} --env MIX_ENV=${MIX_ENV}"
|
||||||
|
supervise_daemon_args="${supervise_daemon_args} --env ERL_EPMD_ADDRESS=${ERL_EPMD_ADDRESS}"
|
||||||
|
supervise_daemon_args="${supervise_daemon_args} --env ERL_AFLAGS='${ERL_AFLAGS}'"
|
||||||
|
|
||||||
# put akkoma_console=YES in /etc/conf.d/akkoma if you want to be able to
|
|
||||||
# connect to akkoma via an elixir console
|
|
||||||
if yesno "${akkoma_console}"; then
|
if yesno "${akkoma_console}"; then
|
||||||
command=elixir
|
command=elixir
|
||||||
command_args="--name akkoma@127.0.0.1 --erl '-kernel inet_dist_listen_min 9001 inet_dist_listen_max 9001 inet_dist_use_interface {127,0,0,1}' -S mix phx.server"
|
command_args="--name akkoma@127.0.0.1 --erl '-kernel inet_dist_listen_min 9001 inet_dist_listen_max 9001 inet_dist_use_interface {127,0,0,1}' -S mix phx.server"
|
||||||
|
@ -31,13 +51,24 @@ else
|
||||||
command_args="phx.server"
|
command_args="phx.server"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export MIX_ENV=prod
|
|
||||||
export ERL_EPMD_ADDRESS=127.0.0.1
|
|
||||||
|
|
||||||
depend() {
|
depend() {
|
||||||
need nginx postgresql
|
need nginx postgresql
|
||||||
}
|
}
|
||||||
|
|
||||||
|
start_pre() {
|
||||||
|
# Ensure logfile ownership and perms are alright
|
||||||
|
checkpath --file --owner "$command_user" "$output_log" "$error_log" \
|
||||||
|
|| eerror "Logfile(s) not owned by $command_user, or not a file!"
|
||||||
|
checkpath --writable "$output_log" "$error_log" \
|
||||||
|
|| eerror "Logfile(s) not writable!"
|
||||||
|
|
||||||
|
# If a recompile is needed perform it with lowest prio
|
||||||
|
# (delaying the actual start) to avoid hogging too much
|
||||||
|
# CPU from other services
|
||||||
|
cd "$directory"
|
||||||
|
doas -u "${command_user%%:*}" env MIX_ENV="$MIX_ENV" nice -n 19 "$command" compile
|
||||||
|
}
|
||||||
|
|
||||||
healthcheck() {
|
healthcheck() {
|
||||||
# put akkoma_health=YES in /etc/conf.d/akkoma if you want healthchecking
|
# put akkoma_health=YES in /etc/conf.d/akkoma if you want healthchecking
|
||||||
# and make sure you have curl installed
|
# and make sure you have curl installed
|
||||||
|
|
|
@ -111,7 +111,7 @@ def run(["get-packs" | args]) do
|
||||||
|
|
||||||
{:ok, _} =
|
{:ok, _} =
|
||||||
:zip.unzip(binary_archive,
|
:zip.unzip(binary_archive,
|
||||||
cwd: pack_path,
|
cwd: to_charlist(pack_path),
|
||||||
file_list: files_to_unzip
|
file_list: files_to_unzip
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -125,7 +125,7 @@ def add_file(%Pack{} = pack, _, _, %Plug.Upload{content_type: "application/zip"}
|
||||||
{:ok, _emoji_files} =
|
{:ok, _emoji_files} =
|
||||||
:zip.unzip(
|
:zip.unzip(
|
||||||
to_charlist(file.path),
|
to_charlist(file.path),
|
||||||
[{:file_list, Enum.map(emojies, & &1[:path])}, {:cwd, tmp_dir}]
|
[{:file_list, Enum.map(emojies, & &1[:path])}, {:cwd, to_charlist(tmp_dir)}]
|
||||||
)
|
)
|
||||||
|
|
||||||
{_, updated_pack} =
|
{_, updated_pack} =
|
||||||
|
|
|
@ -79,6 +79,10 @@ def unzip(zip, dest) do
|
||||||
|
|
||||||
new_file_path = Path.join(dest, path)
|
new_file_path = Path.join(dest, path)
|
||||||
|
|
||||||
|
new_file_path
|
||||||
|
|> Path.dirname()
|
||||||
|
|> File.rm()
|
||||||
|
|
||||||
new_file_path
|
new_file_path
|
||||||
|> Path.dirname()
|
|> Path.dirname()
|
||||||
|> File.mkdir_p!()
|
|> File.mkdir_p!()
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.Keys do
|
|
||||||
# Native generation of RSA keys is only available since OTP 20+ and in default build conditions
|
|
||||||
# We try at compile time to generate natively an RSA key otherwise we fallback on the old way.
|
|
||||||
try do
|
|
||||||
_ = :public_key.generate_key({:rsa, 2048, 65_537})
|
|
||||||
|
|
||||||
def generate_rsa_pem do
|
|
||||||
key = :public_key.generate_key({:rsa, 2048, 65_537})
|
|
||||||
entry = :public_key.pem_entry_encode(:RSAPrivateKey, key)
|
|
||||||
pem = :public_key.pem_encode([entry]) |> String.trim_trailing()
|
|
||||||
{:ok, pem}
|
|
||||||
end
|
|
||||||
rescue
|
|
||||||
_ ->
|
|
||||||
def generate_rsa_pem do
|
|
||||||
port = Port.open({:spawn, "openssl genrsa"}, [:binary])
|
|
||||||
|
|
||||||
{:ok, pem} =
|
|
||||||
receive do
|
|
||||||
{^port, {:data, pem}} -> {:ok, pem}
|
|
||||||
end
|
|
||||||
|
|
||||||
Port.close(port)
|
|
||||||
|
|
||||||
if Regex.match?(~r/RSA PRIVATE KEY/, pem) do
|
|
||||||
{:ok, pem}
|
|
||||||
else
|
|
||||||
:error
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def keys_from_pem(pem) do
|
|
||||||
with [private_key_code] <- :public_key.pem_decode(pem),
|
|
||||||
private_key <- :public_key.pem_entry_decode(private_key_code),
|
|
||||||
{:RSAPrivateKey, _, modulus, exponent, _, _, _, _, _, _, _} <- private_key do
|
|
||||||
{:ok, private_key, {:RSAPublicKey, modulus, exponent}}
|
|
||||||
else
|
|
||||||
error -> {:error, error}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -9,7 +9,6 @@ defmodule Pleroma.Object do
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Config
|
|
||||||
alias Pleroma.Hashtag
|
alias Pleroma.Hashtag
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Object.Fetcher
|
alias Pleroma.Object.Fetcher
|
||||||
|
@ -241,23 +240,11 @@ def delete(%Object{data: %{"id" => id}} = object) do
|
||||||
with {:ok, _obj} = swap_object_with_tombstone(object),
|
with {:ok, _obj} = swap_object_with_tombstone(object),
|
||||||
deleted_activity = Activity.delete_all_by_object_ap_id(id),
|
deleted_activity = Activity.delete_all_by_object_ap_id(id),
|
||||||
{:ok, _} <- invalid_object_cache(object) do
|
{:ok, _} <- invalid_object_cache(object) do
|
||||||
cleanup_attachments(
|
AttachmentsCleanupWorker.enqueue_if_needed(object.data)
|
||||||
Config.get([:instance, :cleanup_attachments]),
|
|
||||||
%{object: object}
|
|
||||||
)
|
|
||||||
|
|
||||||
{:ok, object, deleted_activity}
|
{:ok, object, deleted_activity}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec cleanup_attachments(boolean(), %{required(:object) => map()}) ::
|
|
||||||
{:ok, Oban.Job.t() | nil}
|
|
||||||
def cleanup_attachments(true, %{object: _} = params) do
|
|
||||||
AttachmentsCleanupWorker.enqueue("cleanup_attachments", params)
|
|
||||||
end
|
|
||||||
|
|
||||||
def cleanup_attachments(_, _), do: {:ok, nil}
|
|
||||||
|
|
||||||
def prune(%Object{data: %{"id" => _id}} = object) do
|
def prune(%Object{data: %{"id" => _id}} = object) do
|
||||||
with {:ok, object} <- Repo.delete(object),
|
with {:ok, object} <- Repo.delete(object),
|
||||||
{:ok, _} <- invalid_object_cache(object) do
|
{:ok, _} <- invalid_object_cache(object) do
|
||||||
|
|
|
@ -12,8 +12,6 @@ defmodule Pleroma.Object.Containment do
|
||||||
spoofing, therefore removal of object containment functions is NOT recommended.
|
spoofing, therefore removal of object containment functions is NOT recommended.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
|
||||||
|
|
||||||
def get_actor(%{"actor" => actor}) when is_binary(actor) do
|
def get_actor(%{"actor" => actor}) when is_binary(actor) do
|
||||||
actor
|
actor
|
||||||
end
|
end
|
||||||
|
@ -50,16 +48,39 @@ def get_object(_) do
|
||||||
defp compare_uris(%URI{host: host} = _id_uri, %URI{host: host} = _other_uri), do: :ok
|
defp compare_uris(%URI{host: host} = _id_uri, %URI{host: host} = _other_uri), do: :ok
|
||||||
defp compare_uris(_id_uri, _other_uri), do: :error
|
defp compare_uris(_id_uri, _other_uri), do: :error
|
||||||
|
|
||||||
defp compare_uris_exact(uri, uri), do: :ok
|
defp uri_strip_slash(%URI{path: path} = uri) when is_binary(path),
|
||||||
|
do: %{uri | path: String.replace_suffix(path, "/", "")}
|
||||||
|
|
||||||
defp compare_uris_exact(%URI{} = id, %URI{} = other),
|
defp uri_strip_slash(uri), do: uri
|
||||||
do: compare_uris_exact(URI.to_string(id), URI.to_string(other))
|
|
||||||
|
|
||||||
defp compare_uris_exact(id_uri, other_uri)
|
# domain names are case-insensitive per spec (other parts of URIs aren’t necessarily)
|
||||||
when is_binary(id_uri) and is_binary(other_uri) do
|
defp uri_normalise_host(%URI{host: host} = uri) when is_binary(host),
|
||||||
norm_id = String.replace_suffix(id_uri, "/", "")
|
do: %{uri | host: String.downcase(host, :ascii)}
|
||||||
norm_other = String.replace_suffix(other_uri, "/", "")
|
|
||||||
if norm_id == norm_other, do: :ok, else: :error
|
defp uri_normalise_host(uri), do: uri
|
||||||
|
|
||||||
|
defp compare_uri_identities(uri, uri), do: :ok
|
||||||
|
|
||||||
|
defp compare_uri_identities(id_uri, other_uri) when is_binary(id_uri) and is_binary(other_uri),
|
||||||
|
do: compare_uri_identities(URI.parse(id_uri), URI.parse(other_uri))
|
||||||
|
|
||||||
|
defp compare_uri_identities(%URI{} = id, %URI{} = other) do
|
||||||
|
normid =
|
||||||
|
%{id | fragment: nil}
|
||||||
|
|> uri_strip_slash()
|
||||||
|
|> uri_normalise_host()
|
||||||
|
|
||||||
|
normother =
|
||||||
|
%{other | fragment: nil}
|
||||||
|
|> uri_strip_slash()
|
||||||
|
|> uri_normalise_host()
|
||||||
|
|
||||||
|
# Conversion back to binary avoids issues from non-normalised deprecated authority field
|
||||||
|
if URI.to_string(normid) == URI.to_string(normother) do
|
||||||
|
:ok
|
||||||
|
else
|
||||||
|
:error
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
@ -93,21 +114,13 @@ def contain_origin(id, %{"attributedTo" => actor} = params),
|
||||||
def contain_origin(_id, _data), do: :ok
|
def contain_origin(_id, _data), do: :ok
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Check whether the fetch URL (after redirects) exactly (sans tralining slash) matches either
|
Check whether the fetch URL (after redirects) is the
|
||||||
the canonical ActivityPub id or the objects url field (for display URLs from *key and Mastodon)
|
same location the canonical ActivityPub id points to.
|
||||||
|
|
||||||
Since this is meant to be used for fetches, anonymous or transient objects are not accepted here.
|
Since this is meant to be used for fetches, anonymous or transient objects are not accepted here.
|
||||||
"""
|
"""
|
||||||
def contain_id_to_fetch(url, %{"id" => id} = data) when is_binary(id) do
|
def contain_id_to_fetch(url, %{"id" => id}) when is_binary(id) do
|
||||||
with {:id, :error} <- {:id, compare_uris_exact(id, url)},
|
compare_uri_identities(url, id)
|
||||||
# "url" can be a "Link" object and this is checked before full normalisation
|
|
||||||
display_url <- Transmogrifier.fix_url(data)["url"],
|
|
||||||
true <- display_url != nil do
|
|
||||||
compare_uris_exact(display_url, url)
|
|
||||||
else
|
|
||||||
{:id, :ok} -> :ok
|
|
||||||
_ -> :error
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def contain_id_to_fetch(_url, _data), do: :error
|
def contain_id_to_fetch(_url, _data), do: :error
|
||||||
|
|
|
@ -116,7 +116,7 @@ defp reinject_object(%Object{} = object, new_data) do
|
||||||
@doc "Assumes object already is in our database and refetches from remote to update (e.g. for polls)"
|
@doc "Assumes object already is in our database and refetches from remote to update (e.g. for polls)"
|
||||||
def refetch_object(%Object{data: %{"id" => id}} = object) do
|
def refetch_object(%Object{data: %{"id" => id}} = object) do
|
||||||
with {:local, false} <- {:local, Object.local?(object)},
|
with {:local, false} <- {:local, Object.local?(object)},
|
||||||
{:ok, new_data} <- fetch_and_contain_remote_object_from_id(id),
|
{:ok, new_data} <- fetch_and_contain_remote_object_from_id(id, true),
|
||||||
{:id, true} <- {:id, new_data["id"] == id},
|
{:id, true} <- {:id, new_data["id"] == id},
|
||||||
{:ok, object} <- reinject_object(object, new_data) do
|
{:ok, object} <- reinject_object(object, new_data) do
|
||||||
{:ok, object}
|
{:ok, object}
|
||||||
|
@ -253,14 +253,17 @@ defp maybe_date_fetch(headers, date) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "Fetches arbitrary remote object and performs basic safety and authenticity checks"
|
@doc """
|
||||||
def fetch_and_contain_remote_object_from_id(id)
|
Fetches arbitrary remote object and performs basic safety and authenticity checks.
|
||||||
|
When the fetch URL is known to already be a canonical AP id, checks are stricter.
|
||||||
|
"""
|
||||||
|
def fetch_and_contain_remote_object_from_id(id, is_ap_id \\ false)
|
||||||
|
|
||||||
def fetch_and_contain_remote_object_from_id(%{"id" => id}),
|
def fetch_and_contain_remote_object_from_id(%{"id" => id}, is_ap_id),
|
||||||
do: fetch_and_contain_remote_object_from_id(id)
|
do: fetch_and_contain_remote_object_from_id(id, is_ap_id)
|
||||||
|
|
||||||
def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do
|
def fetch_and_contain_remote_object_from_id(id, is_ap_id) when is_binary(id) do
|
||||||
Logger.debug("Fetching object #{id} via AP")
|
Logger.debug("Fetching object #{id} via AP [ap_id=#{is_ap_id}]")
|
||||||
|
|
||||||
with {:valid_uri_scheme, true} <- {:valid_uri_scheme, String.starts_with?(id, "http")},
|
with {:valid_uri_scheme, true} <- {:valid_uri_scheme, String.starts_with?(id, "http")},
|
||||||
%URI{} = uri <- URI.parse(id),
|
%URI{} = uri <- URI.parse(id),
|
||||||
|
@ -270,18 +273,31 @@ def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do
|
||||||
{:mrf_accept_check, Pleroma.Web.ActivityPub.MRF.SimplePolicy.check_accept(uri)},
|
{:mrf_accept_check, Pleroma.Web.ActivityPub.MRF.SimplePolicy.check_accept(uri)},
|
||||||
{:local_fetch, :ok} <- {:local_fetch, Containment.contain_local_fetch(id)},
|
{:local_fetch, :ok} <- {:local_fetch, Containment.contain_local_fetch(id)},
|
||||||
{:ok, final_id, body} <- get_object(id),
|
{:ok, final_id, body} <- get_object(id),
|
||||||
|
# a canonical ID shouldn't be a redirect
|
||||||
|
true <- !is_ap_id || final_id == id,
|
||||||
{:ok, data} <- safe_json_decode(body),
|
{:ok, data} <- safe_json_decode(body),
|
||||||
{_, :ok} <- {:strict_id, Containment.contain_id_to_fetch(final_id, data)},
|
{_, :ok} <- {:containment, Containment.contain_origin(final_id, data)},
|
||||||
{_, :ok} <- {:containment, Containment.contain_origin(final_id, data)} do
|
{_, _, :ok} <- {:strict_id, data["id"], Containment.contain_id_to_fetch(final_id, data)} do
|
||||||
unless Instances.reachable?(final_id) do
|
unless Instances.reachable?(final_id) do
|
||||||
Instances.set_reachable(final_id)
|
Instances.set_reachable(final_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
{:ok, data}
|
{:ok, data}
|
||||||
else
|
else
|
||||||
{:strict_id, _} = e ->
|
# E.g. Mastodon and *key serve the AP object directly under their display URLs without
|
||||||
log_fetch_error(id, e)
|
# redirecting to their canonical location first, thus ids will expectedly differ.
|
||||||
{:error, :id_mismatch}
|
# 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}
|
||||||
|
end
|
||||||
|
|
||||||
{:mrf_reject_check, _} = e ->
|
{:mrf_reject_check, _} = e ->
|
||||||
log_fetch_error(id, e)
|
log_fetch_error(id, e)
|
||||||
|
@ -301,7 +317,7 @@ def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do
|
||||||
|
|
||||||
{:containment, reason} ->
|
{:containment, reason} ->
|
||||||
log_fetch_error(id, reason)
|
log_fetch_error(id, reason)
|
||||||
{:error, reason}
|
{:error, {:containment, reason}}
|
||||||
|
|
||||||
{:error, e} ->
|
{:error, e} ->
|
||||||
{:error, e}
|
{:error, e}
|
||||||
|
@ -311,25 +327,13 @@ def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_and_contain_remote_object_from_id(_id),
|
def fetch_and_contain_remote_object_from_id(_id, _is_ap_id),
|
||||||
do: {:error, :invalid_id}
|
do: {:error, :invalid_id}
|
||||||
|
|
||||||
defp check_crossdomain_redirect(final_host, original_url)
|
|
||||||
|
|
||||||
# HOPEFULLY TEMPORARY
|
# HOPEFULLY TEMPORARY
|
||||||
# Basically none of our Tesla mocks in tests set the (supposed to
|
# Basically none of our Tesla mocks in tests set the (supposed to
|
||||||
# exist for Tesla proper) url parameter for their responses
|
# exist for Tesla proper) url parameter for their responses
|
||||||
# causing almost every fetch in test to fail otherwise
|
# causing almost every fetch in test to fail otherwise
|
||||||
if @mix_env == :test do
|
|
||||||
defp check_crossdomain_redirect(nil, _) do
|
|
||||||
{:cross_domain_redirect, false}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp check_crossdomain_redirect(final_host, original_url) do
|
|
||||||
{:cross_domain_redirect, final_host != URI.parse(original_url).host}
|
|
||||||
end
|
|
||||||
|
|
||||||
if @mix_env == :test do
|
if @mix_env == :test do
|
||||||
defp get_final_id(nil, initial_url), do: initial_url
|
defp get_final_id(nil, initial_url), do: initial_url
|
||||||
defp get_final_id("", initial_url), do: initial_url
|
defp get_final_id("", initial_url), do: initial_url
|
||||||
|
@ -355,10 +359,6 @@ def get_object(id) do
|
||||||
with {:ok, %{body: body, status: code, headers: headers, url: final_url}}
|
with {:ok, %{body: body, status: code, headers: headers, url: final_url}}
|
||||||
when code in 200..299 <-
|
when code in 200..299 <-
|
||||||
HTTP.Backoff.get(id, headers),
|
HTTP.Backoff.get(id, headers),
|
||||||
remote_host <-
|
|
||||||
URI.parse(final_url).host,
|
|
||||||
{:cross_domain_redirect, false} <-
|
|
||||||
check_crossdomain_redirect(remote_host, id),
|
|
||||||
{:has_content_type, {_, content_type}} <-
|
{:has_content_type, {_, content_type}} <-
|
||||||
{:has_content_type, List.keyfind(headers, "content-type", 0)},
|
{:has_content_type, List.keyfind(headers, "content-type", 0)},
|
||||||
{:parse_content_type, {:ok, "application", subtype, type_params}} <-
|
{:parse_content_type, {:ok, "application", subtype, type_params}} <-
|
||||||
|
@ -369,8 +369,12 @@ def get_object(id) do
|
||||||
{"activity+json", _} ->
|
{"activity+json", _} ->
|
||||||
{:ok, final_id, body}
|
{:ok, final_id, body}
|
||||||
|
|
||||||
{"ld+json", %{"profile" => "https://www.w3.org/ns/activitystreams"}} ->
|
{"ld+json", %{"profile" => profiles}} ->
|
||||||
{:ok, final_id, body}
|
if "https://www.w3.org/ns/activitystreams" in String.split(profiles) do
|
||||||
|
{:ok, final_id, body}
|
||||||
|
else
|
||||||
|
{:error, {:content_type, content_type}}
|
||||||
|
end
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
{:error, {:content_type, content_type}}
|
{:error, {:content_type, content_type}}
|
||||||
|
|
|
@ -5,47 +5,25 @@
|
||||||
defmodule Pleroma.Signature do
|
defmodule Pleroma.Signature do
|
||||||
@behaviour HTTPSignatures.Adapter
|
@behaviour HTTPSignatures.Adapter
|
||||||
|
|
||||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
|
||||||
alias Pleroma.Keys
|
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.User.SigningKey
|
||||||
|
require Logger
|
||||||
@known_suffixes ["/publickey", "/main-key", "#key"]
|
|
||||||
|
|
||||||
def key_id_to_actor_id(key_id) do
|
def key_id_to_actor_id(key_id) do
|
||||||
uri =
|
case SigningKey.key_id_to_ap_id(key_id) do
|
||||||
key_id
|
nil ->
|
||||||
|> URI.parse()
|
# hm, we SHOULD have gotten this in the pipeline before we hit here!
|
||||||
|> Map.put(:fragment, nil)
|
Logger.error("Could not figure out who owns the key #{key_id}")
|
||||||
|> Map.put(:query, nil)
|
{:error, :key_owner_not_found}
|
||||||
|> remove_suffix(@known_suffixes)
|
|
||||||
|
|
||||||
maybe_ap_id = URI.to_string(uri)
|
key ->
|
||||||
|
{:ok, key}
|
||||||
case ObjectValidators.ObjectID.cast(maybe_ap_id) do
|
|
||||||
{:ok, ap_id} ->
|
|
||||||
{:ok, ap_id}
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
case Pleroma.Web.WebFinger.finger(maybe_ap_id) do
|
|
||||||
{:ok, %{"ap_id" => ap_id}} -> {:ok, ap_id}
|
|
||||||
_ -> {:error, maybe_ap_id}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp remove_suffix(uri, [test | rest]) do
|
|
||||||
if not is_nil(uri.path) and String.ends_with?(uri.path, test) do
|
|
||||||
Map.put(uri, :path, String.replace(uri.path, test, ""))
|
|
||||||
else
|
|
||||||
remove_suffix(uri, rest)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp remove_suffix(uri, []), do: uri
|
|
||||||
|
|
||||||
def fetch_public_key(conn) do
|
def fetch_public_key(conn) do
|
||||||
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
|
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, actor_id} <- key_id_to_actor_id(kid),
|
||||||
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
|
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
|
||||||
{:ok, public_key}
|
{:ok, public_key}
|
||||||
|
@ -57,8 +35,8 @@ def fetch_public_key(conn) do
|
||||||
|
|
||||||
def refetch_public_key(conn) do
|
def refetch_public_key(conn) do
|
||||||
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
|
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, actor_id} <- key_id_to_actor_id(kid),
|
||||||
{:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id),
|
|
||||||
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
|
{:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do
|
||||||
{:ok, public_key}
|
{:ok, public_key}
|
||||||
else
|
else
|
||||||
|
@ -67,9 +45,9 @@ def refetch_public_key(conn) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def sign(%User{keys: keys} = user, headers) do
|
def sign(%User{} = user, headers) do
|
||||||
with {:ok, private_key, _} <- Keys.keys_from_pem(keys) do
|
with {:ok, private_key} <- SigningKey.private_key(user) do
|
||||||
HTTPSignatures.sign(private_key, user.ap_id <> "#main-key", headers)
|
HTTPSignatures.sign(private_key, SigningKey.local_key_id(user.ap_id), headers)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ defmodule Pleroma.Upload.Filter.Exiftool.StripMetadata do
|
||||||
|
|
||||||
# Formats not compatible with exiftool at this time
|
# Formats not compatible with exiftool at this time
|
||||||
def filter(%Pleroma.Upload{content_type: "image/webp"}), do: {:ok, :noop}
|
def filter(%Pleroma.Upload{content_type: "image/webp"}), do: {:ok, :noop}
|
||||||
|
def filter(%Pleroma.Upload{content_type: "image/bmp"}), do: {:ok, :noop}
|
||||||
def filter(%Pleroma.Upload{content_type: "image/svg+xml"}), do: {:ok, :noop}
|
def filter(%Pleroma.Upload{content_type: "image/svg+xml"}), do: {:ok, :noop}
|
||||||
|
|
||||||
def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do
|
def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do
|
||||||
|
|
|
@ -25,7 +25,6 @@ defmodule Pleroma.User do
|
||||||
alias Pleroma.Hashtag
|
alias Pleroma.Hashtag
|
||||||
alias Pleroma.User.HashtagFollow
|
alias Pleroma.User.HashtagFollow
|
||||||
alias Pleroma.HTML
|
alias Pleroma.HTML
|
||||||
alias Pleroma.Keys
|
|
||||||
alias Pleroma.MFA
|
alias Pleroma.MFA
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
|
@ -43,6 +42,7 @@ defmodule Pleroma.User do
|
||||||
alias Pleroma.Web.OAuth
|
alias Pleroma.Web.OAuth
|
||||||
alias Pleroma.Web.RelMe
|
alias Pleroma.Web.RelMe
|
||||||
alias Pleroma.Workers.BackgroundWorker
|
alias Pleroma.Workers.BackgroundWorker
|
||||||
|
alias Pleroma.User.SigningKey
|
||||||
|
|
||||||
use Pleroma.Web, :verified_routes
|
use Pleroma.Web, :verified_routes
|
||||||
|
|
||||||
|
@ -101,7 +101,6 @@ defmodule Pleroma.User do
|
||||||
field(:password, :string, virtual: true)
|
field(:password, :string, virtual: true)
|
||||||
field(:password_confirmation, :string, virtual: true)
|
field(:password_confirmation, :string, virtual: true)
|
||||||
field(:keys, :string)
|
field(:keys, :string)
|
||||||
field(:public_key, :string)
|
|
||||||
field(:ap_id, :string)
|
field(:ap_id, :string)
|
||||||
field(:avatar, :map, default: %{})
|
field(:avatar, :map, default: %{})
|
||||||
field(:local, :boolean, default: true)
|
field(:local, :boolean, default: true)
|
||||||
|
@ -222,6 +221,10 @@ defmodule Pleroma.User do
|
||||||
on_replace: :delete
|
on_replace: :delete
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -457,6 +460,7 @@ def remote_user_changeset(struct \\ %User{local: false}, params) do
|
||||||
|> fix_follower_address()
|
|> fix_follower_address()
|
||||||
|
|
||||||
struct
|
struct
|
||||||
|
|> Repo.preload(:signing_key)
|
||||||
|> cast(
|
|> cast(
|
||||||
params,
|
params,
|
||||||
[
|
[
|
||||||
|
@ -466,7 +470,6 @@ def remote_user_changeset(struct \\ %User{local: false}, params) do
|
||||||
:inbox,
|
:inbox,
|
||||||
:shared_inbox,
|
:shared_inbox,
|
||||||
:nickname,
|
:nickname,
|
||||||
:public_key,
|
|
||||||
:avatar,
|
:avatar,
|
||||||
:ap_enabled,
|
:ap_enabled,
|
||||||
:banner,
|
:banner,
|
||||||
|
@ -495,6 +498,7 @@ def remote_user_changeset(struct \\ %User{local: false}, params) do
|
||||||
|> validate_required([:ap_id])
|
|> validate_required([:ap_id])
|
||||||
|> validate_required([:name], trim: false)
|
|> validate_required([:name], trim: false)
|
||||||
|> unique_constraint(:nickname)
|
|> unique_constraint(:nickname)
|
||||||
|
|> cast_assoc(:signing_key, with: &SigningKey.remote_changeset/2, required: false)
|
||||||
|> validate_format(:nickname, @email_regex)
|
|> validate_format(:nickname, @email_regex)
|
||||||
|> validate_length(:bio, max: bio_limit)
|
|> validate_length(:bio, max: bio_limit)
|
||||||
|> validate_length(:name, max: name_limit)
|
|> validate_length(:name, max: name_limit)
|
||||||
|
@ -526,7 +530,6 @@ def update_changeset(struct, params \\ %{}) do
|
||||||
:name,
|
:name,
|
||||||
:emoji,
|
:emoji,
|
||||||
:avatar,
|
:avatar,
|
||||||
:public_key,
|
|
||||||
:inbox,
|
:inbox,
|
||||||
:shared_inbox,
|
:shared_inbox,
|
||||||
:is_locked,
|
:is_locked,
|
||||||
|
@ -570,6 +573,7 @@ def update_changeset(struct, params \\ %{}) do
|
||||||
:pleroma_settings_store,
|
:pleroma_settings_store,
|
||||||
&{:ok, Map.merge(struct.pleroma_settings_store, &1)}
|
&{:ok, Map.merge(struct.pleroma_settings_store, &1)}
|
||||||
)
|
)
|
||||||
|
|> cast_assoc(:signing_key, with: &SigningKey.remote_changeset/2, requred: false)
|
||||||
|> validate_fields(false, struct)
|
|> validate_fields(false, struct)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -828,8 +832,10 @@ def put_following_and_follower_and_featured_address(changeset) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp put_private_key(changeset) do
|
defp put_private_key(changeset) do
|
||||||
{:ok, pem} = Keys.generate_rsa_pem()
|
ap_id = get_field(changeset, :ap_id)
|
||||||
put_change(changeset, :keys, pem)
|
|
||||||
|
changeset
|
||||||
|
|> put_assoc(:signing_key, SigningKey.generate_local_keys(ap_id))
|
||||||
end
|
end
|
||||||
|
|
||||||
defp autofollow_users(user) do
|
defp autofollow_users(user) do
|
||||||
|
@ -1146,7 +1152,8 @@ def update_and_set_cache(%{data: %Pleroma.User{} = user} = changeset) do
|
||||||
was_superuser_before_update = User.superuser?(user)
|
was_superuser_before_update = User.superuser?(user)
|
||||||
|
|
||||||
with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
|
with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
|
||||||
set_cache(user)
|
user
|
||||||
|
|> set_cache()
|
||||||
end
|
end
|
||||||
|> maybe_remove_report_notifications(was_superuser_before_update)
|
|> maybe_remove_report_notifications(was_superuser_before_update)
|
||||||
end
|
end
|
||||||
|
@ -1624,8 +1631,12 @@ def blocks_user?(%User{} = user, %User{} = target) do
|
||||||
|
|
||||||
def blocks_user?(_, _), do: false
|
def blocks_user?(_, _), do: false
|
||||||
|
|
||||||
def blocks_domain?(%User{} = user, %User{} = target) do
|
def blocks_domain?(%User{} = user, %User{ap_id: ap_id}) do
|
||||||
%{host: host} = URI.parse(target.ap_id)
|
blocks_domain?(user, ap_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def blocks_domain?(%User{} = user, url) when is_binary(url) do
|
||||||
|
%{host: host} = URI.parse(url)
|
||||||
Enum.member?(user.domain_blocks, host)
|
Enum.member?(user.domain_blocks, host)
|
||||||
# TODO: functionality should probably be changed such that subdomains block as well,
|
# TODO: functionality should probably be changed such that subdomains block as well,
|
||||||
# but as it stands, this just hecks up the relationships endpoint
|
# but as it stands, this just hecks up the relationships endpoint
|
||||||
|
@ -2047,24 +2058,16 @@ defp create_service_actor(uri, nickname) do
|
||||||
|> set_cache()
|
|> set_cache()
|
||||||
end
|
end
|
||||||
|
|
||||||
def public_key(%{public_key: public_key_pem}) when is_binary(public_key_pem) do
|
defdelegate public_key(user), to: SigningKey
|
||||||
key =
|
|
||||||
public_key_pem
|
|
||||||
|> :public_key.pem_decode()
|
|
||||||
|> hd()
|
|
||||||
|> :public_key.pem_entry_decode()
|
|
||||||
|
|
||||||
{:ok, key}
|
|
||||||
end
|
|
||||||
|
|
||||||
def public_key(_), do: {:error, "key not found"}
|
|
||||||
|
|
||||||
def get_public_key_for_ap_id(ap_id) do
|
def get_public_key_for_ap_id(ap_id) do
|
||||||
with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
|
with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id),
|
||||||
{:ok, public_key} <- public_key(user) do
|
{:ok, public_key} <- SigningKey.public_key(user) do
|
||||||
{:ok, public_key}
|
{:ok, public_key}
|
||||||
else
|
else
|
||||||
_ -> :error
|
e ->
|
||||||
|
Logger.error("Could not get public key for #{ap_id}.\n#{inspect(e)}")
|
||||||
|
{:error, e}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
262
lib/pleroma/user/signing_key.ex
Normal file
262
lib/pleroma/user/signing_key.ex
Normal file
|
@ -0,0 +1,262 @@
|
||||||
|
defmodule Pleroma.User.SigningKey do
|
||||||
|
use Ecto.Schema
|
||||||
|
import Ecto.Query
|
||||||
|
import Ecto.Changeset
|
||||||
|
require Pleroma.Constants
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Repo
|
||||||
|
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
@primary_key false
|
||||||
|
schema "signing_keys" do
|
||||||
|
belongs_to(:user, Pleroma.User, type: FlakeId.Ecto.CompatType)
|
||||||
|
field :public_key, :string
|
||||||
|
field :private_key, :string
|
||||||
|
# This is an arbitrary field given by the remote instance
|
||||||
|
field :key_id, :string, primary_key: true
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_key(%User{} = user) do
|
||||||
|
user
|
||||||
|
|> Repo.preload(:signing_key)
|
||||||
|
end
|
||||||
|
|
||||||
|
def key_id_of_local_user(%User{local: true} = user) do
|
||||||
|
case Repo.preload(user, :signing_key) do
|
||||||
|
%User{signing_key: %__MODULE__{key_id: key_id}} -> key_id
|
||||||
|
_ -> nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec remote_changeset(__MODULE__, map) :: Changeset.t()
|
||||||
|
def remote_changeset(%__MODULE__{} = signing_key, attrs) do
|
||||||
|
signing_key
|
||||||
|
|> cast(attrs, [:public_key, :key_id])
|
||||||
|
|> validate_required([:public_key, :key_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec key_id_to_user_id(String.t()) :: String.t() | nil
|
||||||
|
@doc """
|
||||||
|
Given a key ID, return the user ID associated with that key.
|
||||||
|
Returns nil if the key ID is not found.
|
||||||
|
"""
|
||||||
|
def key_id_to_user_id(key_id) do
|
||||||
|
from(sk in __MODULE__, where: sk.key_id == ^key_id)
|
||||||
|
|> select([sk], sk.user_id)
|
||||||
|
|> Repo.one()
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec key_id_to_ap_id(String.t()) :: String.t() | nil
|
||||||
|
@doc """
|
||||||
|
Given a key ID, return the AP ID associated with that key.
|
||||||
|
Returns nil if the key ID is not found.
|
||||||
|
"""
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec generate_rsa_pem() :: {:ok, binary()}
|
||||||
|
@doc """
|
||||||
|
Generate a new RSA private key and return it as a PEM-encoded string.
|
||||||
|
"""
|
||||||
|
def generate_rsa_pem do
|
||||||
|
key = :public_key.generate_key({:rsa, 2048, 65_537})
|
||||||
|
entry = :public_key.pem_entry_encode(:RSAPrivateKey, key)
|
||||||
|
pem = :public_key.pem_encode([entry]) |> String.trim_trailing()
|
||||||
|
{:ok, pem}
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec generate_local_keys(String.t()) :: {:ok, Changeset.t()} | {:error, String.t()}
|
||||||
|
@doc """
|
||||||
|
Generate a new RSA key pair and create a changeset for it
|
||||||
|
"""
|
||||||
|
def generate_local_keys(ap_id) do
|
||||||
|
{:ok, private_pem} = generate_rsa_pem()
|
||||||
|
{:ok, local_pem} = private_pem_to_public_pem(private_pem)
|
||||||
|
|
||||||
|
%__MODULE__{}
|
||||||
|
|> change()
|
||||||
|
|> put_change(:public_key, local_pem)
|
||||||
|
|> put_change(:private_key, private_pem)
|
||||||
|
|> put_change(:key_id, local_key_id(ap_id))
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec local_key_id(String.t()) :: String.t()
|
||||||
|
@doc """
|
||||||
|
Given an AP ID, return the key ID for the local user.
|
||||||
|
"""
|
||||||
|
def local_key_id(ap_id) do
|
||||||
|
ap_id <> "#main-key"
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec private_pem_to_public_pem(binary) :: {:ok, binary()} | {:error, String.t()}
|
||||||
|
@doc """
|
||||||
|
Given a private key in PEM format, return the corresponding public key in PEM format.
|
||||||
|
"""
|
||||||
|
def private_pem_to_public_pem(private_pem) do
|
||||||
|
[private_key_code] = :public_key.pem_decode(private_pem)
|
||||||
|
private_key = :public_key.pem_entry_decode(private_key_code)
|
||||||
|
{:RSAPrivateKey, _, modulus, exponent, _, _, _, _, _, _, _} = private_key
|
||||||
|
public_key = {:RSAPublicKey, modulus, exponent}
|
||||||
|
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
|
||||||
|
{:ok, :public_key.pem_encode([public_key])}
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec public_key(User.t()) :: {:ok, binary()} | {:error, String.t()}
|
||||||
|
@doc """
|
||||||
|
Given a user, return the public key for that user 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()
|
||||||
|
|
||||||
|
{:ok, key}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{:error, "key not found"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def public_key(_), do: {:error, "key not found"}
|
||||||
|
|
||||||
|
def public_key_pem(%User{} = user) do
|
||||||
|
case Repo.preload(user, :signing_key) do
|
||||||
|
%User{signing_key: %__MODULE__{public_key: public_key_pem}} -> {:ok, public_key_pem}
|
||||||
|
_ -> {:error, "key not found"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def public_key_pem(_e) do
|
||||||
|
{:error, "key not found"}
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec private_key(User.t()) :: {:ok, binary()} | {:error, String.t()}
|
||||||
|
@doc """
|
||||||
|
Given a user, return the private key for that user in binary format.
|
||||||
|
"""
|
||||||
|
def private_key(%User{} = user) do
|
||||||
|
case Repo.preload(user, :signing_key) do
|
||||||
|
%{signing_key: %__MODULE__{private_key: private_key_pem}} ->
|
||||||
|
key =
|
||||||
|
private_key_pem
|
||||||
|
|> :public_key.pem_decode()
|
||||||
|
|> hd()
|
||||||
|
|> :public_key.pem_entry_decode()
|
||||||
|
|
||||||
|
{:ok, key}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{:error, "key not found"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec get_or_fetch_by_key_id(String.t()) :: {:ok, __MODULE__} | {:error, String.t()}
|
||||||
|
@doc """
|
||||||
|
Given a key ID, return the signing key associated with that key.
|
||||||
|
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
|
||||||
|
nil ->
|
||||||
|
fetch_remote_key(key_id)
|
||||||
|
|
||||||
|
user_id ->
|
||||||
|
{:ok, Repo.get_by(__MODULE__, user_id: user_id)}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec fetch_remote_key(String.t()) :: {:ok, __MODULE__} | {:error, String.t()}
|
||||||
|
@doc """
|
||||||
|
Fetch a remote key by key ID.
|
||||||
|
Will send a request to the remote instance to get the key ID.
|
||||||
|
This request should, at the very least, return a user ID and a public key object.
|
||||||
|
Though bear in mind that some implementations (looking at you, pleroma) may require a signature for this request.
|
||||||
|
This has the potential to create an infinite loop if the remote instance requires a signature to fetch the key...
|
||||||
|
So if we're rejected, we should probably just give up.
|
||||||
|
"""
|
||||||
|
def fetch_remote_key(key_id) do
|
||||||
|
Logger.debug("Fetching remote key: #{key_id}")
|
||||||
|
resp = Pleroma.Object.Fetcher.fetch_and_contain_remote_object_from_id(key_id)
|
||||||
|
|
||||||
|
case resp do
|
||||||
|
{:ok, _body} ->
|
||||||
|
case handle_signature_response(resp) do
|
||||||
|
{:ok, ap_id, public_key_pem} ->
|
||||||
|
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,
|
||||||
|
public_key: public_key_pem,
|
||||||
|
key_id: key_id
|
||||||
|
}
|
||||||
|
|
||||||
|
Repo.insert(key, on_conflict: :replace_all, conflict_target: :key_id)
|
||||||
|
|
||||||
|
e ->
|
||||||
|
Logger.debug("Failed to fetch remote key: #{inspect(e)}")
|
||||||
|
{:error, "Could not fetch key"}
|
||||||
|
end
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
Logger.debug("Failed to fetch remote key: #{inspect(resp)}")
|
||||||
|
{:error, "Could not fetch 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
|
||||||
|
if ap_id !== public_key["owner"] do
|
||||||
|
{:error, "Key ID does not match owner"}
|
||||||
|
else
|
||||||
|
%{"publicKeyPem" => public_key_pem} = public_key
|
||||||
|
{:ok, ap_id, public_key_pem}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp handle_signature_response({:ok, body}) do
|
||||||
|
case body do
|
||||||
|
%{
|
||||||
|
"type" => "CryptographicKey",
|
||||||
|
"publicKeyPem" => public_key_pem,
|
||||||
|
"owner" => ap_id
|
||||||
|
} ->
|
||||||
|
{:ok, ap_id, public_key_pem}
|
||||||
|
|
||||||
|
# for when we get a subset of the user object
|
||||||
|
%{
|
||||||
|
"id" => _user_id,
|
||||||
|
"publicKey" => _public_key,
|
||||||
|
"type" => actor_type
|
||||||
|
}
|
||||||
|
when actor_type in Pleroma.Constants.actor_types() ->
|
||||||
|
extract_key_details(body)
|
||||||
|
|
||||||
|
%{"error" => error} ->
|
||||||
|
{:error, error}
|
||||||
|
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
|
|
@ -1547,6 +1547,17 @@ defp normalize_attachment(%{} = attachment), do: [attachment]
|
||||||
defp normalize_attachment(attachment) when is_list(attachment), do: attachment
|
defp normalize_attachment(attachment) when is_list(attachment), do: attachment
|
||||||
defp normalize_attachment(_), do: []
|
defp normalize_attachment(_), do: []
|
||||||
|
|
||||||
|
defp maybe_make_public_key_object(data) do
|
||||||
|
if is_map(data["publicKey"]) && is_binary(data["publicKey"]["publicKeyPem"]) do
|
||||||
|
%{
|
||||||
|
public_key: data["publicKey"]["publicKeyPem"],
|
||||||
|
key_id: data["publicKey"]["id"]
|
||||||
|
}
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
defp object_to_user_data(data, additional) do
|
defp object_to_user_data(data, additional) do
|
||||||
fields =
|
fields =
|
||||||
data
|
data
|
||||||
|
@ -1578,9 +1589,16 @@ defp object_to_user_data(data, additional) do
|
||||||
featured_address = data["featured"]
|
featured_address = data["featured"]
|
||||||
{:ok, pinned_objects} = fetch_and_prepare_featured_from_ap_id(featured_address)
|
{:ok, pinned_objects} = fetch_and_prepare_featured_from_ap_id(featured_address)
|
||||||
|
|
||||||
public_key =
|
# first, check that the owner is correct
|
||||||
if is_map(data["publicKey"]) && is_binary(data["publicKey"]["publicKeyPem"]) do
|
signing_key =
|
||||||
data["publicKey"]["publicKeyPem"]
|
if data["id"] !== data["publicKey"]["owner"] do
|
||||||
|
Logger.error(
|
||||||
|
"Owner of the public key is not the same as the actor - not saving the public key."
|
||||||
|
)
|
||||||
|
|
||||||
|
nil
|
||||||
|
else
|
||||||
|
maybe_make_public_key_object(data)
|
||||||
end
|
end
|
||||||
|
|
||||||
shared_inbox =
|
shared_inbox =
|
||||||
|
@ -1624,7 +1642,7 @@ defp object_to_user_data(data, additional) do
|
||||||
bio: data["summary"] || "",
|
bio: data["summary"] || "",
|
||||||
actor_type: actor_type,
|
actor_type: actor_type,
|
||||||
also_known_as: also_known_as,
|
also_known_as: also_known_as,
|
||||||
public_key: public_key,
|
signing_key: signing_key,
|
||||||
inbox: data["inbox"],
|
inbox: data["inbox"],
|
||||||
shared_inbox: shared_inbox,
|
shared_inbox: shared_inbox,
|
||||||
pinned_objects: pinned_objects,
|
pinned_objects: pinned_objects,
|
||||||
|
|
|
@ -60,7 +60,26 @@ defp relay_active?(conn, _) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def user(conn, %{"nickname" => nickname}) do
|
@doc """
|
||||||
|
Render the user's AP data
|
||||||
|
WARNING: we cannot actually check if the request has a fragment! so let's play defensively
|
||||||
|
- IF we have a valid signature, serve full user
|
||||||
|
- IF we do not, and authorized_fetch_mode is enabled, serve the key only
|
||||||
|
- OTHERWISE, serve the full actor (since we don't need to worry about the signature)
|
||||||
|
"""
|
||||||
|
def user(%{assigns: %{valid_signature: true}} = conn, params) do
|
||||||
|
render_full_user(conn, params)
|
||||||
|
end
|
||||||
|
|
||||||
|
def user(conn, params) do
|
||||||
|
if Pleroma.Config.get([:activitypub, :authorized_fetch_mode], false) do
|
||||||
|
render_key_only_user(conn, params)
|
||||||
|
else
|
||||||
|
render_full_user(conn, params)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp render_full_user(conn, %{"nickname" => nickname}) do
|
||||||
with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
|
with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("application/activity+json")
|
|> put_resp_content_type("application/activity+json")
|
||||||
|
@ -72,6 +91,18 @@ def user(conn, %{"nickname" => nickname}) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def render_key_only_user(conn, %{"nickname" => nickname}) do
|
||||||
|
with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
|
||||||
|
conn
|
||||||
|
|> put_resp_content_type("application/activity+json")
|
||||||
|
|> put_view(UserView)
|
||||||
|
|> render("keys.json", %{user: user})
|
||||||
|
else
|
||||||
|
nil -> {:error, :not_found}
|
||||||
|
%{local: false} -> {:error, :not_found}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def object(%{assigns: assigns} = conn, _) do
|
def object(%{assigns: assigns} = conn, _) do
|
||||||
with ap_id <- Endpoint.url() <> conn.request_path,
|
with ap_id <- Endpoint.url() <> conn.request_path,
|
||||||
%Object{} = object <- Object.get_cached_by_ap_id(ap_id),
|
%Object{} = object <- Object.get_cached_by_ap_id(ap_id),
|
||||||
|
|
|
@ -14,7 +14,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UserValidator do
|
||||||
@behaviour Pleroma.Web.ActivityPub.ObjectValidator.Validating
|
@behaviour Pleroma.Web.ActivityPub.ObjectValidator.Validating
|
||||||
|
|
||||||
alias Pleroma.Object.Containment
|
alias Pleroma.Object.Containment
|
||||||
alias Pleroma.Signature
|
|
||||||
|
|
||||||
require Pleroma.Constants
|
require Pleroma.Constants
|
||||||
|
|
||||||
|
@ -23,8 +22,7 @@ def validate(object, meta)
|
||||||
|
|
||||||
def validate(%{"type" => type, "id" => _id} = data, meta)
|
def validate(%{"type" => type, "id" => _id} = data, meta)
|
||||||
when type in Pleroma.Constants.actor_types() do
|
when type in Pleroma.Constants.actor_types() do
|
||||||
with :ok <- validate_pubkey(data),
|
with :ok <- validate_inbox(data),
|
||||||
:ok <- validate_inbox(data),
|
|
||||||
:ok <- contain_collection_origin(data) do
|
:ok <- contain_collection_origin(data) do
|
||||||
{:ok, data, meta}
|
{:ok, data, meta}
|
||||||
else
|
else
|
||||||
|
@ -35,33 +33,6 @@ def validate(%{"type" => type, "id" => _id} = data, meta)
|
||||||
|
|
||||||
def validate(_, _), do: {:error, "Not a user object"}
|
def validate(_, _), do: {:error, "Not a user object"}
|
||||||
|
|
||||||
defp mabye_validate_owner(nil, _actor), do: :ok
|
|
||||||
defp mabye_validate_owner(actor, actor), do: :ok
|
|
||||||
defp mabye_validate_owner(_owner, _actor), do: :error
|
|
||||||
|
|
||||||
defp validate_pubkey(
|
|
||||||
%{"id" => id, "publicKey" => %{"id" => pk_id, "publicKeyPem" => _key}} = data
|
|
||||||
)
|
|
||||||
when id != nil do
|
|
||||||
with {_, {:ok, kactor}} <- {:key, Signature.key_id_to_actor_id(pk_id)},
|
|
||||||
true <- id == kactor,
|
|
||||||
:ok <- mabye_validate_owner(Map.get(data, "owner"), id) do
|
|
||||||
:ok
|
|
||||||
else
|
|
||||||
{:key, _} ->
|
|
||||||
{:error, "Unable to determine actor id from key id"}
|
|
||||||
|
|
||||||
false ->
|
|
||||||
{:error, "Key id does not relate to user id"}
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
{:error, "Actor does not own its public key"}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# pubkey is optional atm
|
|
||||||
defp validate_pubkey(_data), do: :ok
|
|
||||||
|
|
||||||
defp validate_inbox(%{"id" => id, "inbox" => inbox}) do
|
defp validate_inbox(%{"id" => id, "inbox" => inbox}) do
|
||||||
case Containment.same_origin(id, inbox) do
|
case Containment.same_origin(id, inbox) do
|
||||||
:ok -> :ok
|
:ok -> :ok
|
||||||
|
|
|
@ -951,7 +951,7 @@ defp build_emoji_tag({name, url}) do
|
||||||
"name" => ":" <> name <> ":",
|
"name" => ":" <> name <> ":",
|
||||||
"type" => "Emoji",
|
"type" => "Emoji",
|
||||||
"updated" => "1970-01-01T00:00:00Z",
|
"updated" => "1970-01-01T00:00:00Z",
|
||||||
"id" => url
|
"id" => nil
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
defmodule Pleroma.Web.ActivityPub.UserView do
|
defmodule Pleroma.Web.ActivityPub.UserView do
|
||||||
use Pleroma.Web, :view
|
use Pleroma.Web, :view
|
||||||
|
|
||||||
alias Pleroma.Keys
|
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
@ -33,9 +32,7 @@ def render("endpoints.json", %{user: %User{local: true} = _user}) do
|
||||||
def render("endpoints.json", _), do: %{}
|
def render("endpoints.json", _), do: %{}
|
||||||
|
|
||||||
def render("service.json", %{user: user}) do
|
def render("service.json", %{user: user}) do
|
||||||
{:ok, _, public_key} = Keys.keys_from_pem(user.keys)
|
{:ok, public_key} = User.SigningKey.public_key_pem(user)
|
||||||
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
|
|
||||||
public_key = :public_key.pem_encode([public_key])
|
|
||||||
|
|
||||||
endpoints = render("endpoints.json", %{user: user})
|
endpoints = render("endpoints.json", %{user: user})
|
||||||
|
|
||||||
|
@ -52,7 +49,7 @@ def render("service.json", %{user: user}) do
|
||||||
"url" => user.ap_id,
|
"url" => user.ap_id,
|
||||||
"manuallyApprovesFollowers" => false,
|
"manuallyApprovesFollowers" => false,
|
||||||
"publicKey" => %{
|
"publicKey" => %{
|
||||||
"id" => "#{user.ap_id}#main-key",
|
"id" => User.SigningKey.local_key_id(user.ap_id),
|
||||||
"owner" => user.ap_id,
|
"owner" => user.ap_id,
|
||||||
"publicKeyPem" => public_key
|
"publicKeyPem" => public_key
|
||||||
},
|
},
|
||||||
|
@ -70,9 +67,12 @@ def render("user.json", %{user: %User{nickname: "internal." <> _} = user}),
|
||||||
do: render("service.json", %{user: user}) |> Map.put("preferredUsername", user.nickname)
|
do: render("service.json", %{user: user}) |> Map.put("preferredUsername", user.nickname)
|
||||||
|
|
||||||
def render("user.json", %{user: user}) do
|
def render("user.json", %{user: user}) do
|
||||||
{:ok, _, public_key} = Keys.keys_from_pem(user.keys)
|
public_key =
|
||||||
public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
|
case User.SigningKey.public_key_pem(user) do
|
||||||
public_key = :public_key.pem_encode([public_key])
|
{:ok, public_key} -> public_key
|
||||||
|
_ -> nil
|
||||||
|
end
|
||||||
|
|
||||||
user = User.sanitize_html(user)
|
user = User.sanitize_html(user)
|
||||||
|
|
||||||
endpoints = render("endpoints.json", %{user: user})
|
endpoints = render("endpoints.json", %{user: user})
|
||||||
|
@ -97,7 +97,7 @@ def render("user.json", %{user: user}) do
|
||||||
"url" => user.ap_id,
|
"url" => user.ap_id,
|
||||||
"manuallyApprovesFollowers" => user.is_locked,
|
"manuallyApprovesFollowers" => user.is_locked,
|
||||||
"publicKey" => %{
|
"publicKey" => %{
|
||||||
"id" => "#{user.ap_id}#main-key",
|
"id" => User.SigningKey.local_key_id(user.ap_id),
|
||||||
"owner" => user.ap_id,
|
"owner" => user.ap_id,
|
||||||
"publicKeyPem" => public_key
|
"publicKeyPem" => public_key
|
||||||
},
|
},
|
||||||
|
@ -116,6 +116,20 @@ def render("user.json", %{user: user}) do
|
||||||
|> Map.merge(Utils.make_json_ld_header())
|
|> Map.merge(Utils.make_json_ld_header())
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def render("keys.json", %{user: user}) do
|
||||||
|
{:ok, public_key} = User.SigningKey.public_key_pem(user)
|
||||||
|
|
||||||
|
%{
|
||||||
|
"id" => user.ap_id,
|
||||||
|
"publicKey" => %{
|
||||||
|
"id" => User.SigningKey.key_id_of_local_user(user),
|
||||||
|
"owner" => user.ap_id,
|
||||||
|
"publicKeyPem" => public_key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> Map.merge(Utils.make_json_ld_header())
|
||||||
|
end
|
||||||
|
|
||||||
def render("following.json", %{user: user, page: page} = opts) do
|
def render("following.json", %{user: user, page: page} = opts) do
|
||||||
showing_items = (opts[:for] && opts[:for] == user) || !user.hide_follows
|
showing_items = (opts[:for] && opts[:for] == user) || !user.hide_follows
|
||||||
showing_count = showing_items || !user.hide_follows_count
|
showing_count = showing_items || !user.hide_follows_count
|
||||||
|
|
|
@ -32,7 +32,9 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do
|
||||||
},
|
},
|
||||||
voters_count: %Schema{
|
voters_count: %Schema{
|
||||||
type: :integer,
|
type: :integer,
|
||||||
description: "How many unique accounts have voted. Number."
|
nullable: true,
|
||||||
|
description:
|
||||||
|
"How many unique accounts have voted for a multi-selection poll. Number, or null if single-selection poll."
|
||||||
},
|
},
|
||||||
voted: %Schema{
|
voted: %Schema{
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
|
|
|
@ -66,10 +66,10 @@ defmodule Pleroma.Web.Endpoint do
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
plug(Plug.Static.IndexHtml, at: "/akkoma/swaggerui")
|
plug(Plug.Static.IndexHtml, at: "/pleroma/swaggerui/")
|
||||||
|
|
||||||
plug(Pleroma.Web.Plugs.FrontendStatic,
|
plug(Pleroma.Web.Plugs.FrontendStatic,
|
||||||
at: "/akkoma/swaggerui",
|
at: "/pleroma/swaggerui",
|
||||||
frontend_type: :swagger,
|
frontend_type: :swagger,
|
||||||
gzip: true,
|
gzip: true,
|
||||||
if: &Pleroma.Web.Swagger.ui_enabled?/0,
|
if: &Pleroma.Web.Swagger.ui_enabled?/0,
|
||||||
|
|
|
@ -19,7 +19,7 @@ def render("show.json", %{object: object, multiple: multiple, options: options}
|
||||||
expired: expired,
|
expired: expired,
|
||||||
multiple: multiple,
|
multiple: multiple,
|
||||||
votes_count: votes_count,
|
votes_count: votes_count,
|
||||||
voters_count: voters_count(object),
|
voters_count: voters_count(multiple, object),
|
||||||
options: options,
|
options: options,
|
||||||
emojis: Pleroma.Web.MastodonAPI.StatusView.build_emojis(object.data["emoji"])
|
emojis: Pleroma.Web.MastodonAPI.StatusView.build_emojis(object.data["emoji"])
|
||||||
}
|
}
|
||||||
|
@ -68,11 +68,19 @@ defp options_and_votes_count(options) do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp voters_count(%{data: %{"voters" => voters}}) when is_list(voters) do
|
defp voters_count(false, _poll_data) do
|
||||||
|
# Mastodon always sets voter count to "null" unless multiple options were selectable
|
||||||
|
# Some clients may rely on this to detect multiple selection polls and it can mess
|
||||||
|
# up percentages for some clients if we never got a correct remote voter count and
|
||||||
|
# only count local voters here; see https://akkoma.dev/AkkomaGang/akkoma/issues/190
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
|
||||||
|
defp voters_count(_multiple, %{data: %{"voters" => voters}}) when is_list(voters) do
|
||||||
length(voters)
|
length(voters)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp voters_count(_), do: 0
|
defp voters_count(_, _), do: 0
|
||||||
|
|
||||||
defp voted_and_own_votes(%{object: object} = params, options) do
|
defp voted_and_own_votes(%{object: object} = params, options) do
|
||||||
if params[:for] do
|
if params[:for] do
|
||||||
|
|
|
@ -52,6 +52,14 @@ defp filter_allowed_user_by_ap_id(ap_ids, excluded_ap_ids) do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp filter_allowed_users_by_domain(ap_ids, %User{} = for_user) do
|
||||||
|
Enum.reject(ap_ids, fn ap_id ->
|
||||||
|
User.blocks_domain?(for_user, ap_id)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp filter_allowed_users_by_domain(ap_ids, nil), do: ap_ids
|
||||||
|
|
||||||
def filter_allowed_users(reactions, user, with_muted) do
|
def filter_allowed_users(reactions, user, with_muted) do
|
||||||
exclude_ap_ids =
|
exclude_ap_ids =
|
||||||
if is_nil(user) do
|
if is_nil(user) do
|
||||||
|
@ -62,7 +70,10 @@ def filter_allowed_users(reactions, user, with_muted) do
|
||||||
end
|
end
|
||||||
|
|
||||||
filter_emoji = fn emoji, users, url ->
|
filter_emoji = fn emoji, users, url ->
|
||||||
case filter_allowed_user_by_ap_id(users, exclude_ap_ids) do
|
users
|
||||||
|
|> filter_allowed_user_by_ap_id(exclude_ap_ids)
|
||||||
|
|> filter_allowed_users_by_domain(user)
|
||||||
|
|> case do
|
||||||
[] -> nil
|
[] -> nil
|
||||||
users -> {emoji, users, url}
|
users -> {emoji, users, url}
|
||||||
end
|
end
|
||||||
|
|
32
lib/pleroma/web/plugs/ensure_user_public_key_plug.ex
Normal file
32
lib/pleroma/web/plugs/ensure_user_public_key_plug.ex
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
defmodule Pleroma.Web.Plugs.EnsureUserPublicKeyPlug do
|
||||||
|
@moduledoc """
|
||||||
|
This plug will attempt to pull in a user's public key if we do not have it.
|
||||||
|
We _should_ be able to request the URL from the key URL...
|
||||||
|
"""
|
||||||
|
|
||||||
|
alias Pleroma.User
|
||||||
|
|
||||||
|
def init(options), do: options
|
||||||
|
|
||||||
|
def call(conn, _opts) do
|
||||||
|
key_id = key_id_from_conn(conn)
|
||||||
|
|
||||||
|
unless is_nil(key_id) do
|
||||||
|
User.SigningKey.get_or_fetch_by_key_id(key_id)
|
||||||
|
# now we SHOULD have the user that owns the key locally. maybe.
|
||||||
|
# if we don't, we'll error out when we try to validate.
|
||||||
|
end
|
||||||
|
|
||||||
|
conn
|
||||||
|
end
|
||||||
|
|
||||||
|
defp key_id_from_conn(conn) do
|
||||||
|
case HTTPSignatures.signature_for_conn(conn) do
|
||||||
|
%{"keyId" => key_id} when is_binary(key_id) ->
|
||||||
|
key_id
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -139,12 +139,17 @@ defp maybe_require_signature(
|
||||||
defp maybe_require_signature(conn), do: conn
|
defp maybe_require_signature(conn), do: conn
|
||||||
|
|
||||||
defp signature_host(conn) do
|
defp signature_host(conn) do
|
||||||
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
|
with {:key_id, %{"keyId" => kid}} <- {:key_id, HTTPSignatures.signature_for_conn(conn)},
|
||||||
{:ok, actor_id} <- Signature.key_id_to_actor_id(kid) do
|
{:actor_id, {:ok, actor_id}} <- {:actor_id, Signature.key_id_to_actor_id(kid)} do
|
||||||
actor_id
|
actor_id
|
||||||
else
|
else
|
||||||
e ->
|
{:key_id, e} ->
|
||||||
{:error, 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)}")
|
||||||
|
nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do
|
defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do
|
||||||
alias Pleroma.Helpers.AuthHelper
|
alias Pleroma.Helpers.AuthHelper
|
||||||
alias Pleroma.Signature
|
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
|
|
||||||
|
@ -33,7 +32,7 @@ def call(%{assigns: %{valid_signature: true}, params: %{"actor" => actor}} = con
|
||||||
|> assign(:valid_signature, false)
|
|> assign(:valid_signature, false)
|
||||||
|
|
||||||
# remove me once testsuite uses mapped capabilities instead of what we do now
|
# remove me once testsuite uses mapped capabilities instead of what we do now
|
||||||
{:user, nil} ->
|
{:user, _} ->
|
||||||
Logger.debug("Failed to map identity from signature (lookup failure)")
|
Logger.debug("Failed to map identity from signature (lookup failure)")
|
||||||
Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}, actor=#{actor}")
|
Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}, actor=#{actor}")
|
||||||
|
|
||||||
|
@ -68,7 +67,8 @@ def call(%{assigns: %{valid_signature: true}} = conn, _opts) do
|
||||||
Logger.debug("Failed to map identity from signature (lookup failure)")
|
Logger.debug("Failed to map identity from signature (lookup failure)")
|
||||||
Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}")
|
Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}")
|
||||||
|
|
||||||
only_permit_user_routes(conn)
|
conn
|
||||||
|
|> assign(:valid_signature, false)
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
Logger.debug("Failed to map identity from signature (no payload actor mismatch)")
|
Logger.debug("Failed to map identity from signature (no payload actor mismatch)")
|
||||||
|
@ -82,33 +82,34 @@ def call(%{assigns: %{valid_signature: true}} = conn, _opts) do
|
||||||
# no signature at all
|
# no signature at all
|
||||||
def call(conn, _opts), do: conn
|
def call(conn, _opts), do: conn
|
||||||
|
|
||||||
defp only_permit_user_routes(%{path_info: ["users", _]} = conn) do
|
|
||||||
conn
|
|
||||||
|> assign(:limited_ap, true)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp only_permit_user_routes(conn) do
|
|
||||||
conn
|
|
||||||
|> assign(:valid_signature, false)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp key_id_from_conn(conn) do
|
defp key_id_from_conn(conn) do
|
||||||
with %{"keyId" => key_id} <- HTTPSignatures.signature_for_conn(conn),
|
case HTTPSignatures.signature_for_conn(conn) do
|
||||||
{:ok, ap_id} <- Signature.key_id_to_actor_id(key_id) do
|
%{"keyId" => key_id} when is_binary(key_id) ->
|
||||||
ap_id
|
key_id
|
||||||
else
|
|
||||||
_ ->
|
_ ->
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp user_from_key_id(conn) do
|
defp user_from_key_id(conn) do
|
||||||
with key_actor_id when is_binary(key_actor_id) <- key_id_from_conn(conn),
|
with {:key_id, key_id} when is_binary(key_id) <- {:key_id, key_id_from_conn(conn)},
|
||||||
{:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(key_actor_id) do
|
{:mapped_ap_id, ap_id} when is_binary(ap_id) <-
|
||||||
|
{:mapped_ap_id, User.SigningKey.key_id_to_ap_id(key_id)},
|
||||||
|
{:user_fetch, {:ok, %User{} = user}} <- {:user_fetch, User.get_or_fetch_by_ap_id(ap_id)} do
|
||||||
user
|
user
|
||||||
else
|
else
|
||||||
_ ->
|
{:key_id, nil} ->
|
||||||
nil
|
Logger.debug("Failed to map identity from signature (no key ID)")
|
||||||
|
{:key_id, nil}
|
||||||
|
|
||||||
|
{:mapped_ap_id, nil} ->
|
||||||
|
Logger.debug("Failed to map identity from signature (could not map key ID to AP ID)")
|
||||||
|
{:mapped_ap_id, nil}
|
||||||
|
|
||||||
|
{:user_fetch, {:error, _}} ->
|
||||||
|
Logger.debug("Failed to map identity from signature (lookup failure)")
|
||||||
|
{:user_fetch, nil}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -144,7 +144,14 @@ defmodule Pleroma.Web.Router do
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
pipeline :optional_http_signature do
|
||||||
|
plug(Pleroma.Web.Plugs.EnsureUserPublicKeyPlug)
|
||||||
|
plug(Pleroma.Web.Plugs.HTTPSignaturePlug)
|
||||||
|
plug(Pleroma.Web.Plugs.MappedSignatureToIdentityPlug)
|
||||||
|
end
|
||||||
|
|
||||||
pipeline :http_signature do
|
pipeline :http_signature do
|
||||||
|
plug(Pleroma.Web.Plugs.EnsureUserPublicKeyPlug)
|
||||||
plug(Pleroma.Web.Plugs.HTTPSignaturePlug)
|
plug(Pleroma.Web.Plugs.HTTPSignaturePlug)
|
||||||
plug(Pleroma.Web.Plugs.MappedSignatureToIdentityPlug)
|
plug(Pleroma.Web.Plugs.MappedSignatureToIdentityPlug)
|
||||||
plug(Pleroma.Web.Plugs.EnsureHTTPSignaturePlug)
|
plug(Pleroma.Web.Plugs.EnsureHTTPSignaturePlug)
|
||||||
|
@ -745,7 +752,7 @@ defmodule Pleroma.Web.Router do
|
||||||
scope "/", Pleroma.Web do
|
scope "/", Pleroma.Web do
|
||||||
# Note: html format is supported only if static FE is enabled
|
# Note: html format is supported only if static FE is enabled
|
||||||
# Note: http signature is only considered for json requests (no auth for non-json requests)
|
# Note: http signature is only considered for json requests (no auth for non-json requests)
|
||||||
pipe_through([:accepts_html_xml_json, :http_signature, :static_fe])
|
pipe_through([:accepts_html_xml_json, :optional_http_signature, :static_fe])
|
||||||
|
|
||||||
# Note: returns user _profile_ for json requests, redirects to user _feed_ for non-json ones
|
# Note: returns user _profile_ for json requests, redirects to user _feed_ for non-json ones
|
||||||
get("/users/:nickname", Feed.UserController, :feed_redirect, as: :user_feed)
|
get("/users/:nickname", Feed.UserController, :feed_redirect, as: :user_feed)
|
||||||
|
|
|
@ -5,30 +5,65 @@
|
||||||
defmodule Pleroma.Workers.AttachmentsCleanupWorker do
|
defmodule Pleroma.Workers.AttachmentsCleanupWorker do
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
|
alias Pleroma.Config
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
|
|
||||||
use Pleroma.Workers.WorkerHelper, queue: "attachments_cleanup"
|
use Pleroma.Workers.WorkerHelper, queue: "attachments_cleanup"
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Takes object data and if necessary enqueues a job,
|
||||||
|
deleting all attachments of the post eligible for cleanup
|
||||||
|
"""
|
||||||
|
@spec enqueue_if_needed(map()) :: {:ok, Oban.Job.t()} | {:ok, :skip} | {:error, any()}
|
||||||
|
def enqueue_if_needed(%{
|
||||||
|
"actor" => actor,
|
||||||
|
"attachment" => [_ | _] = attachments
|
||||||
|
}) do
|
||||||
|
with true <- Config.get([:instance, :cleanup_attachments]),
|
||||||
|
true <- URI.parse(actor).host == Pleroma.Web.Endpoint.host(),
|
||||||
|
[_ | _] <- attachments do
|
||||||
|
enqueue(
|
||||||
|
"cleanup_attachments",
|
||||||
|
%{"actor" => actor, "attachments" => attachments},
|
||||||
|
schedule_in: Config.get!([:instance, :cleanup_attachments_delay])
|
||||||
|
)
|
||||||
|
else
|
||||||
|
_ -> {:ok, :skip}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def enqueue_if_needed(_), do: {:ok, :skip}
|
||||||
|
|
||||||
@impl Oban.Worker
|
@impl Oban.Worker
|
||||||
def perform(%Job{
|
def perform(%Job{
|
||||||
args: %{
|
args: %{
|
||||||
"op" => "cleanup_attachments",
|
"op" => "cleanup_attachments",
|
||||||
"object" => %{"data" => %{"attachment" => [_ | _] = attachments, "actor" => actor}}
|
"attachments" => [_ | _] = attachments,
|
||||||
|
"actor" => actor
|
||||||
}
|
}
|
||||||
}) do
|
}) do
|
||||||
if Pleroma.Config.get([:instance, :cleanup_attachments], false) do
|
attachments
|
||||||
attachments
|
|> Enum.flat_map(fn item -> Enum.map(item["url"], & &1["href"]) end)
|
||||||
|> Enum.flat_map(fn item -> Enum.map(item["url"], & &1["href"]) end)
|
|> fetch_objects
|
||||||
|> fetch_objects
|
|> prepare_objects(actor, Enum.map(attachments, & &1["name"]))
|
||||||
|> prepare_objects(actor, Enum.map(attachments, & &1["name"]))
|
|> filter_objects
|
||||||
|> filter_objects
|
|> do_clean
|
||||||
|> do_clean
|
|
||||||
end
|
|
||||||
|
|
||||||
{:ok, :success}
|
{:ok, :success}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Left over already enqueued jobs in the old format
|
||||||
|
# This function clause can be deleted once sufficient time passed after 3.14
|
||||||
|
def perform(%Job{
|
||||||
|
args: %{
|
||||||
|
"op" => "cleanup_attachments",
|
||||||
|
"object" => %{"data" => data}
|
||||||
|
}
|
||||||
|
}) do
|
||||||
|
enqueue_if_needed(data)
|
||||||
|
end
|
||||||
|
|
||||||
def perform(%Job{args: %{"op" => "cleanup_attachments", "object" => _object}}), do: {:ok, :skip}
|
def perform(%Job{args: %{"op" => "cleanup_attachments", "object" => _object}}), do: {:ok, :skip}
|
||||||
|
|
||||||
defp do_clean({object_ids, attachment_urls}) do
|
defp do_clean({object_ids, attachment_urls}) do
|
||||||
|
|
6
mix.exs
6
mix.exs
|
@ -144,7 +144,7 @@ defp deps do
|
||||||
{:ex_aws, "~> 2.4"},
|
{:ex_aws, "~> 2.4"},
|
||||||
{:ex_aws_s3, "~> 2.4"},
|
{:ex_aws_s3, "~> 2.4"},
|
||||||
{:sweet_xml, "~> 0.7"},
|
{:sweet_xml, "~> 0.7"},
|
||||||
{:earmark, "~> 1.4"},
|
{:earmark, "1.4.46"},
|
||||||
{:bbcode_pleroma, "~> 0.2.0"},
|
{:bbcode_pleroma, "~> 0.2.0"},
|
||||||
{:argon2_elixir, "~> 3.1"},
|
{:argon2_elixir, "~> 3.1"},
|
||||||
{:cors_plug, "~> 3.0"},
|
{:cors_plug, "~> 3.0"},
|
||||||
|
@ -180,7 +180,7 @@ defp deps do
|
||||||
{:remote_ip, "~> 1.1.0"},
|
{:remote_ip, "~> 1.1.0"},
|
||||||
{:captcha,
|
{:captcha,
|
||||||
git: "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git",
|
git: "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git",
|
||||||
ref: "90f6ce7672f70f56708792a98d98bd05176c9176"},
|
ref: "6630c42aaaab124e697b4e513190c89d8b64e410"},
|
||||||
{:restarter, path: "./restarter"},
|
{:restarter, path: "./restarter"},
|
||||||
{:majic,
|
{:majic,
|
||||||
git: "https://akkoma.dev/AkkomaGang/majic.git",
|
git: "https://akkoma.dev/AkkomaGang/majic.git",
|
||||||
|
@ -200,7 +200,7 @@ defp deps do
|
||||||
|
|
||||||
## dev & test
|
## dev & test
|
||||||
{:ex_doc, "~> 0.30", only: :dev, runtime: false},
|
{:ex_doc, "~> 0.30", only: :dev, runtime: false},
|
||||||
{:ex_machina, "~> 2.7", only: :test},
|
{:ex_machina, "~> 2.8", only: :test},
|
||||||
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
|
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
|
||||||
{:mock, "~> 0.3.8", only: :test},
|
{:mock, "~> 0.3.8", only: :test},
|
||||||
{:excoveralls, "0.16.1", only: :test},
|
{:excoveralls, "0.16.1", only: :test},
|
||||||
|
|
67
mix.lock
67
mix.lock
|
@ -7,51 +7,51 @@
|
||||||
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
|
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
|
||||||
"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"},
|
"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"},
|
"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"]},
|
"captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "6630c42aaaab124e697b4e513190c89d8b64e410", [ref: "6630c42aaaab124e697b4e513190c89d8b64e410"]},
|
||||||
"castore": {:hex, :castore, "1.0.7", "b651241514e5f6956028147fe6637f7ac13802537e895a724f90bf3e36ddd1dd", [:mix], [], "hexpm", "da7785a4b0d2a021cd1292a60875a784b6caef71e76bf4917bdee1f390455cf5"},
|
"castore": {:hex, :castore, "1.0.9", "5cc77474afadf02c7c017823f460a17daa7908e991b0cc917febc90e466a375c", [:mix], [], "hexpm", "5ea956504f1ba6f2b4eb707061d8e17870de2bee95fb59d512872c2ef06925e7"},
|
||||||
"certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"},
|
"certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"},
|
||||||
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
|
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
|
||||||
"comeonin": {:hex, :comeonin, "5.4.0", "246a56ca3f41d404380fc6465650ddaa532c7f98be4bda1b4656b3a37cc13abe", [:mix], [], "hexpm", "796393a9e50d01999d56b7b8420ab0481a7538d0caf80919da493b4a6e51faf1"},
|
"comeonin": {:hex, :comeonin, "5.5.0", "364d00df52545c44a139bad919d7eacb55abf39e86565878e17cebb787977368", [:mix], [], "hexpm", "6287fc3ba0aad34883cbe3f7949fc1d1e738e5ccdce77165bc99490aa69f47fb"},
|
||||||
"concurrent_limiter": {:git, "https://akkoma.dev/AkkomaGang/concurrent-limiter.git", "a9e0b3d64574bdba761f429bb4fba0cf687b3338", [ref: "a9e0b3d64574bdba761f429bb4fba0cf687b3338"]},
|
"concurrent_limiter": {:git, "https://akkoma.dev/AkkomaGang/concurrent-limiter.git", "a9e0b3d64574bdba761f429bb4fba0cf687b3338", [ref: "a9e0b3d64574bdba761f429bb4fba0cf687b3338"]},
|
||||||
"connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
|
"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"},
|
"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.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"},
|
"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"},
|
"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.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"},
|
"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"},
|
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
|
||||||
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
|
"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"},
|
"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"},
|
"earmark": {:hex, :earmark, "1.4.46", "8c7287bd3137e99d26ae4643e5b7ef2129a260e3dcf41f251750cb4563c8fb81", [:mix], [], "hexpm", "798d86db3d79964e759ddc0c077d5eb254968ed426399fbf5a62de2b5ff8910a"},
|
||||||
"earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"},
|
"earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"},
|
||||||
"eblurhash": {:hex, :eblurhash, "1.2.2", "7da4255aaea984b31bb71155f673257353b0e0554d0d30dcf859547e74602582", [:rebar3], [], "hexpm", "8c20ca00904de023a835a9dcb7b7762fed32264c85a80c3cafa85288e405044c"},
|
"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.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_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.0", "440719cd74f09b3f01c84455707a2c3972b725c513808e68eb6c5b0ab82bf523", [:mix], [{:ecto_sql, "~> 3.7", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 0.18.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1 or ~> 4.0.0", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "f1512812dc196bcb932a96c82e55f69b543dc125e9d39f5e3631a9c4ec65ef12"},
|
"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_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_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"]},
|
"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.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"},
|
"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"},
|
"erlsom": {:hex, :erlsom, "1.5.1", "c8fe2babd33ff0846403f6522328b8ab676f896b793634cfe7ef181c05316c03", [:rebar3], [], "hexpm", "7965485494c5844dd127656ac40f141aadfa174839ec1be1074e7edf5b4239eb"},
|
||||||
"eternal": {:hex, :eternal, "1.2.2", "d1641c86368de99375b98d183042dd6c2b234262b8d08dfd72b9eeaafc2a1abd", [:mix], [], "hexpm", "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782"},
|
"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": {: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.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_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_const": {:hex, :ex_const, "0.3.0", "9d79516679991baf540ef445438eef1455ca91cf1a3c2680d8fb9e5bea2fe4de", [:mix], [], "hexpm", "76546322abb9e40ee4a2f454cf1c8a5b25c3672fa79bed1ea52c31e0d2428ca9"},
|
"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_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.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_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"},
|
"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"},
|
"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"},
|
"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.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"},
|
"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_ex": {:git, "https://akkoma.dev/AkkomaGang/file_ex.git", "cc7067c7d446c2526e9ecf91d40896b088851569", [ref: "cc7067c7d446c2526e9ecf91d40896b088851569"]},
|
||||||
"file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"},
|
"file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"},
|
||||||
"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"},
|
"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"},
|
"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.2", "a7da0193538c93f937714a6704369711998a51a6164a222d710ebd54020aa7a3", [:mix], [], "hexpm", "a8766c0bc92f074e5cb36c4f9961982eda84c5d2b8e979ca67f5c268ec8ed580"},
|
"floki": {:hex, :floki, "0.36.3", "1102f93b16a55bc5383b85ae3ec470f82dee056eaeff9195e8afdf0ef2a43c30", [:mix], [], "hexpm", "fe0158bff509e407735f6d40b3ee0d7deb47f3f3ee7c6c182ad28599f9f6b27a"},
|
||||||
"gen_smtp": {:hex, :gen_smtp, "1.2.0", "9cfc75c72a8821588b9b9fe947ae5ab2aed95a052b81237e0928633a13276fd3", [:rebar3], [{:ranch, ">= 1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "5ee0375680bca8f20c4d85f58c2894441443a743355430ff33a783fe03296779"},
|
"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"},
|
"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.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"},
|
||||||
|
@ -61,42 +61,43 @@
|
||||||
"httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"},
|
"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"},
|
"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"},
|
"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"},
|
"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"},
|
"jose": {:hex, :jose, "1.11.10", "a903f5227417bd2a08c8a00a0cbcc458118be84480955e8d251297a425723f83", [:mix, :rebar3], [], "hexpm", "0d6cd36ff8ba174db29148fc112b5842186b68a90ce9fc2b3ec3afe76593e614"},
|
||||||
"jumper": {:hex, :jumper, "1.0.2", "68cdcd84472a00ac596b4e6459a41b3062d4427cbd4f1e8c8793c5b54f1406a7", [:mix], [], "hexpm", "9b7782409021e01ab3c08270e26f36eb62976a38c1aa64b2eaf6348422f165e1"},
|
"jumper": {:hex, :jumper, "1.0.2", "68cdcd84472a00ac596b4e6459a41b3062d4427cbd4f1e8c8793c5b54f1406a7", [:mix], [], "hexpm", "9b7782409021e01ab3c08270e26f36eb62976a38c1aa64b2eaf6348422f165e1"},
|
||||||
"linkify": {:hex, :linkify, "0.5.3", "5f8143d8f61f5ff08d3aeeff47ef6509492b4948d8f08007fbf66e4d2246a7f2", [:mix], [], "hexpm", "3ef35a1377d47c25506e07c1c005ea9d38d700699d92ee92825f024434258177"},
|
"linkify": {:hex, :linkify, "0.5.3", "5f8143d8f61f5ff08d3aeeff47ef6509492b4948d8f08007fbf66e4d2246a7f2", [:mix], [], "hexpm", "3ef35a1377d47c25506e07c1c005ea9d38d700699d92ee92825f024434258177"},
|
||||||
"mail": {:hex, :mail, "0.3.1", "cb0a14e4ed8904e4e5a08214e686ccf6f9099346885db17d8c309381f865cc5c", [:mix], [], "hexpm", "1db701e89865c1d5fa296b2b57b1cd587587cca8d8a1a22892b35ef5a8e352a6"},
|
"mail": {:hex, :mail, "0.4.2", "959229676ef11dfdfb1269ce4a611622cb70415a1d6f925d79e547848bafc14d", [:mix], [], "hexpm", "08e5b70c72b8d1605cb88ef2df2c7e41d002210a621503ea1c13f1a7916b6bd3"},
|
||||||
"majic": {:git, "https://akkoma.dev/AkkomaGang/majic.git", "80540b36939ec83f48e76c61e5000e0fd67706f0", [ref: "80540b36939ec83f48e76c61e5000e0fd67706f0"]},
|
"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": {: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_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"},
|
"meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"},
|
||||||
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
|
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
|
||||||
"mfm_parser": {:git, "https://akkoma.dev/AkkomaGang/mfm-parser.git", "b21ab7754024af096f2d14247574f55f0063295b", [ref: "b21ab7754024af096f2d14247574f55f0063295b"]},
|
"mfm_parser": {:git, "https://akkoma.dev/AkkomaGang/mfm-parser.git", "b21ab7754024af096f2d14247574f55f0063295b", [ref: "b21ab7754024af096f2d14247574f55f0063295b"]},
|
||||||
"mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"},
|
"mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"},
|
||||||
"mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"},
|
"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"},
|
"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.8", "7046a306b71db2488ef54395eeb74df0a7f335a7caca4a3d3875d1fc81c884dd", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "7fa82364c97617d79bb7d15571193fc0c4fe5afd0c932cef09426b3ee6fe2022"},
|
||||||
"mogrify": {:hex, :mogrify, "0.9.3", "238c782f00271dace01369ad35ae2e9dd020feee3443b9299ea5ea6bed559841", [:mix], [], "hexpm", "0189b1e1de27455f2b9ae8cf88239cefd23d38de9276eb5add7159aea51731e6"},
|
"mogrify": {:hex, :mogrify, "0.9.3", "238c782f00271dace01369ad35ae2e9dd020feee3443b9299ea5ea6bed559841", [:mix], [], "hexpm", "0189b1e1de27455f2b9ae8cf88239cefd23d38de9276eb5add7159aea51731e6"},
|
||||||
"mox": {:hex, :mox, "1.1.0", "0f5e399649ce9ab7602f72e718305c0f9cdc351190f72844599545e4996af73c", [:mix], [], "hexpm", "d44474c50be02d5b72131070281a5d3895c0e7a95c780e90bc0cfe712f633a13"},
|
"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_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_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"},
|
||||||
"nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"},
|
"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"},
|
"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.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"},
|
"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"},
|
"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": {: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.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_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_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_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_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_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_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_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"},
|
"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.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"},
|
"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_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_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"},
|
"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"},
|
"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"},
|
"poison": {:hex, :poison, "5.0.0", "d2b54589ab4157bbb82ec2050757779bfed724463a544b6e20d79855a9e43b24", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "11dc6117c501b80c62a7594f941d043982a1bd05a1184280c0d9166eb4d8d3fc"},
|
||||||
|
@ -104,7 +105,7 @@
|
||||||
"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.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"},
|
"pot": {:hex, :pot, "1.0.2", "13abb849139fdc04ab8154986abbcb63bdee5de6ed2ba7e1713527e33df923dd", [:rebar3], [], "hexpm", "78fe127f5a4f5f919d6ea5a2a671827bd53eb9d37e5b4128c0ad3df99856c2e0"},
|
||||||
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
|
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
|
||||||
"recon": {:hex, :recon, "2.5.5", "c108a4c406fa301a529151a3bb53158cadc4064ec0c5f99b03ddb8c0e4281bdf", [:mix, :rebar3], [], "hexpm", "632a6f447df7ccc1a4a10bdcfce71514412b16660fe59deca0fcf0aa3c054404"},
|
"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"},
|
"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"]},
|
"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"},
|
"sleeplocks": {:hex, :sleeplocks, "1.1.3", "96a86460cc33b435c7310dbd27ec82ca2c1f24ae38e34f8edde97f756503441a", [:rebar3], [], "hexpm", "d3b3958552e6eb16f463921e70ae7c767519ef8f5be46d7696cc1ed649421321"},
|
||||||
|
@ -114,22 +115,22 @@
|
||||||
"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"},
|
"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"},
|
"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.0.0", "3c613a68ebdc6d4d1e731bc973c233500974ec3993c99fcdabb210407b90959b", [:mix], [], "hexpm", "c35c4d5612ca49ebb0344ea10387da4d2afe278387d4019e4d8111e815df8f55"},
|
||||||
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
|
"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": {: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": {: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_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"},
|
"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"]},
|
"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.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"},
|
||||||
"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"},
|
"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"},
|
"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"},
|
"tzdata": {:hex, :tzdata, "1.1.2", "45e5f1fcf8729525ec27c65e163be5b3d247ab1702581a94674e008413eef50b", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "cec7b286e608371602318c414f344941d5eb0375e14cfdab605cca2fe66cba8b"},
|
||||||
"ueberauth": {:hex, :ueberauth, "0.10.5", "806adb703df87e55b5615cf365e809f84c20c68aa8c08ff8a416a5a6644c4b02", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3efd1f31d490a125c7ed453b926f7c31d78b97b8a854c755f5c40064bf3ac9e1"},
|
"ueberauth": {:hex, :ueberauth, "0.10.5", "806adb703df87e55b5615cf365e809f84c20c68aa8c08ff8a416a5a6644c4b02", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3efd1f31d490a125c7ed453b926f7c31d78b97b8a854c755f5c40064bf3ac9e1"},
|
||||||
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
|
"unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"},
|
||||||
"unsafe": {:hex, :unsafe, "1.0.2", "23c6be12f6c1605364801f4b47007c0c159497d0446ad378b5cf05f1855c0581", [:mix], [], "hexpm", "b485231683c3ab01a9cd44cb4a79f152c6f3bb87358439c6f68791b85c2df675"},
|
"unsafe": {:hex, :unsafe, "1.0.2", "23c6be12f6c1605364801f4b47007c0c159497d0446ad378b5cf05f1855c0581", [:mix], [], "hexpm", "b485231683c3ab01a9cd44cb4a79f152c6f3bb87358439c6f68791b85c2df675"},
|
||||||
"vex": {:hex, :vex, "0.9.2", "fe061acc9e0907d983d46b51bf35d58176f0fe6eb7ba3b33c9336401bf42b6d1", [:mix], [], "hexpm", "76e709a9762e98c6b462dfce92e9b5dfbf712839227f2da8add6dd11549b12cb"},
|
"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"},
|
"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": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
|
||||||
"websock_adapter": {:hex, :websock_adapter, "0.5.6", "0437fe56e093fd4ac422de33bf8fc89f7bc1416a3f2d732d8b2c8fd54792fe60", [: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", "e04378d26b0af627817ae84c92083b7e97aca3121196679b73c73b99d0d133ea"},
|
"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"},
|
||||||
"websockex": {:hex, :websockex, "0.4.3", "92b7905769c79c6480c02daacaca2ddd49de936d912976a4d3c923723b647bf0", [:mix], [], "hexpm", "95f2e7072b85a3a4cc385602d42115b73ce0b74a9121d0d6dbbf557645ac53e4"},
|
"websockex": {:hex, :websockex, "0.4.3", "92b7905769c79c6480c02daacaca2ddd49de936d912976a4d3c923723b647bf0", [:mix], [], "hexpm", "95f2e7072b85a3a4cc385602d42115b73ce0b74a9121d0d6dbbf557645ac53e4"},
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.CreateSigningKeyTable do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
create table(:signing_keys, primary_key: false) do
|
||||||
|
add :user_id, references(:users, type: :uuid, on_delete: :delete_all)
|
||||||
|
add :key_id, :text, primary_key: true
|
||||||
|
add :public_key, :text
|
||||||
|
add :private_key, :text
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
37
priv/repo/migrations/20240625220752_move_signing_keys.exs
Normal file
37
priv/repo/migrations/20240625220752_move_signing_keys.exs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.MoveSigningKeys do
|
||||||
|
use Ecto.Migration
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Repo
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
def up do
|
||||||
|
# we do not handle remote users here!
|
||||||
|
# because we want to store a key id -> user id mapping, and we don't
|
||||||
|
# currently store key ids for remote users...
|
||||||
|
query =
|
||||||
|
from(u in User)
|
||||||
|
|> where(local: true)
|
||||||
|
|
||||||
|
Repo.stream(query, timeout: :infinity)
|
||||||
|
|> Enum.each(fn
|
||||||
|
%User{id: user_id, keys: private_key, local: true, ap_id: ap_id} ->
|
||||||
|
IO.puts("Migrating user #{user_id}")
|
||||||
|
# we can precompute the public key here...
|
||||||
|
# we do use it on every user view which makes it a bit of a dos attack vector
|
||||||
|
# so we should probably cache it
|
||||||
|
{:ok, public_key} = User.SigningKey.private_pem_to_public_pem(private_key)
|
||||||
|
|
||||||
|
key = %User.SigningKey{
|
||||||
|
user_id: user_id,
|
||||||
|
public_key: public_key,
|
||||||
|
key_id: User.SigningKey.local_key_id(ap_id),
|
||||||
|
private_key: private_key
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, _} = Repo.insert(key)
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
# no need to rollback
|
||||||
|
def down, do: :ok
|
||||||
|
end
|
|
@ -51,7 +51,6 @@ test "error if file with custom settings doesn't exist" do
|
||||||
clear_config(:configurable_from_database, true)
|
clear_config(:configurable_from_database, true)
|
||||||
end
|
end
|
||||||
|
|
||||||
@tag capture_log: true
|
|
||||||
test "config migration refused when deprecated settings are found" do
|
test "config migration refused when deprecated settings are found" do
|
||||||
clear_config([:media_proxy, :whitelist], ["domain_without_scheme.com"])
|
clear_config([:media_proxy, :whitelist], ["domain_without_scheme.com"])
|
||||||
assert config_records() == []
|
assert config_records() == []
|
||||||
|
|
|
@ -401,6 +401,7 @@ test "We don't have unexpected tables which may contain objects that are referen
|
||||||
["rich_media_card"],
|
["rich_media_card"],
|
||||||
["scheduled_activities"],
|
["scheduled_activities"],
|
||||||
["schema_migrations"],
|
["schema_migrations"],
|
||||||
|
["signing_keys"],
|
||||||
["thread_mutes"],
|
["thread_mutes"],
|
||||||
["user_follows_hashtag"],
|
["user_follows_hashtag"],
|
||||||
["user_frontend_setting_profiles"],
|
["user_frontend_setting_profiles"],
|
||||||
|
|
|
@ -64,7 +64,10 @@ test "returns error when zip file is bad", %{pack: pack} do
|
||||||
path: Path.absname("test/instance_static/emoji/test_pack/blank.png")
|
path: Path.absname("test/instance_static/emoji/test_pack/blank.png")
|
||||||
}
|
}
|
||||||
|
|
||||||
assert Pack.add_file(pack, nil, nil, file) == {:error, :einval}
|
# this varies by erlang OTP
|
||||||
|
possible_error_codes = [:bad_eocd, :einval]
|
||||||
|
{:error, code} = Pack.add_file(pack, nil, nil, file)
|
||||||
|
assert Enum.member?(possible_error_codes, code)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "returns pack when zip file is empty", %{pack: pack} do
|
test "returns pack when zip file is empty", %{pack: pack} do
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Pleroma.KeysTest do
|
|
||||||
use Pleroma.DataCase, async: true
|
|
||||||
|
|
||||||
alias Pleroma.Keys
|
|
||||||
|
|
||||||
test "generates an RSA private key pem" do
|
|
||||||
{:ok, key} = Keys.generate_rsa_pem()
|
|
||||||
|
|
||||||
assert is_binary(key)
|
|
||||||
assert Regex.match?(~r/RSA/, key)
|
|
||||||
end
|
|
||||||
|
|
||||||
test "returns a public and private key from a pem" do
|
|
||||||
pem = File.read!("test/fixtures/private_key.pem")
|
|
||||||
{:ok, private, public} = Keys.keys_from_pem(pem)
|
|
||||||
|
|
||||||
assert elem(private, 0) == :RSAPrivateKey
|
|
||||||
assert elem(public, 0) == :RSAPublicKey
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -9,7 +9,6 @@ defmodule Pleroma.Object.ContainmentTest do
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
import ExUnit.CaptureLog
|
|
||||||
|
|
||||||
setup_all do
|
setup_all do
|
||||||
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
||||||
|
@ -136,23 +135,17 @@ test "contain_id_to_fetch() allows matching IDs" do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "contain_id_to_fetch() allows display URLs" do
|
test "contain_id_to_fetch() allows fragments and normalises domain casing" do
|
||||||
data = %{
|
data = %{
|
||||||
"id" => "http://example.com/~alyssa/activities/1234.json",
|
"id" => "http://example.com/users/capybara",
|
||||||
"url" => "http://example.com/@alyssa/status/1234"
|
"url" => "http://example.com/@capybara"
|
||||||
}
|
}
|
||||||
|
|
||||||
:ok =
|
assert :ok ==
|
||||||
Containment.contain_id_to_fetch(
|
Containment.contain_id_to_fetch(
|
||||||
"http://example.com/@alyssa/status/1234",
|
"http://EXAMPLE.com/users/capybara#key",
|
||||||
data
|
data
|
||||||
)
|
)
|
||||||
|
|
||||||
:ok =
|
|
||||||
Containment.contain_id_to_fetch(
|
|
||||||
"http://example.com/@alyssa/status/1234/",
|
|
||||||
data
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "users cannot be collided through fake direction spoofing attempts" do
|
test "users cannot be collided through fake direction spoofing attempts" do
|
||||||
|
@ -164,10 +157,14 @@ test "users cannot be collided through fake direction spoofing attempts" do
|
||||||
follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})
|
follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"})
|
||||||
})
|
})
|
||||||
|
|
||||||
assert capture_log(fn ->
|
# Fetch from an attempted spoof id will suceed, but automatically retrieve
|
||||||
{:error, _} = User.get_or_fetch_by_ap_id("https://n1u.moe/users/rye")
|
# the real data from the homeserver instead of naïvely using the spoof
|
||||||
end) =~
|
{:ok, fetched_user} = User.get_or_fetch_by_ap_id("https://n1u.moe/users/rye")
|
||||||
"[error] Could not decode user at fetch https://n1u.moe/users/rye"
|
|
||||||
|
refute fetched_user.name == "evil rye"
|
||||||
|
refute fetched_user.raw_bio == "boooo!"
|
||||||
|
assert fetched_user.name == "♡ rye ♡"
|
||||||
|
assert fetched_user.nickname == "rye@niu.moe"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "contain_origin_from_id() gracefully handles cases where no ID is present" do
|
test "contain_origin_from_id() gracefully handles cases where no ID is present" do
|
||||||
|
|
|
@ -22,6 +22,7 @@ defp spoofed_object_with_ids(
|
||||||
|> Jason.decode!()
|
|> Jason.decode!()
|
||||||
|> Map.put("id", id)
|
|> Map.put("id", id)
|
||||||
|> Map.put("actor", actor_id)
|
|> Map.put("actor", actor_id)
|
||||||
|
|> Map.put("attributedTo", actor_id)
|
||||||
|> Jason.encode!()
|
|> Jason.encode!()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -109,7 +110,7 @@ defp spoofed_object_with_ids(
|
||||||
body: spoofed_object_with_ids("https://patch.cx/objects/spoof_media_redirect1")
|
body: spoofed_object_with_ids("https://patch.cx/objects/spoof_media_redirect1")
|
||||||
}
|
}
|
||||||
|
|
||||||
# Spoof: cross-domain redirect with final domain id
|
# Spoof: cross-domain redirect with final domain id, but original id actor
|
||||||
%{method: :get, url: "https://patch.cx/objects/spoof_media_redirect2"} ->
|
%{method: :get, url: "https://patch.cx/objects/spoof_media_redirect2"} ->
|
||||||
%Tesla.Env{
|
%Tesla.Env{
|
||||||
status: 200,
|
status: 200,
|
||||||
|
@ -118,6 +119,19 @@ defp spoofed_object_with_ids(
|
||||||
body: spoofed_object_with_ids("https://media.patch.cx/objects/spoof_media_redirect2")
|
body: spoofed_object_with_ids("https://media.patch.cx/objects/spoof_media_redirect2")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# No-Spoof: cross-domain redirect with id and actor from final domain
|
||||||
|
%{method: :get, url: "https://patch.cx/objects/spoof_media_redirect3"} ->
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
url: "https://media.patch.cx/objects/spoof_media_redirect3",
|
||||||
|
headers: [{"content-type", "application/activity+json"}],
|
||||||
|
body:
|
||||||
|
spoofed_object_with_ids(
|
||||||
|
"https://media.patch.cx/objects/spoof_media_redirect3",
|
||||||
|
"https://media.patch.cx/users/rin"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
# No-Spoof: same domain redirect
|
# No-Spoof: same domain redirect
|
||||||
%{method: :get, url: "https://patch.cx/objects/spoof_redirect"} ->
|
%{method: :get, url: "https://patch.cx/objects/spoof_redirect"} ->
|
||||||
%Tesla.Env{
|
%Tesla.Env{
|
||||||
|
@ -189,7 +203,6 @@ defp spoofed_object_with_ids(
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
@tag capture_log: true
|
|
||||||
test "it works when fetching the OP actor errors out" do
|
test "it works when fetching the OP actor errors out" do
|
||||||
# Here we simulate a case where the author of the OP can't be read
|
# Here we simulate a case where the author of the OP can't be read
|
||||||
assert {:ok, _} =
|
assert {:ok, _} =
|
||||||
|
@ -252,7 +265,7 @@ test "it does not fetch a spoofed object with wrong content type" do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it does not fetch a spoofed object with id different from URL" do
|
test "it does not fetch a spoofed object with id different from URL" do
|
||||||
assert {:error, :id_mismatch} =
|
assert {:error, :not_found} =
|
||||||
Fetcher.fetch_and_contain_remote_object_from_id(
|
Fetcher.fetch_and_contain_remote_object_from_id(
|
||||||
"https://patch.cx/media/03ca3c8b4ac3ddd08bf0f84be7885f2f88de0f709112131a22d83650819e36c2.json"
|
"https://patch.cx/media/03ca3c8b4ac3ddd08bf0f84be7885f2f88de0f709112131a22d83650819e36c2.json"
|
||||||
)
|
)
|
||||||
|
@ -264,19 +277,29 @@ test "it does not fetch a spoofed object with id different from URL" do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it does not fetch an object via cross-domain redirects (initial id)" do
|
test "it does not fetch an object via cross-domain redirects (initial id)" do
|
||||||
assert {:error, {:cross_domain_redirect, true}} =
|
assert {:error, {:containment, _}} =
|
||||||
Fetcher.fetch_and_contain_remote_object_from_id(
|
Fetcher.fetch_and_contain_remote_object_from_id(
|
||||||
"https://patch.cx/objects/spoof_media_redirect1"
|
"https://patch.cx/objects/spoof_media_redirect1"
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it does not fetch an object via cross-domain redirects (final id)" do
|
test "it does not fetch an object via cross-domain redirect if the actor is from the original domain" do
|
||||||
assert {:error, {:cross_domain_redirect, true}} =
|
assert {:error, {:containment, :error}} =
|
||||||
Fetcher.fetch_and_contain_remote_object_from_id(
|
Fetcher.fetch_and_contain_remote_object_from_id(
|
||||||
"https://patch.cx/objects/spoof_media_redirect2"
|
"https://patch.cx/objects/spoof_media_redirect2"
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it allows cross-domain redirects when id and author are from final domain" do
|
||||||
|
assert {:ok, %{"id" => id, "attributedTo" => author}} =
|
||||||
|
Fetcher.fetch_and_contain_remote_object_from_id(
|
||||||
|
"https://patch.cx/objects/spoof_media_redirect3"
|
||||||
|
)
|
||||||
|
|
||||||
|
assert URI.parse(id).host == "media.patch.cx"
|
||||||
|
assert URI.parse(author).host == "media.patch.cx"
|
||||||
|
end
|
||||||
|
|
||||||
test "it accepts same-domain redirects" do
|
test "it accepts same-domain redirects" do
|
||||||
assert {:ok, %{"id" => id} = _object} =
|
assert {:ok, %{"id" => id} = _object} =
|
||||||
Fetcher.fetch_and_contain_remote_object_from_id(
|
Fetcher.fetch_and_contain_remote_object_from_id(
|
||||||
|
@ -755,7 +778,7 @@ test "should return ok if the content type is application/activity+json" do
|
||||||
assert {:ok, _, "{}"} = Fetcher.get_object("https://mastodon.social/2")
|
assert {:ok, _, "{}"} = Fetcher.get_object("https://mastodon.social/2")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "should return ok if the content type is application/ld+json with a profile" do
|
test "should return ok if the content type is application/ld+json with the ActivityStream profile" do
|
||||||
Tesla.Mock.mock(fn
|
Tesla.Mock.mock(fn
|
||||||
%{
|
%{
|
||||||
method: :get,
|
method: :get,
|
||||||
|
@ -775,6 +798,26 @@ test "should return ok if the content type is application/ld+json with a profile
|
||||||
assert {:ok, _, "{}"} = Fetcher.get_object("https://mastodon.social/2")
|
assert {:ok, _, "{}"} = Fetcher.get_object("https://mastodon.social/2")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "should return ok if the content type is application/ld+json with several profiles" do
|
||||||
|
Tesla.Mock.mock(fn
|
||||||
|
%{
|
||||||
|
method: :get,
|
||||||
|
url: "https://mastodon.social/2"
|
||||||
|
} ->
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
url: "https://mastodon.social/2",
|
||||||
|
headers: [
|
||||||
|
{"content-type",
|
||||||
|
"application/ld+json; profile=\"https://example.org/ns/superduperspec https://www.w3.org/ns/activitystreams\""}
|
||||||
|
],
|
||||||
|
body: "{}"
|
||||||
|
}
|
||||||
|
end)
|
||||||
|
|
||||||
|
assert {:ok, _, "{}"} = Fetcher.get_object("https://mastodon.social/2")
|
||||||
|
end
|
||||||
|
|
||||||
test "should not return ok with other content types" do
|
test "should not return ok with other content types" do
|
||||||
Tesla.Mock.mock(fn
|
Tesla.Mock.mock(fn
|
||||||
%{
|
%{
|
||||||
|
|
|
@ -6,7 +6,6 @@ defmodule Pleroma.SignatureTest do
|
||||||
use Pleroma.DataCase, async: false
|
use Pleroma.DataCase, async: false
|
||||||
@moduletag :mocked
|
@moduletag :mocked
|
||||||
|
|
||||||
import ExUnit.CaptureLog
|
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
import Tesla.Mock
|
import Tesla.Mock
|
||||||
import Mock
|
import Mock
|
||||||
|
@ -35,25 +34,23 @@ defp make_fake_conn(key_id),
|
||||||
do: %Plug.Conn{req_headers: %{"signature" => make_fake_signature(key_id <> "#main-key")}}
|
do: %Plug.Conn{req_headers: %{"signature" => make_fake_signature(key_id <> "#main-key")}}
|
||||||
|
|
||||||
describe "fetch_public_key/1" do
|
describe "fetch_public_key/1" do
|
||||||
test "it returns key" do
|
test "it returns the key" do
|
||||||
expected_result = {:ok, @rsa_public_key}
|
expected_result = {:ok, @rsa_public_key}
|
||||||
|
|
||||||
user = insert(:user, public_key: @public_key)
|
user =
|
||||||
|
insert(:user)
|
||||||
|
|> with_signing_key(public_key: @public_key)
|
||||||
|
|
||||||
assert Signature.fetch_public_key(make_fake_conn(user.ap_id)) == expected_result
|
assert Signature.fetch_public_key(make_fake_conn(user.ap_id)) == expected_result
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it returns error when not found user" do
|
|
||||||
assert capture_log(fn ->
|
|
||||||
assert Signature.fetch_public_key(make_fake_conn("https://test-ap-id")) ==
|
|
||||||
{:error, :error}
|
|
||||||
end) =~ "[error] Could not decode user"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it returns error if public key is nil" do
|
test "it returns error if public key is nil" do
|
||||||
user = insert(:user, public_key: nil)
|
# this actually needs the URL to be valid
|
||||||
|
user = insert(:user)
|
||||||
|
key_id = user.ap_id <> "#main-key"
|
||||||
|
Tesla.Mock.mock(fn %{url: ^key_id} -> {:ok, %{status: 404}} end)
|
||||||
|
|
||||||
assert Signature.fetch_public_key(make_fake_conn(user.ap_id)) == {:error, :error}
|
assert {:error, _} = Signature.fetch_public_key(make_fake_conn(user.ap_id))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -63,12 +60,6 @@ test "it returns key" do
|
||||||
|
|
||||||
assert Signature.refetch_public_key(make_fake_conn(ap_id)) == {:ok, @rsa_public_key}
|
assert Signature.refetch_public_key(make_fake_conn(ap_id)) == {:ok, @rsa_public_key}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it returns error when not found user" do
|
|
||||||
assert capture_log(fn ->
|
|
||||||
{:error, _} = Signature.refetch_public_key(make_fake_conn("https://test-ap_id"))
|
|
||||||
end) =~ "[error] Could not decode user"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp split_signature(sig) do
|
defp split_signature(sig) do
|
||||||
|
@ -104,9 +95,9 @@ defp assert_part_equal(part_a, part_b) do
|
||||||
test "it returns signature headers" do
|
test "it returns signature headers" do
|
||||||
user =
|
user =
|
||||||
insert(:user, %{
|
insert(:user, %{
|
||||||
ap_id: "https://mastodon.social/users/lambadalambda",
|
ap_id: "https://mastodon.social/users/lambadalambda"
|
||||||
keys: @private_key
|
|
||||||
})
|
})
|
||||||
|
|> with_signing_key(private_key: @private_key)
|
||||||
|
|
||||||
headers = %{
|
headers = %{
|
||||||
host: "test.test",
|
host: "test.test",
|
||||||
|
@ -121,50 +112,15 @@ test "it returns signature headers" do
|
||||||
"keyId=\"https://mastodon.social/users/lambadalambda#main-key\",algorithm=\"rsa-sha256\",headers=\"content-length host\",signature=\"sibUOoqsFfTDerquAkyprxzDjmJm6erYc42W5w1IyyxusWngSinq5ILTjaBxFvfarvc7ci1xAi+5gkBwtshRMWm7S+Uqix24Yg5EYafXRun9P25XVnYBEIH4XQ+wlnnzNIXQkU3PU9e6D8aajDZVp3hPJNeYt1gIPOA81bROI8/glzb1SAwQVGRbqUHHHKcwR8keiR/W2h7BwG3pVRy4JgnIZRSW7fQogKedDg02gzRXwUDFDk0pr2p3q6bUWHUXNV8cZIzlMK+v9NlyFbVYBTHctAR26GIAN6Hz0eV0mAQAePHDY1mXppbA8Gpp6hqaMuYfwifcXmcc+QFm4e+n3A==\""
|
"keyId=\"https://mastodon.social/users/lambadalambda#main-key\",algorithm=\"rsa-sha256\",headers=\"content-length host\",signature=\"sibUOoqsFfTDerquAkyprxzDjmJm6erYc42W5w1IyyxusWngSinq5ILTjaBxFvfarvc7ci1xAi+5gkBwtshRMWm7S+Uqix24Yg5EYafXRun9P25XVnYBEIH4XQ+wlnnzNIXQkU3PU9e6D8aajDZVp3hPJNeYt1gIPOA81bROI8/glzb1SAwQVGRbqUHHHKcwR8keiR/W2h7BwG3pVRy4JgnIZRSW7fQogKedDg02gzRXwUDFDk0pr2p3q6bUWHUXNV8cZIzlMK+v9NlyFbVYBTHctAR26GIAN6Hz0eV0mAQAePHDY1mXppbA8Gpp6hqaMuYfwifcXmcc+QFm4e+n3A==\""
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it returns error" do
|
|
||||||
user = insert(:user, %{ap_id: "https://mastodon.social/users/lambadalambda", keys: ""})
|
|
||||||
|
|
||||||
assert Signature.sign(
|
|
||||||
user,
|
|
||||||
%{host: "test.test", "content-length": "100"}
|
|
||||||
) == {:error, []}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "key_id_to_actor_id/1" do
|
describe "key_id_to_actor_id/1" do
|
||||||
test "it properly deduces the actor id for misskey" do
|
test "it reverses the key id to actor id" do
|
||||||
assert Signature.key_id_to_actor_id("https://example.com/users/1234/publickey") ==
|
user =
|
||||||
{:ok, "https://example.com/users/1234"}
|
insert(:user)
|
||||||
end
|
|> with_signing_key()
|
||||||
|
|
||||||
test "it properly deduces the actor id for mastodon and pleroma" do
|
assert Signature.key_id_to_actor_id(user.signing_key.key_id) == {:ok, user.ap_id}
|
||||||
assert Signature.key_id_to_actor_id("https://example.com/users/1234#main-key") ==
|
|
||||||
{:ok, "https://example.com/users/1234"}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it deduces the actor id for gotoSocial" do
|
|
||||||
assert Signature.key_id_to_actor_id("https://example.com/users/1234/main-key") ==
|
|
||||||
{:ok, "https://example.com/users/1234"}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it deduces the actor ID for streams" do
|
|
||||||
assert Signature.key_id_to_actor_id("https://example.com/users/1234?operation=getkey") ==
|
|
||||||
{:ok, "https://example.com/users/1234"}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it deduces the actor ID for bridgy" do
|
|
||||||
assert Signature.key_id_to_actor_id("https://example.com/1234#key") ==
|
|
||||||
{:ok, "https://example.com/1234"}
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it calls webfinger for 'acct:' accounts" do
|
|
||||||
with_mock(Pleroma.Web.WebFinger,
|
|
||||||
finger: fn _ -> {:ok, %{"ap_id" => "https://gensokyo.2hu/users/raymoo"}} end
|
|
||||||
) do
|
|
||||||
assert Signature.key_id_to_actor_id("acct:raymoo@gensokyo.2hu") ==
|
|
||||||
{:ok, "https://gensokyo.2hu/users/raymoo"}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -115,6 +115,15 @@ test "verify webp files are skipped" do
|
||||||
assert Filter.Exiftool.StripMetadata.filter(upload) == {:ok, :noop}
|
assert Filter.Exiftool.StripMetadata.filter(upload) == {:ok, :noop}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "verify bmp files are skipped" do
|
||||||
|
upload = %Pleroma.Upload{
|
||||||
|
name: "sample.bmp",
|
||||||
|
content_type: "image/bmp"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert Filter.Exiftool.StripMetadata.filter(upload) == {:ok, :noop}
|
||||||
|
end
|
||||||
|
|
||||||
test "verify svg files are skipped" do
|
test "verify svg files are skipped" do
|
||||||
upload = %Pleroma.Upload{
|
upload = %Pleroma.Upload{
|
||||||
name: "sample.svg",
|
name: "sample.svg",
|
||||||
|
|
|
@ -259,7 +259,7 @@ test "works with URIs" do
|
||||||
|> Map.put(:multi_factor_authentication_settings, nil)
|
|> Map.put(:multi_factor_authentication_settings, nil)
|
||||||
|> Map.put(:notification_settings, nil)
|
|> Map.put(:notification_settings, nil)
|
||||||
|
|
||||||
assert user == expected
|
assert_user_match(user, expected)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "excludes a blocked users from search result" do
|
test "excludes a blocked users from search result" do
|
||||||
|
|
|
@ -639,11 +639,12 @@ test "it sets the password_hash, ap_id, private key and followers collection add
|
||||||
changeset = User.register_changeset(%User{}, @full_user_data)
|
changeset = User.register_changeset(%User{}, @full_user_data)
|
||||||
|
|
||||||
assert changeset.valid?
|
assert changeset.valid?
|
||||||
|
|
||||||
assert is_binary(changeset.changes[:password_hash])
|
assert is_binary(changeset.changes[:password_hash])
|
||||||
assert is_binary(changeset.changes[:keys])
|
|
||||||
assert changeset.changes[:ap_id] == User.ap_id(%User{nickname: @full_user_data.nickname})
|
assert changeset.changes[:ap_id] == User.ap_id(%User{nickname: @full_user_data.nickname})
|
||||||
assert is_binary(changeset.changes[:keys])
|
assert changeset.changes[:signing_key]
|
||||||
|
assert changeset.changes[:signing_key].valid?
|
||||||
|
assert is_binary(changeset.changes[:signing_key].changes.private_key)
|
||||||
|
assert is_binary(changeset.changes[:signing_key].changes.public_key)
|
||||||
assert changeset.changes.follower_address == "#{changeset.changes.ap_id}/followers"
|
assert changeset.changes.follower_address == "#{changeset.changes.ap_id}/followers"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -814,7 +815,6 @@ test "gets an existing user by fully qualified nickname, case insensitive" do
|
||||||
assert user == fetched_user
|
assert user == fetched_user
|
||||||
end
|
end
|
||||||
|
|
||||||
@tag capture_log: true
|
|
||||||
test "returns nil if no user could be fetched" do
|
test "returns nil if no user could be fetched" do
|
||||||
{:error, fetched_user} = User.get_or_fetch_by_nickname("nonexistant@social.heldscal.la")
|
{:error, fetched_user} = User.get_or_fetch_by_nickname("nonexistant@social.heldscal.la")
|
||||||
assert fetched_user == "not found nonexistant@social.heldscal.la"
|
assert fetched_user == "not found nonexistant@social.heldscal.la"
|
||||||
|
@ -871,7 +871,6 @@ test "if nicknames clash, the old user gets a prefix with the old id to the nick
|
||||||
assert orig_user.nickname == "#{orig_user.id}.admin@mastodon.example.org"
|
assert orig_user.nickname == "#{orig_user.id}.admin@mastodon.example.org"
|
||||||
end
|
end
|
||||||
|
|
||||||
@tag capture_log: true
|
|
||||||
test "it returns the old user if stale, but unfetchable" do
|
test "it returns the old user if stale, but unfetchable" do
|
||||||
a_week_ago = NaiveDateTime.add(NaiveDateTime.utc_now(), -604_800)
|
a_week_ago = NaiveDateTime.add(NaiveDateTime.utc_now(), -604_800)
|
||||||
|
|
||||||
|
@ -1149,6 +1148,18 @@ test "it blocks people" do
|
||||||
assert User.blocks?(user, blocked_user)
|
assert User.blocks?(user, blocked_user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it blocks domains" do
|
||||||
|
user = insert(:user)
|
||||||
|
blocked_user = insert(:user)
|
||||||
|
|
||||||
|
refute User.blocks_domain?(user, blocked_user)
|
||||||
|
|
||||||
|
url = URI.parse(blocked_user.ap_id)
|
||||||
|
{:ok, user} = User.block_domain(user, url.host)
|
||||||
|
|
||||||
|
assert User.blocks_domain?(user, blocked_user)
|
||||||
|
end
|
||||||
|
|
||||||
test "it unblocks users" do
|
test "it unblocks users" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
blocked_user = insert(:user)
|
blocked_user = insert(:user)
|
||||||
|
@ -1159,6 +1170,17 @@ test "it unblocks users" do
|
||||||
refute User.blocks?(user, blocked_user)
|
refute User.blocks?(user, blocked_user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it unblocks domains" do
|
||||||
|
user = insert(:user)
|
||||||
|
blocked_user = insert(:user)
|
||||||
|
|
||||||
|
url = URI.parse(blocked_user.ap_id)
|
||||||
|
{:ok, user} = User.block_domain(user, url.host)
|
||||||
|
{:ok, user} = User.unblock_domain(user, url.host)
|
||||||
|
|
||||||
|
refute User.blocks_domain?(user, blocked_user)
|
||||||
|
end
|
||||||
|
|
||||||
test "blocks tear down cyclical follow relationships" do
|
test "blocks tear down cyclical follow relationships" do
|
||||||
blocker = insert(:user)
|
blocker = insert(:user)
|
||||||
blocked = insert(:user)
|
blocked = insert(:user)
|
||||||
|
@ -1642,7 +1664,6 @@ test "delete/1 purges a user when they wouldn't be fully deleted" do
|
||||||
name: "qqqqqqq",
|
name: "qqqqqqq",
|
||||||
password_hash: "pdfk2$1b3n159001",
|
password_hash: "pdfk2$1b3n159001",
|
||||||
keys: "RSA begin buplic key",
|
keys: "RSA begin buplic key",
|
||||||
public_key: "--PRIVATE KEYE--",
|
|
||||||
avatar: %{"a" => "b"},
|
avatar: %{"a" => "b"},
|
||||||
tags: ["qqqqq"],
|
tags: ["qqqqq"],
|
||||||
banner: %{"a" => "b"},
|
banner: %{"a" => "b"},
|
||||||
|
@ -1681,8 +1702,6 @@ test "delete/1 purges a user when they wouldn't be fully deleted" do
|
||||||
email: nil,
|
email: nil,
|
||||||
name: nil,
|
name: nil,
|
||||||
password_hash: nil,
|
password_hash: nil,
|
||||||
keys: "RSA begin buplic key",
|
|
||||||
public_key: "--PRIVATE KEYE--",
|
|
||||||
avatar: %{},
|
avatar: %{},
|
||||||
tags: [],
|
tags: [],
|
||||||
last_refreshed_at: nil,
|
last_refreshed_at: nil,
|
||||||
|
|
|
@ -573,7 +573,6 @@ test "it inserts an incoming activity into the database", %{conn: conn} do
|
||||||
assert Activity.get_by_ap_id(data["id"])
|
assert Activity.get_by_ap_id(data["id"])
|
||||||
end
|
end
|
||||||
|
|
||||||
@tag capture_log: true
|
|
||||||
test "it inserts an incoming activity into the database" <>
|
test "it inserts an incoming activity into the database" <>
|
||||||
"even if we can't fetch the user but have it in our db",
|
"even if we can't fetch the user but have it in our db",
|
||||||
%{conn: conn} do
|
%{conn: conn} do
|
||||||
|
@ -584,6 +583,7 @@ test "it inserts an incoming activity into the database" <>
|
||||||
local: false,
|
local: false,
|
||||||
last_refreshed_at: nil
|
last_refreshed_at: nil
|
||||||
)
|
)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
data =
|
data =
|
||||||
File.read!("test/fixtures/mastodon-post-activity.json")
|
File.read!("test/fixtures/mastodon-post-activity.json")
|
||||||
|
@ -594,7 +594,7 @@ test "it inserts an incoming activity into the database" <>
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|> assign(:valid_signature, true)
|
|> assign(:valid_signature, true)
|
||||||
|> put_req_header("signature", "keyId=\"#{user.ap_id}/main-key\"")
|
|> put_req_header("signature", "keyId=\"#{user.signing_key.key_id}\"")
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|> put_req_header("content-type", "application/activity+json")
|
||||||
|> post("/inbox", data)
|
|> post("/inbox", data)
|
||||||
|
|
||||||
|
@ -608,7 +608,10 @@ test "it clears `unreachable` federation status of the sender", %{conn: conn} do
|
||||||
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
|
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
|
||||||
|
|
||||||
sender_url = data["actor"]
|
sender_url = data["actor"]
|
||||||
sender = insert(:user, ap_id: data["actor"])
|
|
||||||
|
sender =
|
||||||
|
insert(:user, ap_id: data["actor"])
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
Instances.set_consistently_unreachable(sender_url)
|
Instances.set_consistently_unreachable(sender_url)
|
||||||
refute Instances.reachable?(sender_url)
|
refute Instances.reachable?(sender_url)
|
||||||
|
@ -616,7 +619,7 @@ test "it clears `unreachable` federation status of the sender", %{conn: conn} do
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|> assign(:valid_signature, true)
|
|> assign(:valid_signature, true)
|
||||||
|> put_req_header("signature", "keyId=\"#{sender.ap_id}/main-key\"")
|
|> put_req_header("signature", "keyId=\"#{sender.signing_key.key_id}\"")
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|> put_req_header("content-type", "application/activity+json")
|
||||||
|> post("/inbox", data)
|
|> post("/inbox", data)
|
||||||
|
|
||||||
|
@ -641,7 +644,7 @@ test "accept follow activity", %{conn: conn} do
|
||||||
assert "ok" ==
|
assert "ok" ==
|
||||||
conn
|
conn
|
||||||
|> assign(:valid_signature, true)
|
|> assign(:valid_signature, true)
|
||||||
|> put_req_header("signature", "keyId=\"#{followed_relay.ap_id}/main-key\"")
|
|> put_req_header("signature", "keyId=\"#{followed_relay.ap_id}#main-key\"")
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|> put_req_header("content-type", "application/activity+json")
|
||||||
|> post("/inbox", accept)
|
|> post("/inbox", accept)
|
||||||
|> json_response(200)
|
|> json_response(200)
|
||||||
|
@ -678,6 +681,7 @@ test "accepts Add/Remove activities", %{conn: conn} do
|
||||||
|> String.replace("{{nickname}}", "lain")
|
|> String.replace("{{nickname}}", "lain")
|
||||||
|
|
||||||
actor = "https://example.com/users/lain"
|
actor = "https://example.com/users/lain"
|
||||||
|
key_id = "#{actor}/main-key"
|
||||||
|
|
||||||
insert(:user,
|
insert(:user,
|
||||||
ap_id: actor,
|
ap_id: actor,
|
||||||
|
@ -705,6 +709,16 @@ test "accepts Add/Remove activities", %{conn: conn} do
|
||||||
headers: [{"content-type", "application/activity+json"}]
|
headers: [{"content-type", "application/activity+json"}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
%{
|
||||||
|
method: :get,
|
||||||
|
url: ^key_id
|
||||||
|
} ->
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: user,
|
||||||
|
headers: [{"content-type", "application/activity+json"}]
|
||||||
|
}
|
||||||
|
|
||||||
%{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
|
%{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
|
||||||
%Tesla.Env{
|
%Tesla.Env{
|
||||||
status: 200,
|
status: 200,
|
||||||
|
@ -778,12 +792,14 @@ test "mastodon pin/unpin", %{conn: conn} do
|
||||||
|> String.replace("{{nickname}}", "lain")
|
|> String.replace("{{nickname}}", "lain")
|
||||||
|
|
||||||
actor = "https://example.com/users/lain"
|
actor = "https://example.com/users/lain"
|
||||||
|
key_id = "#{actor}/main-key"
|
||||||
|
|
||||||
sender =
|
sender =
|
||||||
insert(:user,
|
insert(:user,
|
||||||
ap_id: actor,
|
ap_id: actor,
|
||||||
featured_address: "https://example.com/users/lain/collections/featured"
|
featured_address: "https://example.com/users/lain/collections/featured"
|
||||||
)
|
)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
Tesla.Mock.mock(fn
|
Tesla.Mock.mock(fn
|
||||||
%{
|
%{
|
||||||
|
@ -806,6 +822,16 @@ test "mastodon pin/unpin", %{conn: conn} do
|
||||||
headers: [{"content-type", "application/activity+json"}]
|
headers: [{"content-type", "application/activity+json"}]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
%{
|
||||||
|
method: :get,
|
||||||
|
url: ^key_id
|
||||||
|
} ->
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: user,
|
||||||
|
headers: [{"content-type", "application/activity+json"}]
|
||||||
|
}
|
||||||
|
|
||||||
%{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
|
%{method: :get, url: "https://example.com/users/lain/collections/featured"} ->
|
||||||
%Tesla.Env{
|
%Tesla.Env{
|
||||||
status: 200,
|
status: 200,
|
||||||
|
@ -839,7 +865,7 @@ test "mastodon pin/unpin", %{conn: conn} do
|
||||||
assert "ok" ==
|
assert "ok" ==
|
||||||
conn
|
conn
|
||||||
|> assign(:valid_signature, true)
|
|> assign(:valid_signature, true)
|
||||||
|> put_req_header("signature", "keyId=\"#{sender.ap_id}/main-key\"")
|
|> put_req_header("signature", "keyId=\"#{sender.signing_key.key_id}\"")
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|> put_req_header("content-type", "application/activity+json")
|
||||||
|> post("/inbox", data)
|
|> post("/inbox", data)
|
||||||
|> json_response(200)
|
|> json_response(200)
|
||||||
|
@ -901,7 +927,9 @@ test "it inserts an incoming activity into the database", %{conn: conn, data: da
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it accepts messages with to as string instead of array", %{conn: conn, data: data} do
|
test "it accepts messages with to as string instead of array", %{conn: conn, data: data} do
|
||||||
user = insert(:user)
|
user =
|
||||||
|
insert(:user)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
data =
|
data =
|
||||||
data
|
data
|
||||||
|
@ -946,7 +974,9 @@ test "it accepts messages with cc as string instead of array", %{conn: conn, dat
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it accepts messages with bcc as string instead of array", %{conn: conn, data: data} do
|
test "it accepts messages with bcc as string instead of array", %{conn: conn, data: data} do
|
||||||
user = insert(:user)
|
user =
|
||||||
|
insert(:user)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
data =
|
data =
|
||||||
data
|
data
|
||||||
|
@ -973,7 +1003,10 @@ test "it accepts announces with to as string instead of array", %{conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
{:ok, post} = CommonAPI.post(user, %{status: "hey"})
|
{:ok, post} = CommonAPI.post(user, %{status: "hey"})
|
||||||
announcer = insert(:user, local: false)
|
|
||||||
|
announcer =
|
||||||
|
insert(:user, local: false)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
data = %{
|
data = %{
|
||||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||||
|
@ -988,7 +1021,7 @@ test "it accepts announces with to as string instead of array", %{conn: conn} do
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|> assign(:valid_signature, true)
|
|> assign(:valid_signature, true)
|
||||||
|> put_req_header("signature", "keyId=\"#{announcer.ap_id}/main-key\"")
|
|> put_req_header("signature", "keyId=\"#{announcer.signing_key.key_id}\"")
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|> put_req_header("content-type", "application/activity+json")
|
||||||
|> post("/users/#{user.nickname}/inbox", data)
|
|> post("/users/#{user.nickname}/inbox", data)
|
||||||
|
|
||||||
|
@ -1003,7 +1036,10 @@ test "it accepts messages from actors that are followed by the user", %{
|
||||||
data: data
|
data: data
|
||||||
} do
|
} do
|
||||||
recipient = insert(:user)
|
recipient = insert(:user)
|
||||||
actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
|
|
||||||
|
actor =
|
||||||
|
insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"})
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
{:ok, recipient, actor} = User.follow(recipient, actor)
|
{:ok, recipient, actor} = User.follow(recipient, actor)
|
||||||
|
|
||||||
|
@ -1019,7 +1055,7 @@ test "it accepts messages from actors that are followed by the user", %{
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|> assign(:valid_signature, true)
|
|> assign(:valid_signature, true)
|
||||||
|> put_req_header("signature", "keyId=\"#{actor.ap_id}/main-key\"")
|
|> put_req_header("signature", "keyId=\"#{actor.signing_key.key_id}\"")
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|> put_req_header("content-type", "application/activity+json")
|
||||||
|> post("/users/#{recipient.nickname}/inbox", data)
|
|> post("/users/#{recipient.nickname}/inbox", data)
|
||||||
|
|
||||||
|
@ -1056,7 +1092,10 @@ test "it returns a note activity in a collection", %{conn: conn} do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
|
test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do
|
||||||
user = insert(:user)
|
user =
|
||||||
|
insert(:user, ap_id: data["actor"])
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
data = Map.put(data, "bcc", [user.ap_id])
|
data = Map.put(data, "bcc", [user.ap_id])
|
||||||
|
|
||||||
sender_host = URI.parse(data["actor"]).host
|
sender_host = URI.parse(data["actor"]).host
|
||||||
|
@ -1066,7 +1105,7 @@ test "it clears `unreachable` federation status of the sender", %{conn: conn, da
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|> assign(:valid_signature, true)
|
|> assign(:valid_signature, true)
|
||||||
|> put_req_header("signature", "keyId=\"#{data["actor"]}/main-key\"")
|
|> put_req_header("signature", "keyId=\"#{user.signing_key.key_id}\"")
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|> put_req_header("content-type", "application/activity+json")
|
||||||
|> post("/users/#{user.nickname}/inbox", data)
|
|> post("/users/#{user.nickname}/inbox", data)
|
||||||
|
|
||||||
|
@ -1074,9 +1113,9 @@ test "it clears `unreachable` federation status of the sender", %{conn: conn, da
|
||||||
assert Instances.reachable?(sender_host)
|
assert Instances.reachable?(sender_host)
|
||||||
end
|
end
|
||||||
|
|
||||||
@tag capture_log: true
|
|
||||||
test "it removes all follower collections but actor's", %{conn: conn} do
|
test "it removes all follower collections but actor's", %{conn: conn} do
|
||||||
[actor, recipient] = insert_pair(:user)
|
[actor, recipient] = insert_pair(:user)
|
||||||
|
actor = with_signing_key(actor)
|
||||||
|
|
||||||
to = [
|
to = [
|
||||||
recipient.ap_id,
|
recipient.ap_id,
|
||||||
|
@ -1105,7 +1144,7 @@ test "it removes all follower collections but actor's", %{conn: conn} do
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> assign(:valid_signature, true)
|
|> assign(:valid_signature, true)
|
||||||
|> put_req_header("signature", "keyId=\"#{actor.ap_id}/main-key\"")
|
|> put_req_header("signature", "keyId=\"#{actor.signing_key.key_id}\"")
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|> put_req_header("content-type", "application/activity+json")
|
||||||
|> post("/users/#{recipient.nickname}/inbox", data)
|
|> post("/users/#{recipient.nickname}/inbox", data)
|
||||||
|> json_response(200)
|
|> json_response(200)
|
||||||
|
@ -1138,10 +1177,13 @@ test "it requires authentication", %{conn: conn} do
|
||||||
assert json_response(ret_conn, 200)
|
assert json_response(ret_conn, 200)
|
||||||
end
|
end
|
||||||
|
|
||||||
@tag capture_log: true
|
|
||||||
test "forwarded report", %{conn: conn} do
|
test "forwarded report", %{conn: conn} do
|
||||||
admin = insert(:user, is_admin: true)
|
admin = insert(:user, is_admin: true)
|
||||||
actor = insert(:user, local: false)
|
|
||||||
|
actor =
|
||||||
|
insert(:user, local: false)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
remote_domain = URI.parse(actor.ap_id).host
|
remote_domain = URI.parse(actor.ap_id).host
|
||||||
reported_user = insert(:user)
|
reported_user = insert(:user)
|
||||||
|
|
||||||
|
@ -1198,7 +1240,7 @@ test "forwarded report", %{conn: conn} do
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> assign(:valid_signature, true)
|
|> assign(:valid_signature, true)
|
||||||
|> put_req_header("signature", "keyId=\"#{actor.ap_id}/main-key\"")
|
|> put_req_header("signature", "keyId=\"#{actor.signing_key.key_id}\"")
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|> put_req_header("content-type", "application/activity+json")
|
||||||
|> post("/users/#{reported_user.nickname}/inbox", data)
|
|> post("/users/#{reported_user.nickname}/inbox", data)
|
||||||
|> json_response(200)
|
|> json_response(200)
|
||||||
|
@ -1215,7 +1257,6 @@ test "forwarded report", %{conn: conn} do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@tag capture_log: true
|
|
||||||
test "forwarded report from mastodon", %{conn: conn} do
|
test "forwarded report from mastodon", %{conn: conn} do
|
||||||
admin = insert(:user, is_admin: true)
|
admin = insert(:user, is_admin: true)
|
||||||
actor = insert(:user, local: false)
|
actor = insert(:user, local: false)
|
||||||
|
@ -1232,12 +1273,22 @@ test "forwarded report from mastodon", %{conn: conn} do
|
||||||
|> File.read!()
|
|> File.read!()
|
||||||
|> String.replace("{{DOMAIN}}", remote_domain)
|
|> String.replace("{{DOMAIN}}", remote_domain)
|
||||||
|
|
||||||
Tesla.Mock.mock(fn %{url: ^remote_actor} ->
|
key_url = "#{remote_actor}#main-key"
|
||||||
%Tesla.Env{
|
|
||||||
status: 200,
|
Tesla.Mock.mock(fn
|
||||||
body: mock_json_body,
|
%{url: ^remote_actor} ->
|
||||||
headers: [{"content-type", "application/activity+json"}]
|
%Tesla.Env{
|
||||||
}
|
status: 200,
|
||||||
|
body: mock_json_body,
|
||||||
|
headers: [{"content-type", "application/activity+json"}]
|
||||||
|
}
|
||||||
|
|
||||||
|
%{url: ^key_url} ->
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: mock_json_body,
|
||||||
|
headers: [{"content-type", "application/activity+json"}]
|
||||||
|
}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
data = %{
|
data = %{
|
||||||
|
@ -1254,7 +1305,7 @@ test "forwarded report from mastodon", %{conn: conn} do
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> assign(:valid_signature, true)
|
|> assign(:valid_signature, true)
|
||||||
|> put_req_header("signature", "keyId=\"#{remote_actor}/main-key\"")
|
|> put_req_header("signature", "keyId=\"#{remote_actor}#main-key\"")
|
||||||
|> put_req_header("content-type", "application/activity+json")
|
|> put_req_header("content-type", "application/activity+json")
|
||||||
|> post("/users/#{reported_user.nickname}/inbox", data)
|
|> post("/users/#{reported_user.nickname}/inbox", data)
|
||||||
|> json_response(200)
|
|> json_response(200)
|
||||||
|
|
|
@ -140,7 +140,9 @@ test "publish to url with with different ports" do
|
||||||
{:ok, %Tesla.Env{status: 200, body: "port 80"}}
|
{:ok, %Tesla.Env{status: 200, body: "port 80"}}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
actor = insert(:user)
|
actor =
|
||||||
|
insert(:user)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
assert {:ok, %{body: "port 42"}} =
|
assert {:ok, %{body: "port 42"}} =
|
||||||
Publisher.publish_one(%{
|
Publisher.publish_one(%{
|
||||||
|
@ -165,7 +167,10 @@ test "publish to url with with different ports" do
|
||||||
Instances,
|
Instances,
|
||||||
[:passthrough],
|
[:passthrough],
|
||||||
[] do
|
[] do
|
||||||
actor = insert(:user)
|
actor =
|
||||||
|
insert(:user)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
inbox = "http://200.site/users/nick1/inbox"
|
inbox = "http://200.site/users/nick1/inbox"
|
||||||
|
|
||||||
assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
|
assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
|
||||||
|
@ -176,7 +181,10 @@ test "publish to url with with different ports" do
|
||||||
Instances,
|
Instances,
|
||||||
[:passthrough],
|
[:passthrough],
|
||||||
[] do
|
[] do
|
||||||
actor = insert(:user)
|
actor =
|
||||||
|
insert(:user)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
inbox = "http://200.site/users/nick1/inbox"
|
inbox = "http://200.site/users/nick1/inbox"
|
||||||
|
|
||||||
assert {:ok, _} =
|
assert {:ok, _} =
|
||||||
|
@ -195,7 +203,10 @@ test "publish to url with with different ports" do
|
||||||
Instances,
|
Instances,
|
||||||
[:passthrough],
|
[:passthrough],
|
||||||
[] do
|
[] do
|
||||||
actor = insert(:user)
|
actor =
|
||||||
|
insert(:user)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
inbox = "http://200.site/users/nick1/inbox"
|
inbox = "http://200.site/users/nick1/inbox"
|
||||||
|
|
||||||
assert {:ok, _} =
|
assert {:ok, _} =
|
||||||
|
@ -214,7 +225,10 @@ test "publish to url with with different ports" do
|
||||||
Instances,
|
Instances,
|
||||||
[:passthrough],
|
[:passthrough],
|
||||||
[] do
|
[] do
|
||||||
actor = insert(:user)
|
actor =
|
||||||
|
insert(:user)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
inbox = "http://404.site/users/nick1/inbox"
|
inbox = "http://404.site/users/nick1/inbox"
|
||||||
|
|
||||||
assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
|
assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
|
||||||
|
@ -226,7 +240,10 @@ test "publish to url with with different ports" do
|
||||||
Instances,
|
Instances,
|
||||||
[:passthrough],
|
[:passthrough],
|
||||||
[] do
|
[] do
|
||||||
actor = insert(:user)
|
actor =
|
||||||
|
insert(:user)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
inbox = "http://connrefused.site/users/nick1/inbox"
|
inbox = "http://connrefused.site/users/nick1/inbox"
|
||||||
|
|
||||||
assert capture_log(fn ->
|
assert capture_log(fn ->
|
||||||
|
@ -241,7 +258,10 @@ test "publish to url with with different ports" do
|
||||||
Instances,
|
Instances,
|
||||||
[:passthrough],
|
[:passthrough],
|
||||||
[] do
|
[] do
|
||||||
actor = insert(:user)
|
actor =
|
||||||
|
insert(:user)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
inbox = "http://200.site/users/nick1/inbox"
|
inbox = "http://200.site/users/nick1/inbox"
|
||||||
|
|
||||||
assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
|
assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1})
|
||||||
|
@ -253,7 +273,10 @@ test "publish to url with with different ports" do
|
||||||
Instances,
|
Instances,
|
||||||
[:passthrough],
|
[:passthrough],
|
||||||
[] do
|
[] do
|
||||||
actor = insert(:user)
|
actor =
|
||||||
|
insert(:user)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
inbox = "http://connrefused.site/users/nick1/inbox"
|
inbox = "http://connrefused.site/users/nick1/inbox"
|
||||||
|
|
||||||
assert capture_log(fn ->
|
assert capture_log(fn ->
|
||||||
|
@ -294,7 +317,9 @@ test "publish to url with with different ports" do
|
||||||
ap_enabled: true
|
ap_enabled: true
|
||||||
})
|
})
|
||||||
|
|
||||||
actor = insert(:user, follower_address: follower.ap_id)
|
actor =
|
||||||
|
insert(:user, follower_address: follower.ap_id)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
{:ok, follower, actor} = Pleroma.User.follow(follower, actor)
|
{:ok, follower, actor} = Pleroma.User.follow(follower, actor)
|
||||||
{:ok, _another_follower, actor} = Pleroma.User.follow(another_follower, actor)
|
{:ok, _another_follower, actor} = Pleroma.User.follow(another_follower, actor)
|
||||||
|
@ -365,7 +390,9 @@ test "publish to url with with different ports" do
|
||||||
ap_enabled: true
|
ap_enabled: true
|
||||||
})
|
})
|
||||||
|
|
||||||
actor = insert(:user, follower_address: follower.ap_id)
|
actor =
|
||||||
|
insert(:user, follower_address: follower.ap_id)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
{:ok, follower, actor} = Pleroma.User.follow(follower, actor)
|
{:ok, follower, actor} = Pleroma.User.follow(follower, actor)
|
||||||
actor = refresh_record(actor)
|
actor = refresh_record(actor)
|
||||||
|
|
|
@ -114,7 +114,6 @@ test "returns error when activity not `Create` type" do
|
||||||
assert Relay.publish(activity) == {:error, "Not implemented"}
|
assert Relay.publish(activity) == {:error, "Not implemented"}
|
||||||
end
|
end
|
||||||
|
|
||||||
@tag capture_log: true
|
|
||||||
test "returns error when activity not public" do
|
test "returns error when activity not public" do
|
||||||
activity = insert(:direct_note_activity)
|
activity = insert(:direct_note_activity)
|
||||||
assert Relay.publish(activity) == {:error, false}
|
assert Relay.publish(activity) == {:error, false}
|
||||||
|
|
|
@ -83,7 +83,6 @@ test "it works for incoming announces, fetching the announced object" do
|
||||||
assert(Activity.get_create_by_object_ap_id(data["object"]))
|
assert(Activity.get_create_by_object_ap_id(data["object"]))
|
||||||
end
|
end
|
||||||
|
|
||||||
@tag capture_log: true
|
|
||||||
test "it works for incoming announces with an existing activity" do
|
test "it works for incoming announces with an existing activity" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
{:ok, activity} = CommonAPI.post(user, %{status: "hey"})
|
{:ok, activity} = CommonAPI.post(user, %{status: "hey"})
|
||||||
|
@ -136,7 +135,6 @@ test "it works for incoming announces with an inlined activity" do
|
||||||
assert object.data["content"] == "this is a private toot"
|
assert object.data["content"] == "this is a private toot"
|
||||||
end
|
end
|
||||||
|
|
||||||
@tag capture_log: true
|
|
||||||
test "it rejects incoming announces with an inlined activity from another origin" do
|
test "it rejects incoming announces with an inlined activity from another origin" do
|
||||||
Tesla.Mock.mock(fn
|
Tesla.Mock.mock(fn
|
||||||
%{method: :get} -> %Tesla.Env{status: 404, body: ""}
|
%{method: :get} -> %Tesla.Env{status: 404, body: ""}
|
||||||
|
|
|
@ -86,7 +86,6 @@ test "it fails for incoming deletes with spoofed origin" do
|
||||||
assert match?({:error, _}, Transmogrifier.handle_incoming(data))
|
assert match?({:error, _}, Transmogrifier.handle_incoming(data))
|
||||||
end
|
end
|
||||||
|
|
||||||
@tag capture_log: true
|
|
||||||
test "it works for incoming user deletes" do
|
test "it works for incoming user deletes" do
|
||||||
%{ap_id: ap_id} = insert(:user, ap_id: "http://mastodon.example.org/users/admin")
|
%{ap_id: ap_id} = insert(:user, ap_id: "http://mastodon.example.org/users/admin")
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,6 @@ test "it ignores an incoming notice if we already have it" do
|
||||||
assert activity == returned_activity
|
assert activity == returned_activity
|
||||||
end
|
end
|
||||||
|
|
||||||
@tag capture_log: true
|
|
||||||
test "it fetches reply-to activities if we don't have them" do
|
test "it fetches reply-to activities if we don't have them" do
|
||||||
data =
|
data =
|
||||||
File.read!("test/fixtures/mastodon-post-activity.json")
|
File.read!("test/fixtures/mastodon-post-activity.json")
|
||||||
|
@ -537,7 +536,6 @@ test "returns object with inReplyTo when denied incoming reply", %{data: data} d
|
||||||
assert modified_object["inReplyTo"] == []
|
assert modified_object["inReplyTo"] == []
|
||||||
end
|
end
|
||||||
|
|
||||||
@tag capture_log: true
|
|
||||||
test "returns modified object when allowed incoming reply", %{data: data} do
|
test "returns modified object when allowed incoming reply", %{data: data} do
|
||||||
object_with_reply =
|
object_with_reply =
|
||||||
Map.put(
|
Map.put(
|
||||||
|
@ -700,7 +698,7 @@ test "take_emoji_tags/1" do
|
||||||
assert Transmogrifier.take_emoji_tags(user) == [
|
assert Transmogrifier.take_emoji_tags(user) == [
|
||||||
%{
|
%{
|
||||||
"icon" => %{"type" => "Image", "url" => "https://example.org/firefox.png"},
|
"icon" => %{"type" => "Image", "url" => "https://example.org/firefox.png"},
|
||||||
"id" => "https://example.org/firefox.png",
|
"id" => nil,
|
||||||
"name" => ":firefox:",
|
"name" => ":firefox:",
|
||||||
"type" => "Emoji",
|
"type" => "Emoji",
|
||||||
"updated" => "1970-01-01T00:00:00Z"
|
"updated" => "1970-01-01T00:00:00Z"
|
||||||
|
|
|
@ -561,7 +561,6 @@ test "returns nil when cannot normalize object" do
|
||||||
end) =~ ":valid_uri_scheme"
|
end) =~ ":valid_uri_scheme"
|
||||||
end
|
end
|
||||||
|
|
||||||
@tag capture_log: true
|
|
||||||
test "returns {:ok, %Object{}} for success case" do
|
test "returns {:ok, %Object{}} for success case" do
|
||||||
assert {:ok, %Object{}} =
|
assert {:ok, %Object{}} =
|
||||||
Transmogrifier.get_obj_helper(
|
Transmogrifier.get_obj_helper(
|
||||||
|
|
|
@ -11,7 +11,9 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
test "Renders a user, including the public key" do
|
test "Renders a user, including the public key" do
|
||||||
user = insert(:user)
|
user =
|
||||||
|
insert(:user)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
result = UserView.render("user.json", %{user: user})
|
result = UserView.render("user.json", %{user: user})
|
||||||
|
|
||||||
|
@ -37,13 +39,15 @@ test "Renders profile fields" do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "Renders with emoji tags" do
|
test "Renders with emoji tags" do
|
||||||
user = insert(:user, emoji: %{"bib" => "/test"})
|
user =
|
||||||
|
insert(:user, emoji: %{"bib" => "/test"})
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
assert %{
|
assert %{
|
||||||
"tag" => [
|
"tag" => [
|
||||||
%{
|
%{
|
||||||
"icon" => %{"type" => "Image", "url" => "/test"},
|
"icon" => %{"type" => "Image", "url" => "/test"},
|
||||||
"id" => "/test",
|
"id" => nil,
|
||||||
"name" => ":bib:",
|
"name" => ":bib:",
|
||||||
"type" => "Emoji",
|
"type" => "Emoji",
|
||||||
"updated" => "1970-01-01T00:00:00Z"
|
"updated" => "1970-01-01T00:00:00Z"
|
||||||
|
@ -74,13 +78,18 @@ test "Does not add an avatar image if the user hasn't set one" do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "renders an invisible user with the invisible property set to true" do
|
test "renders an invisible user with the invisible property set to true" do
|
||||||
user = insert(:user, invisible: true)
|
user =
|
||||||
|
insert(:user, invisible: true)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
assert %{"invisible" => true} = UserView.render("service.json", %{user: user})
|
assert %{"invisible" => true} = UserView.render("service.json", %{user: user})
|
||||||
end
|
end
|
||||||
|
|
||||||
test "service has a few essential fields" do
|
test "service has a few essential fields" do
|
||||||
user = insert(:user)
|
user =
|
||||||
|
insert(:user)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
result = UserView.render("service.json", %{user: user})
|
result = UserView.render("service.json", %{user: user})
|
||||||
assert result["id"]
|
assert result["id"]
|
||||||
assert result["type"] == "Application"
|
assert result["type"] == "Application"
|
||||||
|
@ -120,7 +129,9 @@ test "remote users have an empty endpoints structure" do
|
||||||
end
|
end
|
||||||
|
|
||||||
test "instance users do not expose oAuth endpoints" do
|
test "instance users do not expose oAuth endpoints" do
|
||||||
user = insert(:user, nickname: nil, local: true)
|
user =
|
||||||
|
insert(:user, nickname: nil, local: true)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
result = UserView.render("user.json", %{user: user})
|
result = UserView.render("user.json", %{user: user})
|
||||||
|
|
||||||
|
|
|
@ -79,7 +79,6 @@ test "search", %{conn: conn} do
|
||||||
assert status["id"] == to_string(activity.id)
|
assert status["id"] == to_string(activity.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
@tag capture_log: true
|
|
||||||
test "constructs hashtags from search query", %{conn: conn} do
|
test "constructs hashtags from search query", %{conn: conn} do
|
||||||
results =
|
results =
|
||||||
conn
|
conn
|
||||||
|
|
|
@ -152,7 +152,6 @@ test "filtering", %{conn: conn, user: user} do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "public" do
|
describe "public" do
|
||||||
@tag capture_log: true
|
|
||||||
test "the public timeline", %{conn: conn} do
|
test "the public timeline", %{conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
|
@ -810,7 +809,6 @@ test "filtering", %{user: user, conn: conn} do
|
||||||
describe "hashtag" do
|
describe "hashtag" do
|
||||||
setup do: oauth_access(["n/a"])
|
setup do: oauth_access(["n/a"])
|
||||||
|
|
||||||
@tag capture_log: true
|
|
||||||
test "hashtag timeline", %{conn: conn} do
|
test "hashtag timeline", %{conn: conn} do
|
||||||
following = insert(:user)
|
following = insert(:user)
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@ test "renders a poll" do
|
||||||
%{title: "why are you even asking?", votes_count: 0}
|
%{title: "why are you even asking?", votes_count: 0}
|
||||||
],
|
],
|
||||||
votes_count: 0,
|
votes_count: 0,
|
||||||
voters_count: 0
|
voters_count: nil
|
||||||
}
|
}
|
||||||
|
|
||||||
result = PollView.render("show.json", %{object: object})
|
result = PollView.render("show.json", %{object: object})
|
||||||
|
|
|
@ -33,6 +33,10 @@ test "has an emoji reaction list" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
other_user = insert(:user)
|
other_user = insert(:user)
|
||||||
third_user = insert(:user)
|
third_user = insert(:user)
|
||||||
|
domain_blocked_user = insert(:user, %{ap_id: "https://blocked.com/@blocked"})
|
||||||
|
|
||||||
|
{:ok, user} = User.block_domain(user, "blocked.com")
|
||||||
|
|
||||||
{:ok, activity} = CommonAPI.post(user, %{status: "dae cofe??"})
|
{:ok, activity} = CommonAPI.post(user, %{status: "dae cofe??"})
|
||||||
|
|
||||||
{:ok, _} = CommonAPI.react_with_emoji(activity.id, user, "☕")
|
{:ok, _} = CommonAPI.react_with_emoji(activity.id, user, "☕")
|
||||||
|
@ -40,6 +44,8 @@ test "has an emoji reaction list" do
|
||||||
{:ok, _} = CommonAPI.react_with_emoji(activity.id, third_user, "🍵")
|
{:ok, _} = CommonAPI.react_with_emoji(activity.id, third_user, "🍵")
|
||||||
{:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕")
|
{:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, "☕")
|
||||||
{:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, ":dinosaur:")
|
{:ok, _} = CommonAPI.react_with_emoji(activity.id, other_user, ":dinosaur:")
|
||||||
|
# this should not show up when the user is viewing the status
|
||||||
|
{:ok, _} = CommonAPI.react_with_emoji(activity.id, domain_blocked_user, "😈")
|
||||||
|
|
||||||
activity = Repo.get(Activity, activity.id)
|
activity = Repo.get(Activity, activity.id)
|
||||||
status = StatusView.render("show.json", activity: activity)
|
status = StatusView.render("show.json", activity: activity)
|
||||||
|
@ -55,7 +61,8 @@ test "has an emoji reaction list" do
|
||||||
url: "http://localhost:4001/emoji/dino walking.gif",
|
url: "http://localhost:4001/emoji/dino walking.gif",
|
||||||
account_ids: [other_user.id, user.id]
|
account_ids: [other_user.id, user.id]
|
||||||
},
|
},
|
||||||
%{name: "🍵", count: 1, me: false, url: nil, account_ids: [third_user.id]}
|
%{name: "🍵", count: 1, me: false, url: nil, account_ids: [third_user.id]},
|
||||||
|
%{name: "😈", count: 1, me: false, url: nil, account_ids: [domain_blocked_user.id]}
|
||||||
]
|
]
|
||||||
|
|
||||||
status = StatusView.render("show.json", activity: activity, for: user)
|
status = StatusView.render("show.json", activity: activity, for: user)
|
||||||
|
@ -73,6 +80,8 @@ test "has an emoji reaction list" do
|
||||||
},
|
},
|
||||||
%{name: "🍵", count: 1, me: false, url: nil, account_ids: [third_user.id]}
|
%{name: "🍵", count: 1, me: false, url: nil, account_ids: [third_user.id]}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
refute Enum.any?(status[:pleroma][:emoji_reactions], fn reaction -> reaction[:name] == "😈" end)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "works correctly with badly formatted emojis" do
|
test "works correctly with badly formatted emojis" do
|
||||||
|
|
|
@ -14,6 +14,15 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlugTest do
|
||||||
import Phoenix.Controller, only: [put_format: 2]
|
import Phoenix.Controller, only: [put_format: 2]
|
||||||
import Mock
|
import Mock
|
||||||
|
|
||||||
|
setup do
|
||||||
|
user =
|
||||||
|
:user
|
||||||
|
|> insert(%{ap_id: "http://mastodon.example.org/users/admin"})
|
||||||
|
|> with_signing_key(%{key_id: "http://mastodon.example.org/users/admin#main-key"})
|
||||||
|
|
||||||
|
{:ok, %{user: user}}
|
||||||
|
end
|
||||||
|
|
||||||
setup_with_mocks([
|
setup_with_mocks([
|
||||||
{HTTPSignatures, [],
|
{HTTPSignatures, [],
|
||||||
[
|
[
|
||||||
|
@ -46,15 +55,15 @@ defp submit_to_plug(host, method, path) do
|
||||||
|> HTTPSignaturePlug.call(%{})
|
|> HTTPSignaturePlug.call(%{})
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it call HTTPSignatures to check validity if the actor signed it" do
|
test "it call HTTPSignatures to check validity if the actor signed it", %{user: user} do
|
||||||
params = %{"actor" => "http://mastodon.example.org/users/admin"}
|
params = %{"actor" => user.ap_id}
|
||||||
conn = build_conn(:get, "/doesntmattter", params)
|
conn = build_conn(:get, "/doesntmattter", params)
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|> put_req_header(
|
|> put_req_header(
|
||||||
"signature",
|
"signature",
|
||||||
"keyId=\"http://mastodon.example.org/users/admin#main-key"
|
"keyId=\"#{user.signing_key.key_id}\""
|
||||||
)
|
)
|
||||||
|> put_format("activity+json")
|
|> put_format("activity+json")
|
||||||
|> HTTPSignaturePlug.call(%{})
|
|> HTTPSignaturePlug.call(%{})
|
||||||
|
|
|
@ -8,52 +8,63 @@ defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlugTest do
|
||||||
|
|
||||||
import Tesla.Mock
|
import Tesla.Mock
|
||||||
import Plug.Conn
|
import Plug.Conn
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
import Pleroma.Tests.Helpers, only: [clear_config: 2]
|
import Pleroma.Tests.Helpers, only: [clear_config: 2]
|
||||||
|
|
||||||
setup do
|
setup do
|
||||||
mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
||||||
:ok
|
|
||||||
|
user =
|
||||||
|
insert(:user)
|
||||||
|
|> with_signing_key()
|
||||||
|
|
||||||
|
{:ok, %{user: user}}
|
||||||
end
|
end
|
||||||
|
|
||||||
defp set_signature(conn, key_id) do
|
defp set_signature(conn, ap_id) do
|
||||||
conn
|
conn
|
||||||
|> put_req_header("signature", "keyId=\"#{key_id}\"")
|
|> put_req_header("signature", "keyId=\"#{ap_id}#main-key\"")
|
||||||
|> assign(:valid_signature, true)
|
|> assign(:valid_signature, true)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it successfully maps a valid identity with a valid signature" do
|
test "it successfully maps a valid identity with a valid signature", %{user: user} do
|
||||||
conn =
|
conn =
|
||||||
build_conn(:get, "/doesntmattter")
|
build_conn(:get, "/doesntmattter")
|
||||||
|> set_signature("http://mastodon.example.org/users/admin")
|
|> set_signature(user.ap_id)
|
||||||
|> MappedSignatureToIdentityPlug.call(%{})
|
|> MappedSignatureToIdentityPlug.call(%{})
|
||||||
|
|
||||||
refute is_nil(conn.assigns.user)
|
refute is_nil(conn.assigns.user)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it successfully maps a valid identity with a valid signature with payload" do
|
test "it successfully maps a valid identity with a valid signature with payload", %{user: user} do
|
||||||
conn =
|
conn =
|
||||||
build_conn(:post, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"})
|
build_conn(:post, "/doesntmattter", %{"actor" => user.ap_id})
|
||||||
|> set_signature("http://mastodon.example.org/users/admin")
|
|> set_signature(user.ap_id)
|
||||||
|> MappedSignatureToIdentityPlug.call(%{})
|
|> MappedSignatureToIdentityPlug.call(%{})
|
||||||
|
|
||||||
refute is_nil(conn.assigns.user)
|
refute is_nil(conn.assigns.user)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it considers a mapped identity to be invalid when it mismatches a payload" do
|
test "it considers a mapped identity to be invalid when it mismatches a payload", %{user: user} do
|
||||||
conn =
|
conn =
|
||||||
build_conn(:post, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"})
|
build_conn(:post, "/doesntmattter", %{"actor" => user.ap_id})
|
||||||
|> set_signature("https://niu.moe/users/rye")
|
|> set_signature("https://niu.moe/users/rye")
|
||||||
|> MappedSignatureToIdentityPlug.call(%{})
|
|> MappedSignatureToIdentityPlug.call(%{})
|
||||||
|
|
||||||
assert %{valid_signature: false} == conn.assigns
|
assert %{valid_signature: false} == conn.assigns
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it considers a mapped identity to be invalid when the associated instance is blocked" do
|
test "it considers a mapped identity to be invalid when the associated instance is blocked", %{
|
||||||
|
user: user
|
||||||
|
} do
|
||||||
clear_config([:activitypub, :authorized_fetch_mode], true)
|
clear_config([:activitypub, :authorized_fetch_mode], true)
|
||||||
|
|
||||||
|
# extract domain from user.ap_id
|
||||||
|
url = URI.parse(user.ap_id)
|
||||||
|
|
||||||
clear_config([:mrf_simple, :reject], [
|
clear_config([:mrf_simple, :reject], [
|
||||||
{"mastodon.example.org", "anime is banned"}
|
{url.host, "anime is banned"}
|
||||||
])
|
])
|
||||||
|
|
||||||
on_exit(fn ->
|
on_exit(fn ->
|
||||||
|
@ -62,18 +73,21 @@ test "it considers a mapped identity to be invalid when the associated instance
|
||||||
end)
|
end)
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
build_conn(:post, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"})
|
build_conn(:post, "/doesntmattter", %{"actor" => user.ap_id})
|
||||||
|> set_signature("http://mastodon.example.org/users/admin")
|
|> set_signature(user.ap_id)
|
||||||
|> MappedSignatureToIdentityPlug.call(%{})
|
|> MappedSignatureToIdentityPlug.call(%{})
|
||||||
|
|
||||||
assert %{valid_signature: false} == conn.assigns
|
assert %{valid_signature: false} == conn.assigns
|
||||||
end
|
end
|
||||||
|
|
||||||
test "allowlist federation: it considers a mapped identity to be valid when the associated instance is allowed" do
|
test "allowlist federation: it considers a mapped identity to be valid when the associated instance is allowed",
|
||||||
|
%{user: user} do
|
||||||
clear_config([:activitypub, :authorized_fetch_mode], true)
|
clear_config([:activitypub, :authorized_fetch_mode], true)
|
||||||
|
|
||||||
|
url = URI.parse(user.ap_id)
|
||||||
|
|
||||||
clear_config([:mrf_simple, :accept], [
|
clear_config([:mrf_simple, :accept], [
|
||||||
{"mastodon.example.org", "anime is allowed"}
|
{url.host, "anime is allowed"}
|
||||||
])
|
])
|
||||||
|
|
||||||
on_exit(fn ->
|
on_exit(fn ->
|
||||||
|
@ -82,15 +96,16 @@ test "allowlist federation: it considers a mapped identity to be valid when the
|
||||||
end)
|
end)
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
build_conn(:post, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"})
|
build_conn(:post, "/doesntmattter", %{"actor" => user.ap_id})
|
||||||
|> set_signature("http://mastodon.example.org/users/admin")
|
|> set_signature(user.ap_id)
|
||||||
|> MappedSignatureToIdentityPlug.call(%{})
|
|> MappedSignatureToIdentityPlug.call(%{})
|
||||||
|
|
||||||
assert conn.assigns[:valid_signature]
|
assert conn.assigns[:valid_signature]
|
||||||
refute is_nil(conn.assigns.user)
|
refute is_nil(conn.assigns.user)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "allowlist federation: it considers a mapped identity to be invalid when the associated instance is not allowed" do
|
test "allowlist federation: it considers a mapped identity to be invalid when the associated instance is not allowed",
|
||||||
|
%{user: user} do
|
||||||
clear_config([:activitypub, :authorized_fetch_mode], true)
|
clear_config([:activitypub, :authorized_fetch_mode], true)
|
||||||
|
|
||||||
clear_config([:mrf_simple, :accept], [
|
clear_config([:mrf_simple, :accept], [
|
||||||
|
@ -103,8 +118,8 @@ test "allowlist federation: it considers a mapped identity to be invalid when th
|
||||||
end)
|
end)
|
||||||
|
|
||||||
conn =
|
conn =
|
||||||
build_conn(:post, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"})
|
build_conn(:post, "/doesntmattter", %{"actor" => user.ap_id})
|
||||||
|> set_signature("http://mastodon.example.org/users/admin")
|
|> set_signature(user.ap_id)
|
||||||
|> MappedSignatureToIdentityPlug.call(%{})
|
|> MappedSignatureToIdentityPlug.call(%{})
|
||||||
|
|
||||||
assert %{valid_signature: false} == conn.assigns
|
assert %{valid_signature: false} == conn.assigns
|
||||||
|
|
|
@ -67,7 +67,6 @@ test "performs sending notifications" do
|
||||||
assert Impl.perform(notif) == {:ok, [:ok, :ok]}
|
assert Impl.perform(notif) == {:ok, [:ok, :ok]}
|
||||||
end
|
end
|
||||||
|
|
||||||
@tag capture_log: true
|
|
||||||
test "returns error if notif does not match " do
|
test "returns error if notif does not match " do
|
||||||
assert Impl.perform(%{}) == {:error, :unknown_type}
|
assert Impl.perform(%{}) == {:error, :unknown_type}
|
||||||
end
|
end
|
||||||
|
@ -76,7 +75,6 @@ test "successful message sending" do
|
||||||
assert Impl.push_message(@message, @sub, @api_key, %Subscription{}) == :ok
|
assert Impl.push_message(@message, @sub, @api_key, %Subscription{}) == :ok
|
||||||
end
|
end
|
||||||
|
|
||||||
@tag capture_log: true
|
|
||||||
test "fail message sending" do
|
test "fail message sending" do
|
||||||
assert Impl.push_message(
|
assert Impl.push_message(
|
||||||
@message,
|
@message,
|
||||||
|
|
|
@ -190,7 +190,6 @@ test "prevents spoofing" do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@tag capture_log: true
|
|
||||||
test "prevents forgeries" do
|
test "prevents forgeries" do
|
||||||
Tesla.Mock.mock(fn
|
Tesla.Mock.mock(fn
|
||||||
%{url: "https://bad.com/.well-known/webfinger?resource=acct:meanie@bad.com"} ->
|
%{url: "https://bad.com/.well-known/webfinger?resource=acct:meanie@bad.com"} ->
|
||||||
|
|
86
test/pleroma/workers/attachments_cleanup_worker_test.exs
Normal file
86
test/pleroma/workers/attachments_cleanup_worker_test.exs
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
# Akkoma: Magically expressive social media
|
||||||
|
# Copyright © 2024 Akkoma Authors <https://akkoma.dev/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Workers.AttachmentsCleanupWorkerTest do
|
||||||
|
use Pleroma.DataCase, async: false
|
||||||
|
use Oban.Testing, repo: Pleroma.Repo
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.Workers.AttachmentsCleanupWorker
|
||||||
|
alias Pleroma.Tests.ObanHelpers
|
||||||
|
|
||||||
|
setup do
|
||||||
|
clear_config([:instance, :cleanup_attachments], true)
|
||||||
|
|
||||||
|
file = %Plug.Upload{
|
||||||
|
content_type: "image/jpeg",
|
||||||
|
path: Path.absname("test/fixtures/image.jpg"),
|
||||||
|
filename: "an_image.jpg"
|
||||||
|
}
|
||||||
|
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, %Pleroma.Object{} = attachment} =
|
||||||
|
Pleroma.Web.ActivityPub.ActivityPub.upload(file, actor: user.ap_id)
|
||||||
|
|
||||||
|
{:ok, attachment: attachment, user: user}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "does not enqueue remote post" do
|
||||||
|
remote_data = %{
|
||||||
|
"id" => "https://remote.example/obj/123",
|
||||||
|
"actor" => "https://remote.example/user/1",
|
||||||
|
"content" => "content",
|
||||||
|
"attachment" => [
|
||||||
|
%{
|
||||||
|
"type" => "Document",
|
||||||
|
"mediaType" => "image/png",
|
||||||
|
"name" => "marvellous image",
|
||||||
|
"url" => "https://remote.example/files/image.png"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {:ok, :skip} = AttachmentsCleanupWorker.enqueue_if_needed(remote_data)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "enqueues local post", %{attachment: attachment, user: user} do
|
||||||
|
local_url = Pleroma.Web.Endpoint.url()
|
||||||
|
|
||||||
|
local_data = %{
|
||||||
|
"id" => local_url <> "/obj/123",
|
||||||
|
"actor" => user.ap_id,
|
||||||
|
"content" => "content",
|
||||||
|
"attachment" => [attachment.data]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert {:ok, %Oban.Job{}} = AttachmentsCleanupWorker.enqueue_if_needed(local_data)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "doesn't delete immediately", %{attachment: attachment, user: user} do
|
||||||
|
delay = 6000
|
||||||
|
clear_config([:instance, :cleanup_attachments_delay], delay)
|
||||||
|
|
||||||
|
note = insert(:note, %{user: user, data: %{"attachment" => [attachment.data]}})
|
||||||
|
|
||||||
|
uploads_dir = Pleroma.Config.get!([Pleroma.Uploaders.Local, :uploads])
|
||||||
|
%{"url" => [%{"href" => href}]} = attachment.data
|
||||||
|
path = "#{uploads_dir}/#{Path.basename(href)}"
|
||||||
|
|
||||||
|
assert File.exists?(path)
|
||||||
|
|
||||||
|
Object.delete(note)
|
||||||
|
Process.sleep(2000)
|
||||||
|
|
||||||
|
assert File.exists?(path)
|
||||||
|
|
||||||
|
ObanHelpers.perform(all_enqueued(worker: Pleroma.Workers.AttachmentsCleanupWorker))
|
||||||
|
|
||||||
|
assert Object.get_by_id(note.id).data["deleted"]
|
||||||
|
assert Object.get_by_id(attachment.id) == nil
|
||||||
|
refute File.exists?(path)
|
||||||
|
end
|
||||||
|
end
|
|
@ -47,7 +47,6 @@ def instance_factory(attrs \\ %{}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def user_factory(attrs \\ %{}) do
|
def user_factory(attrs \\ %{}) do
|
||||||
pem = Enum.random(@rsa_keys)
|
|
||||||
# Argon2.hash_pwd_salt("test")
|
# Argon2.hash_pwd_salt("test")
|
||||||
# it really eats CPU time, so we use a precomputed hash
|
# it really eats CPU time, so we use a precomputed hash
|
||||||
password_hash =
|
password_hash =
|
||||||
|
@ -64,8 +63,7 @@ def user_factory(attrs \\ %{}) do
|
||||||
last_refreshed_at: NaiveDateTime.utc_now(),
|
last_refreshed_at: NaiveDateTime.utc_now(),
|
||||||
notification_settings: %Pleroma.User.NotificationSetting{},
|
notification_settings: %Pleroma.User.NotificationSetting{},
|
||||||
multi_factor_authentication_settings: %Pleroma.MFA.Settings{},
|
multi_factor_authentication_settings: %Pleroma.MFA.Settings{},
|
||||||
ap_enabled: true,
|
ap_enabled: true
|
||||||
keys: pem
|
|
||||||
}
|
}
|
||||||
|
|
||||||
urls =
|
urls =
|
||||||
|
@ -97,6 +95,28 @@ def user_factory(attrs \\ %{}) do
|
||||||
|> merge_attributes(attrs)
|
|> merge_attributes(attrs)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def with_signing_key(%User{} = user, attrs \\ %{}) do
|
||||||
|
signing_key =
|
||||||
|
build(:signing_key, %{user: user, key_id: "#{user.ap_id}#main-key"})
|
||||||
|
|> merge_attributes(attrs)
|
||||||
|
|
||||||
|
insert(signing_key)
|
||||||
|
%{user | signing_key: signing_key}
|
||||||
|
end
|
||||||
|
|
||||||
|
def signing_key_factory(attrs \\ %{}) do
|
||||||
|
pem = Enum.random(@rsa_keys)
|
||||||
|
user = attrs[:user] || insert(:user)
|
||||||
|
{:ok, public_key} = Pleroma.User.SigningKey.private_pem_to_public_pem(pem)
|
||||||
|
|
||||||
|
%Pleroma.User.SigningKey{
|
||||||
|
user_id: user.id,
|
||||||
|
public_key: attrs[:public_key] || public_key,
|
||||||
|
private_key: attrs[:private_key] || pem,
|
||||||
|
key_id: attrs[:key_id]
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
def user_relationship_factory(attrs \\ %{}) do
|
def user_relationship_factory(attrs \\ %{}) do
|
||||||
source = attrs[:source] || insert(:user)
|
source = attrs[:source] || insert(:user)
|
||||||
target = attrs[:target] || insert(:user)
|
target = attrs[:target] || insert(:user)
|
||||||
|
|
|
@ -66,6 +66,8 @@ defmacro __using__(_opts) do
|
||||||
clear_config: 2
|
clear_config: 2
|
||||||
]
|
]
|
||||||
|
|
||||||
|
import Pleroma.Test.MatchingHelpers
|
||||||
|
|
||||||
def time_travel(entity, seconds) do
|
def time_travel(entity, seconds) do
|
||||||
new_time = NaiveDateTime.add(entity.inserted_at, seconds)
|
new_time = NaiveDateTime.add(entity.inserted_at, seconds)
|
||||||
|
|
||||||
|
|
|
@ -263,7 +263,12 @@ def get("https://n1u.moe/users/rye", _, _, @activitypub_accept_headers) do
|
||||||
{:ok,
|
{:ok,
|
||||||
%Tesla.Env{
|
%Tesla.Env{
|
||||||
status: 200,
|
status: 200,
|
||||||
body: File.read!("test/fixtures/tesla_mock/rye.json"),
|
body:
|
||||||
|
File.read!("test/fixtures/tesla_mock/rye.json")
|
||||||
|
|> Jason.decode!()
|
||||||
|
|> Map.put("name", "evil rye")
|
||||||
|
|> Map.put("bio", "boooo!")
|
||||||
|
|> Jason.encode!(),
|
||||||
headers: activitypub_object_headers()
|
headers: activitypub_object_headers()
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
@ -419,6 +424,15 @@ def get("http://mastodon.example.org/users/admin", _, _, _) do
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get("http://mastodon.example.org/users/admin/main-key", _, _, _) do
|
||||||
|
{:ok,
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/tesla_mock/admin@mastdon.example.org.json"),
|
||||||
|
headers: activitypub_object_headers()
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
|
||||||
def get(
|
def get(
|
||||||
"http://mastodon.example.org/users/admin/statuses/99512778738411822/replies?min_id=99512778738411824&page=true",
|
"http://mastodon.example.org/users/admin/statuses/99512778738411822/replies?min_id=99512778738411824&page=true",
|
||||||
_,
|
_,
|
||||||
|
@ -953,6 +967,15 @@ def get("https://mastodon.social/users/lambadalambda", _, _, _) do
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get("https://mastodon.social/users/lambadalambda#main-key", _, _, _) do
|
||||||
|
{:ok,
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/lambadalambda.json"),
|
||||||
|
headers: activitypub_object_headers()
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
|
||||||
def get("https://mastodon.social/users/lambadalambda/collections/featured", _, _, _) do
|
def get("https://mastodon.social/users/lambadalambda/collections/featured", _, _, _) do
|
||||||
{:ok,
|
{:ok,
|
||||||
%Tesla.Env{
|
%Tesla.Env{
|
||||||
|
@ -1398,6 +1421,15 @@ def get("https://relay.mastodon.host/actor", _, _, _) do
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def get("https://relay.mastodon.host/actor#main-key", _, _, _) do
|
||||||
|
{:ok,
|
||||||
|
%Tesla.Env{
|
||||||
|
status: 200,
|
||||||
|
body: File.read!("test/fixtures/relay/relay.json"),
|
||||||
|
headers: activitypub_object_headers()
|
||||||
|
}}
|
||||||
|
end
|
||||||
|
|
||||||
def get("http://localhost:4001/", _, "", [{"accept", "text/html"}]) do
|
def get("http://localhost:4001/", _, "", [{"accept", "text/html"}]) do
|
||||||
{:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/7369654.html")}}
|
{:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/7369654.html")}}
|
||||||
end
|
end
|
||||||
|
|
10
test/support/matching_helpers.ex
Normal file
10
test/support/matching_helpers.ex
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
defmodule Pleroma.Test.MatchingHelpers do
|
||||||
|
import ExUnit.Assertions
|
||||||
|
|
||||||
|
@assoc_fields [
|
||||||
|
:signing_key
|
||||||
|
]
|
||||||
|
def assert_user_match(actor1, actor2) do
|
||||||
|
assert Ecto.reset_fields(actor1, @assoc_fields) == Ecto.reset_fields(actor2, @assoc_fields)
|
||||||
|
end
|
||||||
|
end
|
|
@ -3,7 +3,11 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
os_exclude = if :os.type() == {:unix, :darwin}, do: [skip_on_mac: true], else: []
|
os_exclude = if :os.type() == {:unix, :darwin}, do: [skip_on_mac: true], else: []
|
||||||
ExUnit.start(exclude: [:federated, :erratic] ++ os_exclude)
|
|
||||||
|
ExUnit.start(
|
||||||
|
capture_log: true,
|
||||||
|
exclude: [:federated, :erratic] ++ os_exclude
|
||||||
|
)
|
||||||
|
|
||||||
Ecto.Adapters.SQL.Sandbox.mode(Pleroma.Repo, :manual)
|
Ecto.Adapters.SQL.Sandbox.mode(Pleroma.Repo, :manual)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue