authpwn_rails 0.10.5 → 0.10.6

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 (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)