[Ruby] [Rails] A method to set a password for posts, url direct hit countermeasures completed

6 minute read

How to set a password for a post

I added a password feature to my post so I wrote an article for that output.

Goal of this article

You can set the password as shown in the image below.

post_with_password.gif

Create a posting function

Create a quick posting function. It can be made in 1 minute using scaffold.

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

Post function.gif

set bcrypt

The flow from the user posting a password and jumping to the details

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

Use bcrypt(gem) to set the password in advance.

The following gem has been commented out in the Gemfile, so delete #. gem’bcrypt’,’~> 3.1.7’

$ bundle install

Then add one 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

Add has_secure_password to the model, By adding password_digest column, Two attributes are available, password and password_confirmation.

*If the password and password_confirmation are the same on the left, It will be encrypted and stored in password_digest.

1. Set password when posting

Let’s set the main password.

First, let’s allow the user to 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 parameters.

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 the password in db.

2. Require password to open detail page

Next, request the password when displaying the detail page, and if it matches the set password, redirect to the detail page.

Create a page to authenticate the 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 "submit", class:'btn-block' %>
        <% end %>
      </div>
    </div>
  </div>
</div>

Also add 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 password matches

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 be redirected to the details page.

That’s it for yesterday.

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

Since has_secure_password set with bcrypt will empty posts, Add the following so that you can post even if it is empty.

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.

Screenshot 2020-07-13 14.02.37.jpeg

Change the display of Description depending on the presence/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 %>

When password_digest is empty, it is set as usual, and when it is not empty, “secret” is displayed.

Restrict access

Finally, access is restricted and it is completed.

If you do not have a password when you click show, it will be set to jump to the detail page, and if there is a password, it will jump to the password input form.

We will also solve the problem that the password can be broken if you enter the url directly.

The mechanism is as follows.

  1. Check if there is a password for posts/show/:id
  2. Display posts/show/:id without password
  3. If there is a password, redirect to posts/show/:id if the url contains the password_digest value If not included, redirect to password entry form

  4. The explanation is necessary for the explanation. If you do not have a password, you can directly enter the url and the details page will be displayed, so there is no problem.The problem is that if you type the url directly into a post with a password, the detail page will be displayed.

For the time being, “Post with password will be redirected to password input form with access restriction” If you think about it as a short circuit, you will be addicted. (I’m addicted)

You cannot open the detail page for the rest of the time with the above method. This is because the password is attached to the detail page even if you try to authenticate with the password input form.

What I thought was, when the password was successfully authenticated, send the parameters along with redirect_to It is a method to display the detail page when the url includes the parameter.

Parameters can also be sent to redirect_to. This time I will send password_digest.

There are two reasons.

  1. Easy to take out
  2. High safety

I thought about putting random alphanumeric characters in the parameter each time I sent it, but I gave up taking out ~~ I do not understand ~~~.

Also, it is highly safe that you can break it if password_digest is included in the url, The value is 60 digits of capital letters, lower case letters, numbers and symbols, so it will be quite difficult.

Excuse me for the lengthy explanation, but I will write the code from the next.

First, set the parameter in 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

With this, after password authentication, the parameter is added and the url changes.

Next, access is restricted depending on 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 !@post.password_digest.nil? && !url.try(:include?, @post.password_digest)
      redirect_to "/posts_with_password/#{@post.id}", danger:'Please enter password'
    end
  end

I will explain from top to bottom. url = request.url First get the url with the above code.

I got hooked here while writing the code. I was not authenticated even if I got the url as it was.

When using ebug and comparing the contents of url and password_digest, some characters such as $ It was different. When I looked it up, there seems to be a character string that cannot be used in the url, and I needed to encode this.

I couldn’t find a way to convert it in one shot, so I forced to change it using gsub. (If you have any other good writing method, please let me know.)

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

The url acquisition is now complete.

This is the final finish. It is completed by explaining the code below.

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

If you try to open the detail page with a password and url does not include password_digest, Redirect to the password input form.

It is included because an error will occur if there is no password if try is not used.

Summary

That’s it!

Please use bcrypt other than login.

I would be happy if it could be helpful as a portfolio.

Tags: ,

Updated: