You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

基于Clearance gem构建「更新邮箱」功能是否有规范实现方案?

Standard Implementation for Email Updates with Clearance Gem

Great question! While Clearance doesn’t include a pre-built email update feature out of the box, there’s a secure, idiomatic way to implement it that fits right into Clearance’s lightweight, extensible design—no need to reinvent the wheel. Here’s a step-by-step breakdown of the standard pattern:

1. Add Routes

First, define routes for editing the user’s profile, submitting the email update request, and handling email confirmation:

# config/routes.rb
Rails.application.routes.draw do
  # Keep your existing Clearance routes
  resources :users, only: [:edit] do
    patch :update_email, on: :member
    get :confirm_email, on: :member, param: :token
  end
end

2. Update the User Model

Add fields to track email confirmation status. Start with a migration:

rails generate migration AddEmailConfirmationFieldsToUsers email_confirmed:boolean email_confirmation_token:string
rails db:migrate

Then update the model to set defaults and add validations:

# app/models/user.rb
class User < ApplicationRecord
  include Clearance::User

  validates :email, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP }
  after_initialize :set_default_email_confirmed

  private

  def set_default_email_confirmed
    self.email_confirmed ||= false
  end
end

3. Build the Controller Logic

Create or extend a UsersController to handle the email update flow, including critical security checks:

# app/controllers/users_controller.rb
class UsersController < ApplicationController
  before_action :require_login

  def edit
    @user = current_user
  end

  def update_email
    @user = current_user

    # Verify current password to prevent session hijacking
    if @user.authenticate(params[:current_password])
      new_email = params[:user][:email].strip.downcase

      if @user.email != new_email && @user.update(
        email: new_email,
        email_confirmed: false,
        email_confirmation_token: SecureRandom.urlsafe_base64
      )
        # Send confirmation to the new email
        UserMailer.email_confirmation(@user).deliver_now
        redirect_to edit_user_path, notice: "A confirmation link has been sent to your new email. Please verify it to complete the update."
      else
        flash[:alert] = "Failed to update email. Check that the address is valid and not already in use."
        render :edit
      end
    else
      flash[:alert] = "Current password is incorrect."
      render :edit
    end
  end

  def confirm_email
    @user = User.find_by(id: params[:id], email_confirmation_token: params[:token])

    if @user && !@user.email_confirmed
      @user.update!(email_confirmed: true, email_confirmation_token: nil)
      # Optional: Sign out to force fresh authentication with new email
      sign_out
      redirect_to sign_in_path, notice: "Your email has been updated! Please sign in with your new address."
    else
      redirect_to root_path, alert: "Invalid or expired confirmation link."
    end
  end
end

4. Create the Edit Profile View

Build a form for users to enter their current password and new email:

<!-- app/views/users/edit.html.erb -->
<h2>Update Your Account</h2>

<div class="card">
  <div class="card-body">
    <% if flash[:alert] %>
      <div class="alert alert-danger mb-3"><%= flash[:alert] %></div>
    <% end %>
    <% if flash[:notice] %>
      <div class="alert alert-success mb-3"><%= flash[:notice] %></div>
    <% end %>

    <%= form_with(model: @user, url: update_email_user_path(@user), method: :patch) do |form| %>
      <div class="mb-3">
        <%= form.label :current_password, "Current Password (required to confirm changes)", class: "form-label" %>
        <%= form.password_field :current_password, class: "form-control", required: true %>
      </div>

      <div class="mb-3">
        <%= form.label :email, "New Email Address", class: "form-label" %>
        <%= form.email_field :email, class: "form-control", value: @user.email, required: true %>
      </div>

      <%= form.submit "Update Email", class: "btn btn-primary" %>
    <% end %>
  </div>
</div>

5. Set Up the Confirmation Email

Create a mailer to send the confirmation link:

# app/mailers/user_mailer.rb
class UserMailer < ApplicationMailer
  default from: "your-app@example.com"

  def email_confirmation(user)
    @user = user
    @confirmation_url = confirm_email_user_url(user, token: user.email_confirmation_token)

    mail(to: user.email, subject: "Confirm Your New Email Address")
  end
end

And the corresponding email template:

<!-- app/views/user_mailer/email_confirmation.html.erb -->
<h1>Confirm Your New Email</h1>
<p>Hi there,</p>
<p>Click the link below to confirm your new email address for your account:</p>
<p><%= link_to "Confirm Email Address", @confirmation_url %></p>
<p>If you didn't request this change, please ignore this email—your account will remain unchanged.</p>

Key Security & Usability Notes

  • Mandatory Password Check: Verifying the current password blocks attackers who’ve hijacked a user’s session from changing their email.
  • Email Confirmation: Requiring confirmation prevents lockouts from typos and blocks malicious email changes.
  • Session Invalidation: Optionally signing the user out after confirmation forces fresh authentication with the new email, adding extra security.
  • Email Normalization: Stripping whitespace and downcasing emails avoids duplicate accounts from case variations.

This implementation aligns with Clearance’s philosophy—keeping core auth logic minimal while letting you extend functionality safely without overriding internals.

内容的提问来源于stack exchange,提问作者hoffm

火山引擎 最新活动