avocado 0.4.0 → 0.6.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 +8 -39
- data/app/controllers/avocado/affirmations_controller.rb +50 -0
- data/app/controllers/avocado/base_controller.rb +22 -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 +9 -13
- data/app/controllers/avocado/registrations_controller.rb +4 -4
- data/app/controllers/avocado/sessions_controller.rb +4 -4
- data/app/controllers/avocado/verifications_controller.rb +5 -2
- data/app/views/avocado/affirmations/edit.html.erb +7 -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 -1
- data/app/views/avocado/mailer/email_verification.text.erb +2 -2
- data/app/views/avocado/mailer/password_reset.text.erb +1 -1
- data/app/views/avocado/passwords/edit.html.erb +16 -0
- data/app/views/avocado/recoveries/edit.html.erb +2 -2
- data/app/views/avocado/recoveries/new.html.erb +3 -3
- data/app/views/avocado/registrations/new.html.erb +1 -1
- data/app/views/avocado/sessions/new.html.erb +1 -1
- data/app/views/avocado/verifications/edit.html.erb +7 -0
- data/config/routes/avocado.rb +10 -0
- data/config/routes//360/237/245/221.rb +1 -0
- data/docs/USAGE.md +155 -0
- data/lib/avocado/authentication.rb +2 -2
- data/lib/avocado/current.rb +10 -8
- data/lib/avocado/engine.rb +5 -0
- data/lib/avocado/event.rb +32 -0
- data/lib/avocado/session.rb +9 -0
- data/lib/avocado/session_callbacks.rb +34 -0
- data/lib/avocado/user.rb +10 -3
- data/lib/avocado/user_callbacks.rb +45 -0
- data/lib/avocado/user_validations.rb +22 -0
- data/lib/avocado/version.rb +1 -1
- data/lib/avocado.rb +5 -2
- data/lib/generators/avocado/migrations/migrations_generator.rb +36 -0
- data/lib/generators/avocado/migrations/templates/create_events.rb.tt +12 -0
- data/lib/generators/avocado/migrations/templates/create_sessions.rb.tt +12 -0
- data/lib/generators/avocado/migrations/templates/create_users.rb.tt +11 -0
- metadata +28 -37
- data/config/routes.rb +0 -8
- data/lib/avocado/user_email.rb +0 -13
- data/lib/avocado/user_password.rb +0 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 393630ca933c51e34b00e2fe86bbcfb5caa4ea7764fc34882c21ef4f604bb938
|
4
|
+
data.tar.gz: bfc5362c53ecee86c6369a1318c68fddf2bbd7e457287cb5b9c6b57970ded8aa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: efbe7bfe5b298b207e65e7958e3167be69d6612bd50e8a4a880483ec1a1d9e2c30d45fdae86e0516d34bb00442c0cd16416ad77a00a50ac31c707295d1538709
|
7
|
+
data.tar.gz: b96ba925c445b78c92c560a01c395697dab736363a847c766e310879ded4ad58b97b7dae052a73ed1a267f7315bedd171d9f0097978760cb3de6a1e9b3c02969
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,19 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.6.0] - 2023-07-25
|
4
|
+
|
5
|
+
- Change affirmations and verifications to require user action
|
6
|
+
- Use session token instead of id for signed cookie value
|
7
|
+
- Add migration generators
|
8
|
+
|
9
|
+
## [0.5.0] - 2023-07-21
|
10
|
+
|
11
|
+
- Add controller for "passwordless" email-link sign-in
|
12
|
+
- Add event class to log user auth events
|
13
|
+
- Add user-facing email and password edit pages
|
14
|
+
- Add various event logging callbacks
|
15
|
+
- Sign out all non current sessions when password changes
|
16
|
+
|
3
17
|
## [0.4.0] - 2023-07-19
|
4
18
|
|
5
19
|
- Convert the `Avocado::Mailer` module into a class
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
#
|
1
|
+
# 🥑
|
2
2
|
|
3
|
-
|
3
|
+
Authentication library for [Rails] 7.1+ applications.
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
|
@@ -10,45 +10,13 @@ Add to the application's Gemfile by executing:
|
|
10
10
|
|
11
11
|
## Usage
|
12
12
|
|
13
|
-
|
14
|
-
such features via a packaged gem, you can include some Avocado modules into your
|
15
|
-
application to get authentication functionality.
|
16
|
-
|
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.
|
20
|
-
|
21
|
-
With that set, include the modules into your classes:
|
22
|
-
|
23
|
-
```ruby
|
24
|
-
class User < ApplicationRecord
|
25
|
-
include Avocado::User
|
26
|
-
end
|
27
|
-
|
28
|
-
class Session < ApplicationRecord
|
29
|
-
include Avocado::Session
|
30
|
-
end
|
31
|
-
|
32
|
-
class ApplicationController < ActionController::Base
|
33
|
-
include Avocado::Authentication
|
34
|
-
end
|
35
|
-
```
|
36
|
-
|
37
|
-
This will enable a few things:
|
38
|
-
|
39
|
-
- Models will get validations, associations, and normalizations
|
40
|
-
- Rails built-in `has_secure_password` is called within `User`
|
41
|
-
- A mailer with signed token generators is created
|
42
|
-
- Controllers and Routes for sign up, sign in, password reset, email
|
43
|
-
verification, etc
|
44
|
-
|
45
|
-
The `spec/internal` app within this repo has some example usage.
|
13
|
+
Read the [documentation] for more details or the [wiki] for background.
|
46
14
|
|
47
15
|
## Development
|
48
16
|
|
49
|
-
After checking out the repo, run `bin/setup` to install dependencies.
|
50
|
-
`
|
51
|
-
|
17
|
+
After checking out the repo, run `bin/setup` to install dependencies. Use
|
18
|
+
`bin/rspec` to run the full spec suite and `bin/standardrb` to run the linter.
|
19
|
+
Running `bin/rake` will run specs & linter.
|
52
20
|
|
53
21
|
## Contributing
|
54
22
|
|
@@ -58,7 +26,8 @@ Bug reports and pull requests are welcome on [GitHub].
|
|
58
26
|
|
59
27
|
The gem is available as open source under the terms of the [MIT License].
|
60
28
|
|
29
|
+
[documentation]: https://github.com/tcuwp/avocado/blob/main/docs/USAGE.md
|
61
30
|
[GitHub]: https://github.com/tcuwp/avocado
|
62
31
|
[MIT License]: https://opensource.org/licenses/MIT
|
63
32
|
[Rails]: https://github.com/rails/rails
|
64
|
-
[
|
33
|
+
[wiki]: https://github.com/tcuwp/avocado/wiki
|
@@ -0,0 +1,50 @@
|
|
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: %i[edit update]
|
8
|
+
before_action :verify_user, only: :create
|
9
|
+
|
10
|
+
def new
|
11
|
+
end
|
12
|
+
|
13
|
+
def create
|
14
|
+
send_affirmation_email
|
15
|
+
redirect_to new_session_path, notice: "Check your email for sign in instructions"
|
16
|
+
end
|
17
|
+
|
18
|
+
def edit
|
19
|
+
end
|
20
|
+
|
21
|
+
def update
|
22
|
+
sign_in(@user)
|
23
|
+
redirect_to(root_path, notice: "Signed in successfully")
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def set_user
|
29
|
+
@user = user_from_signed_affirmation_token
|
30
|
+
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
31
|
+
redirect_to new_affirmation_path, alert: "That sign in link is invalid"
|
32
|
+
end
|
33
|
+
|
34
|
+
def user_from_signed_affirmation_token
|
35
|
+
::User.find_by_token_for!(:email_affirmation, params[:id])
|
36
|
+
end
|
37
|
+
|
38
|
+
def verify_user
|
39
|
+
unless requested_verified_user
|
40
|
+
redirect_to new_affirmation_path, alert: "You can't sign in until you verify your email"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def send_affirmation_email
|
45
|
+
mailer_for(requested_verified_user)
|
46
|
+
.email_affirmation
|
47
|
+
.deliver_later
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -2,8 +2,30 @@
|
|
2
2
|
|
3
3
|
module Avocado
|
4
4
|
class BaseController < ApplicationController
|
5
|
+
FINDER_PARAMETERS = %i[email]
|
6
|
+
|
5
7
|
private
|
6
8
|
|
9
|
+
def verify_password_challenge
|
10
|
+
unless current_user.authenticate(params_password_challenge)
|
11
|
+
redirect_back alert: "Password challenge failed.", fallback_location: root_path
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def params_password_challenge
|
16
|
+
params.dig(:user, :password_challenge)
|
17
|
+
end
|
18
|
+
|
19
|
+
def requested_verified_user
|
20
|
+
::User.verified.find_by(email: finder_parameters[:email])
|
21
|
+
end
|
22
|
+
|
23
|
+
def finder_parameters
|
24
|
+
params
|
25
|
+
.require(:user)
|
26
|
+
.permit(FINDER_PARAMETERS)
|
27
|
+
end
|
28
|
+
|
7
29
|
def mailer_for(user)
|
8
30
|
Avocado::Mailer.with(user: user)
|
9
31
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Avocado
|
4
|
+
class EmailsController < BaseController
|
5
|
+
UPDATE_PARAMETERS = %i[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(update_parameters)
|
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 update_parameters
|
28
|
+
params
|
29
|
+
.require(:user)
|
30
|
+
.permit(UPDATE_PARAMETERS)
|
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
|
+
UPDATE_PARAMETERS = %i[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(update_parameters)
|
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 update_parameters
|
28
|
+
params
|
29
|
+
.require(:user)
|
30
|
+
.permit(UPDATE_PARAMETERS)
|
31
|
+
.with_defaults(password_challenge: "")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Avocado
|
4
4
|
class RecoveriesController < BaseController
|
5
|
-
|
5
|
+
UPDATE_PARAMETERS = %i[password password_confirmation]
|
6
6
|
|
7
7
|
skip_before_action :authenticate
|
8
8
|
|
@@ -12,16 +12,16 @@ module Avocado
|
|
12
12
|
def new
|
13
13
|
end
|
14
14
|
|
15
|
-
def edit
|
16
|
-
end
|
17
|
-
|
18
15
|
def create
|
19
16
|
send_password_reset_email
|
20
17
|
redirect_to new_session_path, notice: "Check your email for reset instructions."
|
21
18
|
end
|
22
19
|
|
20
|
+
def edit
|
21
|
+
end
|
22
|
+
|
23
23
|
def update
|
24
|
-
if @user.update(
|
24
|
+
if @user.update(update_parameters)
|
25
25
|
redirect_to new_session_path, notice: "Password reset successfully. Please sign in."
|
26
26
|
else
|
27
27
|
render :edit, status: :unprocessable_entity
|
@@ -41,23 +41,19 @@ module Avocado
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def verify_user
|
44
|
-
unless
|
44
|
+
unless requested_verified_user
|
45
45
|
redirect_to new_recovery_path, alert: "Verify email first before resetting password."
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
-
def
|
49
|
+
def update_parameters
|
50
50
|
params
|
51
51
|
.require(:user)
|
52
|
-
.permit(
|
53
|
-
end
|
54
|
-
|
55
|
-
def user_from_params_email
|
56
|
-
::User.find_by(email: params[:email], verified: true)
|
52
|
+
.permit(UPDATE_PARAMETERS)
|
57
53
|
end
|
58
54
|
|
59
55
|
def send_password_reset_email
|
60
|
-
mailer_for(
|
56
|
+
mailer_for(requested_verified_user)
|
61
57
|
.password_reset
|
62
58
|
.deliver_later
|
63
59
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Avocado
|
4
4
|
class RegistrationsController < BaseController
|
5
|
-
|
5
|
+
INITIALIZATION_PARAMETERS = %i[email password password_confirmation]
|
6
6
|
|
7
7
|
skip_before_action :authenticate
|
8
8
|
|
@@ -11,7 +11,7 @@ module Avocado
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def create
|
14
|
-
@user = ::User.new(
|
14
|
+
@user = ::User.new(initialization_parameters)
|
15
15
|
|
16
16
|
if @user.save
|
17
17
|
sign_in(@user)
|
@@ -25,10 +25,10 @@ module Avocado
|
|
25
25
|
|
26
26
|
private
|
27
27
|
|
28
|
-
def
|
28
|
+
def initialization_parameters
|
29
29
|
params
|
30
30
|
.require(:user)
|
31
|
-
.permit(
|
31
|
+
.permit(INITIALIZATION_PARAMETERS)
|
32
32
|
end
|
33
33
|
|
34
34
|
def send_email_verification
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Avocado
|
4
4
|
class SessionsController < BaseController
|
5
|
-
|
5
|
+
AUTHENTICATION_PARAMETERS = %i[email password]
|
6
6
|
|
7
7
|
skip_before_action :authenticate, only: %i[new create]
|
8
8
|
|
@@ -33,15 +33,15 @@ module Avocado
|
|
33
33
|
|
34
34
|
private
|
35
35
|
|
36
|
-
def
|
36
|
+
def authentication_parameters
|
37
37
|
params
|
38
38
|
.require(:session)
|
39
|
-
.permit(
|
39
|
+
.permit(AUTHENTICATION_PARAMETERS)
|
40
40
|
.with_defaults(email: "", password: "")
|
41
41
|
end
|
42
42
|
|
43
43
|
def authenticated_user
|
44
|
-
@_authenticated_user ||= ::User.authenticate_by(
|
44
|
+
@_authenticated_user ||= ::User.authenticate_by(authentication_parameters)
|
45
45
|
end
|
46
46
|
|
47
47
|
def verify_authentication_attempt
|
@@ -2,12 +2,15 @@
|
|
2
2
|
|
3
3
|
module Avocado
|
4
4
|
class VerificationsController < BaseController
|
5
|
-
with_options only:
|
5
|
+
with_options only: %i[edit update] do
|
6
6
|
skip_before_action :authenticate
|
7
7
|
before_action :set_user
|
8
8
|
end
|
9
9
|
|
10
|
-
def
|
10
|
+
def edit
|
11
|
+
end
|
12
|
+
|
13
|
+
def update
|
11
14
|
@user.update! verified: true
|
12
15
|
redirect_to root_path, notice: "Email address verified."
|
13
16
|
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, scope: :user 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>
|
@@ -1,3 +1,3 @@
|
|
1
|
-
|
1
|
+
Verify your email by following this link:
|
2
2
|
|
3
|
-
<%=
|
3
|
+
<%= edit_verification_url(id: @signed_id) %>
|
@@ -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 %>
|
@@ -1,5 +1,5 @@
|
|
1
1
|
<h2>
|
2
|
-
|
2
|
+
Change your password
|
3
3
|
</h2>
|
4
4
|
|
5
5
|
<p>
|
@@ -13,5 +13,5 @@
|
|
13
13
|
<%= form.label :password_confirmation %>
|
14
14
|
<%= form.password_field :password_confirmation, autocomplete: "new-password", required: true %>
|
15
15
|
|
16
|
-
<%= form.button "
|
16
|
+
<%= form.button "Submit", name: nil %>
|
17
17
|
<% end %>
|
@@ -1,14 +1,14 @@
|
|
1
1
|
<h2>
|
2
|
-
|
2
|
+
Reset your password
|
3
3
|
</h2>
|
4
4
|
|
5
5
|
<p>
|
6
6
|
<%= link_to "Sign in to your account", new_session_path %>
|
7
7
|
</p>
|
8
8
|
|
9
|
-
<%= form_with url: recoveries_path do |form| %>
|
9
|
+
<%= form_with url: recoveries_path, scope: :user do |form| %>
|
10
10
|
<%= form.label :email %>
|
11
11
|
<%= form.email_field :email, autofocus: true, autocomplete: "email", required: true %>
|
12
12
|
|
13
|
-
<%= form.button "
|
13
|
+
<%= form.button "Submit", name: nil %>
|
14
14
|
<% end -%>
|
@@ -11,5 +11,5 @@
|
|
11
11
|
<%= form.email_field :email, autofocus: true, autocomplete: "email", required: true %>
|
12
12
|
<%= form.label :password %>
|
13
13
|
<%= form.password_field :password, autocomplete: "current-password", required: true %>
|
14
|
-
<%= form.button "
|
14
|
+
<%= form.button "Submit", name: nil %>
|
15
15
|
<% end -%>
|
@@ -0,0 +1,10 @@
|
|
1
|
+
scope module: :avocado do
|
2
|
+
resource :email, only: %i[edit update]
|
3
|
+
resource :password, only: %i[edit update]
|
4
|
+
resources :affirmations, only: %i[new create edit update]
|
5
|
+
resources :events, only: %i[index]
|
6
|
+
resources :recoveries, only: %i[new create edit update]
|
7
|
+
resources :registrations, only: %i[new create]
|
8
|
+
resources :sessions, only: %i[index new create destroy]
|
9
|
+
resources :verifications, only: %i[create edit update]
|
10
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
avocado.rb
|
data/docs/USAGE.md
ADDED
@@ -0,0 +1,155 @@
|
|
1
|
+
# Overview
|
2
|
+
|
3
|
+
The 🥑 gem is a [Rails Engine] composed of a mixture of Ruby modules that get
|
4
|
+
included into application classes and Ruby classes which will run directly from
|
5
|
+
the 🥑 gem itself. The classes are intended to provide good defaults for basic
|
6
|
+
scenarios, and can be subclassed and overridden for special cases.
|
7
|
+
|
8
|
+
## Requirements
|
9
|
+
|
10
|
+
Apps must be running Rails 7.1 or newer. The 🥑 gem uses features like
|
11
|
+
`authenticate_by`, `has_secure_password`, `generates_token_for`, and
|
12
|
+
`normalizes` which don't exist in earlier versions.
|
13
|
+
|
14
|
+
The database schema must have columns that match the `users`, `sessions`, and
|
15
|
+
`events` tables from the [demo app schema]. More columns in each table are
|
16
|
+
acceptable; the demo is just a minimum. Slight variations (using `uuid` instead
|
17
|
+
of `bigint` for example) are harmless, but large departures will break the
|
18
|
+
integration.
|
19
|
+
|
20
|
+
Run `bin/rails g avocado:migrations` to generate migrations for the tables.
|
21
|
+
|
22
|
+
The application must also have:
|
23
|
+
|
24
|
+
- An `ApplicationController` base controller class
|
25
|
+
- An `ApplicationMailer` base mailer class
|
26
|
+
- A `root_path` method (typically generated by application routes)
|
27
|
+
|
28
|
+
## Usage
|
29
|
+
|
30
|
+
### Models
|
31
|
+
|
32
|
+
Include these modules into `ActiveRecord` model classes:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
class User < ApplicationRecord
|
36
|
+
include Avocado::User
|
37
|
+
end
|
38
|
+
|
39
|
+
class Session < ApplicationRecord
|
40
|
+
include Avocado::Session
|
41
|
+
end
|
42
|
+
|
43
|
+
class Event < ApplicationRecord
|
44
|
+
include Avocado::Event
|
45
|
+
end
|
46
|
+
```
|
47
|
+
|
48
|
+
This will set up some basic associations, validations, callbacks, and
|
49
|
+
normalizations for those models.
|
50
|
+
|
51
|
+
### Controllers
|
52
|
+
|
53
|
+
Add the `Avocado::Authentication` module to the top-level controller:
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
class ApplicationController < ActionController::Base
|
57
|
+
include Avocado::Authentication
|
58
|
+
end
|
59
|
+
```
|
60
|
+
|
61
|
+
### Routes
|
62
|
+
|
63
|
+
The 🥑 gem does not add any routes to the application when initialized. To hook
|
64
|
+
up the controllers to routes, they must be added to the `config/routes.rb` of
|
65
|
+
the application. It's possible to add all of the routes, or just a subset.
|
66
|
+
|
67
|
+
Example that defines a root route and also pulls in every feature route:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
Rails.application.routes.draw do
|
71
|
+
root to: "records#index"
|
72
|
+
draw(🥑)
|
73
|
+
end
|
74
|
+
```
|
75
|
+
|
76
|
+
Example that adds only the sign-up, sign-in, and sign-out actions:
|
77
|
+
|
78
|
+
```ruby
|
79
|
+
Rails.application.routes.draw do
|
80
|
+
root to: "records#index"
|
81
|
+
scope module: :avocado do
|
82
|
+
resources :registrations, only: %i[new create]
|
83
|
+
resources :sessions, only: %i[new create destroy]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
```
|
87
|
+
|
88
|
+
## Summary
|
89
|
+
|
90
|
+
### Routable controller features
|
91
|
+
|
92
|
+
The 🥑 gem will create REST-ful routes that go to the various controllers during
|
93
|
+
app initialization.
|
94
|
+
|
95
|
+
These external (unauthenticated) features are available:
|
96
|
+
|
97
|
+
- `Registrations` -- Fill out new form and create users
|
98
|
+
- `Sessions` -- Sign in and sign out features
|
99
|
+
- `Recoveries` -- Trigger password reset, click link, confirm
|
100
|
+
- `Verifications` -- Email confirmation on account creation or email change
|
101
|
+
- `Affirmations` -- Provides a "passwordless" auth via emailed link
|
102
|
+
|
103
|
+
These internal (authenticated) features are available:
|
104
|
+
|
105
|
+
- `Sessions` -- List active sessions, click to destroy untrusted ones
|
106
|
+
- `Events` -- List view of user activity audit log
|
107
|
+
- `Passwords` -- Edit and update user password
|
108
|
+
- `Emails` -- Edit and update user email
|
109
|
+
|
110
|
+
Linking to any of these internal pages is optional. Apps can use them as-is,
|
111
|
+
override their views, or even ignore them entirely and make local versions.
|
112
|
+
|
113
|
+
### Mailers
|
114
|
+
|
115
|
+
There is an `Avocado::Mailer` which gets called to send emails. The mailer views
|
116
|
+
here are very basic, and should be overriden within applications. Place views
|
117
|
+
within `app/views/avocado/mailer/` to make this happen.
|
118
|
+
|
119
|
+
### Before actions
|
120
|
+
|
121
|
+
There is an `authenticate` method installed as a default `before_action`. Any
|
122
|
+
actions which do not need to be authenticated should disable this with
|
123
|
+
`skip_before_action`.
|
124
|
+
|
125
|
+
There is a `set_current_request_details` method installed as a default
|
126
|
+
`before_action` which takes some loggable request meta information (user agent,
|
127
|
+
IP address) and sets its value in `Current` so that its accesible to code
|
128
|
+
elsewhere in the 🥑 gem.
|
129
|
+
|
130
|
+
### Helpers
|
131
|
+
|
132
|
+
The `Avocado::Authentication` module included into the application controller
|
133
|
+
provides some helper methods available in controllers, views, and helpers:
|
134
|
+
|
135
|
+
- `signed_in?` is true if the session has a signed in user
|
136
|
+
- `current_session` provides the DB record for the session, if one exists
|
137
|
+
- `current_user` returns the user belonging to that session
|
138
|
+
|
139
|
+
Usage of these can be seen in the views in the [demo app].
|
140
|
+
|
141
|
+
## Customization
|
142
|
+
|
143
|
+
There is not any configuration. To override functionality:
|
144
|
+
|
145
|
+
- Redefine a method created in one of the models by the included module
|
146
|
+
- Subclass a controller and update the routing to go to the subclass
|
147
|
+
- Place views in the app where avocado expects them to override the defaults
|
148
|
+
|
149
|
+
## Examples
|
150
|
+
|
151
|
+
There is a [demo app] used by the specs which has some example usage.
|
152
|
+
|
153
|
+
[demo app schema]: https://github.com/tcuwp/avocado/blob/main/spec/internal/db/schema.rb
|
154
|
+
[demo app]: https://github.com/tcuwp/avocado/blob/main/spec/internal
|
155
|
+
[Rails Engine]: https://guides.rubyonrails.org/engines.html#what-are-engines-questionmark
|
@@ -37,12 +37,12 @@ module Avocado
|
|
37
37
|
|
38
38
|
def sign_in(user)
|
39
39
|
::Session.create!(user: user).tap do |session|
|
40
|
-
cookies.signed.permanent[:session_token] = {value: session.
|
40
|
+
cookies.signed.permanent[:session_token] = {value: session.token, httponly: true}
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
44
|
def session_from_token
|
45
|
-
::Session.
|
45
|
+
::Session.find_by_token(cookies.signed[:session_token])
|
46
46
|
end
|
47
47
|
|
48
48
|
def set_current_request_details
|
data/lib/avocado/current.rb
CHANGED
@@ -1,13 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
:
|
6
|
-
|
7
|
-
|
3
|
+
module Avocado
|
4
|
+
class Current < ActiveSupport::CurrentAttributes
|
5
|
+
attribute :session,
|
6
|
+
:user,
|
7
|
+
:user_agent,
|
8
|
+
:ip_address
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
10
|
+
def session=(session)
|
11
|
+
super
|
12
|
+
self.user = session.user
|
13
|
+
end
|
12
14
|
end
|
13
15
|
end
|
data/lib/avocado/engine.rb
CHANGED
@@ -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/session.rb
CHANGED
@@ -4,10 +4,19 @@ module Avocado
|
|
4
4
|
module Session
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
|
+
SECURE_TOKEN_LENGTH = 64
|
8
|
+
|
7
9
|
included do
|
10
|
+
include SessionCallbacks
|
11
|
+
|
12
|
+
has_secure_token length: SECURE_TOKEN_LENGTH, on: :initialize
|
13
|
+
|
8
14
|
belongs_to :user
|
9
15
|
|
16
|
+
validates :token, presence: true
|
17
|
+
|
10
18
|
scope :newest_first, -> { order(created_at: :desc) }
|
19
|
+
scope :non_current, -> { where.not(id: Current.session) }
|
11
20
|
end
|
12
21
|
end
|
13
22
|
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
@@ -5,14 +5,21 @@ module Avocado
|
|
5
5
|
extend ActiveSupport::Concern
|
6
6
|
|
7
7
|
included do
|
8
|
-
include
|
8
|
+
include UserCallbacks
|
9
9
|
include UserTokens
|
10
|
-
include
|
10
|
+
include UserValidations
|
11
11
|
|
12
|
-
|
12
|
+
has_secure_password
|
13
|
+
|
14
|
+
with_options dependent: :destroy do
|
15
|
+
has_many :events
|
16
|
+
has_many :sessions
|
17
|
+
end
|
13
18
|
|
14
19
|
scope :newest_first, -> { order(created_at: :desc) }
|
15
20
|
scope :verified, -> { where(verified: true) }
|
21
|
+
|
22
|
+
normalizes :email, with: ->(email) { email.downcase.strip }
|
16
23
|
end
|
17
24
|
end
|
18
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,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,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_support"
|
3
4
|
require_relative "avocado/engine"
|
4
5
|
|
5
6
|
module Avocado
|
@@ -7,10 +8,12 @@ module Avocado
|
|
7
8
|
|
8
9
|
autoload :Authentication, "avocado/authentication"
|
9
10
|
autoload :Current, "avocado/current"
|
11
|
+
autoload :Event, "avocado/event"
|
10
12
|
autoload :Mailer, "avocado/mailer"
|
11
13
|
autoload :Session, "avocado/session"
|
14
|
+
autoload :SessionCallbacks, "avocado/session_callbacks"
|
12
15
|
autoload :User, "avocado/user"
|
13
|
-
autoload :
|
16
|
+
autoload :UserCallbacks, "avocado/user_callbacks"
|
14
17
|
autoload :UserTokens, "avocado/user_tokens"
|
15
|
-
autoload :
|
18
|
+
autoload :UserValidations, "avocado/user_validations"
|
16
19
|
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/generators/base"
|
4
|
+
require "rails/generators/active_record"
|
5
|
+
|
6
|
+
module Avocado
|
7
|
+
class MigrationsGenerator < Rails::Generators::Base
|
8
|
+
include ActiveRecord::Generators::Migration
|
9
|
+
|
10
|
+
source_root File.expand_path("templates", __dir__)
|
11
|
+
|
12
|
+
def create_migrations
|
13
|
+
migration_template "create_users.rb", "#{db_migrate_path}/create_users.rb"
|
14
|
+
migration_template "create_sessions.rb", "#{db_migrate_path}/create_sessions.rb"
|
15
|
+
migration_template "create_events.rb", "#{db_migrate_path}/create_events.rb"
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def primary_and_foreign_key_types
|
21
|
+
config = Rails.configuration.generators
|
22
|
+
setting = config.options[config.orm][:primary_key_type]
|
23
|
+
primary_key_type = setting || :primary_key
|
24
|
+
foreign_key_type = setting || :bigint
|
25
|
+
[primary_key_type, foreign_key_type]
|
26
|
+
end
|
27
|
+
|
28
|
+
def primary_key_type
|
29
|
+
primary_and_foreign_key_types.first
|
30
|
+
end
|
31
|
+
|
32
|
+
def foreign_key_type
|
33
|
+
primary_and_foreign_key_types.last
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
2
|
+
def change
|
3
|
+
create_table :events, id: :<%= primary_key_type %> do |t|
|
4
|
+
t.references :user, null: false, foreign_key: true, type: :<%= foreign_key_type %>
|
5
|
+
t.string :action, null: false
|
6
|
+
t.string :user_agent
|
7
|
+
t.string :ip_address
|
8
|
+
|
9
|
+
t.timestamps
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
2
|
+
def change
|
3
|
+
create_table :sessions, id: :<%= primary_key_type %> do |t|
|
4
|
+
t.references :user, null: false, foreign_key: true, type: :<%= foreign_key_type %>
|
5
|
+
t.string :token, null: false, index: {unique: true}
|
6
|
+
t.string :user_agent
|
7
|
+
t.string :ip_address
|
8
|
+
|
9
|
+
t.timestamps
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
2
|
+
def change
|
3
|
+
create_table :users, id: :<%= primary_key_type %> do |t|
|
4
|
+
t.string :email, null: false, index: {unique: true}
|
5
|
+
t.string :password_digest, null: false
|
6
|
+
t.boolean :verified, null: false, default: false, index: true
|
7
|
+
|
8
|
+
t.timestamps
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: avocado
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.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-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bcrypt
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '3.1'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '3.1'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rails
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -38,34 +38,6 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: 7.1.0.alpha
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: combustion
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - "~>"
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '1.3'
|
48
|
-
type: :development
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - "~>"
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '1.3'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: sqlite3
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - ">="
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '0'
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - ">="
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '0'
|
69
41
|
description:
|
70
42
|
email:
|
71
43
|
- matt@jankowski.online
|
@@ -80,33 +52,52 @@ files:
|
|
80
52
|
- LICENSE.txt
|
81
53
|
- README.md
|
82
54
|
- Rakefile
|
55
|
+
- app/controllers/avocado/affirmations_controller.rb
|
83
56
|
- app/controllers/avocado/base_controller.rb
|
57
|
+
- app/controllers/avocado/emails_controller.rb
|
58
|
+
- app/controllers/avocado/events_controller.rb
|
59
|
+
- app/controllers/avocado/passwords_controller.rb
|
84
60
|
- app/controllers/avocado/recoveries_controller.rb
|
85
61
|
- app/controllers/avocado/registrations_controller.rb
|
86
62
|
- app/controllers/avocado/sessions_controller.rb
|
87
63
|
- app/controllers/avocado/verifications_controller.rb
|
64
|
+
- app/views/avocado/affirmations/edit.html.erb
|
65
|
+
- app/views/avocado/affirmations/new.html.erb
|
66
|
+
- app/views/avocado/emails/edit.html.erb
|
67
|
+
- app/views/avocado/events/_event.html.erb
|
68
|
+
- app/views/avocado/events/index.html.erb
|
88
69
|
- app/views/avocado/mailer/email_affirmation.text.erb
|
89
70
|
- app/views/avocado/mailer/email_verification.text.erb
|
90
71
|
- app/views/avocado/mailer/password_reset.text.erb
|
72
|
+
- app/views/avocado/passwords/edit.html.erb
|
91
73
|
- app/views/avocado/recoveries/edit.html.erb
|
92
74
|
- app/views/avocado/recoveries/new.html.erb
|
93
75
|
- app/views/avocado/registrations/new.html.erb
|
94
76
|
- app/views/avocado/sessions/_session.html.erb
|
95
77
|
- app/views/avocado/sessions/index.html.erb
|
96
78
|
- app/views/avocado/sessions/new.html.erb
|
79
|
+
- app/views/avocado/verifications/edit.html.erb
|
97
80
|
- config.ru
|
98
|
-
- config/routes.rb
|
81
|
+
- config/routes/avocado.rb
|
82
|
+
- "config/routes/\U0001F951.rb"
|
83
|
+
- docs/USAGE.md
|
99
84
|
- lib/avocado.rb
|
100
85
|
- lib/avocado/authentication.rb
|
101
86
|
- lib/avocado/current.rb
|
102
87
|
- lib/avocado/engine.rb
|
88
|
+
- lib/avocado/event.rb
|
103
89
|
- lib/avocado/mailer.rb
|
104
90
|
- lib/avocado/session.rb
|
91
|
+
- lib/avocado/session_callbacks.rb
|
105
92
|
- lib/avocado/user.rb
|
106
|
-
- lib/avocado/
|
107
|
-
- lib/avocado/user_password.rb
|
93
|
+
- lib/avocado/user_callbacks.rb
|
108
94
|
- lib/avocado/user_tokens.rb
|
95
|
+
- lib/avocado/user_validations.rb
|
109
96
|
- lib/avocado/version.rb
|
97
|
+
- lib/generators/avocado/migrations/migrations_generator.rb
|
98
|
+
- lib/generators/avocado/migrations/templates/create_events.rb.tt
|
99
|
+
- lib/generators/avocado/migrations/templates/create_sessions.rb.tt
|
100
|
+
- lib/generators/avocado/migrations/templates/create_users.rb.tt
|
110
101
|
- sig/avocado.rbs
|
111
102
|
homepage: https://github.com/tcuwp/avocado
|
112
103
|
licenses:
|
data/config/routes.rb
DELETED
@@ -1,8 +0,0 @@
|
|
1
|
-
Rails.application.routes.draw do
|
2
|
-
scope module: :avocado do
|
3
|
-
resources :recoveries, only: %i[new create edit update]
|
4
|
-
resources :registrations, only: %i[new create]
|
5
|
-
resources :sessions, only: %i[index new create destroy]
|
6
|
-
resources :verifications, only: %i[show create]
|
7
|
-
end
|
8
|
-
end
|
data/lib/avocado/user_email.rb
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Avocado
|
4
|
-
module UserEmail
|
5
|
-
extend ActiveSupport::Concern
|
6
|
-
|
7
|
-
included do
|
8
|
-
validates :email, presence: true, uniqueness: true, format: {with: URI::MailTo::EMAIL_REGEXP}
|
9
|
-
|
10
|
-
normalizes :email, with: ->(email) { email.downcase.strip }
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|
@@ -1,16 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Avocado
|
4
|
-
module UserPassword
|
5
|
-
extend ActiveSupport::Concern
|
6
|
-
|
7
|
-
REQUIRED_FORMAT = /\A(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9]).*\z/x
|
8
|
-
REQUIRED_LENGTH = 8
|
9
|
-
|
10
|
-
included do
|
11
|
-
has_secure_password
|
12
|
-
|
13
|
-
validates :password, format: {with: REQUIRED_FORMAT}, length: {minimum: REQUIRED_LENGTH}, allow_nil: true
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|