aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/mix/tasks/deactivate_user.ex19
-rw-r--r--lib/mix/tasks/generate_config.ex47
-rw-r--r--lib/mix/tasks/generate_invite_token.ex32
-rw-r--r--lib/mix/tasks/generate_password_reset.ex33
-rw-r--r--lib/mix/tasks/make_moderator.ex38
-rw-r--r--lib/mix/tasks/migrate_local_uploads.ex97
-rw-r--r--lib/mix/tasks/pleroma/common.ex24
-rw-r--r--lib/mix/tasks/pleroma/instance.ex157
-rw-r--r--lib/mix/tasks/pleroma/relay.ex43
-rw-r--r--lib/mix/tasks/pleroma/sample_config.eex (renamed from lib/mix/tasks/sample_config.eex)24
-rw-r--r--lib/mix/tasks/pleroma/sample_psql.eex (renamed from lib/mix/tasks/sample_psql.eex)0
-rw-r--r--lib/mix/tasks/pleroma/user.ex293
-rw-r--r--lib/mix/tasks/reactivate_user.ex19
-rw-r--r--lib/mix/tasks/register_user.ex30
-rw-r--r--lib/mix/tasks/relay_follow.ex24
-rw-r--r--lib/mix/tasks/relay_unfollow.ex23
-rw-r--r--lib/mix/tasks/rm_user.ex19
-rw-r--r--lib/mix/tasks/set_admin.ex34
-rw-r--r--lib/mix/tasks/set_locked.ex40
-rw-r--r--lib/mix/tasks/unsubscribe_user.ex38
-rw-r--r--lib/pleroma/application.ex3
-rw-r--r--lib/pleroma/emoji.ex2
-rw-r--r--lib/pleroma/http/connection.ex7
-rw-r--r--lib/pleroma/notification.ex1
-rw-r--r--lib/pleroma/plugs/oauth_plug.ex4
-rw-r--r--lib/pleroma/reverse_proxy.ex2
-rw-r--r--lib/pleroma/user.ex4
-rw-r--r--lib/pleroma/user/info.ex5
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub.ex5
-rw-r--r--lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex40
-rw-r--r--lib/pleroma/web/mastodon_api/mastodon_api_controller.ex57
-rw-r--r--lib/pleroma/web/mastodon_api/views/push_subscription_view.ex12
-rw-r--r--lib/pleroma/web/ostatus/ostatus.ex10
-rw-r--r--lib/pleroma/web/push/push.ex116
-rw-r--r--lib/pleroma/web/push/subscription.ex66
-rw-r--r--lib/pleroma/web/router.ex5
-rw-r--r--lib/pleroma/web/salmon/salmon.ex7
-rw-r--r--lib/pleroma/web/twitter_api/controllers/util_controller.ex6
-rw-r--r--lib/pleroma/web/web_finger/web_finger.ex2
-rw-r--r--lib/pleroma/web/websub/websub.ex5
40 files changed, 853 insertions, 540 deletions
diff --git a/lib/mix/tasks/deactivate_user.ex b/lib/mix/tasks/deactivate_user.ex
deleted file mode 100644
index e71ed1ec0..000000000
--- a/lib/mix/tasks/deactivate_user.ex
+++ /dev/null
@@ -1,19 +0,0 @@
-defmodule Mix.Tasks.DeactivateUser do
- use Mix.Task
- alias Pleroma.User
-
- @moduledoc """
- Deactivates a user (local or remote)
-
- Usage: ``mix deactivate_user <nickname>``
-
- Example: ``mix deactivate_user lain``
- """
- def run([nickname]) do
- Mix.Task.run("app.start")
-
- with user <- User.get_by_nickname(nickname) do
- User.deactivate(user)
- end
- end
-end
diff --git a/lib/mix/tasks/generate_config.ex b/lib/mix/tasks/generate_config.ex
deleted file mode 100644
index e3cbbf131..000000000
--- a/lib/mix/tasks/generate_config.ex
+++ /dev/null
@@ -1,47 +0,0 @@
-defmodule Mix.Tasks.GenerateConfig do
- use Mix.Task
-
- @moduledoc """
- Generate a new config
-
- ## Usage
- ``mix generate_config``
-
- This mix task is interactive, and will overwrite the config present at ``config/generated_config.exs``.
- """
-
- def run(_) do
- IO.puts("Answer a few questions to generate a new config\n")
- IO.puts("--- THIS WILL OVERWRITE YOUR config/generated_config.exs! ---\n")
- domain = IO.gets("What is your domain name? (e.g. pleroma.soykaf.com): ") |> String.trim()
- name = IO.gets("What is the name of your instance? (e.g. Pleroma/Soykaf): ") |> String.trim()
- email = IO.gets("What's your admin email address: ") |> String.trim()
-
- secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
- dbpass = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
-
- resultSql = EEx.eval_file("lib/mix/tasks/sample_psql.eex", dbpass: dbpass)
-
- result =
- EEx.eval_file(
- "lib/mix/tasks/sample_config.eex",
- domain: domain,
- email: email,
- name: name,
- secret: secret,
- dbpass: dbpass
- )
-
- IO.puts(
- "\nWriting config to config/generated_config.exs.\n\nCheck it and configure your database, then copy it to either config/dev.secret.exs or config/prod.secret.exs"
- )
-
- File.write("config/generated_config.exs", result)
-
- IO.puts(
- "\nWriting setup_db.psql, please run it as postgre superuser, i.e.: sudo su postgres -c 'psql -f config/setup_db.psql'"
- )
-
- File.write("config/setup_db.psql", resultSql)
- end
-end
diff --git a/lib/mix/tasks/generate_invite_token.ex b/lib/mix/tasks/generate_invite_token.ex
deleted file mode 100644
index 418ef3790..000000000
--- a/lib/mix/tasks/generate_invite_token.ex
+++ /dev/null
@@ -1,32 +0,0 @@
-defmodule Mix.Tasks.GenerateInviteToken do
- use Mix.Task
-
- @moduledoc """
- Generates invite token
-
- This is in the form of a URL to be used by the Invited user to register themselves.
-
- ## Usage
- ``mix generate_invite_token``
- """
- def run([]) do
- Mix.Task.run("app.start")
-
- with {:ok, token} <- Pleroma.UserInviteToken.create_token() do
- IO.puts("Generated user invite token")
-
- IO.puts(
- "Url: #{
- Pleroma.Web.Router.Helpers.redirect_url(
- Pleroma.Web.Endpoint,
- :registration_page,
- token.token
- )
- }"
- )
- else
- _ ->
- IO.puts("Error creating token")
- end
- end
-end
diff --git a/lib/mix/tasks/generate_password_reset.ex b/lib/mix/tasks/generate_password_reset.ex
deleted file mode 100644
index f7f4c4f59..000000000
--- a/lib/mix/tasks/generate_password_reset.ex
+++ /dev/null
@@ -1,33 +0,0 @@
-defmodule Mix.Tasks.GeneratePasswordReset do
- use Mix.Task
- alias Pleroma.User
-
- @moduledoc """
- Generate password reset link for user
-
- Usage: ``mix generate_password_reset <nickname>``
-
- Example: ``mix generate_password_reset lain``
- """
- def run([nickname]) do
- Mix.Task.run("app.start")
-
- with %User{local: true} = user <- User.get_by_nickname(nickname),
- {:ok, token} <- Pleroma.PasswordResetToken.create_token(user) do
- IO.puts("Generated password reset token for #{user.nickname}")
-
- IO.puts(
- "Url: #{
- Pleroma.Web.Router.Helpers.util_url(
- Pleroma.Web.Endpoint,
- :show_password_reset,
- token.token
- )
- }"
- )
- else
- _ ->
- IO.puts("No local user #{nickname}")
- end
- end
-end
diff --git a/lib/mix/tasks/make_moderator.ex b/lib/mix/tasks/make_moderator.ex
deleted file mode 100644
index 8dc0a04dd..000000000
--- a/lib/mix/tasks/make_moderator.ex
+++ /dev/null
@@ -1,38 +0,0 @@
-defmodule Mix.Tasks.SetModerator do
- @moduledoc """
- Set moderator to a local user
-
- Usage: ``mix set_moderator <nickname>``
-
- Example: ``mix set_moderator lain``
- """
-
- use Mix.Task
- import Ecto.Changeset
- alias Pleroma.{Repo, User}
-
- def run([nickname | rest]) do
- Application.ensure_all_started(:pleroma)
-
- moderator =
- case rest do
- [moderator] -> moderator == "true"
- _ -> true
- end
-
- with %User{local: true} = user <- User.get_by_nickname(nickname) do
- info_cng = User.Info.admin_api_update(user.info, %{is_moderator: !!moderator})
-
- user_cng =
- Ecto.Changeset.change(user)
- |> put_embed(:info, info_cng)
-
- {:ok, user} = User.update_and_set_cache(user_cng)
-
- IO.puts("Moderator status of #{nickname}: #{user.info.is_moderator}")
- else
- _ ->
- IO.puts("No local user #{nickname}")
- end
- end
-end
diff --git a/lib/mix/tasks/migrate_local_uploads.ex b/lib/mix/tasks/migrate_local_uploads.ex
deleted file mode 100644
index 8f9e210c0..000000000
--- a/lib/mix/tasks/migrate_local_uploads.ex
+++ /dev/null
@@ -1,97 +0,0 @@
-defmodule Mix.Tasks.MigrateLocalUploads do
- use Mix.Task
- import Mix.Ecto
- alias Pleroma.{Upload, Uploaders.Local, Uploaders.S3}
- require Logger
-
- @log_every 50
- @shortdoc "Migrate uploads from local to remote storage"
-
- def run([target_uploader | args]) do
- delete? = Enum.member?(args, "--delete")
- Application.ensure_all_started(:pleroma)
-
- local_path = Pleroma.Config.get!([Local, :uploads])
- uploader = Module.concat(Pleroma.Uploaders, target_uploader)
-
- unless Code.ensure_loaded?(uploader) do
- raise("The uploader #{inspect(uploader)} is not an existing/loaded module.")
- end
-
- target_enabled? = Pleroma.Config.get([Upload, :uploader]) == uploader
-
- unless target_enabled? do
- Pleroma.Config.put([Upload, :uploader], uploader)
- end
-
- Logger.info("Migrating files from local #{local_path} to #{to_string(uploader)}")
-
- if delete? do
- Logger.warn(
- "Attention: uploaded files will be deleted, hope you have backups! (--delete ; cancel with ^C)"
- )
-
- :timer.sleep(:timer.seconds(5))
- end
-
- uploads =
- File.ls!(local_path)
- |> Enum.map(fn id ->
- root_path = Path.join(local_path, id)
-
- cond do
- File.dir?(root_path) ->
- files = for file <- File.ls!(root_path), do: {id, file, Path.join([root_path, file])}
-
- case List.first(files) do
- {id, file, path} ->
- {%Pleroma.Upload{id: id, name: file, path: id <> "/" <> file, tempfile: path},
- root_path}
-
- _ ->
- nil
- end
-
- File.exists?(root_path) ->
- file = Path.basename(id)
- [hash, ext] = String.split(id, ".")
- {%Pleroma.Upload{id: hash, name: file, path: file, tempfile: root_path}, root_path}
-
- true ->
- nil
- end
- end)
- |> Enum.filter(& &1)
-
- total_count = length(uploads)
- Logger.info("Found #{total_count} uploads")
-
- uploads
- |> Task.async_stream(
- fn {upload, root_path} ->
- case Upload.store(upload, uploader: uploader, filters: [], size_limit: nil) do
- {:ok, _} ->
- if delete?, do: File.rm_rf!(root_path)
- Logger.debug("uploaded: #{inspect(upload.path)} #{inspect(upload)}")
- :ok
-
- error ->
- Logger.error("failed to upload #{inspect(upload.path)}: #{inspect(error)}")
- end
- end,
- timeout: 150_000
- )
- |> Stream.chunk_every(@log_every)
- |> Enum.reduce(0, fn done, count ->
- count = count + length(done)
- Logger.info("Uploaded #{count}/#{total_count} files")
- count
- end)
-
- Logger.info("Done!")
- end
-
- def run(_) do
- Logger.error("Usage: migrate_local_uploads S3|Swift [--delete]")
- end
-end
diff --git a/lib/mix/tasks/pleroma/common.ex b/lib/mix/tasks/pleroma/common.ex
new file mode 100644
index 000000000..06893af05
--- /dev/null
+++ b/lib/mix/tasks/pleroma/common.ex
@@ -0,0 +1,24 @@
+defmodule Mix.Tasks.Pleroma.Common do
+ @shortdoc "Common functions to be reused in mix tasks"
+ def start_pleroma do
+ Mix.Task.run("app.start")
+ end
+
+ def get_option(options, opt, prompt, defval \\ nil, defname \\ nil) do
+ Keyword.get(options, opt) ||
+ case Mix.shell().prompt("#{prompt} [#{defname || defval}]") do
+ "\n" ->
+ case defval do
+ nil -> get_option(options, opt, prompt, defval)
+ defval -> defval
+ end
+
+ opt ->
+ opt |> String.trim()
+ end
+ end
+
+ def escape_sh_path(path) do
+ ~S(') <> String.replace(path, ~S('), ~S(\')) <> ~S(')
+ end
+end
diff --git a/lib/mix/tasks/pleroma/instance.ex b/lib/mix/tasks/pleroma/instance.ex
new file mode 100644
index 000000000..c66322707
--- /dev/null
+++ b/lib/mix/tasks/pleroma/instance.ex
@@ -0,0 +1,157 @@
+defmodule Mix.Tasks.Pleroma.Instance do
+ use Mix.Task
+ alias Pleroma.{Repo, User}
+ alias Mix.Tasks.Pleroma.Common
+
+ @shortdoc "Manages Pleroma instance"
+ @moduledoc """
+ Manages Pleroma instance.
+
+ ## Generate a new instance config.
+
+ mix pleroma.instance gen [OPTION...]
+
+ If any options are left unspecified, you will be prompted interactively
+
+ ## Options
+
+ - `-f`, `--force` - overwrite any output files
+ - `-o PATH`, `--output PATH` - the output file for the generated configuration
+ - `--output-psql PATH` - the output file for the generated PostgreSQL setup
+ - `--domain DOMAIN` - the domain of your instance
+ - `--instance-name INSTANCE_NAME` - the name of your instance
+ - `--admin-email ADMIN_EMAIL` - the email address of the instance admin
+ - `--dbhost HOSTNAME` - the hostname of the PostgreSQL database to use
+ - `--dbname DBNAME` - the name of the database to use
+ - `--dbuser DBUSER` - the user (aka role) to use for the database connection
+ - `--dbpass DBPASS` - the password to use for the database connection
+ """
+
+ def run(["gen" | rest]) do
+ {options, [], []} =
+ OptionParser.parse(
+ rest,
+ strict: [
+ force: :boolean,
+ output: :string,
+ output_psql: :string,
+ domain: :string,
+ instance_name: :string,
+ admin_email: :string,
+ dbhost: :string,
+ dbname: :string,
+ dbuser: :string,
+ dbpass: :string
+ ],
+ aliases: [
+ o: :output,
+ f: :force
+ ]
+ )
+
+ paths =
+ [config_path, psql_path] = [
+ Keyword.get(options, :output, "config/generated_config.exs"),
+ Keyword.get(options, :output_psql, "config/setup_db.psql")
+ ]
+
+ will_overwrite = Enum.filter(paths, &File.exists?/1)
+ proceed? = Enum.empty?(will_overwrite) or Keyword.get(options, :force, false)
+
+ unless not proceed? do
+ domain =
+ Common.get_option(
+ options,
+ :domain,
+ "What domain will your instance use? (e.g pleroma.soykaf.com)"
+ )
+
+ name =
+ Common.get_option(
+ options,
+ :name,
+ "What is the name of your instance? (e.g. Pleroma/Soykaf)"
+ )
+
+ email = Common.get_option(options, :admin_email, "What is your admin email address?")
+
+ dbhost =
+ Common.get_option(options, :dbhost, "What is the hostname of your database?", "localhost")
+
+ dbname =
+ Common.get_option(options, :dbname, "What is the name of your database?", "pleroma_dev")
+
+ dbuser =
+ Common.get_option(
+ options,
+ :dbuser,
+ "What is the user used to connect to your database?",
+ "pleroma"
+ )
+
+ dbpass =
+ Common.get_option(
+ options,
+ :dbpass,
+ "What is the password used to connect to your database?",
+ :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64),
+ "autogenerated"
+ )
+
+ secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
+ {web_push_public_key, web_push_private_key} = :crypto.generate_key(:ecdh, :prime256v1)
+
+ result_config =
+ EEx.eval_file(
+ "sample_config.eex" |> Path.expand(__DIR__),
+ domain: domain,
+ email: email,
+ name: name,
+ dbhost: dbhost,
+ dbname: dbname,
+ dbuser: dbuser,
+ dbpass: dbpass,
+ version: Pleroma.Mixfile.project() |> Keyword.get(:version),
+ secret: secret,
+ web_push_public_key: Base.url_encode64(web_push_public_key, padding: false),
+ web_push_private_key: Base.url_encode64(web_push_private_key, padding: false)
+ )
+
+ result_psql =
+ EEx.eval_file(
+ "sample_psql.eex" |> Path.expand(__DIR__),
+ dbname: dbname,
+ dbuser: dbuser,
+ dbpass: dbpass
+ )
+
+ Mix.shell().info(
+ "Writing config to #{config_path}. You should rename it to config/prod.secret.exs or config/dev.secret.exs."
+ )
+
+ File.write(config_path, result_config)
+ Mix.shell().info("Writing #{psql_path}.")
+ File.write(psql_path, result_psql)
+
+ Mix.shell().info(
+ "\n" <>
+ """
+ To get started:
+ 1. Verify the contents of the generated files.
+ 2. Run `sudo -u postgres psql -f #{Common.escape_sh_path(psql_path)}`.
+ """ <>
+ if config_path in ["config/dev.secret.exs", "config/prod.secret.exs"] do
+ ""
+ else
+ "3. Run `mv #{Common.escape_sh_path(config_path)} 'config/prod.secret.exs'`."
+ end
+ )
+ else
+ Mix.shell().error(
+ "The task would have overwritten the following files:\n" <>
+ (Enum.map(paths, &"- #{&1}\n") |> Enum.join("")) <>
+ "Rerun with `--force` to overwrite them."
+ )
+ end
+ end
+end
diff --git a/lib/mix/tasks/pleroma/relay.ex b/lib/mix/tasks/pleroma/relay.ex
new file mode 100644
index 000000000..2502923af
--- /dev/null
+++ b/lib/mix/tasks/pleroma/relay.ex
@@ -0,0 +1,43 @@
+defmodule Mix.Tasks.Pleroma.Relay do
+ use Mix.Task
+ alias Pleroma.Web.ActivityPub.Relay
+ alias Mix.Tasks.Pleroma.Common
+
+ @shortdoc "Manages remote relays"
+ @moduledoc """
+ Manages remote relays
+
+ ## Follow a remote relay
+
+ ``mix pleroma.relay follow <relay_url>``
+
+ Example: ``mix pleroma.relay follow https://example.org/relay``
+
+ ## Unfollow a remote relay
+
+ ``mix pleroma.relay unfollow <relay_url>``
+
+ Example: ``mix pleroma.relay unfollow https://example.org/relay``
+ """
+ def run(["follow", target]) do
+ Common.start_pleroma()
+
+ with {:ok, activity} <- Relay.follow(target) do
+ # put this task to sleep to allow the genserver to push out the messages
+ :timer.sleep(500)
+ else
+ {:error, e} -> Mix.shell().error("Error while following #{target}: #{inspect(e)}")
+ end
+ end
+
+ def run(["unfollow", target]) do
+ Common.start_pleroma()
+
+ with {:ok, activity} <- Relay.follow(target) do
+ # put this task to sleep to allow the genserver to push out the messages
+ :timer.sleep(500)
+ else
+ {:error, e} -> Mix.shell().error("Error while following #{target}: #{inspect(e)}")
+ end
+ end
+end
diff --git a/lib/mix/tasks/sample_config.eex b/lib/mix/tasks/pleroma/sample_config.eex
index 462c34636..0cd572797 100644
--- a/lib/mix/tasks/sample_config.eex
+++ b/lib/mix/tasks/pleroma/sample_config.eex
@@ -1,3 +1,8 @@
+# Pleroma instance configuration
+
+# NOTE: This file should not be committed to a repo or otherwise made public
+# without removing sensitive information.
+
use Mix.Config
config :pleroma, Pleroma.Web.Endpoint,
@@ -16,15 +21,20 @@ config :pleroma, :media_proxy,
redirect_on_failure: true
#base_url: "https://cache.pleroma.social"
-# Configure your database
config :pleroma, Pleroma.Repo,
adapter: Ecto.Adapters.Postgres,
- username: "pleroma",
+ username: "<%= dbuser %>",
password: "<%= dbpass %>",
- database: "pleroma_dev",
- hostname: "localhost",
+ database: "<%= dbname %>",
+ hostname: "<%= dbhost %>",
pool_size: 10
+# Configure web push notifications
+config :web_push_encryption, :vapid_details,
+ subject: "mailto:<%= email %>",
+ public_key: "<%= web_push_public_key %>",
+ private_key: "<%= web_push_private_key %>"
+
# Enable Strict-Transport-Security once SSL is working:
# config :pleroma, :http_security,
# sts: true
@@ -50,9 +60,9 @@ config :pleroma, Pleroma.Repo,
# Configure Openstack Swift support if desired.
-#
-# Many openstack deployments are different, so config is left very open with
-# no assumptions made on which provider you're using. This should allow very
+#
+# Many openstack deployments are different, so config is left very open with
+# no assumptions made on which provider you're using. This should allow very
# wide support without needing separate handlers for OVH, Rackspace, etc.
#
# config :pleroma, Pleroma.Uploaders.Swift,
diff --git a/lib/mix/tasks/sample_psql.eex b/lib/mix/tasks/pleroma/sample_psql.eex
index c89b34ef2..c89b34ef2 100644
--- a/lib/mix/tasks/sample_psql.eex
+++ b/lib/mix/tasks/pleroma/sample_psql.eex
diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex
new file mode 100644
index 000000000..590553443
--- /dev/null
+++ b/lib/mix/tasks/pleroma/user.ex
@@ -0,0 +1,293 @@
+defmodule Mix.Tasks.Pleroma.User do
+ use Mix.Task
+ import Ecto.Changeset
+ alias Pleroma.{Repo, User}
+ alias Mix.Tasks.Pleroma.Common
+
+ @shortdoc "Manages Pleroma users"
+ @moduledoc """
+ Manages Pleroma users.
+
+ ## Create a new user.
+
+ mix pleroma.user new NICKNAME EMAIL [OPTION...]
+
+ Options:
+ - `--name NAME` - the user's name (i.e., "Lain Iwakura")
+ - `--bio BIO` - the user's bio
+ - `--password PASSWORD` - the user's password
+ - `--moderator`/`--no-moderator` - whether the user is a moderator
+ - `--admin`/`--no-admin` - whether the user is an admin
+
+ ## Generate an invite link.
+
+ mix pleroma.user invite
+
+ ## Delete the user's account.
+
+ mix pleroma.user rm NICKNAME
+
+ ## Deactivate or activate the user's account.
+
+ mix pleroma.user toggle_activated NICKNAME
+
+ ## Unsubscribe local users from user's account and deactivate it
+
+ mix pleroma.user unsubscribe NICKNAME
+
+ ## Create a password reset link.
+
+ mix pleroma.user reset_password NICKNAME
+
+ ## Set the value of the given user's settings.
+
+ mix pleroma.user set NICKNAME [OPTION...]
+
+ Options:
+ - `--locked`/`--no-locked` - whether the user's account is locked
+ - `--moderator`/`--no-moderator` - whether the user is a moderator
+ - `--admin`/`--no-admin` - whether the user is an admin
+ """
+ def run(["new", nickname, email | rest]) do
+ {options, [], []} =
+ OptionParser.parse(
+ rest,
+ strict: [
+ name: :string,
+ bio: :string,
+ password: :string,
+ moderator: :boolean,
+ admin: :boolean
+ ]
+ )
+
+ name = Keyword.get(options, :name, nickname)
+ bio = Keyword.get(options, :bio, "")
+
+ {password, generated_password?} =
+ case Keyword.get(options, :password) do
+ nil ->
+ {:crypto.strong_rand_bytes(16) |> Base.encode64(), true}
+
+ password ->
+ {password, false}
+ end
+
+ moderator? = Keyword.get(options, :moderator, false)
+ admin? = Keyword.get(options, :admin, false)
+
+ Mix.shell().info("""
+ A user will be created with the following information:
+ - nickname: #{nickname}
+ - email: #{email}
+ - password: #{
+ if(generated_password?, do: "[generated; a reset link will be created]", else: password)
+ }
+ - name: #{name}
+ - bio: #{bio}
+ - moderator: #{if(moderator?, do: "true", else: "false")}
+ - admin: #{if(admin?, do: "true", else: "false")}
+ """)
+
+ proceed? = Mix.shell().yes?("Continue?")
+
+ unless not proceed? do
+ Common.start_pleroma()
+
+ params =
+ %{
+ nickname: nickname,
+ email: email,
+ password: password,
+ password_confirmation: password,
+ name: name,
+ bio: bio
+ }
+ |> IO.inspect()
+
+ user = User.register_changeset(%User{}, params)
+ Repo.insert!(user)
+
+ Mix.shell().info("User #{nickname} created")
+
+ if moderator? do
+ run(["set", nickname, "--moderator"])
+ end
+
+ if admin? do
+ run(["set", nickname, "--admin"])
+ end
+
+ if generated_password? do
+ run(["reset_password", nickname])
+ end
+ else
+ Mix.shell().info("User will not be created.")
+ end
+ end
+
+ def run(["rm", nickname]) do
+ Common.start_pleroma()
+
+ with %User{local: true} = user <- User.get_by_nickname(nickname) do
+ User.delete(user)
+ Mix.shell().info("User #{nickname} deleted.")
+ else
+ _ ->
+ Mix.shell().error("No local user #{nickname}")
+ end
+ end
+
+ def run(["toggle_activated", nickname]) do
+ Common.start_pleroma()
+
+ with %User{} = user <- User.get_by_nickname(nickname) do
+ User.deactivate(user, !user.info["deactivated"])
+ Mix.shell().info("Activation status of #{nickname}: #{user.info["deactivated"]}")
+ else
+ _ ->
+ Mix.shell().error("No user #{nickname}")
+ end
+ end
+
+ def run(["reset_password", nickname]) do
+ Common.start_pleroma()
+
+ with %User{local: true} = user <- User.get_by_nickname(nickname),
+ {:ok, token} <- Pleroma.PasswordResetToken.create_token(user) do
+ Mix.shell().info("Generated password reset token for #{user.nickname}")
+
+ IO.puts(
+ "URL: #{
+ Pleroma.Web.Router.Helpers.util_url(
+ Pleroma.Web.Endpoint,
+ :show_password_reset,
+ token.token
+ )
+ }"
+ )
+ else
+ _ ->
+ Mix.shell().error("No local user #{nickname}")
+ end
+ end
+
+ def run(["unsubscribe", nickname]) do
+ Common.start_pleroma()
+
+ with %User{} = user <- User.get_by_nickname(nickname) do
+ Mix.shell().info("Deactivating #{user.nickname}")
+ User.deactivate(user)
+
+ {:ok, friends} = User.get_friends(user)
+
+ Enum.each(friends, fn friend ->
+ user = Repo.get(User, user.id)
+
+ Mix.shell().info("Unsubscribing #{friend.nickname} from #{user.nickname}")
+ User.unfollow(user, friend)
+ end)
+
+ :timer.sleep(500)
+
+ user = Repo.get(User, user.id)
+
+ if length(user.following) == 0 do
+ Mix.shell().info("Successfully unsubscribed all followers from #{user.nickname}")
+ end
+ else
+ _ ->
+ Mix.shell().error("No user #{nickname}")
+ end
+ end
+
+ def run(["set", nickname | rest]) do
+ Common.start_pleroma()
+
+ {options, [], []} =
+ OptionParser.parse(
+ rest,
+ strict: [
+ moderator: :boolean,
+ admin: :boolean,
+ locked: :boolean
+ ]
+ )
+
+ with %User{local: true} = user <- User.get_by_nickname(nickname) do
+ case Keyword.get(options, :moderator) do
+ nil -> nil
+ value -> set_moderator(user, value)
+ end
+
+ case Keyword.get(options, :locked) do
+ nil -> nil
+ value -> set_locked(user, value)
+ end
+
+ case Keyword.get(options, :admin) do
+ nil -> nil
+ value -> set_admin(user, value)
+ end
+ else
+ _ ->
+ Mix.shell().error("No local user #{nickname}")
+ end
+ end
+
+ defp set_moderator(user, value) do
+ info_cng = User.Info.admin_api_update(user.info, %{is_moderator: value})
+
+ user_cng =
+ Ecto.Changeset.change(user)
+ |> put_embed(:info, info_cng)
+
+ {:ok, user} = User.update_and_set_cache(user_cng)
+
+ Mix.shell().info("Moderator status of #{user.nickname}: #{user.info.is_moderator}")
+ end
+
+ defp set_admin(user, value) do
+ info_cng = User.Info.admin_api_update(user.info, %{is_admin: value})
+
+ user_cng =
+ Ecto.Changeset.change(user)
+ |> put_embed(:info, info_cng)
+
+ {:ok, user} = User.update_and_set_cache(user_cng)
+
+ Mix.shell().info("Admin status of #{user.nickname}: #{user.info.is_moderator}")
+ end
+
+ defp set_locked(user, value) do
+ info_cng = User.Info.user_upgrade(user.info, %{locked: value})
+
+ user_cng =
+ Ecto.Changeset.change(user)
+ |> put_embed(:info, info_cng)
+
+ {:ok, user} = User.update_and_set_cache(user_cng)
+
+ Mix.shell().info("Locked status of #{user.nickname}: #{user.info.locked}")
+ end
+
+ def run(["invite"]) do
+ Common.start_pleroma()
+
+ with {:ok, token} <- Pleroma.UserInviteToken.create_token() do
+ Mix.shell().info("Generated user invite token")
+
+ url =
+ Pleroma.Web.Router.Helpers.redirect_url(
+ Pleroma.Web.Endpoint,
+ :registration_page,
+ token.token
+ )
+
+ IO.puts(url)
+ else
+ _ ->
+ Mix.shell().error("Could not create invite token.")
+ end
+ end
+end
diff --git a/lib/mix/tasks/reactivate_user.ex b/lib/mix/tasks/reactivate_user.ex
deleted file mode 100644
index a30d3ac8b..000000000
--- a/lib/mix/tasks/reactivate_user.ex
+++ /dev/null
@@ -1,19 +0,0 @@
-defmodule Mix.Tasks.ReactivateUser do
- use Mix.Task
- alias Pleroma.User
-
- @moduledoc """
- Reactivate a user
-
- Usage: ``mix reactivate_user <nickname>``
-
- Example: ``mix reactivate_user lain``
- """
- def run([nickname]) do
- Mix.Task.run("app.start")
-
- with user <- User.get_by_nickname(nickname) do
- User.deactivate(user, false)
- end
- end
-end
diff --git a/lib/mix/tasks/register_user.ex b/lib/mix/tasks/register_user.ex
deleted file mode 100644
index 1f5321093..000000000
--- a/lib/mix/tasks/register_user.ex
+++ /dev/null
@@ -1,30 +0,0 @@
-defmodule Mix.Tasks.RegisterUser do
- @moduledoc """
- Manually register a local user
-
- Usage: ``mix register_user <name> <nickname> <email> <bio> <password>``
-
- Example: ``mix register_user 仮面の告白 lain lain@example.org "blushy-crushy fediverse idol + pleroma dev" pleaseDontHeckLain``
- """
-
- use Mix.Task
- alias Pleroma.{Repo, User}
-
- @shortdoc "Register user"
- def run([name, nickname, email, bio, password]) do
- Mix.Task.run("app.start")
-
- params = %{
- name: name,
- nickname: nickname,
- email: email,
- password: password,
- password_confirmation: password,
- bio: bio
- }
-
- user = User.register_changeset(%User{}, params)
-
- Repo.insert!(user)
- end
-end
diff --git a/lib/mix/tasks/relay_follow.ex b/lib/mix/tasks/relay_follow.ex
deleted file mode 100644
index 85b1c024d..000000000
--- a/lib/mix/tasks/relay_follow.ex
+++ /dev/null
@@ -1,24 +0,0 @@
-defmodule Mix.Tasks.RelayFollow do
- use Mix.Task
- require Logger
- alias Pleroma.Web.ActivityPub.Relay
-
- @shortdoc "Follows a remote relay"
- @moduledoc """
- Follows a remote relay
-
- Usage: ``mix relay_follow <relay_url>``
-
- Example: ``mix relay_follow https://example.org/relay``
- """
- def run([target]) do
- Mix.Task.run("app.start")
-
- with {:ok, activity} <- Relay.follow(target) do
- # put this task to sleep to allow the genserver to push out the messages
- :timer.sleep(500)
- else
- {:error, e} -> Mix.shell().error("Error while following #{target}: #{inspect(e)}")
- end
- end
-end
diff --git a/lib/mix/tasks/relay_unfollow.ex b/lib/mix/tasks/relay_unfollow.ex
deleted file mode 100644
index 237fb771c..000000000
--- a/lib/mix/tasks/relay_unfollow.ex
+++ /dev/null
@@ -1,23 +0,0 @@
-defmodule Mix.Tasks.RelayUnfollow do
- use Mix.Task
- require Logger
- alias Pleroma.Web.ActivityPub.Relay
-
- @moduledoc """
- Unfollows a remote relay
-
- Usage: ``mix relay_follow <relay_url>``
-
- Example: ``mix relay_follow https://example.org/relay``
- """
- def run([target]) do
- Mix.Task.run("app.start")
-
- with {:ok, activity} <- Relay.follow(target) do
- # put this task to sleep to allow the genserver to push out the messages
- :timer.sleep(500)
- else
- {:error, e} -> Mix.shell().error("Error while following #{target}: #{inspect(e)}")
- end
- end
-end
diff --git a/lib/mix/tasks/rm_user.ex b/lib/mix/tasks/rm_user.ex
deleted file mode 100644
index 50463046c..000000000
--- a/lib/mix/tasks/rm_user.ex
+++ /dev/null
@@ -1,19 +0,0 @@
-defmodule Mix.Tasks.RmUser do
- use Mix.Task
- alias Pleroma.User
-
- @moduledoc """
- Permanently deletes a user
-
- Usage: ``mix rm_user [nickname]``
-
- Example: ``mix rm_user lain``
- """
- def run([nickname]) do
- Mix.Task.run("app.start")
-
- with %User{local: true} = user <- User.get_by_nickname(nickname) do
- {:ok, _} = User.delete(user)
- end
- end
-end
diff --git a/lib/mix/tasks/set_admin.ex b/lib/mix/tasks/set_admin.ex
deleted file mode 100644
index ac26516f1..000000000
--- a/lib/mix/tasks/set_admin.ex
+++ /dev/null
@@ -1,34 +0,0 @@
-defmodule Mix.Tasks.SetAdmin do
- use Mix.Task
- import Ecto.Changeset
- alias Pleroma.User
-
- @doc """
- Sets admin status
- Usage: set_admin nickname [true|false]
- """
- def run([nickname | rest]) do
- Application.ensure_all_started(:pleroma)
-
- admin =
- case rest do
- [admin] -> admin == "true"
- _ -> true
- end
-
- with %User{local: true} = user <- User.get_by_nickname(nickname) do
- info_cng = User.Info.admin_api_update(user.info, %{is_admin: !!admin})
-
- user_cng =
- Ecto.Changeset.change(user)
- |> put_embed(:info, info_cng)
-
- {:ok, user} = User.update_and_set_cache(user_cng)
-
- IO.puts("Admin status of #{nickname}: #{user.info.is_admin}")
- else
- _ ->
- IO.puts("No local user #{nickname}")
- end
- end
-end
diff --git a/lib/mix/tasks/set_locked.ex b/lib/mix/tasks/set_locked.ex
deleted file mode 100644
index e93a63505..000000000
--- a/lib/mix/tasks/set_locked.ex
+++ /dev/null
@@ -1,40 +0,0 @@
-defmodule Mix.Tasks.SetLocked do
- @moduledoc """
- Lock a local user
-
- The local user will then have to manually accept/reject followers. This can also be done by the user into their settings.
-
- Usage: ``mix set_locked <username>``
-
- Example: ``mix set_locked lain``
- """
-
- use Mix.Task
- import Ecto.Changeset
- alias Pleroma.{Repo, User}
-
- def run([nickname | rest]) do
- Application.ensure_all_started(:pleroma)
-
- locked =
- case rest do
- [locked] -> locked == "true"
- _ -> true
- end
-
- with %User{local: true} = user <- User.get_by_nickname(nickname) do
- info_cng = User.Info.profile_update(user.info, %{locked: !!locked})
-
- user_cng =
- Ecto.Changeset.change(user)
- |> put_embed(:info, info_cng)
-
- {:ok, user} = User.update_and_set_cache(user_cng)
-
- IO.puts("Locked status of #{nickname}: #{user.info.locked}")
- else
- _ ->
- IO.puts("No local user #{nickname}")
- end
- end
-end
diff --git a/lib/mix/tasks/unsubscribe_user.ex b/lib/mix/tasks/unsubscribe_user.ex
deleted file mode 100644
index 62ea61a5c..000000000
--- a/lib/mix/tasks/unsubscribe_user.ex
+++ /dev/null
@@ -1,38 +0,0 @@
-defmodule Mix.Tasks.UnsubscribeUser do
- use Mix.Task
- alias Pleroma.{User, Repo}
- require Logger
-
- @moduledoc """
- Deactivate and Unsubscribe local users from a user
-
- Usage: ``mix unsubscribe_user <nickname>``
-
- Example: ``mix unsubscribe_user lain``
- """
- def run([nickname]) do
- Mix.Task.run("app.start")
-
- with %User{} = user <- User.get_by_nickname(nickname) do
- Logger.info("Deactivating #{user.nickname}")
- User.deactivate(user)
-
- {:ok, friends} = User.get_friends(user)
-
- Enum.each(friends, fn friend ->
- user = Repo.get(User, user.id)
-
- Logger.info("Unsubscribing #{friend.nickname} from #{user.nickname}")
- User.unfollow(user, friend)
- end)
-
- :timer.sleep(500)
-
- user = Repo.get(User, user.id)
-
- if length(user.following) == 0 do
- Logger.info("Successfully unsubscribed all followers from #{user.nickname}")
- end
- end
- end
-end
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index cc68d9669..0b0ec0197 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -66,7 +66,8 @@ defmodule Pleroma.Application do
),
worker(Pleroma.Web.Federator.RetryQueue, []),
worker(Pleroma.Web.Federator, []),
- worker(Pleroma.Stats, [])
+ worker(Pleroma.Stats, []),
+ worker(Pleroma.Web.Push, [])
] ++
streamer_child() ++
chat_child() ++
diff --git a/lib/pleroma/emoji.ex b/lib/pleroma/emoji.ex
index 0a5e1d5ce..523dea652 100644
--- a/lib/pleroma/emoji.ex
+++ b/lib/pleroma/emoji.ex
@@ -10,7 +10,7 @@ defmodule Pleroma.Emoji do
"""
use GenServer
@ets __MODULE__.Ets
- @ets_options [:set, :protected, :named_table, {:read_concurrency, true}]
+ @ets_options [:ordered_set, :protected, :named_table, {:read_concurrency, true}]
@doc false
def start_link() do
diff --git a/lib/pleroma/http/connection.ex b/lib/pleroma/http/connection.ex
index 5e8f2aabd..db46f9e55 100644
--- a/lib/pleroma/http/connection.ex
+++ b/lib/pleroma/http/connection.ex
@@ -3,7 +3,12 @@ defmodule Pleroma.HTTP.Connection do
Connection for http-requests.
"""
- @hackney_options [pool: :default]
+ @hackney_options [
+ pool: :default,
+ timeout: 10000,
+ recv_timeout: 20000,
+ follow_redirect: true
+ ]
@adapter Application.get_env(:tesla, :adapter)
@doc """
diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex
index a3aeb1221..a40b8f8c9 100644
--- a/lib/pleroma/notification.ex
+++ b/lib/pleroma/notification.ex
@@ -110,6 +110,7 @@ defmodule Pleroma.Notification do
notification = %Notification{user_id: user.id, activity: activity}
{:ok, notification} = Repo.insert(notification)
Pleroma.Web.Streamer.stream("user", notification)
+ Pleroma.Web.Push.send(notification)
notification
end
end
diff --git a/lib/pleroma/plugs/oauth_plug.ex b/lib/pleroma/plugs/oauth_plug.ex
index 75f9209c2..8b99a74d1 100644
--- a/lib/pleroma/plugs/oauth_plug.ex
+++ b/lib/pleroma/plugs/oauth_plug.ex
@@ -17,7 +17,9 @@ defmodule Pleroma.Plugs.OAuthPlug do
def call(conn, _) do
with {:ok, token} <- fetch_token(conn),
{:ok, user} <- fetch_user(token) do
- assign(conn, :user, user)
+ conn
+ |> assign(:token, token)
+ |> assign(:user, user)
else
_ -> conn
end
diff --git a/lib/pleroma/reverse_proxy.ex b/lib/pleroma/reverse_proxy.ex
index ad9dc82fe..4ca84152a 100644
--- a/lib/pleroma/reverse_proxy.ex
+++ b/lib/pleroma/reverse_proxy.ex
@@ -56,7 +56,7 @@ defmodule Pleroma.ReverseProxy do
@hackney Application.get_env(:pleroma, :hackney, :hackney)
@httpoison Application.get_env(:pleroma, :httpoison, HTTPoison)
- @default_hackney_options [{:follow_redirect, true}]
+ @default_hackney_options []
@inline_content_types [
"image/gif",
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 198f05f8a..a290db04a 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -64,10 +64,6 @@ defmodule Pleroma.User do
|> validate_required([:following])
end
- def info_changeset(struct, params \\ %{}) do
- raise "NOT VALID ANYMORE"
- end
-
def user_info(%User{} = user) do
oneself = if user.local, do: 1, else: 0
diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex
index 7a99787f8..d81b45b8d 100644
--- a/lib/pleroma/user/info.ex
+++ b/lib/pleroma/user/info.ex
@@ -149,6 +149,11 @@ defmodule Pleroma.User.Info do
])
end
+ def mastodon_settings_update(info, params) do
+ info
+ |> cast(params, [:settings])
+ end
+
def set_source_data(info, source_data) do
params = %{source_data: source_data}
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 28da57a10..bf81d8039 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -772,10 +772,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
{:ok, %{body: body, status: code}} when code in 200..299 <-
@httpoison.get(
id,
- [Accept: "application/activity+json"],
- follow_redirect: true,
- timeout: 10000,
- recv_timeout: 20000
+ [{:Accept, "application/activity+json"}]
),
{:ok, data} <- Jason.decode(body),
:ok <- Transmogrifier.contain_origin_from_id(id, data) do
diff --git a/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex b/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex
new file mode 100644
index 000000000..c8c74ede6
--- /dev/null
+++ b/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex
@@ -0,0 +1,40 @@
+defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrepended do
+ alias Pleroma.Object
+
+ @behaviour Pleroma.Web.ActivityPub.MRF
+
+ @reply_prefix Regex.compile!("^re:[[:space:]]*", [:caseless])
+ def filter_by_summary(
+ %{"summary" => parent_summary} = parent,
+ %{"summary" => child_summary} = child
+ )
+ when not is_nil(child_summary) and byte_size(child_summary) > 0 and
+ not is_nil(parent_summary) and byte_size(parent_summary) > 0 do
+ if (child_summary == parent_summary and not Regex.match?(@reply_prefix, child_summary)) or
+ (Regex.match?(@reply_prefix, parent_summary) &&
+ Regex.replace(@reply_prefix, parent_summary, "") == child_summary) do
+ Map.put(child, "summary", "re: " <> child_summary)
+ else
+ child
+ end
+ end
+
+ def filter_by_summary(parent, child), do: child
+
+ def filter(%{"type" => activity_type} = object) when activity_type == "Create" do
+ child = object["object"]
+ in_reply_to = Object.normalize(child["inReplyTo"])
+
+ child =
+ if(in_reply_to,
+ do: filter_by_summary(in_reply_to.data, child),
+ else: child
+ )
+
+ object = Map.put(object, "object", child)
+
+ {:ok, object}
+ end
+
+ def filter(object), do: {:ok, object}
+end
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index eecfc742b..2d7b1a00c 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -2,13 +2,22 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
use Pleroma.Web, :controller
alias Pleroma.{Repo, Object, Activity, User, Notification, Stats}
alias Pleroma.Web
- alias Pleroma.Web.MastodonAPI.{StatusView, AccountView, MastodonView, ListView, FilterView}
+
+ alias Pleroma.Web.MastodonAPI.{
+ StatusView,
+ AccountView,
+ MastodonView,
+ ListView,
+ FilterView,
+ PushSubscriptionView
+ }
+
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.OAuth.{Authorization, Token, App}
alias Pleroma.Web.MediaProxy
- alias Comeonin.Pbkdf2
+
import Ecto.Query
require Logger
@@ -970,9 +979,11 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _params) do
- with new_info <- Map.put(user.info, "settings", settings),
- change <- User.info_changeset(user, %{info: new_info}),
- {:ok, _user} <- User.update_and_set_cache(change) do
+ info_cng = User.Info.mastodon_settings_update(user.info, settings)
+
+ with changeset <- User.update_changeset(user),
+ changeset <- Ecto.Changeset.put_embed(changeset, :info, info_cng),
+ {:ok, user} <- User.update_and_set_cache(changeset) do
conn
|> json(%{})
else
@@ -1160,6 +1171,33 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
json(conn, %{})
end
+ def create_push_subscription(%{assigns: %{user: user, token: token}} = conn, params) do
+ Pleroma.Web.Push.Subscription.delete_if_exists(user, token)
+ {:ok, subscription} = Pleroma.Web.Push.Subscription.create(user, token, params)
+ view = PushSubscriptionView.render("push_subscription.json", subscription: subscription)
+ json(conn, view)
+ end
+
+ def get_push_subscription(%{assigns: %{user: user, token: token}} = conn, _params) do
+ subscription = Pleroma.Web.Push.Subscription.get(user, token)
+ view = PushSubscriptionView.render("push_subscription.json", subscription: subscription)
+ json(conn, view)
+ end
+
+ def update_push_subscription(
+ %{assigns: %{user: user, token: token}} = conn,
+ params
+ ) do
+ {:ok, subscription} = Pleroma.Web.Push.Subscription.update(user, token, params)
+ view = PushSubscriptionView.render("push_subscription.json", subscription: subscription)
+ json(conn, view)
+ end
+
+ def delete_push_subscription(%{assigns: %{user: user, token: token}} = conn, _params) do
+ {:ok, _response} = Pleroma.Web.Push.Subscription.delete(user, token)
+ json(conn, %{})
+ end
+
def errors(conn, _) do
conn
|> put_status(500)
@@ -1180,7 +1218,14 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
url = String.replace(api, "{{host}}", host) |> String.replace("{{user}}", user)
with {:ok, %{status: 200, body: body}} <-
- @httpoison.get(url, [], timeout: timeout, recv_timeout: timeout),
+ @httpoison.get(
+ url,
+ [],
+ adapter: [
+ timeout: timeout,
+ recv_timeout: timeout
+ ]
+ ),
{:ok, data} <- Jason.decode(body) do
data2 =
Enum.slice(data, 0, limit)
diff --git a/lib/pleroma/web/mastodon_api/views/push_subscription_view.ex b/lib/pleroma/web/mastodon_api/views/push_subscription_view.ex
new file mode 100644
index 000000000..68bb45494
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/views/push_subscription_view.ex
@@ -0,0 +1,12 @@
+defmodule Pleroma.Web.MastodonAPI.PushSubscriptionView do
+ use Pleroma.Web, :view
+ alias Pleroma.Web.MastodonAPI.PushSubscriptionView
+
+ def render("push_subscription.json", %{subscription: subscription}) do
+ %{
+ id: to_string(subscription.id),
+ endpoint: subscription.endpoint,
+ alerts: Map.get(subscription.data, "alerts")
+ }
+ end
+end
diff --git a/lib/pleroma/web/ostatus/ostatus.ex b/lib/pleroma/web/ostatus/ostatus.ex
index 67df354db..53d71440e 100644
--- a/lib/pleroma/web/ostatus/ostatus.ex
+++ b/lib/pleroma/web/ostatus/ostatus.ex
@@ -349,12 +349,7 @@ defmodule Pleroma.Web.OStatus do
{:ok, %{body: body, status: code}} when code in 200..299 <-
@httpoison.get(
url,
- [Accept: "application/atom+xml"],
- follow_redirect: true,
- adapter: [
- timeout: 10000,
- recv_timeout: 20000
- ]
+ [{:Accept, "application/atom+xml"}]
) do
Logger.debug("Got document from #{url}, handling...")
handle_incoming(body)
@@ -369,8 +364,7 @@ defmodule Pleroma.Web.OStatus do
Logger.debug("Trying to fetch #{url}")
with true <- String.starts_with?(url, "http"),
- {:ok, %{body: body}} <-
- @httpoison.get(url, [], follow_redirect: true, timeout: 10000, recv_timeout: 20000),
+ {:ok, %{body: body}} <- @httpoison.get(url, []),
{:ok, atom_url} <- get_atom_url(body) do
fetch_activity_from_atom_url(atom_url)
else
diff --git a/lib/pleroma/web/push/push.ex b/lib/pleroma/web/push/push.ex
new file mode 100644
index 000000000..5a873ec19
--- /dev/null
+++ b/lib/pleroma/web/push/push.ex
@@ -0,0 +1,116 @@
+defmodule Pleroma.Web.Push do
+ use GenServer
+
+ alias Pleroma.{Repo, User}
+ alias Pleroma.Web.Push.Subscription
+
+ require Logger
+ import Ecto.Query
+
+ @types ["Create", "Follow", "Announce", "Like"]
+
+ @gcm_api_key nil
+
+ def start_link() do
+ GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
+ end
+
+ def init(:ok) do
+ case Application.get_env(:web_push_encryption, :vapid_details) do
+ nil ->
+ Logger.warn(
+ "VAPID key pair is not found. Please, add VAPID configuration to config. Run `mix web_push.gen.keypair` mix task to create a key pair"
+ )
+
+ :ignore
+
+ _ ->
+ {:ok, %{}}
+ end
+ end
+
+ def send(notification) do
+ if Application.get_env(:web_push_encryption, :vapid_details) do
+ GenServer.cast(Pleroma.Web.Push, {:send, notification})
+ end
+ end
+
+ def handle_cast(
+ {:send, %{activity: %{data: %{"type" => type}}, user_id: user_id} = notification},
+ state
+ )
+ when type in @types do
+ actor = User.get_cached_by_ap_id(notification.activity.data["actor"])
+ body = notification |> format(actor) |> Jason.encode!()
+
+ Subscription
+ |> where(user_id: ^user_id)
+ |> Repo.all()
+ |> Enum.each(fn record ->
+ subscription = %{
+ keys: %{
+ p256dh: record.key_p256dh,
+ auth: record.key_auth
+ },
+ endpoint: record.endpoint
+ }
+
+ case WebPushEncryption.send_web_push(body, subscription, @gcm_api_key) do
+ {:ok, %{status_code: code}} when 400 <= code and code < 500 ->
+ Logger.debug("Removing subscription record")
+ Repo.delete!(record)
+ :ok
+
+ {:ok, %{status_code: code}} when 200 <= code and code < 300 ->
+ :ok
+
+ {:ok, %{status_code: code}} ->
+ Logger.error("Web Push Nonification failed with code: #{code}")
+ :error
+
+ _ ->
+ Logger.error("Web Push Nonification failed with unknown error")
+ :error
+ end
+ end)
+
+ {:noreply, state}
+ end
+
+ def handle_cast({:send, _}, state) do
+ Logger.warn("Unknown notification type")
+ {:noreply, state}
+ end
+
+ def format(%{activity: %{data: %{"type" => "Create"}}}, actor) do
+ %{
+ title: "New Mention",
+ body: "@#{actor.nickname} has mentiond you",
+ icon: User.avatar_url(actor)
+ }
+ end
+
+ def format(%{activity: %{data: %{"type" => "Follow"}}}, actor) do
+ %{
+ title: "New Follower",
+ body: "@#{actor.nickname} has followed you",
+ icon: User.avatar_url(actor)
+ }
+ end
+
+ def format(%{activity: %{data: %{"type" => "Announce"}}}, actor) do
+ %{
+ title: "New Announce",
+ body: "@#{actor.nickname} has announced your post",
+ icon: User.avatar_url(actor)
+ }
+ end
+
+ def format(%{activity: %{data: %{"type" => "Like"}}}, actor) do
+ %{
+ title: "New Like",
+ body: "@#{actor.nickname} has liked your post",
+ icon: User.avatar_url(actor)
+ }
+ end
+end
diff --git a/lib/pleroma/web/push/subscription.ex b/lib/pleroma/web/push/subscription.ex
new file mode 100644
index 000000000..cfab7a98e
--- /dev/null
+++ b/lib/pleroma/web/push/subscription.ex
@@ -0,0 +1,66 @@
+defmodule Pleroma.Web.Push.Subscription do
+ use Ecto.Schema
+ import Ecto.Changeset
+ alias Pleroma.{Repo, User}
+ alias Pleroma.Web.OAuth.Token
+ alias Pleroma.Web.Push.Subscription
+
+ schema "push_subscriptions" do
+ belongs_to(:user, User)
+ belongs_to(:token, Token)
+ field(:endpoint, :string)
+ field(:key_p256dh, :string)
+ field(:key_auth, :string)
+ field(:data, :map, default: %{})
+
+ timestamps()
+ end
+
+ @supported_alert_types ~w[follow favourite mention reblog]
+
+ defp alerts(%{"data" => %{"alerts" => alerts}}) do
+ alerts = Map.take(alerts, @supported_alert_types)
+ %{"alerts" => alerts}
+ end
+
+ def create(
+ %User{} = user,
+ %Token{} = token,
+ %{
+ "subscription" => %{
+ "endpoint" => endpoint,
+ "keys" => %{"auth" => key_auth, "p256dh" => key_p256dh}
+ }
+ } = params
+ ) do
+ Repo.insert(%Subscription{
+ user_id: user.id,
+ token_id: token.id,
+ endpoint: endpoint,
+ key_auth: key_auth,
+ key_p256dh: key_p256dh,
+ data: alerts(params)
+ })
+ end
+
+ def get(%User{id: user_id}, %Token{id: token_id}) do
+ Repo.get_by(Subscription, user_id: user_id, token_id: token_id)
+ end
+
+ def update(user, token, params) do
+ get(user, token)
+ |> change(data: alerts(params))
+ |> Repo.update()
+ end
+
+ def delete(user, token) do
+ Repo.delete(get(user, token))
+ end
+
+ def delete_if_exists(user, token) do
+ case get(user, token) do
+ nil -> {:ok, nil}
+ sub -> Repo.delete(sub)
+ end
+ end
+end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index a07607366..4d24d4c1c 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -200,6 +200,11 @@ defmodule Pleroma.Web.Router do
put("/filters/:id", MastodonAPIController, :update_filter)
delete("/filters/:id", MastodonAPIController, :delete_filter)
+ post("/push/subscription", MastodonAPIController, :create_push_subscription)
+ get("/push/subscription", MastodonAPIController, :get_push_subscription)
+ put("/push/subscription", MastodonAPIController, :update_push_subscription)
+ delete("/push/subscription", MastodonAPIController, :delete_push_subscription)
+
get("/suggestions", MastodonAPIController, :suggestions)
get("/endorsements", MastodonAPIController, :empty_array)
diff --git a/lib/pleroma/web/salmon/salmon.ex b/lib/pleroma/web/salmon/salmon.ex
index 97251c05e..0e2cfddd0 100644
--- a/lib/pleroma/web/salmon/salmon.ex
+++ b/lib/pleroma/web/salmon/salmon.ex
@@ -162,12 +162,7 @@ defmodule Pleroma.Web.Salmon do
poster.(
salmon,
feed,
- [{"Content-Type", "application/magic-envelope+xml"}],
- adapter: [
- timeout: 10000,
- recv_timeout: 20000,
- pool: :default
- ]
+ [{"Content-Type", "application/magic-envelope+xml"}]
) do
Logger.debug(fn -> "Pushed to #{salmon}, code #{code}" end)
else
diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
index b0ed8387e..092779010 100644
--- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex
+++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
@@ -157,13 +157,17 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|> send_resp(200, response)
_ ->
+ vapid_public_key =
+ Keyword.get(Application.get_env(:web_push_encryption, :vapid_details), :public_key)
+
data = %{
name: Keyword.get(instance, :name),
description: Keyword.get(instance, :description),
server: Web.base_url(),
textlimit: to_string(Keyword.get(instance, :limit)),
closed: if(Keyword.get(instance, :registrations_open), do: "0", else: "1"),
- private: if(Keyword.get(instance, :public, true), do: "0", else: "1")
+ private: if(Keyword.get(instance, :public, true), do: "0", else: "1"),
+ vapidPublicKey: vapid_public_key
}
pleroma_fe = %{
diff --git a/lib/pleroma/web/web_finger/web_finger.ex b/lib/pleroma/web/web_finger/web_finger.ex
index 99c65a6bf..0ff3b8b5f 100644
--- a/lib/pleroma/web/web_finger/web_finger.ex
+++ b/lib/pleroma/web/web_finger/web_finger.ex
@@ -221,7 +221,7 @@ defmodule Pleroma.Web.WebFinger do
def find_lrdd_template(domain) do
with {:ok, %{status: status, body: body}} when status in 200..299 <-
- @httpoison.get("http://#{domain}/.well-known/host-meta", [], follow_redirect: true) do
+ @httpoison.get("http://#{domain}/.well-known/host-meta", []) do
get_template_from_xml(body)
else
_ ->
diff --git a/lib/pleroma/web/websub/websub.ex b/lib/pleroma/web/websub/websub.ex
index 0761b5475..8cb07006f 100644
--- a/lib/pleroma/web/websub/websub.ex
+++ b/lib/pleroma/web/websub/websub.ex
@@ -264,11 +264,6 @@ defmodule Pleroma.Web.Websub do
[
{"Content-Type", "application/atom+xml"},
{"X-Hub-Signature", "sha1=#{signature}"}
- ],
- adapter: [
- timeout: 10000,
- recv_timeout: 20000,
- pool: :default
]
) do
Logger.info(fn -> "Pushed to #{callback}, code #{code}" end)