logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma
commit: a17bfb5fababbe25fa2abcfccd013c9f3edc2ef7
parent: d8142ee6fee52529f610c58ba15a39b3e841cdc8
Author: Haelwenn <contact+git.pleroma.social@hacktivis.me>
Date:   Fri, 17 Apr 2020 09:19:35 +0000

Merge branch 'feature/1584-client-captcha-options' into 'develop'

Creating trusted app from adminFE & mix task

Closes #1584

See merge request pleroma/pleroma!2252

Diffstat:

MCHANGELOG.md2++
Mdocs/API/admin_api.md102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Adocs/administration/CLI_tasks/oauth_app.md17+++++++++++++++++
Alib/mix/tasks/pleroma/app.ex49+++++++++++++++++++++++++++++++++++++++++++++++++
Mlib/pleroma/web/admin_api/admin_api_controller.ex79+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mlib/pleroma/web/mastodon_api/controllers/account_controller.ex1+
Mlib/pleroma/web/mastodon_api/views/app_view.ex15+++++++++++++++
Mlib/pleroma/web/oauth/app.ex82++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mlib/pleroma/web/router.ex5+++++
Mlib/pleroma/web/twitter_api/twitter_api.ex3++-
Apriv/repo/migrations/20200227122417_add_trusted_to_apps.exs9+++++++++
Mtest/support/factory.ex2+-
Atest/tasks/app_test.exs65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/web/admin_api/admin_api_controller_test.exs185+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/web/mastodon_api/controllers/account_controller_test.exs67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
15 files changed, 680 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md @@ -155,6 +155,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Add an option `authorized_fetch_mode` to require HTTP signatures for AP fetches. - ActivityPub: support for `replies` collection (output for outgoing federation & fetching on incoming federation). - Mix task to refresh counter cache (`mix pleroma.refresh_counter_cache`) +- Mix task to create trusted OAuth App. <details> <summary>API Changes</summary> @@ -201,6 +202,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - ActivityPub: `[:activitypub, :note_replies_output_limit]` setting sets the number of note self-replies to output on outgoing federation. - Admin API: `GET /api/pleroma/admin/stats` to get status count by visibility scope - Admin API: `GET /api/pleroma/admin/statuses` - list all statuses (accepts `godmode` and `local_only`) +- Admin API: endpoints for create/update/delete OAuth Apps. </details> ### Fixed diff --git a/docs/API/admin_api.md b/docs/API/admin_api.md @@ -1088,3 +1088,104 @@ Loads json generated from `config/descriptions.exs`. } } ``` + +## `GET /api/pleroma/admin/oauth_app` + +### List OAuth app + +- Params: + - *optional* `name` + - *optional* `client_id` + - *optional* `page` + - *optional* `page_size` + - *optional* `trusted` + +- Response: + +```json +{ + "apps": [ + { + "id": 1, + "name": "App name", + "client_id": "yHoDSiWYp5mPV6AfsaVOWjdOyt5PhWRiafi6MRd1lSk", + "client_secret": "nLmis486Vqrv2o65eM9mLQx_m_4gH-Q6PcDpGIMl6FY", + "redirect_uri": "https://example.com/oauth-callback", + "website": "https://example.com", + "trusted": true + } + ], + "count": 17, + "page_size": 50 +} +``` + + +## `POST /api/pleroma/admin/oauth_app` + +### Create OAuth App + +- Params: + - `name` + - `redirect_uris` + - `scopes` + - *optional* `website` + - *optional* `trusted` + +- Response: + +```json +{ + "id": 1, + "name": "App name", + "client_id": "yHoDSiWYp5mPV6AfsaVOWjdOyt5PhWRiafi6MRd1lSk", + "client_secret": "nLmis486Vqrv2o65eM9mLQx_m_4gH-Q6PcDpGIMl6FY", + "redirect_uri": "https://example.com/oauth-callback", + "website": "https://example.com", + "trusted": true +} +``` + +- On failure: +```json +{ + "redirect_uris": "can't be blank", + "name": "can't be blank" +} +``` + +## `PATCH /api/pleroma/admin/oauth_app/:id` + +### Update OAuth App + +- Params: + - *optional* `name` + - *optional* `redirect_uris` + - *optional* `scopes` + - *optional* `website` + - *optional* `trusted` + +- Response: + +```json +{ + "id": 1, + "name": "App name", + "client_id": "yHoDSiWYp5mPV6AfsaVOWjdOyt5PhWRiafi6MRd1lSk", + "client_secret": "nLmis486Vqrv2o65eM9mLQx_m_4gH-Q6PcDpGIMl6FY", + "redirect_uri": "https://example.com/oauth-callback", + "website": "https://example.com", + "trusted": true +} +``` + +## `DELETE /api/pleroma/admin/oauth_app/:id` + +### Delete OAuth App + +- Params: None + +- Response: + - On success: `204`, empty response + - On failure: + - 400 Bad Request `"Invalid parameters"` when `status` is missing+ \ No newline at end of file diff --git a/docs/administration/CLI_tasks/oauth_app.md b/docs/administration/CLI_tasks/oauth_app.md @@ -0,0 +1,16 @@ +# Creating trusted OAuth App + +{! backend/administration/CLI_tasks/general_cli_task_info.include !} + +## Create trusted OAuth App. + +Optional params: + * `-s SCOPES` - scopes for app, e.g. `read,write,follow,push`. + +```sh tab="OTP" + ./bin/pleroma_ctl app create -n APP_NAME -r REDIRECT_URI +``` + +```sh tab="From Source" +mix pleroma.app create -n APP_NAME -r REDIRECT_URI +```+ \ No newline at end of file diff --git a/lib/mix/tasks/pleroma/app.ex b/lib/mix/tasks/pleroma/app.ex @@ -0,0 +1,49 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Mix.Tasks.Pleroma.App do + @moduledoc File.read!("docs/administration/CLI_tasks/oauth_app.md") + use Mix.Task + + import Mix.Pleroma + + @shortdoc "Creates trusted OAuth App" + + def run(["create" | options]) do + start_pleroma() + + {opts, _} = + OptionParser.parse!(options, + strict: [name: :string, redirect_uri: :string, scopes: :string], + aliases: [n: :name, r: :redirect_uri, s: :scopes] + ) + + scopes = + if opts[:scopes] do + String.split(opts[:scopes], ",") + else + ["read", "write", "follow", "push"] + end + + params = %{ + client_name: opts[:name], + redirect_uris: opts[:redirect_uri], + trusted: true, + scopes: scopes + } + + with {:ok, app} <- Pleroma.Web.OAuth.App.create(params) do + shell_info("#{app.client_name} successfully created:") + shell_info("App client_id: " <> app.client_id) + shell_info("App client_secret: " <> app.client_secret) + else + {:error, changeset} -> + shell_error("Creating failed:") + + Enum.each(Pleroma.Web.OAuth.App.errors(changeset), fn {key, error} -> + shell_error("#{key}: #{error}") + end) + end + end +end diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -27,7 +27,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do alias Pleroma.Web.AdminAPI.Search alias Pleroma.Web.CommonAPI alias Pleroma.Web.Endpoint + alias Pleroma.Web.MastodonAPI.AppView alias Pleroma.Web.MastodonAPI.StatusView + alias Pleroma.Web.OAuth.App alias Pleroma.Web.Router require Logger @@ -1017,6 +1019,83 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do conn |> json("") end + def oauth_app_create(conn, params) do + params = + if params["name"] do + Map.put(params, "client_name", params["name"]) + else + params + end + + result = + case App.create(params) do + {:ok, app} -> + AppView.render("show.json", %{app: app, admin: true}) + + {:error, changeset} -> + App.errors(changeset) + end + + json(conn, result) + end + + def oauth_app_update(conn, params) do + params = + if params["name"] do + Map.put(params, "client_name", params["name"]) + else + params + end + + with {:ok, app} <- App.update(params) do + json(conn, AppView.render("show.json", %{app: app, admin: true})) + else + {:error, changeset} -> + json(conn, App.errors(changeset)) + + nil -> + json_response(conn, :bad_request, "") + end + end + + def oauth_app_list(conn, params) do + {page, page_size} = page_params(params) + + search_params = %{ + client_name: params["name"], + client_id: params["client_id"], + page: page, + page_size: page_size + } + + search_params = + if Map.has_key?(params, "trusted") do + Map.put(search_params, :trusted, params["trusted"]) + else + search_params + end + + with {:ok, apps, count} <- App.search(search_params) do + json( + conn, + AppView.render("index.json", + apps: apps, + count: count, + page_size: page_size, + admin: true + ) + ) + end + end + + def oauth_app_delete(conn, params) do + with {:ok, _app} <- App.destroy(params["id"]) do + json_response(conn, :no_content, "") + else + _ -> json_response(conn, :bad_request, "") + end + end + def stats(conn, _) do count = Stats.get_status_visibility_count() diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -104,6 +104,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do |> Map.put("fullname", params["fullname"] || nickname) |> Map.put("bio", params["bio"] || "") |> Map.put("confirm", params["password"]) + |> Map.put("trusted_app", app.trusted) with :ok <- validate_email_param(params), {:ok, user} <- TwitterAPI.register_user(params, need_confirmation: true), diff --git a/lib/pleroma/web/mastodon_api/views/app_view.ex b/lib/pleroma/web/mastodon_api/views/app_view.ex @@ -7,6 +7,21 @@ defmodule Pleroma.Web.MastodonAPI.AppView do alias Pleroma.Web.OAuth.App + def render("index.json", %{apps: apps, count: count, page_size: page_size, admin: true}) do + %{ + apps: render_many(apps, Pleroma.Web.MastodonAPI.AppView, "show.json", %{admin: true}), + count: count, + page_size: page_size + } + end + + def render("show.json", %{admin: true, app: %App{} = app} = assigns) do + "show.json" + |> render(Map.delete(assigns, :admin)) + |> Map.put(:trusted, app.trusted) + |> Map.put(:id, app.id) + end + def render("show.json", %{app: %App{} = app}) do %{ id: app.id |> to_string, diff --git a/lib/pleroma/web/oauth/app.ex b/lib/pleroma/web/oauth/app.ex @@ -5,6 +5,7 @@ defmodule Pleroma.Web.OAuth.App do use Ecto.Schema import Ecto.Changeset + import Ecto.Query alias Pleroma.Repo @type t :: %__MODULE__{} @@ -16,14 +17,24 @@ defmodule Pleroma.Web.OAuth.App do field(:website, :string) field(:client_id, :string) field(:client_secret, :string) + field(:trusted, :boolean, default: false) + + has_many(:oauth_authorizations, Pleroma.Web.OAuth.Authorization, on_delete: :delete_all) + has_many(:oauth_tokens, Pleroma.Web.OAuth.Token, on_delete: :delete_all) timestamps() end + @spec changeset(App.t(), map()) :: Ecto.Changeset.t() + def changeset(struct, params) do + cast(struct, params, [:client_name, :redirect_uris, :scopes, :website, :trusted]) + end + + @spec register_changeset(App.t(), map()) :: Ecto.Changeset.t() def register_changeset(struct, params \\ %{}) do changeset = struct - |> cast(params, [:client_name, :redirect_uris, :scopes, :website]) + |> changeset(params) |> validate_required([:client_name, :redirect_uris, :scopes]) if changeset.valid? do @@ -41,6 +52,21 @@ defmodule Pleroma.Web.OAuth.App do end end + @spec create(map()) :: {:ok, App.t()} | {:error, Ecto.Changeset.t()} + def create(params) do + with changeset <- __MODULE__.register_changeset(%__MODULE__{}, params) do + Repo.insert(changeset) + end + end + + @spec update(map()) :: {:ok, App.t()} | {:error, Ecto.Changeset.t()} + def update(params) do + with %__MODULE__{} = app <- Repo.get(__MODULE__, params["id"]), + changeset <- changeset(app, params) do + Repo.update(changeset) + end + end + @doc """ Gets app by attrs or create new with attrs. And updates the scopes if need. @@ -65,4 +91,58 @@ defmodule Pleroma.Web.OAuth.App do |> change(%{scopes: scopes}) |> Repo.update() end + + @spec search(map()) :: {:ok, [App.t()], non_neg_integer()} + def search(params) do + query = from(a in __MODULE__) + + query = + if params[:client_name] do + from(a in query, where: a.client_name == ^params[:client_name]) + else + query + end + + query = + if params[:client_id] do + from(a in query, where: a.client_id == ^params[:client_id]) + else + query + end + + query = + if Map.has_key?(params, :trusted) do + from(a in query, where: a.trusted == ^params[:trusted]) + else + query + end + + query = + from(u in query, + limit: ^params[:page_size], + offset: ^((params[:page] - 1) * params[:page_size]) + ) + + count = Repo.aggregate(__MODULE__, :count, :id) + + {:ok, Repo.all(query), count} + end + + @spec destroy(pos_integer()) :: {:ok, App.t()} | {:error, Ecto.Changeset.t()} + def destroy(id) do + with %__MODULE__{} = app <- Repo.get(__MODULE__, id) do + Repo.delete(app) + end + end + + @spec errors(Ecto.Changeset.t()) :: map() + def errors(changeset) do + Enum.reduce(changeset.errors, %{}, fn + {:client_name, {error, _}}, acc -> + Map.put(acc, :name, error) + + {key, {error, _}}, acc -> + Map.put(acc, key, error) + end) + end end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex @@ -211,6 +211,11 @@ defmodule Pleroma.Web.Router do post("/reload_emoji", AdminAPIController, :reload_emoji) get("/stats", AdminAPIController, :stats) + + get("/oauth_app", AdminAPIController, :oauth_app_list) + post("/oauth_app", AdminAPIController, :oauth_app_create) + patch("/oauth_app/:id", AdminAPIController, :oauth_app_update) + delete("/oauth_app/:id", AdminAPIController, :oauth_app_delete) end scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -13,6 +13,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do def register_user(params, opts \\ []) do token = params["token"] + trusted_app? = params["trusted_app"] params = %{ nickname: params["nickname"], @@ -29,7 +30,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do captcha_enabled = Pleroma.Config.get([Pleroma.Captcha, :enabled]) # true if captcha is disabled or enabled and valid, false otherwise captcha_ok = - if not captcha_enabled do + if trusted_app? || not captcha_enabled do :ok else Pleroma.Captcha.validate( diff --git a/priv/repo/migrations/20200227122417_add_trusted_to_apps.exs b/priv/repo/migrations/20200227122417_add_trusted_to_apps.exs @@ -0,0 +1,9 @@ +defmodule Pleroma.Repo.Migrations.AddTrustedToApps do + use Ecto.Migration + + def change do + alter table(:apps) do + add(:trusted, :boolean, default: false) + end + end +end diff --git a/test/support/factory.ex b/test/support/factory.ex @@ -294,7 +294,7 @@ defmodule Pleroma.Factory do def oauth_app_factory do %Pleroma.Web.OAuth.App{ - client_name: "Some client", + client_name: sequence(:client_name, &"Some client #{&1}"), redirect_uris: "https://example.com/callback", scopes: ["read", "write", "follow", "push", "admin"], website: "https://example.com", diff --git a/test/tasks/app_test.exs b/test/tasks/app_test.exs @@ -0,0 +1,65 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Mix.Tasks.Pleroma.AppTest do + use Pleroma.DataCase, async: true + + setup_all do + Mix.shell(Mix.Shell.Process) + + on_exit(fn -> + Mix.shell(Mix.Shell.IO) + end) + end + + describe "creates new app" do + test "with default scopes" do + name = "Some name" + redirect = "https://example.com" + Mix.Tasks.Pleroma.App.run(["create", "-n", name, "-r", redirect]) + + assert_app(name, redirect, ["read", "write", "follow", "push"]) + end + + test "with custom scopes" do + name = "Another name" + redirect = "https://example.com" + + Mix.Tasks.Pleroma.App.run([ + "create", + "-n", + name, + "-r", + redirect, + "-s", + "read,write,follow,push,admin" + ]) + + assert_app(name, redirect, ["read", "write", "follow", "push", "admin"]) + end + end + + test "with errors" do + Mix.Tasks.Pleroma.App.run(["create"]) + {:mix_shell, :error, ["Creating failed:"]} + {:mix_shell, :error, ["name: can't be blank"]} + {:mix_shell, :error, ["redirect_uris: can't be blank"]} + end + + defp assert_app(name, redirect, scopes) do + app = Repo.get_by(Pleroma.Web.OAuth.App, client_name: name) + + assert_received {:mix_shell, :info, [message]} + assert message == "#{name} successfully created:" + + assert_received {:mix_shell, :info, [message]} + assert message == "App client_id: #{app.client_id}" + + assert_received {:mix_shell, :info, [message]} + assert message == "App client_secret: #{app.client_secret}" + + assert app.scopes == scopes + assert app.redirect_uris == redirect + end +end diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs @@ -3517,6 +3517,191 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do response["status_visibility"] end end + + describe "POST /api/pleroma/admin/oauth_app" do + test "errors", %{conn: conn} do + response = conn |> post("/api/pleroma/admin/oauth_app", %{}) |> json_response(200) + + assert response == %{"name" => "can't be blank", "redirect_uris" => "can't be blank"} + end + + test "success", %{conn: conn} do + base_url = Pleroma.Web.base_url() + app_name = "Trusted app" + + response = + conn + |> post("/api/pleroma/admin/oauth_app", %{ + name: app_name, + redirect_uris: base_url + }) + |> json_response(200) + + assert %{ + "client_id" => _, + "client_secret" => _, + "name" => ^app_name, + "redirect_uri" => ^base_url, + "trusted" => false + } = response + end + + test "with trusted", %{conn: conn} do + base_url = Pleroma.Web.base_url() + app_name = "Trusted app" + + response = + conn + |> post("/api/pleroma/admin/oauth_app", %{ + name: app_name, + redirect_uris: base_url, + trusted: true + }) + |> json_response(200) + + assert %{ + "client_id" => _, + "client_secret" => _, + "name" => ^app_name, + "redirect_uri" => ^base_url, + "trusted" => true + } = response + end + end + + describe "GET /api/pleroma/admin/oauth_app" do + setup do + app = insert(:oauth_app) + {:ok, app: app} + end + + test "list", %{conn: conn} do + response = + conn + |> get("/api/pleroma/admin/oauth_app") + |> json_response(200) + + assert %{"apps" => apps, "count" => count, "page_size" => _} = response + + assert length(apps) == count + end + + test "with page size", %{conn: conn} do + insert(:oauth_app) + page_size = 1 + + response = + conn + |> get("/api/pleroma/admin/oauth_app", %{page_size: to_string(page_size)}) + |> json_response(200) + + assert %{"apps" => apps, "count" => _, "page_size" => ^page_size} = response + + assert length(apps) == page_size + end + + test "search by client name", %{conn: conn, app: app} do + response = + conn + |> get("/api/pleroma/admin/oauth_app", %{name: app.client_name}) + |> json_response(200) + + assert %{"apps" => [returned], "count" => _, "page_size" => _} = response + + assert returned["client_id"] == app.client_id + assert returned["name"] == app.client_name + end + + test "search by client id", %{conn: conn, app: app} do + response = + conn + |> get("/api/pleroma/admin/oauth_app", %{client_id: app.client_id}) + |> json_response(200) + + assert %{"apps" => [returned], "count" => _, "page_size" => _} = response + + assert returned["client_id"] == app.client_id + assert returned["name"] == app.client_name + end + + test "only trusted", %{conn: conn} do + app = insert(:oauth_app, trusted: true) + + response = + conn + |> get("/api/pleroma/admin/oauth_app", %{trusted: true}) + |> json_response(200) + + assert %{"apps" => [returned], "count" => _, "page_size" => _} = response + + assert returned["client_id"] == app.client_id + assert returned["name"] == app.client_name + end + end + + describe "DELETE /api/pleroma/admin/oauth_app/:id" do + test "with id", %{conn: conn} do + app = insert(:oauth_app) + + response = + conn + |> delete("/api/pleroma/admin/oauth_app/" <> to_string(app.id)) + |> json_response(:no_content) + + assert response == "" + end + + test "with non existance id", %{conn: conn} do + response = + conn + |> delete("/api/pleroma/admin/oauth_app/0") + |> json_response(:bad_request) + + assert response == "" + end + end + + describe "PATCH /api/pleroma/admin/oauth_app/:id" do + test "with id", %{conn: conn} do + app = insert(:oauth_app) + + name = "another name" + url = "https://example.com" + scopes = ["admin"] + id = app.id + website = "http://website.com" + + response = + conn + |> patch("/api/pleroma/admin/oauth_app/" <> to_string(app.id), %{ + name: name, + trusted: true, + redirect_uris: url, + scopes: scopes, + website: website + }) + |> json_response(200) + + assert %{ + "client_id" => _, + "client_secret" => _, + "id" => ^id, + "name" => ^name, + "redirect_uri" => ^url, + "trusted" => true, + "website" => ^website + } = response + end + + test "without id", %{conn: conn} do + response = + conn + |> patch("/api/pleroma/admin/oauth_app/0") + |> json_response(:bad_request) + + assert response == "" + end + end end # Needed for testing diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs @@ -944,6 +944,73 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do res = post(conn, "/api/v1/accounts", valid_params) assert json_response(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) + |> post("/api/v1/accounts", %{ + nickname: "nickanme", + agreement: true, + email: "email@example.com", + fullname: "Lain", + username: "Lain", + password: "some_password", + confirm: "some_password" + }) + |> json_response(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(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