authpwn_rails 0.10.5 → 0.10.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,66 @@
1
+ # :nodoc: namespace
2
+ module Authpwn
3
+
4
+ # Included by the session mailer class.
5
+ #
6
+ # Parts of the codebase assume the mailer will be named SessionMailer.
7
+ module SessionMailer
8
+ # Creates an e-mail containing a verification token for the e-mail address.
9
+ #
10
+ # Params:
11
+ # token:: the e-mail confirmation token
12
+ # host:: the server's hostname (e.g. "localhost:3000")
13
+ def email_verification_email(token, host)
14
+ @token, @host = token, host
15
+
16
+ hostname = host.split(':', 2).first # Strip out any port.
17
+
18
+ mail :to => @token.email,
19
+ :subject => email_verification_subject(token, hostname),
20
+ :from => email_verification_from(token, hostname)
21
+ end
22
+
23
+ # The subject line in an e-mail verification e-mail.
24
+ #
25
+ # The authpwn generator encourages applications to override this method.
26
+ def email_verification_subject(token, server_hostname)
27
+ "#{server_hostname} e-mail verification"
28
+ end
29
+
30
+ # The sender e-mail address for an e-mail verification e-mail.
31
+ #
32
+ # The authpwn generator encourages applications to override this method.
33
+ def email_verification_from(token, server_hostname)
34
+ %Q|"#{server_hostname} staff" <admin@#{server_hostname}>|
35
+ end
36
+
37
+ # Creates an e-mail containing a password reset token.
38
+ #
39
+ # Params:
40
+ # email:: the email to send the token to
41
+ # token:: the password reset token
42
+ # host:: the server's hostname (e.g. "localhost:3000")
43
+ def reset_password_email(email, token, host)
44
+ @email, @token, @host = email, token, host
45
+
46
+ hostname = host.split(':', 2).first # Strip out any port.
47
+ mail :to => email, :subject => reset_password_subject(token, hostname),
48
+ :from => reset_password_from(token, hostname)
49
+ end
50
+
51
+ # The subject line in a password reset e-mail.
52
+ #
53
+ # The authpwn generator encourages applications to override this method.
54
+ def reset_password_subject(token, server_hostname)
55
+ "#{server_hostname} password reset"
56
+ end
57
+
58
+ # The sender e-mail address for a password reset e-mail.
59
+ #
60
+ # The authpwn generator encourages applications to override this method.
61
+ def reset_password_from(token, server_hostname)
62
+ %Q|"#{server_hostname} staff" <admin@#{server_hostname}>|
63
+ end
64
+ end # namespace Authpwn::SessionMailer
65
+
66
+ end # namespace Authpwn
@@ -1,4 +1,4 @@
1
- require File.expand_path('../test_helper', __FILE__)
1
+ require File.expand_path('../../test_helper', __FILE__)
2
2
 
3
3
  class EmailCredentialTest < ActiveSupport::TestCase
4
4
  def setup
@@ -0,0 +1,78 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ class EmailVerificationTokenCredentialTest < ActiveSupport::TestCase
4
+ def setup
5
+ @credential = Tokens::EmailVerification.new(
6
+ :code => 'AyCMIixa5C7BBqU-XFI7l7IaUFJ4zQZPmcK6oNb3FLo',
7
+ :key => 'jane@gmail.com')
8
+ @credential.user = users(:jane)
9
+ end
10
+
11
+ test 'setup' do
12
+ assert @credential.valid?
13
+ end
14
+
15
+ test 'code required' do
16
+ @credential.code = nil
17
+ assert !@credential.valid?
18
+ end
19
+
20
+ test 'code uniqueness' do
21
+ @credential.code = credentials(:john_token).code
22
+ assert !@credential.valid?
23
+ end
24
+
25
+ test 'email required' do
26
+ @credential.email = nil
27
+ assert !@credential.valid?
28
+ end
29
+
30
+ test 'user required' do
31
+ @credential.user = nil
32
+ assert !@credential.valid?
33
+ end
34
+
35
+ test 'email_credential' do
36
+ assert_equal credentials(:jane_email), @credential.email_credential
37
+ assert_equal credentials(:john_email),
38
+ credentials(:john_email_token).email_credential
39
+
40
+ @credential.email = 'bill@gmail.com'
41
+ assert_nil @credential.email_credential
42
+ end
43
+
44
+ test 'spend verifies the e-mail and destroys the token' do
45
+ email_credential = credentials(:john_email)
46
+ assert !email_credential.verified, 'bad setup'
47
+ credential = credentials(:john_email_token)
48
+ assert_equal Tokens::EmailVerification, credential.class, 'bad setup'
49
+
50
+ assert_difference 'Credential.count', -1 do
51
+ credential.spend
52
+ end
53
+ assert credential.frozen?, 'not destroyed'
54
+ assert email_credential.reload.verified?, 'e-mail not verified'
55
+ end
56
+
57
+ test 'spend does not verify a random e-mail and still destroys the token' do
58
+ email_credential = credentials(:john_email)
59
+ credential = credentials(:john_email_token)
60
+ credential.email = 'bill@gmail.com'
61
+
62
+ assert_difference 'Credential.count', -1 do
63
+ credential.spend
64
+ end
65
+ assert credential.frozen?, 'not destroyed'
66
+ assert !email_credential.reload.verified?, 'e-mail wrongly verified'
67
+ end
68
+
69
+ test 'random_for' do
70
+ token = Tokens::EmailVerification.random_for credentials(:jane_email)
71
+ assert token.valid?, 'valid token'
72
+ assert_equal users(:jane), token.user
73
+ assert_equal credentials(:jane_email).email, token.email
74
+ assert_equal Tokens::EmailVerification, token.class
75
+ assert !token.new_record?, 'saved token'
76
+ assert_operator users(:jane).credentials, :include?, token
77
+ end
78
+ end
@@ -1,4 +1,4 @@
1
- require File.expand_path('../test_helper', __FILE__)
1
+ require File.expand_path('../../test_helper', __FILE__)
2
2
 
3
3
  class FacebookCredentialTest < ActiveSupport::TestCase
4
4
  def setup
@@ -0,0 +1,84 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ class OneTimeTokenCredentialTest < ActiveSupport::TestCase
4
+ def setup
5
+ @credential = Tokens::OneTime.new(
6
+ :code => 'AyCMIixa5C7BBqU-XFI7l7IaUFJ4zQZPmcK6oNb3FLo')
7
+ @credential.user = users(:bill)
8
+ end
9
+
10
+ test 'setup' do
11
+ assert @credential.valid?
12
+ end
13
+
14
+ test 'code required' do
15
+ @credential.code = nil
16
+ assert !@credential.valid?
17
+ end
18
+
19
+ test 'code uniqueness' do
20
+ @credential.code = credentials(:john_token).code
21
+ assert !@credential.valid?
22
+ end
23
+
24
+ test 'user presence' do
25
+ @credential.user = nil
26
+ assert !@credential.valid?
27
+ end
28
+
29
+ test 'spend destroys the token' do
30
+ credential = credentials(:john_token)
31
+ assert_equal Tokens::OneTime, credential.class, 'bad setup'
32
+
33
+ assert_difference 'Credential.count', -1 do
34
+ credential.spend
35
+ end
36
+ assert credential.frozen?, 'not destroyed'
37
+ end
38
+
39
+ test 'authenticate spends the token' do
40
+ john = 'YZ-Fo8HX6_NyU6lVZXYi6cMDLV5eAgt35UTF5l8bD6A'
41
+ bogus = 'AyCMIixa5C7BBqU-XFI7l7IaUFJ4zQZPmcK6oNb3FLo'
42
+ assert_difference 'Credential.count', -1, 'token spent' do
43
+ assert_equal users(:john), Credentials::Token.authenticate(john)
44
+ end
45
+ assert_no_difference 'Credential.count', 'token mistakenly spent' do
46
+ assert_equal :invalid, Credentials::Token.authenticate(bogus)
47
+ end
48
+ end
49
+
50
+ test 'authenticate calls User#auth_bounce_reason' do
51
+ john = 'YZ-Fo8HX6_NyU6lVZXYi6cMDLV5eAgt35UTF5l8bD6A'
52
+ jane = '6TXe1vv7BgOw0BkJ1hzUKO6G08fLk4sVfJ3wPDZHS-c'
53
+ bogus = 'AyCMIixa5C7BBqU-XFI7l7IaUFJ4zQZPmcK6oNb3FLo'
54
+
55
+ with_blocked_credential credentials(:john_token), :reason do
56
+ assert_no_difference 'Credential.count', 'no token spent' do
57
+ assert_equal :reason, Credentials::Token.authenticate(john)
58
+ end
59
+ end
60
+ end
61
+
62
+ test 'instance authenticate spends the token' do
63
+ assert_difference 'Credential.count', -1, 'token spent' do
64
+ assert_equal users(:john), credentials(:john_token).authenticate
65
+ end
66
+ end
67
+
68
+ test 'instance authenticate calls User#auth_bounce_reason' do
69
+ with_blocked_credential credentials(:john_token), :reason do
70
+ assert_no_difference 'Credential.count', 'token mistakenly spent' do
71
+ assert_equal :reason, credentials(:john_token).authenticate
72
+ end
73
+ end
74
+ end
75
+
76
+ test 'random_for' do
77
+ token = Tokens::OneTime.random_for users(:john)
78
+ assert token.valid?, 'valid token'
79
+ assert_equal users(:john), token.user
80
+ assert_equal Tokens::OneTime, token.class
81
+ assert !token.new_record?, 'saved token'
82
+ assert_operator users(:john).credentials, :include?, token
83
+ end
84
+ end
@@ -1,4 +1,4 @@
1
- require File.expand_path('../test_helper', __FILE__)
1
+ require File.expand_path('../../test_helper', __FILE__)
2
2
 
3
3
  class PasswordCredentialTest < ActiveSupport::TestCase
4
4
  def setup
@@ -0,0 +1,72 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ class PasswordVerificationTokenTest < ActiveSupport::TestCase
4
+ def setup
5
+ @credential = Tokens::PasswordReset.new(
6
+ :code => 'AyCMIixa5C7BBqU-XFI7l7IaUFJ4zQZPmcK6oNb3FLo')
7
+ @credential.user = users(:john)
8
+ end
9
+
10
+ test 'setup' do
11
+ assert @credential.valid?
12
+ end
13
+
14
+ test 'code required' do
15
+ @credential.code = nil
16
+ assert !@credential.valid?
17
+ end
18
+
19
+ test 'code uniqueness' do
20
+ @credential.code = credentials(:john_token).code
21
+ assert !@credential.valid?
22
+ end
23
+
24
+ test 'user required' do
25
+ @credential.user = nil
26
+ assert !@credential.valid?
27
+ end
28
+
29
+ test 'password_credential' do
30
+ assert_equal credentials(:john_password), @credential.password_credential
31
+ assert_equal credentials(:jane_password),
32
+ credentials(:jane_password_token).password_credential
33
+
34
+ @credential.user = users(:bill)
35
+ assert_nil @credential.password_credential
36
+ end
37
+
38
+ test 'spend blanks out the password and destroys the token' do
39
+ password_credential = credentials(:jane_password)
40
+ credential = credentials(:jane_password_token)
41
+ assert_equal Tokens::PasswordReset, credential.class, 'bad setup'
42
+
43
+ assert_difference 'Credential.count', -2 do
44
+ assert_difference 'Credentials::Password.count', -1 do
45
+ credential.spend
46
+ end
47
+ end
48
+ assert credential.frozen?, 'not destroyed'
49
+ assert_nil Credential.where(:id => password_credential.id).first,
50
+ 'password not blanked out'
51
+ end
52
+
53
+ test 'spend works on password-less user and still destroys the token' do
54
+ password_credential = credentials(:jane_password)
55
+ password_credential.destroy
56
+ credential = credentials(:jane_password_token)
57
+
58
+ assert_difference 'Credential.count', -1 do
59
+ credential.spend
60
+ end
61
+ assert credential.frozen?, 'not destroyed'
62
+ end
63
+
64
+ test 'random_for' do
65
+ token = Tokens::PasswordReset.random_for users(:john)
66
+ assert token.valid?, 'valid token'
67
+ assert_equal users(:john), token.user
68
+ assert_equal Tokens::PasswordReset, token.class
69
+ assert !token.new_record?, 'saved token'
70
+ assert_operator users(:john).credentials, :include?, token
71
+ end
72
+ end
@@ -0,0 +1,102 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ class TokenCredentialTest < ActiveSupport::TestCase
4
+ def setup
5
+ @credential = Credentials::Token.new(
6
+ :code => 'AyCMIixa5C7BBqU-XFI7l7IaUFJ4zQZPmcK6oNb3FLo')
7
+ @credential.user = users(:bill)
8
+ end
9
+
10
+ test 'setup' do
11
+ assert @credential.valid?
12
+ end
13
+
14
+ test 'code required' do
15
+ @credential.code = nil
16
+ assert !@credential.valid?
17
+ end
18
+
19
+ test 'code uniqueness' do
20
+ @credential.code = credentials(:john_token).code
21
+ assert !@credential.valid?
22
+ end
23
+
24
+ test 'user presence' do
25
+ @credential.user = nil
26
+ assert !@credential.valid?
27
+ end
28
+
29
+ test 'spend does nothing' do
30
+ credential = credentials(:jane_token)
31
+ assert_equal Credentials::Token, credential.class, 'bad setup'
32
+
33
+ assert_no_difference 'Credential.count' do
34
+ credential.spend
35
+ end
36
+ end
37
+
38
+ test 'random_for' do
39
+ token = Credentials::Token.random_for users(:john)
40
+ assert token.valid?, 'valid token'
41
+ assert_equal users(:john), token.user
42
+ assert_equal Credentials::Token, token.class
43
+ assert !token.new_record?, 'saved token'
44
+ assert_operator users(:john).credentials, :include?, token
45
+ end
46
+
47
+ test 'with_code' do
48
+ john = 'YZ-Fo8HX6_NyU6lVZXYi6cMDLV5eAgt35UTF5l8bD6A'
49
+ john2 = 'bDSU4tzfjuob79e3R0ykLcOGTBBYvuBWWJ9V06tQrCE'
50
+ jane = '6TXe1vv7BgOw0BkJ1hzUKO6G08fLk4sVfJ3wPDZHS-c'
51
+ bogus = 'AyCMIixa5C7BBqU-XFI7l7IaUFJ4zQZPmcK6oNb3FLo'
52
+ assert_equal credentials(:john_token), Credentials::Token.with_code(john)
53
+ assert_equal credentials(:jane_token), Credentials::Token.with_code(jane)
54
+ assert_equal credentials(:john_email_token),
55
+ Credentials::Token.with_code(john2)
56
+ assert_nil Credentials::Token.with_code(bogus)
57
+ assert_nil Credentials::Token.with_code('john@gmail.com')
58
+ assert_nil Credentials::Token.with_code(credentials(:jane_email).name)
59
+ end
60
+
61
+ test 'find_by_param' do
62
+ assert_equal credentials(:john_token), Credentials::Token.
63
+ find_by_param(credentials(:john_token).to_param)
64
+ assert_equal credentials(:jane_token), Credentials::Token.
65
+ find_by_param(credentials(:jane_token).to_param)
66
+ assert_equal nil, Credentials::Token.find_by_param('bogus token')
67
+ assert_equal nil, Credentials::Token.find_by_param(nil)
68
+ end
69
+
70
+ test 'class authenticate' do
71
+ john = 'YZ-Fo8HX6_NyU6lVZXYi6cMDLV5eAgt35UTF5l8bD6A'
72
+ jane = '6TXe1vv7BgOw0BkJ1hzUKO6G08fLk4sVfJ3wPDZHS-c'
73
+ bogus = 'AyCMIixa5C7BBqU-XFI7l7IaUFJ4zQZPmcK6oNb3FLo'
74
+ assert_equal users(:john), Credentials::Token.authenticate(john)
75
+ assert_equal users(:jane), Credentials::Token.authenticate(jane)
76
+ assert_equal :invalid, Credentials::Token.authenticate(bogus)
77
+ end
78
+
79
+ test 'class authenticate calls User#auth_bounce_reason' do
80
+ john = 'YZ-Fo8HX6_NyU6lVZXYi6cMDLV5eAgt35UTF5l8bD6A'
81
+ jane = '6TXe1vv7BgOw0BkJ1hzUKO6G08fLk4sVfJ3wPDZHS-c'
82
+ bogus = 'AyCMIixa5C7BBqU-XFI7l7IaUFJ4zQZPmcK6oNb3FLo'
83
+
84
+ with_blocked_credential credentials(:john_token), :reason do
85
+ assert_equal :reason, Credentials::Token.authenticate(john)
86
+ assert_equal users(:jane), Credentials::Token.authenticate(jane)
87
+ assert_equal :invalid, Credentials::Token.authenticate(bogus)
88
+ end
89
+ end
90
+
91
+ test 'instance authenticate' do
92
+ assert_equal users(:john), credentials(:john_token).authenticate
93
+ assert_equal users(:jane), credentials(:jane_token).authenticate
94
+ end
95
+
96
+ test 'instance authenticate calls User#auth_bounce_reason' do
97
+ with_blocked_credential credentials(:john_token), :reason do
98
+ assert_equal :reason, credentials(:john_token).authenticate
99
+ assert_equal users(:jane), credentials(:jane_token).authenticate
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,20 @@
1
+ <p>
2
+ This view gets displayed when the user tries to access something forbidden.
3
+ </p>
4
+
5
+ <% if current_user %>
6
+ <p>
7
+ You should inform the user that they are logged in as
8
+ <%= current_user.exuid %> and suggest them to
9
+ <%= link_to 'Log out', session_path, :method => :destroy %> and log in as a
10
+ different user.
11
+ </p>
12
+ <% else %>
13
+ <p>
14
+ The user will only see this if JavaScript is disabled. Ask them to
15
+ <%= link_to 'Log in', new_session_path %>.
16
+ </p>
17
+ <script type="text/javascript">
18
+ window.location = "<%= new_session_path %>";
19
+ </script>
20
+ <% end %>