authkit 0.0.1

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 +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
+