1

更新

我已经进一步澄清了我的问题,列在这篇文章的末尾。

问题总结:

我正在尝试通过包含令牌身份验证的电子邮件 URL 在 Devise 中实现惰性(又名软注册)注册。在我的网站上,aUser has_many :payments和 a Payment belongs_to :user。当 aUser创建一个 new时,它包含代表一个新的非注册用户(我称之为“Guest”)Payment的属性。:email我使用 ActionMailer 向这位新访客发送电子邮件。

在发送的这封电子邮件中,我想包含一个带有令牌身份验证的 URL(例如http://localhost/index?auth_token=TOKENVALUE),以便访客可以查看和编辑需要身份验证的视图并专门为其定制(基于他们的:email)。客人还应该能够注册为User- 因为我已经有了他们的电子邮件地址,他们只需要提供密码。

到目前为止我的进展:

  • 我已经实现了某人使用 Devise 注册网站的能力,并创建了相关的视图、模型和控制器来更改我的Payment模型
  • 我已经设置了 ActionMailer 并正在使用它来发送电子邮件,但还没有设置令牌身份验证,因为鉴于上面的用例,我不确定如何设置

相关资源:

/app/models/payment.rb

class Payment < ActiveRecord::Base
  attr_accessible :amount, :description, :email, :frequency, :paid, :user_id
  belongs_to :user

  validates :email, :presence => true, :format => { :with => /.+@.+\..+/i }
  validates :amount, :presence => true
  validates :description, :presence => true
end

/app/models/user.rb

class User < ActiveRecord::Base
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable, :confirmable

  # I will also need to add ":token_authenticatable"

  attr_accessible :email, :password, :password_confirmation, :remember_me
  has_many :payments
end

问题:

  1. 如何使我的表与创建User的新表保持同步,以便表中创建的Payments任何新表都会自动添加到表中?:emailPaymentUser:email
  2. 从问题 1 开始,新User创建的应包含令牌但不包含密码,因为用户尚未注册该站点。我应该User使用空白密码、随机生成的字符串还是其他方式创建新密码?在任何一种情况下,我认为他们都需要从他们的令牌认证 URL 进入注册页面,以防止其他人使用他们的电子邮件用户名注册(例如懒惰注册)
  3. 从问题 2 开始,我认为我需要区分访客和普通用户,因为某些视图会根据用户类型而变化。除了添加一个具有 0 或 1 的列来划分两种用户类型之外,还有其他首选方法吗?

如果可能的话,我的首选是使用 Devise,因为我正在使用其中的许多功能。我是 RoR 的新手,感谢您提供的任何建议!

编辑:这是我用来解决上述问题 #1 的代码,在我的支付控制器中,以防对其他人有帮助

def create
    @payment = current_user.payments.build(params[:payment])

    #Here is the code I added to keep User :email in sync with Payment :email, without token authentication implemented yet
    unless User.find_by_email(params[:payment][:email].downcase)  
      u = User.new({:email => params[:payment][:email].downcase, :password => nil, :password_confirmation => nil })
      u.skip_confirmation!
      u.save(:validate => false)  #skip validation
    end

    respond_to do |format|
      if @payment.save
        format.html { redirect_to payments_url, :flash => { notice: 'Payment was successfully created.' } }
        format.json { render json: @payment, status: :created, location: @payment }
      else
        format.html { render action: "new" }
        format.json { render json: @payment.errors, status: :unprocessable_entity }
      end
    end
  end
4

1 回答 1

2

当你知道如何很好地使用它们并且你想做的事情符合框架的设计目标时,像 Devise 这样的框架就很棒。但是,当您开始尝试“弯曲”它们以做他们不打算做的事情时,也许试图在不完全了解它们内部工作原理的情况下对它们进行修补,以我的经验,您只会弄得一团糟。我去过那儿。通常,您会被简化为“破解直到它起作用”......这实际上意味着“破解直到它看起来起作用,除了后来才出现的十几个微妙的错误”。

在这种情况下,我倾向于只“滚动我自己的”登录和身份验证代码,或许可以查看 Devise 源代码以获取想法。

(事实上​​,我今天在一个新的 Rails 应用程序上就是这样做的……花了几个小时,一次又一次地检查 Devise 源代码,以编写我的项目实际需要的 200 行代码。自行设计,相比之下,大约有 2500 行。)

您询问了有关代码“整体策略和结构”的想法。如果您决定实现自己的自定义登录/身份验证代码,它可能会是这样的:

  1. 在哈希中存储一些信息session以识别用户是否登录,以及作为哪个访客/用户。确保您的会话存储在数据库中,而不是 cookie 中。
  2. 添加一些方法到ApplicationController(或Module混合到ApplicationController)以封装对此登录信息的访问。
  3. 在所有需要登录的页面上放置一个before_filter,如果用户未登录,它将重定向。
  4. 添加一个控制器,通过密码或令牌对用户进行身份验证,并允许他们登录/注销。
  5. 对于密码存储/身份验证,请使用bcrypt-rubygem。在您的用户模型上,您需要一个password_hash字符串字段,并且可能需要一些方法,例如:

    require 'bcrypt'
    def password
      # BCrypt handles generation and storage of salts
      @password ||= ::BCrypt::Password.new(password_hash)
    end
    def password=(password)
      @password = ::BCrypt::Password.create(password)
      self.password_hash = @password
    end
    def authenticate_by_password(password)
      self.password == password
    end
    
  6. 对于基于令牌的身份验证,将一个login_token字符串字段添加到您的用户模型(或访客或任何它......)。让它成为一个独特的领域。您可以使用类似这样的方法生成唯一令牌(从 Devise 借用并稍作修改):

    require 'securerandom'
    def generate_login_token
      loop do
        token = SecureRandom.base64(15).tr('+/=lIO0', 'pqrsxyz')
        unless User.where(:login_token => token).exists?
          self.login_token = token
          self.save!
          return
        end
      end
    end
    
  7. 然后,当用户login使用 set 执行您的操作时params[:token],只需执行以下操作:

    if user = User.find_by_login_token(params[:token])
      # if the token is supposed to be "one time only", null out the attribute
      # log user in
    else
      # login token was invalid
    end
    

login_token确保在字段上放置数据库索引。

还有别的事吗?

于 2013-06-01T19:36:00.787 回答