# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.AppController do
+ @moduledoc """
+ Controller for supporting app-related actions.
+ If authentication is an option, app tokens (user-unbound) must be supported.
+ """
+
use Pleroma.Web, :controller
alias Pleroma.Repo
plug(
:skip_plug,
[OAuthScopesPlug, EnsurePublicOrAuthenticatedPlug]
- when action == :create
+ when action in [:create, :verify_credentials]
)
- plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :verify_credentials)
-
plug(Pleroma.Web.ApiSpec.CastAndValidate)
@local_mastodon_name "Mastodon-Local"
end
end
- @doc "GET /api/v1/apps/verify_credentials"
- def verify_credentials(%{assigns: %{user: _user, token: token}} = conn, _) do
- with %Token{app: %App{} = app} <- Repo.preload(token, :app) do
- render(conn, "short.json", app: app)
+ @doc """
+ GET /api/v1/apps/verify_credentials
+ Gets compact non-secret representation of the app. Supports app tokens and user tokens.
+ """
+ def verify_credentials(%{assigns: %{token: %Token{} = token}} = conn, _) do
+ with %{app: %App{} = app} <- Repo.preload(token, :app) do
+ render(conn, "compact_non_secret.json", app: app)
end
end
end
|> with_vapid_key()
end
- def render("short.json", %{app: %App{website: webiste, client_name: name}}) do
+ def render("compact_non_secret.json", %{app: %App{website: website, client_name: name}}) do
%{
name: name,
- website: webiste
+ website: website
}
|> with_vapid_key()
end
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.EnsureAuthenticatedPlug do
+ @moduledoc """
+ Ensures _user_ authentication (app-bound user-unbound tokens are not accepted).
+ """
+
import Plug.Conn
import Pleroma.Web.TranslationHelpers
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.EnsurePublicOrAuthenticatedPlug do
+ @moduledoc """
+ Ensures instance publicity or _user_ authentication
+ (app-bound user-unbound tokens are accepted only if the instance is public).
+ """
+
import Pleroma.Web.TranslationHelpers
import Plug.Conn
end
end
+ # App-bound token case (obtained with client_id and client_secret)
+ def call(%{assigns: %{token: %Token{user_id: nil}}} = conn, _) do
+ assign(conn, :user, nil)
+ end
+
def call(conn, _) do
conn
|> assign(:user, nil)
plug(Pleroma.Web.Plugs.EnsureUserTokenAssignsPlug)
end
- pipeline :expect_authentication do
+ # Note: expects _user_ authentication (user-unbound app-bound tokens don't qualify)
+ pipeline :expect_user_authentication do
plug(Pleroma.Web.Plugs.ExpectAuthenticatedCheckPlug)
end
- pipeline :expect_public_instance_or_authentication do
+ # Note: expects public instance or _user_ authentication (user-unbound tokens don't qualify)
+ pipeline :expect_public_instance_or_user_authentication do
plug(Pleroma.Web.Plugs.ExpectPublicOrAuthenticatedCheckPlug)
end
plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)
end
- pipeline :api do
- plug(:expect_public_instance_or_authentication)
+ pipeline :no_auth_or_privacy_expectations_api do
plug(:base_api)
plug(:after_auth)
plug(Pleroma.Web.Plugs.IdempotencyPlug)
end
+ # Pipeline for app-related endpoints (no user auth checks — app-bound tokens must be supported)
+ pipeline :app_api do
+ plug(:no_auth_or_privacy_expectations_api)
+ end
+
+ pipeline :api do
+ plug(:expect_public_instance_or_user_authentication)
+ plug(:no_auth_or_privacy_expectations_api)
+ end
+
pipeline :authenticated_api do
- plug(:expect_authentication)
- plug(:base_api)
- plug(:after_auth)
+ plug(:expect_user_authentication)
+ plug(:no_auth_or_privacy_expectations_api)
plug(Pleroma.Web.Plugs.EnsureAuthenticatedPlug)
- plug(Pleroma.Web.Plugs.IdempotencyPlug)
end
pipeline :admin_api do
- plug(:expect_authentication)
+ plug(:expect_user_authentication)
plug(:base_api)
plug(Pleroma.Web.Plugs.AdminSecretAuthenticationPlug)
plug(:after_auth)
post("/accounts/:id/mute", AccountController, :mute)
post("/accounts/:id/unmute", AccountController, :unmute)
- get("/apps/verify_credentials", AppController, :verify_credentials)
-
get("/conversations", ConversationController, :index)
post("/conversations/:id/read", ConversationController, :mark_as_read)
put("/settings", MastoFEController, :put_settings)
end
+ scope "/api/v1", Pleroma.Web.MastodonAPI do
+ pipe_through(:app_api)
+
+ post("/apps", AppController, :create)
+ get("/apps/verify_credentials", AppController, :verify_credentials)
+ end
+
scope "/api/v1", Pleroma.Web.MastodonAPI do
pipe_through(:api)
get("/instance", InstanceController, :show)
get("/instance/peers", InstanceController, :peers)
- post("/apps", AppController, :create)
-
get("/statuses", StatusController, :index)
get("/statuses/:id", StatusController, :show)
get("/statuses/:id/context", StatusController, :context)
import Pleroma.Factory
test "apps/verify_credentials", %{conn: conn} do
- token = insert(:oauth_token)
+ user_bound_token = insert(:oauth_token)
+ app_bound_token = insert(:oauth_token, user: nil)
+ refute app_bound_token.user
- conn =
- conn
- |> put_req_header("authorization", "Bearer #{token.token}")
- |> get("/api/v1/apps/verify_credentials")
+ for token <- [app_bound_token, user_bound_token] do
+ conn =
+ conn
+ |> put_req_header("authorization", "Bearer #{token.token}")
+ |> get("/api/v1/apps/verify_credentials")
- app = Repo.preload(token, :app).app
+ app = Repo.preload(token, :app).app
- expected = %{
- "name" => app.client_name,
- "website" => app.website,
- "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
- }
+ expected = %{
+ "name" => app.client_name,
+ "website" => app.website,
+ "vapid_key" => Push.vapid_config() |> Keyword.get(:public_key)
+ }
- assert expected == json_response_and_validate_schema(conn, 200)
+ assert expected == json_response_and_validate_schema(conn, 200)
+ end
end
test "creates an oauth app", %{conn: conn} do