diff --git a/changelog.d/token-activity.add b/changelog.d/token-activity.add new file mode 100644 index 000000000..258f283eb --- /dev/null +++ b/changelog.d/token-activity.add @@ -0,0 +1 @@ +Added "last active at" field to OAuth token management diff --git a/lib/pleroma/web/o_auth/token.ex b/lib/pleroma/web/o_auth/token.ex index d96425094..50dd399ca 100644 --- a/lib/pleroma/web/o_auth/token.ex +++ b/lib/pleroma/web/o_auth/token.ex @@ -21,6 +21,7 @@ defmodule Pleroma.Web.OAuth.Token do field(:refresh_token, :string) field(:scopes, {:array, :string}, default: []) field(:valid_until, :naive_datetime_usec) + field(:last_active, :naive_datetime_usec) belongs_to(:user, User, type: FlakeId.Ecto.CompatType) belongs_to(:app, App) @@ -96,6 +97,13 @@ defmodule Pleroma.Web.OAuth.Token do |> validate_required([:valid_until]) end + @spec update_activity(Token.t()) :: {:ok, Token.t()} | {:error, Ecto.Changeset.t()} + def update_activity(token) do + token + |> Ecto.Changeset.change(last_active: NaiveDateTime.utc_now()) + |> Repo.update() + end + @spec create(App.t(), User.t(), map()) :: {:ok, Token.t()} | {:error, Ecto.Changeset.t()} def create(%App{} = app, %User{} = user, attrs \\ %{}) do with {:ok, token} <- do_create(app, user, attrs) do diff --git a/lib/pleroma/web/o_auth/token_view.ex b/lib/pleroma/web/o_auth/token_view.ex index ee54280ec..09e0a18bc 100644 --- a/lib/pleroma/web/o_auth/token_view.ex +++ b/lib/pleroma/web/o_auth/token_view.ex @@ -17,6 +17,7 @@ defmodule Pleroma.Web.OAuth.TokenView do %{ id: token_entry.id, valid_until: token_entry.valid_until, + last_active: token_entry.last_active, app_name: token_entry.app.client_name, scopes: token_entry.scopes, is_current_token: token_entry.id == current_token.id diff --git a/lib/pleroma/web/plugs/o_auth_plug.ex b/lib/pleroma/web/plugs/o_auth_plug.ex index b59ac9d3e..b63a20068 100644 --- a/lib/pleroma/web/plugs/o_auth_plug.ex +++ b/lib/pleroma/web/plugs/o_auth_plug.ex @@ -16,6 +16,8 @@ defmodule Pleroma.Web.Plugs.OAuthPlug do @realm_reg Regex.compile!("Bearer\:?\s+(.*)$", "i") + @update_interval :timer.hours(1) + def init(options), do: options def call(%{assigns: %{user: %User{}}} = conn, _), do: conn @@ -24,16 +26,32 @@ defmodule Pleroma.Web.Plugs.OAuthPlug do with {:ok, token_str} <- fetch_token_str(conn) do with {:ok, user, user_token} <- fetch_user_and_token(token_str), false <- Token.expired?(user_token) do - conn - |> assign(:token, user_token) - |> assign(:user, user) + with true <- needs_update?(user_token), + {:ok, updated_token} <- Token.update_activity(user_token) do + conn + |> assign(:token, updated_token) + |> assign(:user, user) + else + _ -> + conn + |> assign(:token, user_token) + |> assign(:user, user) + end else _ -> with {:ok, app, app_token} <- fetch_app_and_token(token_str), false <- Token.expired?(app_token) do - conn - |> assign(:token, app_token) - |> assign(:app, app) + with true <- needs_update?(app_token), + {:ok, updated_token} <- Token.update_activity(app_token) do + conn + |> assign(:token, updated_token) + |> assign(:app, app) + else + _ -> + conn + |> assign(:token, app_token) + |> assign(:app, app) + end else _ -> conn end @@ -43,6 +61,12 @@ defmodule Pleroma.Web.Plugs.OAuthPlug do end end + defp needs_update?(%Token{last_active: nil}), do: true + + defp needs_update?(%Token{last_active: last_active}) do + NaiveDateTime.diff(NaiveDateTime.utc_now(), last_active, :millisecond) >= @update_interval + end + # Gets user by token # @spec fetch_user_and_token(String.t()) :: {:ok, User.t(), Token.t()} | nil diff --git a/priv/repo/migrations/20261106185600_add_token_activity.exs b/priv/repo/migrations/20261106185600_add_token_activity.exs new file mode 100644 index 000000000..98f4b0184 --- /dev/null +++ b/priv/repo/migrations/20261106185600_add_token_activity.exs @@ -0,0 +1,13 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Repo.Migrations.AddTokenActivity do + use Ecto.Migration + + def change do + alter table(:oauth_tokens) do + add_if_not_exists(:last_active, :naive_datetime_usec) + end + end +end diff --git a/test/pleroma/web/o_auth/token_controller_test.exs b/test/pleroma/web/o_auth/token_controller_test.exs index e41584cd0..6b2b0aeec 100644 --- a/test/pleroma/web/o_auth/token_controller_test.exs +++ b/test/pleroma/web/o_auth/token_controller_test.exs @@ -63,7 +63,14 @@ defmodule Pleroma.Web.OAuth.TokenControllerTest do |> Map.keys() assert Enum.sort(keys) == - Enum.sort(["id", "app_name", "valid_until", "scopes", "is_current_token"]) + Enum.sort([ + "id", + "app_name", + "valid_until", + "scopes", + "is_current_token", + "last_active" + ]) end test "marks which token is being used for the request" do