authkit 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/FEATURES.md +73 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +168 -0
- data/Rakefile +60 -0
- data/authkit.gemspec +27 -0
- data/config/database.yml.example +19 -0
- data/lib/authkit.rb +5 -0
- data/lib/authkit/engine.rb +7 -0
- data/lib/authkit/version.rb +3 -0
- data/lib/generators/authkit/USAGE +18 -0
- data/lib/generators/authkit/install_generator.rb +113 -0
- data/lib/generators/authkit/templates/app/controllers/application_controller.rb +94 -0
- data/lib/generators/authkit/templates/app/controllers/email_confirmation_controller.rb +25 -0
- data/lib/generators/authkit/templates/app/controllers/password_change_controller.rb +29 -0
- data/lib/generators/authkit/templates/app/controllers/password_reset_controller.rb +29 -0
- data/lib/generators/authkit/templates/app/controllers/sessions_controller.rb +35 -0
- data/lib/generators/authkit/templates/app/controllers/users_controller.rb +89 -0
- data/lib/generators/authkit/templates/app/models/user.rb +170 -0
- data/lib/generators/authkit/templates/app/views/password_change/show.html.erb +16 -0
- data/lib/generators/authkit/templates/app/views/password_reset/show.html.erb +12 -0
- data/lib/generators/authkit/templates/app/views/sessions/new.html.erb +13 -0
- data/lib/generators/authkit/templates/app/views/users/edit.html.erb +58 -0
- data/lib/generators/authkit/templates/app/views/users/new.html.erb +58 -0
- data/lib/generators/authkit/templates/db/migrate/add_authkit_fields_to_users.rb +110 -0
- data/lib/generators/authkit/templates/db/migrate/create_users.rb +17 -0
- data/lib/generators/authkit/templates/lib/email_format_validator.rb +11 -0
- data/lib/generators/authkit/templates/spec/controllers/application_controller_spec.rb +188 -0
- data/lib/generators/authkit/templates/spec/controllers/email_confirmation_controller_spec.rb +80 -0
- data/lib/generators/authkit/templates/spec/controllers/password_change_controller_spec.rb +98 -0
- data/lib/generators/authkit/templates/spec/controllers/password_reset_controller_spec.rb +87 -0
- data/lib/generators/authkit/templates/spec/controllers/sessions_controller_spec.rb +111 -0
- data/lib/generators/authkit/templates/spec/controllers/users_controller_spec.rb +195 -0
- data/lib/generators/authkit/templates/spec/models/user_spec.rb +268 -0
- data/spec/spec_helper.rb +16 -0
- 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
|
+
|