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
data/Gemfile CHANGED
@@ -1,11 +1,11 @@
1
- source "http://rubygems.org"
2
- gem "fbgraph_rails", ">= 0.2.2"
3
- gem "rails", ">= 3.2.0.rc2"
1
+ source 'http://rubygems.org'
2
+ gem 'fbgraph_rails', '>= 0.2.2'
3
+ gem 'rails', '>= 3.2.0.rc2'
4
4
 
5
5
  group :development do
6
- gem "bundler", "~> 1.0.0"
7
- gem "flexmock", "~> 0.9.0"
8
- gem "jeweler", "~> 1.6.0"
9
- gem "rcov", ">= 0", :platform => :mri
10
- gem "sqlite3", ">= 1.3.3"
6
+ gem 'bundler', '~> 1.0.0'
7
+ gem 'flexmock', '~> 0.9.0'
8
+ gem 'jeweler', '~> 1.6.0'
9
+ gem 'rcov', '>= 0', :platform => :mri
10
+ gem 'sqlite3', '>= 1.3.5'
11
11
  end
data/Gemfile.lock CHANGED
@@ -122,4 +122,4 @@ DEPENDENCIES
122
122
  jeweler (~> 1.6.0)
123
123
  rails (>= 3.2.0.rc2)
124
124
  rcov
125
- sqlite3 (>= 1.3.3)
125
+ sqlite3 (>= 1.3.5)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.10.5
1
+ 0.10.6
@@ -38,17 +38,25 @@ class Email < ::Credential
38
38
  # Returns the authenticated User instance, or a symbol indicating the reason
39
39
  # why the (potentially valid) password was rejected.
40
40
  def self.authenticate(email)
41
+ credential = with email
42
+ return :invalid unless credential
43
+ user = credential.user
44
+ user.auth_bounce_reason(credential) || user
45
+ end
46
+
47
+ # Locates the credential holding an e-mail address.
48
+ #
49
+ # Returns the User matching the given e-mail, or nil if the e-mail is not
50
+ # associated with any user.
51
+ def self.with(email)
41
52
  # This method is likely to be used to kick off a complex authentication
42
53
  # process, so it makes sense to pre-fetch the user's other credentials.
43
54
  credential = Credentials::Email.where(:name => email).
44
55
  includes(:user => :credentials).first
45
- return :invalid unless credential
46
- user = credential.user
47
- user.auth_bounce_reason(credential) || user
48
56
  end
49
57
 
50
58
  # Forms can only change the e-mail in the credential.
51
59
  attr_accessible :email
52
- end # class Credentials::Email
60
+ end # class Credentials::Email
53
61
 
54
62
  end # namespace Credentials
@@ -0,0 +1,106 @@
1
+ require 'securerandom'
2
+
3
+ # :namespace
4
+ module Credentials
5
+
6
+ # Associates a secret token code with the account.
7
+ #
8
+ # Subclasses of this class are in the tokens namespace.
9
+ class Token < ::Credential
10
+ # The secret token code.
11
+ alias_attribute :code, :name
12
+ # Token names are random, so we can expect they'll be unique across the entire
13
+ # namespace. We need this check to enforce name uniqueness across different
14
+ # token types.
15
+ validates :name, :format => /^[A-Za-z0-9\_\-]+$/, :presence => true,
16
+ :uniqueness => true
17
+
18
+ # Authenticates a user using a secret token code.
19
+ #
20
+ # The token will be spent on successful authentication. One-time tokens are
21
+ # deleted when spent.
22
+ #
23
+ # Returns the authenticated User instance, or a symbol indicating the reason
24
+ # why the (potentially valid) token code was rejected.
25
+ def self.authenticate(code)
26
+ credential = self.with_code code
27
+ credential ? credential.authenticate : :invalid
28
+ end
29
+
30
+ # The token matching a secret code.
31
+ def self.with_code(code)
32
+ # NOTE 1: The where query must be performed off the root type, otherwise
33
+ # Rails will try to guess the right values for the 'type' column,
34
+ # and will sometimes get them wrong.
35
+ # NOTE 2: After using this method, it's likely that the user's other tokens
36
+ # (e.g., email or Facebook OAuth token) will be required, so we
37
+ # pre-fetch them.
38
+ credential = Credential.where(:name => code).
39
+ includes(:user => :credentials).first
40
+
41
+ if credential.is_a? Credentials::Token
42
+ credential
43
+ else
44
+ nil
45
+ end
46
+ end
47
+
48
+ # Authenticates a user using this token.
49
+ #
50
+ # The token will be spent on successful authentication. One-time tokens are
51
+ # deleted when spent.
52
+ #
53
+ # Returns the authenticated User instance, or a symbol indicating the reason
54
+ # why the (potentially valid) token code was rejected.
55
+ def authenticate
56
+ if bounce = user.auth_bounce_reason(self)
57
+ return bounce
58
+ end
59
+ spend
60
+ user
61
+ end
62
+
63
+ # Updates the token's state to reflect that it was used for authentication.
64
+ #
65
+ # Tokens may become invalid after they are spent.
66
+ #
67
+ # Returns the token instance.
68
+ def spend
69
+ self
70
+ end
71
+
72
+ # Creates a new random token for a user.
73
+ #
74
+ # Args:
75
+ # user:: the User who will be authenticated by the token
76
+ # key:: optional data associated with the token
77
+ # klass:: class that will be instantiated (should be a subclass of Token)
78
+ #
79
+ # Returns a newly created and saved token with a random code.
80
+ def self.random_for(user, key = nil, klass = nil)
81
+ klass ||= self
82
+ if key.nil?
83
+ token = self.new(:code => random_code)
84
+ else
85
+ token = self.new(:code => random_code, :key => key)
86
+ end
87
+ user.credentials << token
88
+ token.save!
89
+ token
90
+ end
91
+
92
+ # Generates a random token code.
93
+ def self.random_code
94
+ SecureRandom.urlsafe_base64(32)
95
+ end
96
+
97
+ # Use codes instead of exposing ActiveRecord IDs.
98
+ def to_param
99
+ code
100
+ end
101
+ class <<self
102
+ alias_method :find_by_param, :with_code
103
+ end
104
+ end # class Credentials::Token
105
+
106
+ end # namespace Credentials
@@ -0,0 +1,42 @@
1
+ # :namespace
2
+ module Tokens
3
+
4
+ # A token that verifies the user's ownership of their e-mail address.
5
+ class EmailVerification < OneTime
6
+ # The e-mail address verified by this token.
7
+ #
8
+ # Note that it's useful to keep track of the exact e-mail address that the
9
+ # token vouches for, even if an application only allows a single e-mail per
10
+ # user. Otherwise, a user might be able to change their e-mail address and
11
+ # then use the token to verify the ownership of the wrong address.
12
+ alias_attribute :email, :key
13
+ validates :email, :presence => true
14
+
15
+ # Creates a token with a random code that verifies the given e-mail address.
16
+ def self.random_for(email_credential)
17
+ super email_credential.user, email_credential.email, self
18
+ end
19
+
20
+ # Marks the e-mail associated with the token as verified.
21
+ #
22
+ # Returns the token instance.
23
+ def spend
24
+ self.transaction do
25
+ if credential = email_credential
26
+ credential.verified = true
27
+ credential.save!
28
+ end
29
+ super
30
+ end
31
+ end
32
+
33
+ # The credential whose ownership is verified by this token.
34
+ #
35
+ # This method might return nil if a user is trying to take advantage of a race
36
+ # condition and changes her e-mail address before using the token.
37
+ def email_credential
38
+ user.credentials.find { |c| c.name == email }
39
+ end
40
+ end # class Tokens::EmailVerification
41
+
42
+ end # namespace Tokens
@@ -0,0 +1,16 @@
1
+ # :namespace
2
+ module Tokens
3
+
4
+ # One-time tokens can only be used once to authenticate an account.
5
+ class OneTime < Credentials::Token
6
+ # Updates the token's state to reflect that it was used for authentication.
7
+ #
8
+ # One-time tokens become invalid after they are spent.
9
+ #
10
+ # Returns the token instance.
11
+ def spend
12
+ destroy
13
+ end
14
+ end # class Tokens::OneTime
15
+
16
+ end # namespace Tokens
@@ -0,0 +1,27 @@
1
+ # :namespace
2
+ module Tokens
3
+
4
+ # Lets the user to change their password without knowing the old one.
5
+ class PasswordReset < OneTime
6
+ # Blanks the user's old password, so the new password form won't ask for it.
7
+ #
8
+ # Returns the token instance.
9
+ def spend
10
+ self.transaction do
11
+ if credential = password_credential
12
+ credential.destroy
13
+ end
14
+ super
15
+ end
16
+ end
17
+
18
+ # The credential that is removed by this token.
19
+ #
20
+ # This method might return nil if a user initiates password recovery multiple
21
+ # times.
22
+ def password_credential
23
+ user.credentials.find { |c| c.is_a? Credentials::Password }
24
+ end
25
+ end # class Tokens::PasswordReset
26
+
27
+ end # namespace Tokens
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "authpwn_rails"
8
- s.version = "0.10.5"
8
+ s.version = "0.10.6"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Victor Costan"]
12
- s.date = "2012-01-07"
12
+ s.date = "2012-01-11"
13
13
  s.description = "Works with Facebook."
14
14
  s.email = "victor@costan.us"
15
15
  s.extra_rdoc_files = [
@@ -30,6 +30,10 @@ Gem::Specification.new do |s|
30
30
  "app/models/credentials/email.rb",
31
31
  "app/models/credentials/facebook.rb",
32
32
  "app/models/credentials/password.rb",
33
+ "app/models/credentials/token.rb",
34
+ "app/models/tokens/email_verification.rb",
35
+ "app/models/tokens/one_time.rb",
36
+ "app/models/tokens/password_reset.rb",
33
37
  "authpwn_rails.gemspec",
34
38
  "legacy/migrate_09_to_010.rb",
35
39
  "lib/authpwn_rails.rb",
@@ -44,34 +48,55 @@ Gem::Specification.new do |s|
44
48
  "lib/authpwn_rails/generators/templates/session/forbidden.html.erb",
45
49
  "lib/authpwn_rails/generators/templates/session/home.html.erb",
46
50
  "lib/authpwn_rails/generators/templates/session/new.html.erb",
51
+ "lib/authpwn_rails/generators/templates/session/password_change.html.erb",
47
52
  "lib/authpwn_rails/generators/templates/session/welcome.html.erb",
48
53
  "lib/authpwn_rails/generators/templates/session_controller.rb",
49
54
  "lib/authpwn_rails/generators/templates/session_controller_test.rb",
55
+ "lib/authpwn_rails/generators/templates/session_mailer.rb",
56
+ "lib/authpwn_rails/generators/templates/session_mailer/email_verification_email.html.erb",
57
+ "lib/authpwn_rails/generators/templates/session_mailer/email_verification_email.text.erb",
58
+ "lib/authpwn_rails/generators/templates/session_mailer/reset_password_email.html.erb",
59
+ "lib/authpwn_rails/generators/templates/session_mailer/reset_password_email.text.erb",
60
+ "lib/authpwn_rails/generators/templates/session_mailer_test.rb",
50
61
  "lib/authpwn_rails/generators/templates/user.rb",
51
62
  "lib/authpwn_rails/generators/templates/users.yml",
63
+ "lib/authpwn_rails/routes.rb",
52
64
  "lib/authpwn_rails/session.rb",
53
65
  "lib/authpwn_rails/session_controller.rb",
66
+ "lib/authpwn_rails/session_mailer.rb",
54
67
  "lib/authpwn_rails/test_extensions.rb",
55
68
  "lib/authpwn_rails/user_extensions/email_field.rb",
56
69
  "lib/authpwn_rails/user_extensions/facebook_fields.rb",
57
70
  "lib/authpwn_rails/user_extensions/password_field.rb",
58
71
  "lib/authpwn_rails/user_model.rb",
59
72
  "test/cookie_controller_test.rb",
60
- "test/email_credential_test.rb",
61
- "test/email_field_test.rb",
73
+ "test/credentials/email_credential_test.rb",
74
+ "test/credentials/email_verification_token_test.rb",
75
+ "test/credentials/facebook_credential_test.rb",
76
+ "test/credentials/one_time_token_credential_test.rb",
77
+ "test/credentials/password_credential_test.rb",
78
+ "test/credentials/password_reset_token_test.rb",
79
+ "test/credentials/token_crendential_test.rb",
62
80
  "test/facebook_controller_test.rb",
63
- "test/facebook_credential_test.rb",
64
- "test/facebook_fields_test.rb",
81
+ "test/fixtures/bare_session/forbidden.html.erb",
82
+ "test/fixtures/bare_session/home.html.erb",
83
+ "test/fixtures/bare_session/new.html.erb",
84
+ "test/fixtures/bare_session/password_change.html.erb",
85
+ "test/fixtures/bare_session/welcome.html.erb",
86
+ "test/helpers/action_mailer.rb",
65
87
  "test/helpers/application_controller.rb",
66
88
  "test/helpers/autoload_path.rb",
67
89
  "test/helpers/db_setup.rb",
68
90
  "test/helpers/fbgraph.rb",
69
91
  "test/helpers/routes.rb",
70
92
  "test/helpers/view_helpers.rb",
71
- "test/password_credential_test.rb",
72
- "test/password_field_test.rb",
93
+ "test/routes_test.rb",
73
94
  "test/session_controller_api_test.rb",
95
+ "test/session_mailer_api_test.rb",
74
96
  "test/test_helper.rb",
97
+ "test/user_extensions/email_field_test.rb",
98
+ "test/user_extensions/facebook_fields_test.rb",
99
+ "test/user_extensions/password_field_test.rb",
75
100
  "test/user_test.rb"
76
101
  ]
77
102
  s.homepage = "http://github.com/pwnall/authpwn_rails"
@@ -90,7 +115,7 @@ Gem::Specification.new do |s|
90
115
  s.add_development_dependency(%q<flexmock>, ["~> 0.9.0"])
91
116
  s.add_development_dependency(%q<jeweler>, ["~> 1.6.0"])
92
117
  s.add_development_dependency(%q<rcov>, [">= 0"])
93
- s.add_development_dependency(%q<sqlite3>, [">= 1.3.3"])
118
+ s.add_development_dependency(%q<sqlite3>, [">= 1.3.5"])
94
119
  else
95
120
  s.add_dependency(%q<fbgraph_rails>, [">= 0.2.2"])
96
121
  s.add_dependency(%q<rails>, [">= 3.2.0.rc2"])
@@ -98,7 +123,7 @@ Gem::Specification.new do |s|
98
123
  s.add_dependency(%q<flexmock>, ["~> 0.9.0"])
99
124
  s.add_dependency(%q<jeweler>, ["~> 1.6.0"])
100
125
  s.add_dependency(%q<rcov>, [">= 0"])
101
- s.add_dependency(%q<sqlite3>, [">= 1.3.3"])
126
+ s.add_dependency(%q<sqlite3>, [">= 1.3.5"])
102
127
  end
103
128
  else
104
129
  s.add_dependency(%q<fbgraph_rails>, [">= 0.2.2"])
@@ -107,7 +132,7 @@ Gem::Specification.new do |s|
107
132
  s.add_dependency(%q<flexmock>, ["~> 0.9.0"])
108
133
  s.add_dependency(%q<jeweler>, ["~> 1.6.0"])
109
134
  s.add_dependency(%q<rcov>, [">= 0"])
110
- s.add_dependency(%q<sqlite3>, [">= 1.3.3"])
135
+ s.add_dependency(%q<sqlite3>, [">= 1.3.5"])
111
136
  end
112
137
  end
113
138
 
data/lib/authpwn_rails.rb CHANGED
@@ -6,6 +6,7 @@ module Authpwn
6
6
 
7
7
  autoload :CredentialModel, 'authpwn_rails/credential_model.rb'
8
8
  autoload :SessionController, 'authpwn_rails/session_controller.rb'
9
+ autoload :SessionMailer, 'authpwn_rails/session_mailer.rb'
9
10
  autoload :UserModel, 'authpwn_rails/user_model.rb'
10
11
 
11
12
  # Contains extensions to the User model.
@@ -17,6 +18,7 @@ module Authpwn
17
18
  end
18
19
 
19
20
  require 'authpwn_rails/facebook_session.rb'
21
+ require 'authpwn_rails/routes.rb'
20
22
  require 'authpwn_rails/session.rb'
21
23
  require 'authpwn_rails/test_extensions.rb'
22
24
 
@@ -26,7 +26,7 @@ class AllGenerator < Rails::Generators::Base
26
26
  copy_file File.join('session_controller_test.rb'),
27
27
  File.join('test', 'functional', 'session_controller_test.rb')
28
28
 
29
- route "resource :session, :controller => 'session'"
29
+ route "authpwn_session"
30
30
  route "root :to => 'session#show'"
31
31
  end
32
32
 
@@ -37,9 +37,27 @@ class AllGenerator < Rails::Generators::Base
37
37
  File.join('app', 'views', 'session', 'home.html.erb')
38
38
  copy_file File.join('session', 'new.html.erb'),
39
39
  File.join('app', 'views', 'session', 'new.html.erb')
40
+ copy_file File.join('session', 'password_change.html.erb'),
41
+ File.join('app', 'views', 'session', 'password_change.html.erb')
40
42
  copy_file File.join('session', 'welcome.html.erb'),
41
43
  File.join('app', 'views', 'session', 'welcome.html.erb')
42
- end
44
+ end
45
+
46
+ def create_session_mailer
47
+ copy_file 'session_mailer.rb',
48
+ File.join('app', 'mailers', 'session_mailer.rb')
49
+ copy_file File.join('session_mailer_test.rb'),
50
+ File.join('test', 'functional', 'session_mailer_test.rb')
51
+ end
52
+
53
+ def create_session_mailer_views
54
+ copy_file File.join('session_mailer', 'reset_password_email.html.erb'),
55
+ File.join('app', 'views', 'session_mailer',
56
+ 'reset_password_email.html.erb')
57
+ copy_file File.join('session_mailer', 'reset_password_email.text.erb'),
58
+ File.join('app', 'views', 'session_mailer',
59
+ 'reset_password_email.text.erb')
60
+ end
43
61
  end # class Authpwn::AllGenerator
44
62
 
45
63
  end # namespace Authpwn
@@ -32,3 +32,24 @@ john_facebook:
32
32
  type: Credentials::Facebook
33
33
  name: 702659
34
34
  key: 702659|ffffffffffffffffffffffff-702659|ZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
35
+
36
+ jane_token:
37
+ user: jane
38
+ type: Credentials::Token
39
+ name: "6TXe1vv7BgOw0BkJ1hzUKO6G08fLk4sVfJ3wPDZHS-c"
40
+
41
+ john_token:
42
+ user: john
43
+ type: Tokens::OneTime
44
+ name: YZ-Fo8HX6_NyU6lVZXYi6cMDLV5eAgt35UTF5l8bD6A
45
+
46
+ john_email_token:
47
+ user: john
48
+ type: Tokens::EmailVerification
49
+ name: bDSU4tzfjuob79e3R0ykLcOGTBBYvuBWWJ9V06tQrCE
50
+ key: john@gmail.com
51
+
52
+ jane_password_token:
53
+ user: jane
54
+ type: Tokens::PasswordReset
55
+ name: nbMLTKN18tYy9plBAbsrwT6zdE2jZqoKPk6Ze4lHMSQ