generating tokens with mix
[akkoma] / lib / pleroma / user_invite_token.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.UserInviteToken do
6 use Ecto.Schema
7
8 import Ecto.Changeset
9 import Ecto.Query
10 alias Pleroma.Repo
11 alias Pleroma.UserInviteToken
12
13 @type token :: String.t()
14
15 schema "user_invite_tokens" do
16 field(:token, :string)
17 field(:used, :boolean, default: false)
18 field(:max_use, :integer)
19 field(:expire_at, :date)
20 field(:uses, :integer)
21 field(:token_type)
22
23 timestamps()
24 end
25
26 def create_token(options \\ []) do
27 token = :crypto.strong_rand_bytes(32) |> Base.url_encode64()
28
29 max_use = options[:max_use]
30 expire_at = options[:expire_at]
31
32 token =
33 %UserInviteToken{
34 used: false,
35 token: token,
36 max_use: max_use,
37 expire_at: expire_at,
38 uses: 0
39 }
40 |> token_type()
41
42 Repo.insert(token)
43 end
44
45 def list_invites do
46 query = from(u in UserInviteToken, order_by: u.id)
47 Repo.all(query)
48 end
49
50 def used_changeset(struct) do
51 struct
52 |> cast(%{}, [])
53 |> put_change(:used, true)
54 end
55
56 @spec mark_as_used(token()) :: {:ok, UserInviteToken.t()} | {:error, token()}
57 def mark_as_used(token) do
58 with %{used: false} = token <- Repo.get_by(UserInviteToken, %{token: token}),
59 {:ok, token} <- Repo.update(used_changeset(token)) do
60 {:ok, token}
61 else
62 _e -> {:error, token}
63 end
64 end
65
66 defp token_type(%{expire_at: nil, max_use: nil} = token), do: %{token | token_type: "one_time"}
67
68 defp token_type(%{expire_at: _expire_at, max_use: nil} = token),
69 do: %{token | token_type: "date_limited"}
70
71 defp token_type(%{expire_at: nil, max_use: _max_use} = token),
72 do: %{token | token_type: "reusable"}
73
74 defp token_type(%{expire_at: _expire_at, max_use: _max_use} = token),
75 do: %{token | token_type: "reusable_date_limited"}
76
77 @spec valid_token?(UserInviteToken.t()) :: boolean()
78 def valid_token?(%{token_type: "one_time"} = token) do
79 not token.used
80 end
81
82 def valid_token?(%{token_type: "date_limited"} = token) do
83 not_overdue_date?(token) and not token.used
84 end
85
86 def valid_token?(%{token_type: "reusable"} = token) do
87 token.uses < token.max_use and not token.used
88 end
89
90 def valid_token?(%{token_type: "reusable_date_limited"} = token) do
91 not_overdue_date?(token) and token.uses < token.max_use and not token.used
92 end
93
94 defp not_overdue_date?(%{expire_at: expire_at} = token) do
95 Date.compare(Date.utc_today(), expire_at) in [:lt, :eq] ||
96 (Repo.update!(change(token, used: true)) && false)
97 end
98
99 def update_usage(%{token_type: "date_limited"}), do: nil
100
101 def update_usage(%{token_type: "one_time"} = token) do
102 UserInviteToken.mark_as_used(token.token)
103 end
104
105 def update_usage(%{token_type: token_type} = token)
106 when token_type == "reusable" or token_type == "reusable_date_limited" do
107 new_uses = token.uses + 1
108
109 changes = %{
110 uses: new_uses
111 }
112
113 changes =
114 if new_uses >= token.max_use do
115 Map.put(changes, :used, true)
116 else
117 changes
118 end
119
120 change(token, changes) |> Repo.update!()
121 end
122 end