1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.MFA.Token do
12 alias Pleroma.Web.OAuth.Authorization
16 @type t() :: %__MODULE__{}
18 schema "mfa_tokens" do
19 field(:token, :string)
20 field(:valid_until, :naive_datetime_usec)
22 belongs_to(:user, User, type: FlakeId.Ecto.CompatType)
23 belongs_to(:authorization, Authorization)
28 @spec get_by_token(String.t()) :: {:ok, t()} | {:error, :not_found}
29 def get_by_token(token) do
32 where: t.token == ^token,
33 preload: [:user, :authorization]
35 |> Repo.find_resource()
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
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}
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(%{
57 valid_until: DateTime.from_naive!(token.valid_until, "Etc/UTC"),
65 defp do_create(user, authorization) do
69 |> maybe_assign_authorization(authorization)
75 defp assign_user(changeset, user) do
77 |> put_assoc(:user, user)
78 |> validate_required([:user])
81 defp maybe_assign_authorization(changeset, %Authorization{} = authorization) do
83 |> put_assoc(:authorization, authorization)
84 |> validate_required([:authorization])
87 defp maybe_assign_authorization(changeset, _), do: changeset
89 defp put_token(changeset) do
90 token = Pleroma.Web.OAuth.Token.Utils.generate_token()
93 |> change(%{token: token})
94 |> validate_required([:token])
95 |> unique_constraint(:token)
98 defp put_valid_until(changeset) do
99 expires_in = NaiveDateTime.add(NaiveDateTime.utc_now(), @expires)
102 |> change(%{valid_until: expires_in})
103 |> validate_required([:valid_until])