[RUBY] [Rails] How to set a password for posts, url direct hit measures have been taken

How to set a password for a post

I added a password function to the post, so I wrote an article for that output.

Goal of this article

Make it possible to set a password as shown in the image below.

post_with_password.gif

Create a posting function

Create a quick posting function. If you use scaffold, you can make it in 1 minute.

$ rails new post_with_password $ cd post_with_password $ rails g scaffold Post description:text $ rails db:migrate

投稿機能.gif

Set bcrypt

Flow from user posting with password to jumping in detail

  1. Set a password when posting (posts / new)
  2. Request password when opening details page (posts / show /: id) (if password is available)
  3. Redirect to detail page if id and password match

Use bcrypt (gem) to set a password as a preliminary preparation.

The following gem is commented out in the Gemfile, so delete #. gem 'bcrypt', '~> 3.1.7'

$ bundle install

Then add a sentence to the model.

ruby:_form.html.erb


class Post < ApplicationRecord
  has_secure_password
end

$ rails g migration add_password_digest_to_posts password_digest:string $ rails db:migrate

Added has_secure_password to the model, By adding the password_digest column Two attributes will be available: password and password_confirmation.

1. Set a password when posting

Let's set the password for the main subject.

First, let the user set a password in view.

post.rb


(abridgement)
  <div class="field">
    <%= form.label :description %>
    <%= form.text_area :description %>
  </div>
  
  <div class="field">
    <%= form.label :password %>
    <%= form.password_field :password %>
  </div>

  <div class="actions">
    <%= form.submit %>
  </div>
<% end %>

Then add: password to the strong parameter.

posts_controller.rb


  private
    # Use callbacks to share common setup or constraints between actions.
    def set_post
      @post = Post.find(params[:id])
    end

    # Only allow a list of trusted parameters through.
    def post_params
      params.require(:post).permit(:description, :password)
    end
end

You can now save your password in the db.

2. Require password to open details page

The next time you go to the details page, ask for a password, and if it matches the password you set, redirect to the details page.

Create a page to authenticate your password.

ruby:posts_with_password.html.erb


<div class="users-new-wrapper">
  <div class="container">
    <div class="row">
      <div class="col-md-offset-4 col-md-4 users-new-container">
        <h1 class="text-center text-white">password</h1>
        <%= form_for(:post, {controller: 'posts', action: "posts_with_password/#{@post}" }) do |f| %>
          <div class="form-group">
            <%= f.label :password %>
            <%= f.password_field :password, class: 'form-control' %>
          </div>
            <%= f.submit "Send", class: 'btn-block' %>
        <% end %>
      </div>
    </div>
  </div>
</div>

Also add the routing.

qiita.rb


Rails.application.routes.draw do
  resources :posts
  get       'posts_with_password/:id', to: 'posts#posts_with_password'
  post      'posts_with_password/:id', to: 'posts#authenticate'
end

Redirect to detail page if passwords match

Finally, create the authentication function.

posts_controller.rb


(abridgement)
 def authenticate
    post_id = Post.find(params[:id])
    if post_id && post_id.authenticate(params[:post][:password])
      redirect_to post_path(post_id)
    else
      render 'posts_with_password'
    end
  end

  def posts_with_pass

If the post and password match, you will now be redirected to the details page.

That's it for yesterday.

However, in this case, it is necessary to set a password for all posts, so setting the password is optional.

Since the has_secure_password set by bcrypt will be played by empty posts, Add the following so that you can post even in the sky.

post.rb


class Post < ApplicationRecord
  has_secure_password(validations: false)
end

When passwaord_digest contains a password and when it does not Change the link destination of the detail page.

ruby:index.hetml.erb


(abridgement)
<% @posts.each do |post| %>
  <tr>
    <td><%= post.description %></td>
    <td><%= link_to 'Show', post_path(post) %></td>
    <td><%= link_to 'Edit', edit_post_path(post) %></td>
    <td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td>
  </tr>
<% end %>

However, with this, even if you set a password, you can see the Description from the index.

スクリーンショット 2020-07-13 14.02.37.jpeg

Change the display of Description depending on the presence or absence of password_digest.

ruby:index.hetml.erb


<% @posts.each do |post| %>
  <tr>
    <% if post.password_digest.nil? %>
      <td><%= post.description %></td>
    <% else %>
      <td>secret</td>
    <% end %>
    <td><%= link_to 'Show', post_path(post) %></td>
    <td><%= link_to 'Edit', edit_post_path(post) %></td>
    <td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td>
  </tr>
<% end %>

If password_digest is empty, it will be displayed as usual, and if it is not empty, it will be displayed as "secret".

Restrict access

Finally, access is restricted and it is completed.

If you do not have a password when you click show, set the details page to jump to the password input form if you have a password.

It also solves the problem that the password can be broken by entering the url directly.

The mechanism is as follows.

  1. Check if there is a password for posts / show /: id

  2. If there is no password, posts / show /: id will be displayed as it is

  3. If you have a password, redirect to posts / show /: id if the url contains the value of password_digest If not included, redirect to password entry form

  4. The explanation needs explanation. If you do not have a password, you can type in the url directly to display the details page, and there is no problem. The problem is that if you type the url directly into a post with a password, the details page will be displayed.

For the time being, "Let's redirect the post with password to the password input form with access restrictions" I'm addicted to thinking short-circuited. (I was addicted to it)

You will not be able to open the details page for the rest of your life using the above method. This is because the details page has a password regardless of whether you authenticate using the password input form.

My idea was to send the parameters to redirect_to when the password was successfully authenticated. If the url contains that parameter, it is a way to display the detail page.

You can also send parameters to redirect_to. This time I will send password_digest.

There are two reasons.

  1. Because it is easy to take out
  2. Because it is highly safe

I thought about sending random alphanumeric characters in the parameters every time, but I gave up because it was troublesome to take them out.

Also, it is highly secure if password_digest is included in the url, but it can be broken through. The values are 60 digits uppercase, lowercase, numbers and symbols, so it can be quite difficult.

I'm sorry to explain it for a long time, but I will write the code from the next.

First, set the parameter to redirect_to.

posts_controller.rb


def authenticate
    post_id = Post.find(params[:id])
    if post_id && post_id.authenticate(params[:post][:password])
here-> redirect_to post_path(post_id, parameter: post_id.password_digest)
    else
      render 'index'
    end
  end

After password authentication, parameters will be added and the url will change.

Next, access is restricted by the presence or absence of a password.

posts_controller.rb


before_action :with_password, only: :show

(abridgement)
private
  def with_password
    url = request.url.gsub!(/%21|%22|%23|%24|%24|%25|%26|%27|%28|%29|%2A|%2B|%2C|%2F|%3A|%3B|%3C|%3D|%3E|%3F|%40|%5B|%5D|%5E|%60|%7B|%7C|%7D|%7E|/,
    "%21" => "!", "%22" => '"', "%23" => "#", "%24" => "$", "%25" => "%", "%26" => "&", "%27" => "'", "%28" => "(", "%29" => ")",
    "%2A" => "*", "%2B" => "+", "%2C" => ",", "%2F" => "/", "%3A" => ":", "%3B" => ";", "%3C" => "<", "%3D" => "=", "%3E" => ">", "%3F" => "?", "%40" => "@",
    "%5B" => "[", "%5D" => "]", "%5E" => "^", "%60" => "`", "%7B" => "{", "%7C" => "|", "%7D" => "}", "%7E" => "~")
    @post = Post.find(params[:id])
    if [email protected]_digest.nil? && !url.try(:include?, @post.password_digest)
      redirect_to "/posts_with_password/#{@post.id}", danger: 'Please enter your password'
    end
  end

I will explain in order from the top. url = request.url First, get the url with the above code.

I got hooked here when I was writing the code. Even if I got the url as it is, it was not authenticated.

When comparing the contents of url and password_digest using byebug, some characters such as $ are It was different. After investigating, it seems that there is a character string that can not be used in url, so it became necessary to encode this.

I couldn't find a way to convert it in one shot, so I forcibly changed it using gsub. (If there is any other good way to write it, please let me know.)

I referred to the following information. https://www.seil.jp/doc/index.html#tool/url-encode.html

This completes the acquisition of the url.

This is the final finish. Explain the code below and you're done.

if [email protected]_digest.nil? && !url.try(:include?, @post.password_digest)

If you try to open the details page with a password and the url does not contain password_digest Redirect to the password entry form.

If you do not use try, an error will occur if you do not have a password, so I put it in.

Summary

That's it.

Please use bcrypt other than login.

I hope it will be helpful for your portfolio.

Recommended Posts

[Rails] How to set a password for posts, url direct hit measures have been taken
[Rails] How to create a signed URL for CloudFront
How to prevent direct URL typing in Rails
[Rails 6] How to set a background image in Rails [CSS]
Measures for taking a long time to load images (Rails)
How to build a Ruby on Rails environment using Docker (for Docker beginners)
[Docker] How to create a virtual environment for Rails and Nuxt.js apps
How to insert a video in Rails
How to create a Maven repository for 2020
[rails] How to create a partial template
Needed for iOS 14? How to set NSUserTrackingUsageDescription
Rails: How to write a rake task nicely
How to create a database for H2 Database anywhere
Convert to a tag to URL string in Rails
[Rails] How to write when making a subquery
[Rails] How to create a graph using lazy_high_charts
How to create pagination for a "kaminari" array
[Rails] How to implement unit tests for models
How to make a lightweight JRE for distribution
[Java] (for MacOS) How to set the classpath
How to implement a like feature in Rails
How to easily create a pull-down in Rails
[Rails] How to create a Twitter share button
[Rails] How to use Gem'rails-i18n' for Japanese support
How to make a follow function in Rails
[Ruby on Rails] How to implement tagging / incremental search function for posts (without gem)