stormpath-sdk 0.4.0 → 1.0.0.beta
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +27 -0
- data/CHANGES.md +21 -1
- data/Gemfile +1 -2
- data/README.md +457 -11
- data/Rakefile +15 -1
- data/lib/stormpath-sdk.rb +52 -33
- data/lib/stormpath-sdk/{resource/group_list.rb → api_key.rb} +5 -9
- data/lib/stormpath-sdk/auth/authentication_result.rb +3 -13
- data/lib/stormpath-sdk/auth/basic_authenticator.rb +5 -11
- data/lib/stormpath-sdk/auth/basic_login_attempt.rb +6 -8
- data/lib/stormpath-sdk/auth/username_password_request.rb +2 -5
- data/lib/stormpath-sdk/cache/cache.rb +54 -0
- data/lib/stormpath-sdk/cache/cache_entry.rb +33 -0
- data/lib/stormpath-sdk/cache/cache_manager.rb +22 -0
- data/lib/stormpath-sdk/cache/cache_stats.rb +35 -0
- data/lib/stormpath-sdk/cache/memory_store.rb +29 -0
- data/lib/stormpath-sdk/cache/redis_store.rb +32 -0
- data/lib/stormpath-sdk/client.rb +111 -0
- data/lib/stormpath-sdk/data_store.rb +241 -0
- data/lib/stormpath-sdk/{client/api_key.rb → error.rb} +16 -10
- data/lib/stormpath-sdk/{util → ext}/hash.rb +1 -2
- data/lib/stormpath-sdk/http/authc/sauthc1_signer.rb +8 -4
- data/lib/stormpath-sdk/http/http_client_request_executor.rb +8 -7
- data/lib/stormpath-sdk/http/request.rb +4 -8
- data/lib/stormpath-sdk/{util/request_utils.rb → http/utils.rb} +17 -38
- data/lib/stormpath-sdk/resource/account.rb +12 -108
- data/lib/stormpath-sdk/resource/application.rb +35 -171
- data/lib/stormpath-sdk/resource/associations.rb +97 -0
- data/lib/stormpath-sdk/resource/base.rb +256 -0
- data/lib/stormpath-sdk/resource/collection.rb +94 -0
- data/lib/stormpath-sdk/resource/directory.rb +11 -68
- data/lib/stormpath-sdk/resource/email_verification_token.rb +3 -9
- data/lib/stormpath-sdk/resource/error.rb +4 -38
- data/lib/stormpath-sdk/resource/expansion.rb +28 -0
- data/lib/stormpath-sdk/resource/group.rb +8 -66
- data/lib/stormpath-sdk/resource/group_membership.rb +4 -55
- data/lib/stormpath-sdk/resource/{application_list.rb → instance.rb} +7 -13
- data/lib/stormpath-sdk/resource/password_reset_token.rb +5 -23
- data/lib/stormpath-sdk/resource/status.rb +22 -28
- data/lib/stormpath-sdk/resource/tenant.rb +5 -52
- data/lib/stormpath-sdk/resource/utils.rb +43 -13
- data/lib/stormpath-sdk/util/assert.rb +5 -15
- data/lib/stormpath-sdk/version.rb +3 -3
- data/spec/api_key_spec.rb +19 -0
- data/spec/auth/basic_authenticator_spec.rb +25 -0
- data/spec/auth/sauthc1_signer_spec.rb +42 -0
- data/spec/cache/cache_entry_spec.rb +157 -0
- data/spec/cache/cache_spec.rb +89 -0
- data/spec/cache/cache_stats_spec.rb +106 -0
- data/spec/client_spec.rb +538 -0
- data/spec/data_store_spec.rb +130 -0
- data/spec/resource/account_spec.rb +74 -0
- data/spec/resource/application_spec.rb +148 -0
- data/spec/resource/base_spec.rb +114 -0
- data/spec/resource/collection_spec.rb +169 -0
- data/spec/resource/directory_spec.rb +30 -0
- data/spec/resource/expansion_spec.rb +100 -0
- data/spec/resource/group_spec.rb +49 -0
- data/spec/spec_helper.rb +135 -0
- data/spec/support/resource_factory.rb +48 -0
- data/spec/support/resource_matchers.rb +27 -0
- data/spec/support/test_cache_stores.rb +9 -0
- data/spec/support/test_request_executor.rb +11 -0
- data/stormpath-sdk.gemspec +14 -4
- data/support/api.rb +55 -0
- metadata +214 -44
- data/lib/stormpath-sdk/client/client.rb +0 -38
- data/lib/stormpath-sdk/client/client_application.rb +0 -38
- data/lib/stormpath-sdk/client/client_application_builder.rb +0 -351
- data/lib/stormpath-sdk/client/client_builder.rb +0 -305
- data/lib/stormpath-sdk/ds/data_store.rb +0 -210
- data/lib/stormpath-sdk/ds/resource_factory.rb +0 -37
- data/lib/stormpath-sdk/resource/account_list.rb +0 -32
- data/lib/stormpath-sdk/resource/collection_resource.rb +0 -91
- data/lib/stormpath-sdk/resource/directory_list.rb +0 -30
- data/lib/stormpath-sdk/resource/group_membership_list.rb +0 -32
- data/lib/stormpath-sdk/resource/instance_resource.rb +0 -28
- data/lib/stormpath-sdk/resource/resource.rb +0 -327
- data/lib/stormpath-sdk/resource/resource_error.rb +0 -47
- data/test/client/client.yml +0 -16
- data/test/client/client_application_builder_spec.rb +0 -114
- data/test/client/client_builder_spec.rb +0 -176
- data/test/client/read_spec.rb +0 -254
- data/test/client/write_spec.rb +0 -420
- data/test/resource/resource_spec.rb +0 -41
- data/test/resource/test_resource.rb +0 -28
@@ -22,11 +22,10 @@ module Stormpath
|
|
22
22
|
include Stormpath::Http::Authc
|
23
23
|
include Stormpath::Util::Assert
|
24
24
|
|
25
|
-
def initialize(api_key)
|
25
|
+
def initialize(api_key, options = {})
|
26
26
|
@signer = Sauthc1Signer.new
|
27
27
|
@api_key = api_key
|
28
|
-
@http_client = HTTPClient.new
|
29
|
-
|
28
|
+
@http_client = HTTPClient.new options[:proxy]
|
30
29
|
end
|
31
30
|
|
32
31
|
def execute_request(request)
|
@@ -35,18 +34,20 @@ module Stormpath
|
|
35
34
|
|
36
35
|
@signer.sign_request request, @api_key
|
37
36
|
|
38
|
-
domain = request.
|
37
|
+
domain = if request.query_string.present?
|
38
|
+
[request.href, request.to_s_query_string(true)].join '?'
|
39
|
+
else
|
40
|
+
request.href
|
41
|
+
end
|
39
42
|
|
40
43
|
method = @http_client.method(request.http_method.downcase)
|
41
44
|
|
42
45
|
if request.body.nil?
|
43
46
|
|
44
|
-
response = method.call domain,
|
47
|
+
response = method.call domain, nil, request.http_headers
|
45
48
|
|
46
49
|
else
|
47
50
|
|
48
|
-
add_query_string domain, request.query_string
|
49
|
-
|
50
51
|
response = method.call domain, request.body, request.http_headers
|
51
52
|
|
52
53
|
end
|
@@ -14,12 +14,9 @@
|
|
14
14
|
# limitations under the License.
|
15
15
|
#
|
16
16
|
module Stormpath
|
17
|
-
|
18
17
|
module Http
|
19
|
-
|
20
18
|
class Request
|
21
|
-
|
22
|
-
include Stormpath::Util
|
19
|
+
include Stormpath::Http::Utils
|
23
20
|
|
24
21
|
attr_accessor :http_method, :href, :query_string, :http_headers, :body
|
25
22
|
|
@@ -72,11 +69,10 @@ module Stormpath
|
|
72
69
|
result = ''
|
73
70
|
|
74
71
|
if !@query_string.empty?
|
72
|
+
Hash[@query_string.sort].each do |key, value|
|
75
73
|
|
76
|
-
|
77
|
-
|
78
|
-
enc_key = RequestUtils.encode_url key, false, canonical
|
79
|
-
enc_value = RequestUtils.encode_url value, false, canonical
|
74
|
+
enc_key = encode_url key, false, canonical
|
75
|
+
enc_value = encode_url value, false, canonical
|
80
76
|
|
81
77
|
if !result.empty?
|
82
78
|
result << '&'
|
@@ -14,59 +14,38 @@
|
|
14
14
|
# limitations under the License.
|
15
15
|
#
|
16
16
|
module Stormpath
|
17
|
+
module Http
|
18
|
+
module Utils
|
17
19
|
|
18
|
-
|
19
|
-
|
20
|
-
class RequestUtils
|
21
|
-
|
22
|
-
##
|
23
|
-
# Returns true if the specified URI uses a standard port (i.e. http == 80 or https == 443),
|
24
|
-
# false otherwise.
|
25
|
-
#
|
26
|
-
# param uri
|
27
|
-
# return true if the specified URI is using a non-standard port, false otherwise
|
28
|
-
#
|
29
|
-
def self.default_port? uri
|
20
|
+
def default_port?(uri)
|
30
21
|
scheme = uri.scheme.downcase
|
31
22
|
port = uri.port
|
32
23
|
port <= 0 || (port == 80 && scheme.eql?("http")) || (port == 443 && scheme.eql?("https"))
|
33
24
|
end
|
34
25
|
|
35
|
-
def
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
if canonical
|
26
|
+
def encode_url(value, path, canonical)
|
27
|
+
URI.escape(value.to_s).tap do |encoded|
|
28
|
+
if canonical
|
29
|
+
str_map = {'+' => '%20', '*' => '%2A', '%7E' => '~'}
|
40
30
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
if encoded.include? key
|
46
|
-
encoded[key] = str_value
|
31
|
+
str_map.each do |key, str_value|
|
32
|
+
if encoded.include? key
|
33
|
+
encoded[key] = str_value
|
34
|
+
end
|
47
35
|
end
|
48
36
|
|
49
|
-
|
50
|
-
|
51
|
-
# encoded['%7E'] = '~' --> yes, this is reversed (compared to the other two) intentionally
|
37
|
+
# encoded['%7E'] = '~' --> yes, this is reversed (compared to the other two) intentionally
|
52
38
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
39
|
+
if path
|
40
|
+
str = '%2F'
|
41
|
+
if encoded.include? str
|
42
|
+
encoded[str] = '/'
|
43
|
+
end
|
58
44
|
end
|
59
|
-
|
60
45
|
end
|
61
|
-
|
62
46
|
end
|
63
|
-
|
64
|
-
encoded
|
65
|
-
|
66
47
|
end
|
67
48
|
|
68
49
|
end
|
69
|
-
|
70
50
|
end
|
71
|
-
|
72
51
|
end
|
@@ -13,117 +13,21 @@
|
|
13
13
|
# See the License for the specific language governing permissions and
|
14
14
|
# limitations under the License.
|
15
15
|
#
|
16
|
-
|
16
|
+
class Stormpath::Resource::Account < Stormpath::Resource::Instance
|
17
|
+
include Stormpath::Resource::Status
|
17
18
|
|
18
|
-
|
19
|
+
prop_accessor :username, :email, :given_name, :middle_name,
|
20
|
+
:surname
|
21
|
+
prop_writer :password
|
22
|
+
prop_non_printable :password
|
19
23
|
|
20
|
-
|
24
|
+
belongs_to :directory
|
25
|
+
has_one :email_verification_token
|
21
26
|
|
22
|
-
|
23
|
-
|
24
|
-
USERNAME = "username"
|
25
|
-
EMAIL = "email"
|
26
|
-
PASSWORD = "password"
|
27
|
-
GIVEN_NAME = "givenName"
|
28
|
-
MIDDLE_NAME = "middleName"
|
29
|
-
SURNAME = "surname"
|
30
|
-
STATUS = "status"
|
31
|
-
GROUPS = "groups"
|
32
|
-
DIRECTORY = "directory"
|
33
|
-
EMAIL_VERIFICATION_TOKEN = "emailVerificationToken"
|
34
|
-
GROUP_MEMBERSHIPS = "groupMemberships"
|
35
|
-
|
36
|
-
def get_username
|
37
|
-
get_property USERNAME
|
38
|
-
end
|
39
|
-
|
40
|
-
def set_username username
|
41
|
-
set_property USERNAME, username
|
42
|
-
end
|
43
|
-
|
44
|
-
def get_email
|
45
|
-
get_property EMAIL
|
46
|
-
end
|
47
|
-
|
48
|
-
def set_email email
|
49
|
-
set_property EMAIL, email
|
50
|
-
end
|
51
|
-
|
52
|
-
def set_password password
|
53
|
-
set_property PASSWORD, password
|
54
|
-
end
|
55
|
-
|
56
|
-
def get_given_name
|
57
|
-
get_property GIVEN_NAME
|
58
|
-
end
|
59
|
-
|
60
|
-
def set_given_name given_name
|
61
|
-
set_property GIVEN_NAME, given_name
|
62
|
-
end
|
63
|
-
|
64
|
-
def get_middle_name
|
65
|
-
get_property MIDDLE_NAME
|
66
|
-
end
|
67
|
-
|
68
|
-
def set_middle_name middle_name
|
69
|
-
set_property MIDDLE_NAME, middle_name
|
70
|
-
end
|
71
|
-
|
72
|
-
def get_surname
|
73
|
-
get_property SURNAME
|
74
|
-
end
|
75
|
-
|
76
|
-
def set_surname surname
|
77
|
-
set_property SURNAME, surname
|
78
|
-
end
|
79
|
-
|
80
|
-
def get_status
|
81
|
-
value = get_property STATUS
|
82
|
-
|
83
|
-
if !value.nil?
|
84
|
-
value = value.upcase
|
85
|
-
end
|
86
|
-
|
87
|
-
value
|
88
|
-
end
|
89
|
-
|
90
|
-
def set_status status
|
91
|
-
|
92
|
-
if get_status_hash.has_key? status
|
93
|
-
set_property STATUS, get_status_hash[status]
|
94
|
-
end
|
95
|
-
|
96
|
-
end
|
97
|
-
|
98
|
-
def get_groups
|
99
|
-
get_resource_property GROUPS, GroupList
|
100
|
-
end
|
101
|
-
|
102
|
-
def get_directory
|
103
|
-
get_resource_property DIRECTORY, Directory
|
104
|
-
end
|
105
|
-
|
106
|
-
def get_email_verification_token
|
107
|
-
get_resource_property EMAIL_VERIFICATION_TOKEN, EmailVerificationToken
|
108
|
-
end
|
109
|
-
|
110
|
-
def add_group group
|
111
|
-
|
112
|
-
GroupMembership::_create self, group, data_store
|
113
|
-
|
114
|
-
end
|
115
|
-
|
116
|
-
def get_group_memberships
|
117
|
-
get_resource_property GROUP_MEMBERSHIPS, GroupMembershipList
|
118
|
-
end
|
119
|
-
|
120
|
-
protected
|
121
|
-
def printable_property? property_name
|
122
|
-
PASSWORD != property_name
|
123
|
-
end
|
124
|
-
|
125
|
-
end
|
27
|
+
has_many :groups
|
28
|
+
has_many :group_memberships
|
126
29
|
|
30
|
+
def add_group group
|
31
|
+
client.group_memberships.create group: group, account: self
|
127
32
|
end
|
128
|
-
|
129
33
|
end
|
@@ -13,187 +13,51 @@
|
|
13
13
|
# See the License for the specific language governing permissions and
|
14
14
|
# limitations under the License.
|
15
15
|
#
|
16
|
-
|
16
|
+
class Stormpath::Resource::Application < Stormpath::Resource::Instance
|
17
|
+
include Stormpath::Resource::Status
|
17
18
|
|
18
|
-
|
19
|
+
class LoadError < Stormpath::Error; end
|
19
20
|
|
20
|
-
|
21
|
+
prop_accessor :name, :description
|
21
22
|
|
22
|
-
|
23
|
+
belongs_to :tenant
|
24
|
+
has_many :accounts, can: [:create]
|
25
|
+
has_many :password_reset_tokens, can: [:get, :create]
|
23
26
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
ACCOUNTS = "accounts"
|
29
|
-
PASSWORD_RESET_TOKENS = "passwordResetTokens"
|
27
|
+
def self.load composite_url
|
28
|
+
begin
|
29
|
+
uri = URI(composite_url)
|
30
|
+
api_key_id, api_key_secret = uri.userinfo.split(':')
|
30
31
|
|
31
|
-
|
32
|
-
|
33
|
-
|
32
|
+
client = Stormpath::Client.new api_key: {
|
33
|
+
id: api_key_id,
|
34
|
+
secret: api_key_secret
|
35
|
+
}
|
34
36
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
end
|
42
|
-
|
43
|
-
def set_description description
|
44
|
-
set_property DESCRIPTION, description
|
45
|
-
end
|
46
|
-
|
47
|
-
def get_status
|
48
|
-
value = get_property STATUS
|
49
|
-
|
50
|
-
if !value.nil?
|
51
|
-
value = value.upcase
|
52
|
-
end
|
53
|
-
|
54
|
-
value
|
55
|
-
end
|
56
|
-
|
57
|
-
def set_status status
|
58
|
-
|
59
|
-
if get_status_hash.has_key? status
|
60
|
-
set_property STATUS, get_status_hash[status]
|
61
|
-
end
|
62
|
-
|
63
|
-
end
|
64
|
-
|
65
|
-
def get_tenant
|
66
|
-
get_resource_property TENANT, Tenant
|
67
|
-
end
|
68
|
-
|
69
|
-
def get_accounts
|
70
|
-
|
71
|
-
get_resource_property ACCOUNTS, AccountList
|
72
|
-
end
|
73
|
-
|
74
|
-
|
75
|
-
#
|
76
|
-
# Sends a password reset email for the specified account username or email address. The email will contain
|
77
|
-
# a password reset link that the user can click or copy into their browser address bar.
|
78
|
-
# <p/>
|
79
|
-
# This method merely sends the password reset email that contains the link and nothing else. You will need to
|
80
|
-
# handle the link requests and then reset the account's password as described in the
|
81
|
-
# {@link #verify_password_reset_token} RDoc.
|
82
|
-
#
|
83
|
-
# @param account_username_or_email a username or email address of an Account that may login to the application.
|
84
|
-
# @return the account corresponding to the specified username or email address.
|
85
|
-
# @see #account_username_or_email
|
86
|
-
#
|
87
|
-
def send_password_reset_email account_username_or_email
|
88
|
-
|
89
|
-
password_reset_token = create_password_reset_token account_username_or_email;
|
90
|
-
password_reset_token.get_account
|
91
|
-
|
92
|
-
end
|
93
|
-
|
94
|
-
# Verifies a password reset token in a user-clicked link within an email.
|
95
|
-
# <p/>
|
96
|
-
# <h2>Base Link Configuration</h2>
|
97
|
-
# You need to define the <em>Base</em> link that will process HTTP requests when users click the link in the
|
98
|
-
# email as part of your Application's Workflow Configuration within the Stormpath UI Console. It must be a URL
|
99
|
-
# served by your application's web servers. For example:
|
100
|
-
# <pre>
|
101
|
-
# https://www.myApplication.com/passwordReset
|
102
|
-
# </pre>
|
103
|
-
# <h2>Runtime Link Processing</h2>
|
104
|
-
# When an application user clicks on the link in the email at runtime, your web server needs to process the request
|
105
|
-
# and look for an {@code spToken} request parameter. You can then verify the {@code spToken}, and then finally
|
106
|
-
# change the Account's password.
|
107
|
-
# <p/>
|
108
|
-
# Usage Example:
|
109
|
-
# <p/>
|
110
|
-
# Browser:
|
111
|
-
# {@code GET https://www.myApplication/passwordReset?spToken=someTokenValueHere}
|
112
|
-
# <p/>
|
113
|
-
# Your code:
|
114
|
-
# <pre>
|
115
|
-
# token = #get the spToken query parameter
|
116
|
-
#
|
117
|
-
# account = application.verify_password_reset_token token
|
118
|
-
#
|
119
|
-
# //token has been verified - now set the new password with what the end-user submits:
|
120
|
-
# account.set_password user_submitted_new_password
|
121
|
-
# account.save
|
122
|
-
# </pre>
|
123
|
-
#
|
124
|
-
# @param token the verification token, usually obtained as a request parameter by your application.
|
125
|
-
# @return the Account matching the specified token.
|
126
|
-
def verify_password_reset_token token
|
127
|
-
|
128
|
-
href = get_password_reset_token_href
|
129
|
-
href += '/' + token
|
130
|
-
|
131
|
-
password_reset_props = Hash.new
|
132
|
-
password_reset_props.store HREF_PROP_NAME, href
|
133
|
-
|
134
|
-
password_reset_token = data_store.instantiate PasswordResetToken, password_reset_props
|
135
|
-
|
136
|
-
password_reset_token.get_account
|
137
|
-
|
138
|
-
end
|
139
|
-
|
140
|
-
#
|
141
|
-
# Authenticates an account's submitted principals and credentials (e.g. username and password). The account must
|
142
|
-
# be in one of the Application's
|
143
|
-
# <a href="http://www.stormpath.com/docs/managing-applications-login-sources">assigned Login Sources</a>. If not
|
144
|
-
# in an assigned login source, the authentication attempt will fail.
|
145
|
-
# <h2>Example</h2>
|
146
|
-
# Consider the following username/password-based example:
|
147
|
-
# <p/>
|
148
|
-
# <pre>
|
149
|
-
# request = UsernamePasswordRequest.new username, submittedRawPlaintextPassword, nil
|
150
|
-
# account = appToTest.authenticate_account(request).get_account
|
151
|
-
# </pre>
|
152
|
-
#
|
153
|
-
# @param request the authentication request representing an account's principals and credentials (e.g.
|
154
|
-
# username/password) used to verify their identity.
|
155
|
-
# @return the result of the authentication. The authenticated account can be obtained from
|
156
|
-
# {@code result.}{@link Stormpath::Authentication::AuthenticationResult#get_account}.
|
157
|
-
# @throws ResourceError if the authentication attempt fails.
|
158
|
-
#
|
159
|
-
def authenticate_account request
|
160
|
-
response = Stormpath::Authentication::BasicAuthenticator.new data_store
|
161
|
-
response.authenticate get_href, request
|
162
|
-
end
|
163
|
-
|
164
|
-
private
|
165
|
-
|
166
|
-
def create_password_reset_token email
|
167
|
-
|
168
|
-
href = get_password_reset_token_href
|
169
|
-
|
170
|
-
password_reset_props = Hash.new
|
171
|
-
password_reset_props.store 'email', email
|
172
|
-
|
173
|
-
password_reset_token = data_store.instantiate PasswordResetToken, password_reset_props
|
174
|
-
|
175
|
-
data_store.create href, password_reset_token, PasswordResetToken
|
176
|
-
|
177
|
-
end
|
178
|
-
|
179
|
-
def get_password_reset_token_href
|
180
|
-
|
181
|
-
password_reset_tokens_href = get_property PASSWORD_RESET_TOKENS
|
182
|
-
|
183
|
-
if !password_reset_tokens_href.nil? and
|
184
|
-
password_reset_tokens_href.respond_to? 'empty?' and
|
185
|
-
!password_reset_tokens_href.empty?
|
37
|
+
application_path = uri.path.slice(/\/applications(.)*$/)
|
38
|
+
client.applications.get(application_path)
|
39
|
+
rescue
|
40
|
+
raise LoadError
|
41
|
+
end
|
42
|
+
end
|
186
43
|
|
187
|
-
|
188
|
-
|
44
|
+
def send_password_reset_email email
|
45
|
+
password_reset_token = create_password_reset_token email;
|
46
|
+
password_reset_token.account
|
47
|
+
end
|
189
48
|
|
190
|
-
|
49
|
+
def verify_password_reset_token token
|
50
|
+
password_reset_tokens.get(token).account
|
51
|
+
end
|
191
52
|
|
192
|
-
|
53
|
+
def authenticate_account request
|
54
|
+
response = Stormpath::Authentication::BasicAuthenticator.new data_store
|
55
|
+
response.authenticate href, request
|
56
|
+
end
|
193
57
|
|
194
|
-
|
58
|
+
private
|
195
59
|
|
60
|
+
def create_password_reset_token email
|
61
|
+
password_reset_tokens.create email: email
|
196
62
|
end
|
197
|
-
|
198
63
|
end
|
199
|
-
|