authkit 0.2.1 → 0.4.0

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 (23) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +144 -34
  3. data/Rakefile +8 -0
  4. data/lib/authkit/version.rb +1 -1
  5. data/lib/generators/authkit/install_generator.rb +0 -3
  6. data/lib/generators/authkit/templates/app/controllers/application_controller.rb +20 -13
  7. data/lib/generators/authkit/templates/app/controllers/email_confirmation_controller.rb +21 -3
  8. data/lib/generators/authkit/templates/app/controllers/password_change_controller.rb +37 -5
  9. data/lib/generators/authkit/templates/app/controllers/sessions_controller.rb +3 -1
  10. data/lib/generators/authkit/templates/app/controllers/signup_controller.rb +3 -1
  11. data/lib/generators/authkit/templates/app/models/user.rb +48 -52
  12. data/lib/generators/authkit/templates/app/views/password_change/show.html.erb +1 -0
  13. data/lib/generators/authkit/templates/app/views/sessions/new.html.erb +4 -0
  14. data/lib/generators/authkit/templates/app/views/signup/new.html.erb +5 -1
  15. data/lib/generators/authkit/templates/app/views/users/edit.html.erb +1 -1
  16. data/lib/generators/authkit/templates/spec/controllers/application_controller_spec.rb +26 -26
  17. data/lib/generators/authkit/templates/spec/controllers/email_confirmation_controller_spec.rb +28 -10
  18. data/lib/generators/authkit/templates/spec/controllers/password_change_controller_spec.rb +71 -21
  19. data/lib/generators/authkit/templates/spec/controllers/sessions_controller_spec.rb +14 -0
  20. data/lib/generators/authkit/templates/spec/controllers/signup_controller_spec.rb +14 -0
  21. data/lib/generators/authkit/templates/spec/forms/signup_spec.rb +3 -0
  22. data/lib/generators/authkit/templates/spec/models/user_spec.rb +63 -66
  23. metadata +2 -2
@@ -7,10 +7,12 @@ class SignupController < ApplicationController
7
7
  end
8
8
 
9
9
  def create
10
+ remember = params[:remember_me] == "1"
11
+
10
12
  @signup = Signup.new(signup_params)
11
13
 
12
14
  if @signup.save
13
- login(@signup.user)
15
+ login(@signup.user, remember)
14
16
  respond_to do |format|
15
17
  format.json { head :no_content }
16
18
  format.html {
@@ -31,52 +31,6 @@ class User < ActiveRecord::Base
31
31
  # Confirm emails check for existing emails for uniqueness as a convenience
32
32
  validate :confirmation_email_uniqueness, if: :confirmation_email_set?
33
33
 
34
- def self.user_from_token(token)
35
- verifier = ActiveSupport::MessageVerifier.new(Rails.application.config.secret_key_base)
36
- id = verifier.verify(token)
37
- User.where(id: id).first
38
- rescue ActiveSupport::MessageVerifier::InvalidSignature
39
- nil
40
- end
41
-
42
- # The tokens created by this method have unique indexes but they are digests of the
43
- # id which is unique. Because of this we shouldn't see a conflict. If we do, however
44
- # we want the ActiveRecord::StatementInvalid or ActiveRecord::RecordNotUnique exeception
45
- # to bubble up.
46
- def set_token(field)
47
- return unless self.persisted?
48
- verifier = ActiveSupport::MessageVerifier.new(Rails.application.config.secret_key_base)
49
- self.send("#{field}_created_at=", Time.now)
50
- self.send("#{field}=", verifier.generate(self.id))
51
- self.save
52
- end
53
-
54
- # These methods are a little redundant, but give you the opportunity to
55
- # insert expiry for any of these token based authentication strategies.
56
- # For example:
57
- #
58
- # def self.user_from_remember_token(token)
59
- # user = user_from_token(token)
60
- # user = nil if user && user.remember_token_created_at < 30.days.ago
61
- # user
62
- # end
63
- #
64
- def self.user_from_remember_token(token)
65
- user_from_token(token)
66
- end
67
-
68
- def self.user_from_reset_password_token(token)
69
- user_from_token(token)
70
- end
71
-
72
- def self.user_from_confirmation_token(token)
73
- user_from_token(token)
74
- end
75
-
76
- def self.user_from_unlock_token(token)
77
- user_from_token(token)
78
- end
79
-
80
34
  def display_name
81
35
  [first_name, last_name].compact.join(" ")
82
36
  end
@@ -90,31 +44,72 @@ class User < ActiveRecord::Base
90
44
  self.save
91
45
  end
92
46
 
93
- def send_welcome
94
- # TODO: insert your mailer logic here
95
- true
47
+ # The tokens created by this method have unique indexes but collisions are very
48
+ # unlikely (1/64^32). Because of this there shouldn't be a conflict. If one occurs
49
+ # the ActiveRecord::StatementInvalid or ActiveRecord::RecordNotUnique exeception
50
+ # should bubble up.
51
+ def set_remember_token
52
+ self.remember_token = SecureRandom.urlsafe_base64(32)
53
+ self.remember_token_created_at = Time.now
54
+ self.save!
96
55
  end
97
56
 
98
57
  def clear_remember_token
99
58
  self.remember_token = nil
100
59
  self.remember_token_created_at = nil
101
- self.save
60
+ self.save!
61
+ end
62
+
63
+ def reset_password_token_expired?
64
+ # TODO reset password tokens expire in 1 day by default
65
+ self.reset_password_token_created_at.blank? || self.reset_password_token_created_at <= 1.day.ago
66
+ end
67
+
68
+ def confirmation_token_expired?
69
+ # TODO confirmation tokens expire in 3 days by default
70
+ self.confirmation_token_created_at.blank? || self.confirmation_token_created_at <= 3.days.ago
71
+ end
72
+
73
+ def remember_token_expired?
74
+ # TODO remember tokens expire in 1 year by default
75
+ self.remember_token_created_at.blank? || self.remember_token_created_at <= 1.year.ago
102
76
  end
103
77
 
78
+ def send_welcome
79
+ # TODO insert your mailer logic here
80
+ true
81
+ end
82
+
83
+ # The tokens created by this method have unique indexes but collisions are very
84
+ # unlikely (1/64^32). Because of this there shouldn't be a conflict. If one occurs
85
+ # the ActiveRecord::StatementInvalid or ActiveRecord::RecordNotUnique exeception
86
+ # should bubble up.
104
87
  def send_reset_password
105
- return false unless set_token(:reset_password_token)
88
+ self.reset_password_token = SecureRandom.urlsafe_base64(32)
89
+ self.reset_password_token_created_at = Time.now
90
+ self.save!
106
91
 
107
92
  # TODO: insert your mailer logic here
108
93
  true
109
94
  end
110
95
 
96
+ # The tokens created by this method have unique indexes but collisions are very
97
+ # unlikely (1/64^32). Because of this there shouldn't be a conflict. If one occurs
98
+ # the ActiveRecord::StatementInvalid or ActiveRecord::RecordNotUnique exeception
99
+ # should bubble up.
111
100
  def send_confirmation
112
- return false unless set_token(:confirmation_token)
101
+ self.confirmation_token = SecureRandom.urlsafe_base64(32)
102
+ self.confirmation_token_created_at = Time.now
103
+ self.save!
113
104
 
114
105
  # TODO: insert your mailer logic here
115
106
  true
116
107
  end
117
108
 
109
+ def pending_confirmation?
110
+ self.confirmation_token.present?
111
+ end
112
+
118
113
  def email_confirmed
119
114
  return false if self.confirmation_token.blank? || self.confirmation_email.blank?
120
115
 
@@ -172,4 +167,5 @@ class User < ActiveRecord::Base
172
167
  def confirmation_email_uniqueness
173
168
  errors.add(:confirmation_email, :taken, value: email) if User.where('email = ?', confirmation_email).count > 0
174
169
  end
170
+
175
171
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  <%= form_tag(password_change_path, method: "post") do %>
4
4
  <%= hidden_field_tag :token, params[:token] %>
5
+ <%= hidden_field_tag :email, params[:email] %>
5
6
 
6
7
  <div class="field">
7
8
  <%= label_tag "password" %>
@@ -9,5 +9,9 @@
9
9
  <%= label_tag "password" %>
10
10
  <%= password_field_tag "password" %>
11
11
  </div>
12
+ <div class="field">
13
+ <%= check_box_tag :remember_me, "1", true %>
14
+ <label for="remember_me">Keep me signed in on this computer</label>
15
+ </div>
12
16
  <%= submit_tag "Login" %>
13
17
  <% end %>
@@ -35,7 +35,7 @@
35
35
  <%= f.text_field "phone_number" %>
36
36
  </div>
37
37
  <div class="field">
38
- <%= f.label "phone_number" %>
38
+ <%= f.label "time_zone" %>
39
39
  <%= f.time_zone_select('time_zone', ActiveSupport::TimeZone.us_zones, :default => "Pacific Time (US & Canada)") %>
40
40
  </div>
41
41
  <div class="field">
@@ -54,5 +54,9 @@
54
54
  <%= f.label "password_confirmation" %>
55
55
  <%= f.password_field "password_confirmation" %>
56
56
  </div>
57
+ <div class="field">
58
+ <%= check_box_tag :remember_me, "1", true %>
59
+ <label for="remember_me">Keep me signed in on this computer</label>
60
+ </div>
57
61
  <%= f.submit "Sign up" %>
58
62
  <% end %>
@@ -35,7 +35,7 @@
35
35
  <%= f.text_field "phone_number" %>
36
36
  </div>
37
37
  <div class="field">
38
- <%= f.label "phone_number" %>
38
+ <%= f.label "time_zone" %>
39
39
  <%= f.time_zone_select('time_zone', ActiveSupport::TimeZone.us_zones, :default => "Pacific Time (US & Canada)") %>
40
40
  </div>
41
41
  <div class="field">
@@ -11,7 +11,6 @@ describe ApplicationController do
11
11
 
12
12
  controller do
13
13
  before_filter :require_login, only: [:index]
14
- before_filter :require_token, only: [:show]
15
14
 
16
15
  def new
17
16
  head :ok
@@ -46,15 +45,30 @@ describe ApplicationController do
46
45
  end
47
46
 
48
47
  it "finds the current user from the remember cookie" do
48
+ user.save
49
+ user.set_remember_token
49
50
  # Need to sign the cookie
50
51
  request.env["action_dispatch.secret_token"] = "SECRET"
51
52
  verifier = ActiveSupport::MessageVerifier.new(request.env["action_dispatch.secret_token".freeze])
52
- request.cookies[:remember] = verifier.generate("TOKEN")
53
- User.should_receive(:user_from_remember_token).with("TOKEN").and_return(user)
53
+ request.cookies[:remember] = verifier.generate(user.remember_token)
54
54
  get :index
55
55
  controller.send(:current_user).should == user
56
56
  end
57
57
 
58
+ it "doesn't find the current user from the remember cookie if it is expired" do
59
+ # Setup expired token
60
+ user.set_remember_token
61
+ user.remember_token_created_at = 1.year.ago
62
+ user.save
63
+
64
+ # Need to sign the cookie
65
+ request.env["action_dispatch.secret_token"] = "SECRET"
66
+ verifier = ActiveSupport::MessageVerifier.new(request.env["action_dispatch.secret_token".freeze])
67
+ request.cookies[:remember] = verifier.generate(user.remember_token)
68
+ get :index
69
+ controller.send(:current_user).should be_nil
70
+ end
71
+
58
72
  it "sets the time zone" do
59
73
  User.any_instance.should_receive(:time_zone).and_return("Pacific Time (US & Canada)")
60
74
  get :index, {}, logged_in_session
@@ -111,27 +125,6 @@ describe ApplicationController do
111
125
  end
112
126
  end
113
127
 
114
- describe "tokens" do
115
- it "requires a user token" do
116
- User.should_receive(:user_from_token).with('testtoken').and_return(user)
117
- get 'show', {id: '1', token: 'testtoken'}
118
- end
119
-
120
- it "returns an error if there is no user token" do
121
- User.should_receive(:user_from_token).with('testtoken').and_return(nil)
122
- controller.should_receive(:deny_user)
123
- get 'show', {id: '1', token: 'testtoken'}
124
- end
125
-
126
- it "verifies the token" do
127
- request.env["action_dispatch.secret_token"] = "SECRET"
128
- verifier = ActiveSupport::MessageVerifier.new(request.env["action_dispatch.secret_token".freeze])
129
- token = verifier.generate("TOKEN")
130
- User.should_receive(:user_from_token).with(token).and_return(user)
131
- get 'show', {id: '1', token: token}
132
- end
133
- end
134
-
135
128
  describe "login" do
136
129
  it "tracks the login" do
137
130
  get :new
@@ -142,8 +135,15 @@ describe ApplicationController do
142
135
  it "remembers the user using a token and cookie" do
143
136
  get :new
144
137
  controller.should_receive(:set_remember_cookie)
145
- user.should_receive(:set_token).with(:remember_token).and_return(:true)
146
- controller.send(:login, user)
138
+ user.should_receive(:set_remember_token)
139
+ controller.send(:login, user, true)
140
+ end
141
+
142
+ it "does not remember the user using a token and cookie when not requested" do
143
+ get :new
144
+ controller.should_not_receive(:set_remember_cookie)
145
+ user.should_not_receive(:set_remember_token)
146
+ controller.send(:login, user, false)
147
147
  end
148
148
 
149
149
  it "resets the session" do
@@ -7,8 +7,25 @@ describe EmailConfirmationController do
7
7
  let(:token) { "TOKEN" }
8
8
 
9
9
  describe "GET 'show'" do
10
+ it "requires a login" do
11
+ controller.stub(:current_user).and_return(nil)
12
+ get 'show', token: token
13
+ response.should be_redirect
14
+ flash[:error].should_not be_empty
15
+ end
16
+
10
17
  it "requires a valid token" do
11
- User.should_receive(:user_from_token).with(token).and_return(nil)
18
+ user.confirmation_token = "OTHER TOKEN"
19
+ controller.stub(:current_user).and_return(user)
20
+ get 'show', token: token
21
+ response.should be_redirect
22
+ flash[:error].should_not be_empty
23
+ end
24
+
25
+ it "requires an unexpired token" do
26
+ user.confirmation_token = token
27
+ user.confirmation_token_created_at = 4.days.ago
28
+ controller.stub(:current_user).and_return(user)
12
29
  get 'show', token: token
13
30
  response.should be_redirect
14
31
  flash[:error].should_not be_empty
@@ -18,36 +35,37 @@ describe EmailConfirmationController do
18
35
  before(:each) do
19
36
  user.confirmation_email = "new@example.com"
20
37
  user.confirmation_token = token
38
+ user.confirmation_token_created_at = Time.now
21
39
  end
22
40
 
23
41
  describe "when the confirmation is successful" do
24
42
  it "confirms the user email" do
25
- User.should_receive(:user_from_token).with(token).and_return(user)
43
+ controller.stub(:current_user).and_return(user)
26
44
  user.should_receive(:email_confirmed).and_return(true)
27
45
  get 'show', token: token
28
46
  end
29
47
 
30
- it "signs the user in" do
31
- User.should_receive(:user_from_token).with(token).and_return(user)
32
- controller.should_receive(:login).with(user)
48
+ it "does not sign the user in" do
49
+ controller.stub(:current_user).and_return(user)
50
+ controller.should_not_receive(:login)
33
51
  get 'show', token: token
34
52
  end
35
53
 
36
54
  it "sets the flash" do
37
- User.should_receive(:user_from_token).with(token).and_return(user)
55
+ controller.stub(:current_user).and_return(user)
38
56
  get 'show', token: token
39
57
  flash[:notice].should_not be_nil
40
58
  end
41
59
 
42
60
  it "redirects the user" do
43
- User.should_receive(:user_from_token).with(token).and_return(user)
61
+ controller.stub(:current_user).and_return(user)
44
62
  get 'show', token: token
45
63
  response.should be_redirect
46
64
  end
47
65
 
48
66
  describe "from json" do
49
67
  it "returns http success" do
50
- User.should_receive(:user_from_token).with(token).and_return(user)
68
+ controller.stub(:current_user).and_return(user)
51
69
  get 'show', token: token, format: 'json'
52
70
  response.should be_success
53
71
  end
@@ -57,7 +75,7 @@ describe EmailConfirmationController do
57
75
 
58
76
  describe "when the confirmation is not successful" do
59
77
  it "handles invalid confirmations" do
60
- User.should_receive(:user_from_token).with(token).and_return(user)
78
+ controller.stub(:current_user).and_return(user)
61
79
  user.should_receive(:email_confirmed).and_return(false)
62
80
  get 'show', token: token
63
81
  flash[:error].should_not be_empty
@@ -66,7 +84,7 @@ describe EmailConfirmationController do
66
84
 
67
85
  describe "from json" do
68
86
  it "returns a 422" do
69
- User.should_receive(:user_from_token).with(token).and_return(user)
87
+ controller.stub(:current_user).and_return(user)
70
88
  user.should_receive(:email_confirmed).and_return(false)
71
89
  get 'show', token: token, format: 'json'
72
90
  response.code.should == '422'
@@ -3,63 +3,113 @@ require 'spec_helper'
3
3
  describe PasswordChangeController do
4
4
  render_views
5
5
 
6
- let(:user) { build(:user) }
7
6
  let(:token) { "TOKEN" }
7
+ let(:user) { build(:user, reset_password_token: token, reset_password_token_created_at: Time.now) }
8
+ let(:valid_params) { {token: token, email: user.email} }
9
+ let(:password_params) { valid_params.merge(password: 'newpassword', password_confirmation: 'newpassword') }
8
10
 
9
11
  describe "GET 'show'" do
12
+ it "requires no user" do
13
+ controller.stub(:email_user).and_return(user)
14
+ controller.should_receive(:logout)
15
+ get 'show', valid_params
16
+ end
17
+
18
+ it "requires an email user" do
19
+ user.save
20
+ get 'show', valid_params
21
+ assigns(:user).id.should == user.id
22
+ end
23
+
24
+ it "redirects if there is no email user" do
25
+ user.save
26
+ expect {
27
+ get 'show', {token: token, email: "invalid@example.com"}
28
+ }.to raise_error(ActiveRecord::RecordNotFound)
29
+ end
30
+
10
31
  it "requires a valid token" do
11
- User.should_receive(:user_from_token).with(token).and_return(nil)
12
- get 'show', token: token
32
+ controller.stub(:email_user).and_return(user)
33
+ user.reset_password_token = "OTHER TOKEN"
34
+ get 'show', valid_params
35
+ response.should be_redirect
36
+ flash[:error].should_not be_empty
37
+ end
38
+
39
+ it "requires an unexpired token" do
40
+ controller.stub(:email_user).and_return(user)
41
+ user.reset_password_token_created_at = 1.year.ago
42
+ get 'show', valid_params
13
43
  response.should be_redirect
14
44
  flash[:error].should_not be_empty
15
45
  end
16
46
 
17
47
  it "returns http success" do
18
- User.should_receive(:user_from_token).with(token).and_return(user)
19
- get 'show', token: token
48
+ controller.stub(:email_user).and_return(user)
49
+ get 'show', valid_params
20
50
  response.should be_success
21
51
  end
22
52
  end
23
53
 
24
54
  describe "POST 'create'" do
55
+ it "requires no user" do
56
+ controller.stub(:email_user).and_return(user)
57
+ controller.should_receive(:logout)
58
+ get 'show', valid_params
59
+ end
60
+
61
+ it "requires an email user" do
62
+ user.save
63
+ post 'create', password_params
64
+ assigns(:user).id.should == user.id
65
+ end
66
+
67
+ it "redirects if there is no email user" do
68
+ user.save
69
+ expect {
70
+ get 'show', {token: token, email: "invalid@example.com"}
71
+ }.to raise_error(ActiveRecord::RecordNotFound)
72
+ end
73
+
25
74
  it "requires a valid token" do
26
- User.should_receive(:user_from_token).with(token).and_return(nil)
27
- post 'create', {token: token, password: 'newpassword', password_confirmation: 'newpassword'}
75
+ controller.stub(:email_user).and_return(user)
76
+ user.reset_password_token = "OTHER TOKEN"
77
+ post 'create', password_params
28
78
  response.should be_redirect
29
79
  flash[:error].should_not be_empty
30
80
  end
31
81
 
32
82
  describe "with valid params" do
33
83
  before(:each) do
34
- User.should_receive(:user_from_token).with(token).and_return(user)
84
+ controller.stub(:email_user).and_return(user)
35
85
  end
36
86
 
37
87
  it "changes the password" do
38
88
  expect {
39
- post 'create', {token: token, password: 'newpassword', password_confirmation: 'newpassword'}
89
+ post 'create', password_params
40
90
  }.to change(user, :password_digest)
41
91
 
42
92
  user.should be_valid
43
93
  end
44
94
 
45
- it "signs the user in" do
46
- controller.should_receive(:login).with(user)
47
- post 'create', {token: token, password: 'newpassword', password_confirmation: 'newpassword'}
95
+ it "does not sign the user in" do
96
+ controller.should_not_receive(:login)
97
+ post 'create', password_params
48
98
  end
49
99
 
50
100
  it "redirects the user" do
51
- post 'create', {token: token, password: 'newpassword', password_confirmation: 'newpassword'}
101
+ post 'create', password_params
52
102
  response.should be_redirect
53
103
  end
54
104
 
55
105
  it "sets the flash" do
56
- post 'create', {token: token, password: 'newpassword', password_confirmation: 'newpassword'}
106
+ post 'create', password_params
57
107
  flash[:notice].should =~ /successfully/i
58
108
  end
59
109
 
60
110
  describe "from json" do
61
111
  it "returns http success" do
62
- post 'create', {token: token, password: 'newpassword', password_confirmation: 'newpassword', format: 'json'}
112
+ post 'create', password_params.merge(format: 'json')
63
113
  response.should be_success
64
114
  end
65
115
  end
@@ -67,27 +117,27 @@ describe PasswordChangeController do
67
117
 
68
118
  describe "with invalid params" do
69
119
  before(:each) do
70
- User.should_receive(:user_from_token).with(token).and_return(user)
120
+ controller.stub(:email_user).and_return(user)
71
121
  end
72
122
 
73
123
  it "doesn't sign the user in" do
74
124
  controller.should_not_receive(:login)
75
- post 'create', {token: token, password: 'newpassword', password_confirmation: 'invalid'}
125
+ post 'create', {token: token, email: user.email, password: 'newpassword', password_confirmation: 'invalid'}
76
126
  end
77
127
 
78
128
  it "renders the show template" do
79
- post 'create', {token: token, password: 'newpassword', password_confirmation: 'invalid'}
129
+ post 'create', {token: token, email: user.email, password: 'newpassword', password_confirmation: 'invalid'}
80
130
  response.should render_template(:show)
81
131
  end
82
132
 
83
133
  it "has errors" do
84
- post 'create', {token: token, password: 'newpassword', password_confirmation: 'invalid'}
85
- assigns(:user).should have(2).errors_on(:password_confirmation)
134
+ post 'create', {token: token, email: user.email, password: 'newpassword', password_confirmation: 'invalid'}
135
+ user.should have(2).errors_on(:password_confirmation)
86
136
  end
87
137
 
88
138
  describe "from json" do
89
139
  it "returns an error" do
90
- post 'create', {token: token, password: 'newpassword', password_confirmation: 'invalid', format: 'json'}
140
+ post 'create', {token: token, email: user.email, password: 'newpassword', password_confirmation: 'invalid', format: 'json'}
91
141
  response.code.should == '422'
92
142
  response.body.should =~ /doesn't match/i
93
143
  end