summaryrefslogtreecommitdiff
path: root/lib/pleroma/web/oauth
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pleroma/web/oauth')
-rw-r--r--lib/pleroma/web/oauth/app.ex149
-rw-r--r--lib/pleroma/web/oauth/authorization.ex95
-rw-r--r--lib/pleroma/web/oauth/fallback_controller.ex32
-rw-r--r--lib/pleroma/web/oauth/mfa_controller.ex98
-rw-r--r--lib/pleroma/web/oauth/mfa_view.ex17
-rw-r--r--lib/pleroma/web/oauth/oauth_controller.ex610
-rw-r--r--lib/pleroma/web/oauth/oauth_view.ex30
-rw-r--r--lib/pleroma/web/oauth/scopes.ex76
-rw-r--r--lib/pleroma/web/oauth/token.ex126
-rw-r--r--lib/pleroma/web/oauth/token/clean_worker.ex38
-rw-r--r--lib/pleroma/web/oauth/token/query.ex55
-rw-r--r--lib/pleroma/web/oauth/token/strategy/refresh_token.ex58
-rw-r--r--lib/pleroma/web/oauth/token/strategy/revoke.ex26
-rw-r--r--lib/pleroma/web/oauth/token/utils.ex72
14 files changed, 0 insertions, 1482 deletions
diff --git a/lib/pleroma/web/oauth/app.ex b/lib/pleroma/web/oauth/app.ex
deleted file mode 100644
index df99472e1..000000000
--- a/lib/pleroma/web/oauth/app.ex
+++ /dev/null
@@ -1,149 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.OAuth.App do
- use Ecto.Schema
- import Ecto.Changeset
- import Ecto.Query
- alias Pleroma.Repo
-
- @type t :: %__MODULE__{}
-
- schema "apps" do
- field(:client_name, :string)
- field(:redirect_uris, :string)
- field(:scopes, {:array, :string}, default: [])
- 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(t(), map()) :: Ecto.Changeset.t()
- def changeset(struct, params) do
- cast(struct, params, [:client_name, :redirect_uris, :scopes, :website, :trusted])
- end
-
- @spec register_changeset(t(), map()) :: Ecto.Changeset.t()
- def register_changeset(struct, params \\ %{}) do
- changeset =
- struct
- |> changeset(params)
- |> validate_required([:client_name, :redirect_uris, :scopes])
-
- if changeset.valid? do
- changeset
- |> put_change(
- :client_id,
- :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false)
- )
- |> put_change(
- :client_secret,
- :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false)
- )
- else
- changeset
- end
- end
-
- @spec create(map()) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
- def create(params) do
- %__MODULE__{}
- |> register_changeset(params)
- |> Repo.insert()
- end
-
- @spec update(pos_integer(), map()) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
- def update(id, params) do
- with %__MODULE__{} = app <- Repo.get(__MODULE__, id) do
- app
- |> changeset(params)
- |> Repo.update()
- end
- end
-
- @doc """
- Gets app by attrs or create new with attrs.
- And updates the scopes if need.
- """
- @spec get_or_make(map(), list(String.t())) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
- def get_or_make(attrs, scopes) do
- with %__MODULE__{} = app <- Repo.get_by(__MODULE__, attrs) do
- update_scopes(app, scopes)
- else
- _e ->
- %__MODULE__{}
- |> register_changeset(Map.put(attrs, :scopes, scopes))
- |> Repo.insert()
- end
- end
-
- defp update_scopes(%__MODULE__{} = app, []), do: {:ok, app}
- defp update_scopes(%__MODULE__{scopes: scopes} = app, scopes), do: {:ok, app}
-
- defp update_scopes(%__MODULE__{} = app, scopes) do
- app
- |> change(%{scopes: scopes})
- |> Repo.update()
- end
-
- @spec search(map()) :: {:ok, [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, 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/oauth/authorization.ex b/lib/pleroma/web/oauth/authorization.ex
deleted file mode 100644
index 268ee5b63..000000000
--- a/lib/pleroma/web/oauth/authorization.ex
+++ /dev/null
@@ -1,95 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.OAuth.Authorization do
- use Ecto.Schema
-
- alias Pleroma.Repo
- alias Pleroma.User
- alias Pleroma.Web.OAuth.App
- alias Pleroma.Web.OAuth.Authorization
-
- import Ecto.Changeset
- import Ecto.Query
-
- @type t :: %__MODULE__{}
-
- schema "oauth_authorizations" do
- field(:token, :string)
- field(:scopes, {:array, :string}, default: [])
- field(:valid_until, :naive_datetime_usec)
- field(:used, :boolean, default: false)
- belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
- belongs_to(:app, App)
-
- timestamps()
- end
-
- @spec create_authorization(App.t(), User.t() | %{}, [String.t()] | nil) ::
- {:ok, Authorization.t()} | {:error, Changeset.t()}
- def create_authorization(%App{} = app, %User{} = user, scopes \\ nil) do
- %{
- scopes: scopes || app.scopes,
- user_id: user.id,
- app_id: app.id
- }
- |> create_changeset()
- |> Repo.insert()
- end
-
- @spec create_changeset(map()) :: Changeset.t()
- def create_changeset(attrs \\ %{}) do
- %Authorization{}
- |> cast(attrs, [:user_id, :app_id, :scopes, :valid_until])
- |> validate_required([:app_id, :scopes])
- |> add_token()
- |> add_lifetime()
- end
-
- defp add_token(changeset) do
- token = :crypto.strong_rand_bytes(32) |> Base.url_encode64(padding: false)
- put_change(changeset, :token, token)
- end
-
- defp add_lifetime(changeset) do
- put_change(changeset, :valid_until, NaiveDateTime.add(NaiveDateTime.utc_now(), 60 * 10))
- end
-
- @spec use_changeset(Authtorizatiton.t(), map()) :: Changeset.t()
- def use_changeset(%Authorization{} = auth, params) do
- auth
- |> cast(params, [:used])
- |> validate_required([:used])
- end
-
- @spec use_token(Authorization.t()) ::
- {:ok, Authorization.t()} | {:error, Changeset.t()} | {:error, String.t()}
- def use_token(%Authorization{used: false, valid_until: valid_until} = auth) do
- if NaiveDateTime.diff(NaiveDateTime.utc_now(), valid_until) < 0 do
- Repo.update(use_changeset(auth, %{used: true}))
- else
- {:error, "token expired"}
- end
- end
-
- def use_token(%Authorization{used: true}), do: {:error, "already used"}
-
- @spec delete_user_authorizations(User.t()) :: {integer(), any()}
- def delete_user_authorizations(%User{} = user) do
- user
- |> delete_by_user_query
- |> Repo.delete_all()
- end
-
- def delete_by_user_query(%User{id: user_id}) do
- from(a in __MODULE__, where: a.user_id == ^user_id)
- end
-
- @doc "gets auth for app by token"
- @spec get_by_token(App.t(), String.t()) :: {:ok, t()} | {:error, :not_found}
- def get_by_token(%App{id: app_id} = _app, token) do
- from(t in __MODULE__, where: t.app_id == ^app_id and t.token == ^token)
- |> Repo.find_resource()
- end
-end
diff --git a/lib/pleroma/web/oauth/fallback_controller.ex b/lib/pleroma/web/oauth/fallback_controller.ex
deleted file mode 100644
index a89ced886..000000000
--- a/lib/pleroma/web/oauth/fallback_controller.ex
+++ /dev/null
@@ -1,32 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.OAuth.FallbackController do
- use Pleroma.Web, :controller
- alias Pleroma.Web.OAuth.OAuthController
-
- def call(conn, {:register, :generic_error}) do
- conn
- |> put_status(:internal_server_error)
- |> put_flash(
- :error,
- dgettext("errors", "Unknown error, please check the details and try again.")
- )
- |> OAuthController.registration_details(conn.params)
- end
-
- def call(conn, {:register, _error}) do
- conn
- |> put_status(:unauthorized)
- |> put_flash(:error, dgettext("errors", "Invalid Username/Password"))
- |> OAuthController.registration_details(conn.params)
- end
-
- def call(conn, _error) do
- conn
- |> put_status(:unauthorized)
- |> put_flash(:error, dgettext("errors", "Invalid Username/Password"))
- |> OAuthController.authorize(conn.params)
- end
-end
diff --git a/lib/pleroma/web/oauth/mfa_controller.ex b/lib/pleroma/web/oauth/mfa_controller.ex
deleted file mode 100644
index f102c93e7..000000000
--- a/lib/pleroma/web/oauth/mfa_controller.ex
+++ /dev/null
@@ -1,98 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.OAuth.MFAController do
- @moduledoc """
- The model represents api to use Multi Factor authentications.
- """
-
- use Pleroma.Web, :controller
-
- alias Pleroma.MFA
- alias Pleroma.Web.Auth.TOTPAuthenticator
- alias Pleroma.Web.OAuth.MFAView, as: View
- alias Pleroma.Web.OAuth.OAuthController
- alias Pleroma.Web.OAuth.OAuthView
- alias Pleroma.Web.OAuth.Token
-
- plug(:fetch_session when action in [:show, :verify])
- plug(:fetch_flash when action in [:show, :verify])
-
- @doc """
- Display form to input mfa code or recovery code.
- """
- def show(conn, %{"mfa_token" => mfa_token} = params) do
- template = Map.get(params, "challenge_type", "totp")
-
- conn
- |> put_view(View)
- |> render("#{template}.html", %{
- mfa_token: mfa_token,
- redirect_uri: params["redirect_uri"],
- state: params["state"]
- })
- end
-
- @doc """
- Verification code and continue authorization.
- """
- def verify(conn, %{"mfa" => %{"mfa_token" => mfa_token} = mfa_params} = _) do
- with {:ok, %{user: user, authorization: auth}} <- MFA.Token.validate(mfa_token),
- {:ok, _} <- validates_challenge(user, mfa_params) do
- conn
- |> OAuthController.after_create_authorization(auth, %{
- "authorization" => %{
- "redirect_uri" => mfa_params["redirect_uri"],
- "state" => mfa_params["state"]
- }
- })
- else
- _ ->
- conn
- |> put_flash(:error, "Two-factor authentication failed.")
- |> put_status(:unauthorized)
- |> show(mfa_params)
- end
- end
-
- @doc """
- Verification second step of MFA (or recovery) and returns access token.
-
- ## Endpoint
- POST /oauth/mfa/challenge
-
- params:
- `client_id`
- `client_secret`
- `mfa_token` - access token to check second step of mfa
- `challenge_type` - 'totp' or 'recovery'
- `code`
-
- """
- def challenge(conn, %{"mfa_token" => mfa_token} = params) do
- with {:ok, app} <- Token.Utils.fetch_app(conn),
- {:ok, %{user: user, authorization: auth}} <- MFA.Token.validate(mfa_token),
- {:ok, _} <- validates_challenge(user, params),
- {:ok, token} <- Token.exchange_token(app, auth) do
- json(conn, OAuthView.render("token.json", %{user: user, token: token}))
- else
- _error ->
- conn
- |> put_status(400)
- |> json(%{error: "Invalid code"})
- end
- end
-
- # Verify TOTP Code
- defp validates_challenge(user, %{"challenge_type" => "totp", "code" => code} = _) do
- TOTPAuthenticator.verify(code, user)
- end
-
- # Verify Recovery Code
- defp validates_challenge(user, %{"challenge_type" => "recovery", "code" => code} = _) do
- TOTPAuthenticator.verify_recovery_code(user, code)
- end
-
- defp validates_challenge(_, _), do: {:error, :unsupported_challenge_type}
-end
diff --git a/lib/pleroma/web/oauth/mfa_view.ex b/lib/pleroma/web/oauth/mfa_view.ex
deleted file mode 100644
index 5d87db268..000000000
--- a/lib/pleroma/web/oauth/mfa_view.ex
+++ /dev/null
@@ -1,17 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.OAuth.MFAView do
- use Pleroma.Web, :view
- import Phoenix.HTML.Form
- alias Pleroma.MFA
-
- def render("mfa_response.json", %{token: token, user: user}) do
- %{
- error: "mfa_required",
- mfa_token: token.token,
- supported_challenge_types: MFA.supported_methods(user)
- }
- end
-end
diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex
deleted file mode 100644
index 06b116368..000000000
--- a/lib/pleroma/web/oauth/oauth_controller.ex
+++ /dev/null
@@ -1,610 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.OAuth.OAuthController do
- use Pleroma.Web, :controller
-
- alias Pleroma.Helpers.UriHelper
- alias Pleroma.Maps
- alias Pleroma.MFA
- alias Pleroma.Plugs.RateLimiter
- alias Pleroma.Registration
- alias Pleroma.Repo
- alias Pleroma.User
- alias Pleroma.Web.Auth.Authenticator
- alias Pleroma.Web.ControllerHelper
- alias Pleroma.Web.OAuth.App
- alias Pleroma.Web.OAuth.Authorization
- alias Pleroma.Web.OAuth.MFAController
- alias Pleroma.Web.OAuth.MFAView
- alias Pleroma.Web.OAuth.OAuthView
- alias Pleroma.Web.OAuth.Scopes
- alias Pleroma.Web.OAuth.Token
- alias Pleroma.Web.OAuth.Token.Strategy.RefreshToken
- alias Pleroma.Web.OAuth.Token.Strategy.Revoke, as: RevokeToken
-
- require Logger
-
- if Pleroma.Config.oauth_consumer_enabled?(), do: plug(Ueberauth)
-
- plug(:fetch_session)
- plug(:fetch_flash)
-
- plug(:skip_plug, [Pleroma.Plugs.OAuthScopesPlug, Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug])
-
- plug(RateLimiter, [name: :authentication] when action == :create_authorization)
-
- action_fallback(Pleroma.Web.OAuth.FallbackController)
-
- @oob_token_redirect_uri "urn:ietf:wg:oauth:2.0:oob"
-
- # Note: this definition is only called from error-handling methods with `conn.params` as 2nd arg
- def authorize(%Plug.Conn{} = conn, %{"authorization" => _} = params) do
- {auth_attrs, params} = Map.pop(params, "authorization")
- authorize(conn, Map.merge(params, auth_attrs))
- end
-
- def authorize(%Plug.Conn{assigns: %{token: %Token{}}} = conn, %{"force_login" => _} = params) do
- if ControllerHelper.truthy_param?(params["force_login"]) do
- do_authorize(conn, params)
- else
- handle_existing_authorization(conn, params)
- end
- end
-
- # Note: the token is set in oauth_plug, but the token and client do not always go together.
- # For example, MastodonFE's token is set if user requests with another client,
- # after user already authorized to MastodonFE.
- # So we have to check client and token.
- def authorize(
- %Plug.Conn{assigns: %{token: %Token{} = token}} = conn,
- %{"client_id" => client_id} = params
- ) do
- with %Token{} = t <- Repo.get_by(Token, token: token.token) |> Repo.preload(:app),
- ^client_id <- t.app.client_id do
- handle_existing_authorization(conn, params)
- else
- _ -> do_authorize(conn, params)
- end
- end
-
- def authorize(%Plug.Conn{} = conn, params), do: do_authorize(conn, params)
-
- defp do_authorize(%Plug.Conn{} = conn, params) do
- app = Repo.get_by(App, client_id: params["client_id"])
- available_scopes = (app && app.scopes) || []
- scopes = Scopes.fetch_scopes(params, available_scopes)
-
- scopes =
- if scopes == [] do
- available_scopes
- else
- scopes
- end
-
- # Note: `params` might differ from `conn.params`; use `@params` not `@conn.params` in template
- render(conn, Authenticator.auth_template(), %{
- response_type: params["response_type"],
- client_id: params["client_id"],
- available_scopes: available_scopes,
- scopes: scopes,
- redirect_uri: params["redirect_uri"],
- state: params["state"],
- params: params
- })
- end
-
- defp handle_existing_authorization(
- %Plug.Conn{assigns: %{token: %Token{} = token}} = conn,
- %{"redirect_uri" => @oob_token_redirect_uri}
- ) do
- render(conn, "oob_token_exists.html", %{token: token})
- end
-
- defp handle_existing_authorization(
- %Plug.Conn{assigns: %{token: %Token{} = token}} = conn,
- %{} = params
- ) do
- app = Repo.preload(token, :app).app
-
- redirect_uri =
- if is_binary(params["redirect_uri"]) do
- params["redirect_uri"]
- else
- default_redirect_uri(app)
- end
-
- if redirect_uri in String.split(app.redirect_uris) do
- redirect_uri = redirect_uri(conn, redirect_uri)
- url_params = %{access_token: token.token}
- url_params = Maps.put_if_present(url_params, :state, params["state"])
- url = UriHelper.append_uri_params(redirect_uri, url_params)
- redirect(conn, external: url)
- else
- conn
- |> put_flash(:error, dgettext("errors", "Unlisted redirect_uri."))
- |> redirect(external: redirect_uri(conn, redirect_uri))
- end
- end
-
- def create_authorization(
- %Plug.Conn{} = conn,
- %{"authorization" => _} = params,
- opts \\ []
- ) do
- with {:ok, auth, user} <- do_create_authorization(conn, params, opts[:user]),
- {:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)} do
- after_create_authorization(conn, auth, params)
- else
- error ->
- handle_create_authorization_error(conn, error, params)
- end
- end
-
- def after_create_authorization(%Plug.Conn{} = conn, %Authorization{} = auth, %{
- "authorization" => %{"redirect_uri" => @oob_token_redirect_uri}
- }) do
- # Enforcing the view to reuse the template when calling from other controllers
- conn
- |> put_view(OAuthView)
- |> render("oob_authorization_created.html", %{auth: auth})
- end
-
- def after_create_authorization(%Plug.Conn{} = conn, %Authorization{} = auth, %{
- "authorization" => %{"redirect_uri" => redirect_uri} = auth_attrs
- }) do
- app = Repo.preload(auth, :app).app
-
- # An extra safety measure before we redirect (also done in `do_create_authorization/2`)
- if redirect_uri in String.split(app.redirect_uris) do
- redirect_uri = redirect_uri(conn, redirect_uri)
- url_params = %{code: auth.token}
- url_params = Maps.put_if_present(url_params, :state, auth_attrs["state"])
- url = UriHelper.append_uri_params(redirect_uri, url_params)
- redirect(conn, external: url)
- else
- conn
- |> put_flash(:error, dgettext("errors", "Unlisted redirect_uri."))
- |> redirect(external: redirect_uri(conn, redirect_uri))
- end
- end
-
- defp handle_create_authorization_error(
- %Plug.Conn{} = conn,
- {:error, scopes_issue},
- %{"authorization" => _} = params
- )
- when scopes_issue in [:unsupported_scopes, :missing_scopes] do
- # Per https://github.com/tootsuite/mastodon/blob/
- # 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L39
- conn
- |> put_flash(:error, dgettext("errors", "This action is outside the authorized scopes"))
- |> put_status(:unauthorized)
- |> authorize(params)
- end
-
- defp handle_create_authorization_error(
- %Plug.Conn{} = conn,
- {:account_status, :confirmation_pending},
- %{"authorization" => _} = params
- ) do
- conn
- |> put_flash(:error, dgettext("errors", "Your login is missing a confirmed e-mail address"))
- |> put_status(:forbidden)
- |> authorize(params)
- end
-
- defp handle_create_authorization_error(
- %Plug.Conn{} = conn,
- {:mfa_required, user, auth, _},
- params
- ) do
- {:ok, token} = MFA.Token.create_token(user, auth)
-
- data = %{
- "mfa_token" => token.token,
- "redirect_uri" => params["authorization"]["redirect_uri"],
- "state" => params["authorization"]["state"]
- }
-
- MFAController.show(conn, data)
- end
-
- defp handle_create_authorization_error(
- %Plug.Conn{} = conn,
- {:account_status, :password_reset_pending},
- %{"authorization" => _} = params
- ) do
- conn
- |> put_flash(:error, dgettext("errors", "Password reset is required"))
- |> put_status(:forbidden)
- |> authorize(params)
- end
-
- defp handle_create_authorization_error(
- %Plug.Conn{} = conn,
- {:account_status, :deactivated},
- %{"authorization" => _} = params
- ) do
- conn
- |> put_flash(:error, dgettext("errors", "Your account is currently disabled"))
- |> put_status(:forbidden)
- |> authorize(params)
- end
-
- defp handle_create_authorization_error(%Plug.Conn{} = conn, error, %{"authorization" => _}) do
- Authenticator.handle_error(conn, error)
- end
-
- @doc "Renew access_token with refresh_token"
- def token_exchange(
- %Plug.Conn{} = conn,
- %{"grant_type" => "refresh_token", "refresh_token" => token} = _params
- ) do
- with {:ok, app} <- Token.Utils.fetch_app(conn),
- {:ok, %{user: user} = token} <- Token.get_by_refresh_token(app, token),
- {:ok, token} <- RefreshToken.grant(token) do
- json(conn, OAuthView.render("token.json", %{user: user, token: token}))
- else
- _error -> render_invalid_credentials_error(conn)
- end
- end
-
- def token_exchange(%Plug.Conn{} = conn, %{"grant_type" => "authorization_code"} = params) do
- with {:ok, app} <- Token.Utils.fetch_app(conn),
- fixed_token = Token.Utils.fix_padding(params["code"]),
- {:ok, auth} <- Authorization.get_by_token(app, fixed_token),
- %User{} = user <- User.get_cached_by_id(auth.user_id),
- {:ok, token} <- Token.exchange_token(app, auth) do
- json(conn, OAuthView.render("token.json", %{user: user, token: token}))
- else
- error ->
- handle_token_exchange_error(conn, error)
- end
- end
-
- def token_exchange(
- %Plug.Conn{} = conn,
- %{"grant_type" => "password"} = params
- ) do
- with {:ok, %User{} = user} <- Authenticator.get_user(conn),
- {:ok, app} <- Token.Utils.fetch_app(conn),
- requested_scopes <- Scopes.fetch_scopes(params, app.scopes),
- {:ok, token} <- login(user, app, requested_scopes) do
- json(conn, OAuthView.render("token.json", %{user: user, token: token}))
- else
- error ->
- handle_token_exchange_error(conn, error)
- end
- end
-
- def token_exchange(
- %Plug.Conn{} = conn,
- %{"grant_type" => "password", "name" => name, "password" => _password} = params
- ) do
- params =
- params
- |> Map.delete("name")
- |> Map.put("username", name)
-
- token_exchange(conn, params)
- end
-
- def token_exchange(%Plug.Conn{} = conn, %{"grant_type" => "client_credentials"} = _params) do
- with {:ok, app} <- Token.Utils.fetch_app(conn),
- {:ok, auth} <- Authorization.create_authorization(app, %User{}),
- {:ok, token} <- Token.exchange_token(app, auth) do
- json(conn, OAuthView.render("token.json", %{token: token}))
- else
- _error ->
- handle_token_exchange_error(conn, :invalid_credentails)
- end
- end
-
- # Bad request
- def token_exchange(%Plug.Conn{} = conn, params), do: bad_request(conn, params)
-
- defp handle_token_exchange_error(%Plug.Conn{} = conn, {:mfa_required, user, auth, _}) do
- conn
- |> put_status(:forbidden)
- |> json(build_and_response_mfa_token(user, auth))
- end
-
- defp handle_token_exchange_error(%Plug.Conn{} = conn, {:account_status, :deactivated}) do
- render_error(
- conn,
- :forbidden,
- "Your account is currently disabled",
- %{},
- "account_is_disabled"
- )
- end
-
- defp handle_token_exchange_error(
- %Plug.Conn{} = conn,
- {:account_status, :password_reset_pending}
- ) do
- render_error(
- conn,
- :forbidden,
- "Password reset is required",
- %{},
- "password_reset_required"
- )
- end
-
- defp handle_token_exchange_error(%Plug.Conn{} = conn, {:account_status, :confirmation_pending}) do
- render_error(
- conn,
- :forbidden,
- "Your login is missing a confirmed e-mail address",
- %{},
- "missing_confirmed_email"
- )
- end
-
- defp handle_token_exchange_error(%Plug.Conn{} = conn, {:account_status, :approval_pending}) do
- render_error(
- conn,
- :forbidden,
- "Your account is awaiting approval.",
- %{},
- "awaiting_approval"
- )
- end
-
- defp handle_token_exchange_error(%Plug.Conn{} = conn, _error) do
- render_invalid_credentials_error(conn)
- end
-
- def token_revoke(%Plug.Conn{} = conn, %{"token" => _token} = params) do
- with {:ok, app} <- Token.Utils.fetch_app(conn),
- {:ok, _token} <- RevokeToken.revoke(app, params) do
- json(conn, %{})
- else
- _error ->
- # RFC 7009: invalid tokens [in the request] do not cause an error response
- json(conn, %{})
- end
- end
-
- def token_revoke(%Plug.Conn{} = conn, params), do: bad_request(conn, params)
-
- # Response for bad request
- defp bad_request(%Plug.Conn{} = conn, _) do
- render_error(conn, :internal_server_error, "Bad request")
- end
-
- @doc "Prepares OAuth request to provider for Ueberauth"
- def prepare_request(%Plug.Conn{} = conn, %{
- "provider" => provider,
- "authorization" => auth_attrs
- }) do
- scope =
- auth_attrs
- |> Scopes.fetch_scopes([])
- |> Scopes.to_string()
-
- state =
- auth_attrs
- |> Map.delete("scopes")
- |> Map.put("scope", scope)
- |> Jason.encode!()
-
- params =
- auth_attrs
- |> Map.drop(~w(scope scopes client_id redirect_uri))
- |> Map.put("state", state)
-
- # Handing the request to Ueberauth
- redirect(conn, to: o_auth_path(conn, :request, provider, params))
- end
-
- def request(%Plug.Conn{} = conn, params) do
- message =
- if params["provider"] do
- dgettext("errors", "Unsupported OAuth provider: %{provider}.",
- provider: params["provider"]
- )
- else
- dgettext("errors", "Bad OAuth request.")
- end
-
- conn
- |> put_flash(:error, message)
- |> redirect(to: "/")
- end
-
- def callback(%Plug.Conn{assigns: %{ueberauth_failure: failure}} = conn, params) do
- params = callback_params(params)
- messages = for e <- Map.get(failure, :errors, []), do: e.message
- message = Enum.join(messages, "; ")
-
- conn
- |> put_flash(
- :error,
- dgettext("errors", "Failed to authenticate: %{message}.", message: message)
- )
- |> redirect(external: redirect_uri(conn, params["redirect_uri"]))
- end
-
- def callback(%Plug.Conn{} = conn, params) do
- params = callback_params(params)
-
- with {:ok, registration} <- Authenticator.get_registration(conn) do
- auth_attrs = Map.take(params, ~w(client_id redirect_uri scope scopes state))
-
- case Repo.get_assoc(registration, :user) do
- {:ok, user} ->
- create_authorization(conn, %{"authorization" => auth_attrs}, user: user)
-
- _ ->
- registration_params =
- Map.merge(auth_attrs, %{
- "nickname" => Registration.nickname(registration),
- "email" => Registration.email(registration)
- })
-
- conn
- |> put_session_registration_id(registration.id)
- |> registration_details(%{"authorization" => registration_params})
- end
- else
- error ->
- Logger.debug(inspect(["OAUTH_ERROR", error, conn.assigns]))
-
- conn
- |> put_flash(:error, dgettext("errors", "Failed to set up user account."))
- |> redirect(external: redirect_uri(conn, params["redirect_uri"]))
- end
- end
-
- defp callback_params(%{"state" => state} = params) do
- Map.merge(params, Jason.decode!(state))
- end
-
- def registration_details(%Plug.Conn{} = conn, %{"authorization" => auth_attrs}) do
- render(conn, "register.html", %{
- client_id: auth_attrs["client_id"],
- redirect_uri: auth_attrs["redirect_uri"],
- state: auth_attrs["state"],
- scopes: Scopes.fetch_scopes(auth_attrs, []),
- nickname: auth_attrs["nickname"],
- email: auth_attrs["email"]
- })
- end
-
- def register(%Plug.Conn{} = conn, %{"authorization" => _, "op" => "connect"} = params) do
- with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn),
- %Registration{} = registration <- Repo.get(Registration, registration_id),
- {_, {:ok, auth, _user}} <-
- {:create_authorization, do_create_authorization(conn, params)},
- %User{} = user <- Repo.preload(auth, :user).user,
- {:ok, _updated_registration} <- Registration.bind_to_user(registration, user) do
- conn
- |> put_session_registration_id(nil)
- |> after_create_authorization(auth, params)
- else
- {:create_authorization, error} ->
- {:register, handle_create_authorization_error(conn, error, params)}
-
- _ ->
- {:register, :generic_error}
- end
- end
-
- def register(%Plug.Conn{} = conn, %{"authorization" => _, "op" => "register"} = params) do
- with registration_id when not is_nil(registration_id) <- get_session_registration_id(conn),
- %Registration{} = registration <- Repo.get(Registration, registration_id),
- {:ok, user} <- Authenticator.create_from_registration(conn, registration) do
- conn
- |> put_session_registration_id(nil)
- |> create_authorization(
- params,
- user: user
- )
- else
- {:error, changeset} ->
- message =
- Enum.map(changeset.errors, fn {field, {error, _}} ->
- "#{field} #{error}"
- end)
- |> Enum.join("; ")
-
- message =
- String.replace(
- message,
- "ap_id has already been taken",
- "nickname has already been taken"
- )
-
- conn
- |> put_status(:forbidden)
- |> put_flash(:error, "Error: #{message}.")
- |> registration_details(params)
-
- _ ->
- {:register, :generic_error}
- end
- end
-
- defp do_create_authorization(conn, auth_attrs, user \\ nil)
-
- defp do_create_authorization(
- %Plug.Conn{} = conn,
- %{
- "authorization" =>
- %{
- "client_id" => client_id,
- "redirect_uri" => redirect_uri
- } = auth_attrs
- },
- user
- ) do
- with {_, {:ok, %User{} = user}} <-
- {:get_user, (user && {:ok, user}) || Authenticator.get_user(conn)},
- %App{} = app <- Repo.get_by(App, client_id: client_id),
- true <- redirect_uri in String.split(app.redirect_uris),
- requested_scopes <- Scopes.fetch_scopes(auth_attrs, app.scopes),
- {:ok, auth} <- do_create_authorization(user, app, requested_scopes) do
- {:ok, auth, user}
- end
- end
-
- defp do_create_authorization(%User{} = user, %App{} = app, requested_scopes)
- when is_list(requested_scopes) do
- with {:account_status, :active} <- {:account_status, User.account_status(user)},
- {:ok, scopes} <- validate_scopes(app, requested_scopes),
- {:ok, auth} <- Authorization.create_authorization(app, user, scopes) do
- {:ok, auth}
- end
- end
-
- # Note: intended to be a private function but opened for AccountController that logs in on signup
- @doc "If checks pass, creates authorization and token for given user, app and requested scopes."
- def login(%User{} = user, %App{} = app, requested_scopes) when is_list(requested_scopes) do
- with {:ok, auth} <- do_create_authorization(user, app, requested_scopes),
- {:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)},
- {:ok, token} <- Token.exchange_token(app, auth) do
- {:ok, token}
- end
- end
-
- # Special case: Local MastodonFE
- defp redirect_uri(%Plug.Conn{} = conn, "."), do: auth_url(conn, :login)
-
- defp redirect_uri(%Plug.Conn{}, redirect_uri), do: redirect_uri
-
- defp get_session_registration_id(%Plug.Conn{} = conn), do: get_session(conn, :registration_id)
-
- defp put_session_registration_id(%Plug.Conn{} = conn, registration_id),
- do: put_session(conn, :registration_id, registration_id)
-
- defp build_and_response_mfa_token(user, auth) do
- with {:ok, token} <- MFA.Token.create_token(user, auth) do
- MFAView.render("mfa_response.json", %{token: token, user: user})
- end
- end
-
- @spec validate_scopes(App.t(), map() | list()) ::
- {:ok, list()} | {:error, :missing_scopes | :unsupported_scopes}
- defp validate_scopes(%App{} = app, params) when is_map(params) do
- requested_scopes = Scopes.fetch_scopes(params, app.scopes)
- validate_scopes(app, requested_scopes)
- end
-
- defp validate_scopes(%App{} = app, requested_scopes) when is_list(requested_scopes) do
- Scopes.validate(requested_scopes, app.scopes)
- end
-
- def default_redirect_uri(%App{} = app) do
- app.redirect_uris
- |> String.split()
- |> Enum.at(0)
- end
-
- defp render_invalid_credentials_error(conn) do
- render_error(conn, :bad_request, "Invalid credentials")
- end
-end
diff --git a/lib/pleroma/web/oauth/oauth_view.ex b/lib/pleroma/web/oauth/oauth_view.ex
deleted file mode 100644
index f55247ebd..000000000
--- a/lib/pleroma/web/oauth/oauth_view.ex
+++ /dev/null
@@ -1,30 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.OAuth.OAuthView do
- use Pleroma.Web, :view
- import Phoenix.HTML.Form
-
- alias Pleroma.Web.OAuth.Token.Utils
-
- def render("token.json", %{token: token} = opts) do
- response = %{
- token_type: "Bearer",
- access_token: token.token,
- refresh_token: token.refresh_token,
- expires_in: expires_in(),
- scope: Enum.join(token.scopes, " "),
- created_at: Utils.format_created_at(token)
- }
-
- if user = opts[:user] do
- response
- |> Map.put(:me, user.ap_id)
- else
- response
- end
- end
-
- defp expires_in, do: Pleroma.Config.get([:oauth2, :token_expires_in], 600)
-end
diff --git a/lib/pleroma/web/oauth/scopes.ex b/lib/pleroma/web/oauth/scopes.ex
deleted file mode 100644
index 6f06f1431..000000000
--- a/lib/pleroma/web/oauth/scopes.ex
+++ /dev/null
@@ -1,76 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.OAuth.Scopes do
- @moduledoc """
- Functions for dealing with scopes.
- """
-
- alias Pleroma.Plugs.OAuthScopesPlug
-
- @doc """
- Fetch scopes from request params.
-
- Note: `scopes` is used by Mastodon — supporting it but sticking to
- OAuth's standard `scope` wherever we control it
- """
- @spec fetch_scopes(map() | struct(), list()) :: list()
-
- def fetch_scopes(params, default) do
- parse_scopes(params["scope"] || params["scopes"] || params[:scopes], default)
- end
-
- def parse_scopes(scopes, _default) when is_list(scopes) do
- Enum.filter(scopes, &(&1 not in [nil, ""]))
- end
-
- def parse_scopes(scopes, default) when is_binary(scopes) do
- scopes
- |> to_list
- |> parse_scopes(default)
- end
-
- def parse_scopes(_, default) do
- default
- end
-
- @doc """
- Convert scopes string to list
- """
- @spec to_list(binary()) :: [binary()]
- def to_list(nil), do: []
-
- def to_list(str) do
- str
- |> String.trim()
- |> String.split(~r/[\s,]+/)
- end
-
- @doc """
- Convert scopes list to string
- """
- @spec to_string(list()) :: binary()
- def to_string(scopes), do: Enum.join(scopes, " ")
-
- @doc """
- Validates scopes.
- """
- @spec validate(list() | nil, list()) ::
- {:ok, list()} | {:error, :missing_scopes | :unsupported_scopes}
- def validate(blank_scopes, _app_scopes) when blank_scopes in [nil, []],
- do: {:error, :missing_scopes}
-
- def validate(scopes, app_scopes) do
- case OAuthScopesPlug.filter_descendants(scopes, app_scopes) do
- ^scopes -> {:ok, scopes}
- _ -> {:error, :unsupported_scopes}
- end
- end
-
- def contains_admin_scopes?(scopes) do
- scopes
- |> OAuthScopesPlug.filter_descendants(["admin"])
- |> Enum.any?()
- end
-end
diff --git a/lib/pleroma/web/oauth/token.ex b/lib/pleroma/web/oauth/token.ex
deleted file mode 100644
index 08bb7326d..000000000
--- a/lib/pleroma/web/oauth/token.ex
+++ /dev/null
@@ -1,126 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.OAuth.Token do
- use Ecto.Schema
-
- import Ecto.Changeset
-
- alias Pleroma.Repo
- alias Pleroma.User
- alias Pleroma.Web.OAuth.App
- alias Pleroma.Web.OAuth.Authorization
- alias Pleroma.Web.OAuth.Token
- alias Pleroma.Web.OAuth.Token.Query
-
- @type t :: %__MODULE__{}
-
- schema "oauth_tokens" do
- field(:token, :string)
- field(:refresh_token, :string)
- field(:scopes, {:array, :string}, default: [])
- field(:valid_until, :naive_datetime_usec)
- belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
- belongs_to(:app, App)
-
- timestamps()
- end
-
- @doc "Gets token for app by access token"
- @spec get_by_token(App.t(), String.t()) :: {:ok, t()} | {:error, :not_found}
- def get_by_token(%App{id: app_id} = _app, token) do
- Query.get_by_app(app_id)
- |> Query.get_by_token(token)
- |> Repo.find_resource()
- end
-
- @doc "Gets token for app by refresh token"
- @spec get_by_refresh_token(App.t(), String.t()) :: {:ok, t()} | {:error, :not_found}
- def get_by_refresh_token(%App{id: app_id} = _app, token) do
- Query.get_by_app(app_id)
- |> Query.get_by_refresh_token(token)
- |> Query.preload([:user])
- |> Repo.find_resource()
- end
-
- @spec exchange_token(App.t(), Authorization.t()) :: {:ok, Token.t()} | {:error, Changeset.t()}
- def exchange_token(app, auth) do
- with {:ok, auth} <- Authorization.use_token(auth),
- true <- auth.app_id == app.id do
- user = if auth.user_id, do: User.get_cached_by_id(auth.user_id), else: %User{}
-
- create_token(
- app,
- user,
- %{scopes: auth.scopes}
- )
- end
- end
-
- defp put_token(changeset) do
- changeset
- |> change(%{token: Token.Utils.generate_token()})
- |> validate_required([:token])
- |> unique_constraint(:token)
- end
-
- defp put_refresh_token(changeset, attrs) do
- refresh_token = Map.get(attrs, :refresh_token, Token.Utils.generate_token())
-
- changeset
- |> change(%{refresh_token: refresh_token})
- |> validate_required([:refresh_token])
- |> unique_constraint(:refresh_token)
- end
-
- defp put_valid_until(changeset, attrs) do
- expires_in =
- Map.get(attrs, :valid_until, NaiveDateTime.add(NaiveDateTime.utc_now(), expires_in()))
-
- changeset
- |> change(%{valid_until: expires_in})
- |> validate_required([:valid_until])
- end
-
- @spec create_token(App.t(), User.t(), map()) :: {:ok, Token} | {:error, Changeset.t()}
- def create_token(%App{} = app, %User{} = user, attrs \\ %{}) do
- %__MODULE__{user_id: user.id, app_id: app.id}
- |> cast(%{scopes: attrs[:scopes] || app.scopes}, [:scopes])
- |> validate_required([:scopes, :app_id])
- |> put_valid_until(attrs)
- |> put_token()
- |> put_refresh_token(attrs)
- |> Repo.insert()
- end
-
- def delete_user_tokens(%User{id: user_id}) do
- Query.get_by_user(user_id)
- |> Repo.delete_all()
- end
-
- def delete_user_token(%User{id: user_id}, token_id) do
- Query.get_by_user(user_id)
- |> Query.get_by_id(token_id)
- |> Repo.delete_all()
- end
-
- def delete_expired_tokens do
- Query.get_expired_tokens()
- |> Repo.delete_all()
- end
-
- def get_user_tokens(%User{id: user_id}) do
- Query.get_by_user(user_id)
- |> Query.preload([:app])
- |> Repo.all()
- end
-
- def is_expired?(%__MODULE__{valid_until: valid_until}) do
- NaiveDateTime.diff(NaiveDateTime.utc_now(), valid_until) > 0
- end
-
- def is_expired?(_), do: false
-
- defp expires_in, do: Pleroma.Config.get([:oauth2, :token_expires_in], 600)
-end
diff --git a/lib/pleroma/web/oauth/token/clean_worker.ex b/lib/pleroma/web/oauth/token/clean_worker.ex
deleted file mode 100644
index e3aa4eb7e..000000000
--- a/lib/pleroma/web/oauth/token/clean_worker.ex
+++ /dev/null
@@ -1,38 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.OAuth.Token.CleanWorker do
- @moduledoc """
- The module represents functions to clean an expired OAuth and MFA tokens.
- """
- use GenServer
-
- @ten_seconds 10_000
- @one_day 86_400_000
-
- alias Pleroma.MFA
- alias Pleroma.Web.OAuth
- alias Pleroma.Workers.BackgroundWorker
-
- def start_link(_), do: GenServer.start_link(__MODULE__, %{})
-
- def init(_) do
- Process.send_after(self(), :perform, @ten_seconds)
- {:ok, nil}
- end
-
- @doc false
- def handle_info(:perform, state) do
- BackgroundWorker.enqueue("clean_expired_tokens", %{})
- interval = Pleroma.Config.get([:oauth2, :clean_expired_tokens_interval], @one_day)
-
- Process.send_after(self(), :perform, interval)
- {:noreply, state}
- end
-
- def perform(:clean) do
- OAuth.Token.delete_expired_tokens()
- MFA.Token.delete_expired_tokens()
- end
-end
diff --git a/lib/pleroma/web/oauth/token/query.ex b/lib/pleroma/web/oauth/token/query.ex
deleted file mode 100644
index 93d6e26ed..000000000
--- a/lib/pleroma/web/oauth/token/query.ex
+++ /dev/null
@@ -1,55 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.OAuth.Token.Query do
- @moduledoc """
- Contains queries for OAuth Token.
- """
-
- import Ecto.Query, only: [from: 2]
-
- @type query :: Ecto.Queryable.t() | Token.t()
-
- alias Pleroma.Web.OAuth.Token
-
- @spec get_by_refresh_token(query, String.t()) :: query
- def get_by_refresh_token(query \\ Token, refresh_token) do
- from(q in query, where: q.refresh_token == ^refresh_token)
- end
-
- @spec get_by_token(query, String.t()) :: query
- def get_by_token(query \\ Token, token) do
- from(q in query, where: q.token == ^token)
- end
-
- @spec get_by_app(query, String.t()) :: query
- def get_by_app(query \\ Token, app_id) do
- from(q in query, where: q.app_id == ^app_id)
- end
-
- @spec get_by_id(query, String.t()) :: query
- def get_by_id(query \\ Token, id) do
- from(q in query, where: q.id == ^id)
- end
-
- @spec get_expired_tokens(query, DateTime.t() | nil) :: query
- def get_expired_tokens(query \\ Token, date \\ nil) do
- expired_date = date || Timex.now()
- from(q in query, where: fragment("?", q.valid_until) < ^expired_date)
- end
-
- @spec get_by_user(query, String.t()) :: query
- def get_by_user(query \\ Token, user_id) do
- from(q in query, where: q.user_id == ^user_id)
- end
-
- @spec preload(query, any) :: query
- def preload(query \\ Token, assoc_preload \\ [])
-
- def preload(query, assoc_preload) when is_list(assoc_preload) do
- from(q in query, preload: ^assoc_preload)
- end
-
- def preload(query, _assoc_preload), do: query
-end
diff --git a/lib/pleroma/web/oauth/token/strategy/refresh_token.ex b/lib/pleroma/web/oauth/token/strategy/refresh_token.ex
deleted file mode 100644
index debc29b0b..000000000
--- a/lib/pleroma/web/oauth/token/strategy/refresh_token.ex
+++ /dev/null
@@ -1,58 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.OAuth.Token.Strategy.RefreshToken do
- @moduledoc """
- Functions for dealing with refresh token strategy.
- """
-
- alias Pleroma.Config
- alias Pleroma.Repo
- alias Pleroma.Web.OAuth.Token
- alias Pleroma.Web.OAuth.Token.Strategy.Revoke
-
- @doc """
- Will grant access token by refresh token.
- """
- @spec grant(Token.t()) :: {:ok, Token.t()} | {:error, any()}
- def grant(token) do
- access_token = Repo.preload(token, [:user, :app])
-
- result =
- Repo.transaction(fn ->
- token_params = %{
- app: access_token.app,
- user: access_token.user,
- scopes: access_token.scopes
- }
-
- access_token
- |> revoke_access_token()
- |> create_access_token(token_params)
- end)
-
- case result do
- {:ok, {:error, reason}} -> {:error, reason}
- {:ok, {:ok, token}} -> {:ok, token}
- {:error, reason} -> {:error, reason}
- end
- end
-
- defp revoke_access_token(token) do
- Revoke.revoke(token)
- end
-
- defp create_access_token({:error, error}, _), do: {:error, error}
-
- defp create_access_token({:ok, token}, %{app: app, user: user} = token_params) do
- Token.create_token(app, user, add_refresh_token(token_params, token.refresh_token))
- end
-
- defp add_refresh_token(params, token) do
- case Config.get([:oauth2, :issue_new_refresh_token], false) do
- true -> Map.put(params, :refresh_token, token)
- false -> params
- end
- end
-end
diff --git a/lib/pleroma/web/oauth/token/strategy/revoke.ex b/lib/pleroma/web/oauth/token/strategy/revoke.ex
deleted file mode 100644
index 069c1ee21..000000000
--- a/lib/pleroma/web/oauth/token/strategy/revoke.ex
+++ /dev/null
@@ -1,26 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.OAuth.Token.Strategy.Revoke do
- @moduledoc """
- Functions for dealing with revocation.
- """
-
- alias Pleroma.Repo
- alias Pleroma.Web.OAuth.App
- alias Pleroma.Web.OAuth.Token
-
- @doc "Finds and revokes access token for app and by token"
- @spec revoke(App.t(), map()) :: {:ok, Token.t()} | {:error, :not_found | Ecto.Changeset.t()}
- def revoke(%App{} = app, %{"token" => token} = _attrs) do
- with {:ok, token} <- Token.get_by_token(app, token),
- do: revoke(token)
- end
-
- @doc "Revokes access token"
- @spec revoke(Token.t()) :: {:ok, Token.t()} | {:error, Ecto.Changeset.t()}
- def revoke(%Token{} = token) do
- Repo.delete(token)
- end
-end
diff --git a/lib/pleroma/web/oauth/token/utils.ex b/lib/pleroma/web/oauth/token/utils.ex
deleted file mode 100644
index 43aeab6b0..000000000
--- a/lib/pleroma/web/oauth/token/utils.ex
+++ /dev/null
@@ -1,72 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.OAuth.Token.Utils do
- @moduledoc """
- Auxiliary functions for dealing with tokens.
- """
-
- alias Pleroma.Repo
- alias Pleroma.Web.OAuth.App
-
- @doc "Fetch app by client credentials from request"
- @spec fetch_app(Plug.Conn.t()) :: {:ok, App.t()} | {:error, :not_found}
- def fetch_app(conn) do
- res =
- conn
- |> fetch_client_credentials()
- |> fetch_client
-
- case res do
- %App{} = app -> {:ok, app}
- _ -> {:error, :not_found}
- end
- end
-
- defp fetch_client({id, secret}) when is_binary(id) and is_binary(secret) do
- Repo.get_by(App, client_id: id, client_secret: secret)
- end
-
- defp fetch_client({_id, _secret}), do: nil
-
- defp fetch_client_credentials(conn) do
- # Per RFC 6749, HTTP Basic is preferred to body params
- with ["Basic " <> encoded] <- Plug.Conn.get_req_header(conn, "authorization"),
- {:ok, decoded} <- Base.decode64(encoded),
- [id, secret] <-
- Enum.map(
- String.split(decoded, ":"),
- fn s -> URI.decode_www_form(s) end
- ) do
- {id, secret}
- else
- _ -> {conn.params["client_id"], conn.params["client_secret"]}
- end
- end
-
- @doc "convert token inserted_at to unix timestamp"
- def format_created_at(%{inserted_at: inserted_at} = _token) do
- inserted_at
- |> DateTime.from_naive!("Etc/UTC")
- |> DateTime.to_unix()
- end
-
- @doc false
- @spec generate_token(keyword()) :: binary()
- def generate_token(opts \\ []) do
- opts
- |> Keyword.get(:size, 32)
- |> :crypto.strong_rand_bytes()
- |> Base.url_encode64(padding: false)
- end
-
- # XXX - for whatever reason our token arrives urlencoded, but Plug.Conn should be
- # decoding it. Investigate sometime.
- def fix_padding(token) do
- token
- |> URI.decode()
- |> Base.url_decode64!(padding: false)
- |> Base.url_encode64(padding: false)
- end
-end