authkit 0.5.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +12 -0
  4. data/README.md +11 -3
  5. data/Rakefile +15 -8
  6. data/authkit.gemspec +2 -2
  7. data/lib/authkit/version.rb +1 -1
  8. data/lib/generators/authkit/install_generator.rb +8 -1
  9. data/lib/generators/authkit/templates/app/controllers/application_controller.rb +23 -20
  10. data/lib/generators/authkit/templates/app/controllers/auths_controller.rb +4 -4
  11. data/lib/generators/authkit/templates/app/controllers/email_confirmation_controller.rb +3 -6
  12. data/lib/generators/authkit/templates/app/controllers/password_change_controller.rb +4 -5
  13. data/lib/generators/authkit/templates/app/controllers/signup_controller.rb +4 -1
  14. data/lib/generators/authkit/templates/app/controllers/upload_controller.rb +3 -3
  15. data/lib/generators/authkit/templates/app/controllers/users_controller.rb +1 -3
  16. data/lib/generators/authkit/templates/app/forms/signup.rb +10 -2
  17. data/lib/generators/authkit/templates/app/models/user.rb +2 -22
  18. data/lib/generators/authkit/templates/app/models/user_session.rb +55 -0
  19. data/lib/generators/authkit/templates/app/views/password_reset/show.html.erb +0 -2
  20. data/lib/generators/authkit/templates/db/migrate/add_authkit_fields_to_users.rb +1 -10
  21. data/lib/generators/authkit/templates/db/migrate/create_auths.rb +6 -2
  22. data/lib/generators/authkit/templates/db/migrate/create_avatars.rb +3 -2
  23. data/lib/generators/authkit/templates/db/migrate/create_user_sessions.rb +27 -0
  24. data/lib/generators/authkit/templates/db/migrate/create_users.rb +2 -2
  25. data/lib/generators/authkit/templates/spec/controllers/application_controller_spec.rb +40 -47
  26. data/lib/generators/authkit/templates/spec/controllers/email_confirmation_controller_spec.rb +11 -11
  27. data/lib/generators/authkit/templates/spec/controllers/password_change_controller_spec.rb +21 -26
  28. data/lib/generators/authkit/templates/spec/controllers/password_reset_controller_spec.rb +11 -16
  29. data/lib/generators/authkit/templates/spec/controllers/sessions_controller_spec.rb +17 -23
  30. data/lib/generators/authkit/templates/spec/controllers/signup_controller_spec.rb +21 -29
  31. data/lib/generators/authkit/templates/spec/controllers/users_controller_spec.rb +14 -18
  32. data/lib/generators/authkit/templates/spec/factories/user_session.rb +6 -0
  33. data/lib/generators/authkit/templates/spec/forms/signup_spec.rb +1 -1
  34. data/lib/generators/authkit/templates/spec/models/user_session_spec.rb +81 -0
  35. data/lib/generators/authkit/templates/spec/models/user_spec.rb +18 -45
  36. data/lib/generators/authkit/templates/spec/support/factory_girl.rb +5 -0
  37. data/lib/generators/authkit/templates/spec/support/shoulda_matchers.rb +6 -0
  38. metadata +13 -6
@@ -0,0 +1,55 @@
1
+ class UserSession < ApplicationRecord
2
+ belongs_to :user
3
+
4
+ scope :active, -> { where(revoked_at: nil, logged_out_at: nil) }
5
+
6
+ validates :user, presence: true
7
+
8
+ validates :remember_token, presence: true
9
+
10
+ before_validation :set_remember_token
11
+
12
+ def active?
13
+ !logged_out? && !revoked?
14
+ end
15
+
16
+ def logged_out?
17
+ logged_out_at.present?
18
+ end
19
+
20
+ def revoked?
21
+ revoked_at.present?
22
+ end
23
+
24
+ def sudo?
25
+ sudo_enabled_at.present? && sudo_enabled_at > 1.hour.ago
26
+ end
27
+
28
+ def sudo
29
+ self.sudo_enabled_at = Time.now
30
+ save!
31
+ end
32
+
33
+ def logout
34
+ self.logged_out_at = Time.now
35
+ save!
36
+ end
37
+
38
+ def access(request, tracking=true)
39
+ self.accessed_at = Time.now
40
+ self.ip = request.remote_ip if tracking
41
+ self.user_agent = request.user_agent if tracking
42
+ save!
43
+ end
44
+
45
+ private
46
+
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) if self.remember_token.blank?
53
+ end
54
+ end
55
+
@@ -1,8 +1,6 @@
1
1
  <h1>Reset your password</h1>
2
2
 
3
3
  <%%= form_tag(password_reset_path, method: "post") do %>
4
- <%%= hidden_field_tag :token, params[:token] %>
5
-
6
4
  <div class="field">
7
5
  <%%= label_tag "email", "Email<% if username? %> or username<% end %>" %>
8
6
  <%%= text_field_tag "email" %>
@@ -2,7 +2,7 @@
2
2
  #
3
3
  # Add fields to the users table for managing authentication.
4
4
  #
5
- class AddAuthkitFieldsToUsers < ActiveRecord::Migration
5
+ class AddAuthkitFieldsToUsers < ActiveRecord::Migration[5.0]
6
6
  def self.up
7
7
  add_column :users, :email, :string, :default => "", :null => false
8
8
  add_column :users, :password_digest, :string, :default => "", :null => false
@@ -35,10 +35,6 @@ class AddAuthkitFieldsToUsers < ActiveRecord::Migration
35
35
  add_column :users, :reset_password_token, :string
36
36
  add_column :users, :reset_password_token_created_at, :datetime
37
37
 
38
- # Remember
39
- add_column :users, :remember_token, :string
40
- add_column :users, :remember_token_created_at, :datetime
41
-
42
38
  # Confirmation
43
39
  add_column :users, :confirmation_email, :string
44
40
  add_column :users, :confirmation_token, :string
@@ -56,7 +52,6 @@ class AddAuthkitFieldsToUsers < ActiveRecord::Migration
56
52
  add_index :users, :email, :unique => true
57
53
  <% if username? %>add_index :users, :username, :unique => true
58
54
  <% end %>add_index :users, :reset_password_token, :unique => true
59
- add_index :users, :remember_token, :unique => true
60
55
  add_index :users, :confirmation_token, :unique => true
61
56
  add_index :users, :unlock_token, :unique => true
62
57
 
@@ -93,10 +88,6 @@ class AddAuthkitFieldsToUsers < ActiveRecord::Migration
93
88
  drop_column :users, :reset_password_token
94
89
  drop_column :users, :reset_password_created_at
95
90
 
96
- # Remember
97
- drop_column :users, :remember_token
98
- drop_column :users, :remember_token_created_at
99
-
100
91
  # Confirmation
101
92
  drop_column :users, :confirmation_email
102
93
  drop_column :users, :confirmation_token
@@ -1,7 +1,7 @@
1
1
  # Generated by Authkit.
2
2
  #
3
3
  # Create an auths table for managing OAuth authentication.
4
- class CreateAuths < ActiveRecord::Migration
4
+ class CreateAuths < ActiveRecord::Migration[5.0]
5
5
  def self.up
6
6
  create_table :auths do |t|
7
7
  t.integer :user_id
@@ -14,8 +14,12 @@ class CreateAuths < ActiveRecord::Migration
14
14
  t.string :refresh_token
15
15
  t.string :secret_token
16
16
  t.text :env
17
- t.timestamps
17
+
18
+ t.timestamps null: false
18
19
  end
20
+
21
+ add_index :auths, :user_id
22
+ add_index :auths, [:provider, :uid]
19
23
  end
20
24
 
21
25
  def self.down
@@ -2,7 +2,7 @@
2
2
  #
3
3
  # Create an avatars table for managing user profile images
4
4
  #
5
- class CreateAvatars < ActiveRecord::Migration
5
+ class CreateAvatars < ActiveRecord::Migration[5.0]
6
6
  def self.up
7
7
  create_table :avatars do |t|
8
8
  t.integer :user_id
@@ -17,7 +17,8 @@ class CreateAvatars < ActiveRecord::Migration
17
17
  t.datetime :attachment_updated_at
18
18
  t.boolean :attachment_importing, default: false
19
19
  t.datetime :attachment_uploaded_at
20
- t.timestamps
20
+
21
+ t.timestamps null: false
21
22
  end
22
23
  end
23
24
 
@@ -0,0 +1,27 @@
1
+ # Generated by Authkit.
2
+ #
3
+ # Create an user_sessions table for managing user logins.
4
+ class CreateUserSessions < ActiveRecord::Migration[5.0]
5
+ def self.up
6
+ create_table :user_sessions do |t|
7
+ t.integer :user_id
8
+ t.datetime :accessed_at
9
+ t.datetime :revoked_at
10
+ t.datetime :logged_out_at
11
+ t.datetime :sudo_enabled_at
12
+ t.string :ip
13
+ t.string :user_agent
14
+ t.string :remember_token
15
+
16
+ t.timestamps null: false
17
+ end
18
+
19
+ add_index :user_sessions, :remember_token, :unique => true
20
+ add_index :user_sessions, [:accessed_at, :revoked_at, :logged_out_at], :name => 'index_user_sessions_active'
21
+ add_index :user_sessions, :user_id
22
+ end
23
+
24
+ def self.down
25
+ drop_table :user_sessions
26
+ end
27
+ end
@@ -4,10 +4,10 @@
4
4
  # the authentication are created in the AddAuthkitFieldsToUsers
5
5
  # migration.
6
6
  #
7
- class CreateUsers < ActiveRecord::Migration
7
+ class CreateUsers < ActiveRecord::Migration[5.0]
8
8
  def self.up
9
9
  create_table :users do |t|
10
- t.timestamps
10
+ t.timestamps null: false
11
11
  end
12
12
  end
13
13
 
@@ -1,16 +1,17 @@
1
1
  require 'rails_helper'
2
2
 
3
- describe ApplicationController do
4
- let(:user) { create(:user) }
5
- let(:logged_in_session) { { user_id: user.id } }
6
- let(:unknown_session) { { user_id: user.id + 1000000 } }
3
+ RSpec.describe ApplicationController do
4
+ let(:user_session) { create(:user_session) }
5
+ let(:user) { user_session.user }
6
+ let(:logged_in_session) { { user_session_id: user_session.id } }
7
+ let(:unknown_session) { { user_session_id: user_session.id + 1000000 } }
7
8
 
8
9
  before(:each) do
9
10
  user
10
11
  end
11
12
 
12
13
  controller do
13
- before_filter :require_login, only: [:index]
14
+ before_action :require_login, only: [:index]
14
15
 
15
16
  def new
16
17
  head :ok
@@ -30,46 +31,33 @@ describe ApplicationController do
30
31
  get :new
31
32
  expect(controller.send(:current_user)).to be_nil
32
33
  end
34
+ end
33
35
 
34
- it "does not perform multiple finds" do
35
- where = double(first: nil)
36
- expect(User).to receive(:where).and_return(where)
37
- get :new, {}, unknown_session
38
- expect(controller.send(:current_user)).to be_nil
36
+ describe "current_user_session" do
37
+ it "returns nil if there is no current user session" do
38
+ get :new
39
+ expect(controller.send(:current_user_session)).to be_nil
39
40
  end
40
41
 
41
- it "finds the current user in the session" do
42
- get :new, {}, logged_in_session
43
- expect(controller.send(:current_user)).to eq(user)
42
+ it "finds the current user session in the session" do
43
+ get :new, session: logged_in_session
44
+ expect(controller.send(:current_user_session)).to eq(user_session)
44
45
  end
45
46
 
46
47
  it "finds the current user from the remember cookie" do
47
- user.save
48
- user.set_remember_token
49
- cookies.signed[:remember] = user.remember_token
50
- get :index
51
- expect(controller.send(:current_user)).to eq(user)
52
- end
53
-
54
- it "doesn't find the current user from the remember cookie if it is expired" do
55
- # Setup expired token
56
- user.set_remember_token
57
- user.remember_token_created_at = 1.year.ago
58
- user.save
59
-
60
- cookies.signed[:remember] = user.remember_token
48
+ cookies.signed[:remember] = user_session.remember_token
61
49
  get :index
62
- expect(controller.send(:current_user)).to be_nil
50
+ expect(controller.send(:current_user_session)).to eq(user_session)
63
51
  end
64
52
 
65
53
  it "sets the time zone" do
66
54
  expect_any_instance_of(User).to receive(:time_zone).and_return("Pacific Time (US & Canada)")
67
- get :index, {}, logged_in_session
55
+ get :index, session: logged_in_session
68
56
  expect(Time.zone.name).to eq("Pacific Time (US & Canada)")
69
57
  end
70
58
 
71
59
  it "has a logged in helper method" do
72
- get :new, {}, logged_in_session
60
+ get :new, session: logged_in_session
73
61
  expect(controller.send(:logged_in?)).to eq(true)
74
62
  end
75
63
  end
@@ -89,30 +77,30 @@ describe ApplicationController do
89
77
 
90
78
  describe "when requiring a user" do
91
79
  it "allows access if there is a user" do
92
- get :index, {}, logged_in_session
80
+ get :index, session: logged_in_session
93
81
  expect(response).to be_success
94
82
  end
95
83
 
96
84
  it "stores the return path" do
97
- get :index, {}
85
+ get :index
98
86
  expect(session[:return_url]).to eq("/anonymous")
99
87
  end
100
88
 
101
89
  describe "when responding to html" do
102
90
  it "sets the flash message" do
103
- get :index, {}
91
+ get :index
104
92
  expect(flash).to_not be_empty
105
93
  end
106
94
 
107
95
  it "redirecs the user to login" do
108
- get :index, {}
96
+ get :index
109
97
  expect(response).to be_redirect
110
98
  end
111
99
  end
112
100
 
113
101
  describe "when responding to json" do
114
102
  it "returns a forbidden status" do
115
- get :index, {format: :json}
103
+ get :index, params: { format: :json }
116
104
  expect(response.code).to eq("403")
117
105
  end
118
106
  end
@@ -125,17 +113,22 @@ describe ApplicationController do
125
113
  controller.send(:login, user)
126
114
  end
127
115
 
116
+ it "creates a session" do
117
+ expect {
118
+ get :new
119
+ controller.send(:login, user, true)
120
+ }.to change(UserSession, :count).by(1)
121
+ end
122
+
128
123
  it "remembers the user using a token and cookie" do
129
124
  get :new
130
125
  expect(controller).to receive(:set_remember_cookie)
131
- expect(user).to receive(:set_remember_token)
132
126
  controller.send(:login, user, true)
133
127
  end
134
128
 
135
129
  it "does not remember the user using a token and cookie when not requested" do
136
130
  get :new
137
131
  expect(controller).to_not receive(:set_remember_cookie)
138
- expect(user).to_not receive(:set_remember_token)
139
132
  controller.send(:login, user, false)
140
133
  end
141
134
 
@@ -148,33 +141,33 @@ describe ApplicationController do
148
141
 
149
142
  describe "logout" do
150
143
  it "resets the session" do
151
- get :index, {}, logged_in_session
144
+ get :index, session: logged_in_session
152
145
  expect(controller).to receive(:reset_session)
153
146
  controller.send(:logout)
154
147
  end
155
148
 
156
- it "logs the user out" do
157
- get :index, {}, logged_in_session
149
+ it "marks the user session as logged out" do
150
+ get :index, session: logged_in_session
158
151
  controller.send(:logout)
159
- expect(controller.send(:current_user)).to be_nil
152
+ expect(user_session.reload).to be_logged_out
160
153
  end
161
154
 
162
- it "clears the remember token" do
163
- get :index, {}, logged_in_session
164
- expect_any_instance_of(User).to receive(:clear_remember_token).and_return(:true)
155
+ it "logs the user out" do
156
+ get :index, session: logged_in_session
165
157
  controller.send(:logout)
158
+ expect(controller.send(:current_user)).to be_nil
166
159
  end
167
160
  end
168
161
 
169
162
  it "sets the remember cookie" do
170
163
  request.env["action_dispatch.secret_token"] = "SECRET"
171
164
  get :new
172
- controller.send(:login, user)
173
- expect(cookies.permanent.signed[:remember]).to eq(user.remember_token)
165
+ new_session = controller.send(:login, user, true)
166
+ expect(controller.send(:cookies).permanent.signed[:remember]).to eq(new_session.remember_token)
174
167
  end
175
168
 
176
169
  it "redirects to a stored session location if present" do
177
- get :new, {}, {return_url: "/return"}
170
+ get :new, session: { return_url: "/return" }
178
171
  expect(controller).to receive(:redirect_to).with("/return").and_return(true)
179
172
  controller.send(:redirect_back_or_default)
180
173
  end
@@ -1,6 +1,6 @@
1
1
  require 'rails_helper'
2
2
 
3
- describe EmailConfirmationController do
3
+ RSpec.describe EmailConfirmationController do
4
4
  render_views
5
5
 
6
6
  let(:user) { build(:user) }
@@ -9,7 +9,7 @@ describe EmailConfirmationController do
9
9
  describe "GET 'show'" do
10
10
  it "requires a login" do
11
11
  allow(controller).to receive(:current_user).and_return(nil)
12
- get 'show', token: token
12
+ get 'show', params: { token: token }
13
13
  expect(response).to be_redirect
14
14
  expect(flash[:error]).to_not be_empty
15
15
  end
@@ -17,7 +17,7 @@ describe EmailConfirmationController do
17
17
  it "requires a valid token" do
18
18
  user.confirmation_token = "OTHER TOKEN"
19
19
  allow(controller).to receive(:current_user).and_return(user)
20
- get 'show', token: token
20
+ get 'show', params: { token: token }
21
21
  expect(response).to be_redirect
22
22
  expect(flash[:error]).to_not be_empty
23
23
  end
@@ -26,7 +26,7 @@ describe EmailConfirmationController do
26
26
  user.confirmation_token = token
27
27
  user.confirmation_token_created_at = 4.days.ago
28
28
  allow(controller).to receive(:current_user).and_return(user)
29
- get 'show', token: token
29
+ get 'show', params: { token: token }
30
30
  expect(response).to be_redirect
31
31
  expect(flash[:error]).to_not be_empty
32
32
  end
@@ -42,27 +42,27 @@ describe EmailConfirmationController do
42
42
  describe "when the confirmation is successful" do
43
43
  it "confirms the user email" do
44
44
  expect(user).to receive(:email_confirmed).and_return(true)
45
- get 'show', token: token
45
+ get 'show', params: { token: token }
46
46
  end
47
47
 
48
48
  it "does not sign the user in" do
49
49
  expect(controller).to_not receive(:login)
50
- get 'show', token: token
50
+ get 'show', params: { token: token }
51
51
  end
52
52
 
53
53
  it "sets the flash" do
54
- get 'show', token: token
54
+ get 'show', params: { token: token }
55
55
  expect(flash[:notice]).to_not be_nil
56
56
  end
57
57
 
58
58
  it "redirects the user" do
59
- get 'show', token: token
59
+ get 'show', params: { token: token }
60
60
  expect(response).to be_redirect
61
61
  end
62
62
 
63
63
  describe "from json" do
64
64
  it "returns http success" do
65
- get 'show', token: token, format: 'json'
65
+ get 'show', params: { token: token, format: 'json' }
66
66
  expect(response).to be_success
67
67
  end
68
68
  end
@@ -76,7 +76,7 @@ describe EmailConfirmationController do
76
76
 
77
77
  it "handles invalid confirmations" do
78
78
  expect(user).to receive(:email_confirmed).and_return(false)
79
- get 'show', token: token
79
+ get 'show', params: { token: token }
80
80
  expect(flash[:error]).to_not be_empty
81
81
  expect(response).to be_redirect
82
82
  end
@@ -84,7 +84,7 @@ describe EmailConfirmationController do
84
84
  describe "from json" do
85
85
  it "returns a 422" do
86
86
  expect(user).to receive(:email_confirmed).and_return(false)
87
- get 'show', token: token, format: 'json'
87
+ get 'show', params: { token: token, format: 'json' }
88
88
  expect(response.code).to eq('422')
89
89
  end
90
90
  end