authkit 0.0.1

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 +7 -0
  2. data/.gitignore +17 -0
  3. data/FEATURES.md +73 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +168 -0
  7. data/Rakefile +60 -0
  8. data/authkit.gemspec +27 -0
  9. data/config/database.yml.example +19 -0
  10. data/lib/authkit.rb +5 -0
  11. data/lib/authkit/engine.rb +7 -0
  12. data/lib/authkit/version.rb +3 -0
  13. data/lib/generators/authkit/USAGE +18 -0
  14. data/lib/generators/authkit/install_generator.rb +113 -0
  15. data/lib/generators/authkit/templates/app/controllers/application_controller.rb +94 -0
  16. data/lib/generators/authkit/templates/app/controllers/email_confirmation_controller.rb +25 -0
  17. data/lib/generators/authkit/templates/app/controllers/password_change_controller.rb +29 -0
  18. data/lib/generators/authkit/templates/app/controllers/password_reset_controller.rb +29 -0
  19. data/lib/generators/authkit/templates/app/controllers/sessions_controller.rb +35 -0
  20. data/lib/generators/authkit/templates/app/controllers/users_controller.rb +89 -0
  21. data/lib/generators/authkit/templates/app/models/user.rb +170 -0
  22. data/lib/generators/authkit/templates/app/views/password_change/show.html.erb +16 -0
  23. data/lib/generators/authkit/templates/app/views/password_reset/show.html.erb +12 -0
  24. data/lib/generators/authkit/templates/app/views/sessions/new.html.erb +13 -0
  25. data/lib/generators/authkit/templates/app/views/users/edit.html.erb +58 -0
  26. data/lib/generators/authkit/templates/app/views/users/new.html.erb +58 -0
  27. data/lib/generators/authkit/templates/db/migrate/add_authkit_fields_to_users.rb +110 -0
  28. data/lib/generators/authkit/templates/db/migrate/create_users.rb +17 -0
  29. data/lib/generators/authkit/templates/lib/email_format_validator.rb +11 -0
  30. data/lib/generators/authkit/templates/spec/controllers/application_controller_spec.rb +188 -0
  31. data/lib/generators/authkit/templates/spec/controllers/email_confirmation_controller_spec.rb +80 -0
  32. data/lib/generators/authkit/templates/spec/controllers/password_change_controller_spec.rb +98 -0
  33. data/lib/generators/authkit/templates/spec/controllers/password_reset_controller_spec.rb +87 -0
  34. data/lib/generators/authkit/templates/spec/controllers/sessions_controller_spec.rb +111 -0
  35. data/lib/generators/authkit/templates/spec/controllers/users_controller_spec.rb +195 -0
  36. data/lib/generators/authkit/templates/spec/models/user_spec.rb +268 -0
  37. data/spec/spec_helper.rb +16 -0
  38. metadata +165 -0
@@ -0,0 +1,16 @@
1
+ <h1>Please enter a new password</h1>
2
+
3
+ <%= form_tag(password_change_path, method: "post") do %>
4
+ <%= hidden_field_tag :token, params[:token] %>
5
+
6
+ <div class="field">
7
+ <%= label_tag "password" %>
8
+ <%= password_field_tag "password" %>
9
+ </div>
10
+ <div class="field">
11
+ <%= label_tag "password_confirmation" %>
12
+ <%= password_field_tag "password_confirmation" %>
13
+ </div>
14
+
15
+ <%= submit_tag "Change" %>
16
+ <% end %>
@@ -0,0 +1,12 @@
1
+ <h1>Reset your password</h1>
2
+
3
+ <%= form_tag(password_reset_path, method: "post") do %>
4
+ <%= hidden_field_tag :token, params[:token] %>
5
+
6
+ <div class="field">
7
+ <%= label_tag "email", "Email or username" %>
8
+ <%= text_field_tag "email" %>
9
+ </div>
10
+
11
+ <%= submit_tag "Reset" %>
12
+ <% end %>
@@ -0,0 +1,13 @@
1
+ <h1>Sign In</h1>
2
+
3
+ <%= form_tag(login_path, method: "post") do %>
4
+ <div class="field">
5
+ <%= label_tag "email", "Email or username" %>
6
+ <%= text_field_tag "email" %>
7
+ </div>
8
+ <div class="field">
9
+ <%= label_tag "password" %>
10
+ <%= password_field_tag "password" %>
11
+ </div>
12
+ <%= submit_tag "Login" %>
13
+ <% end %>
@@ -0,0 +1,58 @@
1
+ <h1>Account</h1>
2
+
3
+ <% if @user.errors.any? %>
4
+ <div id="error_explanation">
5
+ <div class="alert alert-error">
6
+ The form contains <%= pluralize(@user.errors.count, "error") %>.
7
+ </div>
8
+ <ul>
9
+ <% @user.errors.full_messages.each do |msg| %>
10
+ <li>* <%= msg %></li>
11
+ <% end %>
12
+ </ul>
13
+ </div>
14
+ <% end %>
15
+
16
+ <%= form_for @user do |f| %>
17
+ <div class="field">
18
+ <%= f.label "first_name" %>
19
+ <%= f.text_field "first_name" %>
20
+ </div>
21
+ <div class="field">
22
+ <%= f.label "last_name" %>
23
+ <%= f.text_field "last_name" %>
24
+ </div>
25
+ <div class="field">
26
+ <%= f.label "bio" %>
27
+ <%= f.text_area "bio" %>
28
+ </div>
29
+ <div class="field">
30
+ <%= f.label "website" %>
31
+ <%= f.text_field "website" %>
32
+ </div>
33
+ <div class="field">
34
+ <%= f.label "phone_number" %>
35
+ <%= f.text_field "phone_number" %>
36
+ </div>
37
+ <div class="field">
38
+ <%= f.label "phone_number" %>
39
+ <%= f.time_zone_select('time_zone', ActiveSupport::TimeZone.us_zones, :default => "Pacific Time (US & Canada)") %>
40
+ </div>
41
+ <div class="field">
42
+ <%= f.label "email" %>
43
+ <%= f.text_field "confirmation_email" %>
44
+ </div>
45
+ <div class="field">
46
+ <%= f.label "username" %>
47
+ <%= f.text_field "username" %>
48
+ </div>
49
+ <div class="field">
50
+ <%= f.label "password" %>
51
+ <%= f.password_field "password" %>
52
+ </div>
53
+ <div class="field">
54
+ <%= f.label "password_confirmation" %>
55
+ <%= f.password_field "password_confirmation" %>
56
+ </div>
57
+ <%= f.submit "Save" %>
58
+ <% end %>
@@ -0,0 +1,58 @@
1
+ <h1>Sign Up</h1>
2
+
3
+ <% if @user.errors.any? %>
4
+ <div id="error_explanation">
5
+ <div class="alert alert-error">
6
+ The form contains <%= pluralize(@user.errors.count, "error") %>.
7
+ </div>
8
+ <ul>
9
+ <% @user.errors.full_messages.each do |msg| %>
10
+ <li>* <%= msg %></li>
11
+ <% end %>
12
+ </ul>
13
+ </div>
14
+ <% end %>
15
+
16
+ <%= form_for @user do |f| %>
17
+ <div class="field">
18
+ <%= f.label "first_name" %>
19
+ <%= f.text_field "first_name" %>
20
+ </div>
21
+ <div class="field">
22
+ <%= f.label "last_name" %>
23
+ <%= f.text_field "last_name" %>
24
+ </div>
25
+ <div class="field">
26
+ <%= f.label "bio" %>
27
+ <%= f.text_area "bio" %>
28
+ </div>
29
+ <div class="field">
30
+ <%= f.label "website" %>
31
+ <%= f.text_field "website" %>
32
+ </div>
33
+ <div class="field">
34
+ <%= f.label "phone_number" %>
35
+ <%= f.text_field "phone_number" %>
36
+ </div>
37
+ <div class="field">
38
+ <%= f.label "phone_number" %>
39
+ <%= f.time_zone_select('time_zone', ActiveSupport::TimeZone.us_zones, :default => "Pacific Time (US & Canada)") %>
40
+ </div>
41
+ <div class="field">
42
+ <%= f.label "email" %>
43
+ <%= f.text_field "email" %>
44
+ </div>
45
+ <div class="field">
46
+ <%= f.label "username" %>
47
+ <%= f.text_field "username" %>
48
+ </div>
49
+ <div class="field">
50
+ <%= f.label "password" %>
51
+ <%= f.text_field "password" %>
52
+ </div>
53
+ <div class="field">
54
+ <%= f.label "password_confirmation" %>
55
+ <%= f.text_field "password_confirmation" %>
56
+ </div>
57
+ <%= f.submit "Sign up" %>
58
+ <% end %>
@@ -0,0 +1,110 @@
1
+ # Generated by Authkit.
2
+ #
3
+ # Add fields to the users table for managing authentication.
4
+ #
5
+ class AddAuthkitFieldsToUsers < ActiveRecord::Migration
6
+ def self.up
7
+ add_column :users, :email, :string, :default => "", :null => false
8
+ add_column :users, :password_digest, :string, :default => "", :null => false
9
+ add_column :users, :username, :string, :limit => 64
10
+
11
+ add_column :users, :time_zone, :string, :default => "Eastern Time (US & Canada)"
12
+ add_column :users, :first_name, :string
13
+ add_column :users, :last_name, :string
14
+ add_column :users, :bio, :text
15
+ add_column :users, :website, :string
16
+ add_column :users, :phone_number, :string
17
+
18
+ # One time password key for two-factor auth
19
+ add_column :users, :otp_secret_key, :string
20
+
21
+ # Tracking
22
+ add_column :users, :sign_in_count, :integer, :default => 0
23
+ add_column :users, :current_sign_in_at, :datetime
24
+ add_column :users, :last_sign_in_at, :datetime
25
+ add_column :users, :current_sign_in_ip, :string
26
+ add_column :users, :last_sign_in_ip, :string
27
+
28
+ # Analytics
29
+ add_column :users, :original_source, :string
30
+ add_column :users, :session_source, :string
31
+ add_column :users, :first_visit_at, :datetime
32
+ add_column :users, :last_visit_at, :datetime
33
+
34
+ # Forgot password / Password reset
35
+ add_column :users, :reset_password_token, :string
36
+ add_column :users, :reset_password_token_created_at, :datetime
37
+
38
+ # Remember
39
+ add_column :users, :remember_token, :string
40
+ add_column :users, :remember_token_created_at, :datetime
41
+
42
+ # Confirmation
43
+ add_column :users, :confirmation_email, :string
44
+ add_column :users, :confirmation_token, :string
45
+ add_column :users, :confirmation_token_created_at, :string
46
+
47
+ # Lockout
48
+ add_column :users, :failed_attempts, :integer, :default => 0
49
+ add_column :users, :locked_at, :datetime
50
+ add_column :users, :unlock_token, :string
51
+ add_column :users, :unlock_token_created_at, :datetime
52
+
53
+ # Make sure the validations are enforced
54
+ add_index :users, :email, :unique => true
55
+ add_index :users, :username, :unique => true
56
+ add_index :users, :reset_password_token, :unique => true
57
+ add_index :users, :remember_token, :unique => true
58
+ add_index :users, :confirmation_token, :unique => true
59
+ add_index :users, :unlock_token, :unique => true
60
+
61
+ end
62
+
63
+ def self.down
64
+ drop_column :users, :email
65
+ drop_column :users, :password_digest
66
+ drop_column :users, :username
67
+
68
+ drop_column :users, :time_zone
69
+ drop_column :users, :first_name
70
+ drop_column :users, :last_name
71
+ drop_column :users, :bio
72
+ drop_column :users, :website
73
+ drop_column :users, :phone_number
74
+
75
+ drop_column :users, :otp_secret_key
76
+
77
+ # Tracking
78
+ drop_column :users, :sign_in_count
79
+ drop_column :users, :current_sign_in_at
80
+ drop_column :users, :last_sign_in_at
81
+ drop_column :users, :current_sign_in_ip
82
+ drop_column :users, :last_sign_in_ip
83
+
84
+ # Analytics
85
+ drop_column :users, :original_source
86
+ drop_column :users, :session_source
87
+ drop_column :users, :first_visit_at
88
+ drop_column :users, :last_visit_at
89
+
90
+ # Forgot password / Password reset
91
+ drop_column :users, :reset_password_token
92
+ drop_column :users, :reset_password_created_at
93
+
94
+ # Remember
95
+ drop_column :users, :remember_token
96
+ drop_column :users, :remember_token_created_at
97
+
98
+ # Confirmation
99
+ drop_column :users, :confirmation_email
100
+ drop_column :users, :confirmation_token
101
+ drop_column :users, :confirmation_token_created_at
102
+
103
+ # Lockout
104
+ drop_column :users, :failed_attempts
105
+ drop_column :users, :locked_at
106
+ drop_column :users, :unlock_token
107
+ drop_column :users, :unlock_token_created_at
108
+ end
109
+ end
110
+
@@ -0,0 +1,17 @@
1
+ # Generated by Authkit.
2
+ #
3
+ # Create a users table for managing authentication. Fields to handle
4
+ # the authentication are created in the AddAuthkitFieldsToUsers
5
+ # migration.
6
+ #
7
+ class CreateUsers < ActiveRecord::Migration
8
+ def self.up
9
+ create_table :users do |t|
10
+ t.timestamps
11
+ end
12
+ end
13
+
14
+ def self.down
15
+ drop_table :users
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ class EmailFormatValidator < ActiveModel::EachValidator
2
+ def validate_each(object, attribute, value)
3
+ unless EmailFormatValidator::is_valid_email(value)
4
+ object.errors[attribute] << (options[:message] || "is not a valid email address")
5
+ end
6
+ end
7
+
8
+ def self.is_valid_email(value)
9
+ value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
10
+ end
11
+ end
@@ -0,0 +1,188 @@
1
+ require 'spec_helper'
2
+
3
+ describe ApplicationController do
4
+ let(:user_params) { { email: "test@example.com", username: "test", password: "example", password_confirmation: "example" } }
5
+ let(:user) { User.new(user_params) }
6
+ let(:logged_in_session) { { user_id: "1" } }
7
+ let(:unknown_session) { { user_id: "2" } }
8
+
9
+ before(:each) do
10
+ User.stub(:find_by).with("1").and_return(user)
11
+ end
12
+
13
+ controller do
14
+ before_filter :require_login, only: [:index]
15
+ before_filter :require_token, only: [:show]
16
+
17
+ def new
18
+ head :ok
19
+ end
20
+
21
+ def index
22
+ head :ok
23
+ end
24
+
25
+ def show
26
+ head :ok
27
+ end
28
+ end
29
+
30
+ describe "current_user" do
31
+ it "returns nil if there is no current user" do
32
+ get :new
33
+ controller.send(:current_user).should be_nil
34
+ end
35
+
36
+ it "does not perform multiple finds" do
37
+ User.should_receive(:find_by)
38
+ get :new, {}, unknown_session
39
+ controller.send(:current_user).should be_nil
40
+ end
41
+
42
+ it "finds the current user in the session" do
43
+ get :new, {}, logged_in_session
44
+ controller.send(:current_user).should == user
45
+ end
46
+
47
+ it "finds the current user from the remember cookie" do
48
+ # Need to sign the cookie
49
+ request.env["action_dispatch.secret_token"] = "SECRET"
50
+ verifier = ActiveSupport::MessageVerifier.new(request.env["action_dispatch.secret_token".freeze])
51
+ request.cookies[:remember] = verifier.generate("TOKEN")
52
+ User.should_receive(:user_from_remember_token).with("TOKEN").and_return(user)
53
+ get :index
54
+ controller.send(:current_user).should == user
55
+ end
56
+
57
+ it "sets the time zone" do
58
+ user.should_receive(:time_zone).and_return("Pacific Time (US & Canada)")
59
+ get :index, {}, logged_in_session
60
+ Time.zone.name.should == "Pacific Time (US & Canada)"
61
+ end
62
+
63
+ it "has a logged in helper method" do
64
+ get :new, {}, logged_in_session
65
+ controller.should be_logged_in
66
+ end
67
+ end
68
+
69
+ describe "tracking" do
70
+ it "does not allow tracking if there is a do not track header" do
71
+ request.headers["DNT"] = "1"
72
+ get :new
73
+ controller.send(:allow_tracking?).should == false
74
+ end
75
+
76
+ it "allows tracking if there is no do not track header" do
77
+ get :new
78
+ controller.send(:allow_tracking?).should == true
79
+ end
80
+ end
81
+
82
+ describe "when requiring a user" do
83
+ it "allows access if there is a user" do
84
+ get :index, {}, logged_in_session
85
+ response.should be_success
86
+ end
87
+
88
+ it "stores the return path" do
89
+ get :index, {}
90
+ session[:return_url].should == "/anonymous"
91
+ end
92
+
93
+ describe "when responding to html" do
94
+ it "sets the flash message" do
95
+ get :index, {}
96
+ flash.should_not be_empty
97
+ end
98
+
99
+ it "redirecs the user to login" do
100
+ get :index, {}
101
+ response.should be_redirect
102
+ end
103
+ end
104
+
105
+ describe "when responding to json" do
106
+ it "returns a forbidden status" do
107
+ get :index, {format: :json}
108
+ response.code.should == "403"
109
+ end
110
+ end
111
+ end
112
+
113
+ describe "tokens" do
114
+ it "requires a user token" do
115
+ User.should_receive(:user_from_token).with('testtoken').and_return(user)
116
+ get 'show', {id: '1', token: 'testtoken'}
117
+ end
118
+
119
+ it "returns an error if there is no user token" do
120
+ User.should_receive(:user_from_token).with('testtoken').and_return(nil)
121
+ controller.should_receive(:deny_user)
122
+ get 'show', {id: '1', token: 'testtoken'}
123
+ end
124
+
125
+ it "verifies the token" do
126
+ request.env["action_dispatch.secret_token"] = "SECRET"
127
+ verifier = ActiveSupport::MessageVerifier.new(request.env["action_dispatch.secret_token".freeze])
128
+ token = verifier.generate("TOKEN")
129
+ User.should_receive(:user_from_token).with(token).and_return(user)
130
+ get 'show', {id: '1', token: token}
131
+ end
132
+ end
133
+
134
+ describe "login" do
135
+ it "tracks the login" do
136
+ get :new
137
+ user.should_receive(:track_sign_in)
138
+ controller.send(:login, user)
139
+ end
140
+
141
+ it "remembers the user using a token and cookie" do
142
+ get :new
143
+ controller.should_receive(:set_remember_cookie)
144
+ user.should_receive(:set_token).with(:remember_token).and_return(:true)
145
+ controller.send(:login, user)
146
+ end
147
+
148
+ it "resets the session" do
149
+ get :new
150
+ controller.should_receive(:reset_session)
151
+ controller.send(:login, user)
152
+ end
153
+ end
154
+
155
+ describe "logout" do
156
+ it "resets the session" do
157
+ get :index, {}, logged_in_session
158
+ controller.should_receive(:reset_session)
159
+ controller.send(:logout)
160
+ end
161
+
162
+ it "logs the user out" do
163
+ get :index, {}, logged_in_session
164
+ controller.send(:logout)
165
+ controller.send(:current_user).should be_nil
166
+ end
167
+
168
+ it "clears the remember token" do
169
+ get :index, {}, logged_in_session
170
+ user.should_receive(:clear_remember_token).and_return(:true)
171
+ controller.send(:logout)
172
+ end
173
+ end
174
+
175
+ it "sets the remember cookie" do
176
+ request.env["action_dispatch.secret_token"] = "SECRET"
177
+ get :new
178
+ controller.send(:login, user)
179
+ cookies.permanent.signed[:remember].should == user.remember_token
180
+ end
181
+
182
+ it "redirects to a stored session location if present" do
183
+ get :new, {}, {return_url: "/return"}
184
+ controller.should_receive(:redirect_to).with("/return").and_return(true)
185
+ controller.send(:redirect_back_or_default)
186
+ end
187
+ end
188
+