avocado 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|