authkit 0.2.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +144 -34
- data/Rakefile +8 -0
- data/lib/authkit/version.rb +1 -1
- data/lib/generators/authkit/install_generator.rb +0 -3
- data/lib/generators/authkit/templates/app/controllers/application_controller.rb +20 -13
- data/lib/generators/authkit/templates/app/controllers/email_confirmation_controller.rb +21 -3
- data/lib/generators/authkit/templates/app/controllers/password_change_controller.rb +37 -5
- data/lib/generators/authkit/templates/app/controllers/sessions_controller.rb +3 -1
- data/lib/generators/authkit/templates/app/controllers/signup_controller.rb +3 -1
- data/lib/generators/authkit/templates/app/models/user.rb +48 -52
- data/lib/generators/authkit/templates/app/views/password_change/show.html.erb +1 -0
- data/lib/generators/authkit/templates/app/views/sessions/new.html.erb +4 -0
- data/lib/generators/authkit/templates/app/views/signup/new.html.erb +5 -1
- data/lib/generators/authkit/templates/app/views/users/edit.html.erb +1 -1
- data/lib/generators/authkit/templates/spec/controllers/application_controller_spec.rb +26 -26
- data/lib/generators/authkit/templates/spec/controllers/email_confirmation_controller_spec.rb +28 -10
- data/lib/generators/authkit/templates/spec/controllers/password_change_controller_spec.rb +71 -21
- data/lib/generators/authkit/templates/spec/controllers/sessions_controller_spec.rb +14 -0
- data/lib/generators/authkit/templates/spec/controllers/signup_controller_spec.rb +14 -0
- data/lib/generators/authkit/templates/spec/forms/signup_spec.rb +3 -0
- data/lib/generators/authkit/templates/spec/models/user_spec.rb +63 -66
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8a50e83ce52b25ebf0d07f9582f6d58485ff4b67
|
4
|
+
data.tar.gz: 1f817df72d8bbcde3524b5da618c00ccaa48b8b3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 78a53378ab13f476cb291fdc648114a3dc0500f8e92a541e6f8f253e4f35ce0732aafe0de35b953729ec9580f49e957cb793193405512c71fdc1e6f801e794f7
|
7
|
+
data.tar.gz: bfa4d0d452bc397d2c8fc5c206182d1bbad95eece55250ad646b53faaf1d26eecf33f6285a41fa28f64178579a8ce879062f35a7093749449bbff165a0bbe1d4
|
data/README.md
CHANGED
@@ -4,21 +4,15 @@ A gem for installing auth into you app.
|
|
4
4
|
|
5
5
|
## Why?
|
6
6
|
|
7
|
-
There are lots of great authentication gems out there; devise? clearance? restful_auth?
|
8
|
-
All of these seek to solve the problem of adding authentication to your application but they all share
|
9
|
-
one philosophy: you shouldn't need to think about authentication to build your app. For me, I find I
|
10
|
-
spend way more time trying to figure out how to customize the tools for the few cases when my
|
11
|
-
application needs to do something different.
|
7
|
+
There are lots of great authentication gems out there; devise? clearance? restful_auth? All of these seek to solve the problem of adding authentication to your application but they all share one philosophy: you shouldn't need to think about authentication to build your app. For me, I find I spend way more time trying to figure out how to customize the tools for the few cases when my application needs to do something different.
|
12
8
|
|
13
|
-
Authkit takes the opposite stance: auth belongs in your app. It is important and it is specific to your
|
14
|
-
|
15
|
-
|
9
|
+
Authkit takes the opposite stance: auth belongs in your app. It is important and it is specific to your app. It only includes generators and installs itself with some specs. You customize it. Everything is right where you would expect it to be.
|
10
|
+
|
11
|
+
Of course, this stance can be very dangerous a it relies on the application developer to not interfere with the authentication mechanisms, and it makes introducing security patches difficult. This is the trade-off. Generally speaking the approaches taken within authkit are designed for the early life-cycle of a small to medium application. It can support much larger platforms, but it is likely that larger platforms will need centralized authentication mechanisms that go beyond the scope of this project.
|
16
12
|
|
17
13
|
## Features
|
18
14
|
|
19
|
-
Authkit supports Ruby down to version 1.9 but targets 2.0. It is built for Rails 4. It is possible
|
20
|
-
that it could support Rails 3.x (currently it relies on strong parameters and the Rails 4
|
21
|
-
message verifier and `secret_key_base`). Some of the features include:
|
15
|
+
Authkit supports Ruby down to version 1.9 but targets 2.0. It is built for Rails 4. It is possible that it could support Rails 3.x (currently it relies on strong parameters and the Rails 4 message verifier and `secret_key_base`). Some of the features include:
|
22
16
|
|
23
17
|
* Signup (username or email)
|
24
18
|
* Login/Logout
|
@@ -42,8 +36,7 @@ Some possible features include:
|
|
42
36
|
* Third party accounts
|
43
37
|
* Installer options (test framework, security bulletins, modules)
|
44
38
|
|
45
|
-
If there is a feature you don't want to use, you just have to go and delete the generated code.
|
46
|
-
It is your application to customize.
|
39
|
+
If there is a feature you don't want to use, you just have to go and delete the generated code. It is your application to customize.
|
47
40
|
|
48
41
|
More information is available in [FEATURES](FEATURES.md).
|
49
42
|
|
@@ -136,21 +129,150 @@ And will add some gems to your Gemfile:
|
|
136
129
|
gemfile shoulda-matchers, :test, :development
|
137
130
|
gemfile factor_girl_rails, :test, :development
|
138
131
|
|
139
|
-
Once you have this installed you can remove the gem, however you may want to
|
140
|
-
keep the gem installed in development as you will be able to update it
|
141
|
-
and check for security bulletins.
|
132
|
+
Once you have this installed you can remove the gem, however you may want to keep the gem installed in development as you will be able to update it and check for security bulletins.
|
142
133
|
|
143
134
|
You'll need to migrate your database (check the migrations before you do):
|
144
135
|
|
145
136
|
rake db:migrate
|
146
137
|
|
147
|
-
You'll also need to connect your mailers for sending password reset instructions
|
148
|
-
|
138
|
+
You'll also need to connect your mailers for sending password reset instructions and email confirmations. (See the TODO in `user.rb`)
|
139
|
+
|
140
|
+
## NOTES
|
141
|
+
|
142
|
+
Authkit has a number of conventions and requirements that should be noted.
|
143
|
+
|
144
|
+
* SSL expected
|
145
|
+
* secure cookies
|
146
|
+
* password complexity is not robust
|
147
|
+
* username resrictions are not implemented
|
148
|
+
* users do not need to confirm their email address to proceed
|
149
|
+
* need a root route
|
150
|
+
|
151
|
+
### SSL
|
152
|
+
|
153
|
+
It is expected that your application be protected by SSL. Though it is possible to segregate your application into SSL/non-SSL areas, Authkit utilizes cookies to store remember token information and assumes that sessions are backed by a cookie store. Because of this you must use SSL to protect against Session Hijacking attacks. Cookies are marked as secure only in the production environment (see `ApplicationController#set_remember_cookie`). If you are using Authkit in a staging environment you might need to adjust this.
|
154
|
+
|
155
|
+
### Password and username validation
|
156
|
+
|
157
|
+
There is only a minimal amount of validation on the password. Because of this users can choose poor passwords (which are not complex or are overly common). To improve this you can adjust the validation in `user.rb`:
|
158
|
+
|
159
|
+
validates :password, presence: true, confirmation: true, length: {minimum: 6}, if: :password_set?
|
160
|
+
|
161
|
+
Likewise, there are no restrictions on `username`. If you want to use this field within the URL you will need to constrain the format of the `username` field. Additionally, there may be some user names you want to explicitly disallow based on your routing setup.
|
162
|
+
|
163
|
+
### Confirmation not required by default
|
164
|
+
|
165
|
+
By default, users can begin using the system without confirming their email address. This simplifies the onboarding process, however it means that malicious users may be operating under false pretense. You can change this by adding a check to `ApplicationController#require_login`:
|
166
|
+
|
167
|
+
def require_login
|
168
|
+
deny_user(nil, login_path) unless logged_in?
|
169
|
+
deny_user("You need to confirm your email address before proceeding", root_path) unless current_user.email_confirmed?
|
170
|
+
end
|
171
|
+
|
172
|
+
And then in `user.rb`:
|
173
|
+
|
174
|
+
def email_confirmed?
|
175
|
+
self.confirmation_token.blank?
|
176
|
+
end
|
177
|
+
|
178
|
+
### Root route
|
179
|
+
|
180
|
+
Additionally, there are several redirects that occur within Authkit (when you have successfully logged in or logged out, etc.). By default the user is redirected to the root_path in these cases. Because of this, you must define a `root` route in your `config/routes.rb`.
|
181
|
+
|
182
|
+
## Tokens
|
183
|
+
|
184
|
+
Authkit makes use of several kinds of tokens:
|
185
|
+
|
186
|
+
* remember tokens
|
187
|
+
* reset password tokens
|
188
|
+
* confirmation tokens (email)
|
189
|
+
* unlock tokens
|
190
|
+
* one time use password tokens
|
191
|
+
* api tokens
|
192
|
+
|
193
|
+
All of the tokens are generated using `SecureRandom.urlsafe_base64(32)`. Each token has a unique index within the database to prevent conflicts but collisions are very unlikely (1/64^32). In the event that a conflict does occur an `ActiveRecord::StatementInvalid` or `ActiveRecord::RecordNotUnique` exeception will be raised.
|
194
|
+
|
195
|
+
It has been suggested that tokens are essentially passwords (though quite complex ones) and that they should not be stored directly in the database. Instead tokens should be stored using Bcrypt (and stored as a token_digest) to prevent someone with read access from gaining control of an account. This is not currently implemented.
|
196
|
+
|
197
|
+
Failed attempts are not currently tracked for tokens. Token misses could be used to contribute to `failed_attempts`, however in certain circumstances this could be used to disrupt service by locking accounts. Ideally, invalid tokens would be logged centrally and an existing tool like `fail2ban` could be used to restrict access.
|
198
|
+
|
199
|
+
You can adjust the default token expiry in `user.rb`.
|
200
|
+
|
201
|
+
Each of these tokens utilizes a different strategy to protect it from attacks.
|
202
|
+
|
203
|
+
### Remember tokens
|
204
|
+
|
205
|
+
Remember tokens are re-generated every-time a user logs in and the resulting token is stored in a cookie on the user's device (i.e., the browser). The cookie is encrypted, signed and only delivered over secure connections (see SSL above).
|
206
|
+
|
207
|
+
Because the token is regenerated on every login, any existing remember cookies (for instance, on another device) will be immediately invalidated.
|
208
|
+
|
209
|
+
Because the cookie mechanism uses `ActiveSupport#MessageVerifier` it is dependent on the security of that class. By default that class securely compares strings and decrypts using strong secret keys (the Rails `secret_key_base` specifically). This protects against timing attacks. Once the verified token is obtained, it can be safely used as part of a database query.
|
210
|
+
|
211
|
+
Changing your `secret_key_base` will invalidate all existing cookies including all remember cookies. This may be a feature as it is likely that you would want to invalidate all sessions in the event your secret key was compromised.
|
212
|
+
|
213
|
+
Because the token is not used directly (it must be included in the cookie), even with read access to the database an attacker cannot login without also having the ability to sign the remember cookie.
|
214
|
+
|
215
|
+
Once the user logs out the token is cleared and is no longer available.
|
216
|
+
|
217
|
+
### Reset password tokens
|
218
|
+
|
219
|
+
When a user forgets their password they can request a password reset so that they can change their password. A new `reset_password_token` is generated when a request is made and an email is sent to the corresponding email address.
|
220
|
+
|
221
|
+
It is possible to encode the resulting token using the message verifier which could later be used to validate that the token really was generated by the system.
|
222
|
+
|
223
|
+
Instead the system employs a two-token approach, using both the corresponding email address and the `reset_password_token`. The token is paired with an email parameter so that the user can be found in the database. Once found the tokens can be securely compared to prevent timing attacks. The email address is chosen over the user id because the reset request was generated using the email address and thus is already known. Using the id would increase information leakage.
|
224
|
+
|
225
|
+
Again, if you are not using SSL this means that the email address and token will be visible in the path information of requests.
|
226
|
+
|
227
|
+
Once the password is changed, the token is cleared and is no longer available.
|
228
|
+
|
229
|
+
### Confirmation tokens
|
230
|
+
|
231
|
+
When a user signs up or changes their email address an email is sent to the specified address to confirm that the user really controls the email. This is done to ensure that users didn't mistype the address and also protects against malicious users impersonating well known accounts.
|
232
|
+
|
233
|
+
Like password resets, these tokens are sent directly in email. In the case of email confirmation, however it is possible to require that the user be logged in to utilize the token. Because of this the tokens can easily be compared securely to prevent timing attacks.
|
234
|
+
|
235
|
+
Once the email is confirmed, the token is cleared and is no longer available.
|
236
|
+
|
237
|
+
### Unlock tokens
|
238
|
+
|
239
|
+
Currently unlock tokens are not implemented. Once implemented unlocks will be sent to logged out users using their email address. Because of this, it is likely that any implementation of unlock tokens will function similar to password reset tokens.
|
240
|
+
|
241
|
+
### API tokens
|
242
|
+
|
243
|
+
Currently API tokens are not implemented. An API token implementation will not have access to a current user. Because of this the API token system can take one of two approaches:
|
244
|
+
|
245
|
+
1. Using a `ActiveSupport::MessageVerifier` to generate verified tokens.
|
246
|
+
2. Using a two token approach in the form of `api_access_key` (which is used for database lookups) and `api_secret_token` (which is compared securely).
|
247
|
+
|
248
|
+
Any implementation of token authentication will likely need to support multiple tokens per account (i.e. a Tokens model). This also allows the user to directly revoke keys.
|
249
|
+
|
250
|
+
In the case of API access, storing a digest of the token is not practical. Bcrypt digesting is slow and would add a significant amount of overhead if used on every request (on average 90ms with the default 10 stretches).
|
251
|
+
|
252
|
+
Additionally, storing only the digest means that a user cannot login to see their API tokens. They would need to be regenerated. This might be considered a feature.
|
253
|
+
|
254
|
+
## What's missing
|
255
|
+
|
256
|
+
There is a significant amount of functionality that is currently unimplemented:
|
257
|
+
|
258
|
+
* Use Bcrypt and token digests instead of storing actual tokens in the database (defense in depth).
|
259
|
+
* Full name option (instead of first name and last name)
|
260
|
+
* Notification for changes to account (security settings changed)
|
261
|
+
* Ability to re-auth for sensitive changes (available for the current session only)
|
262
|
+
* API token support
|
263
|
+
* OAuth2 client support (but not logging in?) in the form of Facebook support, Twitter support, Google support
|
264
|
+
* OAuth2 server support
|
265
|
+
* One time password support completed
|
266
|
+
* Add Authy or Google Authenticator support
|
267
|
+
* Avatars (possibly this should be within uploadkit)
|
268
|
+
* User session tracking and revoking
|
269
|
+
* Audit logs
|
270
|
+
* No internationalization (i18n)
|
271
|
+
* JavaScript validation for username and email availability and password complexity
|
149
272
|
|
150
273
|
## Testing
|
151
274
|
|
152
|
-
The files generated using the installer include specs. To test these you should be
|
153
|
-
able to:
|
275
|
+
The files generated using the installer include specs. To test these you should be able to:
|
154
276
|
|
155
277
|
$ bundle install
|
156
278
|
|
@@ -158,21 +280,9 @@ Then run the default task:
|
|
158
280
|
|
159
281
|
$ rake
|
160
282
|
|
161
|
-
This will run the specs, which by default will generate a new Rails application,
|
162
|
-
run the installer, and execute the specs in the context of that temporary
|
163
|
-
application.
|
164
|
-
|
165
|
-
The specs that are generated utilize a generous amount of mocking and stubbing in
|
166
|
-
an attempt to keep them fast. However, they use vanilla `rspec-rails`, meaning
|
167
|
-
they are not using mocha. The two caveats are shoulda-matchers and FactoryGirl which
|
168
|
-
are required. It is pretty easy to remove these dependencies, it just turned out
|
169
|
-
that more people were using them than not.
|
170
|
-
|
171
|
-
## TODO
|
283
|
+
This will run the specs, which by default will generate a new Rails application, run the installer, and execute the specs in the context of that temporary application.
|
172
284
|
|
173
|
-
|
174
|
-
* Add avatar support (maybe that should be uploadkit)
|
175
|
-
* Add full name option (instead of first name and last name)name
|
285
|
+
The specs that are generated utilize a generous amount of mocking and stubbing in an attempt to keep them fast. However, they use vanilla `rspec-rails`, meaning they are not using mocha. The two caveats are shoulda-matchers and FactoryGirl which are required. It is pretty easy to remove these dependencies, it just turned out that more people were using them than not.
|
176
286
|
|
177
287
|
## Contributing
|
178
288
|
|
data/Rakefile
CHANGED
@@ -16,6 +16,11 @@ namespace :spec do
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
+
# When using sed to replace in place, don't rely on -i for POSIX compatibility
|
20
|
+
def sed(command, filename)
|
21
|
+
system "sed '#{command}' #{filename} > #{filename}.tmp && mv #{filename}.tmp #{filename}"
|
22
|
+
end
|
23
|
+
|
19
24
|
namespace :generator do
|
20
25
|
desc "Cleans up the sample app before running the generator"
|
21
26
|
task :cleanup do
|
@@ -37,6 +42,9 @@ namespace :generator do
|
|
37
42
|
system "cd spec/tmp/sample && bundle install"
|
38
43
|
system "cd spec/tmp/sample && rails g rspec:install"
|
39
44
|
|
45
|
+
# Open up the root route for specs
|
46
|
+
sed("s/# root/root/", "spec/tmp/sample/config/routes.rb")
|
47
|
+
|
40
48
|
# Make a thing
|
41
49
|
system "cd spec/tmp/sample && rails g scaffold thing name:string mood:string"
|
42
50
|
end
|
data/lib/authkit/version.rb
CHANGED
@@ -70,9 +70,6 @@ module Authkit
|
|
70
70
|
|
71
71
|
insert_at_end_of_file "config/initializers/filter_parameter_logging.rb", "config/initializers/filter_parameter_logging.rb"
|
72
72
|
|
73
|
-
# Need a temp root
|
74
|
-
route "root 'welcome#index'"
|
75
|
-
|
76
73
|
# Setup the routes
|
77
74
|
route "get '/email/confirm/:token', to: 'email_confirmation#show', as: :confirm"
|
78
75
|
|
@@ -4,19 +4,22 @@
|
|
4
4
|
helper_method :logged_in?, :current_user
|
5
5
|
|
6
6
|
# It is very unlikely that this exception will be created under normal
|
7
|
-
# circumstances. Unique validations are handled in Rails, but they are
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
7
|
+
# circumstances. Unique validations are handled in Rails, but they are also
|
8
|
+
# enforced at the database level to guarantee data integrity. In certain
|
9
|
+
# cases (double-clicking a save link, multiple distributed servers) it is
|
10
|
+
# possible to get past the Rails validation in which case the database throws
|
11
|
+
# an exception.
|
12
12
|
rescue_from ActiveRecord::RecordNotUnique, with: :record_not_unique
|
13
13
|
|
14
14
|
protected
|
15
15
|
|
16
|
+
# The user is fetched using id or remember token but these come from a
|
17
|
+
# verified cookie (verified using secure compare) so these database calls do
|
18
|
+
# not need to protect against timing attacks.
|
16
19
|
def current_user
|
17
20
|
return @current_user if defined?(@current_user)
|
18
21
|
@current_user ||= User.where(id: session[:user_id]).first if session[:user_id]
|
19
|
-
|
22
|
+
set_current_user_from_remember_token unless @current_user
|
20
23
|
session[:user_id] = @current_user.id if @current_user
|
21
24
|
session[:time_zone] = @current_user.time_zone if @current_user
|
22
25
|
set_time_zone
|
@@ -36,16 +39,12 @@
|
|
36
39
|
deny_user(nil, login_path) unless logged_in?
|
37
40
|
end
|
38
41
|
|
39
|
-
def
|
40
|
-
deny_user("Invalid token", root_path) unless @user = User.user_from_token(params[:token])
|
41
|
-
end
|
42
|
-
|
43
|
-
def login(user)
|
42
|
+
def login(user, remember=false)
|
44
43
|
reset_session
|
45
44
|
@current_user = user
|
46
45
|
current_user.track_sign_in(request.remote_ip) if allow_tracking?
|
47
|
-
current_user.
|
48
|
-
set_remember_cookie
|
46
|
+
current_user.set_remember_token if remember
|
47
|
+
set_remember_cookie if remember
|
49
48
|
session[:user_id] = current_user.id
|
50
49
|
session[:time_zone] = current_user.time_zone
|
51
50
|
set_time_zone
|
@@ -63,6 +62,14 @@
|
|
63
62
|
Time.zone = session[:time_zone] if session[:time_zone].present?
|
64
63
|
end
|
65
64
|
|
65
|
+
def set_current_user_from_remember_token
|
66
|
+
token = cookies.signed[:remember]
|
67
|
+
return if token.blank?
|
68
|
+
@current_user = User.where(remember_token: token).first
|
69
|
+
@current_user = nil if @current_user && @current_user.remember_token_expired?
|
70
|
+
@current_user
|
71
|
+
end
|
72
|
+
|
66
73
|
def set_remember_cookie
|
67
74
|
cookies.permanent.signed[:remember] = {
|
68
75
|
value: current_user.remember_token,
|
@@ -1,19 +1,21 @@
|
|
1
1
|
class EmailConfirmationController < ApplicationController
|
2
|
+
before_filter :require_login
|
2
3
|
before_filter :require_token
|
3
4
|
|
4
5
|
respond_to :html
|
5
6
|
|
6
7
|
def show
|
7
|
-
if
|
8
|
-
|
8
|
+
if current_user.email_confirmed
|
9
|
+
# Do not automatically log in the user
|
9
10
|
flash[:notice] = "Thanks for confirming your email address"
|
11
|
+
|
10
12
|
respond_to do |format|
|
11
13
|
format.json { head :no_content }
|
12
14
|
format.html { redirect_to root_path }
|
13
15
|
end
|
14
16
|
else
|
15
17
|
respond_to do |format|
|
16
|
-
format.json { render json: { status: 'error', errors:
|
18
|
+
format.json { render json: { status: 'error', errors: current_user.errors }.to_json, status: 422 }
|
17
19
|
format.html {
|
18
20
|
flash[:error] = "Could not confirm email address because it is already in use"
|
19
21
|
redirect_to root_path
|
@@ -21,5 +23,21 @@ class EmailConfirmationController < ApplicationController
|
|
21
23
|
end
|
22
24
|
end
|
23
25
|
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
|
29
|
+
# Confirmation tokens confirm an email address. It is conceivable
|
30
|
+
# that an attacker might choose an address out of their control and attempt to
|
31
|
+
# brute-force a confirmation. By default this gains the attacker nothing.
|
32
|
+
#
|
33
|
+
# It is possible to consider failed confirmation tokens failed attempts and
|
34
|
+
# lock the account.
|
35
|
+
def require_token
|
36
|
+
verifier = ActiveSupport::MessageVerifier.new(Rails.application.config.secret_key_base)
|
37
|
+
valid = params[:token].present?
|
38
|
+
valid = valid && verifier.send(:secure_compare, params[:token], current_user.confirmation_token)
|
39
|
+
valid = valid && !current_user.confirmation_token_expired?
|
40
|
+
deny_user("Invalid token", root_path) unless valid
|
41
|
+
end
|
24
42
|
end
|
25
43
|
|
@@ -1,4 +1,6 @@
|
|
1
1
|
class PasswordChangeController < ApplicationController
|
2
|
+
before_filter :require_no_user
|
3
|
+
before_filter :require_email_user
|
2
4
|
before_filter :require_token
|
3
5
|
|
4
6
|
def show
|
@@ -9,21 +11,51 @@ class PasswordChangeController < ApplicationController
|
|
9
11
|
end
|
10
12
|
|
11
13
|
def create
|
12
|
-
if
|
13
|
-
|
14
|
-
|
14
|
+
if email_user.change_password(params[:password], params[:password_confirmation])
|
15
|
+
# Do not automatically log in the user
|
15
16
|
respond_to do |format|
|
16
17
|
format.json { head :no_content }
|
17
18
|
format.html {
|
18
19
|
flash.now[:notice] = "Password updated successfully"
|
19
|
-
redirect_to(
|
20
|
+
redirect_to(login_path)
|
20
21
|
}
|
21
22
|
end
|
22
23
|
else
|
23
24
|
respond_to do |format|
|
24
|
-
format.json { render json: { status: 'error', errors:
|
25
|
+
format.json { render json: { status: 'error', errors: email_user.errors }.to_json, status: 422 }
|
25
26
|
format.html { render :show }
|
26
27
|
end
|
27
28
|
end
|
28
29
|
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
# Any existing user should be logged out to prevent session leakage
|
34
|
+
def require_no_user
|
35
|
+
logout
|
36
|
+
end
|
37
|
+
|
38
|
+
# The token is paired with an email parameter so that the user can be
|
39
|
+
# found in the database. Once found the tokens can be securely compared
|
40
|
+
# to prevent timing attacks. The email address is chosen over the id
|
41
|
+
# because the reset was generated using the email address and thus is
|
42
|
+
# already known. Using the id would increase information leakage.
|
43
|
+
def require_email_user
|
44
|
+
deny_user("Invalid email address", root_path) if params[:email].blank? || email_user.blank?
|
45
|
+
end
|
46
|
+
|
47
|
+
def email_user
|
48
|
+
return @user if defined?(@user)
|
49
|
+
@user = User.where(email: params[:email]).first || raise(ActiveRecord::RecordNotFound)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Reset password tokens expire after 1 day
|
53
|
+
def require_token
|
54
|
+
verifier = ActiveSupport::MessageVerifier.new(Rails.application.config.secret_key_base)
|
55
|
+
valid = params[:token].present?
|
56
|
+
valid = valid && verifier.send(:secure_compare, params[:token], email_user.reset_password_token)
|
57
|
+
valid = valid && !email_user.reset_password_token_expired?
|
58
|
+
deny_user("Invalid token", root_path) unless valid
|
59
|
+
end
|
60
|
+
|
29
61
|
end
|
@@ -4,8 +4,10 @@ class SessionsController < ApplicationController
|
|
4
4
|
end
|
5
5
|
|
6
6
|
def create
|
7
|
+
remember = params[:remember_me] == "1"
|
8
|
+
|
7
9
|
if user && user.authenticate(params[:password])
|
8
|
-
login(user)
|
10
|
+
login(user, remember)
|
9
11
|
respond_to do |format|
|
10
12
|
format.json { head :no_content }
|
11
13
|
format.html { redirect_back_or_default }
|