updated registration API responses

This commit is contained in:
Maksim Pechnikov 2020-10-13 22:20:26 +03:00
parent 481906207e
commit 3607e453e4
10 changed files with 880 additions and 618 deletions

View File

@ -0,0 +1,43 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# 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

View File

@ -0,0 +1,33 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# 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

View File

@ -34,7 +34,7 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
requestBody: request_body("Parameters", create_request(), required: true), requestBody: request_body("Parameters", create_request(), required: true),
responses: %{ responses: %{
200 => Operation.response("Account", "application/json", create_response()), 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), 403 => Operation.response("Error", "application/json", ApiError),
429 => Operation.response("Error", "application/json", ApiError) 429 => Operation.response("Error", "application/json", ApiError)
} }
@ -453,6 +453,35 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do
} }
end 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. # Note: this is a token response (if login succeeds!), but there's no oauth operation file yet.
defp create_response do defp create_response do
%Schema{ %Schema{

View File

@ -47,14 +47,14 @@ defmodule Pleroma.Web.ApiSpec.RenderError do
} }
end 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}.", gettext("%{name} - Invalid schema.type. Got: %{type}.",
name: name, name: name,
type: inspect(type) type: inspect(type)
) )
end end
defp message(%{reason: :null_value, name: name} = error) do def message(%{reason: :null_value, name: name} = error) do
case error.type do case error.type do
nil -> nil ->
gettext("%{name} - null value.", name: name) gettext("%{name} - null value.", name: name)
@ -67,42 +67,42 @@ defmodule Pleroma.Web.ApiSpec.RenderError do
end end
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( gettext(
"Failed to cast value as %{invalid_schema}. Value must be castable using `allOf` schemas listed.", "Failed to cast value as %{invalid_schema}. Value must be castable using `allOf` schemas listed.",
invalid_schema: invalid_schema invalid_schema: invalid_schema
) )
end 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}.", gettext("Failed to cast value using any of: %{failed_schemas}.",
failed_schemas: failed_schemas failed_schemas: failed_schemas
) )
end 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) gettext("Failed to cast value to one of: %{failed_schemas}.", failed_schemas: failed_schemas)
end 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}.", gettext("%{name} - String length is smaller than minLength: %{length}.",
name: name, name: name,
length: length length: length
) )
end 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}.", gettext("%{name} - String length is larger than maxLength: %{length}.",
name: name, name: name,
length: length length: length
) )
end 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) gettext("%{name} - Array items must be unique.", name: name)
end 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}.", gettext("%{name} - Array length %{length} is smaller than minItems: %{min}.",
name: name, name: name,
length: length(array), length: length(array),
@ -110,7 +110,7 @@ defmodule Pleroma.Web.ApiSpec.RenderError do
) )
end 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: %{}.", gettext("%{name} - Array length %{length} is larger than maxItems: %{}.",
name: name, name: name,
length: length(array), length: length(array),
@ -118,7 +118,7 @@ defmodule Pleroma.Web.ApiSpec.RenderError do
) )
end 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}.", gettext("%{name} - %{count} is not a multiple of %{multiple}.",
name: name, name: name,
count: count, count: count,
@ -126,8 +126,8 @@ defmodule Pleroma.Web.ApiSpec.RenderError do
) )
end end
defp message(%{reason: :exclusive_max, length: max, value: value, name: name}) def message(%{reason: :exclusive_max, length: max, value: value, name: name})
when value >= max do when value >= max do
gettext("%{name} - %{value} is larger than exclusive maximum %{max}.", gettext("%{name} - %{value} is larger than exclusive maximum %{max}.",
name: name, name: name,
value: value, value: value,
@ -135,8 +135,8 @@ defmodule Pleroma.Web.ApiSpec.RenderError do
) )
end end
defp message(%{reason: :maximum, length: max, value: value, name: name}) def message(%{reason: :maximum, length: max, value: value, name: name})
when value > max do when value > max do
gettext("%{name} - %{value} is larger than inclusive maximum %{max}.", gettext("%{name} - %{value} is larger than inclusive maximum %{max}.",
name: name, name: name,
value: value, value: value,
@ -144,8 +144,8 @@ defmodule Pleroma.Web.ApiSpec.RenderError do
) )
end end
defp message(%{reason: :exclusive_multiple, length: min, value: value, name: name}) def message(%{reason: :exclusive_multiple, length: min, value: value, name: name})
when value <= min do when value <= min do
gettext("%{name} - %{value} is smaller than exclusive minimum %{min}.", gettext("%{name} - %{value} is smaller than exclusive minimum %{min}.",
name: name, name: name,
value: value, value: value,
@ -153,8 +153,8 @@ defmodule Pleroma.Web.ApiSpec.RenderError do
) )
end end
defp message(%{reason: :minimum, length: min, value: value, name: name}) def message(%{reason: :minimum, length: min, value: value, name: name})
when value < min do when value < min do
gettext("%{name} - %{value} is smaller than inclusive minimum %{min}.", gettext("%{name} - %{value} is smaller than inclusive minimum %{min}.",
name: name, name: name,
value: value, value: value,
@ -162,7 +162,7 @@ defmodule Pleroma.Web.ApiSpec.RenderError do
) )
end 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}.", gettext("%{name} - Invalid %{type}. Got: %{value}.",
name: name, name: name,
value: OpenApiSpex.TermType.type(value), value: OpenApiSpex.TermType.type(value),
@ -170,49 +170,49 @@ defmodule Pleroma.Web.ApiSpec.RenderError do
) )
end 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)) gettext("%{name} - Invalid format. Expected %{format}.", name: name, format: inspect(format))
end 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) gettext("%{name} - Invalid value for enum.", name: name)
end 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}", gettext("Failed to cast to any schema in %{polymorphic_type}",
polymorphic_type: polymorphic_type polymorphic_type: polymorphic_type
) )
end 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)) gettext("Unexpected field: %{name}.", name: safe_string(name))
end 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) gettext("Value used as discriminator for `%{field}` matches no schemas.", name: field)
end 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) gettext("No value provided for required discriminator `%{field}`.", name: field)
end end
defp message(%{reason: :unknown_schema, name: name}) do def message(%{reason: :unknown_schema, name: name}) do
gettext("Unknown schema: %{name}.", name: name) gettext("Unknown schema: %{name}.", name: name)
end end
defp message(%{reason: :missing_field, name: name}) do def message(%{reason: :missing_field, name: name}) do
gettext("Missing field: %{name}.", name: name) gettext("Missing field: %{name}.", name: name)
end end
defp message(%{reason: :missing_header, name: name}) do def message(%{reason: :missing_header, name: name}) do
gettext("Missing header: %{name}.", name: name) gettext("Missing header: %{name}.", name: name)
end 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) gettext("Invalid value for header: %{name}.", name: name)
end end
defp message(%{reason: :max_properties, meta: meta}) do def message(%{reason: :max_properties, meta: meta}) do
gettext( gettext(
"Object property count %{property_count} is greater than maxProperties: %{max_properties}.", "Object property count %{property_count} is greater than maxProperties: %{max_properties}.",
property_count: meta.property_count, property_count: meta.property_count,
@ -220,7 +220,7 @@ defmodule Pleroma.Web.ApiSpec.RenderError do
) )
end end
defp message(%{reason: :min_properties, meta: meta}) do def message(%{reason: :min_properties, meta: meta}) do
gettext( gettext(
"Object property count %{property_count} is less than minProperties: %{min_properties}", "Object property count %{property_count} is less than minProperties: %{min_properties}",
property_count: meta.property_count, property_count: meta.property_count,

View File

@ -19,6 +19,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Builder alias Pleroma.Web.ActivityPub.Builder
alias Pleroma.Web.ActivityPub.Pipeline alias Pleroma.Web.ActivityPub.Pipeline
alias Pleroma.Web.ApiSpec.Errors.RegistrationUserError
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.ListView alias Pleroma.Web.MastodonAPI.ListView
alias Pleroma.Web.MastodonAPI.MastodonAPI alias Pleroma.Web.MastodonAPI.MastodonAPI
@ -31,7 +32,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
alias Pleroma.Web.Plugs.RateLimiter alias Pleroma.Web.Plugs.RateLimiter
alias Pleroma.Web.TwitterAPI.TwitterAPI 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) plug(:skip_plug, [OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug] when action == :create)
@ -101,8 +107,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
with :ok <- validate_email_param(params), with :ok <- validate_email_param(params),
:ok <- TwitterAPI.validate_captcha(app, params), :ok <- TwitterAPI.validate_captcha(app, params),
{:ok, user} <- TwitterAPI.register_user(params), {:ok, user} <- TwitterAPI.register_user(params),
{_, {:ok, token}} <- {_, {:ok, token}} <- {:login, OAuthController.login(user, app, app.scopes)} do
{:login, OAuthController.login(user, app, app.scopes)} do
json(conn, OAuthView.render("token.json", %{user: user, token: token})) json(conn, OAuthView.render("token.json", %{user: user, token: token}))
else else
{:login, {:account_status, :confirmation_pending}} -> {:login, {:account_status, :confirmation_pending}} ->
@ -126,8 +131,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
identifier: "manual_login_required" identifier: "manual_login_required"
}) })
{:error, error} -> {:error, errors} ->
json_response(conn, :bad_request, %{error: error}) json_response(conn, :bad_request, %{
identifier: "review_submission",
error: "Please review the submission",
fields: errors
})
end end
end end
@ -143,8 +152,14 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
defp validate_email_param(_) do defp validate_email_param(_) do
case Pleroma.Config.get([:instance, :account_activation_required]) do case Pleroma.Config.get([:instance, :account_activation_required]) do
true -> {:error, dgettext("errors", "Missing parameter: %{name}", name: "email")} true ->
_ -> :ok {:error,
%{
email: [dgettext("errors", "Missing parameter: %{name}", name: "email")]
}}
_ ->
:ok
end end
end end

View File

@ -3,14 +3,12 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.TwitterAPI.TwitterAPI do defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
import Pleroma.Web.Gettext
alias Pleroma.Emails.Mailer alias Pleroma.Emails.Mailer
alias Pleroma.Emails.UserEmail alias Pleroma.Emails.UserEmail
alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
alias Pleroma.UserInviteToken alias Pleroma.UserInviteToken
@spec register_user(map(), keyword()) :: {:ok, User.t()} | {:error, map()}
def register_user(params, opts \\ []) do def register_user(params, opts \\ []) do
params = params =
params params
@ -28,18 +26,20 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
end end
end end
@spec create_user_with_invite(map(), keyword()) :: {:ok, User.t()} | {:error, map()}
defp create_user_with_invite(params, opts) do defp create_user_with_invite(params, opts) do
with %{token: token} when is_binary(token) <- params, 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 true <- UserInviteToken.valid_invite?(invite) do
UserInviteToken.update_usage!(invite) UserInviteToken.update_usage!(invite)
create_user(params, opts) create_user(params, opts)
else else
nil -> {:error, "Invalid token"} nil -> {:error, %{invite: ["Invalid token"]}}
_ -> {:error, "Expired token"} _ -> {:error, %{invite: ["Expired token"]}}
end end
end end
@spec create_user(map(), keyword()) :: {:ok, User.t()} | {:error, map()}
defp create_user(params, opts) do defp create_user(params, opts) do
changeset = User.register_changeset(%User{}, params, opts) changeset = User.register_changeset(%User{}, params, opts)
@ -52,7 +52,6 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
errors = errors =
changeset changeset
|> Ecto.Changeset.traverse_errors(fn {msg, _opts} -> msg end) |> Ecto.Changeset.traverse_errors(fn {msg, _opts} -> msg end)
|> Jason.encode!()
{:error, errors} {:error, errors}
end end
@ -104,26 +103,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
) do ) do
:ok :ok
else 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} -> {:error, error} ->
captcha_error(error) {:error, %{captcha: [Pleroma.Captcha.Error.message(error)]}}
end end
end end
@ -131,12 +112,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
[:captcha_solution, :captcha_token, :captcha_answer_data] [:captcha_solution, :captcha_token, :captcha_answer_data]
|> Enum.find_value(:ok, fn key -> |> Enum.find_value(:ok, fn key ->
unless is_binary(params[key]) do unless is_binary(params[key]) do
error = dgettext("errors", "Invalid CAPTCHA (Missing parameter: %{name})", name: key) {:error, Pleroma.Captcha.Error.message(:missing_field, %{name: key})}
{:error, error}
end end
end) 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 end

View File

@ -5,12 +5,10 @@
defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do
use Pleroma.Web.ConnCase use Pleroma.Web.ConnCase
alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.InternalFetchActor alias Pleroma.Web.ActivityPub.InternalFetchActor
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
alias Pleroma.Web.OAuth.Token
import Pleroma.Factory 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) assert %{"id" => _id, "blocking" => false} = json_response_and_validate_schema(conn, 200)
end 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 describe "GET /api/v1/accounts/:id/lists - account_lists" do
test "returns lists to which the account belongs" do test "returns lists to which the account belongs" do
%{user: user, conn: conn} = oauth_access(["read:lists"]) %{user: user, conn: conn} = oauth_access(["read:lists"])

View File

@ -0,0 +1,691 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# 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

View File

@ -183,7 +183,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
{:error, msg} = TwitterAPI.register_user(data) {:error, msg} = TwitterAPI.register_user(data)
assert msg == "Invalid token" assert msg == %{invite: ["Invalid token"]}
refute User.get_cached_by_nickname("GrimReaper") refute User.get_cached_by_nickname("GrimReaper")
end end
@ -203,7 +203,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
{:error, msg} = TwitterAPI.register_user(data) {:error, msg} = TwitterAPI.register_user(data)
assert msg == "Expired token" assert msg == %{invite: ["Expired token"]}
refute User.get_cached_by_nickname("GrimReaper") refute User.get_cached_by_nickname("GrimReaper")
end end
end end
@ -258,7 +258,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
{:error, msg} = TwitterAPI.register_user(data) {:error, msg} = TwitterAPI.register_user(data)
assert msg == "Expired token" assert msg == %{invite: ["Expired token"]}
refute User.get_cached_by_nickname("vinny") refute User.get_cached_by_nickname("vinny")
invite = Repo.get_by(UserInviteToken, token: invite.token) invite = Repo.get_by(UserInviteToken, token: invite.token)
@ -302,7 +302,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
{:error, msg} = TwitterAPI.register_user(data) {:error, msg} = TwitterAPI.register_user(data)
assert msg == "Expired token" assert msg == %{invite: ["Expired token"]}
refute User.get_cached_by_nickname("GrimReaper") refute User.get_cached_by_nickname("GrimReaper")
end end
end end
@ -363,7 +363,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
{:error, msg} = TwitterAPI.register_user(data) {:error, msg} = TwitterAPI.register_user(data)
assert msg == "Expired token" assert msg == %{invite: ["Expired token"]}
refute User.get_cached_by_nickname("GrimReaper") refute User.get_cached_by_nickname("GrimReaper")
end end
@ -383,7 +383,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
{:error, msg} = TwitterAPI.register_user(data) {:error, msg} = TwitterAPI.register_user(data)
assert msg == "Expired token" assert msg == %{invite: ["Expired token"]}
refute User.get_cached_by_nickname("GrimReaper") refute User.get_cached_by_nickname("GrimReaper")
end end
@ -405,7 +405,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
{:error, msg} = TwitterAPI.register_user(data) {:error, msg} = TwitterAPI.register_user(data)
assert msg == "Expired token" assert msg == %{invite: ["Expired token"]}
refute User.get_cached_by_nickname("GrimReaper") refute User.get_cached_by_nickname("GrimReaper")
end end
end end
@ -420,7 +420,11 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
{:error, error} = TwitterAPI.register_user(data) {: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") refute User.get_cached_by_nickname("lain")
end end

View File

@ -442,4 +442,13 @@ defmodule Pleroma.Factory do
phrase: "cofe" phrase: "cofe"
} }
end 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 end