-
Notifications
You must be signed in to change notification settings - Fork 2.9k
/
schema_token.ex
141 lines (116 loc) · 4.71 KB
/
schema_token.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
defmodule <%= inspect schema.module %>Token do
use Ecto.Schema
import Ecto.Query
@hash_algorithm :sha256
@rand_size 32
# It is very important to keep the reset password token expiry short,
# since someone with access to the email may take over the account.
@reset_password_validity_in_days 1
@confirm_validity_in_days 7
@change_email_validity_in_days 7
@session_validity_in_days 60
<%= if schema.binary_id do %>
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id<% end %>
schema "<%= schema.table %>_tokens" do
field :token, :binary
field :context, :string
field :sent_to, :string
belongs_to :<%= schema.singular %>, <%= inspect schema.module %>
timestamps(updated_at: false)
end
@doc """
Generates a token that will be stored in a signed place,
such as session or cookie. As they are signed, those
tokens do not need to be hashed.
"""
def build_session_token(<%= schema.singular %>) do
token = :crypto.strong_rand_bytes(@rand_size)
{token, %<%= inspect schema.module %>Token{token: token, context: "session", <%= schema.singular %>_id: <%= schema.singular %>.id}}
end
@doc """
Checks if the token is valid and returns its underlying lookup query.
The query returns the <%= schema.singular %> found by the token.
"""
def verify_session_token_query(token) do
query =
from token in token_and_context_query(token, "session"),
join: <%= schema.singular %> in assoc(token, :<%= schema.singular %>),
where: token.inserted_at > ago(@session_validity_in_days, "day"),
select: <%= schema.singular %>
{:ok, query}
end
@doc """
Builds a token with a hashed counter part.
The non-hashed token is sent to the <%= schema.singular %> email while the
hashed part is stored in the database, to avoid reconstruction.
The token is valid for a week as long as <%= schema.singular %>s don't change
their email.
"""
def build_email_token(<%= schema.singular %>, context) do
build_hashed_token(<%= schema.singular %>, context, <%= schema.singular %>.email)
end
defp build_hashed_token(<%= schema.singular %>, context, sent_to) do
token = :crypto.strong_rand_bytes(@rand_size)
hashed_token = :crypto.hash(@hash_algorithm, token)
{Base.url_encode64(token, padding: false),
%<%= inspect schema.module %>Token{
token: hashed_token,
context: context,
sent_to: sent_to,
<%= schema.singular %>_id: <%= schema.singular %>.id
}}
end
@doc """
Checks if the token is valid and returns its underlying lookup query.
The query returns the <%= schema.singular %> found by the token.
"""
def verify_email_token_query(token, context) do
case Base.url_decode64(token, padding: false) do
{:ok, decoded_token} ->
hashed_token = :crypto.hash(@hash_algorithm, decoded_token)
days = days_for_context(context)
query =
from token in token_and_context_query(hashed_token, context),
join: <%= schema.singular %> in assoc(token, :<%= schema.singular %>),
where: token.inserted_at > ago(^days, "day") and token.sent_to == <%= schema.singular %>.email,
select: <%= schema.singular %>
{:ok, query}
:error ->
:error
end
end
defp days_for_context("confirm"), do: @confirm_validity_in_days
defp days_for_context("reset_password"), do: @reset_password_validity_in_days
@doc """
Checks if the token is valid and returns its underlying lookup query.
The query returns the <%= schema.singular %> token record.
"""
def verify_change_email_token_query(token, context) do
case Base.url_decode64(token, padding: false) do
{:ok, decoded_token} ->
hashed_token = :crypto.hash(@hash_algorithm, decoded_token)
query =
from token in token_and_context_query(hashed_token, context),
where: token.inserted_at > ago(@change_email_validity_in_days, "day")
{:ok, query}
:error ->
:error
end
end
@doc """
Returns the given token with the given context.
"""
def token_and_context_query(token, context) do
from <%= inspect schema.module %>Token, where: [token: ^token, context: ^context]
end
@doc """
Gets all tokens for the given <%= schema.singular %> for the given contexts.
"""
def <%= schema.singular %>_and_contexts_query(<%= schema.singular %>, :all) do
from t in <%= inspect schema.module %>Token, where: t.<%= schema.singular %>_id == ^<%= schema.singular %>.id
end
def <%= schema.singular %>_and_contexts_query(<%= schema.singular %>, [_ | _] = contexts) do
from t in <%= inspect schema.module %>Token, where: t.<%= schema.singular %>_id == ^<%= schema.singular %>.id and t.context in ^contexts
end
end