authpwn_rails 0.12.0 → 0.12.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.
- data/.travis.yml +7 -2
- data/VERSION +1 -1
- data/app/models/credentials/password.rb +16 -8
- data/app/models/credentials/token.rb +8 -0
- data/app/models/tokens/email_verification.rb +3 -0
- data/app/models/tokens/password_reset.rb +5 -2
- data/app/models/tokens/session_uid.rb +54 -0
- data/authpwn_rails.gemspec +8 -2
- data/lib/authpwn_rails.rb +3 -2
- data/lib/authpwn_rails/current_user.rb +1 -10
- data/lib/authpwn_rails/engine.rb +2 -2
- data/lib/authpwn_rails/expires.rb +23 -0
- data/lib/authpwn_rails/generators/all_generator.rb +9 -4
- data/lib/authpwn_rails/generators/templates/credential.rb +1 -1
- data/lib/authpwn_rails/generators/templates/credentials.yml +16 -0
- data/lib/authpwn_rails/generators/templates/initializer.rb +18 -0
- data/lib/authpwn_rails/generators/templates/session/forbidden.html.erb +1 -1
- data/lib/authpwn_rails/generators/templates/session/home.html.erb +1 -1
- data/lib/authpwn_rails/generators/templates/session/new.html.erb +3 -3
- data/lib/authpwn_rails/generators/templates/session/welcome.html.erb +1 -1
- data/lib/authpwn_rails/generators/templates/session_controller.rb +13 -4
- data/lib/authpwn_rails/generators/templates/session_controller_test.rb +12 -2
- data/lib/authpwn_rails/generators/templates/session_mailer.rb +3 -3
- data/lib/authpwn_rails/generators/templates/session_mailer/email_verification_email.html.erb +3 -3
- data/lib/authpwn_rails/generators/templates/session_mailer/reset_password_email.html.erb +3 -3
- data/lib/authpwn_rails/generators/templates/session_mailer_test.rb +4 -4
- data/lib/authpwn_rails/routes.rb +4 -4
- data/lib/authpwn_rails/session.rb +31 -8
- data/lib/authpwn_rails/session_controller.rb +27 -18
- data/lib/authpwn_rails/test_extensions.rb +16 -6
- data/lib/authpwn_rails/user_model.rb +10 -10
- data/test/cookie_controller_test.rb +165 -16
- data/test/credentials/email_verification_token_test.rb +11 -11
- data/test/credentials/password_credential_test.rb +31 -12
- data/test/credentials/session_uid_token_test.rb +98 -0
- data/test/credentials/token_crendential_test.rb +46 -12
- data/test/helpers/db_setup.rb +6 -5
- data/test/helpers/routes.rb +5 -2
- data/test/initializer_test.rb +18 -0
- data/test/session_controller_api_test.rb +127 -53
- data/test/test_extensions_test.rb +41 -0
- data/test/test_helper.rb +3 -0
- data/test/user_test.rb +11 -10
- metadata +9 -3
@@ -20,19 +20,19 @@
|
|
20
20
|
:placeholder => 'your@email.com' %>
|
21
21
|
</span>
|
22
22
|
</div>
|
23
|
-
|
23
|
+
|
24
24
|
<div class="field">
|
25
25
|
<%= label_tag :password %><br />
|
26
26
|
<span class="value">
|
27
27
|
<%= password_field_tag :password %>
|
28
28
|
</span>
|
29
29
|
</div>
|
30
|
-
|
30
|
+
|
31
31
|
<div class="actions">
|
32
32
|
<%= button_tag 'Log in', :name => 'login', :value => 'requested' %>
|
33
33
|
<%= button_tag 'Reset Password', :name => 'reset_password',
|
34
34
|
:value => 'requested', :formaction => reset_password_session_path %>
|
35
|
-
|
35
|
+
|
36
36
|
<% if @redirect_url %>
|
37
37
|
<%= hidden_field_tag :redirect_url, @redirect_url %>
|
38
38
|
<% end %>
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# Manages logging in and out of the application.
|
2
2
|
class SessionController < ApplicationController
|
3
3
|
include Authpwn::SessionController
|
4
|
-
|
4
|
+
|
5
5
|
# Sets up the 'session/welcome' view. No user is logged in.
|
6
6
|
def welcome
|
7
7
|
# You can brag about some statistics.
|
@@ -15,17 +15,19 @@ class SessionController < ApplicationController
|
|
15
15
|
@user = current_user
|
16
16
|
end
|
17
17
|
private :home
|
18
|
-
|
18
|
+
|
19
19
|
# The notification text displayed when a session authentication fails.
|
20
20
|
def bounce_notice_text(reason)
|
21
21
|
case reason
|
22
22
|
when :invalid
|
23
23
|
'Invalid e-mail or password'
|
24
|
+
when :expired
|
25
|
+
'Password expired. Please click "Forget password"'
|
24
26
|
when :blocked
|
25
27
|
'Account blocked. Please verify your e-mail address'
|
26
28
|
end
|
27
29
|
end
|
28
|
-
|
30
|
+
|
29
31
|
# A user is logged in, based on a token.
|
30
32
|
def home_with_token(token)
|
31
33
|
respond_to do |format|
|
@@ -44,7 +46,14 @@ class SessionController < ApplicationController
|
|
44
46
|
end
|
45
47
|
end
|
46
48
|
private :home_with_token
|
47
|
-
|
49
|
+
|
50
|
+
# If true, every successful login results in a SQL query that removes expired
|
51
|
+
# session tokens from the database, to keep its size down.
|
52
|
+
#
|
53
|
+
# For better performance, set this to false and periodically call
|
54
|
+
# Tokens::SessionUid.remove_expired in background thread.
|
55
|
+
self.auto_purge_sessions = true
|
56
|
+
|
48
57
|
# You shouldn't extend the session controller, so you can benefit from future
|
49
58
|
# features. But, if you must, you can do it here.
|
50
59
|
end
|
@@ -16,6 +16,17 @@ class SessionControllerTest < ActionController::TestCase
|
|
16
16
|
assert_select 'a[href="/session"][data-method="delete"]', 'Log out'
|
17
17
|
end
|
18
18
|
|
19
|
+
test "user login works and purges old sessions" do
|
20
|
+
old_token = credentials(:jane_session_token)
|
21
|
+
old_token.updated_at = Time.now - 1.year
|
22
|
+
old_token.save!
|
23
|
+
post :create, :email => @email_credential.email, :password => 'password'
|
24
|
+
assert_equal @user, session_current_user, 'session'
|
25
|
+
assert_redirected_to session_url
|
26
|
+
assert_nil Credentials::Token.with_code(old_token.code),
|
27
|
+
'old session not purged'
|
28
|
+
end
|
29
|
+
|
19
30
|
test "user logged in JSON request" do
|
20
31
|
set_session_current_user @user
|
21
32
|
get :show, :format => 'json'
|
@@ -37,7 +48,7 @@ class SessionControllerTest < ActionController::TestCase
|
|
37
48
|
assert_equal({}, ActiveSupport::JSON.decode(response.body))
|
38
49
|
end
|
39
50
|
|
40
|
-
test "user
|
51
|
+
test "user login page" do
|
41
52
|
get :new
|
42
53
|
assert_template :new
|
43
54
|
|
@@ -63,7 +74,6 @@ class SessionControllerTest < ActionController::TestCase
|
|
63
74
|
'Password not cleared'
|
64
75
|
end
|
65
76
|
|
66
|
-
|
67
77
|
test "password change form" do
|
68
78
|
set_session_current_user @user
|
69
79
|
get :password_change
|
@@ -5,17 +5,17 @@ class SessionMailer < ActionMailer::Base
|
|
5
5
|
# Consider replacing the hostname with a user-friendly application name.
|
6
6
|
"#{server_hostname} e-mail verification"
|
7
7
|
end
|
8
|
-
|
8
|
+
|
9
9
|
def email_verification_from(token, server_hostname, protocol)
|
10
10
|
# You most likely need to replace the e-mail address below.
|
11
11
|
%Q|"#{server_hostname} staff" <admin@#{server_hostname}>|
|
12
|
-
end
|
12
|
+
end
|
13
13
|
|
14
14
|
def reset_password_subject(token, server_hostname, protocol)
|
15
15
|
# Consider replacing the hostname with a user-friendly application name.
|
16
16
|
"#{server_hostname} password reset"
|
17
17
|
end
|
18
|
-
|
18
|
+
|
19
19
|
def reset_password_from(token, server_hostname, protocol)
|
20
20
|
# You most likely need to replace the e-mail address below.
|
21
21
|
%Q|"#{server_hostname} staff" <admin@#{server_hostname}>|
|
data/lib/authpwn_rails/generators/templates/session_mailer/email_verification_email.html.erb
CHANGED
@@ -2,21 +2,21 @@
|
|
2
2
|
<html>
|
3
3
|
<body>
|
4
4
|
<h3>Dear <%= @token.email %>,</h3>
|
5
|
-
|
5
|
+
|
6
6
|
<p>
|
7
7
|
You are receiving this e-mail because someone (hopefully you) registered
|
8
8
|
an account at
|
9
9
|
<%= link_to @host, root_url(:host => @host, :protocol => @protocol) %>
|
10
10
|
using your e-mail address.
|
11
11
|
</p>
|
12
|
-
|
12
|
+
|
13
13
|
<p>
|
14
14
|
Please go
|
15
15
|
<%= link_to 'here', token_session_url(@token, :host => @host,
|
16
16
|
:protocol => @protocol) %>
|
17
17
|
to confirm your e-mail address.
|
18
18
|
</p>
|
19
|
-
|
19
|
+
|
20
20
|
<p>
|
21
21
|
If you haven't registered an account, please ignore this e-mail. Someone
|
22
22
|
most likely mistyped their e-mail.
|
@@ -2,21 +2,21 @@
|
|
2
2
|
<html>
|
3
3
|
<body>
|
4
4
|
<h3>Dear <%= @email %>,</h3>
|
5
|
-
|
5
|
+
|
6
6
|
<p>
|
7
7
|
You are receiving this e-mail because someone (hopefully you) requested a
|
8
8
|
password reset for your
|
9
9
|
<%= link_to @host, root_url(:host => @host, :protocol => @protocol) %>
|
10
10
|
account.
|
11
11
|
</p>
|
12
|
-
|
12
|
+
|
13
13
|
<p>
|
14
14
|
Please go
|
15
15
|
<%= link_to 'here', token_session_url(@token, :host => @host,
|
16
16
|
:protocol => @protocol) %>
|
17
17
|
to reset your password.
|
18
18
|
</p>
|
19
|
-
|
19
|
+
|
20
20
|
<p>
|
21
21
|
If you haven't requested a password reset, please ignore this e-mail.
|
22
22
|
Someone most likely mistyped their e-mail.
|
@@ -13,25 +13,25 @@ class SessionMailerTest < ActionMailer::TestCase
|
|
13
13
|
email = SessionMailer.email_verification_email(@verification_token,
|
14
14
|
@root_url).deliver
|
15
15
|
assert !ActionMailer::Base.deliveries.empty?
|
16
|
-
|
16
|
+
|
17
17
|
assert_equal 'test.host e-mail verification', email.subject
|
18
18
|
assert_equal ['admin@test.host'], email.from
|
19
19
|
assert_equal '"test.host staff" <admin@test.host>', email['from'].to_s
|
20
20
|
assert_equal [@verification_email], email.to
|
21
21
|
assert_match @verification_token.code, email.encoded
|
22
22
|
assert_match @root_url, email.encoded
|
23
|
-
end
|
23
|
+
end
|
24
24
|
|
25
25
|
test 'password reset email' do
|
26
26
|
email = SessionMailer.reset_password_email(@reset_email, @reset_token,
|
27
27
|
@root_url).deliver
|
28
28
|
assert !ActionMailer::Base.deliveries.empty?
|
29
|
-
|
29
|
+
|
30
30
|
assert_equal 'test.host password reset', email.subject
|
31
31
|
assert_equal ['admin@test.host'], email.from
|
32
32
|
assert_equal '"test.host staff" <admin@test.host>', email['from'].to_s
|
33
33
|
assert_equal [@reset_email], email.to
|
34
34
|
assert_match @reset_token.code, email.encoded
|
35
35
|
assert_match @root_url, email.encoded
|
36
|
-
end
|
36
|
+
end
|
37
37
|
end
|
data/lib/authpwn_rails/routes.rb
CHANGED
@@ -21,24 +21,24 @@ module MapperMixin
|
|
21
21
|
controller = options[:controller] || 'session'
|
22
22
|
paths = options[:paths] || controller
|
23
23
|
methods = options[:method_names] || 'session'
|
24
|
-
|
24
|
+
|
25
25
|
get "/#{paths}/token/:code", :controller => controller, :action => 'token',
|
26
26
|
:as => :"token_#{methods}"
|
27
|
-
|
27
|
+
|
28
28
|
get "/#{paths}", :controller => controller, :action => 'show',
|
29
29
|
:as => :"#{methods}"
|
30
30
|
get "/#{paths}/new", :controller => controller, :action => 'new',
|
31
31
|
:as => :"new_#{methods}"
|
32
32
|
post "/#{paths}", :controller => controller, :action => 'create'
|
33
33
|
delete "/#{paths}", :controller => controller, :action => 'destroy'
|
34
|
-
|
34
|
+
|
35
35
|
get "/#{paths}/change_password", :controller => controller,
|
36
36
|
:action => 'password_change',
|
37
37
|
:as => "change_password_#{methods}"
|
38
38
|
post "/#{paths}/change_password", :controller => controller,
|
39
39
|
:action => 'change_password'
|
40
40
|
post "/#{paths}/reset_password", :controller => controller,
|
41
|
-
:action => 'reset_password',
|
41
|
+
:action => 'reset_password',
|
42
42
|
:as => "reset_password_#{methods}"
|
43
43
|
end
|
44
44
|
end
|
@@ -2,16 +2,16 @@ require 'action_controller'
|
|
2
2
|
|
3
3
|
# :nodoc: adds authenticates_using_session
|
4
4
|
class ActionController::Base
|
5
|
-
# Keeps track of the currently authenticated user via the session.
|
5
|
+
# Keeps track of the currently authenticated user via the session.
|
6
6
|
#
|
7
7
|
# Assumes the existence of a User model. A bare ActiveModel model will do the
|
8
8
|
# trick. Model instances must implement id, and the model class must implement
|
9
9
|
# find_by_id.
|
10
10
|
def self.authenticates_using_session(options = {})
|
11
11
|
include Authpwn::ControllerInstanceMethods
|
12
|
-
before_filter :authenticate_using_session, options
|
12
|
+
before_filter :authenticate_using_session, options
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
# True for controllers belonging to the authentication implementation.
|
16
16
|
#
|
17
17
|
# Controllers that return true here are responsible for performing their own
|
@@ -28,6 +28,29 @@ module Authpwn
|
|
28
28
|
module ControllerInstanceMethods
|
29
29
|
include Authpwn::CurrentUser
|
30
30
|
|
31
|
+
# Sets up the session so that it will authenticate the given user.
|
32
|
+
def set_session_current_user(user)
|
33
|
+
# Try to reuse existing sessions.
|
34
|
+
if session[:authpwn_suid]
|
35
|
+
token = Tokens::SessionUid.with_code session[:authpwn_suid]
|
36
|
+
if token
|
37
|
+
if token.user == user
|
38
|
+
token.touch
|
39
|
+
return user
|
40
|
+
else
|
41
|
+
token.destroy
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
if user
|
46
|
+
session[:authpwn_suid] = Tokens::SessionUid.random_for(user,
|
47
|
+
request.remote_ip, request.user_agent).suid
|
48
|
+
else
|
49
|
+
session.delete :authpwn_suid
|
50
|
+
end
|
51
|
+
self.current_user = user
|
52
|
+
end
|
53
|
+
|
31
54
|
# Filter that implements authenticates_using_session.
|
32
55
|
#
|
33
56
|
# If your ApplicationController contains authenticates_using_session, you
|
@@ -36,17 +59,17 @@ module ControllerInstanceMethods
|
|
36
59
|
# skip_before_filter :authenticate_using_session
|
37
60
|
def authenticate_using_session
|
38
61
|
return if current_user
|
39
|
-
|
40
|
-
user =
|
41
|
-
self.current_user = user if user
|
62
|
+
session_uid = session[:authpwn_suid]
|
63
|
+
user = session_uid && Tokens::SessionUid.authenticate(session_uid)
|
64
|
+
self.current_user = user if user && !user.instance_of?(Symbol)
|
42
65
|
end
|
43
66
|
private :authenticate_using_session
|
44
|
-
|
67
|
+
|
45
68
|
# Inform the user that their request is forbidden.
|
46
69
|
#
|
47
70
|
# If a user is logged on, this renders the session/forbidden view with a HTTP
|
48
71
|
# 403 code.
|
49
|
-
#
|
72
|
+
#
|
50
73
|
# If no user is logged in, the user is redirected to session/new, and the
|
51
74
|
# current request's URL is saved in flash[:auth_redirect_url].
|
52
75
|
def bounce_user(redirect_url = request.url)
|
@@ -9,10 +9,14 @@ module Authpwn
|
|
9
9
|
# Session.
|
10
10
|
module SessionController
|
11
11
|
extend ActiveSupport::Concern
|
12
|
-
|
12
|
+
|
13
13
|
included do
|
14
14
|
skip_filter :authenticate_using_session
|
15
15
|
authenticates_using_session :except => [:create, :reset_password, :token]
|
16
|
+
|
17
|
+
# If set, every successful login will cause a database purge.
|
18
|
+
class_attribute :auto_purge_sessions
|
19
|
+
self.auto_purge_sessions = true
|
16
20
|
end
|
17
21
|
|
18
22
|
# GET /session/new
|
@@ -33,7 +37,7 @@ module SessionController
|
|
33
37
|
format.json { render :json => {} }
|
34
38
|
end
|
35
39
|
end
|
36
|
-
else
|
40
|
+
else
|
37
41
|
home
|
38
42
|
unless performed?
|
39
43
|
respond_to do |format|
|
@@ -48,17 +52,20 @@ module SessionController
|
|
48
52
|
end
|
49
53
|
end
|
50
54
|
end
|
51
|
-
|
55
|
+
|
52
56
|
# POST /session
|
53
57
|
def create
|
54
58
|
# Workaround for lack of browser support for the formaction attribute.
|
55
59
|
return reset_password if params[:reset_password]
|
56
|
-
|
60
|
+
|
57
61
|
@redirect_url = params[:redirect_url] || session_url
|
58
62
|
@email = params[:email]
|
59
63
|
auth = User.authenticate_signin @email, params[:password]
|
60
|
-
|
61
|
-
|
64
|
+
unless auth.kind_of? Symbol
|
65
|
+
self.set_session_current_user auth
|
66
|
+
Tokens::SessionUid.remove_expired if auto_purge_sessions
|
67
|
+
end
|
68
|
+
|
62
69
|
respond_to do |format|
|
63
70
|
if current_user
|
64
71
|
format.html { redirect_to @redirect_url }
|
@@ -80,17 +87,17 @@ module SessionController
|
|
80
87
|
end
|
81
88
|
end
|
82
89
|
end
|
83
|
-
|
90
|
+
|
84
91
|
# POST /session/reset_password
|
85
92
|
def reset_password
|
86
93
|
@email = params[:email]
|
87
94
|
credential = Credentials::Email.with @email
|
88
|
-
|
95
|
+
|
89
96
|
if user = (credential && credential.user)
|
90
97
|
token = Tokens::PasswordReset.random_for user
|
91
98
|
::SessionMailer.reset_password_email(@email, token, root_url).deliver
|
92
99
|
end
|
93
|
-
|
100
|
+
|
94
101
|
respond_to do |format|
|
95
102
|
if user
|
96
103
|
format.html do
|
@@ -109,7 +116,7 @@ module SessionController
|
|
109
116
|
end
|
110
117
|
end
|
111
118
|
end
|
112
|
-
|
119
|
+
|
113
120
|
# GET /session/token/token-code
|
114
121
|
def token
|
115
122
|
if token = Credentials::Token.with_code(params[:code])
|
@@ -117,7 +124,7 @@ module SessionController
|
|
117
124
|
else
|
118
125
|
auth = :invalid
|
119
126
|
end
|
120
|
-
|
127
|
+
|
121
128
|
if auth.is_a? Symbol
|
122
129
|
error_text = bounce_notice_text auth
|
123
130
|
respond_to do |format|
|
@@ -128,7 +135,7 @@ module SessionController
|
|
128
135
|
format.json { render :json => { :error => auth, :text => error_text } }
|
129
136
|
end
|
130
137
|
else
|
131
|
-
self.
|
138
|
+
self.set_session_current_user auth
|
132
139
|
home_with_token token
|
133
140
|
unless performed?
|
134
141
|
respond_to do |format|
|
@@ -148,7 +155,7 @@ module SessionController
|
|
148
155
|
|
149
156
|
# DELETE /session
|
150
157
|
def destroy
|
151
|
-
self.
|
158
|
+
self.set_session_current_user nil
|
152
159
|
respond_to do |format|
|
153
160
|
format.html { redirect_to session_url }
|
154
161
|
format.json { head :ok }
|
@@ -174,14 +181,14 @@ module SessionController
|
|
174
181
|
end
|
175
182
|
end
|
176
183
|
end
|
177
|
-
|
184
|
+
|
178
185
|
# POST /session/change_password
|
179
186
|
def change_password
|
180
187
|
unless current_user
|
181
188
|
bounce_user
|
182
189
|
return
|
183
190
|
end
|
184
|
-
|
191
|
+
|
185
192
|
@credential = current_user.credentials.
|
186
193
|
find { |c| c.is_a? Credentials::Password }
|
187
194
|
if @credential
|
@@ -222,22 +229,24 @@ module SessionController
|
|
222
229
|
def home
|
223
230
|
end
|
224
231
|
private :home
|
225
|
-
|
232
|
+
|
226
233
|
# Hook for setting up the welcome view.
|
227
234
|
def welcome
|
228
235
|
end
|
229
236
|
private :welcome
|
230
|
-
|
237
|
+
|
231
238
|
# Hook for setting up the home view after token-based authentication.
|
232
239
|
def home_with_token(token)
|
233
240
|
end
|
234
241
|
private :home_with_token
|
235
242
|
|
236
|
-
# Hook for customizing the bounce notification text.
|
243
|
+
# Hook for customizing the bounce notification text.
|
237
244
|
def bounce_notice_text(reason)
|
238
245
|
case reason
|
239
246
|
when :invalid
|
240
247
|
'Invalid e-mail or password'
|
248
|
+
when :expired
|
249
|
+
'Password expired. Please click "Forget password"'
|
241
250
|
when :blocked
|
242
251
|
'Account blocked. Please verify your e-mail address'
|
243
252
|
end
|