[#699] add worker to clean expired oauth tokens See merge request pleroma/pleroma!1184tags/v1.1.4
@@ -41,6 +41,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). | |||
- OAuth: added support for refresh tokens | |||
- Emoji packs and emoji pack manager | |||
- Object pruning (`mix pleroma.database prune_objects`) | |||
- OAuth: added job to clean expired access tokens | |||
### Changed | |||
- **Breaking:** Configuration: move from Pleroma.Mailer to Pleroma.Emails.Mailer | |||
@@ -481,7 +481,9 @@ config :pleroma, Pleroma.ScheduledActivity, | |||
config :pleroma, :oauth2, | |||
token_expires_in: 600, | |||
issue_new_refresh_token: true | |||
issue_new_refresh_token: true, | |||
clean_expired_tokens: false, | |||
clean_expired_tokens_interval: 86_400_000 | |||
config :pleroma, :database, rum_enabled: false | |||
@@ -550,6 +550,8 @@ Configure OAuth 2 provider capabilities: | |||
* `token_expires_in` - The lifetime in seconds of the access token. | |||
* `issue_new_refresh_token` - Keeps old refresh token or generate new refresh token when to obtain an access token. | |||
* `clean_expired_tokens` - Enable a background job to clean expired oauth tokens. Defaults to `false`. | |||
* `clean_expired_tokens_interval` - Interval to run the job to clean expired tokens. Defaults to `86_400_000` (24 hours). | |||
## :emoji | |||
* `shortcode_globs`: Location of custom emoji files. `*` can be used as a wildcard. Example `["/emoji/custom/**/*.png"]` | |||
@@ -110,6 +110,7 @@ defmodule Pleroma.Application do | |||
hackney_pool_children() ++ | |||
[ | |||
worker(Pleroma.Web.Federator.RetryQueue, []), | |||
worker(Pleroma.Web.OAuth.Token.CleanWorker, []), | |||
worker(Pleroma.Stats, []), | |||
worker(Task, [&Pleroma.Web.Push.init/0], restart: :temporary, id: :web_push_init), | |||
worker(Task, [&Pleroma.Web.Federator.init/0], restart: :temporary, id: :federator_init) | |||
@@ -5,7 +5,6 @@ | |||
defmodule Pleroma.Web.OAuth.Token do | |||
use Ecto.Schema | |||
import Ecto.Query | |||
import Ecto.Changeset | |||
alias Pleroma.Repo | |||
@@ -13,6 +12,7 @@ defmodule Pleroma.Web.OAuth.Token do | |||
alias Pleroma.Web.OAuth.App | |||
alias Pleroma.Web.OAuth.Authorization | |||
alias Pleroma.Web.OAuth.Token | |||
alias Pleroma.Web.OAuth.Token.Query | |||
@expires_in Pleroma.Config.get([:oauth2, :token_expires_in], 600) | |||
@type t :: %__MODULE__{} | |||
@@ -31,17 +31,17 @@ defmodule Pleroma.Web.OAuth.Token do | |||
@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 | |||
from(t in __MODULE__, where: t.app_id == ^app_id and t.token == ^token) | |||
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 | |||
from(t in __MODULE__, | |||
where: t.app_id == ^app_id and t.refresh_token == ^token, | |||
preload: [:user] | |||
) | |||
Query.get_by_app(app_id) | |||
|> Query.get_by_refresh_token(token) | |||
|> Query.preload([:user]) | |||
|> Repo.find_resource() | |||
end | |||
@@ -97,29 +97,25 @@ defmodule Pleroma.Web.OAuth.Token do | |||
end | |||
def delete_user_tokens(%User{id: user_id}) do | |||
from( | |||
t in Token, | |||
where: t.user_id == ^user_id | |||
) | |||
Query.get_by_user(user_id) | |||
|> Repo.delete_all() | |||
end | |||
def delete_user_token(%User{id: user_id}, token_id) do | |||
from( | |||
t in Token, | |||
where: t.user_id == ^user_id, | |||
where: t.id == ^token_id | |||
) | |||
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 | |||
from( | |||
t in Token, | |||
where: t.user_id == ^user_id | |||
) | |||
Query.get_by_user(user_id) | |||
|> Query.preload([:app]) | |||
|> Repo.all() | |||
|> Repo.preload(:app) | |||
end | |||
def is_expired?(%__MODULE__{valid_until: valid_until}) do | |||
@@ -0,0 +1,41 @@ | |||
# Pleroma: A lightweight social networking server | |||
# Copyright © 2017-2018 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 tokens. | |||
""" | |||
# 10 seconds | |||
@start_interval 10_000 | |||
@interval Pleroma.Config.get( | |||
# 24 hours | |||
[:oauth2, :clean_expired_tokens_interval], | |||
86_400_000 | |||
) | |||
@queue :background | |||
alias Pleroma.Web.OAuth.Token | |||
def start_link, do: GenServer.start_link(__MODULE__, nil) | |||
def init(_) do | |||
if Pleroma.Config.get([:oauth2, :clean_expired_tokens], false) do | |||
Process.send_after(self(), :perform, @start_interval) | |||
{:ok, nil} | |||
else | |||
:ignore | |||
end | |||
end | |||
@doc false | |||
def handle_info(:perform, state) do | |||
Process.send_after(self(), :perform, @interval) | |||
PleromaJobQueue.enqueue(@queue, __MODULE__, [:clean]) | |||
{:noreply, state} | |||
end | |||
# Job Worker Callbacks | |||
def perform(:clean), do: Token.delete_expired_tokens() | |||
end |
@@ -0,0 +1,55 @@ | |||
# Pleroma: A lightweight social networking server | |||
# Copyright © 2017-2018 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 |
@@ -69,4 +69,17 @@ defmodule Pleroma.Web.OAuth.TokenTest do | |||
assert tokens == 2 | |||
end | |||
test "deletes expired tokens" do | |||
insert(:oauth_token, valid_until: Timex.shift(Timex.now(), days: -3)) | |||
insert(:oauth_token, valid_until: Timex.shift(Timex.now(), days: -3)) | |||
t3 = insert(:oauth_token) | |||
t4 = insert(:oauth_token, valid_until: Timex.shift(Timex.now(), minutes: 10)) | |||
{tokens, _} = Token.delete_expired_tokens() | |||
assert tokens == 2 | |||
available_tokens = Pleroma.Repo.all(Token) | |||
token_ids = available_tokens |> Enum.map(& &1.id) | |||
assert token_ids == [t3.id, t4.id] | |||
end | |||
end |