Resolve conflicts
This commit is contained in:
commit
657277ffc0
548 changed files with 7933 additions and 1976 deletions
67
lib/mix/pleroma.ex
Normal file
67
lib/mix/pleroma.ex
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Mix.Pleroma do
|
||||
@doc "Common functions to be reused in mix tasks"
|
||||
def start_pleroma do
|
||||
Application.put_env(:phoenix, :serve_endpoints, false, persistent: true)
|
||||
{:ok, _} = Application.ensure_all_started(:pleroma)
|
||||
end
|
||||
|
||||
def load_pleroma do
|
||||
Application.load(:pleroma)
|
||||
end
|
||||
|
||||
def get_option(options, opt, prompt, defval \\ nil, defname \\ nil) do
|
||||
Keyword.get(options, opt) || shell_prompt(prompt, defval, defname)
|
||||
end
|
||||
|
||||
def shell_prompt(prompt, defval \\ nil, defname \\ nil) do
|
||||
prompt_message = "#{prompt} [#{defname || defval}] "
|
||||
|
||||
input =
|
||||
if mix_shell?(),
|
||||
do: Mix.shell().prompt(prompt_message),
|
||||
else: :io.get_line(prompt_message)
|
||||
|
||||
case input do
|
||||
"\n" ->
|
||||
case defval do
|
||||
nil ->
|
||||
shell_prompt(prompt, defval, defname)
|
||||
|
||||
defval ->
|
||||
defval
|
||||
end
|
||||
|
||||
input ->
|
||||
String.trim(input)
|
||||
end
|
||||
end
|
||||
|
||||
def shell_yes?(message) do
|
||||
if mix_shell?(),
|
||||
do: Mix.shell().yes?("Continue?"),
|
||||
else: shell_prompt(message, "Continue?") in ~w(Yn Y y)
|
||||
end
|
||||
|
||||
def shell_info(message) do
|
||||
if mix_shell?(),
|
||||
do: Mix.shell().info(message),
|
||||
else: IO.puts(message)
|
||||
end
|
||||
|
||||
def shell_error(message) do
|
||||
if mix_shell?(),
|
||||
do: Mix.shell().error(message),
|
||||
else: IO.puts(:stderr, message)
|
||||
end
|
||||
|
||||
@doc "Performs a safe check whether `Mix.shell/0` is available (does not raise if Mix is not loaded)"
|
||||
def mix_shell?, do: :erlang.function_exported(Mix, :shell, 0)
|
||||
|
||||
def escape_sh_path(path) do
|
||||
~S(') <> String.replace(path, ~S('), ~S(\')) <> ~S(')
|
||||
end
|
||||
end
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
defmodule Mix.Tasks.Pleroma.Benchmark do
|
||||
import Mix.Pleroma
|
||||
use Mix.Task
|
||||
alias Mix.Tasks.Pleroma.Common
|
||||
|
||||
def run(["search"]) do
|
||||
Common.start_pleroma()
|
||||
start_pleroma()
|
||||
|
||||
Benchee.run(%{
|
||||
"search" => fn ->
|
||||
|
|
@ -13,7 +13,7 @@ defmodule Mix.Tasks.Pleroma.Benchmark do
|
|||
end
|
||||
|
||||
def run(["tag"]) do
|
||||
Common.start_pleroma()
|
||||
start_pleroma()
|
||||
|
||||
Benchee.run(%{
|
||||
"tag" => fn ->
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Mix.Tasks.Pleroma.Common do
|
||||
@doc "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
|
||||
79
lib/mix/tasks/pleroma/config.ex
Normal file
79
lib/mix/tasks/pleroma/config.ex
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
defmodule Mix.Tasks.Pleroma.Config do
|
||||
use Mix.Task
|
||||
import Mix.Pleroma
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.Web.AdminAPI.Config
|
||||
@shortdoc "Manages the location of the config"
|
||||
@moduledoc """
|
||||
Manages the location of the config.
|
||||
|
||||
## Transfers config from file to DB.
|
||||
|
||||
mix pleroma.config migrate_to_db
|
||||
|
||||
## Transfers config from DB to file.
|
||||
|
||||
mix pleroma.config migrate_from_db ENV
|
||||
"""
|
||||
|
||||
def run(["migrate_to_db"]) do
|
||||
start_pleroma()
|
||||
|
||||
if Pleroma.Config.get([:instance, :dynamic_configuration]) do
|
||||
Application.get_all_env(:pleroma)
|
||||
|> Enum.reject(fn {k, _v} -> k in [Pleroma.Repo, :env] end)
|
||||
|> Enum.each(fn {k, v} ->
|
||||
key = to_string(k) |> String.replace("Elixir.", "")
|
||||
{:ok, _} = Config.update_or_create(%{group: "pleroma", key: key, value: v})
|
||||
Mix.shell().info("#{key} is migrated.")
|
||||
end)
|
||||
|
||||
Mix.shell().info("Settings migrated.")
|
||||
else
|
||||
Mix.shell().info(
|
||||
"Migration is not allowed by config. You can change this behavior in instance settings."
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def run(["migrate_from_db", env, delete?]) do
|
||||
start_pleroma()
|
||||
|
||||
delete? = if delete? == "true", do: true, else: false
|
||||
|
||||
if Pleroma.Config.get([:instance, :dynamic_configuration]) do
|
||||
config_path = "config/#{env}.exported_from_db.secret.exs"
|
||||
|
||||
{:ok, file} = File.open(config_path, [:write])
|
||||
IO.write(file, "use Mix.Config\r\n")
|
||||
|
||||
Repo.all(Config)
|
||||
|> Enum.each(fn config ->
|
||||
mark =
|
||||
if String.starts_with?(config.key, "Pleroma.") or
|
||||
String.starts_with?(config.key, "Ueberauth"),
|
||||
do: ",",
|
||||
else: ":"
|
||||
|
||||
IO.write(
|
||||
file,
|
||||
"config :#{config.group}, #{config.key}#{mark} #{
|
||||
inspect(Config.from_binary(config.value))
|
||||
}\r\n"
|
||||
)
|
||||
|
||||
if delete? do
|
||||
{:ok, _} = Repo.delete(config)
|
||||
Mix.shell().info("#{config.key} deleted from DB.")
|
||||
end
|
||||
end)
|
||||
|
||||
File.close(file)
|
||||
System.cmd("mix", ["format", config_path])
|
||||
else
|
||||
Mix.shell().info(
|
||||
"Migration is not allowed by config. You can change this behavior in instance settings."
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -3,12 +3,12 @@
|
|||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Mix.Tasks.Pleroma.Database do
|
||||
alias Mix.Tasks.Pleroma.Common
|
||||
alias Pleroma.Conversation
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
require Logger
|
||||
import Mix.Pleroma
|
||||
use Mix.Task
|
||||
|
||||
@shortdoc "A collection of database related tasks"
|
||||
|
|
@ -45,7 +45,7 @@ defmodule Mix.Tasks.Pleroma.Database do
|
|||
]
|
||||
)
|
||||
|
||||
Common.start_pleroma()
|
||||
start_pleroma()
|
||||
Logger.info("Removing embedded objects")
|
||||
|
||||
Repo.query!(
|
||||
|
|
@ -66,12 +66,12 @@ defmodule Mix.Tasks.Pleroma.Database do
|
|||
end
|
||||
|
||||
def run(["bump_all_conversations"]) do
|
||||
Common.start_pleroma()
|
||||
start_pleroma()
|
||||
Conversation.bump_for_all_activities()
|
||||
end
|
||||
|
||||
def run(["update_users_following_followers_counts"]) do
|
||||
Common.start_pleroma()
|
||||
start_pleroma()
|
||||
|
||||
users = Repo.all(User)
|
||||
Enum.each(users, &User.remove_duplicated_following/1)
|
||||
|
|
@ -89,7 +89,7 @@ defmodule Mix.Tasks.Pleroma.Database do
|
|||
]
|
||||
)
|
||||
|
||||
Common.start_pleroma()
|
||||
start_pleroma()
|
||||
|
||||
deadline = Pleroma.Config.get([:instance, :remote_post_retention_days])
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
defmodule Mix.Tasks.Pleroma.Digest do
|
||||
use Mix.Task
|
||||
alias Mix.Tasks.Pleroma.Common
|
||||
|
||||
@shortdoc "Manages digest emails"
|
||||
@moduledoc """
|
||||
|
|
@ -14,7 +13,7 @@ defmodule Mix.Tasks.Pleroma.Digest do
|
|||
Example: ``mix pleroma.digest test donaldtheduck 2019-05-20``
|
||||
"""
|
||||
def run(["test", nickname | opts]) do
|
||||
Common.start_pleroma()
|
||||
Mix.Pleroma.start_pleroma()
|
||||
|
||||
user = Pleroma.User.get_by_nickname(nickname)
|
||||
|
||||
|
|
|
|||
49
lib/mix/tasks/pleroma/ecto/ecto.ex
Normal file
49
lib/mix/tasks/pleroma/ecto/ecto.ex
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-onl
|
||||
defmodule Mix.Tasks.Pleroma.Ecto do
|
||||
@doc """
|
||||
Ensures the given repository's migrations path exists on the file system.
|
||||
"""
|
||||
@spec ensure_migrations_path(Ecto.Repo.t(), Keyword.t()) :: String.t()
|
||||
def ensure_migrations_path(repo, opts) do
|
||||
path = opts[:migrations_path] || Path.join(source_repo_priv(repo), "migrations")
|
||||
|
||||
path =
|
||||
case Path.type(path) do
|
||||
:relative ->
|
||||
Path.join(Application.app_dir(:pleroma), path)
|
||||
|
||||
:absolute ->
|
||||
path
|
||||
end
|
||||
|
||||
if not File.dir?(path) do
|
||||
raise_missing_migrations(Path.relative_to_cwd(path), repo)
|
||||
end
|
||||
|
||||
path
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the private repository path relative to the source.
|
||||
"""
|
||||
def source_repo_priv(repo) do
|
||||
config = repo.config()
|
||||
priv = config[:priv] || "priv/#{repo |> Module.split() |> List.last() |> Macro.underscore()}"
|
||||
Path.join(Application.app_dir(:pleroma), priv)
|
||||
end
|
||||
|
||||
defp raise_missing_migrations(path, repo) do
|
||||
raise("""
|
||||
Could not find migrations directory #{inspect(path)}
|
||||
for repo #{inspect(repo)}.
|
||||
This may be because you are in a new project and the
|
||||
migration directory has not been created yet. Creating an
|
||||
empty directory at the path above will fix this error.
|
||||
If you expected existing migrations to be found, please
|
||||
make sure your repository has been properly configured
|
||||
and the configured path exists.
|
||||
""")
|
||||
end
|
||||
end
|
||||
63
lib/mix/tasks/pleroma/ecto/migrate.ex
Normal file
63
lib/mix/tasks/pleroma/ecto/migrate.ex
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-onl
|
||||
|
||||
defmodule Mix.Tasks.Pleroma.Ecto.Migrate do
|
||||
use Mix.Task
|
||||
import Mix.Pleroma
|
||||
require Logger
|
||||
|
||||
@shortdoc "Wrapper on `ecto.migrate` task."
|
||||
|
||||
@aliases [
|
||||
n: :step,
|
||||
v: :to
|
||||
]
|
||||
|
||||
@switches [
|
||||
all: :boolean,
|
||||
step: :integer,
|
||||
to: :integer,
|
||||
quiet: :boolean,
|
||||
log_sql: :boolean,
|
||||
strict_version_order: :boolean,
|
||||
migrations_path: :string
|
||||
]
|
||||
|
||||
@moduledoc """
|
||||
Changes `Logger` level to `:info` before start migration.
|
||||
Changes level back when migration ends.
|
||||
|
||||
## Start migration
|
||||
|
||||
mix pleroma.ecto.migrate [OPTIONS]
|
||||
|
||||
Options:
|
||||
- see https://hexdocs.pm/ecto/2.0.0/Mix.Tasks.Ecto.Migrate.html
|
||||
"""
|
||||
|
||||
@impl true
|
||||
def run(args \\ []) do
|
||||
load_pleroma()
|
||||
{opts, _} = OptionParser.parse!(args, strict: @switches, aliases: @aliases)
|
||||
|
||||
opts =
|
||||
if opts[:to] || opts[:step] || opts[:all],
|
||||
do: opts,
|
||||
else: Keyword.put(opts, :all, true)
|
||||
|
||||
opts =
|
||||
if opts[:quiet],
|
||||
do: Keyword.merge(opts, log: false, log_sql: false),
|
||||
else: opts
|
||||
|
||||
path = Mix.Tasks.Pleroma.Ecto.ensure_migrations_path(Pleroma.Repo, opts)
|
||||
|
||||
level = Logger.level()
|
||||
Logger.configure(level: :info)
|
||||
|
||||
{:ok, _, _} = Ecto.Migrator.with_repo(Pleroma.Repo, &Ecto.Migrator.run(&1, path, :up, opts))
|
||||
|
||||
Logger.configure(level: level)
|
||||
end
|
||||
end
|
||||
67
lib/mix/tasks/pleroma/ecto/rollback.ex
Normal file
67
lib/mix/tasks/pleroma/ecto/rollback.ex
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-onl
|
||||
|
||||
defmodule Mix.Tasks.Pleroma.Ecto.Rollback do
|
||||
use Mix.Task
|
||||
import Mix.Pleroma
|
||||
require Logger
|
||||
@shortdoc "Wrapper on `ecto.rollback` task"
|
||||
|
||||
@aliases [
|
||||
n: :step,
|
||||
v: :to
|
||||
]
|
||||
|
||||
@switches [
|
||||
all: :boolean,
|
||||
step: :integer,
|
||||
to: :integer,
|
||||
start: :boolean,
|
||||
quiet: :boolean,
|
||||
log_sql: :boolean,
|
||||
migrations_path: :string
|
||||
]
|
||||
|
||||
@moduledoc """
|
||||
Changes `Logger` level to `:info` before start rollback.
|
||||
Changes level back when rollback ends.
|
||||
|
||||
## Start rollback
|
||||
|
||||
mix pleroma.ecto.rollback
|
||||
|
||||
Options:
|
||||
- see https://hexdocs.pm/ecto/2.0.0/Mix.Tasks.Ecto.Rollback.html
|
||||
"""
|
||||
|
||||
@impl true
|
||||
def run(args \\ []) do
|
||||
load_pleroma()
|
||||
{opts, _} = OptionParser.parse!(args, strict: @switches, aliases: @aliases)
|
||||
|
||||
opts =
|
||||
if opts[:to] || opts[:step] || opts[:all],
|
||||
do: opts,
|
||||
else: Keyword.put(opts, :step, 1)
|
||||
|
||||
opts =
|
||||
if opts[:quiet],
|
||||
do: Keyword.merge(opts, log: false, log_sql: false),
|
||||
else: opts
|
||||
|
||||
path = Mix.Tasks.Pleroma.Ecto.ensure_migrations_path(Pleroma.Repo, opts)
|
||||
|
||||
level = Logger.level()
|
||||
Logger.configure(level: :info)
|
||||
|
||||
if Pleroma.Config.get(:env) == :test do
|
||||
Logger.info("Rollback succesfully")
|
||||
else
|
||||
{:ok, _, _} =
|
||||
Ecto.Migrator.with_repo(Pleroma.Repo, &Ecto.Migrator.run(&1, path, :down, opts))
|
||||
end
|
||||
|
||||
Logger.configure(level: level)
|
||||
end
|
||||
end
|
||||
|
|
@ -55,15 +55,13 @@ defmodule Mix.Tasks.Pleroma.Emoji do
|
|||
are extracted).
|
||||
"""
|
||||
|
||||
@default_manifest Pleroma.Config.get!([:emoji, :default_manifest])
|
||||
|
||||
def run(["ls-packs" | args]) do
|
||||
Application.ensure_all_started(:hackney)
|
||||
|
||||
{options, [], []} = parse_global_opts(args)
|
||||
|
||||
manifest =
|
||||
fetch_manifest(if options[:manifest], do: options[:manifest], else: @default_manifest)
|
||||
fetch_manifest(if options[:manifest], do: options[:manifest], else: default_manifest())
|
||||
|
||||
Enum.each(manifest, fn {name, info} ->
|
||||
to_print = [
|
||||
|
|
@ -88,7 +86,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do
|
|||
|
||||
{options, pack_names, []} = parse_global_opts(args)
|
||||
|
||||
manifest_url = if options[:manifest], do: options[:manifest], else: @default_manifest
|
||||
manifest_url = if options[:manifest], do: options[:manifest], else: default_manifest()
|
||||
|
||||
manifest = fetch_manifest(manifest_url)
|
||||
|
||||
|
|
@ -298,4 +296,6 @@ defmodule Mix.Tasks.Pleroma.Emoji do
|
|||
|
||||
Tesla.client(middleware)
|
||||
end
|
||||
|
||||
defp default_manifest, do: Pleroma.Config.get!([:emoji, :default_manifest])
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
defmodule Mix.Tasks.Pleroma.Instance do
|
||||
use Mix.Task
|
||||
alias Mix.Tasks.Pleroma.Common
|
||||
import Mix.Pleroma
|
||||
|
||||
@shortdoc "Manages Pleroma instance"
|
||||
@moduledoc """
|
||||
|
|
@ -29,7 +29,11 @@ defmodule Mix.Tasks.Pleroma.Instance do
|
|||
- `--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
|
||||
- `--rum Y/N` - Whether to enable RUM indexes
|
||||
- `--indexable Y/N` - Allow/disallow indexing site by search engines
|
||||
- `--db-configurable Y/N` - Allow/disallow configuring instance from admin part
|
||||
- `--uploads-dir` - the directory uploads go in when using a local uploader
|
||||
- `--static-dir` - the directory custom public files should be read from (custom emojis, frontend bundle overrides, robots.txt, etc.)
|
||||
"""
|
||||
|
||||
def run(["gen" | rest]) do
|
||||
|
|
@ -48,7 +52,11 @@ defmodule Mix.Tasks.Pleroma.Instance do
|
|||
dbname: :string,
|
||||
dbuser: :string,
|
||||
dbpass: :string,
|
||||
indexable: :string
|
||||
rum: :string,
|
||||
indexable: :string,
|
||||
db_configurable: :string,
|
||||
uploads_dir: :string,
|
||||
static_dir: :string
|
||||
],
|
||||
aliases: [
|
||||
o: :output,
|
||||
|
|
@ -68,7 +76,7 @@ defmodule Mix.Tasks.Pleroma.Instance do
|
|||
if proceed? do
|
||||
[domain, port | _] =
|
||||
String.split(
|
||||
Common.get_option(
|
||||
get_option(
|
||||
options,
|
||||
:domain,
|
||||
"What domain will your instance use? (e.g pleroma.soykaf.com)"
|
||||
|
|
@ -77,16 +85,16 @@ defmodule Mix.Tasks.Pleroma.Instance do
|
|||
) ++ [443]
|
||||
|
||||
name =
|
||||
Common.get_option(
|
||||
get_option(
|
||||
options,
|
||||
:instance_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?")
|
||||
email = get_option(options, :admin_email, "What is your admin email address?")
|
||||
|
||||
notify_email =
|
||||
Common.get_option(
|
||||
get_option(
|
||||
options,
|
||||
:notify_email,
|
||||
"What email address do you want to use for sending email notifications?",
|
||||
|
|
@ -94,21 +102,27 @@ defmodule Mix.Tasks.Pleroma.Instance do
|
|||
)
|
||||
|
||||
indexable =
|
||||
Common.get_option(
|
||||
get_option(
|
||||
options,
|
||||
:indexable,
|
||||
"Do you want search engines to index your site? (y/n)",
|
||||
"y"
|
||||
) === "y"
|
||||
|
||||
dbhost =
|
||||
Common.get_option(options, :dbhost, "What is the hostname of your database?", "localhost")
|
||||
db_configurable? =
|
||||
get_option(
|
||||
options,
|
||||
:db_configurable,
|
||||
"Do you want to store the configuration in the database (allows controlling it from admin-fe)? (y/n)",
|
||||
"n"
|
||||
) === "y"
|
||||
|
||||
dbname =
|
||||
Common.get_option(options, :dbname, "What is the name of your database?", "pleroma_dev")
|
||||
dbhost = get_option(options, :dbhost, "What is the hostname of your database?", "localhost")
|
||||
|
||||
dbname = get_option(options, :dbname, "What is the name of your database?", "pleroma")
|
||||
|
||||
dbuser =
|
||||
Common.get_option(
|
||||
get_option(
|
||||
options,
|
||||
:dbuser,
|
||||
"What is the user used to connect to your database?",
|
||||
|
|
@ -116,7 +130,7 @@ defmodule Mix.Tasks.Pleroma.Instance do
|
|||
)
|
||||
|
||||
dbpass =
|
||||
Common.get_option(
|
||||
get_option(
|
||||
options,
|
||||
:dbpass,
|
||||
"What is the password used to connect to your database?",
|
||||
|
|
@ -124,14 +138,39 @@ defmodule Mix.Tasks.Pleroma.Instance do
|
|||
"autogenerated"
|
||||
)
|
||||
|
||||
rum_enabled =
|
||||
get_option(
|
||||
options,
|
||||
:rum,
|
||||
"Would you like to use RUM indices?",
|
||||
"n"
|
||||
) === "y"
|
||||
|
||||
uploads_dir =
|
||||
get_option(
|
||||
options,
|
||||
:upload_dir,
|
||||
"What directory should media uploads go in (when using the local uploader)?",
|
||||
Pleroma.Config.get([Pleroma.Uploaders.Local, :uploads])
|
||||
)
|
||||
|
||||
static_dir =
|
||||
get_option(
|
||||
options,
|
||||
:static_dir,
|
||||
"What directory should custom public files be read from (custom emojis, frontend bundle overrides, robots.txt, etc.)?",
|
||||
Pleroma.Config.get([:instance, :static_dir])
|
||||
)
|
||||
|
||||
secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
|
||||
jwt_secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64)
|
||||
signing_salt = :crypto.strong_rand_bytes(8) |> Base.encode64() |> binary_part(0, 8)
|
||||
{web_push_public_key, web_push_private_key} = :crypto.generate_key(:ecdh, :prime256v1)
|
||||
template_dir = Application.app_dir(:pleroma, "priv") <> "/templates"
|
||||
|
||||
result_config =
|
||||
EEx.eval_file(
|
||||
"sample_config.eex" |> Path.expand(__DIR__),
|
||||
template_dir <> "/sample_config.eex",
|
||||
domain: domain,
|
||||
port: port,
|
||||
email: email,
|
||||
|
|
@ -141,47 +180,39 @@ defmodule Mix.Tasks.Pleroma.Instance do
|
|||
dbname: dbname,
|
||||
dbuser: dbuser,
|
||||
dbpass: dbpass,
|
||||
version: Pleroma.Mixfile.project() |> Keyword.get(:version),
|
||||
secret: secret,
|
||||
jwt_secret: jwt_secret,
|
||||
signing_salt: signing_salt,
|
||||
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)
|
||||
web_push_private_key: Base.url_encode64(web_push_private_key, padding: false),
|
||||
db_configurable?: db_configurable?,
|
||||
static_dir: static_dir,
|
||||
uploads_dir: uploads_dir,
|
||||
rum_enabled: rum_enabled
|
||||
)
|
||||
|
||||
result_psql =
|
||||
EEx.eval_file(
|
||||
"sample_psql.eex" |> Path.expand(__DIR__),
|
||||
template_dir <> "/sample_psql.eex",
|
||||
dbname: dbname,
|
||||
dbuser: dbuser,
|
||||
dbpass: dbpass
|
||||
dbpass: dbpass,
|
||||
rum_enabled: rum_enabled
|
||||
)
|
||||
|
||||
Mix.shell().info(
|
||||
"Writing config to #{config_path}. You should rename it to config/prod.secret.exs or config/dev.secret.exs."
|
||||
)
|
||||
shell_info("Writing config to #{config_path}.")
|
||||
|
||||
File.write(config_path, result_config)
|
||||
Mix.shell().info("Writing #{psql_path}.")
|
||||
shell_info("Writing the postgres script to #{psql_path}.")
|
||||
File.write(psql_path, result_psql)
|
||||
|
||||
write_robots_txt(indexable)
|
||||
write_robots_txt(indexable, template_dir)
|
||||
|
||||
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
|
||||
shell_info(
|
||||
"\n All files successfully written! Refer to the installation instructions for your platform for next steps"
|
||||
)
|
||||
else
|
||||
Mix.shell().error(
|
||||
shell_error(
|
||||
"The task would have overwritten the following files:\n" <>
|
||||
(Enum.map(paths, &"- #{&1}\n") |> Enum.join("")) <>
|
||||
"Rerun with `--force` to overwrite them."
|
||||
|
|
@ -189,10 +220,10 @@ defmodule Mix.Tasks.Pleroma.Instance do
|
|||
end
|
||||
end
|
||||
|
||||
defp write_robots_txt(indexable) do
|
||||
defp write_robots_txt(indexable, template_dir) do
|
||||
robots_txt =
|
||||
EEx.eval_file(
|
||||
Path.expand("robots_txt.eex", __DIR__),
|
||||
template_dir <> "/robots_txt.eex",
|
||||
indexable: indexable
|
||||
)
|
||||
|
||||
|
|
@ -206,10 +237,10 @@ defmodule Mix.Tasks.Pleroma.Instance do
|
|||
|
||||
if File.exists?(robots_txt_path) do
|
||||
File.cp!(robots_txt_path, "#{robots_txt_path}.bak")
|
||||
Mix.shell().info("Backing up existing robots.txt to #{robots_txt_path}.bak")
|
||||
shell_info("Backing up existing robots.txt to #{robots_txt_path}.bak")
|
||||
end
|
||||
|
||||
File.write(robots_txt_path, robots_txt)
|
||||
Mix.shell().info("Writing #{robots_txt_path}.")
|
||||
shell_info("Writing #{robots_txt_path}.")
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
defmodule Mix.Tasks.Pleroma.Relay do
|
||||
use Mix.Task
|
||||
alias Mix.Tasks.Pleroma.Common
|
||||
import Mix.Pleroma
|
||||
alias Pleroma.Web.ActivityPub.Relay
|
||||
|
||||
@shortdoc "Manages remote relays"
|
||||
|
|
@ -24,24 +24,24 @@ defmodule Mix.Tasks.Pleroma.Relay do
|
|||
Example: ``mix pleroma.relay unfollow https://example.org/relay``
|
||||
"""
|
||||
def run(["follow", target]) do
|
||||
Common.start_pleroma()
|
||||
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)}")
|
||||
{:error, e} -> shell_error("Error while following #{target}: #{inspect(e)}")
|
||||
end
|
||||
end
|
||||
|
||||
def run(["unfollow", target]) do
|
||||
Common.start_pleroma()
|
||||
start_pleroma()
|
||||
|
||||
with {:ok, _activity} <- Relay.unfollow(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)}")
|
||||
{:error, e} -> shell_error("Error while following #{target}: #{inspect(e)}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,2 +0,0 @@
|
|||
User-Agent: *
|
||||
Disallow: <%= if indexable, do: "", else: "/" %>
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
# 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,
|
||||
url: [host: "<%= domain %>", scheme: "https", port: <%= port %>],
|
||||
secret_key_base: "<%= secret %>",
|
||||
signing_salt: "<%= signing_salt %>"
|
||||
|
||||
config :pleroma, :instance,
|
||||
name: "<%= name %>",
|
||||
email: "<%= email %>",
|
||||
notify_email: "<%= notify_email %>",
|
||||
limit: 5000,
|
||||
registrations_open: true,
|
||||
dedupe_media: false
|
||||
|
||||
config :pleroma, :media_proxy,
|
||||
enabled: false,
|
||||
redirect_on_failure: true
|
||||
#base_url: "https://cache.pleroma.social"
|
||||
|
||||
config :pleroma, Pleroma.Repo,
|
||||
adapter: Ecto.Adapters.Postgres,
|
||||
username: "<%= dbuser %>",
|
||||
password: "<%= dbpass %>",
|
||||
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
|
||||
|
||||
# Configure S3 support if desired.
|
||||
# The public S3 endpoint is different depending on region and provider,
|
||||
# consult your S3 provider's documentation for details on what to use.
|
||||
#
|
||||
# config :pleroma, Pleroma.Uploaders.S3,
|
||||
# bucket: "some-bucket",
|
||||
# public_endpoint: "https://s3.amazonaws.com"
|
||||
#
|
||||
# Configure S3 credentials:
|
||||
# config :ex_aws, :s3,
|
||||
# access_key_id: "xxxxxxxxxxxxx",
|
||||
# secret_access_key: "yyyyyyyyyyyy",
|
||||
# region: "us-east-1",
|
||||
# scheme: "https://"
|
||||
#
|
||||
# For using third-party S3 clones like wasabi, also do:
|
||||
# config :ex_aws, :s3,
|
||||
# host: "s3.wasabisys.com"
|
||||
|
||||
|
||||
# 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
|
||||
# wide support without needing separate handlers for OVH, Rackspace, etc.
|
||||
#
|
||||
# config :pleroma, Pleroma.Uploaders.Swift,
|
||||
# container: "some-container",
|
||||
# username: "api-username-yyyy",
|
||||
# password: "api-key-xxxx",
|
||||
# tenant_id: "<openstack-project/tenant-id>",
|
||||
# auth_url: "https://keystone-endpoint.provider.com",
|
||||
# storage_url: "https://swift-endpoint.prodider.com/v1/AUTH_<tenant>/<container>",
|
||||
# object_url: "https://cdn-endpoint.provider.com/<container>"
|
||||
#
|
||||
|
||||
config :joken, default_signer: "<%= jwt_secret %>"
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
CREATE USER <%= dbuser %> WITH ENCRYPTED PASSWORD '<%= dbpass %>';
|
||||
CREATE DATABASE <%= dbname %> OWNER <%= dbuser %>;
|
||||
\c <%= dbname %>;
|
||||
--Extensions made by ecto.migrate that need superuser access
|
||||
CREATE EXTENSION IF NOT EXISTS citext;
|
||||
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
defmodule Mix.Tasks.Pleroma.Uploads do
|
||||
use Mix.Task
|
||||
alias Mix.Tasks.Pleroma.Common
|
||||
import Mix.Pleroma
|
||||
alias Pleroma.Upload
|
||||
alias Pleroma.Uploaders.Local
|
||||
require Logger
|
||||
|
|
@ -24,7 +24,7 @@ defmodule Mix.Tasks.Pleroma.Uploads do
|
|||
"""
|
||||
def run(["migrate_local", target_uploader | args]) do
|
||||
delete? = Enum.member?(args, "--delete")
|
||||
Common.start_pleroma()
|
||||
start_pleroma()
|
||||
local_path = Pleroma.Config.get!([Local, :uploads])
|
||||
uploader = Module.concat(Pleroma.Uploaders, target_uploader)
|
||||
|
||||
|
|
@ -38,10 +38,10 @@ defmodule Mix.Tasks.Pleroma.Uploads do
|
|||
Pleroma.Config.put([Upload, :uploader], uploader)
|
||||
end
|
||||
|
||||
Mix.shell().info("Migrating files from local #{local_path} to #{to_string(uploader)}")
|
||||
shell_info("Migrating files from local #{local_path} to #{to_string(uploader)}")
|
||||
|
||||
if delete? do
|
||||
Mix.shell().info(
|
||||
shell_info(
|
||||
"Attention: uploaded files will be deleted, hope you have backups! (--delete ; cancel with ^C)"
|
||||
)
|
||||
|
||||
|
|
@ -78,7 +78,7 @@ defmodule Mix.Tasks.Pleroma.Uploads do
|
|||
|> Enum.filter(& &1)
|
||||
|
||||
total_count = length(uploads)
|
||||
Mix.shell().info("Found #{total_count} uploads")
|
||||
shell_info("Found #{total_count} uploads")
|
||||
|
||||
uploads
|
||||
|> Task.async_stream(
|
||||
|
|
@ -90,7 +90,7 @@ defmodule Mix.Tasks.Pleroma.Uploads do
|
|||
:ok
|
||||
|
||||
error ->
|
||||
Mix.shell().error("failed to upload #{inspect(upload.path)}: #{inspect(error)}")
|
||||
shell_error("failed to upload #{inspect(upload.path)}: #{inspect(error)}")
|
||||
end
|
||||
end,
|
||||
timeout: 150_000
|
||||
|
|
@ -99,10 +99,10 @@ defmodule Mix.Tasks.Pleroma.Uploads do
|
|||
# credo:disable-for-next-line Credo.Check.Warning.UnusedEnumOperation
|
||||
|> Enum.reduce(0, fn done, count ->
|
||||
count = count + length(done)
|
||||
Mix.shell().info("Uploaded #{count}/#{total_count} files")
|
||||
shell_info("Uploaded #{count}/#{total_count} files")
|
||||
count
|
||||
end)
|
||||
|
||||
Mix.shell().info("Done!")
|
||||
shell_info("Done!")
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,9 +5,10 @@
|
|||
defmodule Mix.Tasks.Pleroma.User do
|
||||
use Mix.Task
|
||||
import Ecto.Changeset
|
||||
alias Mix.Tasks.Pleroma.Common
|
||||
import Mix.Pleroma
|
||||
alias Pleroma.User
|
||||
alias Pleroma.UserInviteToken
|
||||
alias Pleroma.Web.OAuth
|
||||
|
||||
@shortdoc "Manages Pleroma users"
|
||||
@moduledoc """
|
||||
|
|
@ -49,6 +50,10 @@ defmodule Mix.Tasks.Pleroma.User do
|
|||
|
||||
mix pleroma.user delete_activities NICKNAME
|
||||
|
||||
## Sign user out from all applications (delete user's OAuth tokens and authorizations).
|
||||
|
||||
mix pleroma.user sign_out NICKNAME
|
||||
|
||||
## Deactivate or activate the user's account.
|
||||
|
||||
mix pleroma.user toggle_activated NICKNAME
|
||||
|
|
@ -115,7 +120,7 @@ defmodule Mix.Tasks.Pleroma.User do
|
|||
admin? = Keyword.get(options, :admin, false)
|
||||
assume_yes? = Keyword.get(options, :assume_yes, false)
|
||||
|
||||
Mix.shell().info("""
|
||||
shell_info("""
|
||||
A user will be created with the following information:
|
||||
- nickname: #{nickname}
|
||||
- email: #{email}
|
||||
|
|
@ -128,10 +133,10 @@ defmodule Mix.Tasks.Pleroma.User do
|
|||
- admin: #{if(admin?, do: "true", else: "false")}
|
||||
""")
|
||||
|
||||
proceed? = assume_yes? or Mix.shell().yes?("Continue?")
|
||||
proceed? = assume_yes? or shell_yes?("Continue?")
|
||||
|
||||
if proceed? do
|
||||
Common.start_pleroma()
|
||||
start_pleroma()
|
||||
|
||||
params = %{
|
||||
nickname: nickname,
|
||||
|
|
@ -145,7 +150,7 @@ defmodule Mix.Tasks.Pleroma.User do
|
|||
changeset = User.register_changeset(%User{}, params, need_confirmation: false)
|
||||
{:ok, _user} = User.register(changeset)
|
||||
|
||||
Mix.shell().info("User #{nickname} created")
|
||||
shell_info("User #{nickname} created")
|
||||
|
||||
if moderator? do
|
||||
run(["set", nickname, "--moderator"])
|
||||
|
|
@ -159,64 +164,64 @@ defmodule Mix.Tasks.Pleroma.User do
|
|||
run(["reset_password", nickname])
|
||||
end
|
||||
else
|
||||
Mix.shell().info("User will not be created.")
|
||||
shell_info("User will not be created.")
|
||||
end
|
||||
end
|
||||
|
||||
def run(["rm", nickname]) do
|
||||
Common.start_pleroma()
|
||||
start_pleroma()
|
||||
|
||||
with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
|
||||
User.perform(:delete, user)
|
||||
Mix.shell().info("User #{nickname} deleted.")
|
||||
shell_info("User #{nickname} deleted.")
|
||||
else
|
||||
_ ->
|
||||
Mix.shell().error("No local user #{nickname}")
|
||||
shell_error("No local user #{nickname}")
|
||||
end
|
||||
end
|
||||
|
||||
def run(["toggle_activated", nickname]) do
|
||||
Common.start_pleroma()
|
||||
start_pleroma()
|
||||
|
||||
with %User{} = user <- User.get_cached_by_nickname(nickname) do
|
||||
{:ok, user} = User.deactivate(user, !user.info.deactivated)
|
||||
|
||||
Mix.shell().info(
|
||||
shell_info(
|
||||
"Activation status of #{nickname}: #{if(user.info.deactivated, do: "de", else: "")}activated"
|
||||
)
|
||||
else
|
||||
_ ->
|
||||
Mix.shell().error("No user #{nickname}")
|
||||
shell_error("No user #{nickname}")
|
||||
end
|
||||
end
|
||||
|
||||
def run(["reset_password", nickname]) do
|
||||
Common.start_pleroma()
|
||||
start_pleroma()
|
||||
|
||||
with %User{local: true} = user <- User.get_cached_by_nickname(nickname),
|
||||
{:ok, token} <- Pleroma.PasswordResetToken.create_token(user) do
|
||||
Mix.shell().info("Generated password reset token for #{user.nickname}")
|
||||
shell_info("Generated password reset token for #{user.nickname}")
|
||||
|
||||
IO.puts(
|
||||
"URL: #{
|
||||
Pleroma.Web.Router.Helpers.util_url(
|
||||
Pleroma.Web.Router.Helpers.reset_password_url(
|
||||
Pleroma.Web.Endpoint,
|
||||
:show_password_reset,
|
||||
:reset,
|
||||
token.token
|
||||
)
|
||||
}"
|
||||
)
|
||||
else
|
||||
_ ->
|
||||
Mix.shell().error("No local user #{nickname}")
|
||||
shell_error("No local user #{nickname}")
|
||||
end
|
||||
end
|
||||
|
||||
def run(["unsubscribe", nickname]) do
|
||||
Common.start_pleroma()
|
||||
start_pleroma()
|
||||
|
||||
with %User{} = user <- User.get_cached_by_nickname(nickname) do
|
||||
Mix.shell().info("Deactivating #{user.nickname}")
|
||||
shell_info("Deactivating #{user.nickname}")
|
||||
User.deactivate(user)
|
||||
|
||||
{:ok, friends} = User.get_friends(user)
|
||||
|
|
@ -224,7 +229,7 @@ defmodule Mix.Tasks.Pleroma.User do
|
|||
Enum.each(friends, fn friend ->
|
||||
user = User.get_cached_by_id(user.id)
|
||||
|
||||
Mix.shell().info("Unsubscribing #{friend.nickname} from #{user.nickname}")
|
||||
shell_info("Unsubscribing #{friend.nickname} from #{user.nickname}")
|
||||
User.unfollow(user, friend)
|
||||
end)
|
||||
|
||||
|
|
@ -233,16 +238,16 @@ defmodule Mix.Tasks.Pleroma.User do
|
|||
user = User.get_cached_by_id(user.id)
|
||||
|
||||
if Enum.empty?(user.following) do
|
||||
Mix.shell().info("Successfully unsubscribed all followers from #{user.nickname}")
|
||||
shell_info("Successfully unsubscribed all followers from #{user.nickname}")
|
||||
end
|
||||
else
|
||||
_ ->
|
||||
Mix.shell().error("No user #{nickname}")
|
||||
shell_error("No user #{nickname}")
|
||||
end
|
||||
end
|
||||
|
||||
def run(["set", nickname | rest]) do
|
||||
Common.start_pleroma()
|
||||
start_pleroma()
|
||||
|
||||
{options, [], []} =
|
||||
OptionParser.parse(
|
||||
|
|
@ -274,33 +279,33 @@ defmodule Mix.Tasks.Pleroma.User do
|
|||
end
|
||||
else
|
||||
_ ->
|
||||
Mix.shell().error("No local user #{nickname}")
|
||||
shell_error("No local user #{nickname}")
|
||||
end
|
||||
end
|
||||
|
||||
def run(["tag", nickname | tags]) do
|
||||
Common.start_pleroma()
|
||||
start_pleroma()
|
||||
|
||||
with %User{} = user <- User.get_cached_by_nickname(nickname) do
|
||||
user = user |> User.tag(tags)
|
||||
|
||||
Mix.shell().info("Tags of #{user.nickname}: #{inspect(tags)}")
|
||||
shell_info("Tags of #{user.nickname}: #{inspect(tags)}")
|
||||
else
|
||||
_ ->
|
||||
Mix.shell().error("Could not change user tags for #{nickname}")
|
||||
shell_error("Could not change user tags for #{nickname}")
|
||||
end
|
||||
end
|
||||
|
||||
def run(["untag", nickname | tags]) do
|
||||
Common.start_pleroma()
|
||||
start_pleroma()
|
||||
|
||||
with %User{} = user <- User.get_cached_by_nickname(nickname) do
|
||||
user = user |> User.untag(tags)
|
||||
|
||||
Mix.shell().info("Tags of #{user.nickname}: #{inspect(tags)}")
|
||||
shell_info("Tags of #{user.nickname}: #{inspect(tags)}")
|
||||
else
|
||||
_ ->
|
||||
Mix.shell().error("Could not change user tags for #{nickname}")
|
||||
shell_error("Could not change user tags for #{nickname}")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -321,14 +326,12 @@ defmodule Mix.Tasks.Pleroma.User do
|
|||
end)
|
||||
|> Enum.into(%{})
|
||||
|
||||
Common.start_pleroma()
|
||||
start_pleroma()
|
||||
|
||||
with {:ok, val} <- options[:expires_at],
|
||||
options = Map.put(options, :expires_at, val),
|
||||
{:ok, invite} <- UserInviteToken.create_invite(options) do
|
||||
Mix.shell().info(
|
||||
"Generated user invite token " <> String.replace(invite.invite_type, "_", " ")
|
||||
)
|
||||
shell_info("Generated user invite token " <> String.replace(invite.invite_type, "_", " "))
|
||||
|
||||
url =
|
||||
Pleroma.Web.Router.Helpers.redirect_url(
|
||||
|
|
@ -340,14 +343,14 @@ defmodule Mix.Tasks.Pleroma.User do
|
|||
IO.puts(url)
|
||||
else
|
||||
error ->
|
||||
Mix.shell().error("Could not create invite token: #{inspect(error)}")
|
||||
shell_error("Could not create invite token: #{inspect(error)}")
|
||||
end
|
||||
end
|
||||
|
||||
def run(["invites"]) do
|
||||
Common.start_pleroma()
|
||||
start_pleroma()
|
||||
|
||||
Mix.shell().info("Invites list:")
|
||||
shell_info("Invites list:")
|
||||
|
||||
UserInviteToken.list_invites()
|
||||
|> Enum.each(fn invite ->
|
||||
|
|
@ -361,7 +364,7 @@ defmodule Mix.Tasks.Pleroma.User do
|
|||
" | Max use: #{max_use} Left use: #{max_use - invite.uses}"
|
||||
end
|
||||
|
||||
Mix.shell().info(
|
||||
shell_info(
|
||||
"ID: #{invite.id} | Token: #{invite.token} | Token type: #{invite.invite_type} | Used: #{
|
||||
invite.used
|
||||
}#{expire_info}#{using_info}"
|
||||
|
|
@ -370,40 +373,54 @@ defmodule Mix.Tasks.Pleroma.User do
|
|||
end
|
||||
|
||||
def run(["revoke_invite", token]) do
|
||||
Common.start_pleroma()
|
||||
start_pleroma()
|
||||
|
||||
with {:ok, invite} <- UserInviteToken.find_by_token(token),
|
||||
{:ok, _} <- UserInviteToken.update_invite(invite, %{used: true}) do
|
||||
Mix.shell().info("Invite for token #{token} was revoked.")
|
||||
shell_info("Invite for token #{token} was revoked.")
|
||||
else
|
||||
_ -> Mix.shell().error("No invite found with token #{token}")
|
||||
_ -> shell_error("No invite found with token #{token}")
|
||||
end
|
||||
end
|
||||
|
||||
def run(["delete_activities", nickname]) do
|
||||
Common.start_pleroma()
|
||||
start_pleroma()
|
||||
|
||||
with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
|
||||
{:ok, _} = User.delete_user_activities(user)
|
||||
Mix.shell().info("User #{nickname} statuses deleted.")
|
||||
shell_info("User #{nickname} statuses deleted.")
|
||||
else
|
||||
_ ->
|
||||
Mix.shell().error("No local user #{nickname}")
|
||||
shell_error("No local user #{nickname}")
|
||||
end
|
||||
end
|
||||
|
||||
def run(["toggle_confirmed", nickname]) do
|
||||
Common.start_pleroma()
|
||||
start_pleroma()
|
||||
|
||||
with %User{} = user <- User.get_cached_by_nickname(nickname) do
|
||||
{:ok, user} = User.toggle_confirmation(user)
|
||||
|
||||
message = if user.info.confirmation_pending, do: "needs", else: "doesn't need"
|
||||
|
||||
Mix.shell().info("#{nickname} #{message} confirmation.")
|
||||
shell_info("#{nickname} #{message} confirmation.")
|
||||
else
|
||||
_ ->
|
||||
Mix.shell().error("No local user #{nickname}")
|
||||
shell_error("No local user #{nickname}")
|
||||
end
|
||||
end
|
||||
|
||||
def run(["sign_out", nickname]) do
|
||||
start_pleroma()
|
||||
|
||||
with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
|
||||
OAuth.Token.delete_user_tokens(user)
|
||||
OAuth.Authorization.delete_user_authorizations(user)
|
||||
|
||||
shell_info("#{nickname} signed out from all apps.")
|
||||
else
|
||||
_ ->
|
||||
shell_error("No local user #{nickname}")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -416,7 +433,7 @@ defmodule Mix.Tasks.Pleroma.User do
|
|||
|
||||
{:ok, user} = User.update_and_set_cache(user_cng)
|
||||
|
||||
Mix.shell().info("Moderator status of #{user.nickname}: #{user.info.is_moderator}")
|
||||
shell_info("Moderator status of #{user.nickname}: #{user.info.is_moderator}")
|
||||
user
|
||||
end
|
||||
|
||||
|
|
@ -429,7 +446,7 @@ defmodule Mix.Tasks.Pleroma.User do
|
|||
|
||||
{:ok, user} = User.update_and_set_cache(user_cng)
|
||||
|
||||
Mix.shell().info("Admin status of #{user.nickname}: #{user.info.is_admin}")
|
||||
shell_info("Admin status of #{user.nickname}: #{user.info.is_admin}")
|
||||
user
|
||||
end
|
||||
|
||||
|
|
@ -442,7 +459,7 @@ defmodule Mix.Tasks.Pleroma.User do
|
|||
|
||||
{:ok, user} = User.update_and_set_cache(user_cng)
|
||||
|
||||
Mix.shell().info("Locked status of #{user.nickname}: #{user.info.locked}")
|
||||
shell_info("Locked status of #{user.nickname}: #{user.info.locked}")
|
||||
user
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -39,8 +39,7 @@ defmodule Pleroma.Activity.Search do
|
|||
"to_tsvector('english', ?->>'content') @@ plainto_tsquery('english', ?)",
|
||||
o.data,
|
||||
^search_query
|
||||
),
|
||||
order_by: [desc: :id]
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
|
|
@ -56,18 +55,19 @@ defmodule Pleroma.Activity.Search do
|
|||
)
|
||||
end
|
||||
|
||||
# users can search everything
|
||||
defp maybe_restrict_local(q, %User{}), do: q
|
||||
defp maybe_restrict_local(q, user) do
|
||||
limit = Pleroma.Config.get([:instance, :limit_to_local_content], :unauthenticated)
|
||||
|
||||
# unauthenticated users can only search local activities
|
||||
defp maybe_restrict_local(q, _) do
|
||||
if Pleroma.Config.get([:instance, :limit_unauthenticated_to_local_content], true) do
|
||||
where(q, local: true)
|
||||
else
|
||||
q
|
||||
case {limit, user} do
|
||||
{:all, _} -> restrict_local(q)
|
||||
{:unauthenticated, %User{}} -> q
|
||||
{:unauthenticated, _} -> restrict_local(q)
|
||||
{false, _} -> q
|
||||
end
|
||||
end
|
||||
|
||||
defp restrict_local(q), do: where(q, local: true)
|
||||
|
||||
defp maybe_fetch(activities, user, search_query) do
|
||||
with true <- Regex.match?(~r/https?:/, search_query),
|
||||
{:ok, object} <- Fetcher.fetch_object_from_id(search_query),
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ defmodule Pleroma.Application do
|
|||
[
|
||||
# Start the Ecto repository
|
||||
%{id: Pleroma.Repo, start: {Pleroma.Repo, :start_link, []}, type: :supervisor},
|
||||
%{id: Pleroma.Config.TransferTask, start: {Pleroma.Config.TransferTask, :start_link, []}},
|
||||
%{id: Pleroma.Emoji, start: {Pleroma.Emoji, :start_link, []}},
|
||||
%{id: Pleroma.Captcha, start: {Pleroma.Captcha, :start_link, []}},
|
||||
%{
|
||||
|
|
@ -180,7 +181,6 @@ defmodule Pleroma.Application do
|
|||
Pleroma.Repo.Instrumenter.setup()
|
||||
end
|
||||
|
||||
Prometheus.Registry.register_collector(:prometheus_process_collector)
|
||||
Pleroma.Web.Endpoint.MetricsExporter.setup()
|
||||
Pleroma.Web.Endpoint.PipelineInstrumenter.setup()
|
||||
Pleroma.Web.Endpoint.Instrumenter.setup()
|
||||
|
|
@ -193,14 +193,14 @@ defmodule Pleroma.Application do
|
|||
else
|
||||
[]
|
||||
end ++
|
||||
if Pleroma.Config.get([Pleroma.Uploader, :proxy_remote]) do
|
||||
if Pleroma.Config.get([Pleroma.Upload, :proxy_remote]) do
|
||||
[:upload]
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
if Mix.env() == :test do
|
||||
if Pleroma.Config.get(:env) == :test do
|
||||
defp streamer_child, do: []
|
||||
defp chat_child, do: []
|
||||
else
|
||||
|
|
|
|||
55
lib/pleroma/config/transfer_task.ex
Normal file
55
lib/pleroma/config/transfer_task.ex
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
defmodule Pleroma.Config.TransferTask do
|
||||
use Task
|
||||
alias Pleroma.Web.AdminAPI.Config
|
||||
|
||||
def start_link do
|
||||
load_and_update_env()
|
||||
if Pleroma.Config.get(:env) == :test, do: Ecto.Adapters.SQL.Sandbox.checkin(Pleroma.Repo)
|
||||
:ignore
|
||||
end
|
||||
|
||||
def load_and_update_env do
|
||||
if Pleroma.Config.get([:instance, :dynamic_configuration]) and
|
||||
Ecto.Adapters.SQL.table_exists?(Pleroma.Repo, "config") do
|
||||
for_restart =
|
||||
Pleroma.Repo.all(Config)
|
||||
|> Enum.map(&update_env(&1))
|
||||
|
||||
# We need to restart applications for loaded settings take effect
|
||||
for_restart
|
||||
|> Enum.reject(&(&1 in [:pleroma, :ok]))
|
||||
|> Enum.each(fn app ->
|
||||
Application.stop(app)
|
||||
:ok = Application.start(app)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
defp update_env(setting) do
|
||||
try do
|
||||
key =
|
||||
if String.starts_with?(setting.key, "Pleroma.") do
|
||||
"Elixir." <> setting.key
|
||||
else
|
||||
setting.key
|
||||
end
|
||||
|
||||
group = String.to_existing_atom(setting.group)
|
||||
|
||||
Application.put_env(
|
||||
group,
|
||||
String.to_existing_atom(key),
|
||||
Config.from_binary(setting.value)
|
||||
)
|
||||
|
||||
group
|
||||
rescue
|
||||
e ->
|
||||
require Logger
|
||||
|
||||
Logger.warn(
|
||||
"updating env causes error, key: #{inspect(setting.key)}, error: #{inspect(e)}"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -59,10 +59,10 @@ defmodule Pleroma.Conversation.Participation do
|
|||
def for_user(user, params \\ %{}) do
|
||||
from(p in __MODULE__,
|
||||
where: p.user_id == ^user.id,
|
||||
order_by: [desc: p.updated_at]
|
||||
order_by: [desc: p.updated_at],
|
||||
preload: [conversation: [:users]]
|
||||
)
|
||||
|> Pleroma.Pagination.fetch_paginated(params)
|
||||
|> Repo.preload(conversation: [:users])
|
||||
end
|
||||
|
||||
def for_user_with_last_activity_id(user, params \\ %{}) do
|
||||
|
|
|
|||
|
|
@ -23,13 +23,8 @@ defmodule Pleroma.Emails.UserEmail do
|
|||
defp recipient(email, name), do: {name, email}
|
||||
defp recipient(%Pleroma.User{} = user), do: recipient(user.email, user.name)
|
||||
|
||||
def password_reset_email(user, password_reset_token) when is_binary(password_reset_token) do
|
||||
password_reset_url =
|
||||
Router.Helpers.util_url(
|
||||
Endpoint,
|
||||
:show_password_reset,
|
||||
password_reset_token
|
||||
)
|
||||
def password_reset_email(user, token) when is_binary(token) do
|
||||
password_reset_url = Router.Helpers.reset_password_url(Endpoint, :reset, token)
|
||||
|
||||
html_body = """
|
||||
<h3>Reset your password at #{instance_name()}</h3>
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ defmodule Pleroma.Emoji do
|
|||
|
||||
@ets __MODULE__.Ets
|
||||
@ets_options [:ordered_set, :protected, :named_table, {:read_concurrency, true}]
|
||||
@groups Pleroma.Config.get([:emoji, :groups])
|
||||
|
||||
@doc false
|
||||
def start_link do
|
||||
|
|
@ -87,6 +86,8 @@ defmodule Pleroma.Emoji do
|
|||
"emoji"
|
||||
)
|
||||
|
||||
emoji_groups = Pleroma.Config.get([:emoji, :groups])
|
||||
|
||||
case File.ls(emoji_dir_path) do
|
||||
{:error, :enoent} ->
|
||||
# The custom emoji directory doesn't exist,
|
||||
|
|
@ -98,7 +99,9 @@ defmodule Pleroma.Emoji do
|
|||
Logger.error("Could not access the custom emoji directory #{emoji_dir_path}: #{e}")
|
||||
|
||||
{:ok, results} ->
|
||||
grouped = Enum.group_by(results, &File.dir?/1)
|
||||
grouped =
|
||||
Enum.group_by(results, fn file -> File.dir?(Path.join(emoji_dir_path, file)) end)
|
||||
|
||||
packs = grouped[true] || []
|
||||
files = grouped[false] || []
|
||||
|
||||
|
|
@ -116,7 +119,7 @@ defmodule Pleroma.Emoji do
|
|||
emojis =
|
||||
Enum.flat_map(
|
||||
packs,
|
||||
fn pack -> load_pack(Path.join(emoji_dir_path, pack)) end
|
||||
fn pack -> load_pack(Path.join(emoji_dir_path, pack), emoji_groups) end
|
||||
)
|
||||
|
||||
true = :ets.insert(@ets, emojis)
|
||||
|
|
@ -127,9 +130,9 @@ defmodule Pleroma.Emoji do
|
|||
shortcode_globs = Pleroma.Config.get([:emoji, :shortcode_globs], [])
|
||||
|
||||
emojis =
|
||||
(load_from_file("config/emoji.txt") ++
|
||||
load_from_file("config/custom_emoji.txt") ++
|
||||
load_from_globs(shortcode_globs))
|
||||
(load_from_file("config/emoji.txt", emoji_groups) ++
|
||||
load_from_file("config/custom_emoji.txt", emoji_groups) ++
|
||||
load_from_globs(shortcode_globs, emoji_groups))
|
||||
|> Enum.reject(fn value -> value == nil end)
|
||||
|
||||
true = :ets.insert(@ets, emojis)
|
||||
|
|
@ -137,23 +140,25 @@ defmodule Pleroma.Emoji do
|
|||
:ok
|
||||
end
|
||||
|
||||
defp load_pack(pack_dir) do
|
||||
defp load_pack(pack_dir, emoji_groups) do
|
||||
pack_name = Path.basename(pack_dir)
|
||||
|
||||
emoji_txt = Path.join(pack_dir, "emoji.txt")
|
||||
|
||||
if File.exists?(emoji_txt) do
|
||||
load_from_file(emoji_txt)
|
||||
load_from_file(emoji_txt, emoji_groups)
|
||||
else
|
||||
extensions = Pleroma.Config.get([:emoji, :pack_extensions])
|
||||
|
||||
Logger.info(
|
||||
"No emoji.txt found for pack \"#{pack_name}\", assuming all .png files are emoji"
|
||||
"No emoji.txt found for pack \"#{pack_name}\", assuming all #{Enum.join(extensions, ", ")} files are emoji"
|
||||
)
|
||||
|
||||
make_shortcode_to_file_map(pack_dir, [".png"])
|
||||
make_shortcode_to_file_map(pack_dir, extensions)
|
||||
|> Enum.map(fn {shortcode, rel_file} ->
|
||||
filename = Path.join("/emoji/#{pack_name}", rel_file)
|
||||
|
||||
{shortcode, filename, [to_string(match_extra(@groups, filename))]}
|
||||
{shortcode, filename, [to_string(match_extra(emoji_groups, filename))]}
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
@ -182,21 +187,21 @@ defmodule Pleroma.Emoji do
|
|||
|> Enum.filter(fn f -> Path.extname(f) in exts end)
|
||||
end
|
||||
|
||||
defp load_from_file(file) do
|
||||
defp load_from_file(file, emoji_groups) do
|
||||
if File.exists?(file) do
|
||||
load_from_file_stream(File.stream!(file))
|
||||
load_from_file_stream(File.stream!(file), emoji_groups)
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
defp load_from_file_stream(stream) do
|
||||
defp load_from_file_stream(stream, emoji_groups) do
|
||||
stream
|
||||
|> Stream.map(&String.trim/1)
|
||||
|> Stream.map(fn line ->
|
||||
case String.split(line, ~r/,\s*/) do
|
||||
[name, file] ->
|
||||
{name, file, [to_string(match_extra(@groups, file))]}
|
||||
{name, file, [to_string(match_extra(emoji_groups, file))]}
|
||||
|
||||
[name, file | tags] ->
|
||||
{name, file, tags}
|
||||
|
|
@ -208,7 +213,7 @@ defmodule Pleroma.Emoji do
|
|||
|> Enum.to_list()
|
||||
end
|
||||
|
||||
defp load_from_globs(globs) do
|
||||
defp load_from_globs(globs, emoji_groups) do
|
||||
static_path = Path.join(:code.priv_dir(:pleroma), "static")
|
||||
|
||||
paths =
|
||||
|
|
@ -219,7 +224,7 @@ defmodule Pleroma.Emoji do
|
|||
|> Enum.concat()
|
||||
|
||||
Enum.map(paths, fn path ->
|
||||
tag = match_extra(@groups, Path.join("/", Path.relative_to(path, static_path)))
|
||||
tag = match_extra(emoji_groups, Path.join("/", Path.relative_to(path, static_path)))
|
||||
shortcode = Path.basename(path, Path.extname(path))
|
||||
external_path = Path.join("/", Path.relative_to(path, static_path))
|
||||
{shortcode, external_path, [to_string(tag)]}
|
||||
|
|
|
|||
27
lib/pleroma/helpers/uri_helper.ex
Normal file
27
lib/pleroma/helpers/uri_helper.ex
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Helpers.UriHelper do
|
||||
def append_uri_params(uri, appended_params) do
|
||||
uri = URI.parse(uri)
|
||||
appended_params = for {k, v} <- appended_params, into: %{}, do: {to_string(k), v}
|
||||
existing_params = URI.query_decoder(uri.query || "") |> Enum.into(%{})
|
||||
updated_params_keys = Enum.uniq(Map.keys(existing_params) ++ Map.keys(appended_params))
|
||||
|
||||
updated_params =
|
||||
for k <- updated_params_keys, do: {k, appended_params[k] || existing_params[k]}
|
||||
|
||||
uri
|
||||
|> Map.put(:query, URI.encode_query(updated_params))
|
||||
|> URI.to_string()
|
||||
end
|
||||
|
||||
def append_param_if_present(%{} = params, param_name, param_value) do
|
||||
if param_value do
|
||||
Map.put(params, param_name, param_value)
|
||||
else
|
||||
params
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -89,7 +89,7 @@ defmodule Pleroma.HTML do
|
|||
Cachex.fetch!(:scrubber_cache, key, fn _key ->
|
||||
result =
|
||||
content
|
||||
|> Floki.filter_out("a.mention")
|
||||
|> Floki.filter_out("a.mention,a.hashtag,a[rel~=\"tag\"]")
|
||||
|> Floki.attribute("a", "href")
|
||||
|> Enum.at(0)
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ defmodule Pleroma.Instances do
|
|||
|
||||
def reachability_datetime_threshold do
|
||||
federation_reachability_timeout_days =
|
||||
Pleroma.Config.get(:instance)[:federation_reachability_timeout_days] || 0
|
||||
Pleroma.Config.get([:instance, :federation_reachability_timeout_days], 0)
|
||||
|
||||
if federation_reachability_timeout_days > 0 do
|
||||
NaiveDateTime.add(
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ defmodule Pleroma.Notification do
|
|||
alias Pleroma.User
|
||||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.Web.CommonAPI.Utils
|
||||
alias Pleroma.Web.Push
|
||||
alias Pleroma.Web.Streamer
|
||||
|
||||
import Ecto.Query
|
||||
import Ecto.Changeset
|
||||
|
|
@ -149,8 +151,7 @@ defmodule Pleroma.Notification do
|
|||
end
|
||||
end
|
||||
|
||||
def create_notifications(%Activity{data: %{"to" => _, "type" => type}} = activity)
|
||||
when type in ["Create", "Like", "Announce", "Follow"] do
|
||||
def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = activity) do
|
||||
object = Object.normalize(activity)
|
||||
|
||||
unless object && object.data["type"] == "Answer" do
|
||||
|
|
@ -162,6 +163,13 @@ defmodule Pleroma.Notification do
|
|||
end
|
||||
end
|
||||
|
||||
def create_notifications(%Activity{data: %{"to" => _, "type" => type}} = activity)
|
||||
when type in ["Like", "Announce", "Follow"] do
|
||||
users = get_notified_from_activity(activity)
|
||||
notifications = Enum.map(users, fn user -> create_notification(activity, user) end)
|
||||
{:ok, notifications}
|
||||
end
|
||||
|
||||
def create_notifications(_), do: {:ok, []}
|
||||
|
||||
# TODO move to sql, too.
|
||||
|
|
@ -169,8 +177,9 @@ defmodule Pleroma.Notification do
|
|||
unless skip?(activity, user) 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)
|
||||
Streamer.stream("user", notification)
|
||||
Streamer.stream("user:notification", notification)
|
||||
Push.send(notification)
|
||||
notification
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,3 +1,7 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Object.Containment do
|
||||
@moduledoc """
|
||||
This module contains some useful functions for containing objects to specific
|
||||
|
|
|
|||
|
|
@ -85,6 +85,9 @@ defmodule Pleroma.Object.Fetcher do
|
|||
:ok <- Containment.contain_origin_from_id(id, data) do
|
||||
{:ok, data}
|
||||
else
|
||||
{:ok, %{status: code}} when code in [404, 410] ->
|
||||
{:error, "Object has been deleted"}
|
||||
|
||||
e ->
|
||||
{:error, e}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ defmodule Pleroma.PasswordResetToken do
|
|||
|> put_change(:used, true)
|
||||
end
|
||||
|
||||
@spec reset_password(binary(), map()) :: {:ok, User.t()} | {:error, binary()}
|
||||
def reset_password(token, data) do
|
||||
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
|
||||
%User{} = user <- User.get_cached_by_id(token.user_id),
|
||||
|
|
@ -56,14 +56,14 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do
|
|||
connect_src = "connect-src 'self' #{static_url} #{websocket_url}"
|
||||
|
||||
connect_src =
|
||||
if Mix.env() == :dev do
|
||||
if Pleroma.Config.get(:env) == :dev do
|
||||
connect_src <> " http://localhost:3035/"
|
||||
else
|
||||
connect_src
|
||||
end
|
||||
|
||||
script_src =
|
||||
if Mix.env() == :dev do
|
||||
if Pleroma.Config.get(:env) == :dev do
|
||||
"script-src 'self' 'unsafe-eval'"
|
||||
else
|
||||
"script-src 'self'"
|
||||
|
|
|
|||
84
lib/pleroma/plugs/idempotency_plug.ex
Normal file
84
lib/pleroma/plugs/idempotency_plug.ex
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Plugs.IdempotencyPlug do
|
||||
import Phoenix.Controller, only: [json: 2]
|
||||
import Plug.Conn
|
||||
|
||||
@behaviour Plug
|
||||
|
||||
@impl true
|
||||
def init(opts), do: opts
|
||||
|
||||
# Sending idempotency keys in `GET` and `DELETE` requests has no effect
|
||||
# and should be avoided, as these requests are idempotent by definition.
|
||||
|
||||
@impl true
|
||||
def call(%{method: method} = conn, _) when method in ["POST", "PUT", "PATCH"] do
|
||||
case get_req_header(conn, "idempotency-key") do
|
||||
[key] -> process_request(conn, key)
|
||||
_ -> conn
|
||||
end
|
||||
end
|
||||
|
||||
def call(conn, _), do: conn
|
||||
|
||||
def process_request(conn, key) do
|
||||
case Cachex.get(:idempotency_cache, key) do
|
||||
{:ok, nil} ->
|
||||
cache_resposnse(conn, key)
|
||||
|
||||
{:ok, record} ->
|
||||
send_cached(conn, key, record)
|
||||
|
||||
{atom, message} when atom in [:ignore, :error] ->
|
||||
render_error(conn, message)
|
||||
end
|
||||
end
|
||||
|
||||
defp cache_resposnse(conn, key) do
|
||||
register_before_send(conn, fn conn ->
|
||||
[request_id] = get_resp_header(conn, "x-request-id")
|
||||
content_type = get_content_type(conn)
|
||||
|
||||
record = {request_id, content_type, conn.status, conn.resp_body}
|
||||
{:ok, _} = Cachex.put(:idempotency_cache, key, record)
|
||||
|
||||
conn
|
||||
|> put_resp_header("idempotency-key", key)
|
||||
|> put_resp_header("x-original-request-id", request_id)
|
||||
end)
|
||||
end
|
||||
|
||||
defp send_cached(conn, key, record) do
|
||||
{request_id, content_type, status, body} = record
|
||||
|
||||
conn
|
||||
|> put_resp_header("idempotency-key", key)
|
||||
|> put_resp_header("idempotent-replayed", "true")
|
||||
|> put_resp_header("x-original-request-id", request_id)
|
||||
|> put_resp_content_type(content_type)
|
||||
|> send_resp(status, body)
|
||||
|> halt()
|
||||
end
|
||||
|
||||
defp render_error(conn, message) do
|
||||
conn
|
||||
|> put_status(:unprocessable_entity)
|
||||
|> json(%{error: message})
|
||||
|> halt()
|
||||
end
|
||||
|
||||
defp get_content_type(conn) do
|
||||
[content_type] = get_resp_header(conn, "content-type")
|
||||
|
||||
if String.contains?(content_type, ";") do
|
||||
content_type
|
||||
|> String.split(";")
|
||||
|> hd()
|
||||
else
|
||||
content_type
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Plugs.RateLimitPlug do
|
||||
import Phoenix.Controller, only: [json: 2]
|
||||
import Plug.Conn
|
||||
|
||||
def init(opts), do: opts
|
||||
|
||||
def call(conn, opts) do
|
||||
enabled? = Pleroma.Config.get([:app_account_creation, :enabled])
|
||||
|
||||
case check_rate(conn, Map.put(opts, :enabled, enabled?)) do
|
||||
{:ok, _count} -> conn
|
||||
{:error, _count} -> render_error(conn)
|
||||
%Plug.Conn{} = conn -> conn
|
||||
end
|
||||
end
|
||||
|
||||
defp check_rate(conn, %{enabled: true} = opts) do
|
||||
max_requests = opts[:max_requests]
|
||||
bucket_name = conn.remote_ip |> Tuple.to_list() |> Enum.join(".")
|
||||
|
||||
ExRated.check_rate(bucket_name, opts[:interval] * 1000, max_requests)
|
||||
end
|
||||
|
||||
defp check_rate(conn, _), do: conn
|
||||
|
||||
defp render_error(conn) do
|
||||
conn
|
||||
|> put_status(:forbidden)
|
||||
|> json(%{error: "Rate limit exceeded."})
|
||||
|> halt()
|
||||
end
|
||||
end
|
||||
94
lib/pleroma/plugs/rate_limiter.ex
Normal file
94
lib/pleroma/plugs/rate_limiter.ex
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Plugs.RateLimiter do
|
||||
@moduledoc """
|
||||
|
||||
## Configuration
|
||||
|
||||
A keyword list of rate limiters where a key is a limiter name and value is the limiter configuration. The basic configuration is a tuple where:
|
||||
|
||||
* The first element: `scale` (Integer). The time scale in milliseconds.
|
||||
* The second element: `limit` (Integer). How many requests to limit in the time scale provided.
|
||||
|
||||
It is also possible to have different limits for unauthenticated and authenticated users: the keyword value must be a list of two tuples where the first one is a config for unauthenticated users and the second one is for authenticated.
|
||||
|
||||
To disable a limiter set its value to `nil`.
|
||||
|
||||
### Example
|
||||
|
||||
config :pleroma, :rate_limit,
|
||||
one: {1000, 10},
|
||||
two: [{10_000, 10}, {10_000, 50}],
|
||||
foobar: nil
|
||||
|
||||
Here we have three limiters:
|
||||
|
||||
* `one` which is not over 10req/1s
|
||||
* `two` which has two limits: 10req/10s for unauthenticated users and 50req/10s for authenticated users
|
||||
* `foobar` which is disabled
|
||||
|
||||
## Usage
|
||||
|
||||
Inside a controller:
|
||||
|
||||
plug(Pleroma.Plugs.RateLimiter, :one when action == :one)
|
||||
plug(Pleroma.Plugs.RateLimiter, :two when action in [:two, :three])
|
||||
|
||||
or inside a router pipiline:
|
||||
|
||||
pipeline :api do
|
||||
...
|
||||
plug(Pleroma.Plugs.RateLimiter, :one)
|
||||
...
|
||||
end
|
||||
"""
|
||||
|
||||
import Phoenix.Controller, only: [json: 2]
|
||||
import Plug.Conn
|
||||
|
||||
alias Pleroma.User
|
||||
|
||||
def init(limiter_name) do
|
||||
case Pleroma.Config.get([:rate_limit, limiter_name]) do
|
||||
nil -> nil
|
||||
config -> {limiter_name, config}
|
||||
end
|
||||
end
|
||||
|
||||
# do not limit if there is no limiter configuration
|
||||
def call(conn, nil), do: conn
|
||||
|
||||
def call(conn, opts) do
|
||||
case check_rate(conn, opts) do
|
||||
{:ok, _count} -> conn
|
||||
{:error, _count} -> render_error(conn)
|
||||
end
|
||||
end
|
||||
|
||||
defp check_rate(%{assigns: %{user: %User{id: user_id}}}, {limiter_name, [_, {scale, limit}]}) do
|
||||
ExRated.check_rate("#{limiter_name}:#{user_id}", scale, limit)
|
||||
end
|
||||
|
||||
defp check_rate(conn, {limiter_name, [{scale, limit} | _]}) do
|
||||
ExRated.check_rate("#{limiter_name}:#{ip(conn)}", scale, limit)
|
||||
end
|
||||
|
||||
defp check_rate(conn, {limiter_name, {scale, limit}}) do
|
||||
check_rate(conn, {limiter_name, [{scale, limit}]})
|
||||
end
|
||||
|
||||
def ip(%{remote_ip: remote_ip}) do
|
||||
remote_ip
|
||||
|> Tuple.to_list()
|
||||
|> Enum.join(".")
|
||||
end
|
||||
|
||||
defp render_error(conn) do
|
||||
conn
|
||||
|> put_status(:too_many_requests)
|
||||
|> json(%{error: "Throttled"})
|
||||
|> halt()
|
||||
end
|
||||
end
|
||||
|
|
@ -36,7 +36,7 @@ defmodule Pleroma.Plugs.UploadedMedia do
|
|||
conn
|
||||
end
|
||||
|
||||
config = Pleroma.Config.get([Pleroma.Upload])
|
||||
config = Pleroma.Config.get(Pleroma.Upload)
|
||||
|
||||
with uploader <- Keyword.fetch!(config, :uploader),
|
||||
proxy_remote = Keyword.get(config, :proxy_remote, false),
|
||||
|
|
|
|||
66
lib/pleroma/release_tasks.ex
Normal file
66
lib/pleroma/release_tasks.ex
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.ReleaseTasks do
|
||||
@repo Pleroma.Repo
|
||||
|
||||
def run(args) do
|
||||
[task | args] = String.split(args)
|
||||
|
||||
case task do
|
||||
"migrate" -> migrate(args)
|
||||
"create" -> create()
|
||||
"rollback" -> rollback(args)
|
||||
task -> mix_task(task, args)
|
||||
end
|
||||
end
|
||||
|
||||
defp mix_task(task, args) do
|
||||
Application.load(:pleroma)
|
||||
{:ok, modules} = :application.get_key(:pleroma, :modules)
|
||||
|
||||
module =
|
||||
Enum.find(modules, fn module ->
|
||||
module = Module.split(module)
|
||||
|
||||
match?(["Mix", "Tasks", "Pleroma" | _], module) and
|
||||
String.downcase(List.last(module)) == task
|
||||
end)
|
||||
|
||||
if module do
|
||||
module.run(args)
|
||||
else
|
||||
IO.puts("The task #{task} does not exist")
|
||||
end
|
||||
end
|
||||
|
||||
def migrate(args) do
|
||||
Mix.Tasks.Pleroma.Ecto.Migrate.run(args)
|
||||
end
|
||||
|
||||
def rollback(args) do
|
||||
Mix.Tasks.Pleroma.Ecto.Rollback.run(args)
|
||||
end
|
||||
|
||||
def create do
|
||||
Application.load(:pleroma)
|
||||
|
||||
case @repo.__adapter__.storage_up(@repo.config) do
|
||||
:ok ->
|
||||
IO.puts("The database for #{inspect(@repo)} has been created")
|
||||
|
||||
{:error, :already_up} ->
|
||||
IO.puts("The database for #{inspect(@repo)} has already been created")
|
||||
|
||||
{:error, term} when is_binary(term) ->
|
||||
IO.puts(:stderr, "The database for #{inspect(@repo)} couldn't be created: #{term}")
|
||||
|
||||
{:error, term} ->
|
||||
IO.puts(
|
||||
:stderr,
|
||||
"The database for #{inspect(@repo)} couldn't be created: #{inspect(term)}"
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
34
lib/pleroma/repo_streamer.ex
Normal file
34
lib/pleroma/repo_streamer.ex
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.RepoStreamer do
|
||||
alias Pleroma.Repo
|
||||
import Ecto.Query
|
||||
|
||||
def chunk_stream(query, chunk_size) do
|
||||
Stream.unfold(0, fn
|
||||
:halt ->
|
||||
{[], :halt}
|
||||
|
||||
last_id ->
|
||||
query
|
||||
|> order_by(asc: :id)
|
||||
|> where([r], r.id > ^last_id)
|
||||
|> limit(^chunk_size)
|
||||
|> Repo.all()
|
||||
|> case do
|
||||
[] ->
|
||||
{[], :halt}
|
||||
|
||||
records ->
|
||||
last_id = List.last(records).id
|
||||
{records, last_id}
|
||||
end
|
||||
end)
|
||||
|> Stream.take_while(fn
|
||||
[] -> false
|
||||
_ -> true
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
@ -146,7 +146,7 @@ defmodule Pleroma.ReverseProxy do
|
|||
Logger.debug("#{__MODULE__} #{method} #{url} #{inspect(headers)}")
|
||||
method = method |> String.downcase() |> String.to_existing_atom()
|
||||
|
||||
case :hackney.request(method, url, headers, "", hackney_opts) do
|
||||
case hackney().request(method, url, headers, "", hackney_opts) do
|
||||
{:ok, code, headers, client} when code in @valid_resp_codes ->
|
||||
{:ok, code, downcase_headers(headers), client}
|
||||
|
||||
|
|
@ -196,7 +196,7 @@ defmodule Pleroma.ReverseProxy do
|
|||
duration,
|
||||
Keyword.get(opts, :max_read_duration, @max_read_duration)
|
||||
),
|
||||
{:ok, data} <- :hackney.stream_body(client),
|
||||
{:ok, data} <- hackney().stream_body(client),
|
||||
{:ok, duration} <- increase_read_duration(duration),
|
||||
sent_so_far = sent_so_far + byte_size(data),
|
||||
:ok <- body_size_constraint(sent_so_far, Keyword.get(opts, :max_body_size)),
|
||||
|
|
@ -377,4 +377,6 @@ defmodule Pleroma.ReverseProxy do
|
|||
defp increase_read_duration(_) do
|
||||
{:ok, :no_duration_limit, :no_duration_limit}
|
||||
end
|
||||
|
||||
defp hackney, do: Pleroma.Config.get(:hackney, :hackney)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,12 +9,14 @@ defmodule Pleroma.User do
|
|||
import Ecto.Query
|
||||
|
||||
alias Comeonin.Pbkdf2
|
||||
alias Ecto.Multi
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Keys
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Registration
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.RepoStreamer
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
|
|
@ -194,27 +196,24 @@ defmodule Pleroma.User do
|
|||
end
|
||||
|
||||
def password_update_changeset(struct, params) do
|
||||
changeset =
|
||||
struct
|
||||
|> cast(params, [:password, :password_confirmation])
|
||||
|> validate_required([:password, :password_confirmation])
|
||||
|> validate_confirmation(:password)
|
||||
|
||||
OAuth.Token.delete_user_tokens(struct)
|
||||
OAuth.Authorization.delete_user_authorizations(struct)
|
||||
|
||||
if changeset.valid? do
|
||||
hashed = Pbkdf2.hashpwsalt(changeset.changes[:password])
|
||||
|
||||
changeset
|
||||
|> put_change(:password_hash, hashed)
|
||||
else
|
||||
changeset
|
||||
end
|
||||
struct
|
||||
|> cast(params, [:password, :password_confirmation])
|
||||
|> validate_required([:password, :password_confirmation])
|
||||
|> validate_confirmation(:password)
|
||||
|> put_password_hash
|
||||
end
|
||||
|
||||
def reset_password(user, data) do
|
||||
update_and_set_cache(password_update_changeset(user, data))
|
||||
def reset_password(%User{id: user_id} = user, data) do
|
||||
multi =
|
||||
Multi.new()
|
||||
|> Multi.update(:user, password_update_changeset(user, data))
|
||||
|> Multi.delete_all(:tokens, OAuth.Token.Query.get_by_user(user_id))
|
||||
|> Multi.delete_all(:auth, OAuth.Authorization.delete_by_user_query(user))
|
||||
|
||||
case Repo.transaction(multi) do
|
||||
{:ok, %{user: user} = _} -> set_cache(user)
|
||||
{:error, _, changeset, _} -> {:error, changeset}
|
||||
end
|
||||
end
|
||||
|
||||
def register_changeset(struct, params \\ %{}, opts \\ []) do
|
||||
|
|
@ -250,12 +249,11 @@ defmodule Pleroma.User do
|
|||
end
|
||||
|
||||
if changeset.valid? do
|
||||
hashed = Pbkdf2.hashpwsalt(changeset.changes[:password])
|
||||
ap_id = User.ap_id(%User{nickname: changeset.changes[:nickname]})
|
||||
followers = User.ap_followers(%User{nickname: changeset.changes[:nickname]})
|
||||
|
||||
changeset
|
||||
|> put_change(:password_hash, hashed)
|
||||
|> put_password_hash
|
||||
|> put_change(:ap_id, ap_id)
|
||||
|> unique_constraint(:ap_id)
|
||||
|> put_change(:following, [followers])
|
||||
|
|
@ -933,18 +931,24 @@ defmodule Pleroma.User do
|
|||
|
||||
@spec perform(atom(), User.t()) :: {:ok, User.t()}
|
||||
def perform(:delete, %User{} = user) do
|
||||
{:ok, user} = User.deactivate(user)
|
||||
|
||||
# Remove all relationships
|
||||
{:ok, followers} = User.get_followers(user)
|
||||
|
||||
Enum.each(followers, fn follower -> User.unfollow(follower, user) end)
|
||||
Enum.each(followers, fn follower ->
|
||||
ActivityPub.unfollow(follower, user)
|
||||
User.unfollow(follower, user)
|
||||
end)
|
||||
|
||||
{:ok, friends} = User.get_friends(user)
|
||||
|
||||
Enum.each(friends, fn followed -> User.unfollow(user, followed) end)
|
||||
Enum.each(friends, fn followed ->
|
||||
ActivityPub.unfollow(user, followed)
|
||||
User.unfollow(user, followed)
|
||||
end)
|
||||
|
||||
delete_user_activities(user)
|
||||
|
||||
{:ok, _user} = Repo.delete(user)
|
||||
end
|
||||
|
||||
@spec perform(atom(), User.t()) :: {:ok, User.t()}
|
||||
|
|
@ -1017,18 +1021,35 @@ defmodule Pleroma.User do
|
|||
])
|
||||
|
||||
def delete_user_activities(%User{ap_id: ap_id} = user) do
|
||||
stream =
|
||||
ap_id
|
||||
|> Activity.query_by_actor()
|
||||
|> Repo.stream()
|
||||
|
||||
Repo.transaction(fn -> Enum.each(stream, &delete_activity(&1)) end, timeout: :infinity)
|
||||
ap_id
|
||||
|> Activity.query_by_actor()
|
||||
|> RepoStreamer.chunk_stream(50)
|
||||
|> Stream.each(fn activities ->
|
||||
Enum.each(activities, &delete_activity(&1))
|
||||
end)
|
||||
|> Stream.run()
|
||||
|
||||
{:ok, user}
|
||||
end
|
||||
|
||||
defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
|
||||
Object.normalize(activity) |> ActivityPub.delete()
|
||||
activity
|
||||
|> Object.normalize()
|
||||
|> ActivityPub.delete()
|
||||
end
|
||||
|
||||
defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
|
||||
user = get_cached_by_ap_id(activity.actor)
|
||||
object = Object.normalize(activity)
|
||||
|
||||
ActivityPub.unlike(user, object)
|
||||
end
|
||||
|
||||
defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
|
||||
user = get_cached_by_ap_id(activity.actor)
|
||||
object = Object.normalize(activity)
|
||||
|
||||
ActivityPub.unannounce(user, object)
|
||||
end
|
||||
|
||||
defp delete_activity(_activity), do: "Doing nothing"
|
||||
|
|
@ -1037,9 +1058,7 @@ defmodule Pleroma.User do
|
|||
Pleroma.HTML.Scrubber.TwitterText
|
||||
end
|
||||
|
||||
@default_scrubbers Pleroma.Config.get([:markup, :scrub_policy])
|
||||
|
||||
def html_filter_policy(_), do: @default_scrubbers
|
||||
def html_filter_policy(_), do: Pleroma.Config.get([:markup, :scrub_policy])
|
||||
|
||||
def fetch_by_ap_id(ap_id) do
|
||||
ap_try = ActivityPub.make_user_from_ap_id(ap_id)
|
||||
|
|
@ -1402,4 +1421,12 @@ defmodule Pleroma.User do
|
|||
end
|
||||
|
||||
defdelegate search(query, opts \\ []), to: User.Search
|
||||
|
||||
defp put_password_hash(
|
||||
%Ecto.Changeset{valid?: true, changes: %{password: password}} = changeset
|
||||
) do
|
||||
change(changeset, password_hash: Pbkdf2.hashpwsalt(password))
|
||||
end
|
||||
|
||||
defp put_password_hash(changeset), do: changeset
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,74 +7,97 @@ defmodule Pleroma.User.Search do
|
|||
alias Pleroma.User
|
||||
import Ecto.Query
|
||||
|
||||
def search(query, opts \\ []) do
|
||||
@similarity_threshold 0.25
|
||||
@limit 20
|
||||
|
||||
def search(query_string, opts \\ []) do
|
||||
resolve = Keyword.get(opts, :resolve, false)
|
||||
following = Keyword.get(opts, :following, false)
|
||||
result_limit = Keyword.get(opts, :limit, @limit)
|
||||
offset = Keyword.get(opts, :offset, 0)
|
||||
|
||||
for_user = Keyword.get(opts, :for_user)
|
||||
|
||||
# Strip the beginning @ off if there is a query
|
||||
query = String.trim_leading(query, "@")
|
||||
query_string = String.trim_leading(query_string, "@")
|
||||
|
||||
maybe_resolve(resolve, for_user, query)
|
||||
maybe_resolve(resolve, for_user, query_string)
|
||||
|
||||
{:ok, results} =
|
||||
Repo.transaction(fn ->
|
||||
Ecto.Adapters.SQL.query(Repo, "select set_limit(0.25)", [])
|
||||
Ecto.Adapters.SQL.query(
|
||||
Repo,
|
||||
"select set_limit(#{@similarity_threshold})",
|
||||
[]
|
||||
)
|
||||
|
||||
query
|
||||
|> search_query(for_user)
|
||||
query_string
|
||||
|> search_query(for_user, following)
|
||||
|> paginate(result_limit, offset)
|
||||
|> Repo.all()
|
||||
end)
|
||||
|
||||
results
|
||||
end
|
||||
|
||||
defp maybe_resolve(true, %User{}, query) do
|
||||
User.get_or_fetch(query)
|
||||
end
|
||||
|
||||
defp maybe_resolve(true, _, query) do
|
||||
unless restrict_local?(), do: User.get_or_fetch(query)
|
||||
end
|
||||
|
||||
defp maybe_resolve(_, _, _), do: :noop
|
||||
|
||||
defp search_query(query, for_user) do
|
||||
query
|
||||
|> union_query()
|
||||
defp search_query(query_string, for_user, following) do
|
||||
for_user
|
||||
|> base_query(following)
|
||||
|> search_subqueries(query_string)
|
||||
|> union_subqueries
|
||||
|> distinct_query()
|
||||
|> boost_search_rank_query(for_user)
|
||||
|> subquery()
|
||||
|> order_by(desc: :search_rank)
|
||||
|> limit(20)
|
||||
|> maybe_restrict_local(for_user)
|
||||
end
|
||||
|
||||
defp restrict_local? do
|
||||
Pleroma.Config.get([:instance, :limit_unauthenticated_to_local_content], true)
|
||||
defp base_query(_user, false), do: User
|
||||
defp base_query(user, true), do: User.get_followers_query(user)
|
||||
|
||||
defp paginate(query, limit, offset) do
|
||||
from(q in query, limit: ^limit, offset: ^offset)
|
||||
end
|
||||
|
||||
defp union_query(query) do
|
||||
fts_subquery = fts_search_subquery(query)
|
||||
trigram_subquery = trigram_search_subquery(query)
|
||||
|
||||
defp union_subqueries({fts_subquery, trigram_subquery}) do
|
||||
from(s in trigram_subquery, union_all: ^fts_subquery)
|
||||
end
|
||||
|
||||
defp search_subqueries(base_query, query_string) do
|
||||
{
|
||||
fts_search_subquery(base_query, query_string),
|
||||
trigram_search_subquery(base_query, query_string)
|
||||
}
|
||||
end
|
||||
|
||||
defp distinct_query(q) do
|
||||
from(s in subquery(q), order_by: s.search_type, distinct: s.id)
|
||||
end
|
||||
|
||||
# unauthenticated users can only search local activities
|
||||
defp maybe_restrict_local(q, %User{}), do: q
|
||||
|
||||
defp maybe_restrict_local(q, _) do
|
||||
if restrict_local?() do
|
||||
where(q, [u], u.local == true)
|
||||
else
|
||||
q
|
||||
defp maybe_resolve(true, user, query) do
|
||||
case {limit(), user} do
|
||||
{:all, _} -> :noop
|
||||
{:unauthenticated, %User{}} -> User.get_or_fetch(query)
|
||||
{:unauthenticated, _} -> :noop
|
||||
{false, _} -> User.get_or_fetch(query)
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_resolve(_, _, _), do: :noop
|
||||
|
||||
defp maybe_restrict_local(q, user) do
|
||||
case {limit(), user} do
|
||||
{:all, _} -> restrict_local(q)
|
||||
{:unauthenticated, %User{}} -> q
|
||||
{:unauthenticated, _} -> restrict_local(q)
|
||||
{false, _} -> q
|
||||
end
|
||||
end
|
||||
|
||||
defp limit, do: Pleroma.Config.get([:instance, :limit_to_local_content], :unauthenticated)
|
||||
|
||||
defp restrict_local(q), do: where(q, [u], u.local == true)
|
||||
|
||||
defp boost_search_rank_query(query, nil), do: query
|
||||
|
||||
defp boost_search_rank_query(query, for_user) do
|
||||
|
|
@ -103,7 +126,8 @@ defmodule Pleroma.User.Search do
|
|||
)
|
||||
end
|
||||
|
||||
defp fts_search_subquery(term, query \\ User) do
|
||||
@spec fts_search_subquery(User.t() | Ecto.Query.t(), String.t()) :: Ecto.Query.t()
|
||||
defp fts_search_subquery(query, term) do
|
||||
processed_query =
|
||||
term
|
||||
|> String.replace(~r/\W+/, " ")
|
||||
|
|
@ -145,9 +169,10 @@ defmodule Pleroma.User.Search do
|
|||
|> User.restrict_deactivated()
|
||||
end
|
||||
|
||||
defp trigram_search_subquery(term) do
|
||||
@spec trigram_search_subquery(User.t() | Ecto.Query.t(), String.t()) :: Ecto.Query.t()
|
||||
defp trigram_search_subquery(query, term) do
|
||||
from(
|
||||
u in User,
|
||||
u in query,
|
||||
select_merge: %{
|
||||
# ^1 gives 'Postgrex expected a binary, got 1' for some weird reason
|
||||
search_type: fragment("?", 1),
|
||||
|
|
|
|||
|
|
@ -189,6 +189,22 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
end)
|
||||
end
|
||||
|
||||
def stream_out_participations(%Object{data: %{"context" => context}}, user) do
|
||||
with %Conversation{} = conversation <- Conversation.get_for_ap_id(context),
|
||||
conversation = Repo.preload(conversation, :participations),
|
||||
last_activity_id =
|
||||
fetch_latest_activity_id_for_context(conversation.ap_id, %{
|
||||
"user" => user,
|
||||
"blocking_user" => user
|
||||
}) do
|
||||
if last_activity_id do
|
||||
stream_out_participations(conversation.participations)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def stream_out_participations(_, _), do: :noop
|
||||
|
||||
def stream_out(activity) do
|
||||
public = "https://www.w3.org/ns/activitystreams#Public"
|
||||
|
||||
|
|
@ -401,7 +417,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|
|||
"to" => to,
|
||||
"deleted_activity_id" => activity && activity.id
|
||||
},
|
||||
{:ok, activity} <- insert(data, local),
|
||||
{:ok, activity} <- insert(data, local, false),
|
||||
stream_out_participations(object, user),
|
||||
_ <- decrease_replies_count_if_reply(object),
|
||||
# Changing note count prior to enqueuing federation task in order to avoid
|
||||
# race conditions on updating user.info
|
||||
|
|
|
|||
48
lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex
Normal file
48
lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do
|
||||
alias Pleroma.User
|
||||
|
||||
require Logger
|
||||
|
||||
# has the user successfully posted before?
|
||||
defp old_user?(%User{} = u) do
|
||||
u.info.note_count > 0 || u.info.follower_count > 0
|
||||
end
|
||||
|
||||
# does the post contain links?
|
||||
defp contains_links?(%{"content" => content} = _object) do
|
||||
content
|
||||
|> Floki.filter_out("a.mention,a.hashtag,a[rel~=\"tag\"],a.zrl")
|
||||
|> Floki.attribute("a", "href")
|
||||
|> length() > 0
|
||||
end
|
||||
|
||||
defp contains_links?(_), do: false
|
||||
|
||||
def filter(%{"type" => "Create", "actor" => actor, "object" => object} = message) do
|
||||
with {:ok, %User{} = u} <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:contains_links, true} <- {:contains_links, contains_links?(object)},
|
||||
{:old_user, true} <- {:old_user, old_user?(u)} do
|
||||
{:ok, message}
|
||||
else
|
||||
{:contains_links, false} ->
|
||||
{:ok, message}
|
||||
|
||||
{:old_user, false} ->
|
||||
{:reject, nil}
|
||||
|
||||
{:error, _} ->
|
||||
{:reject, nil}
|
||||
|
||||
e ->
|
||||
Logger.warn("[MRF anti-link-spam] WTF: unhandled error #{inspect(e)}")
|
||||
{:reject, nil}
|
||||
end
|
||||
end
|
||||
|
||||
# in all other cases, pass through
|
||||
def filter(message), do: {:ok, message}
|
||||
end
|
||||
|
|
@ -88,7 +88,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do
|
|||
true
|
||||
else
|
||||
inbox_info = URI.parse(inbox)
|
||||
!Enum.member?(Pleroma.Config.get([:instance, :quarantined_instances], []), inbox_info.host)
|
||||
!Enum.member?(Config.get([:instance, :quarantined_instances], []), inbox_info.host)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -339,7 +339,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
def fix_type(%{"inReplyTo" => reply_id} = object) when is_binary(reply_id) do
|
||||
reply = Object.normalize(reply_id)
|
||||
|
||||
if reply.data["type"] == "Question" and object["name"] do
|
||||
if reply && (reply.data["type"] == "Question" and object["name"]) do
|
||||
Map.put(object, "type", "Answer")
|
||||
else
|
||||
object
|
||||
|
|
|
|||
|
|
@ -151,16 +151,18 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
|
||||
def create_context(context) do
|
||||
context = context || generate_id("contexts")
|
||||
changeset = Object.context_mapping(context)
|
||||
|
||||
case Repo.insert(changeset) do
|
||||
{:ok, object} ->
|
||||
# Ecto has problems accessing the constraint inside the jsonb,
|
||||
# so we explicitly check for the existed object before insert
|
||||
object = Object.get_cached_by_ap_id(context)
|
||||
|
||||
with true <- is_nil(object),
|
||||
changeset <- Object.context_mapping(context),
|
||||
{:ok, inserted_object} <- Repo.insert(changeset) do
|
||||
inserted_object
|
||||
else
|
||||
_ ->
|
||||
object
|
||||
|
||||
# This should be solved by an upsert, but it seems ecto
|
||||
# has problems accessing the constraint inside the jsonb.
|
||||
{:error, _} ->
|
||||
Object.get_cached_by_ap_id(context)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Relay
|
||||
alias Pleroma.Web.AdminAPI.AccountView
|
||||
alias Pleroma.Web.AdminAPI.Config
|
||||
alias Pleroma.Web.AdminAPI.ConfigView
|
||||
alias Pleroma.Web.AdminAPI.ReportView
|
||||
alias Pleroma.Web.AdminAPI.Search
|
||||
alias Pleroma.Web.CommonAPI
|
||||
|
|
@ -362,6 +364,41 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
|||
end
|
||||
end
|
||||
|
||||
def config_show(conn, _params) do
|
||||
configs = Pleroma.Repo.all(Config)
|
||||
|
||||
conn
|
||||
|> put_view(ConfigView)
|
||||
|> render("index.json", %{configs: configs})
|
||||
end
|
||||
|
||||
def config_update(conn, %{"configs" => configs}) do
|
||||
updated =
|
||||
if Pleroma.Config.get([:instance, :dynamic_configuration]) do
|
||||
updated =
|
||||
Enum.map(configs, fn
|
||||
%{"group" => group, "key" => key, "value" => value} ->
|
||||
{:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
|
||||
config
|
||||
|
||||
%{"group" => group, "key" => key, "delete" => "true"} ->
|
||||
{:ok, _} = Config.delete(%{group: group, key: key})
|
||||
nil
|
||||
end)
|
||||
|> Enum.reject(&is_nil(&1))
|
||||
|
||||
Pleroma.Config.TransferTask.load_and_update_env()
|
||||
Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
|
||||
updated
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
||||
conn
|
||||
|> put_view(ConfigView)
|
||||
|> render("index.json", %{configs: updated})
|
||||
end
|
||||
|
||||
def errors(conn, {:error, :not_found}) do
|
||||
conn
|
||||
|> put_status(404)
|
||||
|
|
|
|||
160
lib/pleroma/web/admin_api/config.ex
Normal file
160
lib/pleroma/web/admin_api/config.ex
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.AdminAPI.Config do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
alias __MODULE__
|
||||
alias Pleroma.Repo
|
||||
|
||||
@type t :: %__MODULE__{}
|
||||
|
||||
schema "config" do
|
||||
field(:key, :string)
|
||||
field(:group, :string)
|
||||
field(:value, :binary)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
@spec get_by_params(map()) :: Config.t() | nil
|
||||
def get_by_params(params), do: Repo.get_by(Config, params)
|
||||
|
||||
@spec changeset(Config.t(), map()) :: Changeset.t()
|
||||
def changeset(config, params \\ %{}) do
|
||||
config
|
||||
|> cast(params, [:key, :group, :value])
|
||||
|> validate_required([:key, :group, :value])
|
||||
|> unique_constraint(:key, name: :config_group_key_index)
|
||||
end
|
||||
|
||||
@spec create(map()) :: {:ok, Config.t()} | {:error, Changeset.t()}
|
||||
def create(params) do
|
||||
%Config{}
|
||||
|> changeset(Map.put(params, :value, transform(params[:value])))
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
@spec update(Config.t(), map()) :: {:ok, Config} | {:error, Changeset.t()}
|
||||
def update(%Config{} = config, %{value: value}) do
|
||||
config
|
||||
|> change(value: transform(value))
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@spec update_or_create(map()) :: {:ok, Config.t()} | {:error, Changeset.t()}
|
||||
def update_or_create(params) do
|
||||
with %Config{} = config <- Config.get_by_params(Map.take(params, [:group, :key])) do
|
||||
Config.update(config, params)
|
||||
else
|
||||
nil -> Config.create(params)
|
||||
end
|
||||
end
|
||||
|
||||
@spec delete(map()) :: {:ok, Config.t()} | {:error, Changeset.t()}
|
||||
def delete(params) do
|
||||
with %Config{} = config <- Config.get_by_params(params) do
|
||||
Repo.delete(config)
|
||||
else
|
||||
nil -> {:error, "Config with params #{inspect(params)} not found"}
|
||||
end
|
||||
end
|
||||
|
||||
@spec from_binary(binary()) :: term()
|
||||
def from_binary(value), do: :erlang.binary_to_term(value)
|
||||
|
||||
@spec from_binary_to_map(binary()) :: any()
|
||||
def from_binary_to_map(binary) do
|
||||
from_binary(binary)
|
||||
|> do_convert()
|
||||
end
|
||||
|
||||
defp do_convert([{k, v}] = value) when is_list(value) and length(value) == 1,
|
||||
do: %{k => do_convert(v)}
|
||||
|
||||
defp do_convert(values) when is_list(values), do: for(val <- values, do: do_convert(val))
|
||||
|
||||
defp do_convert({k, v} = value) when is_tuple(value),
|
||||
do: %{k => do_convert(v)}
|
||||
|
||||
defp do_convert(value) when is_tuple(value), do: %{"tuple" => do_convert(Tuple.to_list(value))}
|
||||
|
||||
defp do_convert(value) when is_binary(value) or is_map(value) or is_number(value), do: value
|
||||
|
||||
defp do_convert(value) when is_atom(value) do
|
||||
string = to_string(value)
|
||||
|
||||
if String.starts_with?(string, "Elixir."),
|
||||
do: String.trim_leading(string, "Elixir."),
|
||||
else: value
|
||||
end
|
||||
|
||||
@spec transform(any()) :: binary()
|
||||
def transform(%{"tuple" => _} = entity), do: :erlang.term_to_binary(do_transform(entity))
|
||||
|
||||
def transform(entity) when is_map(entity) do
|
||||
tuples =
|
||||
for {k, v} <- entity,
|
||||
into: [],
|
||||
do: {if(is_atom(k), do: k, else: String.to_atom(k)), do_transform(v)}
|
||||
|
||||
Enum.reject(tuples, fn {_k, v} -> is_nil(v) end)
|
||||
|> Enum.sort()
|
||||
|> :erlang.term_to_binary()
|
||||
end
|
||||
|
||||
def transform(entity) when is_list(entity) do
|
||||
list = Enum.map(entity, &do_transform(&1))
|
||||
:erlang.term_to_binary(list)
|
||||
end
|
||||
|
||||
def transform(entity), do: :erlang.term_to_binary(entity)
|
||||
|
||||
defp do_transform(%Regex{} = value) when is_map(value), do: value
|
||||
|
||||
defp do_transform(%{"tuple" => [k, values] = entity}) when length(entity) == 2 do
|
||||
{do_transform(k), do_transform(values)}
|
||||
end
|
||||
|
||||
defp do_transform(%{"tuple" => values}) do
|
||||
Enum.reduce(values, {}, fn val, acc -> Tuple.append(acc, do_transform(val)) end)
|
||||
end
|
||||
|
||||
defp do_transform(value) when is_map(value) do
|
||||
values = for {key, val} <- value, into: [], do: {String.to_atom(key), do_transform(val)}
|
||||
|
||||
Enum.sort(values)
|
||||
end
|
||||
|
||||
defp do_transform(value) when is_list(value) do
|
||||
Enum.map(value, &do_transform(&1))
|
||||
end
|
||||
|
||||
defp do_transform(entity) when is_list(entity) and length(entity) == 1, do: hd(entity)
|
||||
|
||||
defp do_transform(value) when is_binary(value) do
|
||||
String.trim(value)
|
||||
|> do_transform_string()
|
||||
end
|
||||
|
||||
defp do_transform(value), do: value
|
||||
|
||||
defp do_transform_string(value) when byte_size(value) == 0, do: nil
|
||||
|
||||
defp do_transform_string(value) do
|
||||
cond do
|
||||
String.starts_with?(value, "Pleroma") or String.starts_with?(value, "Phoenix") ->
|
||||
String.to_existing_atom("Elixir." <> value)
|
||||
|
||||
String.starts_with?(value, ":") ->
|
||||
String.replace(value, ":", "") |> String.to_existing_atom()
|
||||
|
||||
String.starts_with?(value, "i:") ->
|
||||
String.replace(value, "i:", "") |> String.to_integer()
|
||||
|
||||
true ->
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
17
lib/pleroma/web/admin_api/views/config_view.ex
Normal file
17
lib/pleroma/web/admin_api/views/config_view.ex
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
defmodule Pleroma.Web.AdminAPI.ConfigView do
|
||||
use Pleroma.Web, :view
|
||||
|
||||
def render("index.json", %{configs: configs}) do
|
||||
%{
|
||||
configs: render_many(configs, __MODULE__, "show.json", as: :config)
|
||||
}
|
||||
end
|
||||
|
||||
def render("show.json", %{config: config}) do
|
||||
%{
|
||||
key: config.key,
|
||||
group: config.group,
|
||||
value: Pleroma.Web.AdminAPI.Config.from_binary_to_map(config.value)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
defmodule Pleroma.Web.AdminAPI.ReportView do
|
||||
use Pleroma.Web, :view
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.HTML
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.CommonAPI.Utils
|
||||
alias Pleroma.Web.MastodonAPI.AccountView
|
||||
|
|
@ -23,6 +24,13 @@ defmodule Pleroma.Web.AdminAPI.ReportView do
|
|||
[account_ap_id | status_ap_ids] = report.data["object"]
|
||||
account = User.get_cached_by_ap_id(account_ap_id)
|
||||
|
||||
content =
|
||||
unless is_nil(report.data["content"]) do
|
||||
HTML.filter_tags(report.data["content"])
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
||||
statuses =
|
||||
Enum.map(status_ap_ids, fn ap_id ->
|
||||
Activity.get_by_ap_id_with_object(ap_id)
|
||||
|
|
@ -32,7 +40,7 @@ defmodule Pleroma.Web.AdminAPI.ReportView do
|
|||
id: report.id,
|
||||
account: AccountView.render("account.json", %{user: account}),
|
||||
actor: AccountView.render("account.json", %{user: user}),
|
||||
content: report.data["content"],
|
||||
content: content,
|
||||
created_at: created_at,
|
||||
statuses: StatusView.render("index.json", %{activities: statuses, as: :activity}),
|
||||
state: report.data["state"]
|
||||
|
|
|
|||
|
|
@ -212,7 +212,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
cw <- data["spoiler_text"] || "",
|
||||
sensitive <- data["sensitive"] || Enum.member?(tags, {"#nsfw", "nsfw"}),
|
||||
full_payload <- String.trim(status <> cw),
|
||||
length when length in 1..limit <- String.length(full_payload),
|
||||
:ok <- validate_character_limit(full_payload, attachments, limit),
|
||||
object <-
|
||||
make_note_data(
|
||||
user.ap_id,
|
||||
|
|
@ -247,6 +247,8 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
|
||||
res
|
||||
else
|
||||
{:private_to_public, true} -> {:error, "The message visibility must be direct"}
|
||||
{:error, _} = e -> e
|
||||
e -> {:error, e}
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -504,4 +504,18 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|
|||
"inReplyTo" => object.data["id"]
|
||||
}
|
||||
end
|
||||
|
||||
def validate_character_limit(full_payload, attachments, limit) do
|
||||
length = String.length(full_payload)
|
||||
|
||||
if length < limit do
|
||||
if length > 0 or Enum.count(attachments) > 0 do
|
||||
:ok
|
||||
else
|
||||
{:error, "Cannot post an empty status without attachments"}
|
||||
end
|
||||
else
|
||||
{:error, "The status is over the character limit"}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -15,4 +15,22 @@ defmodule Pleroma.Web.ControllerHelper do
|
|||
|> put_status(status)
|
||||
|> json(json)
|
||||
end
|
||||
|
||||
@spec fetch_integer_param(map(), String.t(), integer() | nil) :: integer() | nil
|
||||
def fetch_integer_param(params, name, default \\ nil) do
|
||||
params
|
||||
|> Map.get(name, default)
|
||||
|> param_to_integer(default)
|
||||
end
|
||||
|
||||
defp param_to_integer(val, _) when is_integer(val), do: val
|
||||
|
||||
defp param_to_integer(val, default) when is_binary(val) do
|
||||
case Integer.parse(val) do
|
||||
{res, _} -> res
|
||||
_ -> default
|
||||
end
|
||||
end
|
||||
|
||||
defp param_to_integer(_, default), do: default
|
||||
end
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ defmodule Pleroma.Web.Endpoint do
|
|||
Plug.Session,
|
||||
store: :cookie,
|
||||
key: cookie_name,
|
||||
signing_salt: {Pleroma.Config, :get, [[__MODULE__, :signing_salt], "CqaoopA2"]},
|
||||
signing_salt: Pleroma.Config.get([__MODULE__, :signing_salt], "CqaoopA2"),
|
||||
http_only: true,
|
||||
secure: secure_cookies,
|
||||
extra: extra
|
||||
|
|
|
|||
|
|
@ -15,7 +15,9 @@ defmodule Pleroma.Web.Federator.RetryQueue do
|
|||
|
||||
def start_link do
|
||||
enabled =
|
||||
if Mix.env() == :test, do: true, else: Pleroma.Config.get([__MODULE__, :enabled], false)
|
||||
if Pleroma.Config.get(:env) == :test,
|
||||
do: true,
|
||||
else: Pleroma.Config.get([__MODULE__, :enabled], false)
|
||||
|
||||
if enabled do
|
||||
Logger.info("Starting retry queue")
|
||||
|
|
@ -219,7 +221,7 @@ defmodule Pleroma.Web.Federator.RetryQueue do
|
|||
{:noreply, state}
|
||||
end
|
||||
|
||||
if Mix.env() == :test do
|
||||
if Pleroma.Config.get(:env) == :test do
|
||||
defp growth_function(_retries) do
|
||||
_shutit = Pleroma.Config.get([__MODULE__, :initial_timeout])
|
||||
DateTime.to_unix(DateTime.utc_now()) - 1
|
||||
|
|
|
|||
|
|
@ -46,14 +46,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
|
||||
require Logger
|
||||
|
||||
plug(
|
||||
Pleroma.Plugs.RateLimitPlug,
|
||||
%{
|
||||
max_requests: Config.get([:app_account_creation, :max_requests]),
|
||||
interval: Config.get([:app_account_creation, :interval])
|
||||
}
|
||||
when action in [:account_register]
|
||||
)
|
||||
plug(Pleroma.Plugs.RateLimiter, :app_account_creation when action == :account_register)
|
||||
plug(Pleroma.Plugs.RateLimiter, :search when action in [:search, :search2, :account_search])
|
||||
|
||||
@local_mastodon_name "Mastodon-Local"
|
||||
|
||||
|
|
@ -142,6 +136,14 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
_ -> :error
|
||||
end
|
||||
end)
|
||||
|> add_if_present(params, "pleroma_background_image", :background, fn value ->
|
||||
with %Plug.Upload{} <- value,
|
||||
{:ok, object} <- ActivityPub.upload(value, type: :background) do
|
||||
{:ok, object.data}
|
||||
else
|
||||
_ -> :error
|
||||
end
|
||||
end)
|
||||
|> Map.put(:emoji, user_info_emojis)
|
||||
|
||||
info_cng = User.Info.profile_update(user.info, info_params)
|
||||
|
|
@ -166,8 +168,15 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
|
||||
def verify_credentials(%{assigns: %{user: user}} = conn, _) do
|
||||
chat_token = Phoenix.Token.sign(conn, "user socket", user.id)
|
||||
|
||||
account =
|
||||
AccountView.render("account.json", %{user: user, for: user, with_pleroma_settings: true})
|
||||
AccountView.render("account.json", %{
|
||||
user: user,
|
||||
for: user,
|
||||
with_pleroma_settings: true,
|
||||
with_chat_token: chat_token
|
||||
})
|
||||
|
||||
json(conn, account)
|
||||
end
|
||||
|
|
@ -445,12 +454,26 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
end
|
||||
|
||||
defp get_cached_vote_or_vote(user, object, choices) do
|
||||
idempotency_key = "polls:#{user.id}:#{object.data["id"]}"
|
||||
|
||||
{_, res} =
|
||||
Cachex.fetch(:idempotency_cache, idempotency_key, fn _ ->
|
||||
case CommonAPI.vote(user, object, choices) do
|
||||
{:error, _message} = res -> {:ignore, res}
|
||||
res -> {:commit, res}
|
||||
end
|
||||
end)
|
||||
|
||||
res
|
||||
end
|
||||
|
||||
def poll_vote(%{assigns: %{user: user}} = conn, %{"id" => id, "choices" => choices}) do
|
||||
with %Object{} = object <- Object.get_by_id(id),
|
||||
true <- object.data["type"] == "Question",
|
||||
%Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
|
||||
true <- Visibility.visible_for_user?(activity, user),
|
||||
{:ok, _activities, object} <- CommonAPI.vote(user, object, choices) do
|
||||
{:ok, _activities, object} <- get_cached_vote_or_vote(user, object, choices) do
|
||||
conn
|
||||
|> put_view(StatusView)
|
||||
|> try_render("poll.json", %{object: object, for: user})
|
||||
|
|
@ -521,15 +544,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
end
|
||||
|
||||
def post_status(conn, %{"status" => "", "media_ids" => media_ids} = params)
|
||||
when length(media_ids) > 0 do
|
||||
params =
|
||||
params
|
||||
|> Map.put("status", ".")
|
||||
|
||||
post_status(conn, params)
|
||||
end
|
||||
|
||||
def post_status(%{assigns: %{user: user}} = conn, %{"status" => _} = params) do
|
||||
params =
|
||||
params
|
||||
|
|
@ -547,18 +561,13 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
else
|
||||
params = Map.drop(params, ["scheduled_at"])
|
||||
|
||||
case get_cached_status_or_post(conn, params) do
|
||||
{:ignore, message} ->
|
||||
conn
|
||||
|> put_status(422)
|
||||
|> json(%{error: message})
|
||||
|
||||
case CommonAPI.post(user, params) do
|
||||
{:error, message} ->
|
||||
conn
|
||||
|> put_status(422)
|
||||
|> put_status(:unprocessable_entity)
|
||||
|> json(%{error: message})
|
||||
|
||||
{_, activity} ->
|
||||
{:ok, activity} ->
|
||||
conn
|
||||
|> put_view(StatusView)
|
||||
|> try_render("status.json", %{activity: activity, for: user, as: :activity})
|
||||
|
|
@ -566,21 +575,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
end
|
||||
|
||||
defp get_cached_status_or_post(%{assigns: %{user: user}} = conn, params) do
|
||||
idempotency_key =
|
||||
case get_req_header(conn, "idempotency-key") do
|
||||
[key] -> key
|
||||
_ -> Ecto.UUID.generate()
|
||||
end
|
||||
|
||||
Cachex.fetch(:idempotency_cache, idempotency_key, fn _ ->
|
||||
case CommonAPI.post(user, params) do
|
||||
{:ok, activity} -> activity
|
||||
{:error, message} -> {:ignore, message}
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
def delete_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
|
||||
json(conn, %{})
|
||||
|
|
@ -830,7 +824,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
|
||||
conn
|
||||
|> put_view(AccountView)
|
||||
|> render(AccountView, "accounts.json", %{for: user, users: users, as: :user})
|
||||
|> render("accounts.json", %{for: user, users: users, as: :user})
|
||||
else
|
||||
_ -> json(conn, [])
|
||||
end
|
||||
|
|
@ -1124,58 +1118,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
end
|
||||
end
|
||||
|
||||
def search2(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
|
||||
accounts = User.search(query, resolve: params["resolve"] == "true", for_user: user)
|
||||
statuses = Activity.search(user, query)
|
||||
tags_path = Web.base_url() <> "/tag/"
|
||||
|
||||
tags =
|
||||
query
|
||||
|> String.split()
|
||||
|> Enum.uniq()
|
||||
|> Enum.filter(fn tag -> String.starts_with?(tag, "#") end)
|
||||
|> Enum.map(fn tag -> String.slice(tag, 1..-1) end)
|
||||
|> Enum.map(fn tag -> %{name: tag, url: tags_path <> tag} end)
|
||||
|
||||
res = %{
|
||||
"accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user),
|
||||
"statuses" =>
|
||||
StatusView.render("index.json", activities: statuses, for: user, as: :activity),
|
||||
"hashtags" => tags
|
||||
}
|
||||
|
||||
json(conn, res)
|
||||
end
|
||||
|
||||
def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
|
||||
accounts = User.search(query, resolve: params["resolve"] == "true", for_user: user)
|
||||
statuses = Activity.search(user, query)
|
||||
|
||||
tags =
|
||||
query
|
||||
|> String.split()
|
||||
|> Enum.uniq()
|
||||
|> Enum.filter(fn tag -> String.starts_with?(tag, "#") end)
|
||||
|> Enum.map(fn tag -> String.slice(tag, 1..-1) end)
|
||||
|
||||
res = %{
|
||||
"accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user),
|
||||
"statuses" =>
|
||||
StatusView.render("index.json", activities: statuses, for: user, as: :activity),
|
||||
"hashtags" => tags
|
||||
}
|
||||
|
||||
json(conn, res)
|
||||
end
|
||||
|
||||
def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
|
||||
accounts = User.search(query, resolve: params["resolve"] == "true", for_user: user)
|
||||
|
||||
res = AccountView.render("accounts.json", users: accounts, for: user, as: :user)
|
||||
|
||||
json(conn, res)
|
||||
end
|
||||
|
||||
def favourites(%{assigns: %{user: user}} = conn, params) do
|
||||
params =
|
||||
params
|
||||
|
|
|
|||
79
lib/pleroma/web/mastodon_api/search_controller.ex
Normal file
79
lib/pleroma/web/mastodon_api/search_controller.ex
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.MastodonAPI.SearchController do
|
||||
use Pleroma.Web, :controller
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web
|
||||
alias Pleroma.Web.MastodonAPI.AccountView
|
||||
alias Pleroma.Web.MastodonAPI.StatusView
|
||||
|
||||
alias Pleroma.Web.ControllerHelper
|
||||
|
||||
require Logger
|
||||
|
||||
plug(Pleroma.Plugs.RateLimiter, :search when action in [:search, :search2, :account_search])
|
||||
|
||||
def search2(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
|
||||
accounts = User.search(query, search_options(params, user))
|
||||
statuses = Activity.search(user, query)
|
||||
tags_path = Web.base_url() <> "/tag/"
|
||||
|
||||
tags =
|
||||
query
|
||||
|> String.split()
|
||||
|> Enum.uniq()
|
||||
|> Enum.filter(fn tag -> String.starts_with?(tag, "#") end)
|
||||
|> Enum.map(fn tag -> String.slice(tag, 1..-1) end)
|
||||
|> Enum.map(fn tag -> %{name: tag, url: tags_path <> tag} end)
|
||||
|
||||
res = %{
|
||||
"accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user),
|
||||
"statuses" =>
|
||||
StatusView.render("index.json", activities: statuses, for: user, as: :activity),
|
||||
"hashtags" => tags
|
||||
}
|
||||
|
||||
json(conn, res)
|
||||
end
|
||||
|
||||
def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
|
||||
accounts = User.search(query, search_options(params, user))
|
||||
statuses = Activity.search(user, query)
|
||||
|
||||
tags =
|
||||
query
|
||||
|> String.split()
|
||||
|> Enum.uniq()
|
||||
|> Enum.filter(fn tag -> String.starts_with?(tag, "#") end)
|
||||
|> Enum.map(fn tag -> String.slice(tag, 1..-1) end)
|
||||
|
||||
res = %{
|
||||
"accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user),
|
||||
"statuses" =>
|
||||
StatusView.render("index.json", activities: statuses, for: user, as: :activity),
|
||||
"hashtags" => tags
|
||||
}
|
||||
|
||||
json(conn, res)
|
||||
end
|
||||
|
||||
def account_search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
|
||||
accounts = User.search(query, search_options(params, user))
|
||||
res = AccountView.render("accounts.json", users: accounts, for: user, as: :user)
|
||||
|
||||
json(conn, res)
|
||||
end
|
||||
|
||||
defp search_options(params, user) do
|
||||
[
|
||||
resolve: params["resolve"] == "true",
|
||||
following: params["following"] == "true",
|
||||
limit: ControllerHelper.fetch_integer_param(params, "limit"),
|
||||
offset: ControllerHelper.fetch_integer_param(params, "offset"),
|
||||
for_user: user
|
||||
]
|
||||
end
|
||||
end
|
||||
|
|
@ -66,6 +66,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
end
|
||||
|
||||
defp do_render("account.json", %{user: user} = opts) do
|
||||
display_name = HTML.strip_tags(user.name || user.nickname)
|
||||
|
||||
image = User.avatar_url(user) |> MediaProxy.url()
|
||||
header = User.banner_url(user) |> MediaProxy.url()
|
||||
user_info = User.get_cached_user_info(user)
|
||||
|
|
@ -96,7 +98,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
id: to_string(user.id),
|
||||
username: username_from_nickname(user.nickname),
|
||||
acct: user.nickname,
|
||||
display_name: user.name || user.nickname,
|
||||
display_name: display_name,
|
||||
locked: user_info.locked,
|
||||
created_at: Utils.to_masto_date(user.inserted_at),
|
||||
followers_count: user_info.follower_count,
|
||||
|
|
@ -125,13 +127,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
hide_follows: user.info.hide_follows,
|
||||
hide_favorites: user.info.hide_favorites,
|
||||
relationship: relationship,
|
||||
skip_thread_containment: user.info.skip_thread_containment
|
||||
skip_thread_containment: user.info.skip_thread_containment,
|
||||
background_image: image_url(user.info.background) |> MediaProxy.url()
|
||||
}
|
||||
}
|
||||
|> maybe_put_role(user, opts[:for])
|
||||
|> maybe_put_settings(user, opts[:for], user_info)
|
||||
|> maybe_put_notification_settings(user, opts[:for])
|
||||
|> maybe_put_settings_store(user, opts[:for], opts)
|
||||
|> maybe_put_chat_token(user, opts[:for], opts)
|
||||
end
|
||||
|
||||
defp username_from_nickname(string) when is_binary(string) do
|
||||
|
|
@ -163,6 +167,15 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
|
||||
defp maybe_put_settings_store(data, _, _, _), do: data
|
||||
|
||||
defp maybe_put_chat_token(data, %User{id: id}, %User{id: id}, %{
|
||||
with_chat_token: token
|
||||
}) do
|
||||
data
|
||||
|> Kernel.put_in([:pleroma, :chat_token], token)
|
||||
end
|
||||
|
||||
defp maybe_put_chat_token(data, _, _, _), do: data
|
||||
|
||||
defp maybe_put_role(data, %User{info: %{show_role: true}} = user, _) do
|
||||
data
|
||||
|> Kernel.put_in([:pleroma, :is_admin], user.info.is_admin)
|
||||
|
|
@ -182,4 +195,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|
|||
end
|
||||
|
||||
defp maybe_put_notification_settings(data, _, _), do: data
|
||||
|
||||
defp image_url(%{"url" => [%{"href" => href} | _]}), do: href
|
||||
defp image_url(_), do: nil
|
||||
end
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ defmodule Pleroma.Web.MastodonAPI.WebsocketHandler do
|
|||
"public:media",
|
||||
"public:local:media",
|
||||
"user",
|
||||
"user:notification",
|
||||
"direct",
|
||||
"list",
|
||||
"hashtag"
|
||||
|
|
|
|||
|
|
@ -76,14 +76,16 @@ defmodule Pleroma.Web.OAuth.Authorization do
|
|||
def use_token(%Authorization{used: true}), do: {:error, "already used"}
|
||||
|
||||
@spec delete_user_authorizations(User.t()) :: {integer(), any()}
|
||||
def delete_user_authorizations(%User{id: user_id}) do
|
||||
from(
|
||||
a in Pleroma.Web.OAuth.Authorization,
|
||||
where: a.user_id == ^user_id
|
||||
)
|
||||
def delete_user_authorizations(%User{} = user) do
|
||||
user
|
||||
|> delete_by_user_query
|
||||
|> Repo.delete_all()
|
||||
end
|
||||
|
||||
def delete_by_user_query(%User{id: user_id}) do
|
||||
from(a in __MODULE__, where: a.user_id == ^user_id)
|
||||
end
|
||||
|
||||
@doc "gets auth for app by token"
|
||||
@spec get_by_token(App.t(), String.t()) :: {:ok, t()} | {:error, :not_found}
|
||||
def get_by_token(%App{id: app_id} = _app, token) do
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
defmodule Pleroma.Web.OAuth.OAuthController do
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
alias Pleroma.Helpers.UriHelper
|
||||
alias Pleroma.Registration
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
|
@ -26,34 +27,25 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
|
||||
action_fallback(Pleroma.Web.OAuth.FallbackController)
|
||||
|
||||
@oob_token_redirect_uri "urn:ietf:wg:oauth:2.0:oob"
|
||||
|
||||
# Note: this definition is only called from error-handling methods with `conn.params` as 2nd arg
|
||||
def authorize(conn, %{"authorization" => _} = params) do
|
||||
def authorize(%Plug.Conn{} = conn, %{"authorization" => _} = params) do
|
||||
{auth_attrs, params} = Map.pop(params, "authorization")
|
||||
authorize(conn, Map.merge(params, auth_attrs))
|
||||
end
|
||||
|
||||
def authorize(%{assigns: %{token: %Token{} = token}} = conn, params) do
|
||||
def authorize(%Plug.Conn{assigns: %{token: %Token{}}} = conn, params) do
|
||||
if ControllerHelper.truthy_param?(params["force_login"]) do
|
||||
do_authorize(conn, params)
|
||||
else
|
||||
redirect_uri =
|
||||
if is_binary(params["redirect_uri"]) do
|
||||
params["redirect_uri"]
|
||||
else
|
||||
app = Repo.preload(token, :app).app
|
||||
|
||||
app.redirect_uris
|
||||
|> String.split()
|
||||
|> Enum.at(0)
|
||||
end
|
||||
|
||||
redirect(conn, external: redirect_uri(conn, redirect_uri))
|
||||
handle_existing_authorization(conn, params)
|
||||
end
|
||||
end
|
||||
|
||||
def authorize(conn, params), do: do_authorize(conn, params)
|
||||
def authorize(%Plug.Conn{} = conn, params), do: do_authorize(conn, params)
|
||||
|
||||
defp do_authorize(conn, params) do
|
||||
defp do_authorize(%Plug.Conn{} = conn, params) do
|
||||
app = Repo.get_by(App, client_id: params["client_id"])
|
||||
available_scopes = (app && app.scopes) || []
|
||||
scopes = Scopes.fetch_scopes(params, available_scopes)
|
||||
|
|
@ -70,8 +62,41 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
})
|
||||
end
|
||||
|
||||
defp handle_existing_authorization(
|
||||
%Plug.Conn{assigns: %{token: %Token{} = token}} = conn,
|
||||
%{"redirect_uri" => @oob_token_redirect_uri}
|
||||
) do
|
||||
render(conn, "oob_token_exists.html", %{token: token})
|
||||
end
|
||||
|
||||
defp handle_existing_authorization(
|
||||
%Plug.Conn{assigns: %{token: %Token{} = token}} = conn,
|
||||
%{} = params
|
||||
) do
|
||||
app = Repo.preload(token, :app).app
|
||||
|
||||
redirect_uri =
|
||||
if is_binary(params["redirect_uri"]) do
|
||||
params["redirect_uri"]
|
||||
else
|
||||
default_redirect_uri(app)
|
||||
end
|
||||
|
||||
if redirect_uri in String.split(app.redirect_uris) do
|
||||
redirect_uri = redirect_uri(conn, redirect_uri)
|
||||
url_params = %{access_token: token.token}
|
||||
url_params = UriHelper.append_param_if_present(url_params, :state, params["state"])
|
||||
url = UriHelper.append_uri_params(redirect_uri, url_params)
|
||||
redirect(conn, external: url)
|
||||
else
|
||||
conn
|
||||
|> put_flash(:error, "Unlisted redirect_uri.")
|
||||
|> redirect(external: redirect_uri(conn, redirect_uri))
|
||||
end
|
||||
end
|
||||
|
||||
def create_authorization(
|
||||
conn,
|
||||
%Plug.Conn{} = conn,
|
||||
%{"authorization" => _} = params,
|
||||
opts \\ []
|
||||
) do
|
||||
|
|
@ -83,35 +108,33 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
end
|
||||
end
|
||||
|
||||
def after_create_authorization(conn, auth, %{
|
||||
def after_create_authorization(%Plug.Conn{} = conn, %Authorization{} = auth, %{
|
||||
"authorization" => %{"redirect_uri" => @oob_token_redirect_uri}
|
||||
}) do
|
||||
render(conn, "oob_authorization_created.html", %{auth: auth})
|
||||
end
|
||||
|
||||
def after_create_authorization(%Plug.Conn{} = conn, %Authorization{} = auth, %{
|
||||
"authorization" => %{"redirect_uri" => redirect_uri} = auth_attrs
|
||||
}) do
|
||||
redirect_uri = redirect_uri(conn, redirect_uri)
|
||||
|
||||
if redirect_uri == "urn:ietf:wg:oauth:2.0:oob" do
|
||||
render(conn, "results.html", %{
|
||||
auth: auth
|
||||
})
|
||||
else
|
||||
connector = if String.contains?(redirect_uri, "?"), do: "&", else: "?"
|
||||
url = "#{redirect_uri}#{connector}"
|
||||
url_params = %{:code => auth.token}
|
||||
|
||||
url_params =
|
||||
if auth_attrs["state"] do
|
||||
Map.put(url_params, :state, auth_attrs["state"])
|
||||
else
|
||||
url_params
|
||||
end
|
||||
|
||||
url = "#{url}#{Plug.Conn.Query.encode(url_params)}"
|
||||
app = Repo.preload(auth, :app).app
|
||||
|
||||
# An extra safety measure before we redirect (also done in `do_create_authorization/2`)
|
||||
if redirect_uri in String.split(app.redirect_uris) do
|
||||
redirect_uri = redirect_uri(conn, redirect_uri)
|
||||
url_params = %{code: auth.token}
|
||||
url_params = UriHelper.append_param_if_present(url_params, :state, auth_attrs["state"])
|
||||
url = UriHelper.append_uri_params(redirect_uri, url_params)
|
||||
redirect(conn, external: url)
|
||||
else
|
||||
conn
|
||||
|> put_flash(:error, "Unlisted redirect_uri.")
|
||||
|> redirect(external: redirect_uri(conn, redirect_uri))
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_create_authorization_error(
|
||||
conn,
|
||||
%Plug.Conn{} = conn,
|
||||
{:error, scopes_issue},
|
||||
%{"authorization" => _} = params
|
||||
)
|
||||
|
|
@ -125,7 +148,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
end
|
||||
|
||||
defp handle_create_authorization_error(
|
||||
conn,
|
||||
%Plug.Conn{} = conn,
|
||||
{:auth_active, false},
|
||||
%{"authorization" => _} = params
|
||||
) do
|
||||
|
|
@ -137,13 +160,13 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
|> authorize(params)
|
||||
end
|
||||
|
||||
defp handle_create_authorization_error(conn, error, %{"authorization" => _}) do
|
||||
defp handle_create_authorization_error(%Plug.Conn{} = conn, error, %{"authorization" => _}) do
|
||||
Authenticator.handle_error(conn, error)
|
||||
end
|
||||
|
||||
@doc "Renew access_token with refresh_token"
|
||||
def token_exchange(
|
||||
conn,
|
||||
%Plug.Conn{} = conn,
|
||||
%{"grant_type" => "refresh_token", "refresh_token" => token} = _params
|
||||
) do
|
||||
with {:ok, app} <- Token.Utils.fetch_app(conn),
|
||||
|
|
@ -159,7 +182,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
end
|
||||
end
|
||||
|
||||
def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do
|
||||
def token_exchange(%Plug.Conn{} = conn, %{"grant_type" => "authorization_code"} = params) do
|
||||
with {:ok, app} <- Token.Utils.fetch_app(conn),
|
||||
fixed_token = Token.Utils.fix_padding(params["code"]),
|
||||
{:ok, auth} <- Authorization.get_by_token(app, fixed_token),
|
||||
|
|
@ -176,7 +199,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
end
|
||||
|
||||
def token_exchange(
|
||||
conn,
|
||||
%Plug.Conn{} = conn,
|
||||
%{"grant_type" => "password"} = params
|
||||
) do
|
||||
with {:ok, %User{} = user} <- Authenticator.get_user(conn),
|
||||
|
|
@ -207,7 +230,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
end
|
||||
|
||||
def token_exchange(
|
||||
conn,
|
||||
%Plug.Conn{} = conn,
|
||||
%{"grant_type" => "password", "name" => name, "password" => _password} = params
|
||||
) do
|
||||
params =
|
||||
|
|
@ -218,7 +241,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
token_exchange(conn, params)
|
||||
end
|
||||
|
||||
def token_exchange(conn, %{"grant_type" => "client_credentials"} = _params) do
|
||||
def token_exchange(%Plug.Conn{} = conn, %{"grant_type" => "client_credentials"} = _params) do
|
||||
with {:ok, app} <- Token.Utils.fetch_app(conn),
|
||||
{:ok, auth} <- Authorization.create_authorization(app, %User{}),
|
||||
{:ok, token} <- Token.exchange_token(app, auth) do
|
||||
|
|
@ -231,9 +254,9 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
end
|
||||
|
||||
# Bad request
|
||||
def token_exchange(conn, params), do: bad_request(conn, params)
|
||||
def token_exchange(%Plug.Conn{} = conn, params), do: bad_request(conn, params)
|
||||
|
||||
def token_revoke(conn, %{"token" => _token} = params) do
|
||||
def token_revoke(%Plug.Conn{} = conn, %{"token" => _token} = params) do
|
||||
with {:ok, app} <- Token.Utils.fetch_app(conn),
|
||||
{:ok, _token} <- RevokeToken.revoke(app, params) do
|
||||
json(conn, %{})
|
||||
|
|
@ -244,17 +267,20 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
end
|
||||
end
|
||||
|
||||
def token_revoke(conn, params), do: bad_request(conn, params)
|
||||
def token_revoke(%Plug.Conn{} = conn, params), do: bad_request(conn, params)
|
||||
|
||||
# Response for bad request
|
||||
defp bad_request(conn, _) do
|
||||
defp bad_request(%Plug.Conn{} = conn, _) do
|
||||
conn
|
||||
|> put_status(500)
|
||||
|> json(%{error: "Bad request"})
|
||||
end
|
||||
|
||||
@doc "Prepares OAuth request to provider for Ueberauth"
|
||||
def prepare_request(conn, %{"provider" => provider, "authorization" => auth_attrs}) do
|
||||
def prepare_request(%Plug.Conn{} = conn, %{
|
||||
"provider" => provider,
|
||||
"authorization" => auth_attrs
|
||||
}) do
|
||||
scope =
|
||||
auth_attrs
|
||||
|> Scopes.fetch_scopes([])
|
||||
|
|
@ -275,7 +301,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
redirect(conn, to: o_auth_path(conn, :request, provider, params))
|
||||
end
|
||||
|
||||
def request(conn, params) do
|
||||
def request(%Plug.Conn{} = conn, params) do
|
||||
message =
|
||||
if params["provider"] do
|
||||
"Unsupported OAuth provider: #{params["provider"]}."
|
||||
|
|
@ -288,7 +314,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
|> redirect(to: "/")
|
||||
end
|
||||
|
||||
def callback(%{assigns: %{ueberauth_failure: failure}} = conn, params) do
|
||||
def callback(%Plug.Conn{assigns: %{ueberauth_failure: failure}} = conn, params) do
|
||||
params = callback_params(params)
|
||||
messages = for e <- Map.get(failure, :errors, []), do: e.message
|
||||
message = Enum.join(messages, "; ")
|
||||
|
|
@ -298,7 +324,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
|> redirect(external: redirect_uri(conn, params["redirect_uri"]))
|
||||
end
|
||||
|
||||
def callback(conn, params) do
|
||||
def callback(%Plug.Conn{} = conn, params) do
|
||||
params = callback_params(params)
|
||||
|
||||
with {:ok, registration} <- Authenticator.get_registration(conn) do
|
||||
|
|
@ -316,7 +342,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
})
|
||||
|
||||
conn
|
||||
|> put_session(:registration_id, registration.id)
|
||||
|> put_session_registration_id(registration.id)
|
||||
|> registration_details(%{"authorization" => registration_params})
|
||||
end
|
||||
else
|
||||
|
|
@ -333,7 +359,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
Map.merge(params, Jason.decode!(state))
|
||||
end
|
||||
|
||||
def registration_details(conn, %{"authorization" => auth_attrs}) do
|
||||
def registration_details(%Plug.Conn{} = conn, %{"authorization" => auth_attrs}) do
|
||||
render(conn, "register.html", %{
|
||||
client_id: auth_attrs["client_id"],
|
||||
redirect_uri: auth_attrs["redirect_uri"],
|
||||
|
|
@ -344,7 +370,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
})
|
||||
end
|
||||
|
||||
def register(conn, %{"authorization" => _, "op" => "connect"} = params) do
|
||||
def register(%Plug.Conn{} = conn, %{"authorization" => _, "op" => "connect"} = params) do
|
||||
with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn),
|
||||
%Registration{} = registration <- Repo.get(Registration, registration_id),
|
||||
{_, {:ok, auth}} <-
|
||||
|
|
@ -363,7 +389,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
end
|
||||
end
|
||||
|
||||
def register(conn, %{"authorization" => _, "op" => "register"} = params) do
|
||||
def register(%Plug.Conn{} = conn, %{"authorization" => _, "op" => "register"} = params) do
|
||||
with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn),
|
||||
%Registration{} = registration <- Repo.get(Registration, registration_id),
|
||||
{:ok, user} <- Authenticator.create_from_registration(conn, registration) do
|
||||
|
|
@ -399,7 +425,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
end
|
||||
|
||||
defp do_create_authorization(
|
||||
conn,
|
||||
%Plug.Conn{} = conn,
|
||||
%{
|
||||
"authorization" =>
|
||||
%{
|
||||
|
|
@ -420,13 +446,13 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
end
|
||||
|
||||
# Special case: Local MastodonFE
|
||||
defp redirect_uri(conn, "."), do: mastodon_api_url(conn, :login)
|
||||
defp redirect_uri(%Plug.Conn{} = conn, "."), do: mastodon_api_url(conn, :login)
|
||||
|
||||
defp redirect_uri(_conn, redirect_uri), do: redirect_uri
|
||||
defp redirect_uri(%Plug.Conn{}, redirect_uri), do: redirect_uri
|
||||
|
||||
defp get_session_registration_id(conn), do: get_session(conn, :registration_id)
|
||||
defp get_session_registration_id(%Plug.Conn{} = conn), do: get_session(conn, :registration_id)
|
||||
|
||||
defp put_session_registration_id(conn, registration_id),
|
||||
defp put_session_registration_id(%Plug.Conn{} = conn, registration_id),
|
||||
do: put_session(conn, :registration_id, registration_id)
|
||||
|
||||
@spec validate_scopes(App.t(), map()) ::
|
||||
|
|
@ -436,4 +462,10 @@ defmodule Pleroma.Web.OAuth.OAuthController do
|
|||
|> Scopes.fetch_scopes(app.scopes)
|
||||
|> Scopes.validates(app.scopes)
|
||||
end
|
||||
|
||||
def default_redirect_uri(%App{} = app) do
|
||||
app.redirect_uris
|
||||
|> String.split()
|
||||
|> Enum.at(0)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ defmodule Pleroma.Web.OAuth.Token do
|
|||
alias Pleroma.Web.OAuth.Token
|
||||
alias Pleroma.Web.OAuth.Token.Query
|
||||
|
||||
@expires_in Pleroma.Config.get([:oauth2, :token_expires_in], 600)
|
||||
@type t :: %__MODULE__{}
|
||||
|
||||
schema "oauth_tokens" do
|
||||
|
|
@ -78,7 +77,7 @@ defmodule Pleroma.Web.OAuth.Token do
|
|||
|
||||
defp put_valid_until(changeset, attrs) do
|
||||
expires_in =
|
||||
Map.get(attrs, :valid_until, NaiveDateTime.add(NaiveDateTime.utc_now(), @expires_in))
|
||||
Map.get(attrs, :valid_until, NaiveDateTime.add(NaiveDateTime.utc_now(), expires_in()))
|
||||
|
||||
changeset
|
||||
|> change(%{valid_until: expires_in})
|
||||
|
|
@ -123,4 +122,6 @@ defmodule Pleroma.Web.OAuth.Token do
|
|||
end
|
||||
|
||||
def is_expired?(_), do: false
|
||||
|
||||
defp expires_in, do: Pleroma.Config.get([:oauth2, :token_expires_in], 600)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,15 +4,13 @@ defmodule Pleroma.Web.OAuth.Token.Response do
|
|||
alias Pleroma.User
|
||||
alias Pleroma.Web.OAuth.Token.Utils
|
||||
|
||||
@expires_in Pleroma.Config.get([:oauth2, :token_expires_in], 600)
|
||||
|
||||
@doc false
|
||||
def build(%User{} = user, token, opts \\ %{}) do
|
||||
%{
|
||||
token_type: "Bearer",
|
||||
access_token: token.token,
|
||||
refresh_token: token.refresh_token,
|
||||
expires_in: @expires_in,
|
||||
expires_in: expires_in(),
|
||||
scope: Enum.join(token.scopes, " "),
|
||||
me: user.ap_id
|
||||
}
|
||||
|
|
@ -25,8 +23,10 @@ defmodule Pleroma.Web.OAuth.Token.Response do
|
|||
access_token: token.token,
|
||||
refresh_token: token.refresh_token,
|
||||
created_at: Utils.format_created_at(token),
|
||||
expires_in: @expires_in,
|
||||
expires_in: expires_in(),
|
||||
scope: Enum.join(token.scopes, " ")
|
||||
}
|
||||
end
|
||||
|
||||
defp expires_in, do: Pleroma.Config.get([:oauth2, :token_expires_in], 600)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ defmodule Pleroma.Web.RelMe do
|
|||
with_body: true
|
||||
]
|
||||
|
||||
if Mix.env() == :test do
|
||||
if Pleroma.Config.get(:env) == :test do
|
||||
def parse(url) when is_binary(url), do: parse_url(url)
|
||||
else
|
||||
def parse(url) when is_binary(url) do
|
||||
|
|
|
|||
|
|
@ -4,25 +4,53 @@
|
|||
|
||||
defmodule Pleroma.Web.RichMedia.Helpers do
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Config
|
||||
alias Pleroma.HTML
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Web.RichMedia.Parser
|
||||
|
||||
@spec validate_page_url(any()) :: :ok | :error
|
||||
defp validate_page_url(page_url) when is_binary(page_url) do
|
||||
if AutoLinker.Parser.is_url?(page_url, true) do
|
||||
URI.parse(page_url) |> validate_page_url
|
||||
else
|
||||
:error
|
||||
validate_tld = Application.get_env(:auto_linker, :opts)[:validate_tld]
|
||||
|
||||
page_url
|
||||
|> AutoLinker.Parser.url?(scheme: true, validate_tld: validate_tld)
|
||||
|> parse_uri(page_url)
|
||||
end
|
||||
|
||||
defp validate_page_url(%URI{host: host, scheme: scheme, authority: authority})
|
||||
when scheme == "https" and not is_nil(authority) do
|
||||
cond do
|
||||
host in Config.get([:rich_media, :ignore_hosts], []) ->
|
||||
:error
|
||||
|
||||
get_tld(host) in Config.get([:rich_media, :ignore_tld], []) ->
|
||||
:error
|
||||
|
||||
true ->
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defp validate_page_url(%URI{authority: nil}), do: :error
|
||||
defp validate_page_url(%URI{scheme: nil}), do: :error
|
||||
defp validate_page_url(%URI{}), do: :ok
|
||||
defp validate_page_url(_), do: :error
|
||||
|
||||
defp parse_uri(true, url) do
|
||||
url
|
||||
|> URI.parse()
|
||||
|> validate_page_url
|
||||
end
|
||||
|
||||
defp parse_uri(_, _), do: :error
|
||||
|
||||
defp get_tld(host) do
|
||||
host
|
||||
|> String.split(".")
|
||||
|> Enum.reverse()
|
||||
|> hd
|
||||
end
|
||||
|
||||
def fetch_data_for_activity(%Activity{data: %{"type" => "Create"}} = activity) do
|
||||
with true <- Pleroma.Config.get([:rich_media, :enabled]),
|
||||
with true <- Config.get([:rich_media, :enabled]),
|
||||
%Object{} = object <- Object.normalize(activity),
|
||||
false <- object.data["sensitive"] || false,
|
||||
{:ok, page_url} <- HTML.extract_first_external_url(object, object.data["content"]),
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ defmodule Pleroma.Web.RichMedia.Parser do
|
|||
|
||||
def parse(nil), do: {:error, "No URL provided"}
|
||||
|
||||
if Mix.env() == :test do
|
||||
if Pleroma.Config.get(:env) == :test do
|
||||
def parse(url), do: parse_url(url)
|
||||
else
|
||||
def parse(url) do
|
||||
|
|
|
|||
|
|
@ -1,15 +1,19 @@
|
|||
defmodule Pleroma.Web.RichMedia.Parsers.MetaTagsParser do
|
||||
def parse(html, data, prefix, error_message, key_name, value_name \\ "content") do
|
||||
with elements = [_ | _] <- get_elements(html, key_name, prefix),
|
||||
meta_data =
|
||||
Enum.reduce(elements, data, fn el, acc ->
|
||||
attributes = normalize_attributes(el, prefix, key_name, value_name)
|
||||
meta_data =
|
||||
html
|
||||
|> get_elements(key_name, prefix)
|
||||
|> Enum.reduce(data, fn el, acc ->
|
||||
attributes = normalize_attributes(el, prefix, key_name, value_name)
|
||||
|
||||
Map.merge(acc, attributes)
|
||||
end) do
|
||||
{:ok, meta_data}
|
||||
Map.merge(acc, attributes)
|
||||
end)
|
||||
|> maybe_put_title(html)
|
||||
|
||||
if Enum.empty?(meta_data) do
|
||||
{:error, error_message}
|
||||
else
|
||||
_e -> {:error, error_message}
|
||||
{:ok, meta_data}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -27,4 +31,19 @@ defmodule Pleroma.Web.RichMedia.Parsers.MetaTagsParser do
|
|||
|
||||
%{String.to_atom(data[key_name]) => data[value_name]}
|
||||
end
|
||||
|
||||
defp maybe_put_title(%{title: _} = meta, _), do: meta
|
||||
|
||||
defp maybe_put_title(meta, html) when meta != %{} do
|
||||
case get_page_title(html) do
|
||||
"" -> meta
|
||||
title -> Map.put_new(meta, :title, title)
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_put_title(meta, _), do: meta
|
||||
|
||||
defp get_page_title(html) do
|
||||
Floki.find(html, "title") |> Floki.text()
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ defmodule Pleroma.Web.Router do
|
|||
plug(Pleroma.Plugs.UserEnabledPlug)
|
||||
plug(Pleroma.Plugs.SetUserSessionIdPlug)
|
||||
plug(Pleroma.Plugs.EnsureUserKeyPlug)
|
||||
plug(Pleroma.Plugs.IdempotencyPlug)
|
||||
end
|
||||
|
||||
pipeline :authenticated_api do
|
||||
|
|
@ -41,6 +42,7 @@ defmodule Pleroma.Web.Router do
|
|||
plug(Pleroma.Plugs.UserEnabledPlug)
|
||||
plug(Pleroma.Plugs.SetUserSessionIdPlug)
|
||||
plug(Pleroma.Plugs.EnsureAuthenticatedPlug)
|
||||
plug(Pleroma.Plugs.IdempotencyPlug)
|
||||
end
|
||||
|
||||
pipeline :admin_api do
|
||||
|
|
@ -57,6 +59,7 @@ defmodule Pleroma.Web.Router do
|
|||
plug(Pleroma.Plugs.SetUserSessionIdPlug)
|
||||
plug(Pleroma.Plugs.EnsureAuthenticatedPlug)
|
||||
plug(Pleroma.Plugs.UserIsAdminPlug)
|
||||
plug(Pleroma.Plugs.IdempotencyPlug)
|
||||
end
|
||||
|
||||
pipeline :mastodon_html do
|
||||
|
|
@ -133,8 +136,8 @@ defmodule Pleroma.Web.Router do
|
|||
scope "/api/pleroma", Pleroma.Web.TwitterAPI do
|
||||
pipe_through(:pleroma_api)
|
||||
|
||||
get("/password_reset/:token", UtilController, :show_password_reset)
|
||||
post("/password_reset", UtilController, :password_reset)
|
||||
get("/password_reset/:token", PasswordController, :reset, as: :reset_password)
|
||||
post("/password_reset", PasswordController, :do_reset, as: :reset_password)
|
||||
get("/emoji", UtilController, :emoji)
|
||||
get("/captcha", UtilController, :captcha)
|
||||
get("/healthcheck", UtilController, :healthcheck)
|
||||
|
|
@ -202,6 +205,9 @@ defmodule Pleroma.Web.Router do
|
|||
|
||||
put("/statuses/:id", AdminAPIController, :status_update)
|
||||
delete("/statuses/:id", AdminAPIController, :status_delete)
|
||||
|
||||
get("/config", AdminAPIController, :config_show)
|
||||
post("/config", AdminAPIController, :config_update)
|
||||
end
|
||||
|
||||
scope "/", Pleroma.Web.TwitterAPI do
|
||||
|
|
@ -412,7 +418,7 @@ defmodule Pleroma.Web.Router do
|
|||
|
||||
get("/trends", MastodonAPIController, :empty_array)
|
||||
|
||||
get("/accounts/search", MastodonAPIController, :account_search)
|
||||
get("/accounts/search", SearchController, :account_search)
|
||||
|
||||
scope [] do
|
||||
pipe_through(:oauth_read_or_public)
|
||||
|
|
@ -431,7 +437,7 @@ defmodule Pleroma.Web.Router do
|
|||
get("/accounts/:id/following", MastodonAPIController, :following)
|
||||
get("/accounts/:id", MastodonAPIController, :user)
|
||||
|
||||
get("/search", MastodonAPIController, :search)
|
||||
get("/search", SearchController, :search)
|
||||
|
||||
get("/pleroma/accounts/:id/favourites", MastodonAPIController, :user_favourites)
|
||||
end
|
||||
|
|
@ -439,7 +445,7 @@ defmodule Pleroma.Web.Router do
|
|||
|
||||
scope "/api/v2", Pleroma.Web.MastodonAPI do
|
||||
pipe_through([:api, :oauth_read_or_public])
|
||||
get("/search", MastodonAPIController, :search2)
|
||||
get("/search", SearchController, :search2)
|
||||
end
|
||||
|
||||
scope "/api", Pleroma.Web do
|
||||
|
|
@ -606,12 +612,6 @@ defmodule Pleroma.Web.Router do
|
|||
get("/mailer/unsubscribe/:token", Mailer.SubscriptionController, :unsubscribe)
|
||||
end
|
||||
|
||||
scope "/", Pleroma.Web do
|
||||
pipe_through(:oembed)
|
||||
|
||||
get("/oembed", OEmbed.OEmbedController, :url)
|
||||
end
|
||||
|
||||
pipeline :activitypub do
|
||||
plug(:accepts, ["activity+json", "json"])
|
||||
plug(Pleroma.Web.Plugs.HTTPSignaturePlug)
|
||||
|
|
@ -701,7 +701,7 @@ defmodule Pleroma.Web.Router do
|
|||
get("/:sig/:url/:filename", MediaProxyController, :remote)
|
||||
end
|
||||
|
||||
if Mix.env() == :dev do
|
||||
if Pleroma.Config.get(:env) == :dev do
|
||||
scope "/dev" do
|
||||
pipe_through([:mailbox_preview])
|
||||
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ defmodule Pleroma.Web.Salmon do
|
|||
do: Instances.set_reachable(url)
|
||||
|
||||
Logger.debug(fn -> "Pushed to #{url}, code #{code}" end)
|
||||
:ok
|
||||
{:ok, code}
|
||||
else
|
||||
e ->
|
||||
unless params[:unreachable_since], do: Instances.set_reachable(url)
|
||||
|
|
|
|||
|
|
@ -110,23 +110,18 @@ defmodule Pleroma.Web.Streamer do
|
|||
{:noreply, topics}
|
||||
end
|
||||
|
||||
def handle_cast(%{action: :stream, topic: "user", item: %Notification{} = item}, topics) do
|
||||
topic = "user:#{item.user_id}"
|
||||
|
||||
Enum.each(topics[topic] || [], fn socket ->
|
||||
json =
|
||||
%{
|
||||
event: "notification",
|
||||
payload:
|
||||
NotificationView.render("show.json", %{
|
||||
notification: item,
|
||||
for: socket.assigns["user"]
|
||||
})
|
||||
|> Jason.encode!()
|
||||
}
|
||||
|> Jason.encode!()
|
||||
|
||||
send(socket.transport_pid, {:text, json})
|
||||
def handle_cast(
|
||||
%{action: :stream, topic: topic, item: %Notification{} = item},
|
||||
topics
|
||||
)
|
||||
when topic in ["user", "user:notification"] do
|
||||
topics
|
||||
|> Map.get("#{topic}:#{item.user_id}", [])
|
||||
|> Enum.each(fn socket ->
|
||||
send(
|
||||
socket.transport_pid,
|
||||
{:text, represent_notification(socket.assigns[:user], item)}
|
||||
)
|
||||
end)
|
||||
|
||||
{:noreply, topics}
|
||||
|
|
@ -216,6 +211,20 @@ defmodule Pleroma.Web.Streamer do
|
|||
|> Jason.encode!()
|
||||
end
|
||||
|
||||
@spec represent_notification(User.t(), Notification.t()) :: binary()
|
||||
defp represent_notification(%User{} = user, %Notification{} = notify) do
|
||||
%{
|
||||
event: "notification",
|
||||
payload:
|
||||
NotificationView.render(
|
||||
"show.json",
|
||||
%{notification: notify, for: user}
|
||||
)
|
||||
|> Jason.encode!()
|
||||
}
|
||||
|> Jason.encode!()
|
||||
end
|
||||
|
||||
def push_to_socket(topics, topic, %Activity{data: %{"type" => "Announce"}} = item) do
|
||||
Enum.each(topics[topic] || [], fn socket ->
|
||||
# Get the current user so we have up-to-date blocks etc.
|
||||
|
|
@ -274,7 +283,7 @@ defmodule Pleroma.Web.Streamer do
|
|||
end)
|
||||
end
|
||||
|
||||
defp internal_topic(topic, socket) when topic in ~w[user direct] do
|
||||
defp internal_topic(topic, socket) when topic in ~w[user user:notification direct] do
|
||||
"#{topic}:#{socket.assigns[:user].id}"
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
<h1>Authorization exists</h1>
|
||||
<h2>Access token is <%= @token.token %></h2>
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
<h2>Password Reset for <%= @user.nickname %></h2>
|
||||
<%= form_for @conn, util_path(@conn, :password_reset), [as: "data"], fn f -> %>
|
||||
<%= form_for @conn, reset_password_path(@conn, :do_reset), [as: "data"], fn f -> %>
|
||||
<div class="form-row">
|
||||
<%= label f, :password, "Password" %>
|
||||
<%= password_input f, :password %>
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.TwitterAPI.PasswordController do
|
||||
@moduledoc """
|
||||
The module containts functions for reset password.
|
||||
"""
|
||||
|
||||
use Pleroma.Web, :controller
|
||||
|
||||
require Logger
|
||||
|
||||
alias Pleroma.PasswordResetToken
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
||||
def reset(conn, %{"token" => token}) do
|
||||
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
|
||||
%User{} = user <- User.get_cached_by_id(token.user_id) do
|
||||
render(conn, "reset.html", %{
|
||||
token: token,
|
||||
user: user
|
||||
})
|
||||
else
|
||||
_e -> render(conn, "invalid_token.html")
|
||||
end
|
||||
end
|
||||
|
||||
def do_reset(conn, %{"data" => data}) do
|
||||
with {:ok, _} <- PasswordResetToken.reset_password(data["token"], data) do
|
||||
render(conn, "reset_success.html")
|
||||
else
|
||||
_e -> render(conn, "reset_failed.html")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -11,8 +11,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
|||
alias Pleroma.Activity
|
||||
alias Pleroma.Emoji
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.PasswordResetToken
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
|
|
@ -20,26 +18,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
|||
alias Pleroma.Web.OStatus
|
||||
alias Pleroma.Web.WebFinger
|
||||
|
||||
def show_password_reset(conn, %{"token" => token}) do
|
||||
with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}),
|
||||
%User{} = user <- User.get_cached_by_id(token.user_id) do
|
||||
render(conn, "password_reset.html", %{
|
||||
token: token,
|
||||
user: user
|
||||
})
|
||||
else
|
||||
_e -> render(conn, "invalid_token.html")
|
||||
end
|
||||
end
|
||||
|
||||
def password_reset(conn, %{"data" => data}) do
|
||||
with {:ok, _} <- PasswordResetToken.reset_password(data["token"], data) do
|
||||
render(conn, "password_reset_success.html")
|
||||
else
|
||||
_e -> render(conn, "password_reset_failed.html")
|
||||
end
|
||||
end
|
||||
|
||||
def help_test(conn, _params) do
|
||||
json(conn, "ok")
|
||||
end
|
||||
|
|
|
|||
8
lib/pleroma/web/twitter_api/views/password_view.ex
Normal file
8
lib/pleroma/web/twitter_api/views/password_view.ex
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.TwitterAPI.PasswordView do
|
||||
use Pleroma.Web, :view
|
||||
import Phoenix.HTML.Form
|
||||
end
|
||||
|
|
@ -13,7 +13,7 @@ defmodule Pleroma.Web.ErrorView do
|
|||
def render("500.json", assigns) do
|
||||
Logger.error("Internal server error: #{inspect(assigns[:reason])}")
|
||||
|
||||
if Mix.env() != :prod do
|
||||
if Pleroma.Config.get(:env) != :prod do
|
||||
%{errors: %{detail: "Internal server error", reason: inspect(assigns[:reason])}}
|
||||
else
|
||||
%{errors: %{detail: "Internal server error"}}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue