diff --git a/docs/API/differences_in_mastoapi_responses.md b/docs/API/differences_in_mastoapi_responses.md index 289f85930..041563de5 100644 --- a/docs/API/differences_in_mastoapi_responses.md +++ b/docs/API/differences_in_mastoapi_responses.md @@ -202,4 +202,5 @@ Has theses additional parameters (which are the same as in Pleroma-API): - `bio`: optional - `captcha_solution`: optional, contains provider-specific captcha solution, - `captcha_token`: optional, contains provider-specific captcha token +- `captcha_answer_data`: optional, contains provider-specific captcha data - `token`: invite token required when the registrations aren't public. diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index a00938c04..308d8cffa 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -73,7 +73,6 @@ def start(_type, _args) do Pleroma.Repo, Config.TransferTask, Pleroma.Emoji, - Pleroma.Captcha, Pleroma.Plugs.RateLimiter.Supervisor ] ++ cachex_children() ++ diff --git a/lib/pleroma/captcha/captcha.ex b/lib/pleroma/captcha/captcha.ex index cf75c3adc..6ab754b6f 100644 --- a/lib/pleroma/captcha/captcha.ex +++ b/lib/pleroma/captcha/captcha.ex @@ -3,53 +3,22 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Captcha do - import Pleroma.Web.Gettext - alias Calendar.DateTime alias Plug.Crypto.KeyGenerator alias Plug.Crypto.MessageEncryptor - use GenServer - - @doc false - def start_link(_) do - GenServer.start_link(__MODULE__, [], name: __MODULE__) - end - - @doc false - def init(_) do - {:ok, nil} - end - @doc """ Ask the configured captcha service for a new captcha """ def new do - GenServer.call(__MODULE__, :new) - end - - @doc """ - Ask the configured captcha service to validate the captcha - """ - def validate(token, captcha, answer_data) do - GenServer.call(__MODULE__, {:validate, token, captcha, answer_data}) - end - - @doc false - def handle_call(:new, _from, state) do - enabled = Pleroma.Config.get([__MODULE__, :enabled]) - - if !enabled do - {:reply, %{type: :none}, state} + if not enabled?() do + %{type: :none} else new_captcha = method().new() - secret_key_base = Pleroma.Config.get!([Pleroma.Web.Endpoint, :secret_key_base]) - # This make salt a little different for two keys - token = new_captcha[:token] - secret = KeyGenerator.generate(secret_key_base, token <> "_encrypt") - sign_secret = KeyGenerator.generate(secret_key_base, token <> "_sign") + {secret, sign_secret} = secret_pair(new_captcha[:token]) + # Basically copy what Phoenix.Token does here, add the time to # the actual data and make it a binary to then encrypt it encrypted_captcha_answer = @@ -60,55 +29,73 @@ def handle_call(:new, _from, state) do |> :erlang.term_to_binary() |> MessageEncryptor.encrypt(secret, sign_secret) - { - :reply, - # Replace the answer with the encrypted answer - %{new_captcha | answer_data: encrypted_captcha_answer}, - state - } + # Replace the answer with the encrypted answer + %{new_captcha | answer_data: encrypted_captcha_answer} end end - @doc false - def handle_call({:validate, token, captcha, answer_data}, _from, state) do + @doc """ + Ask the configured captcha service to validate the captcha + """ + def validate(token, captcha, answer_data) do + with {:ok, %{at: at, answer_data: answer_md5}} <- validate_answer_data(token, answer_data), + :ok <- validate_expiration(at), + :ok <- validate_usage(token), + :ok <- method().validate(token, captcha, answer_md5), + {:ok, _} <- mark_captcha_as_used(token) do + :ok + end + end + + def enabled?, do: Pleroma.Config.get([__MODULE__, :enabled], false) + + defp seconds_valid, do: Pleroma.Config.get!([__MODULE__, :seconds_valid]) + + defp secret_pair(token) do secret_key_base = Pleroma.Config.get!([Pleroma.Web.Endpoint, :secret_key_base]) secret = KeyGenerator.generate(secret_key_base, token <> "_encrypt") sign_secret = KeyGenerator.generate(secret_key_base, token <> "_sign") + {secret, sign_secret} + end + + defp validate_answer_data(token, answer_data) do + {secret, sign_secret} = secret_pair(token) + + with false <- is_nil(answer_data), + {:ok, data} <- MessageEncryptor.decrypt(answer_data, secret, sign_secret), + %{at: at, answer_data: answer_md5} <- :erlang.binary_to_term(data) do + {:ok, %{at: at, answer_data: answer_md5}} + else + _ -> {:error, :invalid_answer_data} + end + end + + defp validate_expiration(created_at) do # If the time found is less than (current_time-seconds_valid) then the time has already passed # Later we check that the time found is more than the presumed invalidatation time, that means # that the data is still valid and the captcha can be checked - seconds_valid = Pleroma.Config.get!([Pleroma.Captcha, :seconds_valid]) - valid_if_after = DateTime.subtract!(DateTime.now_utc(), seconds_valid) - result = - with false <- is_nil(answer_data), - {:ok, data} <- MessageEncryptor.decrypt(answer_data, secret, sign_secret), - %{at: at, answer_data: answer_md5} <- :erlang.binary_to_term(data) do - try do - if DateTime.before?(at, valid_if_after), - do: throw({:error, dgettext("errors", "CAPTCHA expired")}) + valid_if_after = DateTime.subtract!(DateTime.now_utc(), seconds_valid()) - if not is_nil(Cachex.get!(:used_captcha_cache, token)), - do: throw({:error, dgettext("errors", "CAPTCHA already used")}) + if DateTime.before?(created_at, valid_if_after) do + {:error, :expired} + else + :ok + end + end - res = method().validate(token, captcha, answer_md5) - # Throw if an error occurs - if res != :ok, do: throw(res) + defp validate_usage(token) do + if is_nil(Cachex.get!(:used_captcha_cache, token)) do + :ok + else + {:error, :already_used} + end + end - # Mark this captcha as used - {:ok, _} = - Cachex.put(:used_captcha_cache, token, true, ttl: :timer.seconds(seconds_valid)) - - :ok - catch - :throw, e -> e - end - else - _ -> {:error, dgettext("errors", "Invalid answer data")} - end - - {:reply, result, state} + defp mark_captcha_as_used(token) do + ttl = seconds_valid() |> :timer.seconds() + Cachex.put(:used_captcha_cache, token, true, ttl: ttl) end defp method, do: Pleroma.Config.get!([__MODULE__, :method]) diff --git a/lib/pleroma/captcha/kocaptcha.ex b/lib/pleroma/captcha/kocaptcha.ex index 06ceb20b6..6bc2fa158 100644 --- a/lib/pleroma/captcha/kocaptcha.ex +++ b/lib/pleroma/captcha/kocaptcha.ex @@ -3,7 +3,6 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Captcha.Kocaptcha do - import Pleroma.Web.Gettext alias Pleroma.Captcha.Service @behaviour Service @@ -13,7 +12,7 @@ def new do case Tesla.get(endpoint <> "/new") do {:error, _} -> - %{error: dgettext("errors", "Kocaptcha service unavailable")} + %{error: :kocaptcha_service_unavailable} {:ok, res} -> json_resp = Jason.decode!(res.body) @@ -33,6 +32,6 @@ def validate(_token, captcha, answer_data) do if not is_nil(captcha) and :crypto.hash(:md5, captcha) |> Base.encode16() == String.upcase(answer_data), do: :ok, - else: {:error, dgettext("errors", "Invalid CAPTCHA")} + else: {:error, :invalid} end end diff --git a/lib/pleroma/captcha/native.ex b/lib/pleroma/captcha/native.ex index 06c479ca9..a90631d61 100644 --- a/lib/pleroma/captcha/native.ex +++ b/lib/pleroma/captcha/native.ex @@ -3,7 +3,6 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Captcha.Native do - import Pleroma.Web.Gettext alias Pleroma.Captcha.Service @behaviour Service @@ -11,7 +10,7 @@ defmodule Pleroma.Captcha.Native do def new do case Captcha.get() do :error -> - %{error: dgettext("errors", "Captcha error")} + %{error: :captcha_error} {:ok, answer_data, img_binary} -> %{ @@ -25,7 +24,7 @@ def new do @impl Service def validate(_token, captcha, captcha) when not is_nil(captcha), do: :ok - def validate(_token, _captcha, _answer), do: {:error, dgettext("errors", "Invalid CAPTCHA")} + def validate(_token, _captcha, _answer), do: {:error, :invalid} defp token do 10 diff --git a/lib/pleroma/plugs/mapped_signature_to_identity_plug.ex b/lib/pleroma/plugs/mapped_signature_to_identity_plug.ex index 84b7c5d83..f44d4dee5 100644 --- a/lib/pleroma/plugs/mapped_signature_to_identity_plug.ex +++ b/lib/pleroma/plugs/mapped_signature_to_identity_plug.ex @@ -13,8 +13,9 @@ defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do def init(options), do: options defp key_id_from_conn(conn) do - with %{"keyId" => key_id} <- HTTPSignatures.signature_for_conn(conn) do - Signature.key_id_to_actor_id(key_id) + with %{"keyId" => key_id} <- HTTPSignatures.signature_for_conn(conn), + {:ok, ap_id} <- Signature.key_id_to_actor_id(key_id) do + ap_id else _ -> nil diff --git a/lib/pleroma/signature.ex b/lib/pleroma/signature.ex index 6b0b2c969..d01728361 100644 --- a/lib/pleroma/signature.ex +++ b/lib/pleroma/signature.ex @@ -8,6 +8,7 @@ defmodule Pleroma.Signature do alias Pleroma.Keys alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.ActivityPub.ObjectValidators.Types def key_id_to_actor_id(key_id) do uri = @@ -21,12 +22,23 @@ def key_id_to_actor_id(key_id) do uri end - URI.to_string(uri) + maybe_ap_id = URI.to_string(uri) + + case Types.ObjectID.cast(maybe_ap_id) do + {:ok, ap_id} -> + {:ok, ap_id} + + _ -> + case Pleroma.Web.WebFinger.finger(maybe_ap_id) do + %{"ap_id" => ap_id} -> {:ok, ap_id} + _ -> {:error, maybe_ap_id} + end + end end def fetch_public_key(conn) do with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn), - actor_id <- key_id_to_actor_id(kid), + {:ok, actor_id} <- key_id_to_actor_id(kid), {:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do {:ok, public_key} else @@ -37,7 +49,7 @@ def fetch_public_key(conn) do def refetch_public_key(conn) do with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn), - actor_id <- key_id_to_actor_id(kid), + {:ok, actor_id} <- key_id_to_actor_id(kid), {:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id), {:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do {:ok, public_key} diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 09119137b..c966ec960 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do A module to handle coding from internal to wire ActivityPub and back. """ alias Pleroma.Activity + alias Pleroma.EarmarkRenderer alias Pleroma.FollowingRelationship alias Pleroma.Object alias Pleroma.Object.Containment @@ -43,6 +44,7 @@ def fix_object(object, options \\ []) do |> fix_addressing |> fix_summary |> fix_type(options) + |> fix_content end def fix_summary(%{"summary" => nil} = object) do @@ -357,6 +359,18 @@ def fix_type(%{"inReplyTo" => reply_id, "name" => _} = object, options) def fix_type(object, _), do: object + defp fix_content(%{"mediaType" => "text/markdown", "content" => content} = object) + when is_binary(content) do + html_content = + content + |> Earmark.as_html!(%Earmark.Options{renderer: EarmarkRenderer}) + |> Pleroma.HTML.filter_tags() + + Map.merge(object, %{"content" => html_content, "mediaType" => "text/html"}) + end + + defp fix_content(object), do: object + defp mastodon_follow_hack(%{"id" => id, "actor" => follower_id}, followed) do with true <- id =~ "follows", %User{local: true} = follower <- User.get_cached_by_ap_id(follower_id), @@ -1207,18 +1221,24 @@ def add_attributed_to(object) do def prepare_attachments(object) do attachments = - (object["attachment"] || []) + object + |> Map.get("attachment", []) |> Enum.map(fn data -> [%{"mediaType" => media_type, "href" => href} | _] = data["url"] - %{"url" => href, "mediaType" => media_type, "name" => data["name"], "type" => "Document"} + + %{ + "url" => href, + "mediaType" => media_type, + "name" => data["name"], + "type" => "Document" + } end) Map.put(object, "attachment", attachments) end def strip_internal_fields(object) do - object - |> Map.drop(Pleroma.Constants.object_internal_fields()) + Map.drop(object, Pleroma.Constants.object_internal_fields()) end defp strip_internal_tags(%{"tag" => tags} = object) do diff --git a/lib/pleroma/web/api_spec/helpers.ex b/lib/pleroma/web/api_spec/helpers.ex index ce40fb9e8..183df43ee 100644 --- a/lib/pleroma/web/api_spec/helpers.ex +++ b/lib/pleroma/web/api_spec/helpers.ex @@ -41,9 +41,17 @@ def pagination_params do Operation.parameter( :limit, :query, - %Schema{type: :integer, default: 20, maximum: 40}, - "Limit" + %Schema{type: :integer, default: 20}, + "Maximum number of items to return. Will be ignored if it's more than 40" ) ] end + + def empty_object_response do + Operation.response("Empty object", "application/json", %Schema{type: :object, example: %{}}) + end + + def empty_array_response do + Operation.response("Empty array", "application/json", %Schema{type: :array, example: []}) + end end diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index d3e8bd484..fe9548b1b 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -344,7 +344,7 @@ def endorsements_operation do description: "Not implemented", security: [%{"oAuth" => ["read:accounts"]}], responses: %{ - 200 => Operation.response("Empry array", "application/json", %Schema{type: :array}) + 200 => empty_array_response() } } end @@ -356,7 +356,7 @@ def identity_proofs_operation do operationId: "AccountController.identity_proofs", description: "Not implemented", responses: %{ - 200 => Operation.response("Empry array", "application/json", %Schema{type: :array}) + 200 => empty_array_response() } } end diff --git a/lib/pleroma/web/api_spec/operations/domain_block_operation.ex b/lib/pleroma/web/api_spec/operations/domain_block_operation.ex index 3b7f51ceb..049bcf931 100644 --- a/lib/pleroma/web/api_spec/operations/domain_block_operation.ex +++ b/lib/pleroma/web/api_spec/operations/domain_block_operation.ex @@ -5,7 +5,7 @@ defmodule Pleroma.Web.ApiSpec.DomainBlockOperation do alias OpenApiSpex.Operation alias OpenApiSpex.Schema - alias Pleroma.Web.ApiSpec.Helpers + import Pleroma.Web.ApiSpec.Helpers def open_api_operation(action) do operation = String.to_existing_atom("#{action}_operation") @@ -46,9 +46,7 @@ def create_operation do operationId: "DomainBlockController.create", requestBody: domain_block_request(), security: [%{"oAuth" => ["follow", "write:blocks"]}], - responses: %{ - 200 => Operation.response("Empty object", "application/json", %Schema{type: :object}) - } + responses: %{200 => empty_object_response()} } end @@ -67,7 +65,7 @@ def delete_operation do end defp domain_block_request do - Helpers.request_body( + request_body( "Parameters", %Schema{ type: :object, diff --git a/lib/pleroma/web/api_spec/operations/notification_operation.ex b/lib/pleroma/web/api_spec/operations/notification_operation.ex new file mode 100644 index 000000000..64adc5319 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/notification_operation.ex @@ -0,0 +1,211 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.NotificationOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.Account + alias Pleroma.Web.ApiSpec.Schemas.ApiError + alias Pleroma.Web.ApiSpec.Schemas.BooleanLike + alias Pleroma.Web.ApiSpec.Schemas.Status + alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope + + import Pleroma.Web.ApiSpec.Helpers + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def index_operation do + %Operation{ + tags: ["Notifications"], + summary: "Get all notifications", + description: + "Notifications concerning the user. This API returns Link headers containing links to the next/previous page. However, the links can also be constructed dynamically using query params and `id` values.", + operationId: "NotificationController.index", + security: [%{"oAuth" => ["read:notifications"]}], + parameters: + [ + Operation.parameter( + :exclude_types, + :query, + %Schema{type: :array, items: notification_type()}, + "Array of types to exclude" + ), + Operation.parameter( + :account_id, + :query, + %Schema{type: :string}, + "Return only notifications received from this account" + ), + Operation.parameter( + :exclude_visibilities, + :query, + %Schema{type: :array, items: VisibilityScope}, + "Exclude the notifications for activities with the given visibilities" + ), + Operation.parameter( + :include_types, + :query, + %Schema{type: :array, items: notification_type()}, + "Include the notifications for activities with the given types" + ), + Operation.parameter( + :with_muted, + :query, + BooleanLike, + "Include the notifications from muted users" + ) + ] ++ pagination_params(), + responses: %{ + 200 => + Operation.response("Array of notifications", "application/json", %Schema{ + type: :array, + items: notification() + }), + 404 => Operation.response("Error", "application/json", ApiError) + } + } + end + + def show_operation do + %Operation{ + tags: ["Notifications"], + summary: "Get a single notification", + description: "View information about a notification with a given ID.", + operationId: "NotificationController.show", + security: [%{"oAuth" => ["read:notifications"]}], + parameters: [id_param()], + responses: %{ + 200 => Operation.response("Notification", "application/json", notification()) + } + } + end + + def clear_operation do + %Operation{ + tags: ["Notifications"], + summary: "Dismiss all notifications", + description: "Clear all notifications from the server.", + operationId: "NotificationController.clear", + security: [%{"oAuth" => ["write:notifications"]}], + responses: %{200 => empty_object_response()} + } + end + + def dismiss_operation do + %Operation{ + tags: ["Notifications"], + summary: "Dismiss a single notification", + description: "Clear a single notification from the server.", + operationId: "NotificationController.dismiss", + parameters: [id_param()], + security: [%{"oAuth" => ["write:notifications"]}], + responses: %{200 => empty_object_response()} + } + end + + def dismiss_via_body_operation do + %Operation{ + tags: ["Notifications"], + summary: "Dismiss a single notification", + deprecated: true, + description: "Clear a single notification from the server.", + operationId: "NotificationController.dismiss_via_body", + requestBody: + request_body( + "Parameters", + %Schema{type: :object, properties: %{id: %Schema{type: :string}}}, + required: true + ), + security: [%{"oAuth" => ["write:notifications"]}], + responses: %{200 => empty_object_response()} + } + end + + def destroy_multiple_operation do + %Operation{ + tags: ["Notifications"], + summary: "Dismiss multiple notifications", + operationId: "NotificationController.destroy_multiple", + security: [%{"oAuth" => ["write:notifications"]}], + parameters: [ + Operation.parameter( + :ids, + :query, + %Schema{type: :array, items: %Schema{type: :string}}, + "Array of notification IDs to dismiss", + required: true + ) + ], + responses: %{200 => empty_object_response()} + } + end + + defp notification do + %Schema{ + title: "Notification", + description: "Response schema for a notification", + type: :object, + properties: %{ + id: %Schema{type: :string}, + type: notification_type(), + created_at: %Schema{type: :string, format: :"date-time"}, + account: %Schema{ + allOf: [Account], + description: "The account that performed the action that generated the notification." + }, + status: %Schema{ + allOf: [Status], + description: + "Status that was the object of the notification, e.g. in mentions, reblogs, favourites, or polls.", + nullable: true + } + }, + example: %{ + "id" => "34975861", + "type" => "mention", + "created_at" => "2019-11-23T07:49:02.064Z", + "account" => Account.schema().example, + "status" => Status.schema().example + } + } + end + + defp notification_type do + %Schema{ + type: :string, + enum: [ + "follow", + "favourite", + "reblog", + "mention", + "poll", + "pleroma:emoji_reaction", + "move", + "follow_request" + ], + description: """ + The type of event that resulted in the notification. + + - `follow` - Someone followed you + - `mention` - Someone mentioned you in their status + - `reblog` - Someone boosted one of your statuses + - `favourite` - Someone favourited one of your statuses + - `poll` - A poll you have voted in or created has ended + - `move` - Someone moved their account + - `pleroma:emoji_reaction` - Someone reacted with emoji to your status + """ + } + end + + defp id_param do + Operation.parameter(:id, :path, :string, "Notification ID", + example: "123", + required: true + ) + end +end diff --git a/lib/pleroma/web/api_spec/operations/report_operation.ex b/lib/pleroma/web/api_spec/operations/report_operation.ex new file mode 100644 index 000000000..da4d50703 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/report_operation.ex @@ -0,0 +1,78 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.ReportOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Helpers + alias Pleroma.Web.ApiSpec.Schemas.ApiError + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def create_operation do + %Operation{ + tags: ["reports"], + summary: "File a report", + description: "Report problematic users to your moderators", + operationId: "ReportController.create", + security: [%{"oAuth" => ["follow", "write:reports"]}], + requestBody: Helpers.request_body("Parameters", create_request(), required: true), + responses: %{ + 200 => Operation.response("Report", "application/json", create_response()), + 400 => Operation.response("Report", "application/json", ApiError) + } + } + end + + defp create_request do + %Schema{ + title: "ReportCreateRequest", + description: "POST body for creating a report", + type: :object, + properties: %{ + account_id: %Schema{type: :string, description: "ID of the account to report"}, + status_ids: %Schema{ + type: :array, + items: %Schema{type: :string}, + description: "Array of Statuses to attach to the report, for context" + }, + comment: %Schema{ + type: :string, + description: "Reason for the report" + }, + forward: %Schema{ + type: :boolean, + default: false, + description: + "If the account is remote, should the report be forwarded to the remote admin?" + } + }, + required: [:account_id], + example: %{ + "account_id" => "123", + "status_ids" => ["1337"], + "comment" => "bad status!", + "forward" => "false" + } + } + end + + defp create_response do + %Schema{ + title: "ReportResponse", + type: :object, + properties: %{ + id: %Schema{type: :string, description: "Report ID"}, + action_taken: %Schema{type: :boolean, description: "Is action taken?"} + }, + example: %{ + "id" => "123", + "action_taken" => false + } + } + end +end diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 4112e441a..4618b4bbf 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -382,9 +382,9 @@ def thread_muted?(user, activity) do ThreadMute.exists?(user.id, activity.data["context"]) end - def report(user, %{"account_id" => account_id} = data) do - with {:ok, account} <- get_reported_account(account_id), - {:ok, {content_html, _, _}} <- make_report_content_html(data["comment"]), + def report(user, data) do + with {:ok, account} <- get_reported_account(data.account_id), + {:ok, {content_html, _, _}} <- make_report_content_html(data[:comment]), {:ok, statuses} <- get_report_statuses(account, data) do ActivityPub.flag(%{ context: Utils.generate_context_id(), @@ -392,13 +392,11 @@ def report(user, %{"account_id" => account_id} = data) do account: account, statuses: statuses, content: content_html, - forward: data["forward"] || false + forward: Map.get(data, :forward, false) }) end end - def report(_user, _params), do: {:error, dgettext("errors", "Valid `account_id` required")} - defp get_reported_account(account_id) do case User.get_cached_by_id(account_id) do %User{} = account -> {:ok, account} diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 945e63e22..6540fa5d1 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -504,7 +504,8 @@ def make_report_content_html(comment) do end end - def get_report_statuses(%User{ap_id: actor}, %{"status_ids" => status_ids}) do + def get_report_statuses(%User{ap_id: actor}, %{status_ids: status_ids}) + when is_list(status_ids) do {:ok, Activity.all_by_actor_and_id(actor, status_ids)} end diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 1eedf02d6..61b0e2f63 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -94,24 +94,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do @doc "POST /api/v1/accounts" def create(%{assigns: %{app: app}, body_params: params} = conn, _params) do - params = - params - |> Map.take([ - :email, - :bio, - :captcha_solution, - :captcha_token, - :captcha_answer_data, - :token, - :password, - :fullname - ]) - |> Map.put(:nickname, params.username) - |> Map.put(:fullname, Map.get(params, :fullname, params.username)) - |> Map.put(:confirm, params.password) - |> Map.put(:trusted_app, app.trusted) - with :ok <- validate_email_param(params), + :ok <- TwitterAPI.validate_captcha(app, params), {:ok, user} <- TwitterAPI.register_user(params, need_confirmation: true), {:ok, token} <- Token.create_token(app, user, %{scopes: app.scopes}) do json(conn, %{ @@ -121,7 +105,7 @@ def create(%{assigns: %{app: app}, body_params: params} = conn, _params) do created_at: Token.Utils.format_created_at(token) }) else - {:error, errors} -> json_response(conn, :bad_request, errors) + {:error, error} -> json_response(conn, :bad_request, %{error: error}) end end @@ -133,11 +117,11 @@ def create(conn, _) do render_error(conn, :forbidden, "Invalid credentials") end - defp validate_email_param(%{:email => email}) when not is_nil(email), do: :ok + defp validate_email_param(%{email: email}) when not is_nil(email), do: :ok defp validate_email_param(_) do case Pleroma.Config.get([:instance, :account_activation_required]) do - true -> {:error, %{"error" => "Missing parameters"}} + true -> {:error, dgettext("errors", "Missing parameter: %{name}", name: "email")} _ -> :ok end end diff --git a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex index 311405277..a14c86893 100644 --- a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex @@ -13,6 +13,8 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do @oauth_read_actions [:show, :index] + plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError) + plug( OAuthScopesPlug, %{scopes: ["read:notifications"]} when action in @oauth_read_actions @@ -20,14 +22,16 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do plug(OAuthScopesPlug, %{scopes: ["write:notifications"]} when action not in @oauth_read_actions) + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.NotificationOperation + # GET /api/v1/notifications - def index(conn, %{"account_id" => account_id} = params) do + def index(conn, %{account_id: account_id} = params) do case Pleroma.User.get_cached_by_id(account_id) do %{ap_id: account_ap_id} -> params = params - |> Map.delete("account_id") - |> Map.put("account_ap_id", account_ap_id) + |> Map.delete(:account_id) + |> Map.put(:account_ap_id, account_ap_id) index(conn, params) @@ -39,6 +43,7 @@ def index(conn, %{"account_id" => account_id} = params) do end def index(%{assigns: %{user: user}} = conn, params) do + params = Map.new(params, fn {k, v} -> {to_string(k), v} end) notifications = MastodonAPI.get_notifications(user, params) conn @@ -51,7 +56,7 @@ def index(%{assigns: %{user: user}} = conn, params) do end # GET /api/v1/notifications/:id - def show(%{assigns: %{user: user}} = conn, %{"id" => id}) do + def show(%{assigns: %{user: user}} = conn, %{id: id}) do with {:ok, notification} <- Notification.get(user, id) do render(conn, "show.json", notification: notification, for: user) else @@ -69,8 +74,8 @@ def clear(%{assigns: %{user: user}} = conn, _params) do end # POST /api/v1/notifications/:id/dismiss - # POST /api/v1/notifications/dismiss (deprecated) - def dismiss(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do + + def dismiss(%{assigns: %{user: user}} = conn, %{id: id} = _params) do with {:ok, _notif} <- Notification.dismiss(user, id) do json(conn, %{}) else @@ -81,8 +86,13 @@ def dismiss(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do end end + # POST /api/v1/notifications/dismiss (deprecated) + def dismiss_via_body(%{body_params: params} = conn, _) do + dismiss(conn, params) + end + # DELETE /api/v1/notifications/destroy_multiple - def destroy_multiple(%{assigns: %{user: user}} = conn, %{"ids" => ids} = _params) do + def destroy_multiple(%{assigns: %{user: user}} = conn, %{ids: ids} = _params) do Notification.destroy_multiple(user, ids) json(conn, %{}) end diff --git a/lib/pleroma/web/mastodon_api/controllers/report_controller.ex b/lib/pleroma/web/mastodon_api/controllers/report_controller.ex index 9fbaa7bd1..f65c5c62b 100644 --- a/lib/pleroma/web/mastodon_api/controllers/report_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/report_controller.ex @@ -9,10 +9,13 @@ defmodule Pleroma.Web.MastodonAPI.ReportController do action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + plug(OpenApiSpex.Plug.CastAndValidate, render_error: Pleroma.Web.ApiSpec.RenderError) plug(OAuthScopesPlug, %{scopes: ["write:reports"]} when action == :create) + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.ReportOperation + @doc "POST /api/v1/reports" - def create(%{assigns: %{user: user}} = conn, params) do + def create(%{assigns: %{user: user}, body_params: params} = conn, _) do with {:ok, activity} <- Pleroma.Web.CommonAPI.report(user, params) do render(conn, "show.json", activity: activity) end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index a7e1f2f57..83287a83d 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -396,7 +396,7 @@ defmodule Pleroma.Web.Router do post("/notifications/clear", NotificationController, :clear) delete("/notifications/destroy_multiple", NotificationController, :destroy_multiple) # Deprecated: was removed in Mastodon v3, use `/notifications/:id/dismiss` instead - post("/notifications/dismiss", NotificationController, :dismiss) + post("/notifications/dismiss", NotificationController, :dismiss_via_body) post("/polls/:id/votes", PollController, :vote) diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index cf1d9c74c..5cfb385ac 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -3,54 +3,27 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.TwitterAPI.TwitterAPI do + import Pleroma.Web.Gettext + alias Pleroma.Emails.Mailer alias Pleroma.Emails.UserEmail alias Pleroma.Repo alias Pleroma.User alias Pleroma.UserInviteToken - require Pleroma.Constants - def register_user(params, opts \\ []) do params = params - |> Map.take([ - :nickname, - :password, - :captcha_solution, - :captcha_token, - :captcha_answer_data, - :token, - :email, - :trusted_app - ]) - |> Map.put(:bio, User.parse_bio(params[:bio] || "")) - |> Map.put(:name, params.fullname) - |> Map.put(:password_confirmation, params[:confirm]) + |> Map.take([:email, :token, :password]) + |> Map.put(:bio, params |> Map.get(:bio, "") |> User.parse_bio()) + |> Map.put(:nickname, params[:username]) + |> Map.put(:name, Map.get(params, :fullname, params[:username])) + |> Map.put(:password_confirmation, params[:password]) - case validate_captcha(params) do - :ok -> - if Pleroma.Config.get([:instance, :registrations_open]) do - create_user(params, opts) - else - create_user_with_invite(params, opts) - end - - {:error, error} -> - # I have no idea how this error handling works - {:error, %{error: Jason.encode!(%{captcha: [error]})}} - end - end - - defp validate_captcha(params) do - if params[:trusted_app] || not Pleroma.Config.get([Pleroma.Captcha, :enabled]) do - :ok + if Pleroma.Config.get([:instance, :registrations_open]) do + create_user(params, opts) else - Pleroma.Captcha.validate( - params.captcha_token, - params.captcha_solution, - params.captcha_answer_data - ) + create_user_with_invite(params, opts) end end @@ -75,16 +48,17 @@ defp create_user(params, opts) do {:error, changeset} -> errors = - Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end) + changeset + |> Ecto.Changeset.traverse_errors(fn {msg, _opts} -> msg end) |> Jason.encode!() - {:error, %{error: errors}} + {:error, errors} end end def password_reset(nickname_or_email) do with true <- is_binary(nickname_or_email), - %User{local: true, email: email} = user when not is_nil(email) <- + %User{local: true, email: email} = user when is_binary(email) <- User.get_by_nickname_or_email(nickname_or_email), {:ok, token_record} <- Pleroma.PasswordResetToken.create_token(user) do user @@ -106,4 +80,58 @@ def password_reset(nickname_or_email) do {:error, "unknown user"} end end + + def validate_captcha(app, params) do + if app.trusted || not Pleroma.Captcha.enabled?() do + :ok + else + do_validate_captcha(params) + end + end + + defp do_validate_captcha(params) do + with :ok <- validate_captcha_presence(params), + :ok <- + Pleroma.Captcha.validate( + params[:captcha_token], + params[:captcha_solution], + params[:captcha_answer_data] + ) do + :ok + else + {:error, :captcha_error} -> + captcha_error(dgettext("errors", "CAPTCHA Error")) + + {:error, :invalid} -> + captcha_error(dgettext("errors", "Invalid CAPTCHA")) + + {:error, :kocaptcha_service_unavailable} -> + captcha_error(dgettext("errors", "Kocaptcha service unavailable")) + + {:error, :expired} -> + captcha_error(dgettext("errors", "CAPTCHA expired")) + + {:error, :already_used} -> + captcha_error(dgettext("errors", "CAPTCHA already used")) + + {:error, :invalid_answer_data} -> + captcha_error(dgettext("errors", "Invalid answer data")) + + {:error, error} -> + captcha_error(error) + end + end + + defp validate_captcha_presence(params) do + [:captcha_solution, :captcha_token, :captcha_answer_data] + |> Enum.find_value(:ok, fn key -> + unless is_binary(params[key]) do + error = dgettext("errors", "Invalid CAPTCHA (Missing parameter: %{name})", name: key) + {:error, error} + end + end) + end + + # For some reason FE expects error message to be a serialized JSON + defp captcha_error(error), do: {:error, Jason.encode!(%{captcha: [error]})} end diff --git a/priv/static/index.html b/priv/static/index.html index 66c9b53de..6af441737 100644 --- a/priv/static/index.html +++ b/priv/static/index.html @@ -1 +1 @@ -Pleroma
\ No newline at end of file +Pleroma
\ No newline at end of file diff --git a/priv/static/static/css/app.1055039ce3f2fe4dd110.css b/priv/static/static/css/app.1055039ce3f2fe4dd110.css deleted file mode 100644 index 1867ca81a..000000000 Binary files a/priv/static/static/css/app.1055039ce3f2fe4dd110.css and /dev/null differ diff --git a/priv/static/static/css/app.1055039ce3f2fe4dd110.css.map b/priv/static/static/css/app.1055039ce3f2fe4dd110.css.map deleted file mode 100644 index 861ee8313..000000000 --- a/priv/static/static/css/app.1055039ce3f2fe4dd110.css.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["webpack:///./src/hocs/with_load_more/with_load_more.scss","webpack:///./src/components/tab_switcher/tab_switcher.scss","webpack:///./src/hocs/with_subscription/with_subscription.scss"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,C;ACTA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,C;ACxFA;AACA;AACA;AACA;AACA;AACA;AACA,C","file":"static/css/app.1055039ce3f2fe4dd110.css","sourcesContent":[".with-load-more-footer {\n padding: 10px;\n text-align: center;\n border-top: 1px solid;\n border-top-color: #222;\n border-top-color: var(--border, #222);\n}\n.with-load-more-footer .error {\n font-size: 14px;\n}",".tab-switcher {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n}\n.tab-switcher .contents {\n -ms-flex: 1 0 auto;\n flex: 1 0 auto;\n min-height: 0px;\n}\n.tab-switcher .contents .hidden {\n display: none;\n}\n.tab-switcher .contents.scrollable-tabs {\n -ms-flex-preferred-size: 0;\n flex-basis: 0;\n overflow-y: auto;\n}\n.tab-switcher .tabs {\n display: -ms-flexbox;\n display: flex;\n position: relative;\n width: 100%;\n overflow-y: hidden;\n overflow-x: auto;\n padding-top: 5px;\n box-sizing: border-box;\n}\n.tab-switcher .tabs::after, .tab-switcher .tabs::before {\n display: block;\n content: \"\";\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n border-bottom: 1px solid;\n border-bottom-color: #222;\n border-bottom-color: var(--border, #222);\n}\n.tab-switcher .tabs .tab-wrapper {\n height: 28px;\n position: relative;\n display: -ms-flexbox;\n display: flex;\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n}\n.tab-switcher .tabs .tab-wrapper .tab {\n width: 100%;\n min-width: 1px;\n position: relative;\n border-bottom-left-radius: 0;\n border-bottom-right-radius: 0;\n padding: 6px 1em;\n padding-bottom: 99px;\n margin-bottom: -93px;\n white-space: nowrap;\n color: #b9b9ba;\n color: var(--tabText, #b9b9ba);\n background-color: #182230;\n background-color: var(--tab, #182230);\n}\n.tab-switcher .tabs .tab-wrapper .tab:not(.active) {\n z-index: 4;\n}\n.tab-switcher .tabs .tab-wrapper .tab:not(.active):hover {\n z-index: 6;\n}\n.tab-switcher .tabs .tab-wrapper .tab.active {\n background: transparent;\n z-index: 5;\n color: #b9b9ba;\n color: var(--tabActiveText, #b9b9ba);\n}\n.tab-switcher .tabs .tab-wrapper .tab img {\n max-height: 26px;\n vertical-align: top;\n margin-top: -5px;\n}\n.tab-switcher .tabs .tab-wrapper:not(.active)::after {\n content: \"\";\n position: absolute;\n left: 0;\n right: 0;\n bottom: 0;\n z-index: 7;\n border-bottom: 1px solid;\n border-bottom-color: #222;\n border-bottom-color: var(--border, #222);\n}",".with-subscription-loading {\n padding: 10px;\n text-align: center;\n}\n.with-subscription-loading .error {\n font-size: 14px;\n}"],"sourceRoot":""} \ No newline at end of file diff --git a/priv/static/static/css/app.613cef07981cd95ccceb.css b/priv/static/static/css/app.613cef07981cd95ccceb.css new file mode 100644 index 000000000..c1d5f8188 Binary files /dev/null and b/priv/static/static/css/app.613cef07981cd95ccceb.css differ diff --git a/priv/static/static/css/app.613cef07981cd95ccceb.css.map b/priv/static/static/css/app.613cef07981cd95ccceb.css.map new file mode 100644 index 000000000..556e0bb0b --- /dev/null +++ b/priv/static/static/css/app.613cef07981cd95ccceb.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["webpack:///./src/hocs/with_load_more/with_load_more.scss","webpack:///./src/components/tab_switcher/tab_switcher.scss","webpack:///./src/hocs/with_subscription/with_subscription.scss"],"names":[],"mappings":"AAAA,uBAAuB,aAAa,kBAAkB,qBAAqB,sBAAsB,qCAAqC,8BAA8B,e;ACApK,cAAc,oBAAoB,aAAa,0BAA0B,sBAAsB,wBAAwB,kBAAkB,cAAc,eAAe,gCAAgC,aAAa,wCAAwC,0BAA0B,aAAa,gBAAgB,oBAAoB,oBAAoB,aAAa,kBAAkB,WAAW,kBAAkB,gBAAgB,gBAAgB,sBAAsB,uDAAuD,cAAc,WAAW,kBAAkB,cAAc,wBAAwB,yBAAyB,wCAAwC,iCAAiC,YAAY,kBAAkB,oBAAoB,aAAa,kBAAkB,cAAc,sCAAsC,WAAW,cAAc,kBAAkB,4BAA4B,6BAA6B,gBAAgB,oBAAoB,oBAAoB,mBAAmB,cAAc,8BAA8B,yBAAyB,qCAAqC,mDAAmD,UAAU,yDAAyD,UAAU,6CAA6C,uBAAuB,UAAU,cAAc,oCAAoC,0CAA0C,gBAAgB,mBAAmB,gBAAgB,qDAAqD,WAAW,kBAAkB,OAAO,QAAQ,SAAS,UAAU,wBAAwB,yBAAyB,wC;ACAtlD,2BAA2B,aAAa,kBAAkB,kCAAkC,e","file":"static/css/app.613cef07981cd95ccceb.css","sourcesContent":[".with-load-more-footer{padding:10px;text-align:center;border-top:1px solid;border-top-color:#222;border-top-color:var(--border, #222)}.with-load-more-footer .error{font-size:14px}",".tab-switcher{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.tab-switcher .contents{-ms-flex:1 0 auto;flex:1 0 auto;min-height:0px}.tab-switcher .contents .hidden{display:none}.tab-switcher .contents.scrollable-tabs{-ms-flex-preferred-size:0;flex-basis:0;overflow-y:auto}.tab-switcher .tabs{display:-ms-flexbox;display:flex;position:relative;width:100%;overflow-y:hidden;overflow-x:auto;padding-top:5px;box-sizing:border-box}.tab-switcher .tabs::after,.tab-switcher .tabs::before{display:block;content:\"\";-ms-flex:1 1 auto;flex:1 1 auto;border-bottom:1px solid;border-bottom-color:#222;border-bottom-color:var(--border, #222)}.tab-switcher .tabs .tab-wrapper{height:28px;position:relative;display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto}.tab-switcher .tabs .tab-wrapper .tab{width:100%;min-width:1px;position:relative;border-bottom-left-radius:0;border-bottom-right-radius:0;padding:6px 1em;padding-bottom:99px;margin-bottom:-93px;white-space:nowrap;color:#b9b9ba;color:var(--tabText, #b9b9ba);background-color:#182230;background-color:var(--tab, #182230)}.tab-switcher .tabs .tab-wrapper .tab:not(.active){z-index:4}.tab-switcher .tabs .tab-wrapper .tab:not(.active):hover{z-index:6}.tab-switcher .tabs .tab-wrapper .tab.active{background:transparent;z-index:5;color:#b9b9ba;color:var(--tabActiveText, #b9b9ba)}.tab-switcher .tabs .tab-wrapper .tab img{max-height:26px;vertical-align:top;margin-top:-5px}.tab-switcher .tabs .tab-wrapper:not(.active)::after{content:\"\";position:absolute;left:0;right:0;bottom:0;z-index:7;border-bottom:1px solid;border-bottom-color:#222;border-bottom-color:var(--border, #222)}",".with-subscription-loading{padding:10px;text-align:center}.with-subscription-loading .error{font-size:14px}"],"sourceRoot":""} \ No newline at end of file diff --git a/priv/static/static/css/vendors~app.b2603a50868c68a1c192.css b/priv/static/static/css/vendors~app.18fea621d430000acc27.css similarity index 92% rename from priv/static/static/css/vendors~app.b2603a50868c68a1c192.css rename to priv/static/static/css/vendors~app.18fea621d430000acc27.css index a2e625f5e..ef783cbb3 100644 Binary files a/priv/static/static/css/vendors~app.b2603a50868c68a1c192.css and b/priv/static/static/css/vendors~app.18fea621d430000acc27.css differ diff --git a/priv/static/static/css/vendors~app.18fea621d430000acc27.css.map b/priv/static/static/css/vendors~app.18fea621d430000acc27.css.map new file mode 100644 index 000000000..057d67d6a --- /dev/null +++ b/priv/static/static/css/vendors~app.18fea621d430000acc27.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["webpack:///./node_modules/cropperjs/dist/cropper.css"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA,wCAAwC;AACxC;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA","file":"static/css/vendors~app.18fea621d430000acc27.css","sourcesContent":["/*!\n * Cropper.js v1.5.6\n * https://fengyuanchen.github.io/cropperjs\n *\n * Copyright 2015-present Chen Fengyuan\n * Released under the MIT license\n *\n * Date: 2019-10-04T04:33:44.164Z\n */\n\n.cropper-container {\n direction: ltr;\n font-size: 0;\n line-height: 0;\n position: relative;\n -ms-touch-action: none;\n touch-action: none;\n -webkit-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n\n.cropper-container img {\n display: block;\n height: 100%;\n image-orientation: 0deg;\n max-height: none !important;\n max-width: none !important;\n min-height: 0 !important;\n min-width: 0 !important;\n width: 100%;\n}\n\n.cropper-wrap-box,\n.cropper-canvas,\n.cropper-drag-box,\n.cropper-crop-box,\n.cropper-modal {\n bottom: 0;\n left: 0;\n position: absolute;\n right: 0;\n top: 0;\n}\n\n.cropper-wrap-box,\n.cropper-canvas {\n overflow: hidden;\n}\n\n.cropper-drag-box {\n background-color: #fff;\n opacity: 0;\n}\n\n.cropper-modal {\n background-color: #000;\n opacity: 0.5;\n}\n\n.cropper-view-box {\n display: block;\n height: 100%;\n outline: 1px solid #39f;\n outline-color: rgba(51, 153, 255, 0.75);\n overflow: hidden;\n width: 100%;\n}\n\n.cropper-dashed {\n border: 0 dashed #eee;\n display: block;\n opacity: 0.5;\n position: absolute;\n}\n\n.cropper-dashed.dashed-h {\n border-bottom-width: 1px;\n border-top-width: 1px;\n height: calc(100% / 3);\n left: 0;\n top: calc(100% / 3);\n width: 100%;\n}\n\n.cropper-dashed.dashed-v {\n border-left-width: 1px;\n border-right-width: 1px;\n height: 100%;\n left: calc(100% / 3);\n top: 0;\n width: calc(100% / 3);\n}\n\n.cropper-center {\n display: block;\n height: 0;\n left: 50%;\n opacity: 0.75;\n position: absolute;\n top: 50%;\n width: 0;\n}\n\n.cropper-center::before,\n.cropper-center::after {\n background-color: #eee;\n content: ' ';\n display: block;\n position: absolute;\n}\n\n.cropper-center::before {\n height: 1px;\n left: -3px;\n top: 0;\n width: 7px;\n}\n\n.cropper-center::after {\n height: 7px;\n left: 0;\n top: -3px;\n width: 1px;\n}\n\n.cropper-face,\n.cropper-line,\n.cropper-point {\n display: block;\n height: 100%;\n opacity: 0.1;\n position: absolute;\n width: 100%;\n}\n\n.cropper-face {\n background-color: #fff;\n left: 0;\n top: 0;\n}\n\n.cropper-line {\n background-color: #39f;\n}\n\n.cropper-line.line-e {\n cursor: ew-resize;\n right: -3px;\n top: 0;\n width: 5px;\n}\n\n.cropper-line.line-n {\n cursor: ns-resize;\n height: 5px;\n left: 0;\n top: -3px;\n}\n\n.cropper-line.line-w {\n cursor: ew-resize;\n left: -3px;\n top: 0;\n width: 5px;\n}\n\n.cropper-line.line-s {\n bottom: -3px;\n cursor: ns-resize;\n height: 5px;\n left: 0;\n}\n\n.cropper-point {\n background-color: #39f;\n height: 5px;\n opacity: 0.75;\n width: 5px;\n}\n\n.cropper-point.point-e {\n cursor: ew-resize;\n margin-top: -3px;\n right: -3px;\n top: 50%;\n}\n\n.cropper-point.point-n {\n cursor: ns-resize;\n left: 50%;\n margin-left: -3px;\n top: -3px;\n}\n\n.cropper-point.point-w {\n cursor: ew-resize;\n left: -3px;\n margin-top: -3px;\n top: 50%;\n}\n\n.cropper-point.point-s {\n bottom: -3px;\n cursor: s-resize;\n left: 50%;\n margin-left: -3px;\n}\n\n.cropper-point.point-ne {\n cursor: nesw-resize;\n right: -3px;\n top: -3px;\n}\n\n.cropper-point.point-nw {\n cursor: nwse-resize;\n left: -3px;\n top: -3px;\n}\n\n.cropper-point.point-sw {\n bottom: -3px;\n cursor: nesw-resize;\n left: -3px;\n}\n\n.cropper-point.point-se {\n bottom: -3px;\n cursor: nwse-resize;\n height: 20px;\n opacity: 1;\n right: -3px;\n width: 20px;\n}\n\n@media (min-width: 768px) {\n .cropper-point.point-se {\n height: 15px;\n width: 15px;\n }\n}\n\n@media (min-width: 992px) {\n .cropper-point.point-se {\n height: 10px;\n width: 10px;\n }\n}\n\n@media (min-width: 1200px) {\n .cropper-point.point-se {\n height: 5px;\n opacity: 0.75;\n width: 5px;\n }\n}\n\n.cropper-point.point-se::before {\n background-color: #39f;\n bottom: -50%;\n content: ' ';\n display: block;\n height: 200%;\n opacity: 0;\n position: absolute;\n right: -50%;\n width: 200%;\n}\n\n.cropper-invisible {\n opacity: 0;\n}\n\n.cropper-bg {\n background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC');\n}\n\n.cropper-hide {\n display: block;\n height: 0;\n position: absolute;\n width: 0;\n}\n\n.cropper-hidden {\n display: none !important;\n}\n\n.cropper-move {\n cursor: move;\n}\n\n.cropper-crop {\n cursor: crosshair;\n}\n\n.cropper-disabled .cropper-drag-box,\n.cropper-disabled .cropper-face,\n.cropper-disabled .cropper-line,\n.cropper-disabled .cropper-point {\n cursor: not-allowed;\n}\n"],"sourceRoot":""} \ No newline at end of file diff --git a/priv/static/static/css/vendors~app.b2603a50868c68a1c192.css.map b/priv/static/static/css/vendors~app.b2603a50868c68a1c192.css.map deleted file mode 100644 index e7013b291..000000000 --- a/priv/static/static/css/vendors~app.b2603a50868c68a1c192.css.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["webpack:///./node_modules/cropperjs/dist/cropper.css"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA,wCAAwC;AACxC;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA","file":"static/css/vendors~app.b2603a50868c68a1c192.css","sourcesContent":["/*!\n * Cropper.js v1.4.3\n * https://fengyuanchen.github.io/cropperjs\n *\n * Copyright 2015-present Chen Fengyuan\n * Released under the MIT license\n *\n * Date: 2018-10-24T13:07:11.429Z\n */\n\n.cropper-container {\n direction: ltr;\n font-size: 0;\n line-height: 0;\n position: relative;\n -ms-touch-action: none;\n touch-action: none;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n\n.cropper-container img {\n display: block;\n height: 100%;\n image-orientation: 0deg;\n max-height: none !important;\n max-width: none !important;\n min-height: 0 !important;\n min-width: 0 !important;\n width: 100%;\n}\n\n.cropper-wrap-box,\n.cropper-canvas,\n.cropper-drag-box,\n.cropper-crop-box,\n.cropper-modal {\n bottom: 0;\n left: 0;\n position: absolute;\n right: 0;\n top: 0;\n}\n\n.cropper-wrap-box,\n.cropper-canvas {\n overflow: hidden;\n}\n\n.cropper-drag-box {\n background-color: #fff;\n opacity: 0;\n}\n\n.cropper-modal {\n background-color: #000;\n opacity: .5;\n}\n\n.cropper-view-box {\n display: block;\n height: 100%;\n outline-color: rgba(51, 153, 255, 0.75);\n outline: 1px solid #39f;\n overflow: hidden;\n width: 100%;\n}\n\n.cropper-dashed {\n border: 0 dashed #eee;\n display: block;\n opacity: .5;\n position: absolute;\n}\n\n.cropper-dashed.dashed-h {\n border-bottom-width: 1px;\n border-top-width: 1px;\n height: calc(100% / 3);\n left: 0;\n top: calc(100% / 3);\n width: 100%;\n}\n\n.cropper-dashed.dashed-v {\n border-left-width: 1px;\n border-right-width: 1px;\n height: 100%;\n left: calc(100% / 3);\n top: 0;\n width: calc(100% / 3);\n}\n\n.cropper-center {\n display: block;\n height: 0;\n left: 50%;\n opacity: .75;\n position: absolute;\n top: 50%;\n width: 0;\n}\n\n.cropper-center:before,\n.cropper-center:after {\n background-color: #eee;\n content: ' ';\n display: block;\n position: absolute;\n}\n\n.cropper-center:before {\n height: 1px;\n left: -3px;\n top: 0;\n width: 7px;\n}\n\n.cropper-center:after {\n height: 7px;\n left: 0;\n top: -3px;\n width: 1px;\n}\n\n.cropper-face,\n.cropper-line,\n.cropper-point {\n display: block;\n height: 100%;\n opacity: .1;\n position: absolute;\n width: 100%;\n}\n\n.cropper-face {\n background-color: #fff;\n left: 0;\n top: 0;\n}\n\n.cropper-line {\n background-color: #39f;\n}\n\n.cropper-line.line-e {\n cursor: ew-resize;\n right: -3px;\n top: 0;\n width: 5px;\n}\n\n.cropper-line.line-n {\n cursor: ns-resize;\n height: 5px;\n left: 0;\n top: -3px;\n}\n\n.cropper-line.line-w {\n cursor: ew-resize;\n left: -3px;\n top: 0;\n width: 5px;\n}\n\n.cropper-line.line-s {\n bottom: -3px;\n cursor: ns-resize;\n height: 5px;\n left: 0;\n}\n\n.cropper-point {\n background-color: #39f;\n height: 5px;\n opacity: .75;\n width: 5px;\n}\n\n.cropper-point.point-e {\n cursor: ew-resize;\n margin-top: -3px;\n right: -3px;\n top: 50%;\n}\n\n.cropper-point.point-n {\n cursor: ns-resize;\n left: 50%;\n margin-left: -3px;\n top: -3px;\n}\n\n.cropper-point.point-w {\n cursor: ew-resize;\n left: -3px;\n margin-top: -3px;\n top: 50%;\n}\n\n.cropper-point.point-s {\n bottom: -3px;\n cursor: s-resize;\n left: 50%;\n margin-left: -3px;\n}\n\n.cropper-point.point-ne {\n cursor: nesw-resize;\n right: -3px;\n top: -3px;\n}\n\n.cropper-point.point-nw {\n cursor: nwse-resize;\n left: -3px;\n top: -3px;\n}\n\n.cropper-point.point-sw {\n bottom: -3px;\n cursor: nesw-resize;\n left: -3px;\n}\n\n.cropper-point.point-se {\n bottom: -3px;\n cursor: nwse-resize;\n height: 20px;\n opacity: 1;\n right: -3px;\n width: 20px;\n}\n\n@media (min-width: 768px) {\n .cropper-point.point-se {\n height: 15px;\n width: 15px;\n }\n}\n\n@media (min-width: 992px) {\n .cropper-point.point-se {\n height: 10px;\n width: 10px;\n }\n}\n\n@media (min-width: 1200px) {\n .cropper-point.point-se {\n height: 5px;\n opacity: .75;\n width: 5px;\n }\n}\n\n.cropper-point.point-se:before {\n background-color: #39f;\n bottom: -50%;\n content: ' ';\n display: block;\n height: 200%;\n opacity: 0;\n position: absolute;\n right: -50%;\n width: 200%;\n}\n\n.cropper-invisible {\n opacity: 0;\n}\n\n.cropper-bg {\n background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC');\n}\n\n.cropper-hide {\n display: block;\n height: 0;\n position: absolute;\n width: 0;\n}\n\n.cropper-hidden {\n display: none !important;\n}\n\n.cropper-move {\n cursor: move;\n}\n\n.cropper-crop {\n cursor: crosshair;\n}\n\n.cropper-disabled .cropper-drag-box,\n.cropper-disabled .cropper-face,\n.cropper-disabled .cropper-line,\n.cropper-disabled .cropper-point {\n cursor: not-allowed;\n}\n"],"sourceRoot":""} \ No newline at end of file diff --git a/priv/static/static/font/fontello.1587147224637.woff2 b/priv/static/static/font/fontello.1587147224637.woff2 deleted file mode 100644 index 6192c0f22..000000000 Binary files a/priv/static/static/font/fontello.1587147224637.woff2 and /dev/null differ diff --git a/priv/static/static/font/fontello.1587147224637.eot b/priv/static/static/font/fontello.1588344944597.eot similarity index 99% rename from priv/static/static/font/fontello.1587147224637.eot rename to priv/static/static/font/fontello.1588344944597.eot index 523e14f27..6b4850215 100644 Binary files a/priv/static/static/font/fontello.1587147224637.eot and b/priv/static/static/font/fontello.1588344944597.eot differ diff --git a/priv/static/static/font/fontello.1587147224637.svg b/priv/static/static/font/fontello.1588344944597.svg similarity index 100% rename from priv/static/static/font/fontello.1587147224637.svg rename to priv/static/static/font/fontello.1588344944597.svg diff --git a/priv/static/static/font/fontello.1587147224637.ttf b/priv/static/static/font/fontello.1588344944597.ttf similarity index 99% rename from priv/static/static/font/fontello.1587147224637.ttf rename to priv/static/static/font/fontello.1588344944597.ttf index ec6f7f9b4..b990cea9a 100644 Binary files a/priv/static/static/font/fontello.1587147224637.ttf and b/priv/static/static/font/fontello.1588344944597.ttf differ diff --git a/priv/static/static/font/fontello.1587147224637.woff b/priv/static/static/font/fontello.1588344944597.woff similarity index 98% rename from priv/static/static/font/fontello.1587147224637.woff rename to priv/static/static/font/fontello.1588344944597.woff index da56c9221..93d102c6f 100644 Binary files a/priv/static/static/font/fontello.1587147224637.woff and b/priv/static/static/font/fontello.1588344944597.woff differ diff --git a/priv/static/static/font/fontello.1588344944597.woff2 b/priv/static/static/font/fontello.1588344944597.woff2 new file mode 100644 index 000000000..bc4d4dada Binary files /dev/null and b/priv/static/static/font/fontello.1588344944597.woff2 differ diff --git a/priv/static/static/fontello.1587147224637.css b/priv/static/static/fontello.1588344944597.css similarity index 89% rename from priv/static/static/fontello.1587147224637.css rename to priv/static/static/fontello.1588344944597.css index 48e6a5b3c..000c1207a 100644 Binary files a/priv/static/static/fontello.1587147224637.css and b/priv/static/static/fontello.1588344944597.css differ diff --git a/priv/static/static/js/2.0bcc7512986083cd9ecf.js b/priv/static/static/js/2.0bcc7512986083cd9ecf.js new file mode 100644 index 000000000..680c9f82a Binary files /dev/null and b/priv/static/static/js/2.0bcc7512986083cd9ecf.js differ diff --git a/priv/static/static/js/2.0bcc7512986083cd9ecf.js.map b/priv/static/static/js/2.0bcc7512986083cd9ecf.js.map new file mode 100644 index 000000000..488843d6a Binary files /dev/null and b/priv/static/static/js/2.0bcc7512986083cd9ecf.js.map differ diff --git a/priv/static/static/js/2.f158cbd2b8770e467dfe.js b/priv/static/static/js/2.f158cbd2b8770e467dfe.js deleted file mode 100644 index 24f80fe7b..000000000 Binary files a/priv/static/static/js/2.f158cbd2b8770e467dfe.js and /dev/null differ diff --git a/priv/static/static/js/2.f158cbd2b8770e467dfe.js.map b/priv/static/static/js/2.f158cbd2b8770e467dfe.js.map deleted file mode 100644 index 94ca6f090..000000000 Binary files a/priv/static/static/js/2.f158cbd2b8770e467dfe.js.map and /dev/null differ diff --git a/priv/static/static/js/app.3de9191d7fd30b4bf68c.js b/priv/static/static/js/app.3de9191d7fd30b4bf68c.js new file mode 100644 index 000000000..d8d350a50 Binary files /dev/null and b/priv/static/static/js/app.3de9191d7fd30b4bf68c.js differ diff --git a/priv/static/static/js/app.3de9191d7fd30b4bf68c.js.map b/priv/static/static/js/app.3de9191d7fd30b4bf68c.js.map new file mode 100644 index 000000000..0643ca253 Binary files /dev/null and b/priv/static/static/js/app.3de9191d7fd30b4bf68c.js.map differ diff --git a/priv/static/static/js/app.def6476e8bc9b214218b.js b/priv/static/static/js/app.def6476e8bc9b214218b.js deleted file mode 100644 index 1e6ced42d..000000000 Binary files a/priv/static/static/js/app.def6476e8bc9b214218b.js and /dev/null differ diff --git a/priv/static/static/js/app.def6476e8bc9b214218b.js.map b/priv/static/static/js/app.def6476e8bc9b214218b.js.map deleted file mode 100644 index a03cad258..000000000 Binary files a/priv/static/static/js/app.def6476e8bc9b214218b.js.map and /dev/null differ diff --git a/priv/static/static/js/vendors~app.5b7c43d835cad9e56363.js b/priv/static/static/js/vendors~app.5b7c43d835cad9e56363.js new file mode 100644 index 000000000..0b69788e9 Binary files /dev/null and b/priv/static/static/js/vendors~app.5b7c43d835cad9e56363.js differ diff --git a/priv/static/static/js/vendors~app.5b7c43d835cad9e56363.js.map b/priv/static/static/js/vendors~app.5b7c43d835cad9e56363.js.map new file mode 100644 index 000000000..798010b89 Binary files /dev/null and b/priv/static/static/js/vendors~app.5b7c43d835cad9e56363.js.map differ diff --git a/priv/static/static/js/vendors~app.c5bbd3734647f0cc7eef.js b/priv/static/static/js/vendors~app.c5bbd3734647f0cc7eef.js deleted file mode 100644 index 8964180cd..000000000 Binary files a/priv/static/static/js/vendors~app.c5bbd3734647f0cc7eef.js and /dev/null differ diff --git a/priv/static/static/js/vendors~app.c5bbd3734647f0cc7eef.js.map b/priv/static/static/js/vendors~app.c5bbd3734647f0cc7eef.js.map deleted file mode 100644 index fab720d23..000000000 Binary files a/priv/static/static/js/vendors~app.c5bbd3734647f0cc7eef.js.map and /dev/null differ diff --git a/priv/static/static/static-fe.css b/priv/static/static/static-fe.css deleted file mode 100644 index db61ff266..000000000 Binary files a/priv/static/static/static-fe.css and /dev/null differ diff --git a/priv/static/sw-pleroma.js b/priv/static/sw-pleroma.js index 92361720e..d9c1e6285 100644 Binary files a/priv/static/sw-pleroma.js and b/priv/static/sw-pleroma.js differ diff --git a/priv/static/sw-pleroma.js.map b/priv/static/sw-pleroma.js.map index 5d9874693..c704cb951 100644 Binary files a/priv/static/sw-pleroma.js.map and b/priv/static/sw-pleroma.js.map differ diff --git a/test/captcha_test.exs b/test/captcha_test.exs index ac1d846e8..1ab9019ab 100644 --- a/test/captcha_test.exs +++ b/test/captcha_test.exs @@ -61,7 +61,7 @@ test "new and validate" do assert is_binary(answer) assert :ok = Native.validate(token, answer, answer) - assert {:error, "Invalid CAPTCHA"} == Native.validate(token, answer, answer <> "foobar") + assert {:error, :invalid} == Native.validate(token, answer, answer <> "foobar") end end @@ -78,6 +78,7 @@ test "validate" do assert is_binary(answer) assert :ok = Captcha.validate(token, "63615261b77f5354fb8c4e4986477555", answer) + Cachex.del(:used_captcha_cache, token) end test "doesn't validate invalid answer" do @@ -92,7 +93,7 @@ test "doesn't validate invalid answer" do assert is_binary(answer) - assert {:error, "Invalid answer data"} = + assert {:error, :invalid_answer_data} = Captcha.validate(token, "63615261b77f5354fb8c4e4986477555", answer <> "foobar") end @@ -108,7 +109,7 @@ test "nil answer_data" do assert is_binary(answer) - assert {:error, "Invalid answer data"} = + assert {:error, :invalid_answer_data} = Captcha.validate(token, "63615261b77f5354fb8c4e4986477555", nil) end end diff --git a/test/fixtures/tesla_mock/craigmaloney.json b/test/fixtures/tesla_mock/craigmaloney.json new file mode 100644 index 000000000..56ea9c7c3 --- /dev/null +++ b/test/fixtures/tesla_mock/craigmaloney.json @@ -0,0 +1,112 @@ +{ + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "CacheFile": "pt:CacheFile", + "Hashtag": "as:Hashtag", + "Infohash": "pt:Infohash", + "RsaSignature2017": "https://w3id.org/security#RsaSignature2017", + "category": "sc:category", + "commentsEnabled": { + "@id": "pt:commentsEnabled", + "@type": "sc:Boolean" + }, + "downloadEnabled": { + "@id": "pt:downloadEnabled", + "@type": "sc:Boolean" + }, + "expires": "sc:expires", + "fps": { + "@id": "pt:fps", + "@type": "sc:Number" + }, + "language": "sc:inLanguage", + "licence": "sc:license", + "originallyPublishedAt": "sc:datePublished", + "position": { + "@id": "pt:position", + "@type": "sc:Number" + }, + "pt": "https://joinpeertube.org/ns#", + "sc": "http://schema.org#", + "sensitive": "as:sensitive", + "size": { + "@id": "pt:size", + "@type": "sc:Number" + }, + "startTimestamp": { + "@id": "pt:startTimestamp", + "@type": "sc:Number" + }, + "state": { + "@id": "pt:state", + "@type": "sc:Number" + }, + "stopTimestamp": { + "@id": "pt:stopTimestamp", + "@type": "sc:Number" + }, + "subtitleLanguage": "sc:subtitleLanguage", + "support": { + "@id": "pt:support", + "@type": "sc:Text" + }, + "uuid": "sc:identifier", + "views": { + "@id": "pt:views", + "@type": "sc:Number" + }, + "waitTranscoding": { + "@id": "pt:waitTranscoding", + "@type": "sc:Boolean" + } + }, + { + "comments": { + "@id": "as:comments", + "@type": "@id" + }, + "dislikes": { + "@id": "as:dislikes", + "@type": "@id" + }, + "likes": { + "@id": "as:likes", + "@type": "@id" + }, + "playlists": { + "@id": "pt:playlists", + "@type": "@id" + }, + "shares": { + "@id": "as:shares", + "@type": "@id" + } + } + ], + "endpoints": { + "sharedInbox": "https://peertube.social/inbox" + }, + "followers": "https://peertube.social/accounts/craigmaloney/followers", + "following": "https://peertube.social/accounts/craigmaloney/following", + "icon": { + "mediaType": "image/png", + "type": "Image", + "url": "https://peertube.social/lazy-static/avatars/87bd694b-95bc-4066-83f4-bddfcd2b9caa.png" + }, + "id": "https://peertube.social/accounts/craigmaloney", + "inbox": "https://peertube.social/accounts/craigmaloney/inbox", + "name": "Craig Maloney", + "outbox": "https://peertube.social/accounts/craigmaloney/outbox", + "playlists": "https://peertube.social/accounts/craigmaloney/playlists", + "preferredUsername": "craigmaloney", + "publicKey": { + "id": "https://peertube.social/accounts/craigmaloney#main-key", + "owner": "https://peertube.social/accounts/craigmaloney", + "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9qvGIYUW01yc8CCsrwxK\n5OXlV5s7EbNWY8tJr/p1oGuELZwAnG2XKxtdbvgcCT+YxL5uRXIdCFIIIKrzRFr/\nHfS0mOgNT9u3gu+SstCNgtatciT0RVP77yiC3b2NHq1NRRvvVhzQb4cpIWObIxqh\nb2ypDClTc7XaKtgmQCbwZlGyZMT+EKz/vustD6BlpGsglRkm7iES6s1PPGb1BU+n\nS94KhbS2DOFiLcXCVWt0QarokIIuKznp4+xP1axKyP+SkT5AHx08Nd5TYFb2C1Jl\nz0WD/1q0mAN62m7QrA3SQPUgB+wWD+S3Nzf7FwNPiP4srbBgxVEUnji/r9mQ6BXC\nrQIDAQAB\n-----END PUBLIC KEY-----" + }, + "summary": null, + "type": "Person", + "url": "https://peertube.social/accounts/craigmaloney" +} diff --git a/test/fixtures/tesla_mock/peertube-social.json b/test/fixtures/tesla_mock/peertube-social.json new file mode 100644 index 000000000..0e996ba35 --- /dev/null +++ b/test/fixtures/tesla_mock/peertube-social.json @@ -0,0 +1,234 @@ +{ + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "CacheFile": "pt:CacheFile", + "Hashtag": "as:Hashtag", + "Infohash": "pt:Infohash", + "RsaSignature2017": "https://w3id.org/security#RsaSignature2017", + "category": "sc:category", + "commentsEnabled": { + "@id": "pt:commentsEnabled", + "@type": "sc:Boolean" + }, + "downloadEnabled": { + "@id": "pt:downloadEnabled", + "@type": "sc:Boolean" + }, + "expires": "sc:expires", + "fps": { + "@id": "pt:fps", + "@type": "sc:Number" + }, + "language": "sc:inLanguage", + "licence": "sc:license", + "originallyPublishedAt": "sc:datePublished", + "position": { + "@id": "pt:position", + "@type": "sc:Number" + }, + "pt": "https://joinpeertube.org/ns#", + "sc": "http://schema.org#", + "sensitive": "as:sensitive", + "size": { + "@id": "pt:size", + "@type": "sc:Number" + }, + "startTimestamp": { + "@id": "pt:startTimestamp", + "@type": "sc:Number" + }, + "state": { + "@id": "pt:state", + "@type": "sc:Number" + }, + "stopTimestamp": { + "@id": "pt:stopTimestamp", + "@type": "sc:Number" + }, + "subtitleLanguage": "sc:subtitleLanguage", + "support": { + "@id": "pt:support", + "@type": "sc:Text" + }, + "uuid": "sc:identifier", + "views": { + "@id": "pt:views", + "@type": "sc:Number" + }, + "waitTranscoding": { + "@id": "pt:waitTranscoding", + "@type": "sc:Boolean" + } + }, + { + "comments": { + "@id": "as:comments", + "@type": "@id" + }, + "dislikes": { + "@id": "as:dislikes", + "@type": "@id" + }, + "likes": { + "@id": "as:likes", + "@type": "@id" + }, + "playlists": { + "@id": "pt:playlists", + "@type": "@id" + }, + "shares": { + "@id": "as:shares", + "@type": "@id" + } + } + ], + "attributedTo": [ + { + "id": "https://peertube.social/accounts/craigmaloney", + "type": "Person" + }, + { + "id": "https://peertube.social/video-channels/9909c7d9-6b5b-4aae-9164-c1af7229c91c", + "type": "Group" + } + ], + "category": { + "identifier": "15", + "name": "Science & Technology" + }, + "cc": [ + "https://peertube.social/accounts/craigmaloney/followers" + ], + "comments": "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe/comments", + "commentsEnabled": true, + "content": "Support this and our other Michigan!/usr/group videos and meetings. Learn more at http://mug.org/membership\n\nTwenty Years in Jail: FreeBSD's Jails, Then and Now\n\nJails started as a limited virtualization system, but over the last two years they've...", + "dislikes": "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe/dislikes", + "downloadEnabled": true, + "duration": "PT5151S", + "icon": { + "height": 122, + "mediaType": "image/jpeg", + "type": "Image", + "url": "https://peertube.social/static/thumbnails/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe.jpg", + "width": 223 + }, + "id": "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe", + "language": { + "identifier": "en", + "name": "English" + }, + "licence": { + "identifier": "1", + "name": "Attribution" + }, + "likes": "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe/likes", + "mediaType": "text/markdown", + "name": "Twenty Years in Jail: FreeBSD's Jails, Then and Now", + "originallyPublishedAt": "2019-08-13T00:00:00.000Z", + "published": "2020-02-12T01:06:08.054Z", + "sensitive": false, + "shares": "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe/announces", + "state": 1, + "subtitleLanguage": [], + "support": "Learn more at http://mug.org", + "tag": [ + { + "name": "linux", + "type": "Hashtag" + }, + { + "name": "mug.org", + "type": "Hashtag" + }, + { + "name": "open", + "type": "Hashtag" + }, + { + "name": "oss", + "type": "Hashtag" + }, + { + "name": "source", + "type": "Hashtag" + } + ], + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "type": "Video", + "updated": "2020-02-15T15:01:09.474Z", + "url": [ + { + "href": "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe", + "mediaType": "text/html", + "type": "Link" + }, + { + "fps": 30, + "height": 240, + "href": "https://peertube.social/static/webseed/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe-240.mp4", + "mediaType": "video/mp4", + "size": 119465800, + "type": "Link" + }, + { + "height": 240, + "href": "https://peertube.social/static/torrents/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe-240.torrent", + "mediaType": "application/x-bittorrent", + "type": "Link" + }, + { + "height": 240, + "href": "magnet:?xs=https%3A%2F%2Fpeertube.social%2Fstatic%2Ftorrents%2F278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe-240.torrent&xt=urn:btih:b3365331a8543bf48d09add56d7fe4b1cbbb5659&dn=Twenty+Years+in+Jail%3A+FreeBSD's+Jails%2C+Then+and+Now&tr=wss%3A%2F%2Fpeertube.social%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fpeertube.social%2Ftracker%2Fannounce&ws=https%3A%2F%2Fpeertube.social%2Fstatic%2Fwebseed%2F278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe-240.mp4", + "mediaType": "application/x-bittorrent;x-scheme-handler/magnet", + "type": "Link" + }, + { + "fps": 30, + "height": 360, + "href": "https://peertube.social/static/webseed/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe-360.mp4", + "mediaType": "video/mp4", + "size": 143930318, + "type": "Link" + }, + { + "height": 360, + "href": "https://peertube.social/static/torrents/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe-360.torrent", + "mediaType": "application/x-bittorrent", + "type": "Link" + }, + { + "height": 360, + "href": "magnet:?xs=https%3A%2F%2Fpeertube.social%2Fstatic%2Ftorrents%2F278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe-360.torrent&xt=urn:btih:0d37b23c98cb0d89e28b5dc8f49b3c97a041e569&dn=Twenty+Years+in+Jail%3A+FreeBSD's+Jails%2C+Then+and+Now&tr=wss%3A%2F%2Fpeertube.social%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fpeertube.social%2Ftracker%2Fannounce&ws=https%3A%2F%2Fpeertube.social%2Fstatic%2Fwebseed%2F278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe-360.mp4", + "mediaType": "application/x-bittorrent;x-scheme-handler/magnet", + "type": "Link" + }, + { + "fps": 30, + "height": 480, + "href": "https://peertube.social/static/webseed/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe-480.mp4", + "mediaType": "video/mp4", + "size": 130530754, + "type": "Link" + }, + { + "height": 480, + "href": "https://peertube.social/static/torrents/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe-480.torrent", + "mediaType": "application/x-bittorrent", + "type": "Link" + }, + { + "height": 480, + "href": "magnet:?xs=https%3A%2F%2Fpeertube.social%2Fstatic%2Ftorrents%2F278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe-480.torrent&xt=urn:btih:3a13ff822ad9494165eff6167183ddaaabc1372a&dn=Twenty+Years+in+Jail%3A+FreeBSD's+Jails%2C+Then+and+Now&tr=wss%3A%2F%2Fpeertube.social%3A443%2Ftracker%2Fsocket&tr=https%3A%2F%2Fpeertube.social%2Ftracker%2Fannounce&ws=https%3A%2F%2Fpeertube.social%2Fstatic%2Fwebseed%2F278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe-480.mp4", + "mediaType": "application/x-bittorrent;x-scheme-handler/magnet", + "type": "Link" + } + ], + "uuid": "278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe", + "views": 2, + "waitTranscoding": false +} diff --git a/test/signature_test.exs b/test/signature_test.exs index d5a2a62c4..a7a75aa4d 100644 --- a/test/signature_test.exs +++ b/test/signature_test.exs @@ -44,7 +44,8 @@ test "it returns key" do test "it returns error when not found user" do assert capture_log(fn -> - assert Signature.fetch_public_key(make_fake_conn("test-ap_id")) == {:error, :error} + assert Signature.fetch_public_key(make_fake_conn("https://test-ap-id")) == + {:error, :error} end) =~ "[error] Could not decode user" end @@ -64,7 +65,7 @@ test "it returns key" do test "it returns error when not found user" do assert capture_log(fn -> - {:error, _} = Signature.refetch_public_key(make_fake_conn("test-ap_id")) + {:error, _} = Signature.refetch_public_key(make_fake_conn("https://test-ap_id")) end) =~ "[error] Could not decode user" end end @@ -100,12 +101,21 @@ test "it returns error" do describe "key_id_to_actor_id/1" do test "it properly deduces the actor id for misskey" do assert Signature.key_id_to_actor_id("https://example.com/users/1234/publickey") == - "https://example.com/users/1234" + {:ok, "https://example.com/users/1234"} end test "it properly deduces the actor id for mastodon and pleroma" do assert Signature.key_id_to_actor_id("https://example.com/users/1234#main-key") == - "https://example.com/users/1234" + {:ok, "https://example.com/users/1234"} + end + + test "it calls webfinger for 'acct:' accounts" do + with_mock(Pleroma.Web.WebFinger, + finger: fn _ -> %{"ap_id" => "https://gensokyo.2hu/users/raymoo"} end + ) do + assert Signature.key_id_to_actor_id("acct:raymoo@gensokyo.2hu") == + {:ok, "https://gensokyo.2hu/users/raymoo"} + end end end diff --git a/test/support/captcha_mock.ex b/test/support/captcha_mock.ex index 6dae94edf..7b0c1d5af 100644 --- a/test/support/captcha_mock.ex +++ b/test/support/captcha_mock.ex @@ -6,12 +6,16 @@ defmodule Pleroma.Captcha.Mock do alias Pleroma.Captcha.Service @behaviour Service + @solution "63615261b77f5354fb8c4e4986477555" + + def solution, do: @solution + @impl Service def new, do: %{ type: :mock, token: "afa1815e14e29355e6c8f6b143a39fa2", - answer_data: "63615261b77f5354fb8c4e4986477555", + answer_data: @solution, url: "https://example.org/captcha.png" } diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex index 20cb2b3d1..9624cb0f7 100644 --- a/test/support/http_request_mock.ex +++ b/test/support/http_request_mock.ex @@ -308,6 +308,22 @@ def get("https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3" }} end + def get("https://peertube.social/accounts/craigmaloney", _, _, _) do + {:ok, + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/tesla_mock/craigmaloney.json") + }} + end + + def get("https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe", _, _, _) do + {:ok, + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/tesla_mock/peertube-social.json") + }} + end + def get("https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39", _, _, [ {"accept", "application/activity+json"} ]) do diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index 6057e360a..10d86ee45 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -1221,6 +1221,35 @@ test "it rejects activities without a valid ID" do :error = Transmogrifier.handle_incoming(data) end + test "skip converting the content when it is nil" do + object_id = "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe" + + {:ok, object} = Fetcher.fetch_and_contain_remote_object_from_id(object_id) + + result = + Pleroma.Web.ActivityPub.Transmogrifier.fix_object(Map.merge(object, %{"content" => nil})) + + assert result["content"] == nil + end + + test "it converts content of object to html" do + object_id = "https://peertube.social/videos/watch/278d2b7c-0f38-4aaa-afe6-9ecc0c4a34fe" + + {:ok, %{"content" => content_markdown}} = + Fetcher.fetch_and_contain_remote_object_from_id(object_id) + + {:ok, %Pleroma.Object{data: %{"content" => content}} = object} = + Fetcher.fetch_object_from_id(object_id) + + assert content_markdown == + "Support this and our other Michigan!/usr/group videos and meetings. Learn more at http://mug.org/membership\n\nTwenty Years in Jail: FreeBSD's Jails, Then and Now\n\nJails started as a limited virtualization system, but over the last two years they've..." + + assert content == + "

Support this and our other Michigan!/usr/group videos and meetings. Learn more at http://mug.org/membership

Twenty Years in Jail: FreeBSD’s Jails, Then and Now

Jails started as a limited virtualization system, but over the last two years they’ve…

" + + assert object.data["mediaType"] == "text/html" + end + test "it remaps video URLs as attachments if necessary" do {:ok, object} = Fetcher.fetch_object_from_id( diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs index f80dbf8dd..1862a9589 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -1347,9 +1347,9 @@ test "returns report by its id", %{conn: conn} do {:ok, %{id: report_id}} = CommonAPI.report(reporter, %{ - "account_id" => target_user.id, - "comment" => "I feel offended", - "status_ids" => [activity.id] + account_id: target_user.id, + comment: "I feel offended", + status_ids: [activity.id] }) response = @@ -1374,16 +1374,16 @@ test "returns 404 when report id is invalid", %{conn: conn} do {:ok, %{id: report_id}} = CommonAPI.report(reporter, %{ - "account_id" => target_user.id, - "comment" => "I feel offended", - "status_ids" => [activity.id] + account_id: target_user.id, + comment: "I feel offended", + status_ids: [activity.id] }) {:ok, %{id: second_report_id}} = CommonAPI.report(reporter, %{ - "account_id" => target_user.id, - "comment" => "I feel very offended", - "status_ids" => [activity.id] + account_id: target_user.id, + comment: "I feel very offended", + status_ids: [activity.id] }) %{ @@ -1523,9 +1523,9 @@ test "returns reports", %{conn: conn} do {:ok, %{id: report_id}} = CommonAPI.report(reporter, %{ - "account_id" => target_user.id, - "comment" => "I feel offended", - "status_ids" => [activity.id] + account_id: target_user.id, + comment: "I feel offended", + status_ids: [activity.id] }) response = @@ -1547,15 +1547,15 @@ test "returns reports with specified state", %{conn: conn} do {:ok, %{id: first_report_id}} = CommonAPI.report(reporter, %{ - "account_id" => target_user.id, - "comment" => "I feel offended", - "status_ids" => [activity.id] + account_id: target_user.id, + comment: "I feel offended", + status_ids: [activity.id] }) {:ok, %{id: second_report_id}} = CommonAPI.report(reporter, %{ - "account_id" => target_user.id, - "comment" => "I don't like this user" + account_id: target_user.id, + comment: "I don't like this user" }) CommonAPI.update_report_state(second_report_id, "closed") @@ -3431,9 +3431,9 @@ test "it resend emails for two users", %{conn: conn, admin: admin} do {:ok, %{id: report_id}} = CommonAPI.report(reporter, %{ - "account_id" => target_user.id, - "comment" => "I feel offended", - "status_ids" => [activity.id] + account_id: target_user.id, + comment: "I feel offended", + status_ids: [activity.id] }) post(conn, "/api/pleroma/admin/reports/#{report_id}/notes", %{ diff --git a/test/web/admin_api/views/report_view_test.exs b/test/web/admin_api/views/report_view_test.exs index 5db6629f2..8cfa1dcfa 100644 --- a/test/web/admin_api/views/report_view_test.exs +++ b/test/web/admin_api/views/report_view_test.exs @@ -15,7 +15,7 @@ test "renders a report" do user = insert(:user) other_user = insert(:user) - {:ok, activity} = CommonAPI.report(user, %{"account_id" => other_user.id}) + {:ok, activity} = CommonAPI.report(user, %{account_id: other_user.id}) expected = %{ content: nil, @@ -48,7 +48,7 @@ test "includes reported statuses" do {:ok, activity} = CommonAPI.post(other_user, %{"status" => "toot"}) {:ok, report_activity} = - CommonAPI.report(user, %{"account_id" => other_user.id, "status_ids" => [activity.id]}) + CommonAPI.report(user, %{account_id: other_user.id, status_ids: [activity.id]}) other_user = Pleroma.User.get_by_id(other_user.id) @@ -81,7 +81,7 @@ test "renders report's state" do user = insert(:user) other_user = insert(:user) - {:ok, activity} = CommonAPI.report(user, %{"account_id" => other_user.id}) + {:ok, activity} = CommonAPI.report(user, %{account_id: other_user.id}) {:ok, activity} = CommonAPI.update_report_state(activity.id, "closed") assert %{state: "closed"} = @@ -94,8 +94,8 @@ test "renders report description" do {:ok, activity} = CommonAPI.report(user, %{ - "account_id" => other_user.id, - "comment" => "posts are too good for this instance" + account_id: other_user.id, + comment: "posts are too good for this instance" }) assert %{content: "posts are too good for this instance"} = @@ -108,8 +108,8 @@ test "sanitizes report description" do {:ok, activity} = CommonAPI.report(user, %{ - "account_id" => other_user.id, - "comment" => "" + account_id: other_user.id, + comment: "" }) data = Map.put(activity.data, "content", "") @@ -125,8 +125,8 @@ test "doesn't error out when the user doesn't exists" do {:ok, activity} = CommonAPI.report(user, %{ - "account_id" => other_user.id, - "comment" => "" + account_id: other_user.id, + comment: "" }) Pleroma.User.delete(other_user) diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index 1758662b0..c6ccc02c4 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -485,9 +485,9 @@ test "creates a report" do comment = "foobar" report_data = %{ - "account_id" => target_user.id, - "comment" => comment, - "status_ids" => [activity.id] + account_id: target_user.id, + comment: comment, + status_ids: [activity.id] } note_obj = %{ @@ -517,9 +517,9 @@ test "updates report state" do {:ok, %Activity{id: report_id}} = CommonAPI.report(reporter, %{ - "account_id" => target_user.id, - "comment" => "I feel offended", - "status_ids" => [activity.id] + account_id: target_user.id, + comment: "I feel offended", + status_ids: [activity.id] }) {:ok, report} = CommonAPI.update_report_state(report_id, "resolved") @@ -538,9 +538,9 @@ test "does not update report state when state is unsupported" do {:ok, %Activity{id: report_id}} = CommonAPI.report(reporter, %{ - "account_id" => target_user.id, - "comment" => "I feel offended", - "status_ids" => [activity.id] + account_id: target_user.id, + comment: "I feel offended", + status_ids: [activity.id] }) assert CommonAPI.update_report_state(report_id, "test") == {:error, "Unsupported state"} @@ -552,16 +552,16 @@ test "updates state of multiple reports" do {:ok, %Activity{id: first_report_id}} = CommonAPI.report(reporter, %{ - "account_id" => target_user.id, - "comment" => "I feel offended", - "status_ids" => [activity.id] + account_id: target_user.id, + comment: "I feel offended", + status_ids: [activity.id] }) {:ok, %Activity{id: second_report_id}} = CommonAPI.report(reporter, %{ - "account_id" => target_user.id, - "comment" => "I feel very offended!", - "status_ids" => [activity.id] + account_id: target_user.id, + comment: "I feel very offended!", + status_ids: [activity.id] }) {:ok, report_ids} = diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs index ba70ba66c..b9da7e924 100644 --- a/test/web/mastodon_api/controllers/account_controller_test.exs +++ b/test/web/mastodon_api/controllers/account_controller_test.exs @@ -925,7 +925,8 @@ test "returns bad_request if missing email params when :account_activation_requi |> Map.put(:remote_ip, {127, 0, 0, 5}) |> post("/api/v1/accounts", Map.delete(valid_params, :email)) - assert json_response_and_validate_schema(res, 400) == %{"error" => "Missing parameters"} + assert json_response_and_validate_schema(res, 400) == + %{"error" => "Missing parameter: email"} res = conn @@ -1093,6 +1094,91 @@ test "respects rate limit setting", %{conn: conn} do end end + describe "create account with enabled captcha" do + setup %{conn: conn} do + app_token = insert(:oauth_token, user: nil) + + conn = + conn + |> put_req_header("authorization", "Bearer " <> app_token.token) + |> put_req_header("content-type", "multipart/form-data") + + [conn: conn] + end + + setup do: clear_config([Pleroma.Captcha, :enabled], true) + + test "creates an account and returns 200 if captcha is valid", %{conn: conn} do + %{token: token, answer_data: answer_data} = Pleroma.Captcha.new() + + params = %{ + username: "lain", + email: "lain@example.org", + password: "PlzDontHackLain", + agreement: true, + captcha_solution: Pleroma.Captcha.Mock.solution(), + captcha_token: token, + captcha_answer_data: answer_data + } + + assert %{ + "access_token" => access_token, + "created_at" => _, + "scope" => ["read"], + "token_type" => "Bearer" + } = + conn + |> post("/api/v1/accounts", params) + |> json_response_and_validate_schema(:ok) + + assert Token |> Repo.get_by(token: access_token) |> Repo.preload(:user) |> Map.get(:user) + + Cachex.del(:used_captcha_cache, token) + end + + test "returns 400 if any captcha field is not provided", %{conn: conn} do + captcha_fields = [:captcha_solution, :captcha_token, :captcha_answer_data] + + valid_params = %{ + username: "lain", + email: "lain@example.org", + password: "PlzDontHackLain", + agreement: true, + captcha_solution: "xx", + captcha_token: "xx", + captcha_answer_data: "xx" + } + + for field <- captcha_fields do + expected = %{ + "error" => "{\"captcha\":[\"Invalid CAPTCHA (Missing parameter: #{field})\"]}" + } + + assert expected == + conn + |> post("/api/v1/accounts", Map.delete(valid_params, field)) + |> json_response_and_validate_schema(:bad_request) + end + end + + test "returns an error if captcha is invalid", %{conn: conn} do + params = %{ + username: "lain", + email: "lain@example.org", + password: "PlzDontHackLain", + agreement: true, + captcha_solution: "cofe", + captcha_token: "cofe", + captcha_answer_data: "cofe" + } + + assert %{"error" => "{\"captcha\":[\"Invalid answer data\"]}"} == + conn + |> post("/api/v1/accounts", params) + |> json_response_and_validate_schema(:bad_request) + end + end + describe "GET /api/v1/accounts/:id/lists - account_lists" do test "returns lists to which the account belongs" do %{user: user, conn: conn} = oauth_access(["read:lists"]) diff --git a/test/web/mastodon_api/controllers/notification_controller_test.exs b/test/web/mastodon_api/controllers/notification_controller_test.exs index 8c815b415..db380f76a 100644 --- a/test/web/mastodon_api/controllers/notification_controller_test.exs +++ b/test/web/mastodon_api/controllers/notification_controller_test.exs @@ -25,7 +25,7 @@ test "does NOT render account/pleroma/relationship if this is disabled by defaul conn |> assign(:user, user) |> get("/api/v1/notifications") - |> json_response(200) + |> json_response_and_validate_schema(200) assert Enum.all?(response, fn n -> get_in(n, ["account", "pleroma", "relationship"]) == %{} @@ -50,7 +50,9 @@ test "list of notifications" do user.ap_id }\" rel=\"ugc\">@#{user.nickname}" - assert [%{"status" => %{"content" => response}} | _rest] = json_response(conn, 200) + assert [%{"status" => %{"content" => response}} | _rest] = + json_response_and_validate_schema(conn, 200) + assert response == expected_response end @@ -69,7 +71,7 @@ test "getting a single notification" do user.ap_id }\" rel=\"ugc\">@#{user.nickname}" - assert %{"status" => %{"content" => response}} = json_response(conn, 200) + assert %{"status" => %{"content" => response}} = json_response_and_validate_schema(conn, 200) assert response == expected_response end @@ -84,9 +86,10 @@ test "dismissing a single notification (deprecated endpoint)" do conn = conn |> assign(:user, user) - |> post("/api/v1/notifications/dismiss", %{"id" => notification.id}) + |> put_req_header("content-type", "application/json") + |> post("/api/v1/notifications/dismiss", %{"id" => to_string(notification.id)}) - assert %{} = json_response(conn, 200) + assert %{} = json_response_and_validate_schema(conn, 200) end test "dismissing a single notification" do @@ -102,7 +105,7 @@ test "dismissing a single notification" do |> assign(:user, user) |> post("/api/v1/notifications/#{notification.id}/dismiss") - assert %{} = json_response(conn, 200) + assert %{} = json_response_and_validate_schema(conn, 200) end test "clearing all notifications" do @@ -115,11 +118,11 @@ test "clearing all notifications" do ret_conn = post(conn, "/api/v1/notifications/clear") - assert %{} = json_response(ret_conn, 200) + assert %{} = json_response_and_validate_schema(ret_conn, 200) ret_conn = get(conn, "/api/v1/notifications") - assert all = json_response(ret_conn, 200) + assert all = json_response_and_validate_schema(ret_conn, 200) assert all == [] end @@ -143,7 +146,7 @@ test "paginates notifications using min_id, since_id, max_id, and limit" do result = conn |> get("/api/v1/notifications?limit=2&min_id=#{notification1_id}") - |> json_response(:ok) + |> json_response_and_validate_schema(:ok) assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result @@ -151,7 +154,7 @@ test "paginates notifications using min_id, since_id, max_id, and limit" do result = conn |> get("/api/v1/notifications?limit=2&since_id=#{notification1_id}") - |> json_response(:ok) + |> json_response_and_validate_schema(:ok) assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result @@ -159,7 +162,7 @@ test "paginates notifications using min_id, since_id, max_id, and limit" do result = conn |> get("/api/v1/notifications?limit=2&max_id=#{notification4_id}") - |> json_response(:ok) + |> json_response_and_validate_schema(:ok) assert [%{"id" => ^notification3_id}, %{"id" => ^notification2_id}] = result end @@ -181,36 +184,28 @@ test "filters notifications for mentions" do {:ok, private_activity} = CommonAPI.post(other_user, %{"status" => "@#{user.nickname}", "visibility" => "private"}) - conn_res = - get(conn, "/api/v1/notifications", %{ - exclude_visibilities: ["public", "unlisted", "private"] - }) + query = params_to_query(%{exclude_visibilities: ["public", "unlisted", "private"]}) + conn_res = get(conn, "/api/v1/notifications?" <> query) - assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200) + assert [%{"status" => %{"id" => id}}] = json_response_and_validate_schema(conn_res, 200) assert id == direct_activity.id - conn_res = - get(conn, "/api/v1/notifications", %{ - exclude_visibilities: ["public", "unlisted", "direct"] - }) + query = params_to_query(%{exclude_visibilities: ["public", "unlisted", "direct"]}) + conn_res = get(conn, "/api/v1/notifications?" <> query) - assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200) + assert [%{"status" => %{"id" => id}}] = json_response_and_validate_schema(conn_res, 200) assert id == private_activity.id - conn_res = - get(conn, "/api/v1/notifications", %{ - exclude_visibilities: ["public", "private", "direct"] - }) + query = params_to_query(%{exclude_visibilities: ["public", "private", "direct"]}) + conn_res = get(conn, "/api/v1/notifications?" <> query) - assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200) + assert [%{"status" => %{"id" => id}}] = json_response_and_validate_schema(conn_res, 200) assert id == unlisted_activity.id - conn_res = - get(conn, "/api/v1/notifications", %{ - exclude_visibilities: ["unlisted", "private", "direct"] - }) + query = params_to_query(%{exclude_visibilities: ["unlisted", "private", "direct"]}) + conn_res = get(conn, "/api/v1/notifications?" <> query) - assert [%{"status" => %{"id" => id}}] = json_response(conn_res, 200) + assert [%{"status" => %{"id" => id}}] = json_response_and_validate_schema(conn_res, 200) assert id == public_activity.id end @@ -237,8 +232,8 @@ test "filters notifications for Like activities" do activity_ids = conn - |> get("/api/v1/notifications", %{exclude_visibilities: ["direct"]}) - |> json_response(200) + |> get("/api/v1/notifications?exclude_visibilities[]=direct") + |> json_response_and_validate_schema(200) |> Enum.map(& &1["status"]["id"]) assert public_activity.id in activity_ids @@ -248,8 +243,8 @@ test "filters notifications for Like activities" do activity_ids = conn - |> get("/api/v1/notifications", %{exclude_visibilities: ["unlisted"]}) - |> json_response(200) + |> get("/api/v1/notifications?exclude_visibilities[]=unlisted") + |> json_response_and_validate_schema(200) |> Enum.map(& &1["status"]["id"]) assert public_activity.id in activity_ids @@ -259,8 +254,8 @@ test "filters notifications for Like activities" do activity_ids = conn - |> get("/api/v1/notifications", %{exclude_visibilities: ["private"]}) - |> json_response(200) + |> get("/api/v1/notifications?exclude_visibilities[]=private") + |> json_response_and_validate_schema(200) |> Enum.map(& &1["status"]["id"]) assert public_activity.id in activity_ids @@ -270,8 +265,8 @@ test "filters notifications for Like activities" do activity_ids = conn - |> get("/api/v1/notifications", %{exclude_visibilities: ["public"]}) - |> json_response(200) + |> get("/api/v1/notifications?exclude_visibilities[]=public") + |> json_response_and_validate_schema(200) |> Enum.map(& &1["status"]["id"]) refute public_activity.id in activity_ids @@ -295,8 +290,8 @@ test "filters notifications for Announce activities" do activity_ids = conn - |> get("/api/v1/notifications", %{exclude_visibilities: ["unlisted"]}) - |> json_response(200) + |> get("/api/v1/notifications?exclude_visibilities[]=unlisted") + |> json_response_and_validate_schema(200) |> Enum.map(& &1["status"]["id"]) assert public_activity.id in activity_ids @@ -319,25 +314,27 @@ test "filters notifications using exclude_types" do reblog_notification_id = get_notification_id_by_activity(reblog_activity) follow_notification_id = get_notification_id_by_activity(follow_activity) - conn_res = - get(conn, "/api/v1/notifications", %{exclude_types: ["mention", "favourite", "reblog"]}) + query = params_to_query(%{exclude_types: ["mention", "favourite", "reblog"]}) + conn_res = get(conn, "/api/v1/notifications?" <> query) - assert [%{"id" => ^follow_notification_id}] = json_response(conn_res, 200) + assert [%{"id" => ^follow_notification_id}] = json_response_and_validate_schema(conn_res, 200) - conn_res = - get(conn, "/api/v1/notifications", %{exclude_types: ["favourite", "reblog", "follow"]}) + query = params_to_query(%{exclude_types: ["favourite", "reblog", "follow"]}) + conn_res = get(conn, "/api/v1/notifications?" <> query) - assert [%{"id" => ^mention_notification_id}] = json_response(conn_res, 200) + assert [%{"id" => ^mention_notification_id}] = + json_response_and_validate_schema(conn_res, 200) - conn_res = - get(conn, "/api/v1/notifications", %{exclude_types: ["reblog", "follow", "mention"]}) + query = params_to_query(%{exclude_types: ["reblog", "follow", "mention"]}) + conn_res = get(conn, "/api/v1/notifications?" <> query) - assert [%{"id" => ^favorite_notification_id}] = json_response(conn_res, 200) + assert [%{"id" => ^favorite_notification_id}] = + json_response_and_validate_schema(conn_res, 200) - conn_res = - get(conn, "/api/v1/notifications", %{exclude_types: ["follow", "mention", "favourite"]}) + query = params_to_query(%{exclude_types: ["follow", "mention", "favourite"]}) + conn_res = get(conn, "/api/v1/notifications?" <> query) - assert [%{"id" => ^reblog_notification_id}] = json_response(conn_res, 200) + assert [%{"id" => ^reblog_notification_id}] = json_response_and_validate_schema(conn_res, 200) end test "filters notifications using include_types" do @@ -355,32 +352,34 @@ test "filters notifications using include_types" do reblog_notification_id = get_notification_id_by_activity(reblog_activity) follow_notification_id = get_notification_id_by_activity(follow_activity) - conn_res = get(conn, "/api/v1/notifications", %{include_types: ["follow"]}) + conn_res = get(conn, "/api/v1/notifications?include_types[]=follow") - assert [%{"id" => ^follow_notification_id}] = json_response(conn_res, 200) + assert [%{"id" => ^follow_notification_id}] = json_response_and_validate_schema(conn_res, 200) - conn_res = get(conn, "/api/v1/notifications", %{include_types: ["mention"]}) + conn_res = get(conn, "/api/v1/notifications?include_types[]=mention") - assert [%{"id" => ^mention_notification_id}] = json_response(conn_res, 200) + assert [%{"id" => ^mention_notification_id}] = + json_response_and_validate_schema(conn_res, 200) - conn_res = get(conn, "/api/v1/notifications", %{include_types: ["favourite"]}) + conn_res = get(conn, "/api/v1/notifications?include_types[]=favourite") - assert [%{"id" => ^favorite_notification_id}] = json_response(conn_res, 200) + assert [%{"id" => ^favorite_notification_id}] = + json_response_and_validate_schema(conn_res, 200) - conn_res = get(conn, "/api/v1/notifications", %{include_types: ["reblog"]}) + conn_res = get(conn, "/api/v1/notifications?include_types[]=reblog") - assert [%{"id" => ^reblog_notification_id}] = json_response(conn_res, 200) + assert [%{"id" => ^reblog_notification_id}] = json_response_and_validate_schema(conn_res, 200) - result = conn |> get("/api/v1/notifications") |> json_response(200) + result = conn |> get("/api/v1/notifications") |> json_response_and_validate_schema(200) assert length(result) == 4 + query = params_to_query(%{include_types: ["follow", "mention", "favourite", "reblog"]}) + result = conn - |> get("/api/v1/notifications", %{ - include_types: ["follow", "mention", "favourite", "reblog"] - }) - |> json_response(200) + |> get("/api/v1/notifications?" <> query) + |> json_response_and_validate_schema(200) assert length(result) == 4 end @@ -402,7 +401,7 @@ test "destroy multiple" do result = conn |> get("/api/v1/notifications") - |> json_response(:ok) + |> json_response_and_validate_schema(:ok) assert [%{"id" => ^notification2_id}, %{"id" => ^notification1_id}] = result @@ -414,22 +413,19 @@ test "destroy multiple" do result = conn2 |> get("/api/v1/notifications") - |> json_response(:ok) + |> json_response_and_validate_schema(:ok) assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result - conn_destroy = - conn - |> delete("/api/v1/notifications/destroy_multiple", %{ - "ids" => [notification1_id, notification2_id] - }) + query = params_to_query(%{ids: [notification1_id, notification2_id]}) + conn_destroy = delete(conn, "/api/v1/notifications/destroy_multiple?" <> query) - assert json_response(conn_destroy, 200) == %{} + assert json_response_and_validate_schema(conn_destroy, 200) == %{} result = conn2 |> get("/api/v1/notifications") - |> json_response(:ok) + |> json_response_and_validate_schema(:ok) assert [%{"id" => ^notification4_id}, %{"id" => ^notification3_id}] = result end @@ -443,13 +439,13 @@ test "doesn't see notifications after muting user with notifications" do ret_conn = get(conn, "/api/v1/notifications") - assert length(json_response(ret_conn, 200)) == 1 + assert length(json_response_and_validate_schema(ret_conn, 200)) == 1 {:ok, _user_relationships} = User.mute(user, user2) conn = get(conn, "/api/v1/notifications") - assert json_response(conn, 200) == [] + assert json_response_and_validate_schema(conn, 200) == [] end test "see notifications after muting user without notifications" do @@ -461,13 +457,13 @@ test "see notifications after muting user without notifications" do ret_conn = get(conn, "/api/v1/notifications") - assert length(json_response(ret_conn, 200)) == 1 + assert length(json_response_and_validate_schema(ret_conn, 200)) == 1 {:ok, _user_relationships} = User.mute(user, user2, false) conn = get(conn, "/api/v1/notifications") - assert length(json_response(conn, 200)) == 1 + assert length(json_response_and_validate_schema(conn, 200)) == 1 end test "see notifications after muting user with notifications and with_muted parameter" do @@ -479,13 +475,13 @@ test "see notifications after muting user with notifications and with_muted para ret_conn = get(conn, "/api/v1/notifications") - assert length(json_response(ret_conn, 200)) == 1 + assert length(json_response_and_validate_schema(ret_conn, 200)) == 1 {:ok, _user_relationships} = User.mute(user, user2) - conn = get(conn, "/api/v1/notifications", %{"with_muted" => "true"}) + conn = get(conn, "/api/v1/notifications?with_muted=true") - assert length(json_response(conn, 200)) == 1 + assert length(json_response_and_validate_schema(conn, 200)) == 1 end @tag capture_log: true @@ -512,7 +508,7 @@ test "see move notifications" do conn = get(conn, "/api/v1/notifications") - assert length(json_response(conn, 200)) == 1 + assert length(json_response_and_validate_schema(conn, 200)) == 1 end describe "link headers" do @@ -538,10 +534,10 @@ test "preserves parameters in link headers" do conn = conn |> assign(:user, user) - |> get("/api/v1/notifications", %{media_only: true}) + |> get("/api/v1/notifications?limit=5") assert [link_header] = get_resp_header(conn, "link") - assert link_header =~ ~r/media_only=true/ + assert link_header =~ ~r/limit=5/ assert link_header =~ ~r/min_id=#{notification2.id}/ assert link_header =~ ~r/max_id=#{notification1.id}/ end @@ -560,14 +556,14 @@ test "account_id" do assert [%{"account" => %{"id" => ^account_id}}] = conn |> assign(:user, user) - |> get("/api/v1/notifications", %{account_id: account_id}) - |> json_response(200) + |> get("/api/v1/notifications?account_id=#{account_id}") + |> json_response_and_validate_schema(200) assert %{"error" => "Account is not found"} = conn |> assign(:user, user) - |> get("/api/v1/notifications", %{account_id: "cofe"}) - |> json_response(404) + |> get("/api/v1/notifications?account_id=cofe") + |> json_response_and_validate_schema(404) end end @@ -577,4 +573,11 @@ defp get_notification_id_by_activity(%{id: id}) do |> Map.get(:id) |> to_string() end + + defp params_to_query(%{} = params) do + Enum.map_join(params, "&", fn + {k, v} when is_list(v) -> Enum.map_join(v, "&", &"#{k}[]=#{&1}") + {k, v} -> k <> "=" <> v + end) + end end diff --git a/test/web/mastodon_api/controllers/report_controller_test.exs b/test/web/mastodon_api/controllers/report_controller_test.exs index 34ec8119e..21b037237 100644 --- a/test/web/mastodon_api/controllers/report_controller_test.exs +++ b/test/web/mastodon_api/controllers/report_controller_test.exs @@ -22,8 +22,9 @@ defmodule Pleroma.Web.MastodonAPI.ReportControllerTest do test "submit a basic report", %{conn: conn, target_user: target_user} do assert %{"action_taken" => false, "id" => _} = conn + |> put_req_header("content-type", "application/json") |> post("/api/v1/reports", %{"account_id" => target_user.id}) - |> json_response(200) + |> json_response_and_validate_schema(200) end test "submit a report with statuses and comment", %{ @@ -33,23 +34,25 @@ test "submit a report with statuses and comment", %{ } do assert %{"action_taken" => false, "id" => _} = conn + |> put_req_header("content-type", "application/json") |> post("/api/v1/reports", %{ "account_id" => target_user.id, "status_ids" => [activity.id], "comment" => "bad status!", "forward" => "false" }) - |> json_response(200) + |> json_response_and_validate_schema(200) end test "account_id is required", %{ conn: conn, activity: activity } do - assert %{"error" => "Valid `account_id` required"} = + assert %{"error" => "Missing field: account_id."} = conn + |> put_req_header("content-type", "application/json") |> post("/api/v1/reports", %{"status_ids" => [activity.id]}) - |> json_response(400) + |> json_response_and_validate_schema(400) end test "comment must be up to the size specified in the config", %{ @@ -63,17 +66,21 @@ test "comment must be up to the size specified in the config", %{ assert ^error = conn + |> put_req_header("content-type", "application/json") |> post("/api/v1/reports", %{"account_id" => target_user.id, "comment" => comment}) - |> json_response(400) + |> json_response_and_validate_schema(400) end test "returns error when account is not exist", %{ conn: conn, activity: activity } do - conn = post(conn, "/api/v1/reports", %{"status_ids" => [activity.id], "account_id" => "foo"}) + conn = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/reports", %{"status_ids" => [activity.id], "account_id" => "foo"}) - assert json_response(conn, 400) == %{"error" => "Account not found"} + assert json_response_and_validate_schema(conn, 400) == %{"error" => "Account not found"} end test "doesn't fail if an admin has no email", %{conn: conn, target_user: target_user} do @@ -81,7 +88,8 @@ test "doesn't fail if an admin has no email", %{conn: conn, target_user: target_ assert %{"action_taken" => false, "id" => _} = conn + |> put_req_header("content-type", "application/json") |> post("/api/v1/reports", %{"account_id" => target_user.id}) - |> json_response(200) + |> json_response_and_validate_schema(200) end end diff --git a/test/web/mastodon_api/controllers/suggestion_controller_test.exs b/test/web/mastodon_api/controllers/suggestion_controller_test.exs index 8d0e70db8..f120bd0cd 100644 --- a/test/web/mastodon_api/controllers/suggestion_controller_test.exs +++ b/test/web/mastodon_api/controllers/suggestion_controller_test.exs @@ -5,8 +5,6 @@ defmodule Pleroma.Web.MastodonAPI.SuggestionControllerTest do use Pleroma.Web.ConnCase - alias Pleroma.Config - setup do: oauth_access(["read"]) test "returns empty result", %{conn: conn} do diff --git a/test/web/twitter_api/twitter_api_test.exs b/test/web/twitter_api/twitter_api_test.exs index 7926a0757..368533292 100644 --- a/test/web/twitter_api/twitter_api_test.exs +++ b/test/web/twitter_api/twitter_api_test.exs @@ -18,7 +18,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do test "it registers a new user and returns the user." do data = %{ - :nickname => "lain", + :username => "lain", :email => "lain@wired.jp", :fullname => "lain iwakura", :password => "bear", @@ -35,7 +35,7 @@ test "it registers a new user and returns the user." do test "it registers a new user with empty string in bio and returns the user." do data = %{ - :nickname => "lain", + :username => "lain", :email => "lain@wired.jp", :fullname => "lain iwakura", :bio => "", @@ -60,7 +60,7 @@ test "it sends confirmation email if :account_activation_required is specified i end data = %{ - :nickname => "lain", + :username => "lain", :email => "lain@wired.jp", :fullname => "lain iwakura", :bio => "", @@ -87,7 +87,7 @@ test "it sends confirmation email if :account_activation_required is specified i test "it registers a new user and parses mentions in the bio" do data1 = %{ - :nickname => "john", + :username => "john", :email => "john@gmail.com", :fullname => "John Doe", :bio => "test", @@ -98,7 +98,7 @@ test "it registers a new user and parses mentions in the bio" do {:ok, user1} = TwitterAPI.register_user(data1) data2 = %{ - :nickname => "lain", + :username => "lain", :email => "lain@wired.jp", :fullname => "lain iwakura", :bio => "@john test", @@ -123,7 +123,7 @@ test "returns user on success" do {:ok, invite} = UserInviteToken.create_invite() data = %{ - :nickname => "vinny", + :username => "vinny", :email => "pasta@pizza.vs", :fullname => "Vinny Vinesauce", :bio => "streamer", @@ -145,7 +145,7 @@ test "returns user on success" do test "returns error on invalid token" do data = %{ - :nickname => "GrimReaper", + :username => "GrimReaper", :email => "death@reapers.afterlife", :fullname => "Reaper Grim", :bio => "Your time has come", @@ -165,7 +165,7 @@ test "returns error on expired token" do UserInviteToken.update_invite!(invite, used: true) data = %{ - :nickname => "GrimReaper", + :username => "GrimReaper", :email => "death@reapers.afterlife", :fullname => "Reaper Grim", :bio => "Your time has come", @@ -186,7 +186,7 @@ test "returns error on expired token" do setup do data = %{ - :nickname => "vinny", + :username => "vinny", :email => "pasta@pizza.vs", :fullname => "Vinny Vinesauce", :bio => "streamer", @@ -250,7 +250,7 @@ test "returns user on success, after him registration fails" do UserInviteToken.update_invite!(invite, uses: 99) data = %{ - :nickname => "vinny", + :username => "vinny", :email => "pasta@pizza.vs", :fullname => "Vinny Vinesauce", :bio => "streamer", @@ -269,7 +269,7 @@ test "returns user on success, after him registration fails" do AccountView.render("show.json", %{user: fetched_user}) data = %{ - :nickname => "GrimReaper", + :username => "GrimReaper", :email => "death@reapers.afterlife", :fullname => "Reaper Grim", :bio => "Your time has come", @@ -292,7 +292,7 @@ test "returns user on success" do {:ok, invite} = UserInviteToken.create_invite(%{expires_at: Date.utc_today(), max_use: 100}) data = %{ - :nickname => "vinny", + :username => "vinny", :email => "pasta@pizza.vs", :fullname => "Vinny Vinesauce", :bio => "streamer", @@ -317,7 +317,7 @@ test "error after max uses" do UserInviteToken.update_invite!(invite, uses: 99) data = %{ - :nickname => "vinny", + :username => "vinny", :email => "pasta@pizza.vs", :fullname => "Vinny Vinesauce", :bio => "streamer", @@ -335,7 +335,7 @@ test "error after max uses" do AccountView.render("show.json", %{user: fetched_user}) data = %{ - :nickname => "GrimReaper", + :username => "GrimReaper", :email => "death@reapers.afterlife", :fullname => "Reaper Grim", :bio => "Your time has come", @@ -355,7 +355,7 @@ test "returns error on overdue date" do UserInviteToken.create_invite(%{expires_at: Date.add(Date.utc_today(), -1), max_use: 100}) data = %{ - :nickname => "GrimReaper", + :username => "GrimReaper", :email => "death@reapers.afterlife", :fullname => "Reaper Grim", :bio => "Your time has come", @@ -377,7 +377,7 @@ test "returns error on with overdue date and after max" do UserInviteToken.update_invite!(invite, uses: 100) data = %{ - :nickname => "GrimReaper", + :username => "GrimReaper", :email => "death@reapers.afterlife", :fullname => "Reaper Grim", :bio => "Your time has come", @@ -395,16 +395,15 @@ test "returns error on with overdue date and after max" do test "it returns the error on registration problems" do data = %{ - :nickname => "lain", + :username => "lain", :email => "lain@wired.jp", :fullname => "lain iwakura", - :bio => "close the world.", - :password => "bear" + :bio => "close the world." } - {:error, error_object} = TwitterAPI.register_user(data) + {:error, error} = TwitterAPI.register_user(data) - assert is_binary(error_object[:error]) + assert is_binary(error) refute User.get_cached_by_nickname("lain") end