authkit 0.5.0 → 0.7.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 (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