diff --git a/lib/pleroma/captcha/error.ex b/lib/pleroma/captcha/error.ex
new file mode 100644
index 000000000..ec77f1336
--- /dev/null
+++ b/lib/pleroma/captcha/error.ex
@@ -0,0 +1,43 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Captcha.Error do
+ import Pleroma.Web.Gettext
+
+ def message(_reason, opts \\ [])
+
+ def message(:missing_field, %{name: name}) do
+ dgettext(
+ "errors",
+ "Invalid CAPTCHA (Missing parameter: %{name})",
+ name: name
+ )
+ end
+
+ def message(:captcha_error, _) do
+ dgettext("errors", "CAPTCHA Error")
+ end
+
+ def message(:invalid, _) do
+ dgettext("errors", "Invalid CAPTCHA")
+ end
+
+ def message(:kocaptcha_service_unavailable, _) do
+ dgettext("errors", "Kocaptcha service unavailable")
+ end
+
+ def message(:expired, _) do
+ dgettext("errors", "CAPTCHA expired")
+ end
+
+ def message(:already_used, _) do
+ dgettext("errors", "CAPTCHA already used")
+ end
+
+ def message(:invalid_answer_data, _) do
+ dgettext("errors", "Invalid answer data")
+ end
+
+ def message(error, _), do: error
+end
diff --git a/lib/pleroma/web/api_spec/errors/registration_user_error.ex b/lib/pleroma/web/api_spec/errors/registration_user_error.ex
new file mode 100644
index 000000000..1e48e12c9
--- /dev/null
+++ b/lib/pleroma/web/api_spec/errors/registration_user_error.ex
@@ -0,0 +1,33 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Errors.RegistrationUserError do
+ @behaviour Plug
+
+ import Plug.Conn, only: [put_status: 2]
+ import Phoenix.Controller, only: [json: 2]
+ alias Pleroma.Web.ApiSpec.RenderError
+
+ @impl Plug
+ def init(opts), do: opts
+
+ @impl Plug
+
+ def call(conn, errors) do
+ field_errors =
+ errors
+ |> Enum.group_by(& &1.name)
+ |> Enum.into(%{}, fn {field, field_errors} ->
+ {field, Enum.map(field_errors, &RenderError.message/1)}
+ end)
+
+ conn
+ |> put_status(:bad_request)
+ |> json(%{
+ error: "Please review the submission",
+ identifier: "review_submission",
+ fields: field_errors
+ })
+ 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 d90ddb787..d68501bb8 100644
--- a/lib/pleroma/web/api_spec/operations/account_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/account_operation.ex
@@ -34,7 +34,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
requestBody: request_body("Parameters", create_request(), required: true),
responses: %{
200 => Operation.response("Account", "application/json", create_response()),
- 400 => Operation.response("Error", "application/json", ApiError),
+ 400 => Operation.response("Error", "application/json", error_response()),
403 => Operation.response("Error", "application/json", ApiError),
429 => Operation.response("Error", "application/json", ApiError)
}
@@ -453,6 +453,35 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
}
end
+ defp error_response do
+ %Schema{
+ title: "AccountCreateErrorResponse",
+ description: "Response schema for errors",
+ type: :object,
+ properties: %{
+ identifier: %Schema{type: :string},
+ message: %Schema{type: :string},
+ fields: %Schema{
+ type: :object,
+ properties: %{
+ captcha: %Schema{type: :array, items: %Schema{type: :string}},
+ email: %Schema{type: :array, items: %Schema{type: :string}},
+ invite: %Schema{type: :array, items: %Schema{type: :string}},
+ password: %Schema{type: :array, items: %Schema{type: :string}},
+ username: %Schema{type: :array, items: %Schema{type: :string}}
+ }
+ }
+ },
+ example: %{
+ "error" => "Please review the submission",
+ "identifier" => "review_submission",
+ "fields" => %{
+ "captcha" => ["Invalid CAPTCHA"]
+ }
+ }
+ }
+ end
+
# Note: this is a token response (if login succeeds!), but there's no oauth operation file yet.
defp create_response do
%Schema{
diff --git a/lib/pleroma/web/api_spec/render_error.ex b/lib/pleroma/web/api_spec/render_error.ex
index d476b8ef3..59154b39e 100644
--- a/lib/pleroma/web/api_spec/render_error.ex
+++ b/lib/pleroma/web/api_spec/render_error.ex
@@ -47,14 +47,14 @@ defmodule Pleroma.Web.ApiSpec.RenderError do
}
end
- defp message(%{reason: :invalid_schema_type, type: type, name: name}) do
+ def message(%{reason: :invalid_schema_type, type: type, name: name}) do
gettext("%{name} - Invalid schema.type. Got: %{type}.",
name: name,
type: inspect(type)
)
end
- defp message(%{reason: :null_value, name: name} = error) do
+ def message(%{reason: :null_value, name: name} = error) do
case error.type do
nil ->
gettext("%{name} - null value.", name: name)
@@ -67,42 +67,42 @@ defmodule Pleroma.Web.ApiSpec.RenderError do
end
end
- defp message(%{reason: :all_of, meta: %{invalid_schema: invalid_schema}}) do
+ def message(%{reason: :all_of, meta: %{invalid_schema: invalid_schema}}) do
gettext(
"Failed to cast value as %{invalid_schema}. Value must be castable using `allOf` schemas listed.",
invalid_schema: invalid_schema
)
end
- defp message(%{reason: :any_of, meta: %{failed_schemas: failed_schemas}}) do
+ def message(%{reason: :any_of, meta: %{failed_schemas: failed_schemas}}) do
gettext("Failed to cast value using any of: %{failed_schemas}.",
failed_schemas: failed_schemas
)
end
- defp message(%{reason: :one_of, meta: %{failed_schemas: failed_schemas}}) do
+ def message(%{reason: :one_of, meta: %{failed_schemas: failed_schemas}}) do
gettext("Failed to cast value to one of: %{failed_schemas}.", failed_schemas: failed_schemas)
end
- defp message(%{reason: :min_length, length: length, name: name}) do
+ def message(%{reason: :min_length, length: length, name: name}) do
gettext("%{name} - String length is smaller than minLength: %{length}.",
name: name,
length: length
)
end
- defp message(%{reason: :max_length, length: length, name: name}) do
+ def message(%{reason: :max_length, length: length, name: name}) do
gettext("%{name} - String length is larger than maxLength: %{length}.",
name: name,
length: length
)
end
- defp message(%{reason: :unique_items, name: name}) do
+ def message(%{reason: :unique_items, name: name}) do
gettext("%{name} - Array items must be unique.", name: name)
end
- defp message(%{reason: :min_items, length: min, value: array, name: name}) do
+ def message(%{reason: :min_items, length: min, value: array, name: name}) do
gettext("%{name} - Array length %{length} is smaller than minItems: %{min}.",
name: name,
length: length(array),
@@ -110,7 +110,7 @@ defmodule Pleroma.Web.ApiSpec.RenderError do
)
end
- defp message(%{reason: :max_items, length: max, value: array, name: name}) do
+ def message(%{reason: :max_items, length: max, value: array, name: name}) do
gettext("%{name} - Array length %{length} is larger than maxItems: %{}.",
name: name,
length: length(array),
@@ -118,7 +118,7 @@ defmodule Pleroma.Web.ApiSpec.RenderError do
)
end
- defp message(%{reason: :multiple_of, length: multiple, value: count, name: name}) do
+ def message(%{reason: :multiple_of, length: multiple, value: count, name: name}) do
gettext("%{name} - %{count} is not a multiple of %{multiple}.",
name: name,
count: count,
@@ -126,8 +126,8 @@ defmodule Pleroma.Web.ApiSpec.RenderError do
)
end
- defp message(%{reason: :exclusive_max, length: max, value: value, name: name})
- when value >= max do
+ def message(%{reason: :exclusive_max, length: max, value: value, name: name})
+ when value >= max do
gettext("%{name} - %{value} is larger than exclusive maximum %{max}.",
name: name,
value: value,
@@ -135,8 +135,8 @@ defmodule Pleroma.Web.ApiSpec.RenderError do
)
end
- defp message(%{reason: :maximum, length: max, value: value, name: name})
- when value > max do
+ def message(%{reason: :maximum, length: max, value: value, name: name})
+ when value > max do
gettext("%{name} - %{value} is larger than inclusive maximum %{max}.",
name: name,
value: value,
@@ -144,8 +144,8 @@ defmodule Pleroma.Web.ApiSpec.RenderError do
)
end
- defp message(%{reason: :exclusive_multiple, length: min, value: value, name: name})
- when value <= min do
+ def message(%{reason: :exclusive_multiple, length: min, value: value, name: name})
+ when value <= min do
gettext("%{name} - %{value} is smaller than exclusive minimum %{min}.",
name: name,
value: value,
@@ -153,8 +153,8 @@ defmodule Pleroma.Web.ApiSpec.RenderError do
)
end
- defp message(%{reason: :minimum, length: min, value: value, name: name})
- when value < min do
+ def message(%{reason: :minimum, length: min, value: value, name: name})
+ when value < min do
gettext("%{name} - %{value} is smaller than inclusive minimum %{min}.",
name: name,
value: value,
@@ -162,7 +162,7 @@ defmodule Pleroma.Web.ApiSpec.RenderError do
)
end
- defp message(%{reason: :invalid_type, type: type, value: value, name: name}) do
+ def message(%{reason: :invalid_type, type: type, value: value, name: name}) do
gettext("%{name} - Invalid %{type}. Got: %{value}.",
name: name,
value: OpenApiSpex.TermType.type(value),
@@ -170,49 +170,49 @@ defmodule Pleroma.Web.ApiSpec.RenderError do
)
end
- defp message(%{reason: :invalid_format, format: format, name: name}) do
+ def message(%{reason: :invalid_format, format: format, name: name}) do
gettext("%{name} - Invalid format. Expected %{format}.", name: name, format: inspect(format))
end
- defp message(%{reason: :invalid_enum, name: name}) do
+ def message(%{reason: :invalid_enum, name: name}) do
gettext("%{name} - Invalid value for enum.", name: name)
end
- defp message(%{reason: :polymorphic_failed, type: polymorphic_type}) do
+ def message(%{reason: :polymorphic_failed, type: polymorphic_type}) do
gettext("Failed to cast to any schema in %{polymorphic_type}",
polymorphic_type: polymorphic_type
)
end
- defp message(%{reason: :unexpected_field, name: name}) do
+ def message(%{reason: :unexpected_field, name: name}) do
gettext("Unexpected field: %{name}.", name: safe_string(name))
end
- defp message(%{reason: :no_value_for_discriminator, name: field}) do
+ def message(%{reason: :no_value_for_discriminator, name: field}) do
gettext("Value used as discriminator for `%{field}` matches no schemas.", name: field)
end
- defp message(%{reason: :invalid_discriminator_value, name: field}) do
+ def message(%{reason: :invalid_discriminator_value, name: field}) do
gettext("No value provided for required discriminator `%{field}`.", name: field)
end
- defp message(%{reason: :unknown_schema, name: name}) do
+ def message(%{reason: :unknown_schema, name: name}) do
gettext("Unknown schema: %{name}.", name: name)
end
- defp message(%{reason: :missing_field, name: name}) do
+ def message(%{reason: :missing_field, name: name}) do
gettext("Missing field: %{name}.", name: name)
end
- defp message(%{reason: :missing_header, name: name}) do
+ def message(%{reason: :missing_header, name: name}) do
gettext("Missing header: %{name}.", name: name)
end
- defp message(%{reason: :invalid_header, name: name}) do
+ def message(%{reason: :invalid_header, name: name}) do
gettext("Invalid value for header: %{name}.", name: name)
end
- defp message(%{reason: :max_properties, meta: meta}) do
+ def message(%{reason: :max_properties, meta: meta}) do
gettext(
"Object property count %{property_count} is greater than maxProperties: %{max_properties}.",
property_count: meta.property_count,
@@ -220,7 +220,7 @@ defmodule Pleroma.Web.ApiSpec.RenderError do
)
end
- defp message(%{reason: :min_properties, meta: meta}) do
+ def message(%{reason: :min_properties, meta: meta}) do
gettext(
"Object property count %{property_count} is less than minProperties: %{min_properties}",
property_count: meta.property_count,
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index 4f9696d52..2047e2439 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -19,6 +19,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Builder
alias Pleroma.Web.ActivityPub.Pipeline
+ alias Pleroma.Web.ApiSpec.Errors.RegistrationUserError
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.ListView
alias Pleroma.Web.MastodonAPI.MastodonAPI
@@ -31,7 +32,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
alias Pleroma.Web.Plugs.RateLimiter
alias Pleroma.Web.TwitterAPI.TwitterAPI
- plug(Pleroma.Web.ApiSpec.CastAndValidate)
+ plug(
+ Pleroma.Web.ApiSpec.CastAndValidate,
+ [render_error: RegistrationUserError] when action in [:create]
+ )
+
+ plug(Pleroma.Web.ApiSpec.CastAndValidate when action not in [:create])
plug(:skip_plug, [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :create)
@@ -101,8 +107,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
with :ok <- validate_email_param(params),
:ok <- TwitterAPI.validate_captcha(app, params),
{:ok, user} <- TwitterAPI.register_user(params),
- {_, {:ok, token}} <-
- {:login, OAuthController.login(user, app, app.scopes)} do
+ {_, {:ok, token}} <- {:login, OAuthController.login(user, app, app.scopes)} do
json(conn, OAuthView.render("token.json", %{user: user, token: token}))
else
{:login, {:account_status, :confirmation_pending}} ->
@@ -126,8 +131,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
identifier: "manual_login_required"
})
- {:error, error} ->
- json_response(conn, :bad_request, %{error: error})
+ {:error, errors} ->
+ json_response(conn, :bad_request, %{
+ identifier: "review_submission",
+ error: "Please review the submission",
+ fields: errors
+ })
end
end
@@ -143,8 +152,14 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
defp validate_email_param(_) do
case Pleroma.Config.get([:instance, :account_activation_required]) do
- true -> {:error, dgettext("errors", "Missing parameter: %{name}", name: "email")}
- _ -> :ok
+ true ->
+ {:error,
+ %{
+ email: [dgettext("errors", "Missing parameter: %{name}", name: "email")]
+ }}
+
+ _ ->
+ :ok
end
end
diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex
index 5d7948507..92b071a81 100644
--- a/lib/pleroma/web/twitter_api/twitter_api.ex
+++ b/lib/pleroma/web/twitter_api/twitter_api.ex
@@ -3,14 +3,12 @@
# 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
+ @spec register_user(map(), keyword()) :: {:ok, User.t()} | {:error, map()}
def register_user(params, opts \\ []) do
params =
params
@@ -28,18 +26,20 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
end
end
+ @spec create_user_with_invite(map(), keyword()) :: {:ok, User.t()} | {:error, map()}
defp create_user_with_invite(params, opts) do
with %{token: token} when is_binary(token) <- params,
- %UserInviteToken{} = invite <- Repo.get_by(UserInviteToken, %{token: token}),
+ {:ok, invite} <- UserInviteToken.find_by_token(token),
true <- UserInviteToken.valid_invite?(invite) do
UserInviteToken.update_usage!(invite)
create_user(params, opts)
else
- nil -> {:error, "Invalid token"}
- _ -> {:error, "Expired token"}
+ nil -> {:error, %{invite: ["Invalid token"]}}
+ _ -> {:error, %{invite: ["Expired token"]}}
end
end
+ @spec create_user(map(), keyword()) :: {:ok, User.t()} | {:error, map()}
defp create_user(params, opts) do
changeset = User.register_changeset(%User{}, params, opts)
@@ -52,7 +52,6 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
errors =
changeset
|> Ecto.Changeset.traverse_errors(fn {msg, _opts} -> msg end)
- |> Jason.encode!()
{:error, errors}
end
@@ -104,26 +103,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
) 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)
+ {:error, %{captcha: [Pleroma.Captcha.Error.message(error)]}}
end
end
@@ -131,12 +112,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI 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}
+ {:error, Pleroma.Captcha.Error.message(:missing_field, %{name: key})}
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/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs
index f7f1369e4..69b5c6fb7 100644
--- a/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs
@@ -5,12 +5,10 @@
defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
use Pleroma.Web.ConnCase
- alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.InternalFetchActor
alias Pleroma.Web.CommonAPI
- alias Pleroma.Web.OAuth.Token
import Pleroma.Factory
@@ -888,543 +886,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
assert %{"id" => _id, "blocking" => false} = json_response_and_validate_schema(conn, 200)
end
- describe "create account by app" do
- setup do
- valid_params = %{
- username: "lain",
- email: "lain@example.org",
- password: "PlzDontHackLain",
- agreement: true
- }
-
- [valid_params: valid_params]
- end
-
- test "registers and logs in without :account_activation_required / :account_approval_required",
- %{conn: conn} do
- clear_config([:instance, :account_activation_required], false)
- clear_config([:instance, :account_approval_required], false)
-
- conn =
- conn
- |> put_req_header("content-type", "application/json")
- |> post("/api/v1/apps", %{
- client_name: "client_name",
- redirect_uris: "urn:ietf:wg:oauth:2.0:oob",
- scopes: "read, write, follow"
- })
-
- assert %{
- "client_id" => client_id,
- "client_secret" => client_secret,
- "id" => _,
- "name" => "client_name",
- "redirect_uri" => "urn:ietf:wg:oauth:2.0:oob",
- "vapid_key" => _,
- "website" => nil
- } = json_response_and_validate_schema(conn, 200)
-
- conn =
- post(conn, "/oauth/token", %{
- grant_type: "client_credentials",
- client_id: client_id,
- client_secret: client_secret
- })
-
- assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} =
- json_response(conn, 200)
-
- assert token
- token_from_db = Repo.get_by(Token, token: token)
- assert token_from_db
- assert refresh
- assert scope == "read write follow"
-
- clear_config([User, :email_blacklist], ["example.org"])
-
- params = %{
- username: "lain",
- email: "lain@example.org",
- password: "PlzDontHackLain",
- bio: "Test Bio",
- agreement: true
- }
-
- conn =
- build_conn()
- |> put_req_header("content-type", "multipart/form-data")
- |> put_req_header("authorization", "Bearer " <> token)
- |> post("/api/v1/accounts", params)
-
- assert %{"error" => "{\"email\":[\"Invalid email\"]}"} =
- json_response_and_validate_schema(conn, 400)
-
- Pleroma.Config.put([User, :email_blacklist], [])
-
- conn =
- build_conn()
- |> put_req_header("content-type", "multipart/form-data")
- |> put_req_header("authorization", "Bearer " <> token)
- |> post("/api/v1/accounts", params)
-
- %{
- "access_token" => token,
- "created_at" => _created_at,
- "scope" => ^scope,
- "token_type" => "Bearer"
- } = json_response_and_validate_schema(conn, 200)
-
- token_from_db = Repo.get_by(Token, token: token)
- assert token_from_db
- user = Repo.preload(token_from_db, :user).user
-
- assert user
- refute user.confirmation_pending
- refute user.approval_pending
- end
-
- test "registers but does not log in with :account_activation_required", %{conn: conn} do
- clear_config([:instance, :account_activation_required], true)
- clear_config([:instance, :account_approval_required], false)
-
- conn =
- conn
- |> put_req_header("content-type", "application/json")
- |> post("/api/v1/apps", %{
- client_name: "client_name",
- redirect_uris: "urn:ietf:wg:oauth:2.0:oob",
- scopes: "read, write, follow"
- })
-
- assert %{
- "client_id" => client_id,
- "client_secret" => client_secret,
- "id" => _,
- "name" => "client_name",
- "redirect_uri" => "urn:ietf:wg:oauth:2.0:oob",
- "vapid_key" => _,
- "website" => nil
- } = json_response_and_validate_schema(conn, 200)
-
- conn =
- post(conn, "/oauth/token", %{
- grant_type: "client_credentials",
- client_id: client_id,
- client_secret: client_secret
- })
-
- assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} =
- json_response(conn, 200)
-
- assert token
- token_from_db = Repo.get_by(Token, token: token)
- assert token_from_db
- assert refresh
- assert scope == "read write follow"
-
- conn =
- build_conn()
- |> put_req_header("content-type", "multipart/form-data")
- |> put_req_header("authorization", "Bearer " <> token)
- |> post("/api/v1/accounts", %{
- username: "lain",
- email: "lain@example.org",
- password: "PlzDontHackLain",
- bio: "Test Bio",
- agreement: true
- })
-
- response = json_response_and_validate_schema(conn, 200)
- assert %{"identifier" => "missing_confirmed_email"} = response
- refute response["access_token"]
- refute response["token_type"]
-
- user = Repo.get_by(User, email: "lain@example.org")
- assert user.confirmation_pending
- end
-
- test "registers but does not log in with :account_approval_required", %{conn: conn} do
- clear_config([:instance, :account_approval_required], true)
- clear_config([:instance, :account_activation_required], false)
-
- conn =
- conn
- |> put_req_header("content-type", "application/json")
- |> post("/api/v1/apps", %{
- client_name: "client_name",
- redirect_uris: "urn:ietf:wg:oauth:2.0:oob",
- scopes: "read, write, follow"
- })
-
- assert %{
- "client_id" => client_id,
- "client_secret" => client_secret,
- "id" => _,
- "name" => "client_name",
- "redirect_uri" => "urn:ietf:wg:oauth:2.0:oob",
- "vapid_key" => _,
- "website" => nil
- } = json_response_and_validate_schema(conn, 200)
-
- conn =
- post(conn, "/oauth/token", %{
- grant_type: "client_credentials",
- client_id: client_id,
- client_secret: client_secret
- })
-
- assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} =
- json_response(conn, 200)
-
- assert token
- token_from_db = Repo.get_by(Token, token: token)
- assert token_from_db
- assert refresh
- assert scope == "read write follow"
-
- conn =
- build_conn()
- |> put_req_header("content-type", "multipart/form-data")
- |> put_req_header("authorization", "Bearer " <> token)
- |> post("/api/v1/accounts", %{
- username: "lain",
- email: "lain@example.org",
- password: "PlzDontHackLain",
- bio: "Test Bio",
- agreement: true,
- reason: "I'm a cool dude, bro"
- })
-
- response = json_response_and_validate_schema(conn, 200)
- assert %{"identifier" => "awaiting_approval"} = response
- refute response["access_token"]
- refute response["token_type"]
-
- user = Repo.get_by(User, email: "lain@example.org")
-
- assert user.approval_pending
- assert user.registration_reason == "I'm a cool dude, bro"
- end
-
- test "returns error when user already registred", %{conn: conn, valid_params: valid_params} do
- _user = insert(:user, email: "lain@example.org")
- app_token = insert(:oauth_token, user: nil)
-
- res =
- conn
- |> put_req_header("authorization", "Bearer " <> app_token.token)
- |> put_req_header("content-type", "application/json")
- |> post("/api/v1/accounts", valid_params)
-
- assert json_response_and_validate_schema(res, 400) == %{
- "error" => "{\"email\":[\"has already been taken\"]}"
- }
- end
-
- test "returns bad_request if missing required params", %{
- conn: conn,
- valid_params: valid_params
- } do
- app_token = insert(:oauth_token, user: nil)
-
- conn =
- conn
- |> put_req_header("authorization", "Bearer " <> app_token.token)
- |> put_req_header("content-type", "application/json")
-
- res = post(conn, "/api/v1/accounts", valid_params)
- assert json_response_and_validate_schema(res, 200)
-
- [{127, 0, 0, 1}, {127, 0, 0, 2}, {127, 0, 0, 3}, {127, 0, 0, 4}]
- |> Stream.zip(Map.delete(valid_params, :email))
- |> Enum.each(fn {ip, {attr, _}} ->
- res =
- conn
- |> Map.put(:remote_ip, ip)
- |> post("/api/v1/accounts", Map.delete(valid_params, attr))
- |> json_response_and_validate_schema(400)
-
- assert res == %{
- "error" => "Missing field: #{attr}.",
- "errors" => [
- %{
- "message" => "Missing field: #{attr}",
- "source" => %{"pointer" => "/#{attr}"},
- "title" => "Invalid value"
- }
- ]
- }
- end)
- end
-
- test "returns bad_request if missing email params when :account_activation_required is enabled",
- %{conn: conn, valid_params: valid_params} do
- clear_config([:instance, :account_activation_required], true)
-
- app_token = insert(:oauth_token, user: nil)
-
- conn =
- conn
- |> put_req_header("authorization", "Bearer " <> app_token.token)
- |> put_req_header("content-type", "application/json")
-
- res =
- conn
- |> 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 parameter: email"}
-
- res =
- conn
- |> Map.put(:remote_ip, {127, 0, 0, 6})
- |> post("/api/v1/accounts", Map.put(valid_params, :email, ""))
-
- assert json_response_and_validate_schema(res, 400) == %{
- "error" => "{\"email\":[\"can't be blank\"]}"
- }
- end
-
- test "allow registration without an email", %{conn: conn, valid_params: valid_params} do
- app_token = insert(:oauth_token, user: nil)
- conn = put_req_header(conn, "authorization", "Bearer " <> app_token.token)
-
- res =
- conn
- |> put_req_header("content-type", "application/json")
- |> Map.put(:remote_ip, {127, 0, 0, 7})
- |> post("/api/v1/accounts", Map.delete(valid_params, :email))
-
- assert json_response_and_validate_schema(res, 200)
- end
-
- test "allow registration with an empty email", %{conn: conn, valid_params: valid_params} do
- app_token = insert(:oauth_token, user: nil)
- conn = put_req_header(conn, "authorization", "Bearer " <> app_token.token)
-
- res =
- conn
- |> put_req_header("content-type", "application/json")
- |> Map.put(:remote_ip, {127, 0, 0, 8})
- |> post("/api/v1/accounts", Map.put(valid_params, :email, ""))
-
- assert json_response_and_validate_schema(res, 200)
- end
-
- test "returns forbidden if token is invalid", %{conn: conn, valid_params: valid_params} do
- res =
- conn
- |> put_req_header("authorization", "Bearer " <> "invalid-token")
- |> put_req_header("content-type", "multipart/form-data")
- |> post("/api/v1/accounts", valid_params)
-
- assert json_response_and_validate_schema(res, 403) == %{"error" => "Invalid credentials"}
- end
-
- test "registration from trusted app" do
- clear_config([Pleroma.Captcha, :enabled], true)
- app = insert(:oauth_app, trusted: true, scopes: ["read", "write", "follow", "push"])
-
- conn =
- build_conn()
- |> post("/oauth/token", %{
- "grant_type" => "client_credentials",
- "client_id" => app.client_id,
- "client_secret" => app.client_secret
- })
-
- assert %{"access_token" => token, "token_type" => "Bearer"} = json_response(conn, 200)
-
- response =
- build_conn()
- |> Plug.Conn.put_req_header("authorization", "Bearer " <> token)
- |> put_req_header("content-type", "multipart/form-data")
- |> post("/api/v1/accounts", %{
- nickname: "nickanme",
- agreement: true,
- email: "email@example.com",
- fullname: "Lain",
- username: "Lain",
- password: "some_password",
- confirm: "some_password"
- })
- |> json_response_and_validate_schema(200)
-
- assert %{
- "access_token" => access_token,
- "created_at" => _,
- "scope" => "read write follow push",
- "token_type" => "Bearer"
- } = response
-
- response =
- build_conn()
- |> Plug.Conn.put_req_header("authorization", "Bearer " <> access_token)
- |> get("/api/v1/accounts/verify_credentials")
- |> json_response_and_validate_schema(200)
-
- assert %{
- "acct" => "Lain",
- "bot" => false,
- "display_name" => "Lain",
- "follow_requests_count" => 0,
- "followers_count" => 0,
- "following_count" => 0,
- "locked" => false,
- "note" => "",
- "source" => %{
- "fields" => [],
- "note" => "",
- "pleroma" => %{
- "actor_type" => "Person",
- "discoverable" => false,
- "no_rich_text" => false,
- "show_role" => true
- },
- "privacy" => "public",
- "sensitive" => false
- },
- "statuses_count" => 0,
- "username" => "Lain"
- } = response
- end
- end
-
- describe "create account by app / rate limit" do
- setup do: clear_config([:rate_limit, :app_account_creation], {10_000, 2})
-
- test "respects rate limit setting", %{conn: conn} do
- app_token = insert(:oauth_token, user: nil)
-
- conn =
- conn
- |> put_req_header("authorization", "Bearer " <> app_token.token)
- |> Map.put(:remote_ip, {15, 15, 15, 15})
- |> put_req_header("content-type", "multipart/form-data")
-
- for i <- 1..2 do
- conn =
- conn
- |> post("/api/v1/accounts", %{
- username: "#{i}lain",
- email: "#{i}lain@example.org",
- password: "PlzDontHackLain",
- agreement: true
- })
-
- %{
- "access_token" => token,
- "created_at" => _created_at,
- "scope" => _scope,
- "token_type" => "Bearer"
- } = json_response_and_validate_schema(conn, 200)
-
- token_from_db = Repo.get_by(Token, token: token)
- assert token_from_db
- token_from_db = Repo.preload(token_from_db, :user)
- assert token_from_db.user
- end
-
- conn =
- post(conn, "/api/v1/accounts", %{
- username: "6lain",
- email: "6lain@example.org",
- password: "PlzDontHackLain",
- agreement: true
- })
-
- assert json_response_and_validate_schema(conn, :too_many_requests) == %{
- "error" => "Throttled"
- }
- 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/pleroma/web/mastodon_api/registration_user_test.exs b/test/pleroma/web/mastodon_api/registration_user_test.exs
new file mode 100644
index 000000000..086d29832
--- /dev/null
+++ b/test/pleroma/web/mastodon_api/registration_user_test.exs
@@ -0,0 +1,691 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.RegistrationUserTest do
+ use Pleroma.Web.ConnCase
+
+ alias Pleroma.Repo
+ alias Pleroma.User
+ alias Pleroma.UserInviteToken
+ alias Pleroma.Web.OAuth.Token
+
+ import Pleroma.Factory
+
+ describe "create account by app" do
+ setup do
+ valid_params = %{
+ username: "lain",
+ email: "lain@example.org",
+ password: "PlzDontHackLain",
+ agreement: true
+ }
+
+ [valid_params: valid_params]
+ end
+
+ test "registers and logs in without :account_activation_required / :account_approval_required",
+ %{conn: conn} do
+ clear_config([:instance, :account_activation_required], false)
+ clear_config([:instance, :account_approval_required], false)
+
+ conn =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/apps", %{
+ client_name: "client_name",
+ redirect_uris: "urn:ietf:wg:oauth:2.0:oob",
+ scopes: "read, write, follow"
+ })
+
+ assert %{
+ "client_id" => client_id,
+ "client_secret" => client_secret,
+ "id" => _,
+ "name" => "client_name",
+ "redirect_uri" => "urn:ietf:wg:oauth:2.0:oob",
+ "vapid_key" => _,
+ "website" => nil
+ } = json_response_and_validate_schema(conn, 200)
+
+ conn =
+ post(conn, "/oauth/token", %{
+ grant_type: "client_credentials",
+ client_id: client_id,
+ client_secret: client_secret
+ })
+
+ assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} =
+ json_response(conn, 200)
+
+ assert token
+ token_from_db = Repo.get_by(Token, token: token)
+ assert token_from_db
+ assert refresh
+ assert scope == "read write follow"
+
+ clear_config([User, :email_blacklist], ["example.org"])
+
+ params = %{
+ username: "lain",
+ email: "lain@example.org",
+ password: "PlzDontHackLain",
+ bio: "Test Bio",
+ agreement: true
+ }
+
+ response =
+ build_conn()
+ |> put_req_header("content-type", "multipart/form-data")
+ |> put_req_header("authorization", "Bearer " <> token)
+ |> post("/api/v1/accounts", params)
+ |> json_response_and_validate_schema(400)
+
+ assert response == %{
+ "error" => "Please review the submission",
+ "fields" => %{"email" => ["Invalid email"]},
+ "identifier" => "review_submission"
+ }
+
+ Pleroma.Config.put([User, :email_blacklist], [])
+
+ conn =
+ build_conn()
+ |> put_req_header("content-type", "multipart/form-data")
+ |> put_req_header("authorization", "Bearer " <> token)
+ |> post("/api/v1/accounts", params)
+
+ %{
+ "access_token" => token,
+ "created_at" => _created_at,
+ "scope" => ^scope,
+ "token_type" => "Bearer"
+ } = json_response_and_validate_schema(conn, 200)
+
+ token_from_db = Repo.get_by(Token, token: token)
+ assert token_from_db
+ user = Repo.preload(token_from_db, :user).user
+
+ assert user
+ refute user.confirmation_pending
+ refute user.approval_pending
+ end
+
+ test "registers but does not log in with :account_activation_required", %{conn: conn} do
+ clear_config([:instance, :account_activation_required], true)
+ clear_config([:instance, :account_approval_required], false)
+
+ conn =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/apps", %{
+ client_name: "client_name",
+ redirect_uris: "urn:ietf:wg:oauth:2.0:oob",
+ scopes: "read, write, follow"
+ })
+
+ assert %{
+ "client_id" => client_id,
+ "client_secret" => client_secret,
+ "id" => _,
+ "name" => "client_name",
+ "redirect_uri" => "urn:ietf:wg:oauth:2.0:oob",
+ "vapid_key" => _,
+ "website" => nil
+ } = json_response_and_validate_schema(conn, 200)
+
+ conn =
+ post(conn, "/oauth/token", %{
+ grant_type: "client_credentials",
+ client_id: client_id,
+ client_secret: client_secret
+ })
+
+ assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} =
+ json_response(conn, 200)
+
+ assert token
+ token_from_db = Repo.get_by(Token, token: token)
+ assert token_from_db
+ assert refresh
+ assert scope == "read write follow"
+
+ conn =
+ build_conn()
+ |> put_req_header("content-type", "multipart/form-data")
+ |> put_req_header("authorization", "Bearer " <> token)
+ |> post("/api/v1/accounts", %{
+ username: "lain",
+ email: "lain@example.org",
+ password: "PlzDontHackLain",
+ bio: "Test Bio",
+ agreement: true
+ })
+
+ response = json_response_and_validate_schema(conn, 200)
+ assert %{"identifier" => "missing_confirmed_email"} = response
+ refute response["access_token"]
+ refute response["token_type"]
+
+ user = Repo.get_by(User, email: "lain@example.org")
+ assert user.confirmation_pending
+ end
+
+ test "registers but does not log in with :account_approval_required", %{conn: conn} do
+ clear_config([:instance, :account_approval_required], true)
+ clear_config([:instance, :account_activation_required], false)
+
+ conn =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/apps", %{
+ client_name: "client_name",
+ redirect_uris: "urn:ietf:wg:oauth:2.0:oob",
+ scopes: "read, write, follow"
+ })
+
+ assert %{
+ "client_id" => client_id,
+ "client_secret" => client_secret,
+ "id" => _,
+ "name" => "client_name",
+ "redirect_uri" => "urn:ietf:wg:oauth:2.0:oob",
+ "vapid_key" => _,
+ "website" => nil
+ } = json_response_and_validate_schema(conn, 200)
+
+ conn =
+ post(conn, "/oauth/token", %{
+ grant_type: "client_credentials",
+ client_id: client_id,
+ client_secret: client_secret
+ })
+
+ assert %{"access_token" => token, "refresh_token" => refresh, "scope" => scope} =
+ json_response(conn, 200)
+
+ assert token
+ token_from_db = Repo.get_by(Token, token: token)
+ assert token_from_db
+ assert refresh
+ assert scope == "read write follow"
+
+ conn =
+ build_conn()
+ |> put_req_header("content-type", "multipart/form-data")
+ |> put_req_header("authorization", "Bearer " <> token)
+ |> post("/api/v1/accounts", %{
+ username: "lain",
+ email: "lain@example.org",
+ password: "PlzDontHackLain",
+ bio: "Test Bio",
+ agreement: true,
+ reason: "I'm a cool dude, bro"
+ })
+
+ response = json_response_and_validate_schema(conn, 200)
+ assert %{"identifier" => "awaiting_approval"} = response
+ refute response["access_token"]
+ refute response["token_type"]
+
+ user = Repo.get_by(User, email: "lain@example.org")
+
+ assert user.approval_pending
+ assert user.registration_reason == "I'm a cool dude, bro"
+ end
+
+ test "returns error when user already registred", %{conn: conn, valid_params: valid_params} do
+ _user = insert(:user, email: "lain@example.org")
+ app_token = insert(:oauth_token, user: nil)
+
+ res =
+ conn
+ |> put_req_header("authorization", "Bearer " <> app_token.token)
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/accounts", valid_params)
+ |> json_response_and_validate_schema(400)
+
+ assert res == %{
+ "error" => "Please review the submission",
+ "fields" => %{"email" => ["has already been taken"]},
+ "identifier" => "review_submission"
+ }
+ end
+
+ test "returns bad_request if missing required params", %{
+ conn: conn,
+ valid_params: valid_params
+ } do
+ app_token = insert(:oauth_token, user: nil)
+
+ conn =
+ conn
+ |> put_req_header("authorization", "Bearer " <> app_token.token)
+ |> put_req_header("content-type", "application/json")
+
+ res = post(conn, "/api/v1/accounts", valid_params)
+ assert json_response_and_validate_schema(res, 200)
+
+ [{127, 0, 0, 1}, {127, 0, 0, 2}, {127, 0, 0, 3}, {127, 0, 0, 4}]
+ |> Stream.zip(Map.delete(valid_params, :email))
+ |> Enum.each(fn {ip, {attr, _}} ->
+ res =
+ conn
+ |> Map.put(:remote_ip, ip)
+ |> post("/api/v1/accounts", Map.delete(valid_params, attr))
+ |> json_response_and_validate_schema(400)
+
+ assert res == %{
+ "error" => "Please review the submission",
+ "fields" => %{"#{attr}" => ["Missing field: #{attr}."]},
+ "identifier" => "review_submission"
+ }
+ end)
+ end
+
+ test "returns bad_request if missing email params when :account_activation_required is enabled",
+ %{conn: conn, valid_params: valid_params} do
+ clear_config([:instance, :account_activation_required], true)
+
+ app_token = insert(:oauth_token, user: nil)
+
+ conn =
+ conn
+ |> put_req_header("authorization", "Bearer " <> app_token.token)
+ |> put_req_header("content-type", "application/json")
+
+ res =
+ conn
+ |> Map.put(:remote_ip, {127, 0, 0, 5})
+ |> post("/api/v1/accounts", Map.delete(valid_params, :email))
+ |> json_response_and_validate_schema(400)
+
+ assert res == %{
+ "error" => "Please review the submission",
+ "fields" => %{"email" => ["Missing parameter: email"]},
+ "identifier" => "review_submission"
+ }
+
+ res =
+ conn
+ |> Map.put(:remote_ip, {127, 0, 0, 6})
+ |> post("/api/v1/accounts", Map.put(valid_params, :email, ""))
+ |> json_response_and_validate_schema(400)
+
+ assert res == %{
+ "error" => "Please review the submission",
+ "fields" => %{"email" => ["can't be blank"]},
+ "identifier" => "review_submission"
+ }
+ end
+
+ test "allow registration without an email", %{conn: conn, valid_params: valid_params} do
+ app_token = insert(:oauth_token, user: nil)
+ conn = put_req_header(conn, "authorization", "Bearer " <> app_token.token)
+
+ res =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> Map.put(:remote_ip, {127, 0, 0, 7})
+ |> post("/api/v1/accounts", Map.delete(valid_params, :email))
+
+ assert json_response_and_validate_schema(res, 200)
+ end
+
+ test "allow registration with an empty email", %{conn: conn, valid_params: valid_params} do
+ app_token = insert(:oauth_token, user: nil)
+ conn = put_req_header(conn, "authorization", "Bearer " <> app_token.token)
+
+ res =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> Map.put(:remote_ip, {127, 0, 0, 8})
+ |> post("/api/v1/accounts", Map.put(valid_params, :email, ""))
+
+ assert json_response_and_validate_schema(res, 200)
+ end
+
+ test "returns forbidden if token is invalid", %{conn: conn, valid_params: valid_params} do
+ res =
+ conn
+ |> put_req_header("authorization", "Bearer " <> "invalid-token")
+ |> put_req_header("content-type", "multipart/form-data")
+ |> post("/api/v1/accounts", valid_params)
+
+ assert json_response_and_validate_schema(res, 403) == %{"error" => "Invalid credentials"}
+ end
+
+ test "registration from trusted app" do
+ clear_config([Pleroma.Captcha, :enabled], true)
+ app = insert(:oauth_app, trusted: true, scopes: ["read", "write", "follow", "push"])
+
+ conn =
+ build_conn()
+ |> post("/oauth/token", %{
+ "grant_type" => "client_credentials",
+ "client_id" => app.client_id,
+ "client_secret" => app.client_secret
+ })
+
+ assert %{"access_token" => token, "token_type" => "Bearer"} = json_response(conn, 200)
+
+ response =
+ build_conn()
+ |> Plug.Conn.put_req_header("authorization", "Bearer " <> token)
+ |> put_req_header("content-type", "multipart/form-data")
+ |> post("/api/v1/accounts", %{
+ nickname: "nickanme",
+ agreement: true,
+ email: "email@example.com",
+ fullname: "Lain",
+ username: "Lain",
+ password: "some_password",
+ confirm: "some_password"
+ })
+ |> json_response_and_validate_schema(200)
+
+ assert %{
+ "access_token" => access_token,
+ "created_at" => _,
+ "scope" => "read write follow push",
+ "token_type" => "Bearer"
+ } = response
+
+ response =
+ build_conn()
+ |> Plug.Conn.put_req_header("authorization", "Bearer " <> access_token)
+ |> get("/api/v1/accounts/verify_credentials")
+ |> json_response_and_validate_schema(200)
+
+ assert %{
+ "acct" => "Lain",
+ "bot" => false,
+ "display_name" => "Lain",
+ "follow_requests_count" => 0,
+ "followers_count" => 0,
+ "following_count" => 0,
+ "locked" => false,
+ "note" => "",
+ "source" => %{
+ "fields" => [],
+ "note" => "",
+ "pleroma" => %{
+ "actor_type" => "Person",
+ "discoverable" => false,
+ "no_rich_text" => false,
+ "show_role" => true
+ },
+ "privacy" => "public",
+ "sensitive" => false
+ },
+ "statuses_count" => 0,
+ "username" => "Lain"
+ } = response
+ end
+ end
+
+ describe "create account by app / rate limit" do
+ setup do: clear_config([:rate_limit, :app_account_creation], {10_000, 2})
+
+ test "respects rate limit setting", %{conn: conn} do
+ app_token = insert(:oauth_token, user: nil)
+
+ conn =
+ conn
+ |> put_req_header("authorization", "Bearer " <> app_token.token)
+ |> Map.put(:remote_ip, {15, 15, 15, 15})
+ |> put_req_header("content-type", "multipart/form-data")
+
+ for i <- 1..2 do
+ conn =
+ conn
+ |> post("/api/v1/accounts", %{
+ username: "#{i}lain",
+ email: "#{i}lain@example.org",
+ password: "PlzDontHackLain",
+ agreement: true
+ })
+
+ %{
+ "access_token" => token,
+ "created_at" => _created_at,
+ "scope" => _scope,
+ "token_type" => "Bearer"
+ } = json_response_and_validate_schema(conn, 200)
+
+ token_from_db = Repo.get_by(Token, token: token)
+ assert token_from_db
+ token_from_db = Repo.preload(token_from_db, :user)
+ assert token_from_db.user
+ end
+
+ conn =
+ post(conn, "/api/v1/accounts", %{
+ username: "6lain",
+ email: "6lain@example.org",
+ password: "PlzDontHackLain",
+ agreement: true
+ })
+
+ assert json_response_and_validate_schema(conn, :too_many_requests) == %{
+ "error" => "Throttled"
+ }
+ end
+ end
+
+ describe "create account via invite" 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([:instance, :registrations_open], false)
+ setup do: clear_config([Pleroma.Captcha, :enabled], false)
+
+ test "creates an account", %{conn: conn} do
+ invite = insert(:user_invite_token, %{invite_type: "one_time"})
+
+ params = %{
+ username: "lain",
+ email: "lain@example.org",
+ password: "PlzDontHackLain",
+ agreement: true,
+ token: invite.token
+ }
+
+ res =
+ conn
+ |> post("/api/v1/accounts", params)
+ |> json_response_and_validate_schema(:ok)
+
+ invite = Repo.get_by(UserInviteToken, token: invite.token)
+ assert invite.used == true
+
+ assert %{
+ "access_token" => access_token,
+ "created_at" => _,
+ "scope" => "read",
+ "token_type" => "Bearer"
+ } = res
+
+ user = Repo.get_by(Token, token: access_token) |> Repo.preload(:user) |> Map.get(:user)
+ assert user.email == "lain@example.org"
+ end
+
+ test "returns error when already used", %{conn: conn} do
+ invite = insert(:user_invite_token, %{used: true, invite_type: "one_time"})
+
+ params = %{
+ username: "lain",
+ email: "lain@example.org",
+ password: "PlzDontHackLain",
+ agreement: true,
+ token: invite.token
+ }
+
+ res =
+ conn
+ |> post("/api/v1/accounts", params)
+ |> json_response_and_validate_schema(400)
+
+ assert res == %{
+ "error" => "Please review the submission",
+ "fields" => %{"invite" => ["Expired token"]},
+ "identifier" => "review_submission"
+ }
+ end
+
+ test "returns errors when invite is invalid", %{conn: conn} do
+ params = %{
+ username: "lain",
+ email: "lain@example.org",
+ password: "PlzDontHackLain",
+ agreement: true,
+ token: "fake-token"
+ }
+
+ res =
+ conn
+ |> post("/api/v1/accounts", params)
+ |> json_response_and_validate_schema(400)
+
+ assert res == %{
+ "error" => "Please review the submission",
+ "fields" => %{"invite" => ["Invalid token"]},
+ "identifier" => "review_submission"
+ }
+ 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" => "Please review the submission",
+ "fields" => %{"captcha" => ["Invalid CAPTCHA (Missing parameter: #{field})"]},
+ "identifier" => "review_submission"
+ }
+
+ 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" => "Please review the submission",
+ "fields" => %{"captcha" => ["Invalid answer data"]},
+ "identifier" => "review_submission"
+ } ==
+ conn
+ |> post("/api/v1/accounts", params)
+ |> json_response_and_validate_schema(:bad_request)
+ end
+ end
+
+ describe "api spec errors" 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([:instance, :registrations_open], true)
+ setup do: clear_config([Pleroma.Captcha, :enabled], false)
+
+ test "returns errors when missed required field", %{conn: conn} do
+ params = %{
+ email: "lain@example.org",
+ agreement: true
+ }
+
+ assert %{
+ "error" => "Please review the submission",
+ "fields" => %{
+ "password" => ["Missing field: password."],
+ "username" => ["Missing field: username."]
+ },
+ "identifier" => "review_submission"
+ } ==
+ conn
+ |> post("/api/v1/accounts", params)
+ |> json_response_and_validate_schema(:bad_request)
+ end
+ end
+end
diff --git a/test/pleroma/web/twitter_api/twitter_api_test.exs b/test/pleroma/web/twitter_api/twitter_api_test.exs
index 20a45cb6f..d433b507b 100644
--- a/test/pleroma/web/twitter_api/twitter_api_test.exs
+++ b/test/pleroma/web/twitter_api/twitter_api_test.exs
@@ -183,7 +183,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
{:error, msg} = TwitterAPI.register_user(data)
- assert msg == "Invalid token"
+ assert msg == %{invite: ["Invalid token"]}
refute User.get_cached_by_nickname("GrimReaper")
end
@@ -203,7 +203,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
{:error, msg} = TwitterAPI.register_user(data)
- assert msg == "Expired token"
+ assert msg == %{invite: ["Expired token"]}
refute User.get_cached_by_nickname("GrimReaper")
end
end
@@ -258,7 +258,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
{:error, msg} = TwitterAPI.register_user(data)
- assert msg == "Expired token"
+ assert msg == %{invite: ["Expired token"]}
refute User.get_cached_by_nickname("vinny")
invite = Repo.get_by(UserInviteToken, token: invite.token)
@@ -302,7 +302,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
{:error, msg} = TwitterAPI.register_user(data)
- assert msg == "Expired token"
+ assert msg == %{invite: ["Expired token"]}
refute User.get_cached_by_nickname("GrimReaper")
end
end
@@ -363,7 +363,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
{:error, msg} = TwitterAPI.register_user(data)
- assert msg == "Expired token"
+ assert msg == %{invite: ["Expired token"]}
refute User.get_cached_by_nickname("GrimReaper")
end
@@ -383,7 +383,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
{:error, msg} = TwitterAPI.register_user(data)
- assert msg == "Expired token"
+ assert msg == %{invite: ["Expired token"]}
refute User.get_cached_by_nickname("GrimReaper")
end
@@ -405,7 +405,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
{:error, msg} = TwitterAPI.register_user(data)
- assert msg == "Expired token"
+ assert msg == %{invite: ["Expired token"]}
refute User.get_cached_by_nickname("GrimReaper")
end
end
@@ -420,7 +420,11 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
{:error, error} = TwitterAPI.register_user(data)
- assert is_binary(error)
+ assert error == %{
+ password: ["can't be blank"],
+ password_confirmation: ["can't be blank"]
+ }
+
refute User.get_cached_by_nickname("lain")
end
diff --git a/test/support/factory.ex b/test/support/factory.ex
index fb82be0c4..b4f745413 100644
--- a/test/support/factory.ex
+++ b/test/support/factory.ex
@@ -442,4 +442,13 @@ defmodule Pleroma.Factory do
phrase: "cofe"
}
end
+
+ def user_invite_token_factory do
+ %Pleroma.UserInviteToken{
+ token: Base.url_encode64(:crypto.strong_rand_bytes(32)),
+ used: false,
+ invite_type: "one_time",
+ uses: 0
+ }
+ end
end