authpwn_rails 0.10.5 → 0.10.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/Gemfile +8 -8
  2. data/Gemfile.lock +1 -1
  3. data/VERSION +1 -1
  4. data/app/models/credentials/email.rb +12 -4
  5. data/app/models/credentials/token.rb +106 -0
  6. data/app/models/tokens/email_verification.rb +42 -0
  7. data/app/models/tokens/one_time.rb +16 -0
  8. data/app/models/tokens/password_reset.rb +27 -0
  9. data/authpwn_rails.gemspec +36 -11
  10. data/lib/authpwn_rails.rb +2 -0
  11. data/lib/authpwn_rails/generators/all_generator.rb +20 -2
  12. data/lib/authpwn_rails/generators/templates/credentials.yml +21 -0
  13. data/lib/authpwn_rails/generators/templates/session/new.html.erb +10 -5
  14. data/lib/authpwn_rails/generators/templates/session/password_change.html.erb +37 -0
  15. data/lib/authpwn_rails/generators/templates/session_controller.rb +20 -2
  16. data/lib/authpwn_rails/generators/templates/session_controller_test.rb +71 -0
  17. data/lib/authpwn_rails/generators/templates/session_mailer.rb +26 -0
  18. data/lib/authpwn_rails/generators/templates/session_mailer/email_verification_email.html.erb +23 -0
  19. data/lib/authpwn_rails/generators/templates/session_mailer/email_verification_email.text.erb +11 -0
  20. data/lib/authpwn_rails/generators/templates/session_mailer/reset_password_email.html.erb +23 -0
  21. data/lib/authpwn_rails/generators/templates/session_mailer/reset_password_email.text.erb +11 -0
  22. data/lib/authpwn_rails/generators/templates/session_mailer_test.rb +37 -0
  23. data/lib/authpwn_rails/routes.rb +50 -0
  24. data/lib/authpwn_rails/session_controller.rb +129 -0
  25. data/lib/authpwn_rails/session_mailer.rb +66 -0
  26. data/test/{email_credential_test.rb → credentials/email_credential_test.rb} +1 -1
  27. data/test/credentials/email_verification_token_test.rb +78 -0
  28. data/test/{facebook_credential_test.rb → credentials/facebook_credential_test.rb} +1 -1
  29. data/test/credentials/one_time_token_credential_test.rb +84 -0
  30. data/test/{password_credential_test.rb → credentials/password_credential_test.rb} +1 -1
  31. data/test/credentials/password_reset_token_test.rb +72 -0
  32. data/test/credentials/token_crendential_test.rb +102 -0
  33. data/test/fixtures/bare_session/forbidden.html.erb +20 -0
  34. data/test/fixtures/bare_session/home.html.erb +5 -0
  35. data/test/fixtures/bare_session/new.html.erb +32 -0
  36. data/test/fixtures/bare_session/password_change.html.erb +30 -0
  37. data/test/fixtures/bare_session/welcome.html.erb +5 -0
  38. data/test/helpers/action_mailer.rb +8 -0
  39. data/test/helpers/routes.rb +8 -2
  40. data/test/routes_test.rb +31 -0
  41. data/test/session_controller_api_test.rb +310 -15
  42. data/test/session_mailer_api_test.rb +67 -0
  43. data/test/test_helper.rb +3 -1
  44. data/test/{email_field_test.rb → user_extensions/email_field_test.rb} +1 -1
  45. data/test/{facebook_fields_test.rb → user_extensions/facebook_fields_test.rb} +1 -1
  46. data/test/{password_field_test.rb → user_extensions/password_field_test.rb} +1 -1
  47. metadata +49 -24
@@ -1,5 +1,3 @@
1
- <p>This is a sample login form. You should customize it for your users.</p>
2
-
3
1
  <% if flash[:notice] %>
4
2
  <p class="notice"><%= flash[:notice] %></p>
5
3
  <% end %>
@@ -14,16 +12,23 @@
14
12
  <%= form_tag session_path do %>
15
13
  <div class="field">
16
14
  <%= label_tag :email, 'Email Address' %><br />
17
- <%= email_field_tag :email, @email %>
15
+ <span class="value">
16
+ <%= email_field_tag :email, @email, :autofocus => true, :required => true,
17
+ :placeholder => 'your@email.com' %>
18
+ </span>
18
19
  </div>
19
20
 
20
21
  <div class="field">
21
22
  <%= label_tag :password %><br />
22
- <%= password_field_tag :password %>
23
+ <span class="value">
24
+ <%= password_field_tag :password %>
25
+ </span>
23
26
  </div>
24
27
 
25
28
  <div class="actions">
26
- <%= submit_tag 'Log in' %>
29
+ <%= button_tag 'Log in', :name => 'login', :value => 'requested' %>
30
+ <%= button_tag 'Reset Password', :name => 'reset_password',
31
+ :value => 'requested', :formaction => reset_password_session_path %>
27
32
 
28
33
  <% if @redirect_url %>
29
34
  <%= hidden_field_tag :redirect_url, @redirect_url %>
@@ -0,0 +1,37 @@
1
+ <h1>Password Change</h1>
2
+
3
+ <% if flash[:notice] %>
4
+ <p class="notice"><%= flash[:notice] %></p>
5
+ <% end %>
6
+
7
+ <%= form_for @credential, :url => change_password_session_path,
8
+ :as => :credential, :method => :post do |f| %>
9
+ <section class="fields">
10
+ <% unless @credential.new_record? %>
11
+ <div class="field">
12
+ <%= label_tag :old_password, 'Current Password' %><br />
13
+ <span class="value">
14
+ <%= password_field_tag :old_password %>
15
+ </span>
16
+ </div>
17
+ <% end %>
18
+
19
+ <div class="field">
20
+ <%= f.label :password, 'New Password' %><br />
21
+ <span class="value">
22
+ <%= f.password_field :password %>
23
+ </span>
24
+ </div>
25
+
26
+ <div class="field">
27
+ <%= f.label :password_confirmation, 'Re-enter New Password' %><br />
28
+ <span class="value">
29
+ <%= f.password_field :password_confirmation %>
30
+ </span>
31
+ </div>
32
+ </section>
33
+
34
+ <div class="actions">
35
+ <%= f.submit 'Change Password' %>
36
+ </div>
37
+ <% end %>
@@ -26,7 +26,25 @@ class SessionController < ApplicationController
26
26
  end
27
27
  end
28
28
 
29
+ # A user is logged in, based on a token.
30
+ def home_with_token(token)
31
+ respond_to do |format|
32
+ format.html do
33
+ case token
34
+ when Tokens::EmailVerification
35
+ redirect_to session_url, :notice => 'E-mail address confirmed'
36
+ when Tokens::PasswordReset
37
+ redirect_to change_password_session_url
38
+ # Handle other token types here.
39
+ end
40
+ end
41
+ format.json do
42
+ # Rely on default behavior.
43
+ end
44
+ end
45
+ end
46
+ private :home_with_token
47
+
29
48
  # You shouldn't extend the session controller, so you can benefit from future
30
- # features, like Facebook / Twitter / OpenID integration. But, if you must,
31
- # you can do it here.
49
+ # features. But, if you must, you can do it here.
32
50
  end
@@ -3,6 +3,9 @@ require 'test_helper'
3
3
  class SessionControllerTest < ActionController::TestCase
4
4
  setup do
5
5
  @user = users(:john)
6
+ @email_credential = credentials(:john_email)
7
+ @password_credential = credentials(:john_password)
8
+ @token_credential = credentials(:john_email_token)
6
9
  end
7
10
 
8
11
  test "user home page" do
@@ -33,4 +36,72 @@ class SessionControllerTest < ActionController::TestCase
33
36
 
34
37
  assert_equal({}, ActiveSupport::JSON.decode(response.body))
35
38
  end
39
+
40
+ test "user signup page" do
41
+ get :new
42
+ assert_template :new
43
+
44
+ assert_select 'form[action=?]', session_path do
45
+ assert_select 'input[name="email"]'
46
+ assert_select 'input[name="password"]'
47
+ assert_select 'button[name="login"]'
48
+ assert_select 'button[name="reset_password"]'
49
+ end
50
+ end
51
+
52
+ test "e-mail verification link" do
53
+ get :token, :code => @token_credential.code
54
+ assert_redirected_to session_url
55
+ assert @email_credential.reload.verified?, 'Email not verified'
56
+ end
57
+
58
+ test "password reset link" do
59
+ password_credential = credentials(:jane_password)
60
+ get :token, :code => credentials(:jane_password_token).code
61
+ assert_redirected_to change_password_session_url
62
+ assert_nil Credential.where(:id => password_credential.id).first,
63
+ 'Password not cleared'
64
+ end
65
+
66
+
67
+ test "password change form" do
68
+ set_session_current_user @user
69
+ get :password_change
70
+
71
+ assert_select 'form[action=?][method="post"]',
72
+ change_password_session_path do
73
+ assert_select 'input[name="old_password"]'
74
+ assert_select 'input[name=?]', 'credential[password]'
75
+ assert_select 'input[name=?]', 'credential[password_confirmation]'
76
+ assert_select 'input[type=submit]'
77
+ end
78
+ end
79
+
80
+ test "password reset form" do
81
+ set_session_current_user @user
82
+ @password_credential.destroy
83
+ get :password_change
84
+
85
+ assert_select 'form[action=?][method="post"]',
86
+ change_password_session_path do
87
+ assert_select 'input[name="old_password"]', :count => 0
88
+ assert_select 'input[name=?]', 'credential[password]'
89
+ assert_select 'input[name=?]', 'credential[password_confirmation]'
90
+ assert_select 'input[type=submit]'
91
+ end
92
+ end
93
+
94
+ test "password reset request" do
95
+ ActionMailer::Base.deliveries = []
96
+
97
+ assert_difference 'Credential.count', 1 do
98
+ post :reset_password, :email => @email_credential.email
99
+ end
100
+
101
+ assert !ActionMailer::Base.deliveries.empty?, 'email generated'
102
+ email = ActionMailer::Base.deliveries.last
103
+ assert_equal [@email_credential.email], email.to
104
+
105
+ assert_redirected_to new_session_url
106
+ end
36
107
  end
@@ -0,0 +1,26 @@
1
+ class SessionMailer < ActionMailer::Base
2
+ include Authpwn::SessionMailer
3
+
4
+ def email_verification_subject(token, server_hostname)
5
+ # Consider replacing the hostname with a user-friendly application name.
6
+ "#{server_hostname} e-mail verification"
7
+ end
8
+
9
+ def email_verification_from(token, server_hostname)
10
+ # You most likely need to replace the e-mail address below.
11
+ %Q|"#{server_hostname} staff" <admin@#{server_hostname}>|
12
+ end
13
+
14
+ def reset_password_subject(token, server_hostname)
15
+ # Consider replacing the hostname with a user-friendly application name.
16
+ "#{server_hostname} password reset"
17
+ end
18
+
19
+ def reset_password_from(token, server_hostname)
20
+ # You most likely need to replace the e-mail address below.
21
+ %Q|"#{server_hostname} staff" <admin@#{server_hostname}>|
22
+ end
23
+
24
+ # You shouldn't extend the session controller, so you can benefit from future
25
+ # features. But, if you must, you can do it here.
26
+ end
@@ -0,0 +1,23 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <body>
4
+ <h3>Dear <%= @token.email %>,</h3>
5
+
6
+ <p>
7
+ You are receiving this e-mail because someone (hopefully you) registered
8
+ an account at <%= link_to @host, root_url(:host => @host) %>
9
+ using your e-mail address.
10
+ </p>
11
+
12
+ <p>
13
+ Please go
14
+ <%= link_to 'here', token_session_url(@token, :host => @host) %>
15
+ to confirm your e-mail address.
16
+ </p>
17
+
18
+ <p>
19
+ If you haven't registered an account, please ignore this e-mail. Someone
20
+ most likely mistyped their e-mail.
21
+ </p>
22
+ </body>
23
+ </html>
@@ -0,0 +1,11 @@
1
+ Dear <%= @token.email %>,
2
+
3
+
4
+ You are receiving this e-mail because someone (hopefully you) registered an
5
+ account at <%= @host %> using your e-mail address.
6
+
7
+ Please go to the address below to confirm your e-mail address.
8
+ <%= link_to 'here', token_session_url(@token, :host => @host) %>
9
+
10
+ If you haven't registered an account, please ignore this e-mail. Someone most
11
+ likely mistyped their e-mail.
@@ -0,0 +1,23 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <body>
4
+ <h3>Dear <%= @email %>,</h3>
5
+
6
+ <p>
7
+ You are receiving this e-mail because someone (hopefully you) requested a
8
+ password reset for your <%= link_to @host, root_url(:host => @host) %>
9
+ account.
10
+ </p>
11
+
12
+ <p>
13
+ Please go
14
+ <%= link_to 'here', token_session_url(@token, :host => @host) %>
15
+ to reset your password.
16
+ </p>
17
+
18
+ <p>
19
+ If you haven't requested a password reset, please ignore this e-mail.
20
+ Someone most likely mistyped their e-mail.
21
+ </p>
22
+ </body>
23
+ </html>
@@ -0,0 +1,11 @@
1
+ Dear <%= @email %>,
2
+
3
+
4
+ You are receiving this e-mail because someone (hopefully you) requested a
5
+ password reset for your <%= @host %> account.
6
+
7
+ Please go to the address below to reset your password.
8
+ <%= token_session_url(@token, :host => @host) %>
9
+
10
+ If you haven't requested a password reset, please ignore this e-mail. Someone
11
+ most likely mistyped their e-mail.
@@ -0,0 +1,37 @@
1
+ require 'test_helper'
2
+
3
+ class SessionMailerTest < ActionMailer::TestCase
4
+ setup do
5
+ @reset_email = credentials(:jane_email).email
6
+ @reset_token = credentials(:jane_password_token)
7
+ @verification_token = credentials(:john_email_token)
8
+ @verification_email = credentials(:john_email).email
9
+ @host = 'test.host'
10
+ end
11
+
12
+ test 'email verification email' do
13
+ email = SessionMailer.email_verification_email(@verification_token, @host).
14
+ deliver
15
+ assert !ActionMailer::Base.deliveries.empty?
16
+
17
+ assert_equal 'test.host e-mail verification', email.subject
18
+ assert_equal ['admin@test.host'], email.from
19
+ assert_equal '"test.host staff" <admin@test.host>', email['from'].to_s
20
+ assert_equal [@verification_email], email.to
21
+ assert_match @verification_token.code, email.encoded
22
+ assert_match @host, email.encoded
23
+ end
24
+
25
+ test 'password reset email' do
26
+ email = SessionMailer.reset_password_email(@reset_email, @reset_token,
27
+ @host).deliver
28
+ assert !ActionMailer::Base.deliveries.empty?
29
+
30
+ assert_equal 'test.host password reset', email.subject
31
+ assert_equal ['admin@test.host'], email.from
32
+ assert_equal '"test.host staff" <admin@test.host>', email['from'].to_s
33
+ assert_equal [@reset_email], email.to
34
+ assert_match @reset_token.code, email.encoded
35
+ assert_match @host, email.encoded
36
+ end
37
+ end
@@ -0,0 +1,50 @@
1
+ require 'action_pack'
2
+
3
+ # :nodoc: namespace
4
+ module Authpwn
5
+
6
+ # :nodoc: namespace
7
+ module Routes
8
+
9
+ # :nodoc: mixed into ActionPack's route mapper.
10
+ module MapperMixin
11
+ # Draws the routes for a session controller.
12
+ #
13
+ # The options hash accepts the following keys.
14
+ # :controller:: the name of the controller; defaults to "session" for
15
+ # SessionController
16
+ # :paths:: the prefix of the route paths; defaults to the controller name
17
+ # :method_names:: the root of name used in the path methods; defaults to
18
+ # "session", which will generate names like session_path,
19
+ # new_session_path, and token_session_path
20
+ def authpwn_session(options = {})
21
+ controller = options[:controller] || 'session'
22
+ paths = options[:paths] || controller
23
+ methods = options[:method_names] || 'session'
24
+
25
+ get "/#{paths}/token/:code", :controller => controller, :action => 'token',
26
+ :as => :"token_#{methods}"
27
+
28
+ get "/#{paths}", :controller => controller, :action => 'show',
29
+ :as => :"#{methods}"
30
+ get "/#{paths}/new", :controller => controller, :action => 'new',
31
+ :as => :"new_#{methods}"
32
+ post "/#{paths}", :controller => controller, :action => 'create'
33
+ delete "/#{paths}", :controller => controller, :action => 'destroy'
34
+
35
+ get "/#{paths}/change_password", :controller => controller,
36
+ :action => 'password_change',
37
+ :as => "change_password_#{methods}"
38
+ post "/#{paths}/change_password", :controller => controller,
39
+ :action => 'change_password'
40
+ post "/#{paths}/reset_password", :controller => controller,
41
+ :action => 'reset_password',
42
+ :as => "reset_password_#{methods}"
43
+ end
44
+ end
45
+
46
+ ActionDispatch::Routing::Mapper.send :include, MapperMixin
47
+
48
+ end # namespace Authpwn::Routes
49
+
50
+ end # namespace Authpwn
@@ -50,6 +50,9 @@ module SessionController
50
50
 
51
51
  # POST /session
52
52
  def create
53
+ # Workaround for lack of browser support for the formaction attribute.
54
+ return reset_password if params[:reset_password]
55
+
53
56
  @redirect_url = params[:redirect_url] || session_url
54
57
  @email = params[:email]
55
58
  auth = Credentials::Password.authenticate_email @email, params[:password]
@@ -76,6 +79,72 @@ module SessionController
76
79
  end
77
80
  end
78
81
  end
82
+
83
+ # POST /session/reset_password
84
+ def reset_password
85
+ @email = params[:email]
86
+ credential = Credentials::Email.with @email
87
+
88
+ if user = (credential && credential.user)
89
+ token = Tokens::PasswordReset.random_for user
90
+ ::SessionMailer.reset_password_email(@email, token,
91
+ request.host_with_port).deliver
92
+ end
93
+
94
+ respond_to do |format|
95
+ if user
96
+ format.html do
97
+ redirect_to new_session_url, :notice =>
98
+ 'Please check your e-mail for instructions'
99
+ end
100
+ format.json { render :json => { } }
101
+ else
102
+ notice = 'Invalid e-mail'
103
+ format.html do
104
+ redirect_to new_session_url, :notice => notice
105
+ end
106
+ format.json do
107
+ render :json => { :error => :not_found, :text => notice }
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ # GET /session/token/token-code
114
+ def token
115
+ if token = Credentials::Token.with_code(params[:code])
116
+ auth = token.authenticate
117
+ else
118
+ auth = :invalid
119
+ end
120
+
121
+ if auth.is_a? Symbol
122
+ notice = bounce_notice_text auth
123
+ respond_to do |format|
124
+ format.html do
125
+ redirect_to new_session_url, :flash => { :notice => notice,
126
+ :auth_redirect_url => session_url }
127
+ end
128
+ format.json { render :json => { :error => auth, :text => notice } }
129
+ end
130
+ else
131
+ self.current_user = auth
132
+ home_with_token token
133
+ unless performed?
134
+ respond_to do |format|
135
+ format.html { redirect_to session_url }
136
+ format.json do
137
+ user_data = current_user.as_json
138
+ if current_user.class.include_root_in_json
139
+ user_data = user_data['user']
140
+ end
141
+ render :json => { :user => user_data,
142
+ :csrf => form_authenticity_token }
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
79
148
 
80
149
  # DELETE /session
81
150
  def destroy
@@ -86,6 +155,61 @@ module SessionController
86
155
  end
87
156
  end
88
157
 
158
+ # GET /session/change_password
159
+ def password_change
160
+ unless current_user
161
+ bounce_user
162
+ return
163
+ end
164
+
165
+ respond_to do |format|
166
+ format.html do
167
+ @credential = current_user.credentials.
168
+ find { |c| c.is_a? Credentials::Password }
169
+ unless @credential
170
+ @credential = Credentials::Password.new
171
+ @credential.user = current_user
172
+ end
173
+ # Renders session/password_change.html.erb
174
+ end
175
+ end
176
+ end
177
+
178
+ # POST /session/change_password
179
+ def change_password
180
+ unless current_user
181
+ bounce_user
182
+ return
183
+ end
184
+
185
+ @credential = current_user.credentials.
186
+ find { |c| c.is_a? Credentials::Password }
187
+ if @credential
188
+ # An old password is set, must verify it.
189
+ if @credential.check_password params[:old_password]
190
+ success = @credential.update_attributes params[:credential]
191
+ else
192
+ success = false
193
+ flash[:notice] = 'Incorrect old password. Please try again.'
194
+ end
195
+ else
196
+ @credential = Credentials::Password.new params[:credential]
197
+ @credential.user = current_user
198
+ success = @credential.save
199
+ end
200
+ respond_to do |format|
201
+ if success
202
+ format.html do
203
+ redirect_to session_url, :notice => 'Password updated'
204
+ end
205
+ format.json { head :ok }
206
+ else
207
+ format.html { render :action => :password_change }
208
+ format.json { render :json => { :error => :invalid } }
209
+ end
210
+ end
211
+ end
212
+
89
213
  # Hook for setting up the home view.
90
214
  def home
91
215
  end
@@ -95,6 +219,11 @@ module SessionController
95
219
  def welcome
96
220
  end
97
221
  private :welcome
222
+
223
+ # Hook for setting up the home view after token-based authentication.
224
+ def home_with_token(token)
225
+ end
226
+ private :home_with_token
98
227
 
99
228
  # Hook for customizing the bounce notification text.
100
229
  def bounce_notice_text(reason)