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,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 %>