avocado 0.3.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +29 -27
- data/app/controllers/avocado/affirmations_controller.rb +51 -0
- data/app/controllers/avocado/base_controller.rb +21 -0
- data/app/controllers/avocado/emails_controller.rb +48 -0
- data/app/controllers/avocado/events_controller.rb +9 -0
- data/app/controllers/avocado/passwords_controller.rb +34 -0
- data/app/controllers/avocado/recoveries_controller.rb +65 -0
- data/app/controllers/avocado/registrations_controller.rb +40 -0
- data/app/controllers/avocado/sessions_controller.rb +57 -0
- data/app/controllers/avocado/verifications_controller.rb +38 -0
- data/app/views/avocado/affirmations/new.html.erb +14 -0
- data/app/views/avocado/emails/edit.html.erb +20 -0
- data/app/views/avocado/events/_event.html.erb +6 -0
- data/app/views/avocado/events/index.html.erb +17 -0
- data/app/views/avocado/mailer/email_affirmation.text.erb +3 -0
- data/app/views/avocado/mailer/email_verification.text.erb +3 -0
- data/app/views/avocado/mailer/password_reset.text.erb +3 -0
- data/app/views/avocado/passwords/edit.html.erb +16 -0
- data/app/views/avocado/recoveries/edit.html.erb +17 -0
- data/app/views/avocado/recoveries/new.html.erb +14 -0
- data/app/views/avocado/registrations/new.html.erb +23 -0
- data/app/views/avocado/sessions/_session.html.erb +8 -0
- data/app/views/avocado/sessions/index.html.erb +21 -0
- data/app/views/avocado/sessions/new.html.erb +15 -0
- data/config/routes.rb +12 -0
- data/lib/avocado/authentication.rb +53 -0
- data/lib/avocado/current.rb +15 -0
- data/lib/avocado/engine.rb +9 -0
- data/lib/avocado/event.rb +32 -0
- data/lib/avocado/mailer.rb +4 -10
- data/lib/avocado/session.rb +16 -0
- data/lib/avocado/session_callbacks.rb +34 -0
- data/lib/avocado/user.rb +15 -7
- data/lib/avocado/user_callbacks.rb +45 -0
- data/lib/avocado/user_tokens.rb +33 -0
- data/lib/avocado/user_validations.rb +22 -0
- data/lib/avocado/version.rb +1 -1
- data/lib/avocado.rb +9 -6
- metadata +39 -11
- data/lib/avocado/user_email.rb +0 -15
- data/lib/avocado/user_email_affirmation.rb +0 -15
- data/lib/avocado/user_email_verification.rb +0 -17
- data/lib/avocado/user_password.rb +0 -18
- data/lib/avocado/user_password_reset.rb +0 -27
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a530ebf9605c2bb861d2da09da546513e68c4f647fc97c80686e07b32985cabe
|
4
|
+
data.tar.gz: 3be481be5c31ce2ee3bb1ea1a2d796bb7f8cbe850f7ccf2de6f296384a656bb2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cf3320eb985cc65c05c2da2d007697b4b9016d9a85f7ebebbd0735d744d7f8ca77096d86182c6761b63ea6f51f4b942a3c2dd363bb102ccc4ed53da6f5702c4f
|
7
|
+
data.tar.gz: f0b021b9e0f3433f2034e5cccdc751a446b50bc870474376e1a39b84e973bbabfaef95b59d96f60986bbddc7695afe88b860bd2d1568f991a17b3b654d7f3995
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,19 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.5.0] - 2023-07-21
|
4
|
+
|
5
|
+
- Add controller for "passwordless" email-link sign-in
|
6
|
+
- Add event class to log user auth events
|
7
|
+
- Add user-facing email and password edit pages
|
8
|
+
- Add various event logging callbacks
|
9
|
+
- Sign out all non current sessions when password changes
|
10
|
+
|
11
|
+
## [0.4.0] - 2023-07-19
|
12
|
+
|
13
|
+
- Convert the `Avocado::Mailer` module into a class
|
14
|
+
- Add controllers for signing up, signing in, password reset and email
|
15
|
+
verification
|
16
|
+
|
3
17
|
## [0.3.0] - 2023-07-17
|
4
18
|
|
5
19
|
- Add an `Avocado::Mailer` which generates each of the signed ids
|
data/README.md
CHANGED
@@ -4,42 +4,49 @@ A collection of authentication tools for use in [Rails] 7.1+ applications.
|
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
|
7
|
-
|
7
|
+
Add to the application's Gemfile by executing:
|
8
8
|
|
9
9
|
$ bundle add avocado
|
10
10
|
|
11
|
-
|
11
|
+
## Usage
|
12
12
|
|
13
|
-
|
13
|
+
If you are nervous about using Rails features directly, preferring to consume
|
14
|
+
such features via a packaged gem, you can include some Avocado modules into your
|
15
|
+
application to get authentication functionality.
|
14
16
|
|
15
|
-
|
17
|
+
As a prerequisite, you should have a database schema with columns that match the
|
18
|
+
users and sessions tables from [the demo app schema]. It's ok to have more
|
19
|
+
columns, but you need at least what is shown there.
|
16
20
|
|
17
|
-
|
18
|
-
features directly, preferring to consume the features via a packaged gem, add
|
19
|
-
the `Avocado::User` to your `User` model:
|
21
|
+
With that set, include the modules into your classes:
|
20
22
|
|
21
23
|
```ruby
|
22
24
|
class User < ApplicationRecord
|
23
25
|
include Avocado::User
|
24
26
|
end
|
25
|
-
```
|
26
27
|
|
27
|
-
|
28
|
+
class Session < ApplicationRecord
|
29
|
+
include Avocado::Session
|
30
|
+
end
|
28
31
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
- Provide signed token generators for `password_reset`, `email_verification`,
|
33
|
-
and `email_affirmation`
|
32
|
+
class Event < ApplicationRecord
|
33
|
+
include Avocado::Event
|
34
|
+
end
|
34
35
|
|
35
|
-
|
36
|
-
|
36
|
+
class ApplicationController < ActionController::Base
|
37
|
+
include Avocado::Authentication
|
38
|
+
end
|
39
|
+
```
|
37
40
|
|
38
|
-
|
39
|
-
provides some basic mailers.
|
41
|
+
This will enable a few things:
|
40
42
|
|
41
|
-
|
42
|
-
|
43
|
+
- Models will get validations, associations, and normalizations
|
44
|
+
- Rails built-in `has_secure_password` is called within `User`
|
45
|
+
- A mailer with signed token generators is created
|
46
|
+
- Controllers and Routes for sign up, sign in, password reset, email
|
47
|
+
verification, etc
|
48
|
+
|
49
|
+
The `spec/internal` app within this repo has some example usage.
|
43
50
|
|
44
51
|
## Development
|
45
52
|
|
@@ -47,11 +54,6 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
47
54
|
`rake spec` to run the tests. You can also run `bin/console` for an interactive
|
48
55
|
prompt that will allow you to experiment.
|
49
56
|
|
50
|
-
To install this gem onto your local machine, run `bundle exec rake install`. To
|
51
|
-
release a new version, update the version number in `version.rb`, and then run
|
52
|
-
`bundle exec rake release`, which will create a git tag for the version, push
|
53
|
-
git commits and the created tag, and push the `.gem` file to [RubyGems].
|
54
|
-
|
55
57
|
## Contributing
|
56
58
|
|
57
59
|
Bug reports and pull requests are welcome on [GitHub].
|
@@ -60,7 +62,7 @@ Bug reports and pull requests are welcome on [GitHub].
|
|
60
62
|
|
61
63
|
The gem is available as open source under the terms of the [MIT License].
|
62
64
|
|
63
|
-
[GitHub]: https://github.com/
|
65
|
+
[GitHub]: https://github.com/tcuwp/avocado
|
64
66
|
[MIT License]: https://opensource.org/licenses/MIT
|
65
67
|
[Rails]: https://github.com/rails/rails
|
66
|
-
[
|
68
|
+
[the demo app schema]: https://github.com/tcuwp/avocado/blob/main/spec/internal/db/schema.rb
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Avocado
|
4
|
+
class AffirmationsController < BaseController
|
5
|
+
skip_before_action :authenticate
|
6
|
+
|
7
|
+
before_action :set_user, only: :show
|
8
|
+
before_action :verify_user, only: :create
|
9
|
+
|
10
|
+
def new
|
11
|
+
end
|
12
|
+
|
13
|
+
def show
|
14
|
+
sign_in(@user)
|
15
|
+
redirect_to(root_path, notice: "Signed in successfully")
|
16
|
+
end
|
17
|
+
|
18
|
+
def create
|
19
|
+
send_affirmation_email
|
20
|
+
redirect_to new_session_path, notice: "Check your email for sign in instructions"
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def set_user
|
26
|
+
@user = user_from_signed_affirmation_token
|
27
|
+
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
28
|
+
redirect_to new_affirmation_path, alert: "That sign in link is invalid"
|
29
|
+
end
|
30
|
+
|
31
|
+
def user_from_signed_affirmation_token
|
32
|
+
::User.find_by_token_for!(:email_affirmation, params[:id])
|
33
|
+
end
|
34
|
+
|
35
|
+
def verify_user
|
36
|
+
unless user_from_params_email
|
37
|
+
redirect_to new_affirmation_path, alert: "You can't sign in until you verify your email"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def send_affirmation_email
|
42
|
+
mailer_for(user_from_params_email)
|
43
|
+
.email_affirmation
|
44
|
+
.deliver_later
|
45
|
+
end
|
46
|
+
|
47
|
+
def user_from_params_email
|
48
|
+
::User.verified.find_by(email: params[:email])
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Avocado
|
4
|
+
class BaseController < ApplicationController
|
5
|
+
private
|
6
|
+
|
7
|
+
def verify_password_challenge
|
8
|
+
unless current_user.authenticate(params_password_challenge)
|
9
|
+
redirect_back alert: "Password challenge failed.", fallback_location: root_path
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def params_password_challenge
|
14
|
+
params.dig(:user, :password_challenge)
|
15
|
+
end
|
16
|
+
|
17
|
+
def mailer_for(user)
|
18
|
+
Avocado::Mailer.with(user: user)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Avocado
|
4
|
+
class EmailsController < BaseController
|
5
|
+
PERMITTED_PARAMS = [:email]
|
6
|
+
|
7
|
+
before_action :set_user
|
8
|
+
before_action :verify_password_challenge, only: :update
|
9
|
+
|
10
|
+
def edit
|
11
|
+
end
|
12
|
+
|
13
|
+
def update
|
14
|
+
if @user.update(user_params)
|
15
|
+
process_email_update
|
16
|
+
else
|
17
|
+
render :edit, status: :unprocessable_entity
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def set_user
|
24
|
+
@user = current_user
|
25
|
+
end
|
26
|
+
|
27
|
+
def user_params
|
28
|
+
params
|
29
|
+
.require(:user)
|
30
|
+
.permit(PERMITTED_PARAMS)
|
31
|
+
end
|
32
|
+
|
33
|
+
def process_email_update
|
34
|
+
if @user.email_previously_changed?
|
35
|
+
resend_email_verification
|
36
|
+
redirect_to root_path, notice: "Your email has been changed"
|
37
|
+
else
|
38
|
+
redirect_to root_path
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def resend_email_verification
|
43
|
+
mailer_for(@user)
|
44
|
+
.email_verification
|
45
|
+
.deliver_later
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Avocado
|
4
|
+
class PasswordsController < BaseController
|
5
|
+
PERMITTED_PARAMS = [:password, :password_confirmation, :password_challenge]
|
6
|
+
|
7
|
+
before_action :set_user
|
8
|
+
before_action :verify_password_challenge, only: :update
|
9
|
+
|
10
|
+
def edit
|
11
|
+
end
|
12
|
+
|
13
|
+
def update
|
14
|
+
if @user.update(user_params)
|
15
|
+
redirect_to root_path, notice: "Your password has been changed"
|
16
|
+
else
|
17
|
+
render :edit, status: :unprocessable_entity
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def set_user
|
24
|
+
@user = current_user
|
25
|
+
end
|
26
|
+
|
27
|
+
def user_params
|
28
|
+
params
|
29
|
+
.require(:user)
|
30
|
+
.permit(PERMITTED_PARAMS)
|
31
|
+
.with_defaults(password_challenge: "")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Avocado
|
4
|
+
class RecoveriesController < BaseController
|
5
|
+
PERMITTED_PARAMS = %i[password password_confirmation]
|
6
|
+
|
7
|
+
skip_before_action :authenticate
|
8
|
+
|
9
|
+
before_action :set_user, only: %i[edit update]
|
10
|
+
before_action :verify_user, only: :create
|
11
|
+
|
12
|
+
def new
|
13
|
+
end
|
14
|
+
|
15
|
+
def edit
|
16
|
+
end
|
17
|
+
|
18
|
+
def create
|
19
|
+
send_password_reset_email
|
20
|
+
redirect_to new_session_path, notice: "Check your email for reset instructions."
|
21
|
+
end
|
22
|
+
|
23
|
+
def update
|
24
|
+
if @user.update(user_params)
|
25
|
+
redirect_to new_session_path, notice: "Password reset successfully. Please sign in."
|
26
|
+
else
|
27
|
+
render :edit, status: :unprocessable_entity
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def set_user
|
34
|
+
@user = user_from_signed_password_reset_token
|
35
|
+
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
36
|
+
redirect_to new_recovery_path, alert: "Password reset link is invalid."
|
37
|
+
end
|
38
|
+
|
39
|
+
def user_from_signed_password_reset_token
|
40
|
+
::User.find_by_token_for!(:password_reset, params[:id])
|
41
|
+
end
|
42
|
+
|
43
|
+
def verify_user
|
44
|
+
unless user_from_params_email
|
45
|
+
redirect_to new_recovery_path, alert: "Verify email first before resetting password."
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def user_params
|
50
|
+
params
|
51
|
+
.require(:user)
|
52
|
+
.permit(PERMITTED_PARAMS)
|
53
|
+
end
|
54
|
+
|
55
|
+
def user_from_params_email
|
56
|
+
::User.find_by(email: params[:email], verified: true)
|
57
|
+
end
|
58
|
+
|
59
|
+
def send_password_reset_email
|
60
|
+
mailer_for(user_from_params_email)
|
61
|
+
.password_reset
|
62
|
+
.deliver_later
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Avocado
|
4
|
+
class RegistrationsController < BaseController
|
5
|
+
PERMITTED_PARAMS = %i[email password password_confirmation]
|
6
|
+
|
7
|
+
skip_before_action :authenticate
|
8
|
+
|
9
|
+
def new
|
10
|
+
@user = ::User.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def create
|
14
|
+
@user = ::User.new(user_params)
|
15
|
+
|
16
|
+
if @user.save
|
17
|
+
sign_in(@user)
|
18
|
+
|
19
|
+
send_email_verification
|
20
|
+
redirect_to root_path, notice: "Registration successful"
|
21
|
+
else
|
22
|
+
render :new, status: :unprocessable_entity
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def user_params
|
29
|
+
params
|
30
|
+
.require(:user)
|
31
|
+
.permit(PERMITTED_PARAMS)
|
32
|
+
end
|
33
|
+
|
34
|
+
def send_email_verification
|
35
|
+
mailer_for(@user)
|
36
|
+
.email_verification
|
37
|
+
.deliver_later
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Avocado
|
4
|
+
class SessionsController < BaseController
|
5
|
+
PERMITTED_PARAMS = %i[email password]
|
6
|
+
|
7
|
+
skip_before_action :authenticate, only: %i[new create]
|
8
|
+
|
9
|
+
with_options only: :create do
|
10
|
+
before_action :verify_authentication_attempt
|
11
|
+
end
|
12
|
+
|
13
|
+
before_action :set_session, only: :destroy
|
14
|
+
|
15
|
+
def index
|
16
|
+
@sessions = current_user.sessions.newest_first
|
17
|
+
end
|
18
|
+
|
19
|
+
def new
|
20
|
+
@session = ::Session.new
|
21
|
+
end
|
22
|
+
|
23
|
+
def create
|
24
|
+
sign_in(authenticated_user)
|
25
|
+
|
26
|
+
redirect_to root_path, notice: "Session created"
|
27
|
+
end
|
28
|
+
|
29
|
+
def destroy
|
30
|
+
@session.destroy
|
31
|
+
redirect_to sessions_path, notice: "Session destroyed"
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def session_params
|
37
|
+
params
|
38
|
+
.require(:session)
|
39
|
+
.permit(PERMITTED_PARAMS)
|
40
|
+
.with_defaults(email: "", password: "")
|
41
|
+
end
|
42
|
+
|
43
|
+
def authenticated_user
|
44
|
+
@_authenticated_user ||= ::User.authenticate_by(session_params)
|
45
|
+
end
|
46
|
+
|
47
|
+
def verify_authentication_attempt
|
48
|
+
if authenticated_user.blank?
|
49
|
+
redirect_to new_session_path, alert: "Authentication failed"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def set_session
|
54
|
+
@session = current_user.sessions.find(params[:id])
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Avocado
|
4
|
+
class VerificationsController < BaseController
|
5
|
+
with_options only: :show do
|
6
|
+
skip_before_action :authenticate
|
7
|
+
before_action :set_user
|
8
|
+
end
|
9
|
+
|
10
|
+
def show
|
11
|
+
@user.update! verified: true
|
12
|
+
redirect_to root_path, notice: "Email address verified."
|
13
|
+
end
|
14
|
+
|
15
|
+
def create
|
16
|
+
send_email_verification
|
17
|
+
redirect_to root_path, notice: "Verification email sent to your address."
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def set_user
|
23
|
+
@user = user_from_signed_email_verification_token
|
24
|
+
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
25
|
+
redirect_to root_path, alert: "Email verification link is invalid."
|
26
|
+
end
|
27
|
+
|
28
|
+
def user_from_signed_email_verification_token
|
29
|
+
::User.find_by_token_for!(:email_verification, params[:id])
|
30
|
+
end
|
31
|
+
|
32
|
+
def send_email_verification
|
33
|
+
mailer_for(current_user)
|
34
|
+
.email_verification
|
35
|
+
.deliver_later
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<h2>
|
2
|
+
Sign in without password
|
3
|
+
</h2>
|
4
|
+
|
5
|
+
<p>
|
6
|
+
<%= link_to "Reset your password", new_recovery_path %>
|
7
|
+
</p>
|
8
|
+
|
9
|
+
<%= form_with url: affirmations_path do |form| %>
|
10
|
+
<%= form.label :email %>
|
11
|
+
<%= form.email_field :email, autofocus: true, autocomplete: "email", required: true %>
|
12
|
+
|
13
|
+
<%= form.button "Submit", name: nil %>
|
14
|
+
<% end -%>
|
@@ -0,0 +1,20 @@
|
|
1
|
+
<h2>
|
2
|
+
Change your email address
|
3
|
+
</h2>
|
4
|
+
|
5
|
+
<% if current_user.verified? %>
|
6
|
+
<p>Your email address is verified.</p>
|
7
|
+
<% else %>
|
8
|
+
<p>Your email address is not verified. Check your email and follow the instructions to confirm it's your email address.</p>
|
9
|
+
<p><%= button_to "Re-send verification email", verifications_path %></p>
|
10
|
+
<% end %>
|
11
|
+
|
12
|
+
<%= form_with model: @user, url: email_path, method: :patch do |form| %>
|
13
|
+
<%= form.label :email %>
|
14
|
+
<%= form.email_field :email, autocomplete: "email", required: true %>
|
15
|
+
|
16
|
+
<%= form.label :password_challenge, "Current password" %>
|
17
|
+
<%= form.password_field :password_challenge, autocomplete: "current-password", required: true %>
|
18
|
+
|
19
|
+
<%= form.button "Submit", name: nil %>
|
20
|
+
<% end %>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<h2>
|
2
|
+
Events
|
3
|
+
</h2>
|
4
|
+
|
5
|
+
<table id="events">
|
6
|
+
<thead>
|
7
|
+
<tr>
|
8
|
+
<th scope="col">Action</th>
|
9
|
+
<th scope="col">Created</th>
|
10
|
+
<th scope="col">User Agent</th>
|
11
|
+
<th scope="col">IP Address</th>
|
12
|
+
</tr>
|
13
|
+
</thead>
|
14
|
+
<tbody>
|
15
|
+
<%= render @events %>
|
16
|
+
</tbody>
|
17
|
+
</table>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
<h2>
|
2
|
+
Change your password
|
3
|
+
</h2>
|
4
|
+
|
5
|
+
<%= form_with model: @user, url: password_path, method: :patch do |form| %>
|
6
|
+
<%= form.label :password_challenge, "Current password" %>
|
7
|
+
<%= form.password_field :password_challenge, autocomplete: "current-password", required: true %>
|
8
|
+
|
9
|
+
<%= form.label :password %>
|
10
|
+
<%= form.password_field :password, autocomplete: "new-password", required: true %>
|
11
|
+
|
12
|
+
<%= form.label :password_confirmation %>
|
13
|
+
<%= form.password_field :password_confirmation, autocomplete: "new-password", required: true %>
|
14
|
+
|
15
|
+
<%= form.button "Submit", name: nil %>
|
16
|
+
<% end %>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<h2>
|
2
|
+
Change your password
|
3
|
+
</h2>
|
4
|
+
|
5
|
+
<p>
|
6
|
+
<%= link_to "Sign in to your account" %>
|
7
|
+
</p>
|
8
|
+
|
9
|
+
<%= form_with model: @user, url: recovery_path(id: params[:id]), method: :patch do |form| %>
|
10
|
+
<%= form.label :password %>
|
11
|
+
<%= form.password_field :password, autocomplete: "new-password", required: true %>
|
12
|
+
|
13
|
+
<%= form.label :password_confirmation %>
|
14
|
+
<%= form.password_field :password_confirmation, autocomplete: "new-password", required: true %>
|
15
|
+
|
16
|
+
<%= form.button "Submit", name: nil %>
|
17
|
+
<% end %>
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<h2>
|
2
|
+
Reset your password
|
3
|
+
</h2>
|
4
|
+
|
5
|
+
<p>
|
6
|
+
<%= link_to "Sign in to your account", new_session_path %>
|
7
|
+
</p>
|
8
|
+
|
9
|
+
<%= form_with url: recoveries_path do |form| %>
|
10
|
+
<%= form.label :email %>
|
11
|
+
<%= form.email_field :email, autofocus: true, autocomplete: "email", required: true %>
|
12
|
+
|
13
|
+
<%= form.button "Submit", name: nil %>
|
14
|
+
<% end -%>
|
@@ -0,0 +1,23 @@
|
|
1
|
+
<h2>
|
2
|
+
Sign up for a new account
|
3
|
+
</h2>
|
4
|
+
|
5
|
+
<p>
|
6
|
+
<%= link_to "Sign in to an existing account", new_session_path %>
|
7
|
+
</p>
|
8
|
+
|
9
|
+
<%= form_with model: @user, url: registrations_path do |form| %>
|
10
|
+
<div>
|
11
|
+
<%= form.label :email %>
|
12
|
+
<%= form.email_field :email, autofocus: true, autocomplete: "email", required: true %>
|
13
|
+
</div>
|
14
|
+
<div>
|
15
|
+
<%= form.label :password %>
|
16
|
+
<%= form.password_field :password, autocomplete: "new-password", required: true %>
|
17
|
+
</div>
|
18
|
+
<div>
|
19
|
+
<%= form.label :password_confirmation %>
|
20
|
+
<%= form.password_field :password_confirmation, autocomplete: "new-password", required: true %>
|
21
|
+
</div>
|
22
|
+
<%= form.button "Submit", name: nil %>
|
23
|
+
<% end %>
|
@@ -0,0 +1,21 @@
|
|
1
|
+
<h2>
|
2
|
+
Sessions
|
3
|
+
</h2>
|
4
|
+
|
5
|
+
<div>
|
6
|
+
<table id="sessions">
|
7
|
+
<thead>
|
8
|
+
<tr>
|
9
|
+
<th scope="col">User Agent</th>
|
10
|
+
<th scope="col">IP Address</th>
|
11
|
+
<th scope="col">Created</th>
|
12
|
+
<th scope="col">
|
13
|
+
<span class="sr-only">Edit</span>
|
14
|
+
</th>
|
15
|
+
</tr>
|
16
|
+
</thead>
|
17
|
+
<tbody>
|
18
|
+
<%= render @sessions %>
|
19
|
+
</tbody>
|
20
|
+
</table>
|
21
|
+
</div>
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<h2>
|
2
|
+
Sign in to your account
|
3
|
+
</h2>
|
4
|
+
|
5
|
+
<p>
|
6
|
+
<%= link_to "sign up for a new account", new_registration_path %>
|
7
|
+
</p>
|
8
|
+
|
9
|
+
<%= form_with model: @session do |form| %>
|
10
|
+
<%= form.label :email %>
|
11
|
+
<%= form.email_field :email, autofocus: true, autocomplete: "email", required: true %>
|
12
|
+
<%= form.label :password %>
|
13
|
+
<%= form.password_field :password, autocomplete: "current-password", required: true %>
|
14
|
+
<%= form.button "Submit", name: nil %>
|
15
|
+
<% end -%>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
Rails.application.routes.draw do
|
2
|
+
scope module: :avocado do
|
3
|
+
resource :email, only: %i[edit update]
|
4
|
+
resource :password, only: %i[edit update]
|
5
|
+
resources :affirmations, only: %i[new show create]
|
6
|
+
resources :events, only: %i[index]
|
7
|
+
resources :recoveries, only: %i[new create edit update]
|
8
|
+
resources :registrations, only: %i[new create]
|
9
|
+
resources :sessions, only: %i[index new create destroy]
|
10
|
+
resources :verifications, only: %i[show create]
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Avocado
|
4
|
+
module Authentication
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
before_action :set_current_request_details
|
9
|
+
before_action :authenticate
|
10
|
+
|
11
|
+
helper_method :current_user
|
12
|
+
helper_method :signed_in?
|
13
|
+
helper_method :current_session
|
14
|
+
end
|
15
|
+
|
16
|
+
def current_user
|
17
|
+
Current.user
|
18
|
+
end
|
19
|
+
|
20
|
+
def signed_in?
|
21
|
+
current_user.present?
|
22
|
+
end
|
23
|
+
|
24
|
+
def current_session
|
25
|
+
Current.session
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def authenticate
|
31
|
+
if session_from_token
|
32
|
+
Current.session = session_from_token
|
33
|
+
else
|
34
|
+
redirect_to new_session_path
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def sign_in(user)
|
39
|
+
::Session.create!(user: user).tap do |session|
|
40
|
+
cookies.signed.permanent[:session_token] = {value: session.id, httponly: true}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def session_from_token
|
45
|
+
::Session.find_by_id(cookies.signed[:session_token])
|
46
|
+
end
|
47
|
+
|
48
|
+
def set_current_request_details
|
49
|
+
Current.user_agent = request.user_agent
|
50
|
+
Current.ip_address = request.ip
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Avocado
|
4
|
+
module Event
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
VALID_ACTIONS = [
|
8
|
+
"email:update",
|
9
|
+
"email:verified",
|
10
|
+
"password:update",
|
11
|
+
"session:create",
|
12
|
+
"session:destroy"
|
13
|
+
]
|
14
|
+
|
15
|
+
included do
|
16
|
+
belongs_to :user
|
17
|
+
|
18
|
+
scope :newest_first, -> { order(created_at: :desc) }
|
19
|
+
|
20
|
+
validates :action, inclusion: VALID_ACTIONS
|
21
|
+
|
22
|
+
before_create :capture_request_details
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def capture_request_details
|
28
|
+
self.user_agent = Current.user_agent
|
29
|
+
self.ip_address = Current.ip_address
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/avocado/mailer.rb
CHANGED
@@ -1,17 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "active_support/concern"
|
4
|
-
|
5
3
|
module Avocado
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
included do
|
10
|
-
before_action :set_user
|
11
|
-
before_action :set_signed_id
|
4
|
+
class Mailer < ApplicationMailer
|
5
|
+
before_action :set_user
|
6
|
+
before_action :set_signed_id
|
12
7
|
|
13
|
-
|
14
|
-
end
|
8
|
+
default to: -> { @user.email }
|
15
9
|
|
16
10
|
def email_affirmation
|
17
11
|
mail
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Avocado
|
4
|
+
module Session
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
include SessionCallbacks
|
9
|
+
|
10
|
+
belongs_to :user
|
11
|
+
|
12
|
+
scope :newest_first, -> { order(created_at: :desc) }
|
13
|
+
scope :non_current, -> { where.not(id: Current.session) }
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Avocado
|
4
|
+
module SessionCallbacks
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
after_create :record_activity_create
|
9
|
+
|
10
|
+
after_destroy :record_activity_destroy
|
11
|
+
|
12
|
+
before_create :capture_request_details
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def record_activity_create
|
18
|
+
create_user_event "session:create"
|
19
|
+
end
|
20
|
+
|
21
|
+
def record_activity_destroy
|
22
|
+
create_user_event "session:destroy"
|
23
|
+
end
|
24
|
+
|
25
|
+
def create_user_event(action)
|
26
|
+
user.events.create! action: action
|
27
|
+
end
|
28
|
+
|
29
|
+
def capture_request_details
|
30
|
+
self.user_agent = Current.user_agent
|
31
|
+
self.ip_address = Current.ip_address
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/avocado/user.rb
CHANGED
@@ -1,17 +1,25 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "active_support/concern"
|
4
|
-
|
5
3
|
module Avocado
|
6
4
|
module User
|
7
5
|
extend ActiveSupport::Concern
|
8
6
|
|
9
7
|
included do
|
10
|
-
include
|
11
|
-
include
|
12
|
-
include
|
13
|
-
|
14
|
-
|
8
|
+
include UserCallbacks
|
9
|
+
include UserTokens
|
10
|
+
include UserValidations
|
11
|
+
|
12
|
+
has_secure_password
|
13
|
+
|
14
|
+
with_options dependent: :destroy do
|
15
|
+
has_many :events
|
16
|
+
has_many :sessions
|
17
|
+
end
|
18
|
+
|
19
|
+
scope :newest_first, -> { order(created_at: :desc) }
|
20
|
+
scope :verified, -> { where(verified: true) }
|
21
|
+
|
22
|
+
normalizes :email, with: ->(email) { email.downcase.strip }
|
15
23
|
end
|
16
24
|
end
|
17
25
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Avocado
|
4
|
+
module UserCallbacks
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
with_options if: :password_digest_previously_changed? do
|
9
|
+
after_update :destroy_non_current_sessions
|
10
|
+
after_update :record_activity_password_update
|
11
|
+
end
|
12
|
+
|
13
|
+
after_update :record_activity_email_update, if: :email_previously_changed?
|
14
|
+
after_update :record_activity_email_verified, if: %i[verified_previously_changed? verified?]
|
15
|
+
|
16
|
+
before_validation :remove_email_verification, if: :email_changed?, on: :update
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def record_activity_password_update
|
22
|
+
create_event "password:update"
|
23
|
+
end
|
24
|
+
|
25
|
+
def record_activity_email_update
|
26
|
+
create_event "email:update"
|
27
|
+
end
|
28
|
+
|
29
|
+
def record_activity_email_verified
|
30
|
+
create_event "email:verified"
|
31
|
+
end
|
32
|
+
|
33
|
+
def create_event(action)
|
34
|
+
events.create! action: action
|
35
|
+
end
|
36
|
+
|
37
|
+
def remove_email_verification
|
38
|
+
self.verified = false
|
39
|
+
end
|
40
|
+
|
41
|
+
def destroy_non_current_sessions
|
42
|
+
sessions.non_current.destroy_all
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Avocado
|
4
|
+
module UserTokens
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
EXPIRES_FAST = 16.minutes
|
8
|
+
EXPIRES_LATER = 64.minutes
|
9
|
+
EXPIRES_LONG = 2_048.minutes
|
10
|
+
|
11
|
+
included do
|
12
|
+
generates_token_for :email_affirmation, expires_in: EXPIRES_FAST
|
13
|
+
|
14
|
+
generates_token_for :email_verification, expires_in: EXPIRES_LONG do
|
15
|
+
email
|
16
|
+
end
|
17
|
+
|
18
|
+
generates_token_for :password_reset, expires_in: EXPIRES_LATER do
|
19
|
+
password_digest_salt
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def password_digest_salt
|
26
|
+
password_from_digest.salt[-10..]
|
27
|
+
end
|
28
|
+
|
29
|
+
def password_from_digest
|
30
|
+
BCrypt::Password.new(password_digest)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Avocado
|
4
|
+
module UserValidations
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
PASSWORD_FORMAT = /\A(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9]).*\z/x
|
8
|
+
PASSWORD_MINIMUM_LENGTH = 8
|
9
|
+
|
10
|
+
included do
|
11
|
+
validates :email,
|
12
|
+
presence: true,
|
13
|
+
uniqueness: true,
|
14
|
+
format: {with: URI::MailTo::EMAIL_REGEXP}
|
15
|
+
|
16
|
+
validates :password,
|
17
|
+
format: {with: PASSWORD_FORMAT},
|
18
|
+
length: {minimum: PASSWORD_MINIMUM_LENGTH},
|
19
|
+
allow_nil: true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/avocado/version.rb
CHANGED
data/lib/avocado.rb
CHANGED
@@ -1,15 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "avocado/
|
3
|
+
require_relative "avocado/engine"
|
4
4
|
|
5
5
|
module Avocado
|
6
6
|
class Error < StandardError; end
|
7
7
|
|
8
|
+
autoload :Authentication, "avocado/authentication"
|
9
|
+
autoload :Current, "avocado/current"
|
10
|
+
autoload :Event, "avocado/event"
|
8
11
|
autoload :Mailer, "avocado/mailer"
|
12
|
+
autoload :Session, "avocado/session"
|
13
|
+
autoload :SessionCallbacks, "avocado/session_callbacks"
|
9
14
|
autoload :User, "avocado/user"
|
10
|
-
autoload :
|
11
|
-
autoload :
|
12
|
-
autoload :
|
13
|
-
autoload :UserPassword, "avocado/user_password"
|
14
|
-
autoload :UserPasswordReset, "avocado/user_password_reset"
|
15
|
+
autoload :UserCallbacks, "avocado/user_callbacks"
|
16
|
+
autoload :UserTokens, "avocado/user_tokens"
|
17
|
+
autoload :UserValidations, "avocado/user_validations"
|
15
18
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: avocado
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matt Jankowski
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-07-
|
11
|
+
date: 2023-07-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bcrypt
|
@@ -80,24 +80,52 @@ files:
|
|
80
80
|
- LICENSE.txt
|
81
81
|
- README.md
|
82
82
|
- Rakefile
|
83
|
+
- app/controllers/avocado/affirmations_controller.rb
|
84
|
+
- app/controllers/avocado/base_controller.rb
|
85
|
+
- app/controllers/avocado/emails_controller.rb
|
86
|
+
- app/controllers/avocado/events_controller.rb
|
87
|
+
- app/controllers/avocado/passwords_controller.rb
|
88
|
+
- app/controllers/avocado/recoveries_controller.rb
|
89
|
+
- app/controllers/avocado/registrations_controller.rb
|
90
|
+
- app/controllers/avocado/sessions_controller.rb
|
91
|
+
- app/controllers/avocado/verifications_controller.rb
|
92
|
+
- app/views/avocado/affirmations/new.html.erb
|
93
|
+
- app/views/avocado/emails/edit.html.erb
|
94
|
+
- app/views/avocado/events/_event.html.erb
|
95
|
+
- app/views/avocado/events/index.html.erb
|
96
|
+
- app/views/avocado/mailer/email_affirmation.text.erb
|
97
|
+
- app/views/avocado/mailer/email_verification.text.erb
|
98
|
+
- app/views/avocado/mailer/password_reset.text.erb
|
99
|
+
- app/views/avocado/passwords/edit.html.erb
|
100
|
+
- app/views/avocado/recoveries/edit.html.erb
|
101
|
+
- app/views/avocado/recoveries/new.html.erb
|
102
|
+
- app/views/avocado/registrations/new.html.erb
|
103
|
+
- app/views/avocado/sessions/_session.html.erb
|
104
|
+
- app/views/avocado/sessions/index.html.erb
|
105
|
+
- app/views/avocado/sessions/new.html.erb
|
83
106
|
- config.ru
|
107
|
+
- config/routes.rb
|
84
108
|
- lib/avocado.rb
|
109
|
+
- lib/avocado/authentication.rb
|
110
|
+
- lib/avocado/current.rb
|
111
|
+
- lib/avocado/engine.rb
|
112
|
+
- lib/avocado/event.rb
|
85
113
|
- lib/avocado/mailer.rb
|
114
|
+
- lib/avocado/session.rb
|
115
|
+
- lib/avocado/session_callbacks.rb
|
86
116
|
- lib/avocado/user.rb
|
87
|
-
- lib/avocado/
|
88
|
-
- lib/avocado/
|
89
|
-
- lib/avocado/
|
90
|
-
- lib/avocado/user_password.rb
|
91
|
-
- lib/avocado/user_password_reset.rb
|
117
|
+
- lib/avocado/user_callbacks.rb
|
118
|
+
- lib/avocado/user_tokens.rb
|
119
|
+
- lib/avocado/user_validations.rb
|
92
120
|
- lib/avocado/version.rb
|
93
121
|
- sig/avocado.rbs
|
94
|
-
homepage: https://github.com/
|
122
|
+
homepage: https://github.com/tcuwp/avocado
|
95
123
|
licenses:
|
96
124
|
- MIT
|
97
125
|
metadata:
|
98
|
-
homepage_uri: https://github.com/
|
99
|
-
source_code_uri: https://github.com/
|
100
|
-
changelog_uri: https://github.com/
|
126
|
+
homepage_uri: https://github.com/tcuwp/avocado
|
127
|
+
source_code_uri: https://github.com/tcuwp/avocado
|
128
|
+
changelog_uri: https://github.com/tcuwp/avocado/blob/main/CHANGELOG.md
|
101
129
|
post_install_message:
|
102
130
|
rdoc_options: []
|
103
131
|
require_paths:
|
data/lib/avocado/user_email.rb
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "active_support/concern"
|
4
|
-
|
5
|
-
module Avocado
|
6
|
-
module UserEmail
|
7
|
-
extend ActiveSupport::Concern
|
8
|
-
|
9
|
-
included do
|
10
|
-
validates :email, presence: true, uniqueness: true, format: {with: URI::MailTo::EMAIL_REGEXP}
|
11
|
-
|
12
|
-
normalizes :email, with: ->(email) { email.downcase.strip }
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
@@ -1,15 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "active_support/concern"
|
4
|
-
|
5
|
-
module Avocado
|
6
|
-
module UserEmailAffirmation
|
7
|
-
extend ActiveSupport::Concern
|
8
|
-
|
9
|
-
TOKEN_EXPIRATION = 16.minutes
|
10
|
-
|
11
|
-
included do
|
12
|
-
generates_token_for :email_affirmation, expires_in: TOKEN_EXPIRATION
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
@@ -1,17 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "active_support/concern"
|
4
|
-
|
5
|
-
module Avocado
|
6
|
-
module UserEmailVerification
|
7
|
-
extend ActiveSupport::Concern
|
8
|
-
|
9
|
-
TOKEN_EXPIRATION = 2_048.minutes
|
10
|
-
|
11
|
-
included do
|
12
|
-
generates_token_for :email_verification, expires_in: TOKEN_EXPIRATION do
|
13
|
-
email
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
@@ -1,18 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "active_support/concern"
|
4
|
-
|
5
|
-
module Avocado
|
6
|
-
module UserPassword
|
7
|
-
extend ActiveSupport::Concern
|
8
|
-
|
9
|
-
REQUIRED_FORMAT = /\A(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9]).*\z/x
|
10
|
-
REQUIRED_LENGTH = 8
|
11
|
-
|
12
|
-
included do
|
13
|
-
has_secure_password
|
14
|
-
|
15
|
-
validates :password, format: {with: REQUIRED_FORMAT}, length: {minimum: REQUIRED_LENGTH}, allow_nil: true
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
@@ -1,27 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "active_support/concern"
|
4
|
-
|
5
|
-
module Avocado
|
6
|
-
module UserPasswordReset
|
7
|
-
extend ActiveSupport::Concern
|
8
|
-
|
9
|
-
TOKEN_EXPIRATION = 64.minutes
|
10
|
-
|
11
|
-
included do
|
12
|
-
generates_token_for :password_reset, expires_in: TOKEN_EXPIRATION do
|
13
|
-
password_digest_salt
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
private
|
18
|
-
|
19
|
-
def password_digest_salt
|
20
|
-
password_from_digest.salt[-10..]
|
21
|
-
end
|
22
|
-
|
23
|
-
def password_from_digest
|
24
|
-
BCrypt::Password.new(password_digest)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
end
|