简体   繁体   English

Devise 忘记登录用户的密码

[英]Devise Forgot Password for logged in user

I'm wondering if there is a manner of calling the 'forgot password' procedure without forcing my user to log out我想知道是否有一种方法可以在不强制我的用户注销的情况下调用“忘记密码”程序

The case I'm running into is:我遇到的情况是:

  1. a user logs in with Facebook, a fake password is generated for them用户使用 Facebook 登录,为他们生成一个假密码
  2. the user then wants to change their email/name/password, or just use non-facebook login然后用户想更改他们的电子邮件/姓名/密码,或者只使用非 Facebook 登录

since devise requires a password to change these fields, as it should, the user is unable to modify them由于 devise 需要密码才能更改这些字段,因此用户无法修改它们

I had thought about just not forcing the password to be set but that doesn't make sense to security wise so instead I just display the fields as text and notify the user to follow the 'forgot password' procedure in order to set a password and then they can change the fields我曾考虑过不强制设置密码,但这对安全性没有意义,所以我只是将字段显示为文本并通知用户按照“忘记密码”程序来设置密码和然后他们可以更改字段

The issue then is that I cannot simply link to this from the user profile since devise will tell the user that they can't do this while already logged in.那么问题是我不能简单地从用户个人资料链接到这个,因为 devise 会告诉用户他们不能在已经登录的情况下这样做。

So is there a manner of overriding the forgot password or /users/password/edit method so that a logged-in user can perform this action as well?那么有没有一种方法可以覆盖忘记密码或 /users/password/edit 方法,以便登录用户也可以执行此操作?

The reason that you cannot reset password is because the devise tries to authenticate the user with the current session and when succeeded you are automatically redirected to whatever path it is supposed to go to. 您无法重置密码的原因是设备尝试使用当前会话对用户进行身份验证,并且当成功时,您将自动重定向到它应该去的任何路径。 What you need is to override the edit and update action of passwords controller to make it skip this step. 您需要的是覆盖密码控制器的编辑和更新操作,使其跳过此步骤。

Here's the code. 这是代码。 In your passwords controller add the following codes (you can ask devise to generate the controllers for you, or you can just create the following controller). 在您的密码控制器中添加以下代码(您可以请设计为您生成控制器,或者您可以创建以下控制器)。 The override for update is necessary because otherwise a logged in user will be automatically signout after your reset password. 更新覆盖是必要的,否则登录用户将在重置密码后自动注销。 (Or if you want it to be like that you can get rid of the #update override) (或者,如果你希望它像你那样可以摆脱#update覆盖)

class PasswordsController < Devise::PasswordsController
  # here we need to skip the automatic authentication based on current session for the following two actions
  # edit: shows the reset password form. need to skip, otherwise it will go directly to root
  # update: updates the password, need to skip otherwise it won't even reset if already logged in
  skip_before_filter :require_no_authentication, :only => [:edit, :update]

  # we need to override the update, too.
  # After a password is reset, all outstanding sessions are gone.
  # When already logged in, sign_in is a no op, so the session will expire, too.
  # The solution is to logout and then re-login which will make the session right.
  def update
    super
    if resource.errors.empty?
      sign_out(resource_name)
      sign_in(resource_name, resource)
    end
  end
end

The routes are like the following 路线如下

# config/routes.rb
devise_for :users, :controllers => {:passwords => 'passwords'}

You can use the @user.send_reset_password_instructions to generate the password reset token and send the email. 您可以使用@user.send_reset_password_instructions生成密码重置令牌并发送电子邮件。 If you just call the mailer directly, a password reset token won't be generated to authenticate the reset. 如果您只是直接调用邮件程序,则不会生成密码重置令牌来验证重置。

My complete solution here, because I then also learned that the user would have to log out after clicking the link in the email, was to add an some additional UserController actions for actually editing the password as well as saving it. 我在这里完整的解决方案,因为我还了解到用户在点击电子邮件中的链接后必须注销,就是添加一些额外的UserController操作来实际编辑密码并保存密码。 This is not an ideal solution and cold probably be done in a better manner but it works for me. 这不是一个理想的解决方案,冷却可能以更好的方式完成,但它对我有用。

users controller; 用户控制器; added methods to do the reset 添加了重置方法

    before_filter :authenticate_user!, :except => [:do_reset_password, :reset_password_edit]

    def reset_password
        id = params[:id]
        if id.nil?
          id = current_user.id
        end    
        if (!user_signed_in? || current_user.id.to_s != id.to_s)
        flash[:alert] = "You don't have that right." 
          redirect_to '/home'
          return
        end

        @user = User.find(id)
        @user.send_reset_password_instructions

        respond_to do |format|
            format.html { redirect_to '/users/edit', notice: 'You will receive an email with instructions about how to reset your password in a few minutes.' }
        end
     end


    def do_reset_password
        id = params[:id]
        if id.nil? && !current_user.nil?
          id = current_user.id
        end

        if id.nil?
            @user = User.where(:reset_password_token => params[:user][:reset_password_token]).first
        else
            @user = User.find(id)
        end
        if  @user.nil? || @user.reset_password_token.to_s != params[:user][:reset_password_token]
          flash[:alert] = "Url to reset was incorrect, please resend reset email." 
          redirect_to '/home'
          return
        end

        # there may be a better way of doing this, devise should be able to give us these messages
        if params[:user][:password] != params[:user][:password_confirmation]
            flash[:alert] = "Passwords must match." 
              redirect_to :back
              return
        end
        if @user.reset_password!(params[:user][:password],params[:user][:password_confirmation])
            @user.hasSetPassword = true
            @user.save
            respond_to do |format|
                format.html { redirect_to '/home', notice: 'Your password has been changed.' }
            end
        else
            flash[:alert] = "Invalid password, must be at least 6 charactors." 
              redirect_to :back 
        end
    end

    def reset_password_edit
        @user = User.where(:reset_password_token => params[:reset_password_token]).first
        if  @user.nil? || !@user.reset_password_period_valid?
            flash[:alert] = "Password reset period expired, please resend reset email" 
            redirect_to "/home"
            return
        end
    end

views/devise/registrations/edit; 意见/设计/注册/编辑; changed the view to not let the user edit fields that require a password 将视图更改为不让用户编辑需要密码的字段

    <%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), :html => { :method => :put }) do |f| %>
      <%= devise_error_messages! %>

      <% if !resource.hasSetPassword %>                                           
          <%= f.label :name %><br />
          <p style="line-height:24px;"><b><%= @user.name %></b></p>             
          <div><%= f.label :email %><br />
              <p style="line-height:24px;"><b><%= @user.email %> </b></p>
              <p style="position:relative; left:150px; width:420px;">
                <i>you cannot change any settings because you have not set a password <br />yet, you can do so by following the </i>
                <%= link_to "Forgot your password", "/users/reset_password" %> <i> procedure</i>
              </p>
          </div>
      <% else %>                      
          <p><%= f.label :name %><br />
          <%= f.text_field :name %></p>         
          <div><%= f.label :email %><br />
          <%= f.email_field :email %></div>

          <div><%= f.label :password %> <br />
          <%= f.password_field :password %><i>(leave blank if you don't want to change it)</i></div>

          <div><%= f.label :password_confirmation %><br />
          <%= f.password_field :password_confirmation %></div>

          <div><%= f.label :current_password %> <br />
          <%= f.password_field :current_password %>
          <i>(we need your current password to confirm your changes)</i>
          </div>
        <div><%= f.submit "Update" %></div>
      <% end %>
    <% end %>

views/devise/mailer/reset_password_instructions; 视图/设计/邮寄者/ reset_password_instructions; had to change it to point to the right URL in our new case 在我们的新案例中,我必须将其更改为指向正确的URL

    <p>Hello <%= @resource.email %>!</p>

    <p>Someone has requested a link to change your password, and you can do this through the link below.</p>

    <% if !@resource.hasSetPassword %>
        <p><%= link_to 'Change my password', 'http://streetsbehind.me/users/reset_password_edit?reset_password_token='+@resource.reset_password_token %></p>
    <!-- todo: there's probably a better way of doing this than just hardcoding streetsbehind.me -->
    <% else %>
        <p><%= link_to 'Change my password', edit_password_url(@resource, :reset_password_token => @resource.reset_password_token) %></p>
    <% end %>
    <p>If you didn't request this, please ignore this email.</p>
    <p>Your password won't change until you access the link above and create a new one.</p>

views/users/reset_password_edit.erb 意见/用户/ reset_password_edit.erb

<%= form_for(@user, :url => url_for(:action => :do_reset_password) , :html => { :method => :post }) do |f| %>

  <%= f.hidden_field :reset_password_token %>

  <div><%= f.label :password, "New password" %><br />
  <%= f.password_field :password %></div>

  <div><%= f.label :password_confirmation, "Confirm new password" %><br />
  <%= f.password_field :password_confirmation %></div>

  <div><%= f.submit "Change my password" %></div>
<% end %>

config/routes.rb 配置/ routes.rb中

get "users/reset_password"
get "users/reset_password_edit"

resource :users do
  post 'do_reset_password'
end

I adapted @user3294438's answer to make it work perfectly for me.我改编了@user3294438 的答案以使其完美适合我。

class PasswordsController < Devise::PasswordsController
  # here we need to skip the automatic authentication based on current session for the following four actions

  # new : shows the "enter email to reset". need to skip, otherwise it will go directly to root
  # create : launches the reset email
  # edit: shows the reset password form. need to skip, otherwise it will go directly to root
  # update: updates the password, need to skip otherwise it won't even reset if already logged in
  skip_before_action :require_no_authentication, :only => [:new, :create, :edit, :update]

  # we need to override the update, too.
  # After a password is reset, all outstanding sessions are gone.
  # When already logged in, sign_in is a no op, so the session will expire, too.
  # The solution is to logout and then re-login which will make the session right.
  def update
    super
    if resource.errors.empty?
      sign_out(resource_name)
      sign_in(resource_name, resource)
    end
  end

  private

  # Overriding this method allows to show a nice flash message to the signed-in user that just 
  # asked for a password reset by email. Otherwise he gets a "you are already signed in" falsh error
  def after_sending_reset_password_instructions_path_for(resource_name)
    if current_user
      flash[:info] = I18n.t "devise.passwords.send_instructions"
      return root_path
    end

    super
  end
end

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM