kaze 0.7.0 → 0.9.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/README.md +2 -0
- 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/auth/login_form.rb +2 -2
- data/stubs/default/app/forms/auth/new_password_form.rb +2 -2
- data/stubs/default/app/forms/auth/send_password_reset_link_form.rb +2 -4
- 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/session_guard.rb +157 -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 +2 -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 +4 -4
- data/stubs/default/test/test_helper.rb +1 -1
- data/stubs/hotwire/app/components/application_logo_component.rb +2 -5
- 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/set_current_auth.rb +1 -1
- data/stubs/hotwire/app/controllers/concerns/validate_signature.rb +17 -0
- data/stubs/hotwire/app/controllers/password_controller.rb +1 -1
- data/stubs/hotwire/app/controllers/profile_controller.rb +2 -2
- data/stubs/hotwire/app/views/auth/verify_email.html.erb +23 -0
- data/stubs/hotwire/app/views/layouts/_navigation.html.erb +1 -1
- data/stubs/hotwire/app/views/layouts/guest.html.erb +1 -1
- data/stubs/hotwire/app/views/profile/partials/_delete_user_form.html.erb +1 -1
- 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/set_current_auth.rb +1 -1
- data/stubs/inertia-common/app/controllers/concerns/validate_signature.rb +17 -0
- data/stubs/inertia-common/app/controllers/password_controller.rb +1 -1
- data/stubs/inertia-common/app/controllers/profile_controller.rb +2 -2
- data/stubs/inertia-common/test/integration/password_update_test.rb +2 -2
- data/stubs/inertia-common/test/integration/profile_test.rb +4 -4
- data/stubs/inertia-react-ts/app/javascript/Components/ApplicationLogo.tsx +2 -9
- 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 +15 -53
- data/stubs/inertia-react-ts/app/javascript/Layouts/GuestLayout.tsx +1 -1
- data/stubs/inertia-react-ts/app/javascript/Pages/Auth/ForgotPassword.tsx +3 -6
- data/stubs/inertia-react-ts/app/javascript/Pages/Auth/Login.tsx +9 -23
- data/stubs/inertia-react-ts/app/javascript/Pages/Auth/Register.tsx +1 -4
- data/stubs/inertia-react-ts/app/javascript/Pages/Auth/ResetPassword.tsx +1 -4
- 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 +1 -5
- data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Partials/DeleteUserForm.tsx +7 -19
- data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Partials/UpdatePasswordForm.tsx +7 -15
- data/stubs/inertia-react-ts/app/javascript/Pages/Profile/Partials/UpdateProfileInformationForm.tsx +7 -16
- 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/config/tailwind.config.js +1 -6
- data/stubs/inertia-vue-ts/app/javascript/Components/ApplicationLogo.vue +4 -9
- 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 +3 -7
- 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 +1 -4
- 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 +3 -13
- data/stubs/inertia-vue-ts/app/javascript/Pages/Welcome.vue +2 -5
- data/stubs/inertia-vue-ts/config/tailwind.config.js +1 -6
- metadata +15 -4
- data/MIT-LICENSE +0 -20
- data/stubs/default/app/models/auth.rb +0 -57
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 53f603a627b691ff3fee85613cd44327e85799c85c3b75557ee10c9757dba9e7
|
|
4
|
+
data.tar.gz: ca7677bad4b8446b1288a1d3a7d4db40edb6a77f851140a37b86e3607223c63d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 979b1c79b3e9c447a4cf5adf3dfb1661b166f603114ce2e107c61719180c8a8102d2f5614de513ecef3b5f8847664f4618ea8e5bc6689f624d2646b76596ae3e
|
|
7
|
+
data.tar.gz: 4cb564c4b6ffd71e46adae28a2aeb903774bf7bd30db4c5375a0e649a91f77b4fd24ef8ddd79856513ef80d5b5f7b8c9e0f8aed8752cd9c637a65c589289a95b
|
data/README.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
<p align="center"><img src="/art/logo.svg" alt="Logo Kaze"></p>
|
|
2
|
+
|
|
1
3
|
# Kaze
|
|
2
4
|
|
|
3
5
|
Heavily inspired by [Laravel Breeze](https://github.com/laravel/breeze), this gem offers authentication and application starter kits to give you a head start building your new Rails application. These kits automatically scaffold your application with the routes, controllers, and views you need to register and authenticate your application's users.
|
|
@@ -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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
class Auth::LoginForm < ApplicationForm
|
|
2
|
-
attr_accessor :email, :password
|
|
2
|
+
attr_accessor :email, :password, :remember
|
|
3
3
|
|
|
4
4
|
validates :email, presence: true, email: true
|
|
5
5
|
validates :password, presence: true
|
|
@@ -7,6 +7,6 @@ class Auth::LoginForm < ApplicationForm
|
|
|
7
7
|
def authenticate
|
|
8
8
|
return if invalid?
|
|
9
9
|
|
|
10
|
-
errors.add(:email, message: 'These credentials do not match our records.') unless Current.auth.attempt(email: email, password: password)
|
|
10
|
+
errors.add(:email, message: 'These credentials do not match our records.') unless Current.auth.attempt({ email: @email, password: @password }, @remember)
|
|
11
11
|
end
|
|
12
12
|
end
|
|
@@ -7,14 +7,14 @@ class Auth::NewPasswordForm < ApplicationForm
|
|
|
7
7
|
def reset?
|
|
8
8
|
return false if invalid?
|
|
9
9
|
|
|
10
|
-
user = User.find_by_token_for(:password_reset, token)
|
|
10
|
+
user = User.find_by_token_for(:password_reset, @token)
|
|
11
11
|
|
|
12
12
|
if user.nil?
|
|
13
13
|
errors.add(:password, message: 'This password reset token is invalid.')
|
|
14
14
|
return false
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
user.update(password: password)
|
|
17
|
+
user.update(password: @password)
|
|
18
18
|
|
|
19
19
|
true
|
|
20
20
|
end
|
|
@@ -6,16 +6,14 @@ class Auth::SendPasswordResetLinkForm < ApplicationForm
|
|
|
6
6
|
def send_reset_link?
|
|
7
7
|
return false if invalid?
|
|
8
8
|
|
|
9
|
-
user = User.find_by(email: email)
|
|
9
|
+
user = User.find_by(email: @email)
|
|
10
10
|
|
|
11
11
|
if user.nil?
|
|
12
12
|
errors.add(:email, message: "We can't find a user with that email address.")
|
|
13
13
|
return false
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
user.send_password_reset_notification(token)
|
|
16
|
+
user.send_password_reset_notification(user.generate_token_for(:password_reset))
|
|
19
17
|
|
|
20
18
|
true
|
|
21
19
|
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
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
class SessionGuard
|
|
2
|
+
attr_reader :name
|
|
3
|
+
attr_reader :last_attempted
|
|
4
|
+
attr_reader :via_remember
|
|
5
|
+
attr_reader :remember_duration
|
|
6
|
+
attr_reader :session
|
|
7
|
+
attr_reader :cookies
|
|
8
|
+
attr_reader :logged_out
|
|
9
|
+
attr_reader :recall_attempted
|
|
10
|
+
attr_reader :user
|
|
11
|
+
|
|
12
|
+
def initialize(name:, session:, cookies: nil)
|
|
13
|
+
@name = name
|
|
14
|
+
@session = session
|
|
15
|
+
@cookies = cookies
|
|
16
|
+
@via_remember = false
|
|
17
|
+
@remember_duration = 576000
|
|
18
|
+
@logged_out = false
|
|
19
|
+
@recall_attempted = false
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def get_user
|
|
23
|
+
return nil if @logged_out
|
|
24
|
+
|
|
25
|
+
return @user unless @user.nil?
|
|
26
|
+
|
|
27
|
+
id = @session[get_name]
|
|
28
|
+
|
|
29
|
+
@user = User.find_by(id: id) unless id.nil?
|
|
30
|
+
|
|
31
|
+
if @user.nil?
|
|
32
|
+
@user = user_from_recaller
|
|
33
|
+
|
|
34
|
+
update_session(@user.id) if @user
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
@user
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def attempt(credentials = {}, remember = false)
|
|
41
|
+
@last_attempted = user = User.authenticate_by(credentials)
|
|
42
|
+
|
|
43
|
+
return false if user.nil?
|
|
44
|
+
|
|
45
|
+
login(user, remember)
|
|
46
|
+
|
|
47
|
+
true
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def login(user, remember = false)
|
|
51
|
+
update_session(user.id)
|
|
52
|
+
|
|
53
|
+
if remember
|
|
54
|
+
ensure_remember_token_is_set(user)
|
|
55
|
+
create_recaller_cookie(user)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
set_user(user)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def logout
|
|
62
|
+
user = get_user
|
|
63
|
+
|
|
64
|
+
clear_user_data_from_storage
|
|
65
|
+
|
|
66
|
+
cycle_remember_token(user) unless @user.nil? || user.remember_token.blank?
|
|
67
|
+
|
|
68
|
+
@user = nil
|
|
69
|
+
|
|
70
|
+
@logged_out = true
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def set_user(user)
|
|
74
|
+
@user = user
|
|
75
|
+
@logged_out = false
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def check?
|
|
79
|
+
!get_user.nil?
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def user_from_recaller
|
|
85
|
+
_recaller = recaller
|
|
86
|
+
|
|
87
|
+
return nil if _recaller.nil? || !_recaller.valid? || @recall_attempted
|
|
88
|
+
|
|
89
|
+
@recall_attempted = true
|
|
90
|
+
|
|
91
|
+
user = User.find_by(id: _recaller.id, remember_token: _recaller.token)
|
|
92
|
+
|
|
93
|
+
@via_remember = true unless user.nil?
|
|
94
|
+
|
|
95
|
+
user
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def recaller
|
|
99
|
+
return nil if @cookies.nil?
|
|
100
|
+
|
|
101
|
+
_recaller = @cookies.signed[get_recaller_name]
|
|
102
|
+
|
|
103
|
+
_recaller ? Recaller.new(_recaller) : nil
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def update_session(id)
|
|
107
|
+
@session[get_name] = id
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def ensure_remember_token_is_set(user)
|
|
111
|
+
cycle_remember_token(user) if user.remember_token.blank?
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def create_recaller_cookie(user)
|
|
115
|
+
@cookies.signed[get_recaller_name] = {
|
|
116
|
+
value: "#{user.id}|#{user.remember_token}|#{user.password_digest}",
|
|
117
|
+
expires: @remember_duration.minutes.from_now
|
|
118
|
+
}
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def clear_user_data_from_storage
|
|
122
|
+
@session.delete(get_name)
|
|
123
|
+
@cookies.delete(get_recaller_name) unless @cookies.nil?
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def cycle_remember_token(user)
|
|
127
|
+
user.update(remember_token: SecureRandom.alphanumeric(60))
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def get_name
|
|
131
|
+
"login_#{@name}_#{Digest::SHA1.hexdigest(self.class.name)}".to_sym
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def get_recaller_name
|
|
135
|
+
"remember_#{@name}_#{Digest::SHA1.hexdigest(self.class.name)}".to_sym
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
class Recaller
|
|
140
|
+
attr_reader :recaller
|
|
141
|
+
|
|
142
|
+
def initialize(recaller)
|
|
143
|
+
@recaller = recaller.split('|')
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def id
|
|
147
|
+
@recaller[0]
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def token
|
|
151
|
+
@recaller[1]
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def valid?
|
|
155
|
+
@recaller.length >= 3 && @recaller[0].present? && @recaller[1].present?
|
|
156
|
+
end
|
|
157
|
+
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 'email/verification-notification', 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
|
|
@@ -3,7 +3,9 @@ class CreateUsers < ActiveRecord::Migration
|
|
|
3
3
|
create_table :users do |table|
|
|
4
4
|
table.string :name
|
|
5
5
|
table.string :email
|
|
6
|
+
table.timestamp :email_verified_at, null: true
|
|
6
7
|
table.string :password_digest
|
|
8
|
+
table.string :remember_token, null: true
|
|
7
9
|
table.timestamps
|
|
8
10
|
end
|
|
9
11
|
end
|
|
@@ -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',
|
|
@@ -26,7 +26,7 @@ class ProfileTest < ActionDispatch::IntegrationTest
|
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
test 'user can delete their account' do
|
|
29
|
-
user = FactoryBot.create
|
|
29
|
+
user = FactoryBot.create(:user)
|
|
30
30
|
|
|
31
31
|
acting_as(user).delete profile_destroy_path, params: {
|
|
32
32
|
password: 'password'
|
|
@@ -39,7 +39,7 @@ class ProfileTest < ActionDispatch::IntegrationTest
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
test 'correct password must be provided to delete account' do
|
|
42
|
-
user = FactoryBot.create
|
|
42
|
+
user = FactoryBot.create(:user)
|
|
43
43
|
|
|
44
44
|
acting_as(user).delete profile_destroy_path, params: {
|
|
45
45
|
password: 'wrong-password'
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
class ApplicationLogoComponent < ViewComponent::Base
|
|
2
2
|
erb_template <<~ERB
|
|
3
|
-
<svg viewBox="0
|
|
4
|
-
<
|
|
5
|
-
<path d="M0-6h32v32H0z"/>
|
|
6
|
-
<path fill="#c00" fill-rule="nonzero" d="M.985 19.636s.422-4.163 3.375-9.087c2.954-4.924 7.99-8.65 12.083-9.017 8.144-.816 15.46 6.485 15.46 6.485s-.24.168-.494.38C23.42 2.49 18.54 5.274 17.005 6.02c-7.033 3.925-4.91 13.616-4.91 13.616H.987zM24.137 2.32c-.45-.182-.9-.35-1.364-.505l.056-.93c.885.254 1.237.423 1.363.493l-.056.943zM22.8 5.304c.45.028.915.084 1.393.183l-.056.872c-.464-.1-.928-.155-1.392-.17l.056-.885zM17.597.913c-.407 0-.815.015-1.223.058l-.268-.83c.465-.056.915-.084 1.35-.084l.282.858h-.14zm.676 5.178c.35-.154.76-.31 1.237-.45l.31.93c-.41.125-.817.294-1.225.49l-.323-.97zm-6.386-3.7c-.366.184-.718.395-1.083.62l-.647-.985c.38-.225.745-.42 1.097-.604l.633.97zm2.883 6.33c.252-.323.548-.646.87-.942l.634.957c-.31.323-.59.647-.83 1L14.77 8.72zm-2.04 4.53c.112-.506.24-1.027.422-1.547l1.012.802c-.14.548-.24 1.097-.295 1.645l-1.14-.9zM6.57 6.57c-.34.35-.662.73-.958 1.11L4.53 6.752c.323-.352.674-.704 1.04-1.055l1 .872zm-4.25 6.286c-.224.52-.52 1.21-.702 1.688L0 13.954c.14-.38.436-1.084.703-1.69l1.618.592zm10.2 3.967l1.518.548c.084.663.21 1.28.337 1.83l-1.688-.605c-.07-.422-.14-1.027-.168-1.772z"/>
|
|
7
|
-
</g>
|
|
3
|
+
<svg viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg" <%= sanitize @attributes.join(" ") %>>
|
|
4
|
+
<path d="m 237.03985,281.43515 c -1.54085,-2.49315 -1.43016,-32.97159 0.24592,-67.72985 3.02813,-62.79557 2.98467,-63.24086 -6.84044,-70.12264 -5.43834,-3.80913 -16.32202,-6.92574 -24.18594,-6.92574 -22.00734,0 -30.84257,-6.63121 -30.84257,-23.14872 0,-18.460367 5.31742,-22.209117 35.7331,-25.191397 27.97004,-2.74248 30.5663,-4.04226 26.94175,-13.48771 -2.14986,-5.6024 -13.90805,-6.59952 -77.8227,-6.59952 -69.400535,0 -75.177944,-0.58548 -73.8551,-7.48424 1.284851,-6.700534 9.490461,-7.708169 78.36353,-9.622595 42.31063,-1.176095 74.04161,-3.44104 70.51333,-5.033217 -11.28272,-5.091508 -103.7232,-12.917843 -134.65799,-11.40068 l -29.878628,1.465415 -3.171696,17.106837 c -1.744436,9.40876 -4.948521,63.009007 -7.120182,119.111597 -2.277699,58.84196 -5.815243,103.87156 -8.360116,106.41647 -2.4264,2.42639 -7.676475,3.15877 -11.666833,1.62754 -5.877786,-2.25554 -6.747202,-6.22009 -4.579445,-20.88223 1.471667,-9.954 3.74014,-63.32438 5.041047,-118.60085 2.647925,-112.511911 3.2489,-117.857541 13.890324,-123.552663 4.513472,-2.415528 45.033525,-3.067128 100.341569,-1.613517 109.0299,2.86548 110.09891,3.263428 110.09891,40.985371 0,30.923222 -7.28516,39.906492 -35.24013,43.454229 -29.06687,3.68892 -37.46391,6.17185 -37.46391,11.07783 0,2.19365 11.40965,3.98851 25.35478,3.98851 37.65833,0 38.79584,2.06051 38.79584,70.27428 0,31.25825 -1.20855,65.65073 -2.68573,76.42778 -2.59506,18.93304 -11.18137,28.79145 -16.94872,19.45971 z M 102.84208,259.48743 c -5.809371,-2.9396 -13.184926,-10.12819 -16.390123,-15.97462 -8.396565,-15.3158 2.912296,-41.49358 19.494463,-45.12587 20.60401,-4.51326 22.42665,-5.69589 22.42665,-14.55198 0,-6.8018 -2.78507,-8.68766 -12.83013,-8.68766 -16.253558,0 -29.936969,-13.44405 -29.936969,-29.41328 0,-14.42513 5.553783,-19.82093 24.591079,-23.89154 9.82484,-2.1008 13.8993,-5.37975 13.8993,-11.1856 0,-6.50902 -3.05408,-8.21363 -14.71608,-8.21363 -16.019022,0 -24.105594,-7.285117 -14.699611,-13.242787 3.241585,-2.05321 12.812471,-3.76252 21.268621,-3.79858 19.92576,-0.0847 30.60952,11.25947 28.76224,30.540617 -1.22046,12.73879 -3.23695,14.76612 -18.47681,18.57607 -30.922304,7.73057 -36.377959,19.24518 -9.11843,19.24518 22.57064,0 29.1179,7.65172 27.56665,32.21701 l -1.34139,21.24186 -20.81946,5.13821 c -11.45071,2.826 -21.79219,6.1109 -22.981063,7.29978 -4.590664,4.59067 3.475793,15.68791 14.430943,19.85304 16.19851,6.15868 88.0503,3.43548 90.32313,-3.42324 1.28741,-3.88501 -5.33813,-5.34589 -24.24513,-5.34589 -19.38669,0 -26.01664,-1.51819 -26.01664,-5.9575 0,-8.34429 9.63998,-11.14934 38.3165,-11.14934 28.67934,0 35.63045,5.45011 33.61018,26.35253 -0.96086,9.94142 -4.50427,15.66293 -12.05276,19.46151 -14.27136,7.1817 -96.89032,7.20826 -111.06516,0.0342 z m 57.07409,-60.66299 c -4.41463,-11.5043 3.01548,-16.23905 21.50632,-13.70463 20.26375,2.77747 30.64103,-1.74032 26.8271,-11.67922 -1.8078,-4.7111 -8.00515,-6.84671 -19.86838,-6.84671 -19.48707,0 -22.72529,-6.68 -6.27087,-12.93598 25.01034,-9.50892 44.62703,0.19588 44.62703,22.07791 0,17.75258 -8.58709,23.84253 -37.57444,26.6478 -21.20481,2.05209 -27.38199,1.30033 -29.24676,-3.55917 z" />
|
|
8
5
|
</svg>
|
|
9
6
|
ERB
|
|
10
7
|
|