quo_vadis 2.0.1 → 2.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +31 -0
- data/README.md +59 -33
- data/app/controllers/quo_vadis/confirmations_controller.rb +47 -1
- data/app/controllers/quo_vadis/password_resets_controller.rb +11 -8
- data/app/controllers/quo_vadis/passwords_controller.rb +4 -2
- data/app/controllers/quo_vadis/recovery_codes_controller.rb +1 -1
- data/app/controllers/quo_vadis/sessions_controller.rb +3 -3
- data/app/controllers/quo_vadis/totps_controller.rb +1 -1
- data/app/models/quo_vadis/account.rb +14 -0
- data/app/models/quo_vadis/log.rb +4 -2
- data/app/models/quo_vadis/password.rb +16 -0
- data/{test/dummy/app → app}/views/quo_vadis/confirmations/edit.html.erb +0 -0
- data/app/views/quo_vadis/confirmations/edit_email.html.erb +14 -0
- data/app/views/quo_vadis/confirmations/index.html.erb +14 -0
- data/{test/dummy/app → app}/views/quo_vadis/confirmations/new.html.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/logs/index.html.erb +3 -1
- data/{test/dummy/app → app}/views/quo_vadis/mailer/account_confirmation.text.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/mailer/email_change_notification.text.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/mailer/identifier_change_notification.text.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/mailer/password_change_notification.text.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/mailer/password_reset_notification.text.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/mailer/recovery_codes_generation_notification.text.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/mailer/reset_password.text.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/mailer/totp_reuse_notification.text.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/mailer/totp_setup_notification.text.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/mailer/twofa_deactivated_notification.text.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/password_resets/edit.html.erb +1 -9
- data/{test/dummy/app → app}/views/quo_vadis/password_resets/index.html.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/password_resets/new.html.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/passwords/edit.html.erb +1 -9
- data/{test/dummy/app → app}/views/quo_vadis/recovery_codes/challenge.html.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/recovery_codes/index.html.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/sessions/index.html.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/sessions/new.html.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/totps/challenge.html.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/totps/new.html.erb +0 -0
- data/{test/dummy/app → app}/views/quo_vadis/twofas/show.html.erb +0 -0
- data/config/locales/quo_vadis.en.yml +30 -1
- data/config/routes.rb +11 -5
- data/lib/generators/quo_vadis/install_generator.rb +1 -1
- data/lib/quo_vadis/controller.rb +3 -3
- data/lib/quo_vadis/defaults.rb +1 -1
- data/lib/quo_vadis/model.rb +8 -11
- data/lib/quo_vadis/version.rb +1 -1
- data/lib/quo_vadis.rb +5 -1
- data/test/dummy/app/controllers/sign_ups_controller.rb +1 -1
- data/test/dummy/config/initializers/quo_vadis.rb +0 -4
- data/test/integration/account_confirmation_test.rb +35 -2
- data/test/integration/logging_test.rb +14 -5
- data/test/integration/password_change_test.rb +16 -10
- data/test/integration/password_login_test.rb +14 -2
- data/test/integration/password_reset_test.rb +6 -6
- data/test/integration/totps_test.rb +1 -1
- data/test/models/account_test.rb +16 -0
- data/test/models/password_test.rb +16 -0
- data/test/models/recovery_code_test.rb +7 -1
- data/test/models/token_test.rb +1 -1
- metadata +29 -28
- data/test/dummy/app/views/quo_vadis/confirmations/index.html.erb +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5b81cb83f9cd0b6bb6e9af4015dba59b5cef0f5c3fccd5a82ee6d7e1bbc5768a
|
4
|
+
data.tar.gz: 8a9f5f0c013ec89de6849e9ddb3393fee46b84a2d08b20e606126cf97bba8032
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a7b5e766f22e55e5bc269d00e6fb9132cc54ba2523a72996d0d7da478dbf0662764d29265c591f5847d5f2cfaeaca1e78fb4d48fb8fecb24af0bee9e9c2047e2
|
7
|
+
data.tar.gz: 47bf8bba82c90f3b7527be6a316ec8f9a4b229077a6846454abecebd6d2ae29910f7e416635d9cc1d54e3b99fe87d4c7d0ca871741907f1987d1ee42bf79994b
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,37 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
3
|
|
4
|
+
## HEAD
|
5
|
+
|
6
|
+
|
7
|
+
## 2.1.1 (8 July 2021)
|
8
|
+
|
9
|
+
* Remove unnecessary route names.
|
10
|
+
* Add user revocation.
|
11
|
+
* Ensure password is only updated via #change or #reset.
|
12
|
+
* Move views into gem's app/views/ directory.
|
13
|
+
|
14
|
+
|
15
|
+
## 2.1.0 (25 June 2021)
|
16
|
+
|
17
|
+
* Do not require password on create.
|
18
|
+
* Fix incorrect assignment of built association.
|
19
|
+
* Add i18n translations for log actions.
|
20
|
+
* Use model instance in change-password form.
|
21
|
+
* Ensure password-reset flash notice not displayed when emailed link is clicked.
|
22
|
+
* Use model instance in password-reset form.
|
23
|
+
* Give no indication of unknown account on request of password reset email.
|
24
|
+
* Use 422 status code for form submission error responses.
|
25
|
+
* Make default cookie name depend on Rails environment.
|
26
|
+
|
27
|
+
|
28
|
+
## 2.0.2 (24 May 2021)
|
29
|
+
|
30
|
+
* Account confirmation: enable updating of email address.
|
31
|
+
* Account confirmation: enable direct resending of email.
|
32
|
+
* Log unknown identifier in metadata.
|
33
|
+
|
34
|
+
|
4
35
|
## 2.0.1 (18 May 2021)
|
5
36
|
|
6
37
|
* Remove Gemfile.lock from repo.
|
data/README.md
CHANGED
@@ -86,9 +86,9 @@ class Person < ApplicationRecord
|
|
86
86
|
end
|
87
87
|
```
|
88
88
|
|
89
|
-
When
|
89
|
+
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`.
|
90
90
|
|
91
|
-
|
91
|
+
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`.
|
92
92
|
|
93
93
|
The minimum password length is configured by `QuoVadis.password_minimum_length` (12 by default).
|
94
94
|
|
@@ -232,7 +232,7 @@ class UsersController < ApplicationController
|
|
232
232
|
@user = User.new user_params
|
233
233
|
if @user.save
|
234
234
|
request_confirmation @user
|
235
|
-
redirect_to
|
235
|
+
redirect_to quo_vadis.confirmations_path # a page where you advise the user to check their email
|
236
236
|
else
|
237
237
|
# ...
|
238
238
|
end
|
@@ -246,17 +246,19 @@ class UsersController < ApplicationController
|
|
246
246
|
end
|
247
247
|
```
|
248
248
|
|
249
|
-
QuoVadis will send the user an email with a link. Write the email view ([example](https://github.com/airblade/quo_vadis/blob/master/
|
249
|
+
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.
|
250
250
|
|
251
251
|
See the Configuration section below for how to set QuoVadis's emails' from addresses, headers, etc.
|
252
252
|
|
253
|
-
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/
|
253
|
+
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`.
|
254
254
|
|
255
|
-
|
255
|
+
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.
|
256
256
|
|
257
|
-
Next, write the page to which the link in the email points ([example](https://github.com/airblade/quo_vadis/blob/master/
|
257
|
+
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`.
|
258
258
|
|
259
|
-
|
259
|
+
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`.
|
260
|
+
|
261
|
+
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
262
|
|
261
263
|
After the user has confirmed their account, they will be logged in and redirected to the first of these that exists:
|
262
264
|
|
@@ -270,7 +272,7 @@ So add whichever works best for you.
|
|
270
272
|
|
271
273
|
Use `before_action :require_password_authentication` or `before_action :require_authentication` in your controllers.
|
272
274
|
|
273
|
-
Write the login view ([example](https://github.com/airblade/quo_vadis/blob/master/
|
275
|
+
Write the login view ([example](https://github.com/airblade/quo_vadis/blob/master/app/views/quo_vadis/sessions/new.html.erb)). Your login form must be in `app/views/quo_vadis/sessions/new.html.:format`. Note it must capture the user's identifier (not email, unless the identifier is email).
|
274
276
|
|
275
277
|
If you include a `remember` checkbox in your login form:
|
276
278
|
|
@@ -290,7 +292,7 @@ After authenticating the user will be redirected to the first of these that exis
|
|
290
292
|
|
291
293
|
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.
|
292
294
|
|
293
|
-
If you do want 2FA, you can choose whether it is
|
295
|
+
If you do want 2FA, you can choose whether it is mandatory or optional for your users by setting `QuoVadis.two_factor_authentication_mandatory <true|false>` in your configuration.
|
294
296
|
|
295
297
|
Use `before_action :require_two_factor_authentication` in your controllers (which supersedes `:require_password_authentication`). This will require the user, after authenticating with their password, to authenticate with 2FA – when 2FA is mandatory, or when it is optional and the user has set up 2FA.
|
296
298
|
|
@@ -310,22 +312,22 @@ In your views, have a link where users can manage their 2FA:
|
|
310
312
|
link_to '2FA', quo_vadis.twofa_path
|
311
313
|
```
|
312
314
|
|
313
|
-
Write the 2FA overview page ([example](https://github.com/airblade/quo_vadis/blob/master/
|
315
|
+
Write the 2FA overview page ([example](https://github.com/airblade/quo_vadis/blob/master/app/views/quo_vadis/twofas/show.html.erb)). It must be in `app/views/quo_vadis/twofas/show.html.:format`. This page allows the user to set up 2FA, deactivate or reset it, and generate new recovery codes.
|
314
316
|
|
315
|
-
Next, write the TOTP setup page ([example](https://github.com/airblade/quo_vadis/blob/master/
|
317
|
+
Next, write the TOTP setup page ([example](https://github.com/airblade/quo_vadis/blob/master/app/views/quo_vadis/totps/new.html.erb)). It must be in `app/views/quo_vadis/totps/new.html.:format`. This page shows the user a QR code (and the key as text) which they scan with their authenticator.
|
316
318
|
|
317
|
-
Next, write the recovery codes page ([example](https://github.com/airblade/quo_vadis/blob/master/
|
319
|
+
Next, write the recovery codes page ([example](https://github.com/airblade/quo_vadis/blob/master/app/views/quo_vadis/recovery_codes/index.html.erb)). It must be in `app/views/quo_vadis/recovery_codes/index.html.:format`. This shows the recovery codes immediately after TOTP is setup, and immediately after generating fresh recovery codes, but not otherwise.
|
318
320
|
|
319
|
-
Next, write the TOTP challenge page where a user inputs their 6-digit TOTP ([example](https://github.com/airblade/quo_vadis/blob/master/
|
321
|
+
Next, write the TOTP challenge page where a user inputs their 6-digit TOTP ([example](https://github.com/airblade/quo_vadis/blob/master/app/views/quo_vadis/totps/challenge.html.erb)). It must be in `app/views/quo_vadis/totps/challenge.html.:format`. It's a good idea to link to the recovery code page (`challenge_recovery_codes_path`) for any user who has lost their authenticator.
|
320
322
|
|
321
|
-
Finally, write the recovery code challenge page where a user inputs one of their recovery codes ([example](https://github.com/airblade/quo_vadis/blob/master/
|
323
|
+
Finally, write the recovery code challenge page where a user inputs one of their recovery codes ([example](https://github.com/airblade/quo_vadis/blob/master/app/views/quo_vadis/recovery_codes/challenge.html.erb)). It must be in `app/views/quo_vadis/recovery_codes/challenge.html.:format`. A recovery code can only be used once, and using one deactivates TOTP – so the user will have to set it up again next time.
|
322
324
|
|
323
325
|
|
324
326
|
### Change password
|
325
327
|
|
326
328
|
To change their password, the user must provide their current one as well as the new one.
|
327
329
|
|
328
|
-
Write the change-password form ([example](https://github.com/airblade/quo_vadis/blob/master/
|
330
|
+
Write the change-password form ([example](https://github.com/airblade/quo_vadis/blob/master/app/views/quo_vadis/passwords/edit.html.erb)). It must be in `app/views/quo_vadis/passwords/edit.html.:format`.
|
329
331
|
|
330
332
|
After the password has been changed, the user is redirected to the first of:
|
331
333
|
|
@@ -345,17 +347,17 @@ The user can reset their password if they lose it. The flow is:
|
|
345
347
|
4. [Password-reset confirmation page] The user enters their new password and clicks a button.
|
346
348
|
5. QuoVadis sets the user's password and logs them in.
|
347
349
|
|
348
|
-
First, write the page where the user requests a password-reset ([example](https://github.com/airblade/quo_vadis/blob/master/
|
350
|
+
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).
|
349
351
|
|
350
352
|
See the Configuration section below for how to set QuoVadis's emails' from addresses, headers, etc.
|
351
353
|
|
352
|
-
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/
|
354
|
+
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`.
|
353
355
|
|
354
356
|
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.
|
355
357
|
|
356
|
-
|
358
|
+
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.
|
357
359
|
|
358
|
-
|
360
|
+
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`.
|
359
361
|
|
360
362
|
After the user has reset their password, they will be logged in and redirected to the first of these that exists:
|
361
363
|
|
@@ -369,14 +371,14 @@ A logged-in session lasts for either the browser session or `QuoVadis.session_li
|
|
369
371
|
|
370
372
|
A user can view their active sessions and log out of any of them.
|
371
373
|
|
372
|
-
Write the view showing the sessions ([example](https://github.com/airblade/quo_vadis/blob/master/
|
374
|
+
Write the view showing the sessions ([example](https://github.com/airblade/quo_vadis/blob/master/app/views/quo_vadis/sessions/index.html.erb)). It must be in `app/views/quo_vadis/sessions/index.html.:format`.
|
373
375
|
|
374
376
|
|
375
377
|
### Audit trail
|
376
378
|
|
377
379
|
An audit trail is kept of authentication events. You can see the full list in the [`Log`](https://github.com/airblade/quo_vadis/blob/master/app/models/quo_vadis/log.rb) class.
|
378
380
|
|
379
|
-
Write the view showing the events ([example](https://github.com/airblade/quo_vadis/blob/master/
|
381
|
+
Write the view showing the events ([example](https://github.com/airblade/quo_vadis/blob/master/app/views/quo_vadis/logs/index.html.erb)). It must be in `app/views/quo_vadis/logs/index.html.:format`.
|
380
382
|
|
381
383
|
|
382
384
|
### Notifications
|
@@ -385,18 +387,23 @@ QuoVadis notifies users by email whenever their authentication details are chang
|
|
385
387
|
|
386
388
|
Write the corresponding mailer views:
|
387
389
|
|
388
|
-
- change of email ([example](https://github.com/airblade/quo_vadis/blob/master/
|
389
|
-
- change of identifier (unless the identifier is email) ([example](https://github.com/airblade/quo_vadis/blob/master/
|
390
|
-
- change of password ([example](https://github.com/airblade/quo_vadis/blob/master/
|
391
|
-
- reset of password ([example](https://github.com/airblade/quo_vadis/blob/master/
|
392
|
-
- TOTP setup ([example](https://github.com/airblade/quo_vadis/blob/master/
|
393
|
-
- TOTP code used a second time ([example](https://github.com/airblade/quo_vadis/blob/master/
|
394
|
-
- 2FA deactivated ([example](https://github.com/airblade/quo_vadis/blob/master/
|
395
|
-
- recovery codes generated ([example](https://github.com/airblade/quo_vadis/blob/master/
|
390
|
+
- change of email ([example](https://github.com/airblade/quo_vadis/blob/master/app/views/quo_vadis/mailer/email_change_notification.text.erb))
|
391
|
+
- change of identifier (unless the identifier is email) ([example](https://github.com/airblade/quo_vadis/blob/master/app/views/quo_vadis/mailer/identifier_change_notification.text.erb))
|
392
|
+
- change of password ([example](https://github.com/airblade/quo_vadis/blob/master/app/views/quo_vadis/mailer/password_change_notification.text.erb))
|
393
|
+
- reset of password ([example](https://github.com/airblade/quo_vadis/blob/master/app/views/quo_vadis/mailer/password_reset_notification.text.erb))
|
394
|
+
- TOTP setup ([example](https://github.com/airblade/quo_vadis/blob/master/app/views/quo_vadis/mailer/totp_setup_notification.text.erb))
|
395
|
+
- TOTP code used a second time ([example](https://github.com/airblade/quo_vadis/blob/master/app/views/quo_vadis/mailer/totp_reuse_notification.text.erb))
|
396
|
+
- 2FA deactivated ([example](https://github.com/airblade/quo_vadis/blob/master/app/views/quo_vadis/mailer/twofa_deactivated_notification.text.erb))
|
397
|
+
- recovery codes generated ([example](https://github.com/airblade/quo_vadis/blob/master/app/views/quo_vadis/mailer/recovery_codes_generation_notification.text.erb))
|
396
398
|
|
397
399
|
They must be in `app/views/quo_vadis/mailer/NAME.{text,html}.erb`.
|
398
400
|
|
399
401
|
|
402
|
+
### Revocation
|
403
|
+
|
404
|
+
You can revoke a user's access by calling `#revoke_authentication_credentials` on the model instance. This deletes the user's password, TOTP credential, recovery codes, and active sessions. Their authentication logs, or audit trail, are preserved.
|
405
|
+
|
406
|
+
|
400
407
|
## Configuration
|
401
408
|
|
402
409
|
This is QuoVadis' [default configuration](https://github.com/airblade/quo_vadis/blob/master/lib/quo_vadis/defaults.rb):
|
@@ -405,7 +412,7 @@ This is QuoVadis' [default configuration](https://github.com/airblade/quo_vadis/
|
|
405
412
|
QuoVadis.configure do
|
406
413
|
password_minimum_length 12
|
407
414
|
mask_ips false
|
408
|
-
cookie_name '__Host-qv'
|
415
|
+
cookie_name (Rails.env.production? ? '__Host-qv' : 'qv')
|
409
416
|
session_lifetime :session
|
410
417
|
session_lifetime_extend_to_end_of_day false
|
411
418
|
session_idle_timeout :lifetime
|
@@ -436,7 +443,7 @@ Masking means setting the last octet (IPv4) or the last 80 bits (IPv6) to 0.
|
|
436
443
|
|
437
444
|
__`cookie_name`__ (string)
|
438
445
|
|
439
|
-
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).
|
446
|
+
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).
|
440
447
|
|
441
448
|
__`session_lifetime`__ (`:session` | `ActiveSupport::Duration` | integer)
|
442
449
|
|
@@ -488,13 +495,32 @@ For example, the default login path is at `/login`. If you set `mount_point` to
|
|
488
495
|
|
489
496
|
#### Rails configuration
|
490
497
|
|
498
|
+
__Mailer URLs__
|
499
|
+
|
491
500
|
You must also configure the mailer host so URLs are generated correctly in emails:
|
492
501
|
|
493
502
|
```ruby
|
494
503
|
config.action_mailer.default_url_options: { host: 'example.com' }
|
495
504
|
```
|
496
505
|
|
497
|
-
|
506
|
+
__Layouts__
|
507
|
+
|
508
|
+
You can specify QuoVadis's controllers' layouts in a `#to_prepare` block in your application configuration. For example:
|
509
|
+
|
510
|
+
```ruby
|
511
|
+
# config/application.rb
|
512
|
+
module YourApp
|
513
|
+
class Application < Rails::Application
|
514
|
+
config.to_prepare do
|
515
|
+
QuoVadis::ConfirmationsController.layout 'your_layout'
|
516
|
+
end
|
517
|
+
end
|
518
|
+
end
|
519
|
+
```
|
520
|
+
|
521
|
+
__Routes__
|
522
|
+
|
523
|
+
You can set up your post-authentication and post-password-change routes. If you don't, you must have a root route. For example:
|
498
524
|
|
499
525
|
```ruby
|
500
526
|
# config/routes.rb
|
@@ -5,6 +5,7 @@ module QuoVadis
|
|
5
5
|
|
6
6
|
# holding page
|
7
7
|
def index
|
8
|
+
@account = find_pending_account_from_session
|
8
9
|
end
|
9
10
|
|
10
11
|
|
@@ -45,12 +46,57 @@ module QuoVadis
|
|
45
46
|
end
|
46
47
|
|
47
48
|
account.confirmed!
|
48
|
-
|
49
49
|
qv.log account, Log::ACCOUNT_CONFIRMATION
|
50
50
|
|
51
|
+
session.delete :account_pending_confirmation
|
52
|
+
|
51
53
|
login account.model, true
|
52
54
|
redirect_to qv.path_after_authentication, notice: QuoVadis.translate('flash.confirmation.confirmed')
|
53
55
|
end
|
54
56
|
|
57
|
+
|
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
|
+
def resend
|
84
|
+
account = find_pending_account_from_session
|
85
|
+
|
86
|
+
unless account
|
87
|
+
redirect_to confirmations_path, alert: QuoVadis.translate('flash.confirmation.unknown') and return
|
88
|
+
end
|
89
|
+
|
90
|
+
request_confirmation account.model
|
91
|
+
redirect_to confirmations_path
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
def find_pending_account_from_session
|
98
|
+
Account.find(session[:account_pending_confirmation]) if session[:account_pending_confirmation]
|
99
|
+
end
|
100
|
+
|
55
101
|
end
|
56
102
|
end
|
@@ -13,15 +13,14 @@ module QuoVadis
|
|
13
13
|
|
14
14
|
|
15
15
|
def create
|
16
|
-
flash[:notice] = QuoVadis.translate 'flash.password_reset.create'
|
17
|
-
|
18
16
|
account = QuoVadis.find_account_by_identifier_in_params params
|
19
|
-
return unless account
|
20
17
|
|
21
|
-
|
22
|
-
|
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)
|
21
|
+
end
|
23
22
|
|
24
|
-
redirect_to password_resets_path
|
23
|
+
redirect_to password_resets_path, notice: QuoVadis.translate('flash.password_reset.create')
|
25
24
|
end
|
26
25
|
|
27
26
|
|
@@ -33,6 +32,10 @@ module QuoVadis
|
|
33
32
|
redirect_to new_password_reset_path, alert: QuoVadis.translate('flash.password_reset.unknown') and return
|
34
33
|
end
|
35
34
|
|
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
|
38
|
+
|
36
39
|
@password = QuoVadis::Password.new
|
37
40
|
end
|
38
41
|
|
@@ -46,7 +49,7 @@ module QuoVadis
|
|
46
49
|
end
|
47
50
|
|
48
51
|
@password = account.password
|
49
|
-
if @password.reset params[:password], params[:password_confirmation]
|
52
|
+
if @password.reset params[:password][:password], params[:password][:password_confirmation]
|
50
53
|
# Logout account's sessions because password has changed.
|
51
54
|
# Note model is not logged in here.
|
52
55
|
@password.account.sessions.destroy_all
|
@@ -57,7 +60,7 @@ module QuoVadis
|
|
57
60
|
login @password.account.model, true
|
58
61
|
redirect_to qv.path_after_authentication, notice: QuoVadis.translate('flash.password_reset.reset')
|
59
62
|
else
|
60
|
-
render :edit
|
63
|
+
render :edit, status: :unprocessable_entity
|
61
64
|
end
|
62
65
|
end
|
63
66
|
|
@@ -11,14 +11,16 @@ module QuoVadis
|
|
11
11
|
|
12
12
|
def update
|
13
13
|
@password = authenticated_model.qv_account.password
|
14
|
-
if @password.change
|
14
|
+
if @password.change(params[:password][:password],
|
15
|
+
params[:password][:new_password],
|
16
|
+
params[:password][:new_password_confirmation])
|
15
17
|
qv.log authenticated_model.qv_account, Log::PASSWORD_CHANGE
|
16
18
|
QuoVadis.notify :password_change_notification, email: authenticated_model.email
|
17
19
|
qv.logout_other_sessions
|
18
20
|
qv.replace_session
|
19
21
|
redirect_to qv.path_after_password_change, notice: QuoVadis.translate('flash.password.update')
|
20
22
|
else
|
21
|
-
render :edit
|
23
|
+
render :edit, status: :unprocessable_entity
|
22
24
|
end
|
23
25
|
end
|
24
26
|
|
@@ -21,16 +21,16 @@ module QuoVadis
|
|
21
21
|
account = QuoVadis.find_account_by_identifier_in_params params
|
22
22
|
|
23
23
|
unless account
|
24
|
-
qv.log nil, Log::LOGIN_UNKNOWN
|
24
|
+
qv.log nil, Log::LOGIN_UNKNOWN, identifier: QuoVadis.identifier_value_in_params(params)
|
25
25
|
flash.now[:alert] = QuoVadis.translate 'flash.login.failed'
|
26
|
-
render :new
|
26
|
+
render :new, status: :unprocessable_entity
|
27
27
|
return
|
28
28
|
end
|
29
29
|
|
30
30
|
unless account.password.authenticate params[:password]
|
31
31
|
qv.log account, Log::LOGIN_FAILURE
|
32
32
|
flash.now[:alert] = QuoVadis.translate 'flash.login.failed'
|
33
|
-
render :new
|
33
|
+
render :new, status: :unprocessable_entity
|
34
34
|
return
|
35
35
|
end
|
36
36
|
|
@@ -32,9 +32,23 @@ module QuoVadis
|
|
32
32
|
|
33
33
|
# Returns an array of the recovery codes' codes.
|
34
34
|
def generate_recovery_codes
|
35
|
+
recovery_codes.delete_all
|
35
36
|
Array.new(MAX_NUMBER_OF_RECOVERY_CODES) { recovery_codes.create }.map &:code
|
36
37
|
end
|
37
38
|
|
39
|
+
def revoke
|
40
|
+
password&.destroy
|
41
|
+
totp&.destroy
|
42
|
+
recovery_codes.destroy_all
|
43
|
+
sessions.destroy_all
|
44
|
+
|
45
|
+
Log.create(
|
46
|
+
account: self,
|
47
|
+
action: Log::REVOKE,
|
48
|
+
ip: (CurrentRequestDetails.ip || '')
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
38
52
|
private
|
39
53
|
|
40
54
|
def log_identifier_change
|
data/app/models/quo_vadis/log.rb
CHANGED
@@ -21,7 +21,8 @@ module QuoVadis
|
|
21
21
|
PASSWORD_RESET = 'password.reset'
|
22
22
|
ACCOUNT_CONFIRMATION = 'account.confirmation'
|
23
23
|
LOGOUT_OTHER = 'logout.other'
|
24
|
-
LOGOUT = 'logout'
|
24
|
+
LOGOUT = 'logout.self'
|
25
|
+
REVOKE = 'revoke'
|
25
26
|
|
26
27
|
ACTIONS = [
|
27
28
|
LOGIN_SUCCESS,
|
@@ -41,7 +42,8 @@ module QuoVadis
|
|
41
42
|
PASSWORD_RESET,
|
42
43
|
ACCOUNT_CONFIRMATION,
|
43
44
|
LOGOUT_OTHER,
|
44
|
-
LOGOUT
|
45
|
+
LOGOUT,
|
46
|
+
REVOKE
|
45
47
|
]
|
46
48
|
|
47
49
|
belongs_to :account, optional: true # optional only for LOGIN_UNKNOWN
|
@@ -7,6 +7,7 @@ module QuoVadis
|
|
7
7
|
has_secure_password
|
8
8
|
|
9
9
|
validates_length_of :password, minimum: QuoVadis.password_minimum_length, allow_blank: true
|
10
|
+
validate :password_updated_legitimately, on: :update
|
10
11
|
|
11
12
|
attr_accessor :new_password
|
12
13
|
|
@@ -48,5 +49,20 @@ module QuoVadis
|
|
48
49
|
save
|
49
50
|
end
|
50
51
|
|
52
|
+
private
|
53
|
+
|
54
|
+
def password_updated_legitimately
|
55
|
+
return unless password_digest_changed?
|
56
|
+
|
57
|
+
unless change_or_reset_called?
|
58
|
+
errors.add :password, 'must be updated via #change or #reset'
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def change_or_reset_called?
|
63
|
+
caller_locations.any? { |loc|
|
64
|
+
['change', 'reset'].include?(loc.label) && Pathname.new(loc.path).basename.to_s == 'password.rb'
|
65
|
+
}
|
66
|
+
end
|
51
67
|
end
|
52
68
|
end
|
File without changes
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<h1>Account confirmation: change email</h1>
|
2
|
+
|
3
|
+
<p>Please update your email address.</p>
|
4
|
+
|
5
|
+
<%= form_with url: update_email_confirmations_path, method: :put do |f| %>
|
6
|
+
<p>
|
7
|
+
<%= f.label :email %>
|
8
|
+
<%= f.text_field :email, value: @email, inputmode: 'email', autocomplete: 'email' %>
|
9
|
+
</p>
|
10
|
+
|
11
|
+
<p>
|
12
|
+
<%= f.submit 'Update my email address and send me a new confirmation email' %>
|
13
|
+
</p>
|
14
|
+
<% end %>
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<h1>Account confirmation</h1>
|
2
|
+
|
3
|
+
<% if @account %>
|
4
|
+
<p>We have sent an email to <%= @account.model.email %>.</p>
|
5
|
+
|
6
|
+
<p>Wrong address? <%= link_to 'Change it', edit_email_confirmations_path %>.</p>
|
7
|
+
|
8
|
+
<p>Didn't receive it? <%= button_to 'Get another one', resend_confirmations_path %></p>
|
9
|
+
|
10
|
+
<% else %>
|
11
|
+
<p>We have sent an email to you.</p>
|
12
|
+
|
13
|
+
<p><%= link_to 'Request a new email', new_confirmation_path %></p>
|
14
|
+
<% end %>
|
File without changes
|
@@ -3,6 +3,7 @@
|
|
3
3
|
<table>
|
4
4
|
<thead>
|
5
5
|
<tr>
|
6
|
+
<th>Timestamp</th>
|
6
7
|
<th>Action</th>
|
7
8
|
<th>IP</th>
|
8
9
|
<th>Metadata</th>
|
@@ -11,7 +12,8 @@
|
|
11
12
|
<tbody>
|
12
13
|
<% @logs.each do |log| %>
|
13
14
|
<tr>
|
14
|
-
<td><%= log.
|
15
|
+
<td><%= log.created_at %></td>
|
16
|
+
<td><%= QuoVadis.translate "log.action.#{log.action}" %></td>
|
15
17
|
<td><%= log.ip %></td>
|
16
18
|
<td><%= log.metadata.empty? ? '' : log.metadata.map {|k,v| "#{k}: #{v}"}.join(', ') %></td>
|
17
19
|
</tr>
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
data/{test/dummy/app → app}/views/quo_vadis/mailer/recovery_codes_generation_notification.text.erb
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
@@ -1,14 +1,6 @@
|
|
1
1
|
<h1>Reset password</h1>
|
2
2
|
|
3
|
-
|
4
|
-
<ul>
|
5
|
-
<% @password.errors.full_messages.each do |msg| %>
|
6
|
-
<li><%= msg %></li>
|
7
|
-
<% end %>
|
8
|
-
</ul>
|
9
|
-
<% end %>
|
10
|
-
|
11
|
-
<%= form_with url: password_reset_path(params[:token]), method: :put do |f| %>
|
3
|
+
<%= form_with model: @password, url: password_reset_path(params[:token]), method: :put do |f| %>
|
12
4
|
<p>
|
13
5
|
<%= f.label :password %>
|
14
6
|
<%= f.password_field :password, autocomplete: 'new-password' %>
|
File without changes
|
File without changes
|