diff options
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/mix/tasks/pleroma/user.ex | 75 | ||||
| -rw-r--r-- | lib/pleroma/user_invite_token.ex | 89 | 
2 files changed, 154 insertions, 10 deletions
| diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex index 0d0bea8c0..00a933292 100644 --- a/lib/mix/tasks/pleroma/user.ex +++ b/lib/mix/tasks/pleroma/user.ex @@ -7,6 +7,7 @@ defmodule Mix.Tasks.Pleroma.User do    import Ecto.Changeset    alias Mix.Tasks.Pleroma.Common    alias Pleroma.User +  alias Pleroma.UserInviteToken    @shortdoc "Manages Pleroma users"    @moduledoc """ @@ -26,7 +27,19 @@ defmodule Mix.Tasks.Pleroma.User do    ## Generate an invite link. -      mix pleroma.user invite +      mix pleroma.user invite [OPTION...] + +    Options: +    - `--expire_date DATE` - last day on which token is active (e.g. "2019-04-05") +    - `--max_use NUMBER` - maximum numbers of token use + +  ## Generated invites list + +      mix pleroma.user invites_list + +  ## Revoke invite + +      mix pleroma.user invite_revoke TOKEN OR TOKEN_ID    ## Delete the user's account. @@ -287,11 +300,28 @@ defmodule Mix.Tasks.Pleroma.User do      end    end -  def run(["invite"]) do +  def run(["invite" | rest]) do +    {options, [], []} = +      OptionParser.parse(rest, +        strict: [ +          expire_date: :string, +          max_use: :integer +        ] +      ) + +    expire_at = +      with expire_date when expire_date != nil <- Keyword.get(options, :expire_date) do +        Date.from_iso8601!(expire_date) +      end + +    options = Keyword.put(options, :expire_at, expire_at) +      Common.start_pleroma() -    with {:ok, token} <- Pleroma.UserInviteToken.create_token() do -      Mix.shell().info("Generated user invite token") +    with {:ok, token} <- UserInviteToken.create_token(options) do +      Mix.shell().info( +        "Generated user invite token " <> String.replace(token.token_type, "_", " ") +      )        url =          Pleroma.Web.Router.Helpers.redirect_url( @@ -307,6 +337,43 @@ defmodule Mix.Tasks.Pleroma.User do      end    end +  def run(["invites_list"]) do +    Common.start_pleroma() + +    Mix.shell().info("Invites list:") + +    UserInviteToken.list_invites() +    |> Enum.each(fn invite -> +      expire_date = +        case invite.expire_at do +          nil -> nil +          date -> " | Expire date: #{Date.to_string(date)}" +        end + +      using_info = +        case invite.max_use do +          nil -> nil +          max_use -> " | Max use: #{max_use}    Left use: #{max_use - invite.uses}" +        end + +      Mix.shell().info( +        "ID: #{invite.id} | Token: #{invite.token} | Token type: #{invite.token_type} | Used: #{ +          invite.used +        }#{expire_date}#{using_info}" +      ) +    end) +  end + +  def run(["invite_revoke", token]) do +    Common.start_pleroma() + +    with {:ok, _} <- UserInviteToken.mark_as_used(token) do +      Mix.shell().info("Invite for token #{token} was revoked.") +    else +      _ -> Mix.shell().error("No invite found with token #{token}") +    end +  end +    def run(["delete_activities", nickname]) do      Common.start_pleroma() diff --git a/lib/pleroma/user_invite_token.ex b/lib/pleroma/user_invite_token.ex index 9c5579934..3ed39ddd3 100644 --- a/lib/pleroma/user_invite_token.ex +++ b/lib/pleroma/user_invite_token.ex @@ -6,34 +6,54 @@ defmodule Pleroma.UserInviteToken do    use Ecto.Schema    import Ecto.Changeset - +  import Ecto.Query    alias Pleroma.Repo    alias Pleroma.UserInviteToken +  @type token :: String.t() +    schema "user_invite_tokens" do      field(:token, :string)      field(:used, :boolean, default: false) +    field(:max_use, :integer) +    field(:expire_at, :date) +    field(:uses, :integer) +    field(:token_type)      timestamps()    end -  def create_token do +  def create_token(options \\ []) do      token = :crypto.strong_rand_bytes(32) |> Base.url_encode64() -    token = %UserInviteToken{ -      used: false, -      token: token -    } +    max_use = options[:max_use] +    expire_at = options[:expire_at] + +    token = +      %UserInviteToken{ +        used: false, +        token: token, +        max_use: max_use, +        expire_at: expire_at, +        uses: 0 +      } +      |> token_type()      Repo.insert(token)    end +  def list_invites do +    query = from(u in UserInviteToken, order_by: u.id) +    Repo.all(query) +  end +    def used_changeset(struct) do      struct      |> cast(%{}, [])      |> put_change(:used, true)    end +  @spec mark_as_used(token()) :: {:ok, UserInviteToken.t()} | {:error, token()}    def mark_as_used(token) do      with %{used: false} = token <- Repo.get_by(UserInviteToken, %{token: token}),           {:ok, token} <- Repo.update(used_changeset(token)) do @@ -42,4 +62,61 @@ defmodule Pleroma.UserInviteToken do        _e -> {:error, token}      end    end + +  defp token_type(%{expire_at: nil, max_use: nil} = token), do: %{token | token_type: "one_time"} + +  defp token_type(%{expire_at: _expire_at, max_use: nil} = token), +    do: %{token | token_type: "date_limited"} + +  defp token_type(%{expire_at: nil, max_use: _max_use} = token), +    do: %{token | token_type: "reusable"} + +  defp token_type(%{expire_at: _expire_at, max_use: _max_use} = token), +    do: %{token | token_type: "reusable_date_limited"} + +  @spec valid_token?(UserInviteToken.t()) :: boolean() +  def valid_token?(%{token_type: "one_time"} = token) do +    not token.used +  end + +  def valid_token?(%{token_type: "date_limited"} = token) do +    not_overdue_date?(token) and not token.used +  end + +  def valid_token?(%{token_type: "reusable"} = token) do +    token.uses < token.max_use and not token.used +  end + +  def valid_token?(%{token_type: "reusable_date_limited"} = token) do +    not_overdue_date?(token) and token.uses < token.max_use and not token.used +  end + +  defp not_overdue_date?(%{expire_at: expire_at} = token) do +    Date.compare(Date.utc_today(), expire_at) in [:lt, :eq] || +      (Repo.update!(change(token, used: true)) && false) +  end + +  def update_usage(%{token_type: "date_limited"}), do: nil + +  def update_usage(%{token_type: "one_time"} = token) do +    UserInviteToken.mark_as_used(token.token) +  end + +  def update_usage(%{token_type: token_type} = token) +      when token_type == "reusable" or token_type == "reusable_date_limited" do +    new_uses = token.uses + 1 + +    changes = %{ +      uses: new_uses +    } + +    changes = +      if new_uses >= token.max_use do +        Map.put(changes, :used, true) +      else +        changes +      end + +    change(token, changes) |> Repo.update!() +  end  end | 
