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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -0
- data/README.md +85 -100
- data/app/controllers/quo_vadis/confirmations_controller.rb +26 -61
- data/app/controllers/quo_vadis/password_resets_controller.rb +64 -32
- data/app/controllers/quo_vadis/sessions_controller.rb +0 -5
- data/app/mailers/quo_vadis/mailer.rb +2 -2
- data/app/models/quo_vadis/account.rb +32 -0
- data/app/models/quo_vadis/password.rb +6 -1
- data/app/views/quo_vadis/confirmations/new.html.erb +19 -7
- data/app/views/quo_vadis/mailer/account_confirmation.text.erb +2 -3
- data/app/views/quo_vadis/mailer/reset_password.text.erb +2 -2
- data/app/views/quo_vadis/password_resets/edit.html.erb +13 -1
- data/app/views/quo_vadis/password_resets/new.html.erb +2 -2
- data/app/views/quo_vadis/sessions/new.html.erb +1 -1
- data/config/locales/quo_vadis.en.yml +9 -7
- data/config/routes.rb +4 -12
- data/lib/quo_vadis/controller.rb +21 -11
- data/lib/quo_vadis/defaults.rb +2 -2
- data/lib/quo_vadis/version.rb +1 -1
- data/lib/quo_vadis.rb +2 -2
- data/test/README.md +6 -0
- data/test/dummy/app/controllers/articles_controller.rb +1 -0
- data/test/dummy/app/controllers/sign_ups_controller.rb +2 -11
- data/test/fixtures/quo_vadis/mailer/account_confirmation.text +2 -3
- data/test/fixtures/quo_vadis/mailer/reset_password.text +2 -2
- data/test/integration/account_confirmation_test.rb +42 -86
- data/test/integration/controller_test.rb +8 -8
- data/test/integration/logging_test.rb +31 -7
- data/test/integration/password_login_test.rb +1 -1
- data/test/integration/password_reset_test.rb +89 -54
- data/test/integration/sessions_test.rb +16 -0
- data/test/mailers/mailer_test.rb +2 -2
- data/test/models/account_test.rb +48 -0
- data/test/models/session_test.rb +4 -0
- metadata +4 -11
- data/app/models/quo_vadis/account_confirmation_token.rb +0 -17
- data/app/models/quo_vadis/password_reset_token.rb +0 -17
- data/app/models/quo_vadis/token.rb +0 -42
- data/app/views/quo_vadis/confirmations/edit.html.erb +0 -10
- data/app/views/quo_vadis/confirmations/edit_email.html.erb +0 -14
- data/app/views/quo_vadis/confirmations/index.html.erb +0 -14
- data/app/views/quo_vadis/password_resets/index.html.erb +0 -5
- data/test/models/token_test.rb +0 -70
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f55b91cf69117006b0dce03a6b0d38423b587bf460c05e24984735de6a4cf8a3
|
4
|
+
data.tar.gz: 6a309a19fd35aaacbf1ec8ff5df7544ab980cecfad30664f6f0abb3778eb1d37
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
-
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
118
|
+
#### `login(model, browser_session = true, metadata: {})`
|
119
119
|
|
120
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
210
|
+
The confirmation code is valid for `QuoVadis.account_confirmation_otp_lifetime`.
|
250
211
|
|
251
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
341
|
-
3. [The email] The user
|
342
|
-
4. [Password-reset
|
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`.
|
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
|
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
|
-
|
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
|
-
|
399
|
+
password_reset_otp_lifetime 10.minutes
|
415
400
|
accounts_require_confirmation false
|
416
|
-
|
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
|
-
|
414
|
+
#### `password_minimum_length` (integer)
|
430
415
|
|
431
416
|
The minimum number of characters for a password.
|
432
417
|
|
433
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
442
|
+
#### `password_reset_otp_lifetime` (`ActiveSupport::Duration` | integer)
|
458
443
|
|
459
|
-
The `Duration` or number of seconds for which a password-reset
|
444
|
+
The `Duration` or number of seconds for which a password-reset code is valid.
|
460
445
|
|
461
|
-
|
446
|
+
#### `accounts_require_confirmation` (boolean)
|
462
447
|
|
463
448
|
Whether new users must confirm their account before they can log in.
|
464
449
|
|
465
|
-
|
450
|
+
#### `account_confirmation_otp_lifetime` (`ActiveSupport::Duration` | integer)
|
466
451
|
|
467
|
-
The `Duration` or number of seconds for which an account-confirmation
|
452
|
+
The `Duration` or number of seconds for which an account-confirmation code is valid.
|
468
453
|
|
469
|
-
|
454
|
+
#### `mailer_superclass` (string)
|
470
455
|
|
471
456
|
The class from which QuoVadis's mailer inherits.
|
472
457
|
|
473
|
-
|
458
|
+
#### `mail_headers` (hash)
|
474
459
|
|
475
460
|
Mail headers which QuoVadis' emails should have.
|
476
461
|
|
477
|
-
|
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
|
-
|
466
|
+
#### `app_name` (string)
|
482
467
|
|
483
468
|
Used in the provisioning URI for the TOTP QR code.
|
484
469
|
|
485
|
-
|
470
|
+
#### `two_factor_authentication_mandatory` (boolean)
|
486
471
|
|
487
472
|
Whether users must set up and use a second authentication factor.
|
488
473
|
|
489
|
-
|
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
|
-
|
480
|
+
### Rails configuration
|
496
481
|
|
497
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
7
|
-
def index
|
6
|
+
def new
|
8
7
|
@account = find_pending_account_from_session
|
9
|
-
end
|
10
8
|
|
11
|
-
|
12
|
-
|
13
|
-
|
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 =
|
16
|
+
@account = find_pending_account_from_session
|
20
17
|
|
21
|
-
unless account
|
22
|
-
redirect_to
|
18
|
+
unless @account
|
19
|
+
redirect_to qv.path_after_signup, alert: QuoVadis.translate('flash.confirmation.unknown')
|
20
|
+
return
|
23
21
|
end
|
24
22
|
|
25
|
-
|
26
|
-
redirect_to confirmations_path
|
27
|
-
end
|
23
|
+
expiry = session[:account_confirmation_expires_at]
|
28
24
|
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
41
|
-
|
42
|
-
|
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
|
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
|
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
|
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
|
-
|
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
|
-
#
|
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
|
-
|
19
|
-
|
20
|
-
|
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
|
-
|
24
|
-
|
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
|
-
|
28
|
-
def edit
|
29
|
-
account = PasswordResetToken.find_account params[:token]
|
39
|
+
otp = account.otp_for_password_reset(expiration)
|
30
40
|
|
31
|
-
|
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
|
-
|
36
|
-
|
37
|
-
|
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
|
-
#
|
54
|
+
# update password if otp and password are valid
|
44
55
|
def update
|
45
|
-
account =
|
56
|
+
account = find_account_resetting_password_from_session
|
46
57
|
|
47
|
-
unless account
|
48
|
-
redirect_to new_password_reset_path
|
58
|
+
unless account
|
59
|
+
redirect_to new_password_reset_path
|
60
|
+
return
|
49
61
|
end
|
50
62
|
|
51
|
-
|
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
|
-
|
58
|
-
|
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
|
-
|
61
|
-
redirect_to
|
62
|
-
|
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
|