57bc11ed520312bd926b3ef31bc0825e38a33237
[anni] / lib / pleroma / mfa / token.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.MFA.Token do
6   use Ecto.Schema
7   import Ecto.Query
8   import Ecto.Changeset
9
10   alias Pleroma.Repo
11   alias Pleroma.User
12   alias Pleroma.Web.OAuth.Authorization
13
14   @expires 300
15
16   @type t() :: %__MODULE__{}
17
18   schema "mfa_tokens" do
19     field(:token, :string)
20     field(:valid_until, :naive_datetime_usec)
21
22     belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
23     belongs_to(:authorization, Authorization)
24
25     timestamps()
26   end
27
28   @spec get_by_token(String.t()) :: {:ok, t()} | {:error, :not_found}
29   def get_by_token(token) do
30     from(
31       t in __MODULE__,
32       where: t.token == ^token,
33       preload: [:user, :authorization]
34     )
35     |> Repo.find_resource()
36   end
37
38   @spec validate(String.t()) :: {:ok, t()} | {:error, :not_found} | {:error, :expired_token}
39   def validate(token_str) do
40     with {:ok, token} <- get_by_token(token_str),
41          false <- expired?(token) do
42       {:ok, token}
43     end
44   end
45
46   defp expired?(%__MODULE__{valid_until: valid_until}) do
47     with true <- NaiveDateTime.diff(NaiveDateTime.utc_now(), valid_until) > 0 do
48       {:error, :expired_token}
49     end
50   end
51
52   @spec create(User.t(), Authorization.t() | nil) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
53   def create(user, authorization \\ nil) do
54     with {:ok, token} <- do_create(user, authorization) do
55       Pleroma.Workers.PurgeExpiredToken.enqueue(%{
56         token_id: token.id,
57         valid_until: DateTime.from_naive!(token.valid_until, "Etc/UTC"),
58         mod: __MODULE__
59       })
60
61       {:ok, token}
62     end
63   end
64
65   defp do_create(user, authorization) do
66     %__MODULE__{}
67     |> change()
68     |> assign_user(user)
69     |> maybe_assign_authorization(authorization)
70     |> put_token()
71     |> put_valid_until()
72     |> Repo.insert()
73   end
74
75   defp assign_user(changeset, user) do
76     changeset
77     |> put_assoc(:user, user)
78     |> validate_required([:user])
79   end
80
81   defp maybe_assign_authorization(changeset, %Authorization{} = authorization) do
82     changeset
83     |> put_assoc(:authorization, authorization)
84     |> validate_required([:authorization])
85   end
86
87   defp maybe_assign_authorization(changeset, _), do: changeset
88
89   defp put_token(changeset) do
90     token = Pleroma.Web.OAuth.Token.Utils.generate_token()
91
92     changeset
93     |> change(%{token: token})
94     |> validate_required([:token])
95     |> unique_constraint(:token)
96   end
97
98   defp put_valid_until(changeset) do
99     expires_in = NaiveDateTime.add(NaiveDateTime.utc_now(), @expires)
100
101     changeset
102     |> change(%{valid_until: expires_in})
103     |> validate_required([:valid_until])
104   end
105 end