我正在构建 Rails API 应用程序,我正在使用设计无密码库。
如果我向用户发送魔法链接,而用户等待太久才来到我的应用程序,我如何验证使用的令牌是否有效并生成/发送新令牌?
我正在寻找类似的东西:
User.find_by(
email: magic_link_request_params[:email],
old_token: magic_link_request_params[:old_token] )
.send_magic_link(true)
可以肯定的是,用户之前已经被授权的管理员/经理邀请过。如果是这样,我希望能够重新发送魔术链接,否则我不想让用户进入我的应用程序,因此仅通过电子邮件查找他是不够的。
默认情况下,Devise 不会在任何地方保存令牌。
它编码和解码令牌而不依赖于用户记录。
它也没有方法 just 生成令牌 without 通过电子邮件发送它,因此您将对这个 gem 进行相当多的修改以获得您的行为。
首先,您需要迁移以将字段添加到您的用户对象。在我的示例中,我使用
last_magic_link
和 last_magic_link_sent_at
如果您愿意编写自己的模块并
prepend
ing 它们,最干净的解决方案是创建一个 initializer 更改 send_magic_link
以在生成令牌和发送电子邮件之间更新您的 User
记录发送:
# /config/initializers/devise/passwordless/mailer_override.rb
# from: https://github.com/abevoelker/devise-passwordless/blob/master/lib/devise/passwordless/mailer.rb#L6
# we need `require "devise/passwordless"` and `require "devise/mailer"`
# I believe we get them all by requiring all of "devise"
require "devise"
# a custom module to override Devise::Passwordless::Mailer#magic_link
# NOTE: I pasted this untested and untried code from SO, I should check to make sure this works as intended
module MailerAndRecordUpdater
def magic_link(record, token, remember_me, opts = {})
@token = token
@remember_me = remember_me
devise_mail(record, :magic_link, opts)
# record is a User record
record.update(last_magic_link: @token, last_magic_link_sent_at: Time.now)
end
end
[Devise::Passwordless::Mailer, Devise::Passwordless::Mailer.singleton_class].each do |mod|
mod.prepend MailerAndRecordUpdater
end
after_magic_link_authentication
的方法帮了您一个忙,一旦他们成功登录,您就可以使用该方法从 User
中删除这些记录属性:
class User < ApplicationRecord
def after_magic_link_authentication
# intentionally leaving :last_magic_link_sent_at for history
update(last_magic_link: nil)
end
end
现在您将在生成和使用令牌时保存和删除令牌。
但是您仍然有您的主要用例:如果令牌和电子邮件已过期,请查看它是否匹配。
不幸的是,gem 引发了一个非常广泛的错误:
InvalidOrExpiredTokenError
并且它没有告诉你实际情况是什么:无效或过期。
Devise::Passwordless::LoginToken#decrypt
上写一个类似的覆盖,如果令牌过期则向raise InvalidOrExpiredTokenError
添加一条消息:
created_at = ActiveSupport::TimeZone["UTC"].at(decrypted_data["created_at"])
if as_of.to_f > (created_at + expire_duration).to_f
raise InvalidOrExpiredTokenError, "expired" #<- custom message
end
然后,在
Devise::Passwordless::MagicLinksController
(你必须ask Devise生成)你可以从那个错误中解救出来:
def show
begin
self.resource = warden.authenticate!(auth_options)
rescue InvalidOrExpiredTokenError -> error
# check to see if the error's message is "expired"
# check to see if the token matches the user record
# then do what you want if both of the above are true
end
set_flash_message!(:notice, :signed_in)
sign_in(resource_name, resource)
yield resource if block_given?
redirect_to after_sign_in_path_for(resource)
end