quo_vadis 2.1.10 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/README.md +50 -99
  4. data/app/controllers/quo_vadis/confirmations_controller.rb +27 -62
  5. data/app/controllers/quo_vadis/logs_controller.rb +1 -1
  6. data/app/controllers/quo_vadis/password_resets_controller.rb +65 -33
  7. data/app/controllers/quo_vadis/passwords_controller.rb +1 -1
  8. data/app/controllers/quo_vadis/quo_vadis_controller.rb +6 -0
  9. data/app/controllers/quo_vadis/recovery_codes_controller.rb +1 -1
  10. data/app/controllers/quo_vadis/sessions_controller.rb +1 -6
  11. data/app/controllers/quo_vadis/totps_controller.rb +1 -1
  12. data/app/controllers/quo_vadis/twofas_controller.rb +1 -1
  13. data/app/mailers/quo_vadis/mailer.rb +2 -2
  14. data/app/models/quo_vadis/account.rb +32 -0
  15. data/app/models/quo_vadis/password.rb +6 -1
  16. data/app/views/quo_vadis/confirmations/new.html.erb +19 -7
  17. data/app/views/quo_vadis/mailer/account_confirmation.text.erb +2 -3
  18. data/app/views/quo_vadis/mailer/reset_password.text.erb +2 -2
  19. data/app/views/quo_vadis/password_resets/edit.html.erb +13 -1
  20. data/app/views/quo_vadis/password_resets/new.html.erb +1 -1
  21. data/config/locales/quo_vadis.en.yml +9 -7
  22. data/config/routes.rb +4 -12
  23. data/lib/quo_vadis/controller.rb +20 -10
  24. data/lib/quo_vadis/defaults.rb +2 -2
  25. data/lib/quo_vadis/version.rb +1 -1
  26. data/lib/quo_vadis.rb +2 -2
  27. data/test/dummy/app/controllers/sign_ups_controller.rb +2 -11
  28. data/test/fixtures/quo_vadis/mailer/account_confirmation.text +2 -3
  29. data/test/fixtures/quo_vadis/mailer/reset_password.text +2 -2
  30. data/test/integration/account_confirmation_test.rb +42 -86
  31. data/test/integration/controller_test.rb +8 -8
  32. data/test/integration/logging_test.rb +31 -7
  33. data/test/integration/password_login_test.rb +1 -1
  34. data/test/integration/password_reset_test.rb +89 -54
  35. data/test/mailers/mailer_test.rb +2 -2
  36. data/test/models/account_test.rb +48 -0
  37. data/test/models/session_test.rb +4 -0
  38. metadata +3 -10
  39. data/app/models/quo_vadis/account_confirmation_token.rb +0 -17
  40. data/app/models/quo_vadis/password_reset_token.rb +0 -17
  41. data/app/models/quo_vadis/token.rb +0 -42
  42. data/app/views/quo_vadis/confirmations/edit.html.erb +0 -10
  43. data/app/views/quo_vadis/confirmations/edit_email.html.erb +0 -14
  44. data/app/views/quo_vadis/confirmations/index.html.erb +0 -14
  45. data/app/views/quo_vadis/password_resets/index.html.erb +0 -5
  46. data/test/models/token_test.rb +0 -70
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f75e1f5d40a86773e5cec5133d3822431d8ed76e581d7e712912481ae56ec29a
4
- data.tar.gz: 3789a04b43d20d1c3b4e4aaf6e5c8b88f53c4bc97fce5ee4b546063e1bfa9801
3
+ metadata.gz: 5e9808f4e29d96b1c9deac895bb45ebacbeb046928f22851fe618593abb49fa4
4
+ data.tar.gz: 38980863633e441f4c5d28c2fe03e5d8e6357afc4f4ddd4546f4492205aee48c
5
5
  SHA512:
6
- metadata.gz: ac21c181a2339c6d515ae2c7defe20ced03c103ac9e144ca4edcf3169d0ea4bafa18f4a2011912c611310e0adf5736285f7d8b2dff5b031468e735940bcbc3b0
7
- data.tar.gz: 432bb81c9d8de26a659a718a533579bdd982c4565ae5df96511dc5ec60bf84c4d4665c694e1db9b0837434b6a9d5d43a9db7006b6e278a1b9d1c9d8cf2f54c2e
6
+ metadata.gz: cb68d8909ca3343ed508dbe5c0510860a358cb39398d2bb5b91f999c6a74935e8bb09d41a27de5bcdc1a3061cb280865ce146fa168fa88690fc37f030cd83a78
7
+ data.tar.gz: 6b5e022eab6f659dd4620117595c9ff9b9527f532d6963b732d149db4316f833626cca40f5e89cd7b8071ebea7e87ff64462c71c8104628b033447debec7a2a2
data/CHANGELOG.md CHANGED
@@ -4,6 +4,20 @@
4
4
  ## HEAD
5
5
 
6
6
 
7
+ ## 2.2.0 (17 April 2023)
8
+
9
+ * Improve the readme with internal links and more section headings.
10
+ * Rename `password_reset_token_lifetime` to `password_reset_otp_lifetime`.
11
+ * Use OTP instead of link for password reset.
12
+ * Rename `account_confirmation_token_lifetime` to `account_confirmation_otp_lifetime`.
13
+ * Use OTP instead of link for account confirmation.
14
+
15
+
16
+ ## 2.1.11 (14 September 2022)
17
+
18
+ * Introduce common controller superclass.
19
+
20
+
7
21
  ## 2.1.10 (14 September 2022)
8
22
 
9
23
  * Enable configuration of mailer 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-%3D-true) 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:
206
+ Follow the steps above for sign-up.
211
207
 
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.
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.
217
209
 
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:
210
+ The confirmation code is valid for `QuoVadis.account_confirmation_otp_lifetime`.
219
211
 
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.
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.
224
213
 
225
- In your controller, call `#request_confirmation`:
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.
226
215
 
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
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`.
238
217
 
239
- private
240
-
241
- def user_params
242
- params.require(:user).permit(:name, :email, :password, :password_confirmation)
243
- end
244
- end
245
- ```
246
-
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.
248
-
249
- See the Configuration section below for how to set QuoVadis's emails' from addresses, headers, etc.
250
-
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`.
252
-
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.
254
-
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`.
256
-
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
@@ -334,31 +289,27 @@ A successful password change logs out any other sessions the user has (e.g. on o
334
289
 
335
290
  ### Reset password
336
291
 
337
- The user can reset their password if they lose it. The flow is:
292
+ The user can reset their password if they lose it and cannot log in. The flow is:
338
293
 
339
294
  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.
295
+ 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.
296
+ 3. [The email] The user reads the code.
297
+ 4. [Password-reset page] The user enters the 6-digt code and their new password and clicks the save button.
343
298
  5. QuoVadis sets the user's password and logs them in.
344
299
 
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).
300
+ 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`.
346
301
 
347
- See the Configuration section below for how to set QuoVadis's emails' from addresses, headers, etc.
302
+ 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.
348
303
 
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`.
350
-
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`.
304
+ 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
305
 
357
306
  After the user has reset their password, they will be logged in and redirected to the first of these that exists:
358
307
 
359
308
  - a route named `:after_login`;
360
309
  - your root route.
361
310
 
311
+ When the user resets their password, they are logged out of any other sessions they may have, for example on other devices.
312
+
362
313
 
363
314
  ### Sessions
364
315
 
@@ -411,9 +362,9 @@ QuoVadis.configure do
411
362
  session_lifetime :session
412
363
  session_lifetime_extend_to_end_of_day false
413
364
  session_idle_timeout :lifetime
414
- password_reset_token_lifetime 10.minutes
365
+ password_reset_otp_lifetime 10.minutes
415
366
  accounts_require_confirmation false
416
- account_confirmation_token_lifetime 10.minutes
367
+ account_confirmation_otp_lifetime 10.minutes
417
368
  mail_headers ({ from: 'Example App <support@example.com>' })
418
369
  enqueue_transactional_emails true
419
370
  app_name Rails.app_class.to_s.deconstantize # for the TOTP QR code
@@ -426,75 +377,75 @@ You can override any of it with a similarly structured file in `config/initializ
426
377
 
427
378
  Here are the options in detail:
428
379
 
429
- __`password_minimum_length`__ (integer)
380
+ #### `password_minimum_length` (integer)
430
381
 
431
382
  The minimum number of characters for a password.
432
383
 
433
- __`mask_ips`__ (boolean)
384
+ #### `mask_ips` (boolean)
434
385
 
435
386
  Whether to mask the IP address in the sessions list and the audit trail.
436
387
 
437
388
  Masking means setting the last octet (IPv4) or the last 80 bits (IPv6) to 0.
438
389
 
439
- __`cookie_name`__ (string)
390
+ #### `cookie_name` (string)
440
391
 
441
392
  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
393
 
443
- __`session_lifetime`__ (`:session` | `ActiveSupport::Duration` | integer)
394
+ #### `session_lifetime` (`:session` | `ActiveSupport::Duration` | integer)
444
395
 
445
396
  The lifetime of a logged-in session. Use `:session` for the browser session, or a `Duration` or number of seconds.
446
397
 
447
- __`session_lifetime_extend_to_end_of_day`__ (boolean)
398
+ #### `session_lifetime_extend_to_end_of_day` (boolean)
448
399
 
449
400
  Whether to extend the session's lifetime to the end of the day it will expire on.
450
401
 
451
402
  Set `true` to reduce the chance of a user being logged out while actively using your application.
452
403
 
453
- __`session_idle_timeout`__ (`:lifetime` | `ActiveSupport::Duration` | integer)
404
+ #### `session_idle_timeout` (`:lifetime` | `ActiveSupport::Duration` | integer)
454
405
 
455
406
  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
407
 
457
- __`password_reset_token_lifetime`__ (`ActiveSupport::Duration` | integer)
408
+ #### `password_reset_otp_lifetime` (`ActiveSupport::Duration` | integer)
458
409
 
459
- The `Duration` or number of seconds for which a password-reset token is valid.
410
+ The `Duration` or number of seconds for which a password-reset code is valid.
460
411
 
461
- __`accounts_require_confirmation`__ (boolean)
412
+ #### `accounts_require_confirmation` (boolean)
462
413
 
463
414
  Whether new users must confirm their account before they can log in.
464
415
 
465
- __`account_confirmation_token_lifetime`__ (`ActiveSupport::Duration` | integer)
416
+ #### `account_confirmation_otp_lifetime` (`ActiveSupport::Duration` | integer)
466
417
 
467
- The `Duration` or number of seconds for which an account-confirmation token is valid.
418
+ The `Duration` or number of seconds for which an account-confirmation code is valid.
468
419
 
469
- __`mailer_superclass`__ (string)
420
+ #### `mailer_superclass` (string)
470
421
 
471
422
  The class from which QuoVadis's mailer inherits.
472
423
 
473
- __`mail_headers`__ (hash)
424
+ #### `mail_headers` (hash)
474
425
 
475
426
  Mail headers which QuoVadis' emails should have.
476
427
 
477
- __`enqueue_transactional_emails`__ (boolean)
428
+ #### `enqueue_transactional_emails` (boolean)
478
429
 
479
430
  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
431
 
481
- __`app_name`__ (string)
432
+ #### `app_name` (string)
482
433
 
483
434
  Used in the provisioning URI for the TOTP QR code.
484
435
 
485
- __`two_factor_authentication_mandatory`__ (boolean)
436
+ #### `two_factor_authentication_mandatory` (boolean)
486
437
 
487
438
  Whether users must set up and use a second authentication factor.
488
439
 
489
- __`mount_point`__ (string)
440
+ #### `mount_point` (string)
490
441
 
491
442
  The path prefix for QuoVadis's routes.
492
443
 
493
444
  For example, the default login path is at `/login`. If you set `mount_point` to `/auth`, the login path would be `/auth/login`.
494
445
 
495
- #### Rails configuration
446
+ ### Rails configuration
496
447
 
497
- __Mailer URLs__
448
+ #### Mailer URLs
498
449
 
499
450
  You must also configure the mailer host so URLs are generated correctly in emails:
500
451
 
@@ -502,7 +453,7 @@ You must also configure the mailer host so URLs are generated correctly in email
502
453
  config.action_mailer.default_url_options: { host: 'example.com' }
503
454
  ```
504
455
 
505
- __Layouts__
456
+ #### Layouts
506
457
 
507
458
  You can specify QuoVadis's controllers' layouts in a `#to_prepare` block in your application configuration. For example:
508
459
 
@@ -517,7 +468,7 @@ module YourApp
517
468
  end
518
469
  ```
519
470
 
520
- __Routes__
471
+ #### Routes
521
472
 
522
473
  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
474
 
@@ -1,101 +1,66 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module QuoVadis
4
- class ConfirmationsController < ApplicationController
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
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module QuoVadis
4
- class LogsController < ApplicationController
4
+ class LogsController < QuoVadisController
5
5
  before_action :require_password_authentication
6
6
 
7
7
  PER_PAGE = 25
@@ -1,68 +1,100 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module QuoVadis
4
- class PasswordResetsController < ApplicationController
5
-
6
- # holding page for after the create action
7
- def index
8
- end
9
-
4
+ class PasswordResetsController < QuoVadisController
10
5
 
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
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module QuoVadis
4
- class PasswordsController < ApplicationController
4
+ class PasswordsController < QuoVadisController
5
5
 
6
6
  before_action :require_authentication
7
7
 
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module QuoVadis
4
+ class QuoVadisController < ApplicationController
5
+ end
6
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module QuoVadis
4
- class RecoveryCodesController < ApplicationController
4
+ class RecoveryCodesController < QuoVadisController
5
5
  before_action :require_password_authentication
6
6
 
7
7