kaze 0.8.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/kaze/commands/install_command.rb +3 -1
- data/lib/kaze/commands/installs_hotwire_stack.rb +2 -2
- data/lib/kaze/commands/installs_inertia_stacks.rb +7 -7
- data/lib/kaze/version.rb +1 -1
- data/stubs/default/app/forms/update_profile_information_form.rb +8 -0
- data/stubs/default/app/mailers/application_mailer.rb +1 -1
- data/stubs/default/app/mailers/user_mailer.rb +16 -1
- data/stubs/default/app/models/concerns/can_reset_password.rb +1 -1
- data/stubs/default/app/models/concerns/must_verify_email.rb +15 -0
- data/stubs/default/app/models/user.rb +1 -0
- data/stubs/default/app/views/user_mailer/reset_password.html.erb +2 -2
- data/stubs/default/app/views/user_mailer/verify_email.html.erb +33 -0
- data/stubs/default/config/routes.rb +4 -0
- data/stubs/default/db/migrate/20240101000000_create_users.rb +1 -0
- data/stubs/default/test/factories/users.rb +5 -0
- data/stubs/default/test/integration/auth/authentication_test.rb +4 -6
- data/stubs/default/test/integration/auth/email_verification_test.rb +40 -0
- data/stubs/default/test/integration/auth/password_reset_test.rb +3 -3
- data/stubs/default/test/integration/password_update_test.rb +2 -2
- data/stubs/default/test/integration/profile_test.rb +16 -4
- data/stubs/hotwire/app/components/modal_component.rb +5 -5
- data/stubs/hotwire/app/controllers/auth/authenticated_session_controller.rb +3 -2
- data/stubs/hotwire/app/controllers/auth/email_verification_notification_controller.rb +21 -0
- data/stubs/hotwire/app/controllers/auth/new_password_controller.rb +2 -2
- data/stubs/hotwire/app/controllers/auth/password_reset_link_controller.rb +1 -1
- data/stubs/hotwire/app/controllers/auth/registered_user_controller.rb +6 -2
- data/stubs/hotwire/app/controllers/auth/verified_email_controller.rb +23 -0
- data/stubs/hotwire/app/controllers/concerns/authenticate.rb +10 -0
- data/stubs/hotwire/app/controllers/concerns/validate_signature.rb +17 -0
- data/stubs/hotwire/app/controllers/password_controller.rb +3 -1
- data/stubs/hotwire/app/controllers/profile_controller.rb +5 -3
- data/stubs/hotwire/app/views/auth/verify_email.html.erb +23 -0
- data/stubs/hotwire/app/views/profile/partials/_delete_user_form.html.erb +1 -1
- data/stubs/hotwire/app/views/profile/partials/_update_profile_information_form.html.erb +18 -0
- data/stubs/inertia-common/app/controllers/auth/authenticated_session_controller.rb +3 -3
- data/stubs/inertia-common/app/controllers/auth/email_verification_notification_controller.rb +21 -0
- data/stubs/inertia-common/app/controllers/auth/new_password_controller.rb +2 -3
- data/stubs/inertia-common/app/controllers/auth/password_reset_link_controller.rb +3 -3
- data/stubs/inertia-common/app/controllers/auth/registered_user_controller.rb +6 -2
- data/stubs/inertia-common/app/controllers/auth/verified_email_controller.rb +23 -0
- data/stubs/inertia-common/app/controllers/concerns/authenticate.rb +10 -0
- data/stubs/inertia-common/app/controllers/concerns/validate_signature.rb +17 -0
- data/stubs/inertia-common/app/controllers/password_controller.rb +3 -1
- data/stubs/inertia-common/app/controllers/profile_controller.rb +7 -4
- data/stubs/inertia-common/test/integration/password_update_test.rb +2 -2
- data/stubs/inertia-common/test/integration/profile_test.rb +16 -4
- data/stubs/inertia-react-ts/app/javascript/Components/Checkbox.tsx +1 -4
- data/stubs/inertia-react-ts/app/javascript/Components/DangerButton.tsx +3 -5
- data/stubs/inertia-react-ts/app/javascript/Components/Dropdown.tsx +4 -25
- data/stubs/inertia-react-ts/app/javascript/Components/InputError.tsx +1 -4
- data/stubs/inertia-react-ts/app/javascript/Components/InputLabel.tsx +1 -4
- data/stubs/inertia-react-ts/app/javascript/Components/NavLink.tsx +3 -5
- data/stubs/inertia-react-ts/app/javascript/Components/PrimaryButton.tsx +3 -5
- data/stubs/inertia-react-ts/app/javascript/Components/SecondaryButton.tsx +3 -5
- data/stubs/inertia-react-ts/app/javascript/Components/TextInput.tsx +1 -7
- data/stubs/inertia-react-ts/app/javascript/Layouts/AuthenticatedLayout.tsx +14 -52
- data/stubs/inertia-react-ts/app/javascript/Pages/Auth/ForgotPassword.tsx +4 -7
- data/stubs/inertia-react-ts/app/javascript/Pages/Auth/Login.tsx +10 -24
- data/stubs/inertia-react-ts/app/javascript/Pages/Auth/Register.tsx +2 -5
- data/stubs/inertia-react-ts/app/javascript/Pages/Auth/ResetPassword.tsx +2 -5
- data/stubs/inertia-react-ts/app/javascript/Pages/Auth/VerifyEmail.tsx +47 -0
- data/stubs/inertia-react-ts/app/javascript/Pages/Dashboard.tsx +2 -8
- data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Edit.tsx +10 -10
- data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Partials/DeleteUserForm.tsx +10 -20
- data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Partials/UpdatePasswordForm.tsx +10 -18
- data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Partials/UpdateProfileInformationForm.tsx +35 -12
- data/stubs/inertia-react-ts/app/javascript/Pages/Welcome.tsx +1 -2
- data/stubs/inertia-react-ts/app/javascript/entrypoints/application.tsx +1 -5
- data/stubs/inertia-react-ts/app/javascript/entrypoints/bootstrap.ts +3 -3
- data/stubs/inertia-react-ts/app/javascript/types/global.d.ts +4 -4
- data/stubs/inertia-react-ts/app/javascript/types/index.d.ts +8 -8
- data/stubs/inertia-react-ts/config/tailwind.config.js +1 -6
- data/stubs/inertia-vue-ts/app/javascript/Components/ApplicationLogo.vue +3 -1
- data/stubs/inertia-vue-ts/app/javascript/Components/Dropdown.vue +1 -4
- data/stubs/inertia-vue-ts/app/javascript/Components/Modal.vue +3 -13
- data/stubs/inertia-vue-ts/app/javascript/Layouts/AuthenticatedLayout.vue +10 -45
- data/stubs/inertia-vue-ts/app/javascript/Layouts/GuestLayout.vue +2 -6
- data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/ForgotPassword.vue +4 -11
- data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/Login.vue +2 -10
- data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/Register.vue +1 -5
- data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/ResetPassword.vue +2 -22
- data/stubs/inertia-vue-ts/app/javascript/Pages/Auth/VerifyEmail.vue +50 -0
- data/stubs/inertia-vue-ts/app/javascript/Pages/Dashboard.vue +3 -11
- data/stubs/inertia-vue-ts/app/javascript/Pages/Profile/Edit.vue +2 -10
- data/stubs/inertia-vue-ts/app/javascript/Pages/Profile/Partials/DeleteUserForm.vue +5 -9
- data/stubs/inertia-vue-ts/app/javascript/Pages/Profile/Partials/UpdatePasswordForm.vue +2 -9
- data/stubs/inertia-vue-ts/app/javascript/Pages/Profile/Partials/UpdateProfileInformationForm.vue +26 -15
- data/stubs/inertia-vue-ts/app/javascript/Pages/Welcome.vue +2 -5
- data/stubs/inertia-vue-ts/app/javascript/entrypoints/application.ts +27 -23
- data/stubs/inertia-vue-ts/app/javascript/entrypoints/bootstrap.ts +3 -3
- data/stubs/inertia-vue-ts/app/javascript/types/global.d.ts +7 -7
- data/stubs/inertia-vue-ts/app/javascript/types/index.d.ts +8 -8
- data/stubs/inertia-vue-ts/config/tailwind.config.js +1 -6
- metadata +14 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 27a8de2c9dc1c9297b72ca20e60fc00582579e39cf1372ad019d6ab18c28942f
|
4
|
+
data.tar.gz: fadd95b9700cc5409c2cffcaae03ba3ad68cc79b8331b6a9e27612a47d6e5ce7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a5bd8c23395b302a93fa836e33c10da76de5681d563f84c1f16ff70317583aa3470e05d03cdfd726257c0ece38ef20c8d72ffe0974e0cbdb14d93a7688a1686c
|
7
|
+
data.tar.gz: 70cd7f58402805e19197e0c9f161ebde8891e5696eafdec0c96dc3017c52fe6f966068329c77f3f55a3e989cc983050694d91a68097502691fed80e4ea4084c0
|
@@ -9,6 +9,8 @@ class Kaze::Commands::InstallCommand < Thor
|
|
9
9
|
|
10
10
|
desc 'install [STACK]', 'Install the Kaze controllers and resources. Supported stacks: hotwire, react, vue.'
|
11
11
|
def install(stack = 'hotwire')
|
12
|
+
return say 'Kaze must be run in a new Rails application.', :red unless File.exist?("#{Dir.pwd}/bin/rails")
|
13
|
+
|
12
14
|
if stack == 'hotwire'
|
13
15
|
return install_hotwire_stack
|
14
16
|
end
|
@@ -53,7 +55,7 @@ class Kaze::Commands::InstallCommand < Thor
|
|
53
55
|
def install_migrations
|
54
56
|
ensure_directory_exists("#{Dir.pwd}/db/migrate")
|
55
57
|
FileUtils.copy_entry("#{File.dirname(__FILE__)}/../../../stubs/default/db/migrate", "#{Dir.pwd}/db/migrate")
|
56
|
-
stdin, _ = Open3.capture3(
|
58
|
+
stdin, _ = Open3.capture3("#{Dir.pwd}/bin/rails version")
|
57
59
|
versions = stdin.gsub!('Rails ', '').split('.')
|
58
60
|
railsVersion = [ versions[0], versions[1] ].join('.')
|
59
61
|
Dir.children("#{Dir.pwd}/db/migrate").each do |file|
|
@@ -32,7 +32,7 @@ module Kaze::Commands::InstallsHotwireStack
|
|
32
32
|
FileUtils.copy_entry("#{File.dirname(__FILE__)}/../../../stubs/hotwire/app/views", "#{Dir.pwd}/app/views")
|
33
33
|
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/default/app/views/layouts/mailer.html.erb", "#{Dir.pwd}/app/views/layouts/mailer.html.erb")
|
34
34
|
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/default/app/views/layouts/mailer.text.erb", "#{Dir.pwd}/app/views/layouts/mailer.text.erb")
|
35
|
-
FileUtils.
|
35
|
+
FileUtils.copy_entry("#{File.dirname(__FILE__)}/../../../stubs/default/app/views/user_mailer", "#{Dir.pwd}/app/views/user_mailer")
|
36
36
|
|
37
37
|
# Components + Pages...
|
38
38
|
ensure_directory_exists("#{Dir.pwd}/app/components")
|
@@ -57,7 +57,7 @@ module Kaze::Commands::InstallsHotwireStack
|
|
57
57
|
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/hotwire/config/tailwind.config.js", "#{Dir.pwd}/config/tailwind.config.js")
|
58
58
|
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/default/bin/dev", "#{Dir.pwd}/bin/dev")
|
59
59
|
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/default/Procfile.dev", "#{Dir.pwd}/Procfile.dev")
|
60
|
-
run_command(
|
60
|
+
run_command("#{Dir.pwd}/bin/rails tailwindcss:build")
|
61
61
|
|
62
62
|
say ''
|
63
63
|
say 'Kaze scaffolding installed successfully.', :green
|
@@ -34,7 +34,7 @@ module Kaze::Commands::InstallsInertiaStacks
|
|
34
34
|
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/inertia-react-ts/app/views/layouts/application.html.erb", "#{Dir.pwd}/app/views/layouts/application.html.erb")
|
35
35
|
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/default/app/views/layouts/mailer.html.erb", "#{Dir.pwd}/app/views/layouts/mailer.html.erb")
|
36
36
|
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/default/app/views/layouts/mailer.text.erb", "#{Dir.pwd}/app/views/layouts/mailer.text.erb")
|
37
|
-
FileUtils.
|
37
|
+
FileUtils.copy_entry("#{File.dirname(__FILE__)}/../../../stubs/default/app/views/user_mailer", "#{Dir.pwd}/app/views/user_mailer")
|
38
38
|
|
39
39
|
# Components + Pages...
|
40
40
|
ensure_directory_exists("#{Dir.pwd}/app/javascript")
|
@@ -60,9 +60,9 @@ module Kaze::Commands::InstallsInertiaStacks
|
|
60
60
|
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/default/bin/dev", "#{Dir.pwd}/bin/dev")
|
61
61
|
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/inertia-common/bin/vite", "#{Dir.pwd}/bin/vite")
|
62
62
|
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/default/Procfile.dev", "#{Dir.pwd}/Procfile.dev")
|
63
|
-
File.write("#{Dir.pwd}/Procfile.dev", "#{File.read("#{Dir.pwd}/Procfile.dev")}\nvite: bin/vite dev\n")
|
64
|
-
run_command(
|
65
|
-
run_command(
|
63
|
+
File.write("#{Dir.pwd}/Procfile.dev", "#{File.read("#{Dir.pwd}/Procfile.dev")}\nvite: bundle exec rake js:routes:typescript && bin/vite dev\n")
|
64
|
+
run_command("#{Dir.pwd}/bin/rails generate js_routes:middleware")
|
65
|
+
run_command("#{Dir.pwd}/bin/rails tailwindcss:build")
|
66
66
|
|
67
67
|
say ''
|
68
68
|
say 'Installing and building Node dependencies.', :magenta
|
@@ -140,9 +140,9 @@ module Kaze::Commands::InstallsInertiaStacks
|
|
140
140
|
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/default/bin/dev", "#{Dir.pwd}/bin/dev")
|
141
141
|
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/inertia-common/bin/vite", "#{Dir.pwd}/bin/vite")
|
142
142
|
FileUtils.copy_file("#{File.dirname(__FILE__)}/../../../stubs/default/Procfile.dev", "#{Dir.pwd}/Procfile.dev")
|
143
|
-
File.write("#{Dir.pwd}/Procfile.dev", "#{File.read("#{Dir.pwd}/Procfile.dev")}\nvite: bin/vite dev\n")
|
144
|
-
run_command(
|
145
|
-
run_command(
|
143
|
+
File.write("#{Dir.pwd}/Procfile.dev", "#{File.read("#{Dir.pwd}/Procfile.dev")}\nvite: bundle exec rake js:routes:typescript && bin/vite dev\n")
|
144
|
+
run_command("#{Dir.pwd}/bin/rails generate js_routes:middleware")
|
145
|
+
run_command("#{Dir.pwd}/bin/rails tailwindcss:build")
|
146
146
|
|
147
147
|
say ''
|
148
148
|
say 'Installing and building Node dependencies.', :magenta
|
data/lib/kaze/version.rb
CHANGED
@@ -3,4 +3,12 @@ class UpdateProfileInformationForm < ApplicationForm
|
|
3
3
|
|
4
4
|
validates :name, presence: true
|
5
5
|
validates :email, presence: true, lowercase: true, email: true, uniqueness: { model: User, attribute: :email, conditions: -> { where.not(id: Current.auth.user.id) } }
|
6
|
+
|
7
|
+
def update
|
8
|
+
Current.auth.user.name = name
|
9
|
+
Current.auth.user.email = email
|
10
|
+
Current.auth.user.email_verified_at = nil if Current.auth.user.changed.include?('email')
|
11
|
+
|
12
|
+
Current.auth.user.save
|
13
|
+
end
|
6
14
|
end
|
@@ -1,8 +1,23 @@
|
|
1
1
|
class UserMailer < ApplicationMailer
|
2
|
-
def reset_password
|
2
|
+
def reset_password(token)
|
3
|
+
@reset_url = password_reset_url(token: token)
|
4
|
+
|
3
5
|
mail(
|
4
6
|
to: params[:user].email,
|
5
7
|
subject: 'Reset Password Notification'
|
6
8
|
)
|
7
9
|
end
|
10
|
+
|
11
|
+
def verify_email
|
12
|
+
verifier = ActiveSupport::MessageVerifier.new(ENV.fetch('RAILS_MASTER_KEY', ''))
|
13
|
+
|
14
|
+
signature = verifier.generate(verification_verify_url(id: params[:user].id, hash: Digest::SHA1.hexdigest(params[:user].email)), expires_in: 60.minutes.from_now.to_i)
|
15
|
+
|
16
|
+
@verification_url = verification_verify_url(id: params[:user].id, hash: Digest::SHA1.hexdigest(params[:user].email), signature: signature)
|
17
|
+
|
18
|
+
mail(
|
19
|
+
to: params[:user].email,
|
20
|
+
subject: 'Verify Email Address'
|
21
|
+
)
|
22
|
+
end
|
8
23
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module MustVerifyEmail
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
def has_verified_email?
|
5
|
+
!email_verified_at.nil?
|
6
|
+
end
|
7
|
+
|
8
|
+
def mark_email_as_verified
|
9
|
+
self.update(email_verified_at: Time.now)
|
10
|
+
end
|
11
|
+
|
12
|
+
def send_email_verification_notification
|
13
|
+
UserMailer.with(user: self).verify_email.deliver_later
|
14
|
+
end
|
15
|
+
end
|
@@ -12,7 +12,7 @@
|
|
12
12
|
<table border="0" cellpadding="0" cellspacing="0" role="presentation">
|
13
13
|
<tr>
|
14
14
|
<td>
|
15
|
-
<a href="<%=
|
15
|
+
<a href="<%= @reset_url %>" class="button button-primary" target="_blank" rel="noopener">Reset Password</a>
|
16
16
|
</td>
|
17
17
|
</tr>
|
18
18
|
</table>
|
@@ -30,5 +30,5 @@
|
|
30
30
|
<%= ENV.fetch("APP_NAME", "Rails") %></p>
|
31
31
|
<!-- Subcopy -->
|
32
32
|
<% content_for :subcopy do %>
|
33
|
-
<p>If you're having trouble clicking the "Reset Password" button, copy and paste the URL below into your web browser: <span class="break-all"><%= link_to
|
33
|
+
<p>If you're having trouble clicking the "Reset Password" button, copy and paste the URL below into your web browser: <span class="break-all"><%= link_to @reset_url, @reset_url %><span></p>
|
34
34
|
<% end %>
|
@@ -0,0 +1,33 @@
|
|
1
|
+
<!-- Greeting -->
|
2
|
+
<h1>Hello!</h1>
|
3
|
+
<!-- Intro Lines -->
|
4
|
+
<p>Please click the button below to verify your email address.</p>
|
5
|
+
<!-- Action Button -->
|
6
|
+
<table class="action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
|
7
|
+
<tr>
|
8
|
+
<td align="center">
|
9
|
+
<table width="100%" border="0" cellpadding="0" cellspacing="0" role="presentation">
|
10
|
+
<tr>
|
11
|
+
<td align="center">
|
12
|
+
<table border="0" cellpadding="0" cellspacing="0" role="presentation">
|
13
|
+
<tr>
|
14
|
+
<td>
|
15
|
+
<a href="<%= @verification_url %>" class="button button-primary" target="_blank" rel="noopener">Verify Email Address</a>
|
16
|
+
</td>
|
17
|
+
</tr>
|
18
|
+
</table>
|
19
|
+
</td>
|
20
|
+
</tr>
|
21
|
+
</table>
|
22
|
+
</td>
|
23
|
+
</tr>
|
24
|
+
</table>
|
25
|
+
<!-- Outro Lines -->
|
26
|
+
<p>If you did not create an account, no further action is required.</p>
|
27
|
+
<!-- Salutation -->
|
28
|
+
<p>Regards,<br>
|
29
|
+
<%= ENV.fetch("APP_NAME", "Rails") %></p>
|
30
|
+
<!-- Subcopy -->
|
31
|
+
<% content_for :subcopy do %>
|
32
|
+
<p>If you're having trouble clicking the "Verify Email Address" button, copy and paste the URL below into your web browser: <span class="break-all"><%= link_to @verification_url, @verification_url %><span></p>
|
33
|
+
<% end %>
|
@@ -15,6 +15,10 @@ Rails.application.routes.draw do
|
|
15
15
|
get 'reset-password/:token', to: 'auth/new_password#new', as: :password_reset
|
16
16
|
post 'reset-password', to: 'auth/new_password#create', as: :password_store
|
17
17
|
|
18
|
+
get 'verify-email', to: 'auth/email_verification_notification#new', as: :verification_notice
|
19
|
+
post 'verify-email', to: 'auth/email_verification_notification#create', as: :verification_send
|
20
|
+
get 'verify-email/:id/:hash', to: 'auth/verified_email#create', as: :verification_verify
|
21
|
+
|
18
22
|
post 'logout', to: 'auth/authenticated_session#destroy', as: :logout
|
19
23
|
|
20
24
|
get 'dashboard', to: 'dashboard#index', as: :dashboard
|
@@ -8,7 +8,7 @@ class Auth::AuthenticationTest < ActionDispatch::IntegrationTest
|
|
8
8
|
end
|
9
9
|
|
10
10
|
test 'users can authenticate using the login screen' do
|
11
|
-
user = FactoryBot.create
|
11
|
+
user = FactoryBot.create(:user)
|
12
12
|
|
13
13
|
post login_path, params: {
|
14
14
|
email: user.email,
|
@@ -20,7 +20,7 @@ class Auth::AuthenticationTest < ActionDispatch::IntegrationTest
|
|
20
20
|
end
|
21
21
|
|
22
22
|
test 'users cannot authenticate with invalid password' do
|
23
|
-
user = FactoryBot.create
|
23
|
+
user = FactoryBot.create(:user)
|
24
24
|
|
25
25
|
post login_path, params: {
|
26
26
|
email: user.email,
|
@@ -31,11 +31,9 @@ class Auth::AuthenticationTest < ActionDispatch::IntegrationTest
|
|
31
31
|
end
|
32
32
|
|
33
33
|
test 'users can logout' do
|
34
|
-
user = FactoryBot.create
|
34
|
+
user = FactoryBot.create(:user)
|
35
35
|
|
36
|
-
acting_as
|
37
|
-
|
38
|
-
post logout_path
|
36
|
+
acting_as(user).post logout_path
|
39
37
|
|
40
38
|
assert_guest
|
41
39
|
assert_redirected_to '/'
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class EmailVerificationTest < ActionDispatch::IntegrationTest
|
4
|
+
if User.include?(MustVerifyEmail)
|
5
|
+
test 'email verification screen can be rendered' do
|
6
|
+
user = FactoryBot.create(:user, :unverified)
|
7
|
+
|
8
|
+
acting_as(user).get verification_notice_path
|
9
|
+
|
10
|
+
assert_response :success
|
11
|
+
end
|
12
|
+
|
13
|
+
test 'email can be verified' do
|
14
|
+
user = FactoryBot.create(:user, :unverified)
|
15
|
+
|
16
|
+
acting_as(user).get verification_url(id: user.id, hash: Digest::SHA1.hexdigest(user.email))
|
17
|
+
|
18
|
+
assert user.reload.has_verified_email?
|
19
|
+
assert_redirected_to dashboard_path(verified: '1')
|
20
|
+
end
|
21
|
+
|
22
|
+
test 'email is not verified with invalid hash' do
|
23
|
+
user = FactoryBot.create(:user, :unverified)
|
24
|
+
|
25
|
+
acting_as(user).get verification_url(id: user.id, hash: Digest::SHA1.hexdigest('wrong-email'))
|
26
|
+
|
27
|
+
assert_not user.reload.has_verified_email?
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def verification_url(params)
|
34
|
+
verifier = ActiveSupport::MessageVerifier.new(ENV.fetch('RAILS_MASTER_KEY', ''))
|
35
|
+
|
36
|
+
signature = verifier.generate(verification_verify_url(params), expires_in: 60.minutes.from_now.to_i)
|
37
|
+
|
38
|
+
verification_verify_url(params.merge(signature: signature))
|
39
|
+
end
|
40
|
+
end
|
@@ -8,7 +8,7 @@ class Auth::PasswordResetTest < ActionDispatch::IntegrationTest
|
|
8
8
|
end
|
9
9
|
|
10
10
|
test 'reset password link can be requested' do
|
11
|
-
user = FactoryBot.create
|
11
|
+
user = FactoryBot.create(:user)
|
12
12
|
token = user.generate_token_for(:password_reset)
|
13
13
|
email = UserMailer.with(user: user, token: token).reset_password
|
14
14
|
|
@@ -20,7 +20,7 @@ class Auth::PasswordResetTest < ActionDispatch::IntegrationTest
|
|
20
20
|
end
|
21
21
|
|
22
22
|
test 'reset password screen can be rendered' do
|
23
|
-
user = FactoryBot.create
|
23
|
+
user = FactoryBot.create(:user)
|
24
24
|
|
25
25
|
get password_reset_path(token: user.generate_token_for(:password_reset))
|
26
26
|
|
@@ -28,7 +28,7 @@ class Auth::PasswordResetTest < ActionDispatch::IntegrationTest
|
|
28
28
|
end
|
29
29
|
|
30
30
|
test 'password can be reset_with_valid_token' do
|
31
|
-
user = FactoryBot.create
|
31
|
+
user = FactoryBot.create(:user)
|
32
32
|
|
33
33
|
post password_store_path, params: {
|
34
34
|
token: user.generate_token_for(:password_reset),
|
@@ -2,7 +2,7 @@ require 'test_helper'
|
|
2
2
|
|
3
3
|
class PasswordUpdateTest < ActionDispatch::IntegrationTest
|
4
4
|
test 'password can be updated' do
|
5
|
-
user = FactoryBot.create
|
5
|
+
user = FactoryBot.create(:user)
|
6
6
|
|
7
7
|
acting_as(user).put password_update_path, params: {
|
8
8
|
current_password: 'password',
|
@@ -15,7 +15,7 @@ class PasswordUpdateTest < ActionDispatch::IntegrationTest
|
|
15
15
|
end
|
16
16
|
|
17
17
|
test 'correct password must be provided to update password' do
|
18
|
-
user = FactoryBot.create
|
18
|
+
user = FactoryBot.create(:user)
|
19
19
|
|
20
20
|
acting_as(user).put password_update_path, params: {
|
21
21
|
current_password: 'wrong-password',
|
@@ -2,7 +2,7 @@ require 'test_helper'
|
|
2
2
|
|
3
3
|
class ProfileTest < ActionDispatch::IntegrationTest
|
4
4
|
test 'profile page is displayed' do
|
5
|
-
user = FactoryBot.create
|
5
|
+
user = FactoryBot.create(:user)
|
6
6
|
|
7
7
|
acting_as(user).get profile_edit_path
|
8
8
|
|
@@ -10,7 +10,7 @@ class ProfileTest < ActionDispatch::IntegrationTest
|
|
10
10
|
end
|
11
11
|
|
12
12
|
test 'profile information can be updated' do
|
13
|
-
user = FactoryBot.create
|
13
|
+
user = FactoryBot.create(:user)
|
14
14
|
|
15
15
|
acting_as(user).patch profile_edit_path, params: {
|
16
16
|
name: 'Test User',
|
@@ -25,8 +25,20 @@ class ProfileTest < ActionDispatch::IntegrationTest
|
|
25
25
|
assert_equal 'test@example.com', user.email
|
26
26
|
end
|
27
27
|
|
28
|
+
test 'email verification status is unchanged when the email address is unchanged' do
|
29
|
+
user = FactoryBot.create(:user)
|
30
|
+
|
31
|
+
acting_as(user).patch profile_edit_path, params: {
|
32
|
+
name: 'Test User',
|
33
|
+
email: user.email
|
34
|
+
}
|
35
|
+
|
36
|
+
assert_redirected_to profile_edit_path
|
37
|
+
assert_not user.reload.email_verified_at.blank?
|
38
|
+
end
|
39
|
+
|
28
40
|
test 'user can delete their account' do
|
29
|
-
user = FactoryBot.create
|
41
|
+
user = FactoryBot.create(:user)
|
30
42
|
|
31
43
|
acting_as(user).delete profile_destroy_path, params: {
|
32
44
|
password: 'password'
|
@@ -39,7 +51,7 @@ class ProfileTest < ActionDispatch::IntegrationTest
|
|
39
51
|
end
|
40
52
|
|
41
53
|
test 'correct password must be provided to delete account' do
|
42
|
-
user = FactoryBot.create
|
54
|
+
user = FactoryBot.create(:user)
|
43
55
|
|
44
56
|
acting_as(user).delete profile_destroy_path, params: {
|
45
57
|
password: 'wrong-password'
|
@@ -3,11 +3,11 @@ class ModalComponent < ViewComponent::Base
|
|
3
3
|
@name = attributes[:name]
|
4
4
|
@show = attributes[:show] || false
|
5
5
|
@max_width = {
|
6
|
-
:
|
7
|
-
:
|
8
|
-
:
|
9
|
-
:
|
10
|
-
'2xl'
|
6
|
+
sm: 'sm:max-w-sm',
|
7
|
+
md: 'sm:max-w-md',
|
8
|
+
lg: 'sm:max-w-lg',
|
9
|
+
xl: 'sm:max-w-xl',
|
10
|
+
'2xl': 'sm:max-w-2xl'
|
11
11
|
}[attributes[:max_width] || '2xl']
|
12
12
|
@attributes = attributes.without(:name, :show, :max_width)
|
13
13
|
end
|
@@ -2,7 +2,8 @@ class Auth::AuthenticatedSessionController < ApplicationController
|
|
2
2
|
include RedirectIfAuthenticated
|
3
3
|
|
4
4
|
skip_authenticate only: %i[new create]
|
5
|
-
skip_redirect_if_authenticated only:
|
5
|
+
skip_redirect_if_authenticated only: :destroy
|
6
|
+
skip_ensure_email_is_verified only: :destroy
|
6
7
|
|
7
8
|
layout 'guest'
|
8
9
|
|
@@ -13,7 +14,7 @@ class Auth::AuthenticatedSessionController < ApplicationController
|
|
13
14
|
end
|
14
15
|
|
15
16
|
def create
|
16
|
-
@form = Auth::LoginForm.new
|
17
|
+
@form = Auth::LoginForm.new(params.permit(:email, :password, :remember))
|
17
18
|
|
18
19
|
@form.authenticate
|
19
20
|
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Auth::EmailVerificationNotificationController < ApplicationController
|
2
|
+
skip_ensure_email_is_verified
|
3
|
+
|
4
|
+
before_action { redirect_to dashboard_path unless User.include?(MustVerifyEmail) }
|
5
|
+
|
6
|
+
layout 'guest'
|
7
|
+
|
8
|
+
def new
|
9
|
+
return redirect_to dashboard_path if Current.auth.user.has_verified_email?
|
10
|
+
|
11
|
+
render 'auth/verify_email'
|
12
|
+
end
|
13
|
+
|
14
|
+
def create
|
15
|
+
return redirect_to dashboard_path if Current.auth.user.has_verified_email?
|
16
|
+
|
17
|
+
Current.auth.user.send_email_verification_notification
|
18
|
+
|
19
|
+
redirect_back_or_to verification_notice_path, flash: { status: 'verification-link-sent' }
|
20
|
+
end
|
21
|
+
end
|
@@ -6,13 +6,13 @@ class Auth::NewPasswordController < ApplicationController
|
|
6
6
|
layout 'guest'
|
7
7
|
|
8
8
|
def new
|
9
|
-
@form = Auth::NewPasswordForm.new
|
9
|
+
@form = Auth::NewPasswordForm.new(params.permit(:token))
|
10
10
|
|
11
11
|
render 'auth/reset_password'
|
12
12
|
end
|
13
13
|
|
14
14
|
def create
|
15
|
-
@form = Auth::NewPasswordForm.new
|
15
|
+
@form = Auth::NewPasswordForm.new(params.permit(:token, :password, :password_confirmation))
|
16
16
|
|
17
17
|
return redirect_to login_path, flash: { status: 'Your password has been reset.' } if @form.reset?
|
18
18
|
|
@@ -12,7 +12,7 @@ class Auth::PasswordResetLinkController < ApplicationController
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def create
|
15
|
-
@form = Auth::SendPasswordResetLinkForm.new
|
15
|
+
@form = Auth::SendPasswordResetLinkForm.new(params.permit(:email))
|
16
16
|
|
17
17
|
return redirect_back_or_to password_request_path, flash: { status: 'We have emailed your password reset link.' } if @form.send_reset_link?
|
18
18
|
|
@@ -12,13 +12,17 @@ class Auth::RegisteredUserController < ApplicationController
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def create
|
15
|
-
@form = Auth::RegisterForm.new
|
15
|
+
@form = Auth::RegisterForm.new(params.permit(:name, :email, :password, :password_confirmation))
|
16
16
|
|
17
17
|
return render 'auth/register', status: :unprocessable_entity if @form.invalid?
|
18
18
|
|
19
19
|
user = User.create(name: @form.name, email: @form.email, password: @form.password)
|
20
20
|
|
21
|
-
|
21
|
+
if User.include?(MustVerifyEmail) && !user.has_verified_email?
|
22
|
+
user.send_email_verification_notification
|
23
|
+
end
|
24
|
+
|
25
|
+
Current.auth.login(user)
|
22
26
|
|
23
27
|
redirect_to dashboard_path
|
24
28
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
class Auth::VerifiedEmailController < ApplicationController
|
2
|
+
include ValidateSignature
|
3
|
+
|
4
|
+
skip_ensure_email_is_verified
|
5
|
+
|
6
|
+
before_action { redirect_to dashboard_path unless User.include?(MustVerifyEmail) }
|
7
|
+
|
8
|
+
def create
|
9
|
+
return redirect_to dashboard_path(verified: '1') if Current.auth.user.has_verified_email?
|
10
|
+
|
11
|
+
Current.auth.user.mark_email_as_verified if email_verification_request_is_authorized?
|
12
|
+
|
13
|
+
redirect_to dashboard_path(verified: '1')
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def email_verification_request_is_authorized?
|
19
|
+
return false if !ActiveSupport::SecurityUtils.secure_compare Current.auth.user.id.to_s, params[:id]
|
20
|
+
|
21
|
+
ActiveSupport::SecurityUtils.secure_compare Digest::SHA1.hexdigest(Current.auth.user.email), params[:hash]
|
22
|
+
end
|
23
|
+
end
|
@@ -3,11 +3,17 @@ module Authenticate
|
|
3
3
|
|
4
4
|
included do
|
5
5
|
before_action :authenticate!
|
6
|
+
before_action :ensure_email_is_verified! if User.include?(MustVerifyEmail)
|
6
7
|
end
|
7
8
|
|
8
9
|
class_methods do
|
9
10
|
def skip_authenticate(**options)
|
10
11
|
skip_before_action :authenticate!, **options
|
12
|
+
skip_ensure_email_is_verified(**options)
|
13
|
+
end
|
14
|
+
|
15
|
+
def skip_ensure_email_is_verified(**options)
|
16
|
+
skip_before_action :ensure_email_is_verified!, **options if User.include?(MustVerifyEmail)
|
11
17
|
end
|
12
18
|
end
|
13
19
|
|
@@ -16,4 +22,8 @@ module Authenticate
|
|
16
22
|
def authenticate!
|
17
23
|
redirect_to login_path unless Current.auth.check?
|
18
24
|
end
|
25
|
+
|
26
|
+
def ensure_email_is_verified!
|
27
|
+
redirect_to verification_notice_path unless Current.auth.user && Current.auth.user.has_verified_email?
|
28
|
+
end
|
19
29
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module ValidateSignature
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
before_action do
|
6
|
+
render file: "#{Rails.root}/public/404.html", layout: false, status: :not_found unless has_valid_signature?
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def has_valid_signature?
|
13
|
+
request.original_url.split('?')[0] == ActiveSupport::MessageVerifier.new(ENV.fetch('RAILS_MASTER_KEY', '')).verify(params[:signature])
|
14
|
+
rescue
|
15
|
+
false
|
16
|
+
end
|
17
|
+
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
class PasswordController < ApplicationController
|
2
|
+
skip_ensure_email_is_verified
|
3
|
+
|
2
4
|
def update
|
3
|
-
@update_password_form = UpdatePasswordForm.new
|
5
|
+
@update_password_form = UpdatePasswordForm.new(params.permit(:current_password, :password, :password_confirmation))
|
4
6
|
|
5
7
|
return render partial: 'profile/partials/update_password_form', status: :unprocessable_entity if @update_password_form.invalid?
|
6
8
|
|
@@ -1,4 +1,6 @@
|
|
1
1
|
class ProfileController < ApplicationController
|
2
|
+
skip_ensure_email_is_verified
|
3
|
+
|
2
4
|
def edit
|
3
5
|
@update_profile_information_form = UpdateProfileInformationForm.new(name: Current.auth.user.name, email: Current.auth.user.email)
|
4
6
|
@update_password_form = UpdatePasswordForm.new
|
@@ -8,17 +10,17 @@ class ProfileController < ApplicationController
|
|
8
10
|
end
|
9
11
|
|
10
12
|
def update
|
11
|
-
@update_profile_information_form = UpdateProfileInformationForm.new
|
13
|
+
@update_profile_information_form = UpdateProfileInformationForm.new(params.permit(:name, :email))
|
12
14
|
|
13
15
|
return render partial: 'profile/partials/update_profile_information_form', status: :unprocessable_entity if @update_profile_information_form.invalid?
|
14
16
|
|
15
|
-
|
17
|
+
@update_profile_information_form.update
|
16
18
|
|
17
19
|
redirect_to profile_edit_path, flash: { status: 'profile-updated' }
|
18
20
|
end
|
19
21
|
|
20
22
|
def destroy
|
21
|
-
@delete_user_form = DeleteUserForm.new
|
23
|
+
@delete_user_form = DeleteUserForm.new(params.permit(:password))
|
22
24
|
|
23
25
|
return render partial: 'profile/partials/delete_user_form', status: :unprocessable_entity if @delete_user_form.invalid?
|
24
26
|
|