Merge remote-tracking branch 'remotes/origin/develop' into ostatus-controller-no...
[akkoma] / lib / pleroma / mfa / token.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 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