authpwn_rails 0.10.5 → 0.10.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/Gemfile +8 -8
  2. data/Gemfile.lock +1 -1
  3. data/VERSION +1 -1
  4. data/app/models/credentials/email.rb +12 -4
  5. data/app/models/credentials/token.rb +106 -0
  6. data/app/models/tokens/email_verification.rb +42 -0
  7. data/app/models/tokens/one_time.rb +16 -0
  8. data/app/models/tokens/password_reset.rb +27 -0
  9. data/authpwn_rails.gemspec +36 -11
  10. data/lib/authpwn_rails.rb +2 -0
  11. data/lib/authpwn_rails/generators/all_generator.rb +20 -2
  12. data/lib/authpwn_rails/generators/templates/credentials.yml +21 -0
  13. data/lib/authpwn_rails/generators/templates/session/new.html.erb +10 -5
  14. data/lib/authpwn_rails/generators/templates/session/password_change.html.erb +37 -0
  15. data/lib/authpwn_rails/generators/templates/session_controller.rb +20 -2
  16. data/lib/authpwn_rails/generators/templates/session_controller_test.rb +71 -0
  17. data/lib/authpwn_rails/generators/templates/session_mailer.rb +26 -0
  18. data/lib/authpwn_rails/generators/templates/session_mailer/email_verification_email.html.erb +23 -0
  19. data/lib/authpwn_rails/generators/templates/session_mailer/email_verification_email.text.erb +11 -0
  20. data/lib/authpwn_rails/generators/templates/session_mailer/reset_password_email.html.erb +23 -0
  21. data/lib/authpwn_rails/generators/templates/session_mailer/reset_password_email.text.erb +11 -0
  22. data/lib/authpwn_rails/generators/templates/session_mailer_test.rb +37 -0
  23. data/lib/authpwn_rails/routes.rb +50 -0
  24. data/lib/authpwn_rails/session_controller.rb +129 -0
  25. data/lib/authpwn_rails/session_mailer.rb +66 -0
  26. data/test/{email_credential_test.rb → credentials/email_credential_test.rb} +1 -1
  27. data/test/credentials/email_verification_token_test.rb +78 -0
  28. data/test/{facebook_credential_test.rb → credentials/facebook_credential_test.rb} +1 -1
  29. data/test/credentials/one_time_token_credential_test.rb +84 -0
  30. data/test/{password_credential_test.rb → credentials/password_credential_test.rb} +1 -1
  31. data/test/credentials/password_reset_token_test.rb +72 -0
  32. data/test/credentials/token_crendential_test.rb +102 -0
  33. data/test/fixtures/bare_session/forbidden.html.erb +20 -0
  34. data/test/fixtures/bare_session/home.html.erb +5 -0
  35. data/test/fixtures/bare_session/new.html.erb +32 -0
  36. data/test/fixtures/bare_session/password_change.html.erb +30 -0
  37. data/test/fixtures/bare_session/welcome.html.erb +5 -0
  38. data/test/helpers/action_mailer.rb +8 -0
  39. data/test/helpers/routes.rb +8 -2
  40. data/test/routes_test.rb +31 -0
  41. data/test/session_controller_api_test.rb +310 -15
  42. data/test/session_mailer_api_test.rb +67 -0
  43. data/test/test_helper.rb +3 -1
  44. data/test/{email_field_test.rb → user_extensions/email_field_test.rb} +1 -1
  45. data/test/{facebook_fields_test.rb → user_extensions/facebook_fields_test.rb} +1 -1
  46. data/test/{password_field_test.rb → user_extensions/password_field_test.rb} +1 -1
  47. metadata +49 -24
@@ -0,0 +1,5 @@
1
+ <p>
2
+ This view gets displayed when the user is logged in. Right now,
3
+ user <%= current_user.exuid %> is logged in. You should allow the user to
4
+ <%= link_to 'Log out', session_path, :method => :destroy %>.
5
+ </p>
@@ -0,0 +1,32 @@
1
+ <p>This is a sample login form. You should customize it for your users.</p>
2
+
3
+ <% if flash[:notice] %>
4
+ <p class="notice"><%= flash[:notice] %></p>
5
+ <% end %>
6
+
7
+ <% if @redirect_url %>
8
+ <p>
9
+ We need you to log in before we can show you the page that you are trying to
10
+ view.
11
+ </p>
12
+ <% end %>
13
+
14
+ <%= form_tag session_path do %>
15
+ <div class="field">
16
+ <%= label_tag :email, 'Email Address' %><br />
17
+ <%= email_field_tag :email, @email %>
18
+ </div>
19
+
20
+ <div class="field">
21
+ <%= label_tag :password %><br />
22
+ <%= password_field_tag :password %>
23
+ </div>
24
+
25
+ <div class="actions">
26
+ <%= submit_tag 'Log in' %>
27
+
28
+ <% if @redirect_url %>
29
+ <%= hidden_field_tag :redirect_url, @redirect_url %>
30
+ <% end %>
31
+ </div>
32
+ <% end %>
@@ -0,0 +1,30 @@
1
+ <%= form_for @credential, :url => change_password_session_path do |f| %>
2
+ <section class="fields">
3
+ <% unless @credential.new_record? %>
4
+ <div class="field">
5
+ <%= label_tag :old_password, 'Current Password' %><br />
6
+ <span class="value">
7
+ <%= password_field_tag :old_password %>
8
+ </span>
9
+ </div>
10
+ <% end %>
11
+
12
+ <div class="field">
13
+ <%= f.label :password, 'New Password' %><br />
14
+ <span class="value">
15
+ <%= f.password_field :password %>
16
+ </span
17
+ </div>
18
+
19
+ <div class="field">
20
+ <%= f.label :password_confirmation, 'Re-enter New Password' %><br />
21
+ <span class="value">
22
+ <%= f.password_field :password_confirmation %>
23
+ </span
24
+ </div>
25
+ </section>
26
+
27
+ <p class="action">
28
+ <%= submit_tag 'Log in' %>
29
+ </p>
30
+ <% end %>
@@ -0,0 +1,5 @@
1
+ <p>
2
+ This view gets displayed when the user is not logged in. Entice the user to
3
+ sign up for your application, and allow them to
4
+ <%= link_to 'Log in', new_session_path %>.
5
+ </p>
@@ -0,0 +1,8 @@
1
+ # :nodoc: add our views to the path
2
+ class ActionMailer::Base
3
+ prepend_view_path File.expand_path(
4
+ '../../../lib/authpwn_rails/generators/templates', __FILE__)
5
+
6
+ self.delivery_method = :test
7
+ self.perform_deliveries = true
8
+ end
@@ -7,11 +7,17 @@ class ActionController::TestCase
7
7
  collection { get :bouncer }
8
8
  end
9
9
  resource :facebook, :controller => 'facebook'
10
- # NOTE: this route should be kept in sync with the session template.
11
- resource :session, :controller => 'session'
10
+ authpwn_session :controller => 'bare_session',
11
+ :method_names => 'bare_session'
12
+ authpwn_session :controller => 'bare_session2',
13
+ :method_names => 'bare_session2'
12
14
  root :to => 'session#index'
15
+
16
+ # NOTE: this route should be kept in sync with the session template.
17
+ authpwn_session
13
18
  end
14
19
  ApplicationController.send :include, @routes.url_helpers
20
+ ActionMailer::Base.send :include, @routes.url_helpers
15
21
  end
16
22
 
17
23
  setup :setup_routes
@@ -0,0 +1,31 @@
1
+ require File.expand_path('../test_helper', __FILE__)
2
+
3
+ require 'authpwn_rails/generators/templates/session_controller.rb'
4
+
5
+ # Tests the routes created by authpwn_session.
6
+ class RoutesTest < ActionController::TestCase
7
+ tests SessionController
8
+
9
+ test "authpwn_session routes" do
10
+ assert_routing({:path => "/session", :method => :get},
11
+ {:controller => 'session', :action => 'show'})
12
+ assert_routing({:path => "/session/new", :method => :get},
13
+ {:controller => 'session', :action => 'new'})
14
+ assert_routing({:path => "/session", :method => :post},
15
+ {:controller => 'session', :action => 'create'})
16
+ assert_routing({:path => "/session", :method => :delete},
17
+ {:controller => 'session', :action => 'destroy'})
18
+ assert_routing({:path => "/session", :method => :delete},
19
+ {:controller => 'session', :action => 'destroy'})
20
+ assert_routing({:path => "/session/change_password", :method => :get},
21
+ {:controller => 'session', :action => 'password_change'})
22
+ assert_routing({:path => "/session/change_password", :method => :post},
23
+ {:controller => 'session', :action => 'change_password'})
24
+ assert_routing({:path => "/session/reset_password", :method => :post},
25
+ {:controller => 'session', :action => 'reset_password'})
26
+
27
+ code = 'YZ-Fo8HX6_NyU6lVZXYi6cMDLV5eAgt35UTF5l8bD6A'
28
+ assert_routing({:path => "/session/token/#{code}", :method => :get},
29
+ {:controller => 'session', :action => 'token', :code => code})
30
+ end
31
+ end
@@ -5,50 +5,55 @@ require 'authpwn_rails/generators/templates/session_controller.rb'
5
5
  # Run the tests in the generator, to make sure they pass.
6
6
  require 'authpwn_rails/generators/templates/session_controller_test.rb'
7
7
 
8
+ class BareSessionController < ApplicationController
9
+ include Authpwn::SessionController
10
+ self.append_view_path File.expand_path('../fixtures', __FILE__)
11
+ end
12
+
8
13
  # Tests the methods injected by authpwn_session_controller.
9
14
  class SessionControllerApiTest < ActionController::TestCase
10
- tests SessionController
15
+ tests BareSessionController
11
16
 
12
17
  setup do
13
18
  @user = users(:john)
14
19
  @email_credential = credentials(:john_email)
20
+ @password_credential = credentials(:john_password)
21
+ @token_credential = credentials(:john_token)
15
22
  end
16
23
 
17
24
  test "show renders welcome without a user" do
25
+ flexmock(@controller).should_receive(:welcome).once.and_return(nil)
18
26
  get :show
19
27
  assert_template :welcome
20
28
  assert_nil assigns(:current_user)
21
- assert_equal User.count, assigns(:user_count),
22
- 'welcome controller method not called'
23
29
  end
24
30
 
25
31
  test "show json renders empty object without a user" do
32
+ flexmock(@controller).should_receive(:welcome).once.and_return(nil)
26
33
  get :show, :format => 'json'
27
34
  assert_response :ok
28
35
  assert_equal({}, ActiveSupport::JSON.decode(response.body))
29
- assert_equal User.count, assigns(:user_count),
30
- 'welcome controller method not called'
31
36
  end
32
37
 
33
38
  test "show renders home with a user" do
39
+ flexmock(@controller).should_receive(:home).once.and_return(nil)
34
40
  set_session_current_user @user
35
41
  get :show
36
42
  assert_template :home
37
43
  assert_equal @user, assigns(:current_user)
38
- assert_equal @user, assigns(:user), 'home controller method not called'
39
44
  end
40
45
 
41
46
  test "show json renders user when logged in" do
42
47
  set_session_current_user @user
48
+ flexmock(@controller).should_receive(:home).once.and_return(nil)
43
49
  get :show, :format => 'json'
44
50
  assert_response :ok
45
51
  data = ActiveSupport::JSON.decode response.body
46
52
  assert_equal @user.exuid, data['user']['exuid']
47
53
  assert_equal session[:_csrf_token], data['csrf']
48
- assert_equal @user, assigns(:user), 'home controller method not called'
49
54
  end
50
55
 
51
- test "new redirects homes with a user" do
56
+ test "new redirects to session#show when a user is logged in" do
52
57
  set_session_current_user @user
53
58
  get :new
54
59
  assert_redirected_to session_url
@@ -58,12 +63,6 @@ class SessionControllerApiTest < ActionController::TestCase
58
63
  get :new
59
64
  assert_template :new
60
65
  assert_nil assigns(:current_user), 'current_user should not be set'
61
-
62
- assert_select 'form[action="/session"]' do
63
- assert_select 'input#email'
64
- assert_select 'input#password'
65
- assert_select 'input[type=submit]'
66
- end
67
66
  end
68
67
 
69
68
  test "new renders redirect_url when present in flash" do
@@ -78,9 +77,9 @@ class SessionControllerApiTest < ActionController::TestCase
78
77
 
79
78
  test "create logs in with good account details" do
80
79
  post :create, :email => @email_credential.email, :password => 'password'
81
- assert_redirected_to session_url
82
80
  assert_equal @user, assigns(:current_user), 'instance variable'
83
81
  assert_equal @user, session_current_user, 'session'
82
+ assert_redirected_to session_url
84
83
  end
85
84
 
86
85
  test "create by json logs in with good account details" do
@@ -160,6 +159,79 @@ class SessionControllerApiTest < ActionController::TestCase
160
159
  assert_not_nil flash[:notice]
161
160
  end
162
161
 
162
+ test "token logs in with good token" do
163
+ flexmock(@controller).should_receive(:home_with_token).once.
164
+ with(@token_credential).and_return(nil)
165
+ assert_difference 'Credential.count', -1, 'one-time credential is spent' do
166
+ get :token, :code => @token_credential.code
167
+ end
168
+ assert_redirected_to session_url
169
+ assert_equal @user, assigns(:current_user), 'instance variable'
170
+ assert_equal @user, session_current_user, 'session'
171
+ end
172
+
173
+ test "token by json logs in with good token" do
174
+ flexmock(@controller).should_receive(:home_with_token).once.
175
+ with(@token_credential).and_return(nil)
176
+ assert_difference 'Credential.count', -1, 'one-time credential is spent' do
177
+ get :token, :code => @token_credential.code, :format => 'json'
178
+ end
179
+ assert_response :ok
180
+ data = ActiveSupport::JSON.decode response.body
181
+ assert_equal @user.exuid, data['user']['exuid']
182
+ assert_equal session[:_csrf_token], data['csrf']
183
+ assert_equal @user, assigns(:current_user), 'instance variable'
184
+ assert_equal @user, session_current_user, 'session'
185
+ end
186
+
187
+ test "token does not log in with random token" do
188
+ assert_no_difference 'Credential.count', 'no credential is spent' do
189
+ get :token, :code => 'no-such-token'
190
+ end
191
+ assert_redirected_to new_session_url
192
+ assert_nil assigns(:current_user), 'instance variable'
193
+ assert_nil session_current_user, 'session'
194
+ assert_match(/Invalid/, flash[:notice])
195
+ end
196
+
197
+ test "token does not log in blocked accounts" do
198
+ with_blocked_credential @token_credential do
199
+ assert_no_difference 'Credential.count', 'no credential is spent' do
200
+ get :token, :code => @token_credential.code
201
+ end
202
+ end
203
+ assert_redirected_to new_session_url
204
+ assert_nil assigns(:current_user), 'instance variable'
205
+ assert_nil session_current_user, 'session'
206
+ assert_match(/ blocked/, flash[:notice])
207
+ end
208
+
209
+ test "token by json does not log in with random token" do
210
+ assert_no_difference 'Credential.count', 'no credential is spent' do
211
+ get :token, :code => 'no-such-token', :format => 'json'
212
+ end
213
+ assert_response :ok
214
+ data = ActiveSupport::JSON.decode response.body
215
+ assert_equal 'invalid', data['error']
216
+ assert_match(/invalid/i , data['text'])
217
+ assert_nil assigns(:current_user), 'instance variable'
218
+ assert_nil session_current_user, 'session'
219
+ end
220
+
221
+ test "token by json does not log in blocked accounts" do
222
+ with_blocked_credential @token_credential do
223
+ assert_no_difference 'Credential.count', 'no credential is spent' do
224
+ get :token, :code => @token_credential.code, :format => 'json'
225
+ end
226
+ end
227
+ assert_response :ok
228
+ data = ActiveSupport::JSON.decode response.body
229
+ assert_equal 'blocked', data['error']
230
+ assert_match(/blocked/i , data['text'])
231
+ assert_nil assigns(:current_user), 'instance variable'
232
+ assert_nil session_current_user, 'session'
233
+ end
234
+
163
235
  test "logout" do
164
236
  set_session_current_user @user
165
237
  delete :destroy
@@ -175,4 +247,227 @@ class SessionControllerApiTest < ActionController::TestCase
175
247
  assert_response :ok
176
248
  assert_nil assigns(:current_user)
177
249
  end
250
+
251
+ test "password_change bounces without logged in user" do
252
+ get :password_change
253
+ assert_response :forbidden
254
+ end
255
+
256
+ test "password_change renders correct form" do
257
+ set_session_current_user @user
258
+ get :password_change
259
+ assert_response :ok
260
+ assert_template :password_change
261
+ assert_equal @password_credential, assigns(:credential)
262
+ end
263
+
264
+ test "change_password bounces without logged in user" do
265
+ post :change_password, :old_password => 'password',
266
+ :credential => { :password => 'hacks',
267
+ :password_confirmation => 'hacks'}
268
+ assert_response :forbidden
269
+ end
270
+
271
+ test "change_password works with correct input" do
272
+ set_session_current_user @user
273
+ post :change_password, :old_password => 'password',
274
+ :credential => { :password => 'hacks',
275
+ :password_confirmation => 'hacks'}
276
+ assert_redirected_to session_url
277
+ assert_equal @password_credential, assigns(:credential)
278
+ assert_equal @user, Credentials::Password.authenticate_email(
279
+ @email_credential.email, 'hacks'), 'password not changed'
280
+ end
281
+
282
+ test "change_password rejects bad old password" do
283
+ set_session_current_user @user
284
+ post :change_password, :old_password => '_password',
285
+ :credential => { :password => 'hacks',
286
+ :password_confirmation => 'hacks'}
287
+ assert_response :ok
288
+ assert_template :password_change
289
+ assert_equal @password_credential, assigns(:credential)
290
+ assert_equal @user, Credentials::Password.authenticate_email(
291
+ @email_credential.email, 'password'), 'password wrongly changed'
292
+ end
293
+
294
+ test "change_password rejects un-confirmed password" do
295
+ set_session_current_user @user
296
+ post :change_password, :old_password => 'password',
297
+ :credential => { :password => 'hacks',
298
+ :password_confirmation => 'hacks_'}
299
+ assert_response :ok
300
+ assert_template :password_change
301
+ assert_equal @password_credential, assigns(:credential)
302
+ assert_equal @user, Credentials::Password.authenticate_email(
303
+ @email_credential.email, 'password'), 'password wrongly changed'
304
+ end
305
+
306
+ test "change_password works for password recovery" do
307
+ set_session_current_user @user
308
+ @password_credential.destroy
309
+ post :change_password,
310
+ :credential => { :password => 'hacks',
311
+ :password_confirmation => 'hacks'}
312
+ assert_redirected_to session_url
313
+ assert_equal @user, Credentials::Password.authenticate_email(
314
+ @email_credential.email, 'hacks'), 'password not changed'
315
+ end
316
+
317
+ test "change_password rejects un-confirmed password on recovery" do
318
+ set_session_current_user @user
319
+ @password_credential.destroy
320
+ assert_no_difference 'Credential.count' do
321
+ post :change_password,
322
+ :credential => { :password => 'hacks',
323
+ :password_confirmation => 'hacks_'}
324
+ end
325
+ assert_response :ok
326
+ assert_template :password_change
327
+ end
328
+
329
+ test "change_password by json bounces without logged in user" do
330
+ post :change_password, :format => 'json', :old_password => 'password',
331
+ :credential => { :password => 'hacks',
332
+ :password_confirmation => 'hacks'}
333
+ assert_response :ok
334
+ data = ActiveSupport::JSON.decode response.body
335
+ assert_equal 'Please sign in', data['error']
336
+ end
337
+
338
+ test "change_password by json works with correct input" do
339
+ set_session_current_user @user
340
+ post :change_password, :format => 'json', :old_password => 'password',
341
+ :credential => { :password => 'hacks',
342
+ :password_confirmation => 'hacks'}
343
+ assert_response :ok
344
+ assert_equal @user, Credentials::Password.authenticate_email(
345
+ @email_credential.email, 'hacks'), 'password not changed'
346
+ end
347
+
348
+ test "change_password by json rejects bad old password" do
349
+ set_session_current_user @user
350
+ post :change_password, :format => 'json', :old_password => '_password',
351
+ :credential => { :password => 'hacks',
352
+ :password_confirmation => 'hacks'}
353
+ assert_response :ok
354
+ data = ActiveSupport::JSON.decode response.body
355
+ assert_equal 'invalid', data['error']
356
+ assert_equal @password_credential, assigns(:credential)
357
+ assert_equal @user, Credentials::Password.authenticate_email(
358
+ @email_credential.email, 'password'), 'password wrongly changed'
359
+ end
360
+
361
+ test "change_password by json rejects un-confirmed password" do
362
+ set_session_current_user @user
363
+ post :change_password, :format => 'json', :old_password => 'password',
364
+ :credential => { :password => 'hacks',
365
+ :password_confirmation => 'hacks_'}
366
+ assert_response :ok
367
+ data = ActiveSupport::JSON.decode response.body
368
+ assert_equal 'invalid', data['error']
369
+ assert_equal @user, Credentials::Password.authenticate_email(
370
+ @email_credential.email, 'password'), 'password wrongly changed'
371
+ end
372
+
373
+ test "change_password by json works for password recovery" do
374
+ set_session_current_user @user
375
+ @password_credential.destroy
376
+ post :change_password, :format => 'json',
377
+ :credential => { :password => 'hacks',
378
+ :password_confirmation => 'hacks'}
379
+ assert_response :ok
380
+ assert_equal @user, Credentials::Password.authenticate_email(
381
+ @email_credential.email, 'hacks'), 'password not changed'
382
+ end
383
+
384
+ test "change_password by json rejects un-confirmed password on recovery" do
385
+ set_session_current_user @user
386
+ @password_credential.destroy
387
+ assert_no_difference 'Credential.count' do
388
+ post :change_password, :format => 'json',
389
+ :credential => { :password => 'hacks',
390
+ :password_confirmation => 'hacks_'}
391
+ end
392
+ assert_response :ok
393
+ data = ActiveSupport::JSON.decode response.body
394
+ assert_equal 'invalid', data['error']
395
+ end
396
+
397
+ test "reset_password for good e-mail" do
398
+ ActionMailer::Base.deliveries = []
399
+ @request.host = 'mail.test.host:1234'
400
+
401
+ assert_difference 'Credential.count', 1 do
402
+ post :reset_password, :email => @email_credential.email
403
+ end
404
+
405
+ token = Credential.last
406
+ assert_operator token, :kind_of?, Tokens::PasswordReset
407
+ assert_equal @user, token.user, 'password reset token user'
408
+
409
+ assert !ActionMailer::Base.deliveries.empty?, 'email generated'
410
+ email = ActionMailer::Base.deliveries.last
411
+ assert_equal '"mail.test.host staff" <admin@mail.test.host>',
412
+ email['from'].to_s
413
+ assert_equal [@email_credential.email], email.to
414
+ assert_match 'http://mail.test.host:1234/', email.encoded
415
+ assert_match token.code, email.encoded
416
+
417
+ assert_redirected_to new_session_url
418
+ end
419
+
420
+ test "reset_password for good e-mail by json" do
421
+ ActionMailer::Base.deliveries = []
422
+
423
+ assert_difference 'Credential.count', 1 do
424
+ post :reset_password, :email => @email_credential.email, :format => 'json'
425
+ end
426
+
427
+ token = Credential.last
428
+ assert_operator token, :kind_of?, Tokens::PasswordReset
429
+ assert_equal @user, token.user, 'password reset token user'
430
+
431
+ assert !ActionMailer::Base.deliveries.empty?, 'email generated'
432
+
433
+ assert_response :ok
434
+ assert_equal '{}', response.body
435
+ end
436
+
437
+ test "reset_password for invalid e-mail" do
438
+ ActionMailer::Base.deliveries = []
439
+
440
+ assert_no_difference 'Credential.count' do
441
+ post :reset_password, :email => 'no@such.email'
442
+ end
443
+ assert ActionMailer::Base.deliveries.empty?, 'no email generated'
444
+
445
+ assert_redirected_to new_session_url
446
+ end
447
+
448
+ test "reset_password for invalid e-mail by json" do
449
+ ActionMailer::Base.deliveries = []
450
+
451
+ assert_no_difference 'Credential.count' do
452
+ post :reset_password, :email => 'no@such.email', :format => 'json'
453
+ end
454
+ assert ActionMailer::Base.deliveries.empty?, 'no email generated'
455
+
456
+ assert_response :ok
457
+ data = ActiveSupport::JSON.decode response.body
458
+ assert_equal 'not_found', data['error']
459
+ end
460
+
461
+ test "create delegation to reset_password" do
462
+ ActionMailer::Base.deliveries = []
463
+
464
+ assert_difference 'Credential.count', 1 do
465
+ post :create, :email => @email_credential.email, :password => '',
466
+ :reset_password => :requested
467
+ end
468
+
469
+ token = Credential.last
470
+ assert_operator token, :kind_of?, Tokens::PasswordReset
471
+ assert_equal @user, token.user, 'password reset token user'
472
+ end
178
473
  end