From ab0007651bcc9b26056ba86581fa8767d688aa0c Mon Sep 17 00:00:00 2001
From: Trevor Vallender
Date: Sun, 14 Apr 2024 20:01:32 +0100
Subject: [PATCH] Email verification workflow
---
.../account_verifications_controller.rb | 13 ++++
app/controllers/users_controller.rb | 31 ++++++++++
app/helpers/mailer_helper.rb | 6 ++
app/mailers/user_mailer.rb | 16 +++++
app/models/user.rb | 8 ++-
app/views/layouts/application.html.erb | 8 ++-
app/views/layouts/mailer.html.erb | 2 +
app/views/layouts/mailer.text.erb | 4 ++
app/views/shared/_flash_messages.html.erb | 5 ++
.../user_mailer/email_verification.html.erb | 4 ++
.../user_mailer/email_verification.text.erb | 3 +
app/views/user_mailer/email_verified.html.erb | 1 +
app/views/user_mailer/email_verified.text.erb | 1 +
app/views/users/_form.html.erb | 23 +++++++
app/views/users/new.html.erb | 5 ++
config/environments/development.rb | 2 +-
config/environments/test.rb | 2 +-
config/locales/en.yml | 62 ++++++++++---------
config/routes.rb | 11 ++--
.../20240414122652_add_verified_to_user.rb | 6 ++
db/schema.rb | 4 +-
.../account_verifications_controller_test.rb | 14 +++++
test/controllers/users_controller_test.rb | 40 ++++++++++++
test/fixtures/users.yml | 21 +++++--
test/helpers/mailer_helper_test.rb | 23 +++++++
test/mailers/previews/user_mailer_preview.rb | 10 +++
test/models/user_test.rb | 8 +++
27 files changed, 286 insertions(+), 47 deletions(-)
create mode 100644 app/controllers/account_verifications_controller.rb
create mode 100644 app/controllers/users_controller.rb
create mode 100644 app/helpers/mailer_helper.rb
create mode 100644 app/mailers/user_mailer.rb
create mode 100644 app/views/shared/_flash_messages.html.erb
create mode 100644 app/views/user_mailer/email_verification.html.erb
create mode 100644 app/views/user_mailer/email_verification.text.erb
create mode 100644 app/views/user_mailer/email_verified.html.erb
create mode 100644 app/views/user_mailer/email_verified.text.erb
create mode 100644 app/views/users/_form.html.erb
create mode 100644 app/views/users/new.html.erb
create mode 100644 db/migrate/20240414122652_add_verified_to_user.rb
create mode 100644 test/controllers/account_verifications_controller_test.rb
create mode 100644 test/controllers/users_controller_test.rb
create mode 100644 test/helpers/mailer_helper_test.rb
create mode 100644 test/mailers/previews/user_mailer_preview.rb
diff --git a/app/controllers/account_verifications_controller.rb b/app/controllers/account_verifications_controller.rb
new file mode 100644
index 0000000..17c4fb0
--- /dev/null
+++ b/app/controllers/account_verifications_controller.rb
@@ -0,0 +1,13 @@
+class AccountVerificationsController < ApplicationController
+ def show
+ user = User.find_by_token_for(:email_verification, params[:id])
+ unless user
+ flash[:alert] = t(".error")
+ redirect_to :root and return
+ end
+ user.update(verified: true)
+ UserMailer.with(user: user).email_verified.deliver_later
+ flash[:notice] = t(".success")
+ redirect_to :root # TODO: New session path
+ end
+end
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
new file mode 100644
index 0000000..d8f90c3
--- /dev/null
+++ b/app/controllers/users_controller.rb
@@ -0,0 +1,31 @@
+class UsersController < ApplicationController
+ def new
+ @user = User.new
+ end
+
+ def create
+ @user = User.new(user_params)
+ if @user.save
+ token = @user.generate_token_for(:email_verification)
+ UserMailer.with(user: @user, token: token).email_verification.deliver_later
+ flash[:notice] = t(".success", name: @user.first_name)
+ redirect_to :root
+ else
+ flash[:alert] = t(".error", error: @user.errors.full_messages.to_sentence)
+ render :new, status: :unprocessable_entity
+ end
+ end
+
+ private
+
+ def user_params
+ params.require(:user).permit(
+ :username,
+ :password,
+ :password_confirmation,
+ :email,
+ :first_name,
+ :last_name,
+ )
+ end
+end
diff --git a/app/helpers/mailer_helper.rb b/app/helpers/mailer_helper.rb
new file mode 100644
index 0000000..5f01347
--- /dev/null
+++ b/app/helpers/mailer_helper.rb
@@ -0,0 +1,6 @@
+module MailerHelper
+ def htmlify_email(content)
+ content.gsub!("\n\n", "
\n\n")
+ "
#{content.chomp}
".html_safe
+ end
+end
diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb
new file mode 100644
index 0000000..7ed3d14
--- /dev/null
+++ b/app/mailers/user_mailer.rb
@@ -0,0 +1,16 @@
+class UserMailer < ApplicationMailer
+ helper(:mailer)
+
+ def email_verification
+ @user = params[:user]
+ @token = params[:token]
+
+ mail(to: @user.email, subject: "[Forg] Verify your email")
+ end
+
+ def email_verified
+ @user = params[:user]
+
+ mail(to: @user.email, subject: "[Forg] Your email has been verified")
+ end
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index 2e96f49..32ca2d1 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -3,6 +3,9 @@ class User < ApplicationRecord
generates_token_for :password_reset, expires_in: 4.hours do
password_salt.last(10) # Invalidates when password changed
end
+ generates_token_for :email_verification, expires_in: 1.week do
+ verified.to_s
+ end
validates :username,
presence: true,
@@ -14,7 +17,7 @@ class User < ApplicationRecord
uniqueness: true,
length: { minimum: 5, maximum: 100 },
format: { with: URI::MailTo::EMAIL_REGEXP,
- message: "must be a valid email address",
+ message: I18n.t("users.validations.email_format"),
}
normalizes :email, with: ->(email) { email.strip.downcase }
validates :first_name,
@@ -24,6 +27,9 @@ class User < ApplicationRecord
allow_nil: false,
length: { maximum: 50 }
+ scope :verified, -> { where(verified: true) }
+ scope :unverified, -> { where(verified: false) }
+
def full_name
return first_name if last_name.blank?
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index 255a84d..a7c1096 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -1,16 +1,22 @@
- Forg
+ <%= "#{yield(:title)} | " if content_for? :title %><%= t("forg") %>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
+
+
+
+ <%= link_to t("forg"), root_path %>
+
+ <%= render partial: "shared/flash_messages" %>
<%= yield %>
diff --git a/app/views/layouts/mailer.html.erb b/app/views/layouts/mailer.html.erb
index 3aac900..533e85a 100644
--- a/app/views/layouts/mailer.html.erb
+++ b/app/views/layouts/mailer.html.erb
@@ -8,6 +8,8 @@
+ <%= t(".greeting", name: @user.first_name) %>
<%= yield %>
+ <%= t(".sign_off_html") %>