quo_vadis 2.1.11 → 2.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -0
  3. data/README.md +85 -100
  4. data/app/controllers/quo_vadis/confirmations_controller.rb +26 -61
  5. data/app/controllers/quo_vadis/password_resets_controller.rb +64 -32
  6. data/app/controllers/quo_vadis/sessions_controller.rb +0 -5
  7. data/app/mailers/quo_vadis/mailer.rb +2 -2
  8. data/app/models/quo_vadis/account.rb +32 -0
  9. data/app/models/quo_vadis/password.rb +6 -1
  10. data/app/views/quo_vadis/confirmations/new.html.erb +19 -7
  11. data/app/views/quo_vadis/mailer/account_confirmation.text.erb +2 -3
  12. data/app/views/quo_vadis/mailer/reset_password.text.erb +2 -2
  13. data/app/views/quo_vadis/password_resets/edit.html.erb +13 -1
  14. data/app/views/quo_vadis/password_resets/new.html.erb +2 -2
  15. data/app/views/quo_vadis/sessions/new.html.erb +1 -1
  16. data/config/locales/quo_vadis.en.yml +9 -7
  17. data/config/routes.rb +4 -12
  18. data/lib/quo_vadis/controller.rb +21 -11
  19. data/lib/quo_vadis/defaults.rb +2 -2
  20. data/lib/quo_vadis/version.rb +1 -1
  21. data/lib/quo_vadis.rb +2 -2
  22. data/test/README.md +6 -0
  23. data/test/dummy/app/controllers/articles_controller.rb +1 -0
  24. data/test/dummy/app/controllers/sign_ups_controller.rb +2 -11
  25. data/test/fixtures/quo_vadis/mailer/account_confirmation.text +2 -3
  26. data/test/fixtures/quo_vadis/mailer/reset_password.text +2 -2
  27. data/test/integration/account_confirmation_test.rb +42 -86
  28. data/test/integration/controller_test.rb +8 -8
  29. data/test/integration/logging_test.rb +31 -7
  30. data/test/integration/password_login_test.rb +1 -1
  31. data/test/integration/password_reset_test.rb +89 -54
  32. data/test/integration/sessions_test.rb +16 -0
  33. data/test/mailers/mailer_test.rb +2 -2
  34. data/test/models/account_test.rb +48 -0
  35. data/test/models/session_test.rb +4 -0
  36. metadata +4 -11
  37. data/app/models/quo_vadis/account_confirmation_token.rb +0 -17
  38. data/app/models/quo_vadis/password_reset_token.rb +0 -17
  39. data/app/models/quo_vadis/token.rb +0 -42
  40. data/app/views/quo_vadis/confirmations/edit.html.erb +0 -10
  41. data/app/views/quo_vadis/confirmations/edit_email.html.erb +0 -14
  42. data/app/views/quo_vadis/confirmations/index.html.erb +0 -14
  43. data/app/views/quo_vadis/password_resets/index.html.erb +0 -5
  44. data/test/models/token_test.rb +0 -70
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 75eaf7f87a444de55a110ed4dd37d5fa14ad1c0d2d51bae7b0e3eb549681578b
4
- data.tar.gz: c79cbcd1d629bcfb6bd4b95f6b77e95617173bfb5f649d73c493f26f53635c2b
3
+ metadata.gz: f55b91cf69117006b0dce03a6b0d38423b587bf460c05e24984735de6a4cf8a3
4
+ data.tar.gz: 6a309a19fd35aaacbf1ec8ff5df7544ab980cecfad30664f6f0abb3778eb1d37
5
5
  SHA512:
6
- metadata.gz: 0c79e6e5844e6ff1292d49beabf8728a086c7460726244b9452c5d77d1d1bc32b1aca11217345e0da8ce89e6c15c8bbb2baece066033e796bce119b7757f3c09
7
- data.tar.gz: 073ba2aae8cd45f600ffa2d58e8b5e6ae0889a7fa976dd58447132c11993fbd5f0ba16c419eba7bcb55fedcdfb21ec1f2f01a9c94c532f7c1460588958dcae0b
6
+ metadata.gz: 70bb7a3fc80f540889eb0aff8416d75759e4b2564d17d8605ad368ea8eb53e803bda66b91dd8ba0192ee061148a62fe034d164becfd4b41876100c740edfe008
7
+ data.tar.gz: cfaa08ccde542121a46a361dbdbff4a56fd6d77b4295cc1f5e0232b7c94e1514a7a422a7138add4d9e8f964a451e0038fa065f8e49a8d79a5594e180c0bb63a2
data/CHANGELOG.md CHANGED
@@ -4,6 +4,22 @@
4
4
  ## HEAD
5
5
 
6
6
 
7
+ ## 2.2.1 (1 August 2023)
8
+
9
+ * Do not clear application session data on logout.
10
+ * Use 'email' type for email input fields.
11
+ * Document how to log out.
12
+
13
+
14
+ ## 2.2.0 (17 April 2023)
15
+
16
+ * Improve the readme with internal links and more section headings.
17
+ * Rename `password_reset_token_lifetime` to `password_reset_otp_lifetime`.
18
+ * Use OTP instead of link for password reset.
19
+ * Rename `account_confirmation_token_lifetime` to `account_confirmation_otp_lifetime`.
20
+ * Use OTP instead of link for account confirmation.
21
+
22
+
7
23
  ## 2.1.11 (14 September 2022)
8
24
 
9
25
  * Introduce common controller superclass.
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Quo Vadis
2
2
 
3
- Multifactor authentication for your Rails 6 or Rails 7 app.
3
+ Multifactor authentication for your Rails app (Rails 7 and Rails 6).
4
4
 
5
5
  Designed in accordance with the [OWASP Application Security Verification Standard](https://owasp.org/www-project-application-security-verification-standard/) and relevant [OWASP Cheatsheets](https://cheatsheetseries.owasp.org).
6
6
 
@@ -23,8 +23,8 @@ Simple to integrate into your application. The main task is customising the exa
23
23
  - Two-factor authentication (2FA) by TOTP with recovery codes as a backup factor. Can be optional or mandatory.
24
24
  - Change password.
25
25
  - Reset password.
26
- - Account confirmation (optional).
27
- - Tokens (account confirmation, password reset), TOTPs, and recovery codes are all one-time-only.
26
+ - Account confirmation (a.k.a. email verification) (optional).
27
+ - OTPs (account confirmation, password reset), TOTPs, and recovery codes are all one-time-only.
28
28
  - Sessions expired after lifetime or idle time exceeded.
29
29
  - Session replaced after any privilege change.
30
30
  - View active sessions, log out of any of them.
@@ -86,7 +86,7 @@ end
86
86
 
87
87
  You can create and update your models as before. When you want to set a password for the first time, just include `:password` and, optionally, `:password_confirmation` in the attributes to `#create` or `#update`.
88
88
 
89
- If you want to change an existing password, use the Change Password feature (see below). If you update a model (that already has a password) with a `:password` attribute, it will raise a `QuoVadis::PasswordExistsError`.
89
+ If you want to change an existing password, use the [Change Password](#change-password) feature. If you update a model (that already has a password) with a `:password` attribute, it will raise a `QuoVadis::PasswordExistsError`.
90
90
 
91
91
  The minimum password length is configured by `QuoVadis.password_minimum_length` (12 by default).
92
92
 
@@ -95,7 +95,7 @@ The minimum password length is configured by `QuoVadis.password_minimum_length`
95
95
 
96
96
  You can use these methods in your controllers.
97
97
 
98
- __`require_password_authentication`__
98
+ #### `require_password_authentication`
99
99
 
100
100
  Use this to restrict actions to password-authenticated users. It is aliased to `:require_authentication` for convenience.
101
101
 
@@ -105,7 +105,7 @@ class FoosController < ApplicationController
105
105
  end
106
106
  ```
107
107
 
108
- __`require_two_factor_authentication`__
108
+ #### `require_two_factor_authentication`
109
109
 
110
110
  Use this to restrict actions to users authenticated with both a password and a second factor. (You do not need to use `:require_password_authentication` for these actions.)
111
111
 
@@ -115,21 +115,17 @@ class BarsController < ApplicationController
115
115
  end
116
116
  ```
117
117
 
118
- __`login(model, browser_session = true)`__
118
+ #### `login(model, browser_session = true, metadata: {})`
119
119
 
120
- To log in a user who has authenticated with a password, call `#login(model, browser_session = true, metadata: {})`. For the `browser_session` argument, optionally pass `true` to log in for the duration of the browser session, or `false` to log in for `QuoVadis.session_lifetime` (which could be the browser session anyway). Any metadata are stored in the log entry for the login.
120
+ Use this to log in a user who has authenticated with a password. For the optional `browser_session` argument, pass `true` to log in for the duration of the browser session, or `false` to log in for `QuoVadis.session_lifetime` (which could be the browser session anyway). Any metadata are stored in the log entry for the login.
121
121
 
122
- __`request_confirmation(model)`__
123
-
124
- This is used to sent an account confirmation email to the user. See the Account Confirmation feature below for details.
125
-
126
- __`authenticated_model`__
122
+ #### `authenticated_model`
127
123
 
128
124
  Call this to get the authenticated user. Feel free to alias this to `:current_user` or set it into an `ActiveSupport::CurrentAttributes` class.
129
125
 
130
126
  Available in controllers and views.
131
127
 
132
- __`logged_in?`__
128
+ #### `logged_in?`
133
129
 
134
130
  Call this to find out whether a user has authenticated with a password.
135
131
 
@@ -175,7 +171,7 @@ Your new user sign-up form ([example](https://github.com/airblade/quo_vadis/blob
175
171
  - a field for their identifier;
176
172
  - an `:email` field if the identifier is not their email.
177
173
 
178
- In your controller, use the `#login` method to log in your new user. The optional second argument sets the length of the session (defaults to the browser session) - see the description above in the Controllers section).
174
+ In your controller, use the [`#login`](#loginmodel-browser_session--true-metadata-) method to log in your new user. The optional second argument specifies for how long the user should be logged in, and any metadata you supply is logged in the audit log.
179
175
 
180
176
  After logging in the user, redirect them wherever you like. You can use `qv.path_after_signup` which resolves to the first of these routes that exists: `:after_signup`, `:after_login`, the root route.
181
177
 
@@ -207,60 +203,19 @@ get '/dashboard', as: 'after_login'
207
203
 
208
204
  ### Sign up with account confirmation
209
205
 
210
- Here's the workflow:
211
-
212
- 1. [Sign up page] The user fills in their details.
213
- 2. [Your controller] Your code tells QuoVadis to email the user a confirmation link. The link is valid for `QuoVadis.account_confirmation_token_lifetime`.
214
- 3. [The email] The user clicks the link.
215
- 4. [Account-confirmation confirmation page] The user clicks a button to confirm their account. (This step is to prevent any link prefetching in the user's mail client from confirming them unintentionally.)
216
- 5. QuoVadis confirms the user's account and logs them in.
217
-
218
- Your new user sign-up form ([example](https://github.com/airblade/quo_vadis/blob/master/test/dummy/app/views/sign_ups/new.html.erb)) must include:
219
-
220
- - a `:password` field;
221
- - optionally a `:password_confirmation` field;
222
- - a field for their identifier;
223
- - an `:email` field if the identifier is not their email.
224
-
225
- In your controller, call `#request_confirmation`:
226
-
227
- ```ruby
228
- class UsersController < ApplicationController
229
- def create
230
- @user = User.new user_params
231
- if @user.save
232
- request_confirmation @user
233
- redirect_to quo_vadis.confirmations_path # a page where you advise the user to check their email
234
- else
235
- # ...
236
- end
237
- end
238
-
239
- private
240
-
241
- def user_params
242
- params.require(:user).permit(:name, :email, :password, :password_confirmation)
243
- end
244
- end
245
- ```
206
+ Follow the steps above for sign-up.
246
207
 
247
- QuoVadis will send the user an email with a link. Write the email view ([example](https://github.com/airblade/quo_vadis/blob/master/app/views/quo_vadis/mailer/account_confirmation.text.erb)). It must be in `app/views/quo_vadis/mailer/account_confirmation.{text,html}.erb` and output the `@url` variable.
208
+ After you have logged in the user and redirected them (to any page which requires being logged in), QuoVadis detects that they need to confirm their account. QuoVadis emails them a 6-digit confirmation code and redirects them to the confirmation page where they can enter that code.
248
209
 
249
- See the Configuration section below for how to set QuoVadis's emails' from addresses, headers, etc.
210
+ The confirmation code is valid for `QuoVadis.account_confirmation_otp_lifetime`.
250
211
 
251
- Now write the page to where the user is redirected while they wait for the email ([example](https://github.com/airblade/quo_vadis/blob/master/app/views/quo_vadis/confirmations/index.html.erb)). It must be in `app/views/quo_vadis/confirmations/index.html.:format`.
212
+ Once the user has confirmed their account, they will be redirected to `qv.path_after_signup` which resolves to the first of these routes that exists: `:after_signup`, `:after_login`, the root route. Add whichever works best for you.
252
213
 
253
- On that page you can show the user the address the email was sent to, enable them to update their email address if they make a mistake on the sign-up form, and provide a button to resend another email directly. If the sign-up occurred in a different browser session, you can instead link to `new_confirmation_path` where the user can request another email if need be.
214
+ You need to write the email view ([example](https://github.com/airblade/quo_vadis/blob/master/app/views/quo_vadis/mailer/account_confirmation.text.erb)). It must be in `app/views/quo_vadis/mailer/account_confirmation.{text,html}.erb` and output the `@otp` variable. See the [Configuration](#configuration) section for how to set QuoVadis's emails' from addresses, headers, etc.
254
215
 
255
- Next, write the page to which the link in the email points ([example](https://github.com/airblade/quo_vadis/blob/master/app/views/quo_vadis/confirmations/edit.html.erb)). It must be in `app/views/quo_vadis/confirmations/edit.html.:format`.
216
+ Now write the confirmation page where the user types in the confirmation code from the email ([example](https://github.com/airblade/quo_vadis/blob/master/app/views/quo_vadis/confirmations/new.html.erb)). It must be in `app/views/quo_vadis/confirmations/new.html.:format` and must POST the `otp` field to `confirm_path`. You can provide a button to send a new confirmation code (perhaps the original email didn't arrive, or the user didn't have time to act on it before it expired) – it should POST to `send_confirmation_path`.
256
217
 
257
- Next, write the page where the user can amend their email address if they made a mistake when signing up ([example](https://github.com/airblade/quo_vadis/blob/master/app/views/quo_vadis/confirmations/edit_email.html.erb)). It must be in `app/views/quo_vadis/confirmations/edit_email.html.:format`.
258
-
259
- Finally, write the page where people can put in their identifier (not their email, unless the identifier is email) again to request another confirmation email ([example](https://github.com/airblade/quo_vadis/blob/master/app/views/quo_vadis/confirmations/new.html.erb)). It must be in `app/views/quo_vadis/confirmations/new.html.:format`.
260
-
261
- After the user has confirmed their account, they will be logged in and redirected to `qv.path_after_signup` which resolves to the first of these routes that exists: `:after_signup`, `:after_login`, the root route.
262
-
263
- So add whichever works best for you.
218
+ If the user closes their browser after signing up but before they have confirmed their account, when they next access a page which requires being logged in they will be sent a new confirmation code and redirected to the confirmation page, as if they had just signed up.
264
219
 
265
220
 
266
221
  ### Login
@@ -283,6 +238,40 @@ After authenticating the user will be redirected to the first of these that exis
283
238
  - your root route.
284
239
 
285
240
 
241
+ ### Logout
242
+
243
+ Send a DELETE request to `quo_vadis.logout_path`. For example:
244
+
245
+ ```ruby
246
+ button_to 'Log out', quo_vadis.logout_path, method: :delete
247
+ ```
248
+
249
+ Note you are responsible for removing any application session data you want removed. To do so, subclass `QuoVadis::SessionsController` and override the `destroy` method:
250
+
251
+ ````ruby
252
+ # app/controllers/custom_sessions_controller.rb
253
+ class CustomSessionsController < QuoVadis::SessionsController
254
+ def destroy
255
+ reset_session
256
+ super
257
+ end
258
+ end
259
+ ```
260
+
261
+ Add a route:
262
+
263
+ ```ruby
264
+ # config/routes.rb
265
+ delete 'logout', to: 'custom_sessions#destroy'
266
+ ```
267
+
268
+ And then point your log out button at your custom action:
269
+
270
+ ```ruby
271
+ button_to 'Log out', main_app.logout_path, method: :delete
272
+ ```
273
+
274
+
286
275
  ### Two-factor authentication (2FA) or Two-step verification (2SV)
287
276
 
288
277
  If you do not want 2FA at all, set `QuoVadis.two_factor_authentication_mandatory false` in your configuration and skip the rest of this section.
@@ -334,31 +323,27 @@ A successful password change logs out any other sessions the user has (e.g. on o
334
323
 
335
324
  ### Reset password
336
325
 
337
- The user can reset their password if they lose it. The flow is:
326
+ The user can reset their password if they lose it and cannot log in. The flow is:
338
327
 
339
328
  1. [Request password-reset page] User enters their identifier (not their email unless the identifier is email).
340
- 2. QuoVadis emails the user a link. The link is valid for `QuoVadis.password_reset_token_lifetime`.
341
- 3. [The email] The user clicks the link.
342
- 4. [Password-reset confirmation page] The user enters their new password and clicks a button.
329
+ 2. QuoVadis emails the user a 6-digit reset code, which is valid for `QuoVadis.password_reset_otp_lifetime`, and redirects to the password-reset page.
330
+ 3. [The email] The user reads the code.
331
+ 4. [Password-reset page] The user enters the 6-digt code and their new password and clicks the save button.
343
332
  5. QuoVadis sets the user's password and logs them in.
344
333
 
345
- First, write the page where the user requests a password-reset ([example](https://github.com/airblade/quo_vadis/blob/master/app/views/quo_vadis/password_resets/new.html.erb)). It must be in `app/views/quo_vadis/password_resets/new.html.:format`. Note it must capture the user's identifier (not email, unless the identifier is email).
346
-
347
- See the Configuration section below for how to set QuoVadis's emails' from addresses, headers, etc.
334
+ First, write the page where the user requests a password-reset ([example](https://github.com/airblade/quo_vadis/blob/master/app/views/quo_vadis/password_resets/new.html.erb)). It must be in `app/views/quo_vadis/password_resets/new.html.:format`. It must POST the user's identifier (not email, unless the identifier is email) to `password_reset_path`.
348
335
 
349
- Now write the page to where the user is redirected while they wait for the email ([example](https://github.com/airblade/quo_vadis/blob/master/app/views/quo_vadis/password_resets/index.html.erb)). It must be in `app/views/quo_vadis/password_resets/index.html.:format`.
336
+ Now write the email view ([example](https://github.com/airblade/quo_vadis/blob/master/app/views/quo_vadis/mailer/reset_password.text.erb)). It must be in `app/views/quo_vadis/mailer/reset_password.{text,html}.erb` and output the `@otp` variable. See the [Configuration](#configuration) section for how to set QuoVadis's emails' from addresses, headers, etc.
350
337
 
351
- It's a good idea for that page to link to `new_password_reset_path` where the user can request another email if need be.
352
-
353
- Now write the email view ([example](https://github.com/airblade/quo_vadis/blob/master/app/views/quo_vadis/mailer/reset_password.text.erb)). It must be in `app/views/quo_vadis/mailer/reset_password.{text,html}.erb` and output the `@url` variable.
354
-
355
- Next, write the page to which the link in the email points ([example](https://github.com/airblade/quo_vadis/blob/master/app/views/quo_vadis/password_resets/edit.html.erb)). It must be in `app/views/quo_vadis/password_resets/edit.html.:format`.
338
+ Now write the page where the user types in the reset code from the email and their new password ([example](https://github.com/airblade/quo_vadis/blob/master/app/views/quo_vadis/password_resets/edit.html.erb)). It must be in `app/views/quo_vadis/password_resets/edit.html.:format` and must PUT the `otp`, `password`, and `password_confirmation` fields to `password_reset_path`.
356
339
 
357
340
  After the user has reset their password, they will be logged in and redirected to the first of these that exists:
358
341
 
359
342
  - a route named `:after_login`;
360
343
  - your root route.
361
344
 
345
+ When the user resets their password, they are logged out of any other sessions they may have, for example on other devices.
346
+
362
347
 
363
348
  ### Sessions
364
349
 
@@ -411,9 +396,9 @@ QuoVadis.configure do
411
396
  session_lifetime :session
412
397
  session_lifetime_extend_to_end_of_day false
413
398
  session_idle_timeout :lifetime
414
- password_reset_token_lifetime 10.minutes
399
+ password_reset_otp_lifetime 10.minutes
415
400
  accounts_require_confirmation false
416
- account_confirmation_token_lifetime 10.minutes
401
+ account_confirmation_otp_lifetime 10.minutes
417
402
  mail_headers ({ from: 'Example App <support@example.com>' })
418
403
  enqueue_transactional_emails true
419
404
  app_name Rails.app_class.to_s.deconstantize # for the TOTP QR code
@@ -426,75 +411,75 @@ You can override any of it with a similarly structured file in `config/initializ
426
411
 
427
412
  Here are the options in detail:
428
413
 
429
- __`password_minimum_length`__ (integer)
414
+ #### `password_minimum_length` (integer)
430
415
 
431
416
  The minimum number of characters for a password.
432
417
 
433
- __`mask_ips`__ (boolean)
418
+ #### `mask_ips` (boolean)
434
419
 
435
420
  Whether to mask the IP address in the sessions list and the audit trail.
436
421
 
437
422
  Masking means setting the last octet (IPv4) or the last 80 bits (IPv6) to 0.
438
423
 
439
- __`cookie_name`__ (string)
424
+ #### `cookie_name` (string)
440
425
 
441
426
  The name of the cookie QuoVadis uses to store the session identifier. The `__Host-` prefix is [recommended](https://developer.mozilla.org/en-US/docs/Web/API/document/cookie) in an SSL environment (but cannot be used in a non-SSL environment).
442
427
 
443
- __`session_lifetime`__ (`:session` | `ActiveSupport::Duration` | integer)
428
+ #### `session_lifetime` (`:session` | `ActiveSupport::Duration` | integer)
444
429
 
445
430
  The lifetime of a logged-in session. Use `:session` for the browser session, or a `Duration` or number of seconds.
446
431
 
447
- __`session_lifetime_extend_to_end_of_day`__ (boolean)
432
+ #### `session_lifetime_extend_to_end_of_day` (boolean)
448
433
 
449
434
  Whether to extend the session's lifetime to the end of the day it will expire on.
450
435
 
451
436
  Set `true` to reduce the chance of a user being logged out while actively using your application.
452
437
 
453
- __`session_idle_timeout`__ (`:lifetime` | `ActiveSupport::Duration` | integer)
438
+ #### `session_idle_timeout` (`:lifetime` | `ActiveSupport::Duration` | integer)
454
439
 
455
440
  The logged-in session is expired if the user isn't seen for this `Duration` or number of seconds. Use `:lifetime` to set the idle timeout to the session's lifetime (i.e. to turn off the idle timeout).
456
441
 
457
- __`password_reset_token_lifetime`__ (`ActiveSupport::Duration` | integer)
442
+ #### `password_reset_otp_lifetime` (`ActiveSupport::Duration` | integer)
458
443
 
459
- The `Duration` or number of seconds for which a password-reset token is valid.
444
+ The `Duration` or number of seconds for which a password-reset code is valid.
460
445
 
461
- __`accounts_require_confirmation`__ (boolean)
446
+ #### `accounts_require_confirmation` (boolean)
462
447
 
463
448
  Whether new users must confirm their account before they can log in.
464
449
 
465
- __`account_confirmation_token_lifetime`__ (`ActiveSupport::Duration` | integer)
450
+ #### `account_confirmation_otp_lifetime` (`ActiveSupport::Duration` | integer)
466
451
 
467
- The `Duration` or number of seconds for which an account-confirmation token is valid.
452
+ The `Duration` or number of seconds for which an account-confirmation code is valid.
468
453
 
469
- __`mailer_superclass`__ (string)
454
+ #### `mailer_superclass` (string)
470
455
 
471
456
  The class from which QuoVadis's mailer inherits.
472
457
 
473
- __`mail_headers`__ (hash)
458
+ #### `mail_headers` (hash)
474
459
 
475
460
  Mail headers which QuoVadis' emails should have.
476
461
 
477
- __`enqueue_transactional_emails`__ (boolean)
462
+ #### `enqueue_transactional_emails` (boolean)
478
463
 
479
464
  Set `true` if account-confirmation and password-reset emails should be queued for later delivery (`#deliver_later`) or `false` if they should be sent inline (`#deliver_now`).
480
465
 
481
- __`app_name`__ (string)
466
+ #### `app_name` (string)
482
467
 
483
468
  Used in the provisioning URI for the TOTP QR code.
484
469
 
485
- __`two_factor_authentication_mandatory`__ (boolean)
470
+ #### `two_factor_authentication_mandatory` (boolean)
486
471
 
487
472
  Whether users must set up and use a second authentication factor.
488
473
 
489
- __`mount_point`__ (string)
474
+ #### `mount_point` (string)
490
475
 
491
476
  The path prefix for QuoVadis's routes.
492
477
 
493
478
  For example, the default login path is at `/login`. If you set `mount_point` to `/auth`, the login path would be `/auth/login`.
494
479
 
495
- #### Rails configuration
480
+ ### Rails configuration
496
481
 
497
- __Mailer URLs__
482
+ #### Mailer URLs
498
483
 
499
484
  You must also configure the mailer host so URLs are generated correctly in emails:
500
485
 
@@ -502,7 +487,7 @@ You must also configure the mailer host so URLs are generated correctly in email
502
487
  config.action_mailer.default_url_options: { host: 'example.com' }
503
488
  ```
504
489
 
505
- __Layouts__
490
+ #### Layouts
506
491
 
507
492
  You can specify QuoVadis's controllers' layouts in a `#to_prepare` block in your application configuration. For example:
508
493
 
@@ -517,7 +502,7 @@ module YourApp
517
502
  end
518
503
  ```
519
504
 
520
- __Routes__
505
+ #### Routes
521
506
 
522
507
  You can set up your post-signup, post-authentication, and post-password-change routes. If you don't, you must have a root route. For example:
523
508
 
@@ -539,6 +524,6 @@ If you don't want a specific flash message at all, give the key an empty value i
539
524
 
540
525
  ## Intellectual Property
541
526
 
542
- Copyright 2011-2022 Andrew Stewart (boss@airbladesoftware.com).
527
+ Copyright Andrew Stewart (boss@airbladesoftware.com).
543
528
 
544
529
  Released under the MIT licence.
@@ -3,99 +3,64 @@
3
3
  module QuoVadis
4
4
  class ConfirmationsController < QuoVadisController
5
5
 
6
- # holding page
7
- def index
6
+ def new
8
7
  @account = find_pending_account_from_session
9
- end
10
8
 
11
-
12
- # form for requesting new confirmation email
13
- def new
9
+ unless @account
10
+ redirect_to qv.path_after_signup, alert: QuoVadis.translate('flash.confirmation.unknown')
11
+ end
14
12
  end
15
13
 
16
14
 
17
- # send new confirmation email form submits here
18
15
  def create
19
- account = QuoVadis.find_account_by_identifier_in_params params
16
+ @account = find_pending_account_from_session
20
17
 
21
- unless account
22
- redirect_to new_confirmation_path, alert: QuoVadis.translate('flash.confirmation.identifier') and return
18
+ unless @account
19
+ redirect_to qv.path_after_signup, alert: QuoVadis.translate('flash.confirmation.unknown')
20
+ return
23
21
  end
24
22
 
25
- request_confirmation account.model
26
- redirect_to confirmations_path
27
- end
23
+ expiry = session[:account_confirmation_expires_at]
28
24
 
29
-
30
- # emailed confirmation link points here
31
- def edit
32
- account = AccountConfirmationToken.find_account params[:token]
33
-
34
- unless account # expired or already confirmed
35
- redirect_to new_confirmation_path, alert: QuoVadis.translate('flash.confirmation.unknown') and return
25
+ if Time.current.to_i > expiry
26
+ redirect_to confirm_path, alert: QuoVadis.translate('flash.confirmation.expired')
27
+ return
36
28
  end
37
- end
38
29
 
30
+ confirmed = @account.confirm(params[:otp], expiry)
39
31
 
40
- # confirm the account (and login)
41
- def update
42
- account = AccountConfirmationToken.find_account params[:token]
43
-
44
- unless account # expired or already confirmed
45
- redirect_to new_confirmation_path, alert: QuoVadis.translate('flash.confirmation.unknown') and return
32
+ if !confirmed
33
+ redirect_to confirm_path, alert: QuoVadis.translate('flash.confirmation.invalid')
34
+ return
46
35
  end
47
36
 
48
- account.confirmed!
49
- qv.log account, Log::ACCOUNT_CONFIRMATION
37
+ qv.log @account, Log::ACCOUNT_CONFIRMATION
50
38
 
51
39
  session.delete :account_pending_confirmation
40
+ session.delete :account_confirmation_expires_at
52
41
 
53
- login account.model, true
54
42
  redirect_to qv.path_after_signup, notice: QuoVadis.translate('flash.confirmation.confirmed')
55
43
  end
56
44
 
57
45
 
58
- def edit_email
59
- account = find_pending_account_from_session
60
-
61
- unless account
62
- redirect_to confirmations_path, alert: QuoVadis.translate('flash.confirmation.unknown') and return
63
- end
64
-
65
- @email = account.model.email
66
- end
67
-
68
-
69
- def update_email
70
- account = find_pending_account_from_session
71
-
72
- unless account
73
- redirect_to confirmations_path, alert: QuoVadis.translate('flash.confirmation.unknown') and return
74
- end
75
-
76
- account.model.update email: params[:email]
77
-
78
- request_confirmation account.model
79
- redirect_to confirmations_path
80
- end
81
-
82
-
83
46
  def resend
84
- account = find_pending_account_from_session
47
+ @account = find_pending_account_from_session
85
48
 
86
- unless account
87
- redirect_to confirmations_path, alert: QuoVadis.translate('flash.confirmation.unknown') and return
49
+ unless @account
50
+ redirect_to qv.path_after_signup, alert: QuoVadis.translate('flash.confirmation.unknown')
88
51
  end
89
52
 
90
- request_confirmation account.model
91
- redirect_to confirmations_path
53
+ qv.request_confirmation @account.model
54
+ redirect_to confirm_path, notice: QuoVadis.translate('flash.confirmation.sent')
92
55
  end
93
56
 
94
57
 
95
58
  private
96
59
 
97
60
  def find_pending_account_from_session
98
- Account.find(session[:account_pending_confirmation]) if session[:account_pending_confirmation]
61
+ if session[:account_pending_confirmation]
62
+ Account.unconfirmed.find(session[:account_pending_confirmation])
63
+ end
99
64
  end
100
65
 
101
66
  end
@@ -3,66 +3,98 @@
3
3
  module QuoVadis
4
4
  class PasswordResetsController < QuoVadisController
5
5
 
6
- # holding page for after the create action
7
- def index
8
- end
9
-
10
-
6
+ # form where user enters their identifier
11
7
  def new
12
8
  end
13
9
 
14
10
 
11
+ # generate and email an otp
15
12
  def create
16
13
  account = QuoVadis.find_account_by_identifier_in_params params
17
14
 
18
- if account
19
- token = QuoVadis::PasswordResetToken.generate account
20
- QuoVadis.deliver :reset_password, {email: account.model.email, url: quo_vadis.password_reset_url(token)}
15
+ # The recommendation is to show the user the same message whether
16
+ # or not their account was found. This favours privacy over
17
+ # helpfulness and is the default.
18
+ #
19
+ # If you would prefer helpfulness over privacy -- perhaps the user
20
+ # simply typo'd their identifier -- set the `unknown` flash message
21
+ # to something helpful.
22
+ message_known = QuoVadis.translate('flash.password_reset.create')
23
+ message_unknown = QuoVadis.translate('flash.password_reset.unknown')
24
+
25
+ if message_known == message_unknown
26
+ flash[:notice] = message_known
27
+ elsif account
28
+ flash[:notice] = message_known
29
+ else
30
+ flash[:alert] = message_unknown
21
31
  end
22
32
 
23
- redirect_to password_resets_path, notice: QuoVadis.translate('flash.password_reset.create')
24
- end
33
+ if account
34
+ session[:account_resetting_password] = account.id
25
35
 
36
+ expiration = QuoVadis.password_reset_otp_lifetime.from_now.to_i
37
+ session[:password_reset_expires_at] = expiration
26
38
 
27
- # emailed password-reset link points here
28
- def edit
29
- account = PasswordResetToken.find_account params[:token]
39
+ otp = account.otp_for_password_reset(expiration)
30
40
 
31
- unless account # expired or password already changed
32
- redirect_to new_password_reset_path, alert: QuoVadis.translate('flash.password_reset.unknown') and return
41
+ QuoVadis.deliver :reset_password, {email: account.model.email, otp: otp}
33
42
  end
34
43
 
35
- # Ensure the flash notice set in the create action does not appear when the user clicks the
36
- # link in the email they were sent.
37
- flash.delete :notice
44
+ redirect_to edit_password_reset_path
45
+ end
46
+
38
47
 
48
+ # form for otp and new password
49
+ def edit
39
50
  @password = QuoVadis::Password.new
40
51
  end
41
52
 
42
53
 
43
- # really reset the password
54
+ # update password if otp and password are valid
44
55
  def update
45
- account = PasswordResetToken.find_account params[:token]
56
+ account = find_account_resetting_password_from_session
46
57
 
47
- unless account # expired or password already changed
48
- redirect_to new_password_reset_path, alert: QuoVadis.translate('flash.password_reset.unknown') and return
58
+ unless account
59
+ redirect_to new_password_reset_path
60
+ return
49
61
  end
50
62
 
51
- @password = account.password
52
- if @password.reset params[:password][:password], params[:password][:password_confirmation]
53
- # Logout account's sessions because password has changed.
54
- # Note model is not logged in here.
55
- @password.account.sessions.destroy_all
63
+ expiry = session[:password_reset_expires_at]
56
64
 
57
- qv.log @password.account, Log::PASSWORD_RESET
58
- QuoVadis.notify :password_reset_notification, email: @password.account.model.email
65
+ if Time.current.to_i > expiry
66
+ redirect_to new_password_reset_path, alert: QuoVadis.translate('flash.password_reset.expired')
67
+ return
68
+ end
59
69
 
60
- login @password.account.model, true
61
- redirect_to qv.path_after_authentication, notice: QuoVadis.translate('flash.password_reset.reset')
62
- else
70
+ unless account.verify_password_reset(params[:password][:otp], expiry)
71
+ redirect_to new_password_reset_path, alert: QuoVadis.translate('flash.password_reset.invalid')
72
+ return
73
+ end
74
+
75
+ @password = account.password
76
+ unless @password.reset(params[:password][:password], params[:password][:password_confirmation])
63
77
  render :edit, status: :unprocessable_entity
78
+ return
64
79
  end
80
+
81
+ session.delete :account_resetting_password
82
+ session.delete :password_reset_expires_at
83
+
84
+ qv.log account, Log::PASSWORD_RESET
85
+ QuoVadis.notify :password_reset_notification, email: account.model.email
86
+
87
+ login account.model, true
88
+
89
+ redirect_to qv.path_after_authentication, notice: QuoVadis.translate('flash.password_reset.reset')
65
90
  end
66
91
 
92
+ private
93
+
94
+ def find_account_resetting_password_from_session
95
+ if session[:account_resetting_password]
96
+ Account.find(session[:account_resetting_password])
97
+ end
98
+ end
67
99
  end
68
100
  end